Add IndexInput isLoaded (#13998)

This commit adds IndexInput::isLoaded to help determine if the contents of an input is resident in physical memory.

The intent of this new method is to help build inspection and diagnostic infrastructure on top.
This commit is contained in:
Chris Hegarty 2024-11-29 10:28:32 +00:00 committed by GitHub
parent 98c59a710e
commit 7dbbd0daa9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 92 additions and 0 deletions

View File

@ -18,6 +18,7 @@ package org.apache.lucene.store;
import java.io.Closeable;
import java.io.IOException;
import java.util.Optional;
import org.apache.lucene.codecs.CompoundFormat;
/**
@ -234,4 +235,19 @@ public abstract class IndexInput extends DataInput implements Closeable {
* <p>The default implementation is a no-op.
*/
public void updateReadAdvice(ReadAdvice readAdvice) throws IOException {}
/**
* Returns a hint whether all the contents of this input are resident in physical memory. It's a
* hint because the operating system may have paged out some of the data by the time this method
* returns. If the optional is true, then it's likely that the contents of this input are resident
* in physical memory. A value of false does not imply that the contents are not resident in
* physical memory. An empty optional is returned if it is not possible to determine.
*
* <p>This runs in linear time with the {@link #length()} of this input / page size.
*
* <p>The default implementation returns an empty optional.
*/
public Optional<Boolean> isLoaded() {
return Optional.empty();
}
}

View File

@ -17,6 +17,7 @@
package org.apache.lucene.store;
import java.io.IOException;
import java.util.Optional;
import org.apache.lucene.util.BitUtil; // javadocs
/**
@ -77,4 +78,13 @@ public interface RandomAccessInput {
* @see IndexInput#prefetch
*/
default void prefetch(long offset, long length) throws IOException {}
/**
* Returns a hint whether all the contents of this input are resident in physical memory.
*
* @see IndexInput#isLoaded()
*/
default Optional<Boolean> isLoaded() {
return Optional.empty();
}
}

View File

@ -420,6 +420,16 @@ abstract class MemorySegmentIndexInput extends IndexInput
}
}
@Override
public Optional<Boolean> isLoaded() {
for (MemorySegment seg : segments) {
if (seg.isLoaded() == false) {
return Optional.of(Boolean.FALSE);
}
}
return Optional.of(Boolean.TRUE);
}
@Override
public byte readByte(long pos) throws IOException {
try {

View File

@ -51,9 +51,11 @@ import org.apache.lucene.store.AlreadyClosedException;
import org.apache.lucene.store.ChecksumIndexInput;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.FSDirectory;
import org.apache.lucene.store.FilterDirectory;
import org.apache.lucene.store.IOContext;
import org.apache.lucene.store.IndexInput;
import org.apache.lucene.store.IndexOutput;
import org.apache.lucene.store.MMapDirectory;
import org.apache.lucene.store.RandomAccessInput;
import org.apache.lucene.store.ReadAdvice;
import org.apache.lucene.tests.mockfile.ExtrasFS;
@ -1636,4 +1638,44 @@ public abstract class BaseDirectoryTestCase extends LuceneTestCase {
}
}
}
public void testIsLoaded() throws IOException {
testIsLoaded(0);
}
public void testIsLoadedOnSlice() throws IOException {
testIsLoaded(TestUtil.nextInt(random(), 1, 1024));
}
private void testIsLoaded(int startOffset) throws IOException {
try (Directory dir = getDirectory(createTempDir())) {
if (FilterDirectory.unwrap(dir) instanceof MMapDirectory mMapDirectory) {
mMapDirectory.setPreload(MMapDirectory.ALL_FILES);
}
final int totalLength = startOffset + TestUtil.nextInt(random(), 16384, 65536);
byte[] arr = new byte[totalLength];
random().nextBytes(arr);
try (IndexOutput out = dir.createOutput("temp.bin", IOContext.DEFAULT)) {
out.writeBytes(arr, arr.length);
}
try (IndexInput orig = dir.openInput("temp.bin", IOContext.DEFAULT)) {
IndexInput in;
if (startOffset == 0) {
in = orig.clone();
} else {
in = orig.slice("slice", startOffset, totalLength - startOffset);
}
var loaded = in.isLoaded();
if (FilterDirectory.unwrap(dir) instanceof MMapDirectory
// direct IO wraps MMap but does not support isLoaded
&& !(dir.getClass().getName().contains("DirectIO"))) {
assertTrue(loaded.isPresent());
assertTrue(loaded.get());
} else {
assertFalse(loaded.isPresent());
}
}
}
}
}

View File

@ -19,6 +19,7 @@ package org.apache.lucene.tests.store;
import java.io.Closeable;
import java.io.IOException;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import org.apache.lucene.internal.tests.TestSecrets;
import org.apache.lucene.store.FilterIndexInput;
@ -184,6 +185,13 @@ public class MockIndexInputWrapper extends FilterIndexInput {
in.prefetch(offset, length);
}
@Override
public Optional<Boolean> isLoaded() {
ensureOpen();
ensureAccessible();
return in.isLoaded();
}
@Override
public void updateReadAdvice(ReadAdvice readAdvice) throws IOException {
ensureOpen();

View File

@ -17,6 +17,7 @@
package org.apache.lucene.tests.store;
import java.io.IOException;
import java.util.Optional;
import java.util.concurrent.atomic.LongAdder;
import org.apache.lucene.internal.hppc.LongHashSet;
import org.apache.lucene.store.ChecksumIndexInput;
@ -206,5 +207,10 @@ public class SerialIOCountingDirectory extends FilterDirectory {
IndexInput clone = in.clone();
return new SerializedIOCountingIndexInput(clone, readAdvice, sliceOffset, sliceLength);
}
@Override
public Optional<Boolean> isLoaded() {
return in.isLoaded();
}
}
}