5151import java .util .concurrent .ExecutionException ;
5252import java .util .concurrent .ExecutorService ;
5353import java .util .concurrent .TimeUnit ;
54+ import java .util .concurrent .atomic .AtomicBoolean ;
5455import java .util .concurrent .atomic .AtomicLong ;
56+ import java .util .concurrent .atomic .LongAdder ;
5557import java .util .concurrent .locks .ReentrantReadWriteLock ;
58+ import java .util .function .LongSupplier ;
5659
5760/**
5861 * This is a cache for {@link BitSet} instances that are used with the {@link DocumentSubsetReader}.
@@ -122,18 +125,27 @@ public final class DocumentSubsetBitsetCache implements IndexReader.ClosedListen
122125 private final Cache <BitsetCacheKey , BitSet > bitsetCache ;
123126 private final Map <IndexReader .CacheKey , Set <BitsetCacheKey >> keysByIndex ;
124127 private final AtomicLong cacheFullWarningTime ;
128+ private final LongAdder hitsTimeInNanos ;
129+ private final LongAdder missesTimeInNanos ;
130+ private final LongSupplier relativeNanoTimeProvider ;
125131
126132 public DocumentSubsetBitsetCache (Settings settings , ThreadPool threadPool ) {
127133 this (settings , threadPool .executor (ThreadPool .Names .GENERIC ));
128134 }
129135
136+ // visible for testing
137+ DocumentSubsetBitsetCache (Settings settings , ExecutorService cleanupExecutor ) {
138+ this (settings , cleanupExecutor , System ::nanoTime );
139+ }
140+
130141 /**
131142 * @param settings The global settings object for this node
132143 * @param cleanupExecutor An executor on which the cache cleanup tasks can be run. Due to the way the cache is structured internally,
133144 * it is sometimes necessary to run an asynchronous task to synchronize the internal state.
145+ * @param relativeNanoTimeProvider Provider of nanos for code that needs to measure relative time.
134146 */
135147 // visible for testing
136- DocumentSubsetBitsetCache (Settings settings , ExecutorService cleanupExecutor ) {
148+ DocumentSubsetBitsetCache (Settings settings , ExecutorService cleanupExecutor , LongSupplier relativeNanoTimeProvider ) {
137149 final ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock ();
138150 this .cacheEvictionLock = new ReleasableLock (readWriteLock .writeLock ());
139151 this .cacheModificationLock = new ReleasableLock (readWriteLock .readLock ());
@@ -150,6 +162,9 @@ public DocumentSubsetBitsetCache(Settings settings, ThreadPool threadPool) {
150162
151163 this .keysByIndex = new ConcurrentHashMap <>();
152164 this .cacheFullWarningTime = new AtomicLong (0 );
165+ this .hitsTimeInNanos = new LongAdder ();
166+ this .missesTimeInNanos = new LongAdder ();
167+ this .relativeNanoTimeProvider = Objects .requireNonNull (relativeNanoTimeProvider );
153168 }
154169
155170 @ Override
@@ -222,6 +237,8 @@ public long ramBytesUsed() {
222237 */
223238 @ Nullable
224239 public BitSet getBitSet (final Query query , final LeafReaderContext context ) throws ExecutionException {
240+ final long startTimeNs = relativeNanoTimeProvider .getAsLong ();
241+
225242 final IndexReader .CacheHelper coreCacheHelper = context .reader ().getCoreCacheHelper ();
226243 if (coreCacheHelper == null ) {
227244 try {
@@ -235,7 +252,9 @@ public BitSet getBitSet(final Query query, final LeafReaderContext context) thro
235252 final BitsetCacheKey cacheKey = new BitsetCacheKey (indexKey , query );
236253
237254 try (ReleasableLock ignored = cacheModificationLock .acquire ()) {
255+ final AtomicBoolean cacheKeyWasPresent = new AtomicBoolean (true );
238256 final BitSet bitSet = bitsetCache .computeIfAbsent (cacheKey , ignore1 -> {
257+ cacheKeyWasPresent .set (false );
239258 // This ensures all insertions into the set are guarded by ConcurrentHashMap's atomicity guarantees.
240259 keysByIndex .compute (indexKey , (ignore2 , set ) -> {
241260 if (set == null ) {
@@ -264,6 +283,11 @@ public BitSet getBitSet(final Query query, final LeafReaderContext context) thro
264283 }
265284 return result ;
266285 });
286+ if (cacheKeyWasPresent .get ()) {
287+ hitsTimeInNanos .add (relativeNanoTimeProvider .getAsLong () - startTimeNs );
288+ } else {
289+ missesTimeInNanos .add (relativeNanoTimeProvider .getAsLong () - startTimeNs );
290+ }
267291 if (bitSet == NULL_MARKER ) {
268292 return null ;
269293 } else {
@@ -331,6 +355,8 @@ public Map<String, Object> usageStats() {
331355 stats .put ("hits" , cacheStats .getHits ());
332356 stats .put ("misses" , cacheStats .getMisses ());
333357 stats .put ("evictions" , cacheStats .getEvictions ());
358+ stats .put ("hits_time_in_millis" , TimeValue .nsecToMSec (hitsTimeInNanos .sum ()));
359+ stats .put ("misses_time_in_millis" , TimeValue .nsecToMSec (missesTimeInNanos .sum ()));
334360 return Collections .unmodifiableMap (stats );
335361 }
336362
0 commit comments