diff --git a/rest-api-spec/src/yamlRestTest/resources/rest-api-spec/test/indices.stats/80_search_load.yml b/rest-api-spec/src/yamlRestTest/resources/rest-api-spec/test/indices.stats/80_search_load.yml new file mode 100644 index 0000000000000..a8c8fe46c9f34 --- /dev/null +++ b/rest-api-spec/src/yamlRestTest/resources/rest-api-spec/test/indices.stats/80_search_load.yml @@ -0,0 +1,52 @@ +--- +setup: + - requires: + cluster_features: ["mapper.search_load_per_shard"] + reason: Shard search load stats were introduced in 9.1 +--- +"Search load is tracked at shard level": + - do: + indices.create: + index: index + body: + mappings: + properties: + name: + type: text + description: + type: text + price: + type: double + + - do: + indices.stats: + index: "index" + level: shards + metric: [ search ] + + - match: { _all.total.search.recent_search_load: 0.0 } + - match: { indices.index.total.search.recent_search_load: 0.0 } + - match: { indices.index.shards.0.0.search.recent_search_load: 0.0 } + + - do: + index: + index: index + body: { "name": "specialty coffee", "description": "arabica coffee beans", "price": 100 } + + - do: + search: + index: index + body: + query: + match: { name: "specialty coffee" } + size: 1 + + - do: + indices.stats: + index: "index" + level: shards + metric: [ search ] + + - gte: { _all.total.search.recent_search_load: 0.0 } + - gte: { indices.index.total.search.recent_search_load: 0.0 } + - gte: { indices.index.shards.0.0.search.recent_search_load: 0.0 } diff --git a/server/src/internalClusterTest/java/org/elasticsearch/index/shard/IndexShardIT.java b/server/src/internalClusterTest/java/org/elasticsearch/index/shard/IndexShardIT.java index 507724bbaeb3c..11e8b15432d35 100644 --- a/server/src/internalClusterTest/java/org/elasticsearch/index/shard/IndexShardIT.java +++ b/server/src/internalClusterTest/java/org/elasticsearch/index/shard/IndexShardIT.java @@ -53,6 +53,7 @@ import org.elasticsearch.index.flush.FlushStats; import org.elasticsearch.index.mapper.MapperMetrics; import org.elasticsearch.index.mapper.SourceToParse; +import org.elasticsearch.index.search.stats.SearchStatsSettings; import org.elasticsearch.index.seqno.RetentionLeaseSyncer; import org.elasticsearch.index.seqno.SequenceNumbers; import org.elasticsearch.index.translog.TestTranslog; @@ -638,7 +639,8 @@ public static final IndexShard newIndexShard( System::nanoTime, null, MapperMetrics.NOOP, - new IndexingStatsSettings(ClusterSettings.createBuiltInClusterSettings()) + new IndexingStatsSettings(ClusterSettings.createBuiltInClusterSettings()), + new SearchStatsSettings(ClusterSettings.createBuiltInClusterSettings()) ); } diff --git a/server/src/main/java/org/elasticsearch/TransportVersions.java b/server/src/main/java/org/elasticsearch/TransportVersions.java index 669952c6026e7..bca87f8dd8750 100644 --- a/server/src/main/java/org/elasticsearch/TransportVersions.java +++ b/server/src/main/java/org/elasticsearch/TransportVersions.java @@ -292,6 +292,7 @@ static TransportVersion def(int id) { public static final TransportVersion SEARCH_SOURCE_EXCLUDE_VECTORS_PARAM = def(9_092_0_00); public static final TransportVersion SNAPSHOT_INDEX_SHARD_STATUS_MISSING_STATS = def(9_093_0_00); public static final TransportVersion ML_INFERENCE_ELASTIC_RERANK = def(9_094_0_00); + public static final TransportVersion SEARCH_LOAD_PER_INDEX_STATS = def(9_095_0_00); /* * STOP! READ THIS FIRST! No, really, diff --git a/server/src/main/java/org/elasticsearch/common/settings/ClusterSettings.java b/server/src/main/java/org/elasticsearch/common/settings/ClusterSettings.java index dea61e770b2f6..e9982b476c520 100644 --- a/server/src/main/java/org/elasticsearch/common/settings/ClusterSettings.java +++ b/server/src/main/java/org/elasticsearch/common/settings/ClusterSettings.java @@ -90,6 +90,7 @@ import org.elasticsearch.index.MergePolicyConfig; import org.elasticsearch.index.engine.ThreadPoolMergeExecutorService; import org.elasticsearch.index.engine.ThreadPoolMergeScheduler; +import org.elasticsearch.index.search.stats.SearchStatsSettings; import org.elasticsearch.index.shard.IndexingStatsSettings; import org.elasticsearch.indices.IndexingMemoryController; import org.elasticsearch.indices.IndicesQueryCache; @@ -640,6 +641,7 @@ public void apply(Settings value, Settings current, Settings previous) { ShardsAvailabilityHealthIndicatorService.REPLICA_UNASSIGNED_BUFFER_TIME, DataStreamFailureStoreSettings.DATA_STREAM_FAILURE_STORED_ENABLED_SETTING, IndexingStatsSettings.RECENT_WRITE_LOAD_HALF_LIFE_SETTING, + SearchStatsSettings.RECENT_READ_LOAD_HALF_LIFE_SETTING, TransportGetAllocationStatsAction.CACHE_TTL_SETTING ); } diff --git a/server/src/main/java/org/elasticsearch/index/IndexModule.java b/server/src/main/java/org/elasticsearch/index/IndexModule.java index 3418d8a9b7b2e..42410c6b8025e 100644 --- a/server/src/main/java/org/elasticsearch/index/IndexModule.java +++ b/server/src/main/java/org/elasticsearch/index/IndexModule.java @@ -48,6 +48,7 @@ import org.elasticsearch.index.mapper.MapperMetrics; import org.elasticsearch.index.mapper.MapperRegistry; import org.elasticsearch.index.mapper.MapperService; +import org.elasticsearch.index.search.stats.SearchStatsSettings; import org.elasticsearch.index.shard.IndexEventListener; import org.elasticsearch.index.shard.IndexingOperationListener; import org.elasticsearch.index.shard.IndexingStatsSettings; @@ -179,6 +180,7 @@ public interface DirectoryWrapper { private final SetOnce indexCommitListener = new SetOnce<>(); private final MapperMetrics mapperMetrics; private final IndexingStatsSettings indexingStatsSettings; + private final SearchStatsSettings searchStatsSettings; /** * Construct the index module for the index with the specified index settings. The index module contains extension points for plugins @@ -200,7 +202,8 @@ public IndexModule( final SlowLogFieldProvider slowLogFieldProvider, final MapperMetrics mapperMetrics, final List searchOperationListeners, - final IndexingStatsSettings indexingStatsSettings + final IndexingStatsSettings indexingStatsSettings, + final SearchStatsSettings searchStatsSettings ) { this.indexSettings = indexSettings; this.analysisRegistry = analysisRegistry; @@ -216,6 +219,7 @@ public IndexModule( this.recoveryStateFactories = recoveryStateFactories; this.mapperMetrics = mapperMetrics; this.indexingStatsSettings = indexingStatsSettings; + this.searchStatsSettings = searchStatsSettings; } /** @@ -552,7 +556,8 @@ public IndexService newIndexService( indexCommitListener.get(), mapperMetrics, queryRewriteInterceptor, - indexingStatsSettings + indexingStatsSettings, + searchStatsSettings ); success = true; return indexService; diff --git a/server/src/main/java/org/elasticsearch/index/IndexService.java b/server/src/main/java/org/elasticsearch/index/IndexService.java index d5c00294aa6b8..2c9616e0791e5 100644 --- a/server/src/main/java/org/elasticsearch/index/IndexService.java +++ b/server/src/main/java/org/elasticsearch/index/IndexService.java @@ -67,6 +67,7 @@ import org.elasticsearch.index.query.QueryRewriteContext; import org.elasticsearch.index.query.SearchExecutionContext; import org.elasticsearch.index.query.SearchIndexNameMatcher; +import org.elasticsearch.index.search.stats.SearchStatsSettings; import org.elasticsearch.index.seqno.RetentionLeaseSyncer; import org.elasticsearch.index.shard.GlobalCheckpointSyncer; import org.elasticsearch.index.shard.IndexEventListener; @@ -170,6 +171,7 @@ public class IndexService extends AbstractIndexComponent implements IndicesClust private final MapperMetrics mapperMetrics; private final QueryRewriteInterceptor queryRewriteInterceptor; private final IndexingStatsSettings indexingStatsSettings; + private final SearchStatsSettings searchStatsSettings; @SuppressWarnings("this-escape") public IndexService( @@ -207,7 +209,8 @@ public IndexService( Engine.IndexCommitListener indexCommitListener, MapperMetrics mapperMetrics, QueryRewriteInterceptor queryRewriteInterceptor, - IndexingStatsSettings indexingStatsSettings + IndexingStatsSettings indexingStatsSettings, + SearchStatsSettings searchStatsSettings ) { super(indexSettings); assert indexCreationContext != IndexCreationContext.RELOAD_ANALYZERS @@ -293,6 +296,7 @@ public IndexService( this.retentionLeaseSyncTask = new AsyncRetentionLeaseSyncTask(this); } this.indexingStatsSettings = indexingStatsSettings; + this.searchStatsSettings = searchStatsSettings; updateFsyncTaskIfNecessary(); } @@ -583,7 +587,8 @@ public synchronized IndexShard createShard( System::nanoTime, indexCommitListener, mapperMetrics, - indexingStatsSettings + indexingStatsSettings, + searchStatsSettings ); eventListener.indexShardStateChanged(indexShard, null, indexShard.state(), "shard created"); eventListener.afterIndexShardCreated(indexShard); diff --git a/server/src/main/java/org/elasticsearch/index/mapper/MapperFeatures.java b/server/src/main/java/org/elasticsearch/index/mapper/MapperFeatures.java index b54c7d787cfda..cd56f281aa953 100644 --- a/server/src/main/java/org/elasticsearch/index/mapper/MapperFeatures.java +++ b/server/src/main/java/org/elasticsearch/index/mapper/MapperFeatures.java @@ -43,6 +43,7 @@ public class MapperFeatures implements FeatureSpecification { static final NodeFeature NPE_ON_DIMS_UPDATE_FIX = new NodeFeature("mapper.npe_on_dims_update_fix"); static final NodeFeature IVF_FORMAT_CLUSTER_FEATURE = new NodeFeature("mapper.ivf_format_cluster_feature"); static final NodeFeature IVF_NESTED_SUPPORT = new NodeFeature("mapper.ivf_nested_support"); + static final NodeFeature SEARCH_LOAD_PER_SHARD = new NodeFeature("mapper.search_load_per_shard"); @Override public Set getTestFeatures() { @@ -72,7 +73,8 @@ public Set getTestFeatures() { RESCORE_ZERO_VECTOR_QUANTIZED_VECTOR_MAPPING, USE_DEFAULT_OVERSAMPLE_VALUE_FOR_BBQ, IVF_FORMAT_CLUSTER_FEATURE, - IVF_NESTED_SUPPORT + IVF_NESTED_SUPPORT, + SEARCH_LOAD_PER_SHARD ); } } diff --git a/server/src/main/java/org/elasticsearch/index/search/stats/SearchStats.java b/server/src/main/java/org/elasticsearch/index/search/stats/SearchStats.java index 8b19d72ccc09d..4d6247796af59 100644 --- a/server/src/main/java/org/elasticsearch/index/search/stats/SearchStats.java +++ b/server/src/main/java/org/elasticsearch/index/search/stats/SearchStats.java @@ -49,6 +49,10 @@ public static class Stats implements Writeable, ToXContentFragment { private long queryFailure; private long fetchFailure; + // This tracks the search execution time across different phases (e.g., query, fetch, etc.), favouring more recent + // values by assigning them greater significance than older values. + private double recentSearchLoad; + private Stats() { // for internal use, initializes all counts to 0 } @@ -67,7 +71,8 @@ public Stats( long scrollCurrent, long suggestCount, long suggestTimeInMillis, - long suggestCurrent + long suggestCurrent, + double recentSearchLoad ) { this.queryCount = queryCount; this.queryTimeInMillis = queryTimeInMillis; @@ -86,6 +91,9 @@ public Stats( this.suggestCount = suggestCount; this.suggestTimeInMillis = suggestTimeInMillis; this.suggestCurrent = suggestCurrent; + + this.recentSearchLoad = recentSearchLoad; + } private Stats(StreamInput in) throws IOException { @@ -109,6 +117,10 @@ private Stats(StreamInput in) throws IOException { queryFailure = in.readVLong(); fetchFailure = in.readVLong(); } + + if (in.getTransportVersion().onOrAfter(TransportVersions.SEARCH_LOAD_PER_INDEX_STATS)) { + recentSearchLoad = in.readDouble(); + } } @Override @@ -133,6 +145,10 @@ public void writeTo(StreamOutput out) throws IOException { out.writeVLong(queryFailure); out.writeVLong(fetchFailure); } + + if (out.getTransportVersion().onOrAfter(TransportVersions.SEARCH_LOAD_PER_INDEX_STATS)) { + out.writeDouble(recentSearchLoad); + } } public void add(Stats stats) { @@ -153,6 +169,8 @@ public void add(Stats stats) { suggestCount += stats.suggestCount; suggestTimeInMillis += stats.suggestTimeInMillis; suggestCurrent += stats.suggestCurrent; + + recentSearchLoad += stats.recentSearchLoad; } public void addForClosingShard(Stats stats) { @@ -171,6 +189,8 @@ public void addForClosingShard(Stats stats) { suggestCount += stats.suggestCount; suggestTimeInMillis += stats.suggestTimeInMillis; + + recentSearchLoad += stats.recentSearchLoad; } public long getQueryCount() { @@ -245,6 +265,10 @@ public long getSuggestCurrent() { return suggestCurrent; } + public double getSearchLoadRate() { + return recentSearchLoad; + } + public static Stats readStats(StreamInput in) throws IOException { return new Stats(in); } @@ -269,6 +293,8 @@ public XContentBuilder toXContent(XContentBuilder builder, Params params) throws builder.humanReadableField(Fields.SUGGEST_TIME_IN_MILLIS, Fields.SUGGEST_TIME, getSuggestTime()); builder.field(Fields.SUGGEST_CURRENT, suggestCurrent); + builder.field(Fields.RECENT_SEARCH_LOAD, recentSearchLoad); + return builder; } @@ -290,7 +316,8 @@ public boolean equals(Object o) { && scrollCurrent == that.scrollCurrent && suggestCount == that.suggestCount && suggestTimeInMillis == that.suggestTimeInMillis - && suggestCurrent == that.suggestCurrent; + && suggestCurrent == that.suggestCurrent + && recentSearchLoad == that.recentSearchLoad; } @Override @@ -309,7 +336,8 @@ public int hashCode() { scrollCurrent, suggestCount, suggestTimeInMillis, - suggestCurrent + suggestCurrent, + recentSearchLoad ); } } @@ -427,6 +455,7 @@ static final class Fields { static final String SUGGEST_TIME = "suggest_time"; static final String SUGGEST_TIME_IN_MILLIS = "suggest_time_in_millis"; static final String SUGGEST_CURRENT = "suggest_current"; + static final String RECENT_SEARCH_LOAD = "recent_search_load"; } @Override diff --git a/server/src/main/java/org/elasticsearch/index/search/stats/SearchStatsSettings.java b/server/src/main/java/org/elasticsearch/index/search/stats/SearchStatsSettings.java new file mode 100644 index 0000000000000..2ab13ae60411e --- /dev/null +++ b/server/src/main/java/org/elasticsearch/index/search/stats/SearchStatsSettings.java @@ -0,0 +1,50 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +package org.elasticsearch.index.search.stats; + +import org.elasticsearch.common.settings.ClusterSettings; +import org.elasticsearch.common.settings.Setting; +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.core.TimeValue; + +/** + * Container for cluster settings + */ +public class SearchStatsSettings { + + public static final TimeValue RECENT_READ_LOAD_HALF_LIFE_DEFAULT = TimeValue.timeValueMinutes(5); + static final TimeValue RECENT_READ_LOAD_HALF_LIFE_MIN = TimeValue.timeValueSeconds(1); // A sub-second half-life makes no sense + static final TimeValue RECENT_READ_LOAD_HALF_LIFE_MAX = TimeValue.timeValueDays(100_000); // Long.MAX_VALUE nanos, rounded down + + /** + * A cluster setting giving the half-life, in seconds, to use for the Exponentially Weighted Moving Rate calculation used for the + * recency-weighted read load + * + *

This is dynamic, but changes only apply to newly-opened shards. + */ + public static final Setting RECENT_READ_LOAD_HALF_LIFE_SETTING = Setting.timeSetting( + "indices.stats.recent_read_load.half_life", + RECENT_READ_LOAD_HALF_LIFE_DEFAULT, + RECENT_READ_LOAD_HALF_LIFE_MIN, + RECENT_READ_LOAD_HALF_LIFE_MAX, + Setting.Property.Dynamic, + Setting.Property.NodeScope + ); + + private volatile TimeValue recentReadLoadHalfLifeForNewShards = RECENT_READ_LOAD_HALF_LIFE_SETTING.getDefault(Settings.EMPTY); + + public SearchStatsSettings(ClusterSettings clusterSettings) { + clusterSettings.initializeAndWatch(RECENT_READ_LOAD_HALF_LIFE_SETTING, value -> recentReadLoadHalfLifeForNewShards = value); + } + + public TimeValue getRecentReadLoadHalfLifeForNewShards() { + return recentReadLoadHalfLifeForNewShards; + } +} diff --git a/server/src/main/java/org/elasticsearch/index/search/stats/ShardSearchStats.java b/server/src/main/java/org/elasticsearch/index/search/stats/ShardSearchStats.java index 6e6f744f6b719..d01ea2a184dbd 100644 --- a/server/src/main/java/org/elasticsearch/index/search/stats/ShardSearchStats.java +++ b/server/src/main/java/org/elasticsearch/index/search/stats/ShardSearchStats.java @@ -10,6 +10,7 @@ package org.elasticsearch.index.search.stats; import org.elasticsearch.common.metrics.CounterMetric; +import org.elasticsearch.common.metrics.ExponentiallyWeightedMovingRate; import org.elasticsearch.common.metrics.MeanMetric; import org.elasticsearch.common.regex.Regex; import org.elasticsearch.common.util.CollectionUtils; @@ -26,9 +27,15 @@ public final class ShardSearchStats implements SearchOperationListener { - private final StatsHolder totalStats = new StatsHolder(); + private final StatsHolder totalStats; private final CounterMetric openContexts = new CounterMetric(); private volatile Map groupsStats = emptyMap(); + private final SearchStatsSettings searchStatsSettings; + + public ShardSearchStats(SearchStatsSettings searchStatsSettings) { + this.searchStatsSettings = searchStatsSettings; + this.totalStats = new StatsHolder(searchStatsSettings); + } /** * Returns the stats, including group specific stats. If the groups are null/0 length, then nothing @@ -78,9 +85,11 @@ public void onFailedQueryPhase(SearchContext searchContext) { @Override public void onQueryPhase(SearchContext searchContext, long tookInNanos) { computeStats(searchContext, searchContext.hasOnlySuggest() ? statsHolder -> { + statsHolder.recentSearchLoad.addIncrement(tookInNanos, System.nanoTime()); statsHolder.suggestMetric.inc(tookInNanos); statsHolder.suggestCurrent.dec(); } : statsHolder -> { + statsHolder.recentSearchLoad.addIncrement(tookInNanos, System.nanoTime()); statsHolder.queryMetric.inc(tookInNanos); statsHolder.queryCurrent.dec(); }); @@ -102,6 +111,7 @@ public void onFailedFetchPhase(SearchContext searchContext) { @Override public void onFetchPhase(SearchContext searchContext, long tookInNanos) { computeStats(searchContext, statsHolder -> { + statsHolder.recentSearchLoad.addIncrement(tookInNanos, System.nanoTime()); statsHolder.fetchMetric.inc(tookInNanos); statsHolder.fetchCurrent.dec(); }); @@ -123,7 +133,7 @@ private StatsHolder groupStats(String group) { synchronized (this) { stats = groupsStats.get(group); if (stats == null) { - stats = new StatsHolder(); + stats = new StatsHolder(searchStatsSettings); groupsStats = Maps.copyMapWithAddedEntry(groupsStats, group, stats); } } @@ -173,6 +183,13 @@ static final class StatsHolder { final CounterMetric queryFailure = new CounterMetric(); final CounterMetric fetchFailure = new CounterMetric(); + final ExponentiallyWeightedMovingRate recentSearchLoad; + + StatsHolder(SearchStatsSettings searchStatsSettings) { + double lambdaInInverseNanos = Math.log(2.0) / searchStatsSettings.getRecentReadLoadHalfLifeForNewShards().nanos(); + this.recentSearchLoad = new ExponentiallyWeightedMovingRate(lambdaInInverseNanos, System.nanoTime()); + } + SearchStats.Stats stats() { return new SearchStats.Stats( queryMetric.count(), @@ -188,7 +205,8 @@ SearchStats.Stats stats() { scrollCurrent.count(), suggestMetric.count(), TimeUnit.NANOSECONDS.toMillis(suggestMetric.sum()), - suggestCurrent.count() + suggestCurrent.count(), + recentSearchLoad.getRate(System.nanoTime()) ); } } diff --git a/server/src/main/java/org/elasticsearch/index/shard/IndexShard.java b/server/src/main/java/org/elasticsearch/index/shard/IndexShard.java index 4b38a5d378ecf..397532a4182f1 100644 --- a/server/src/main/java/org/elasticsearch/index/shard/IndexShard.java +++ b/server/src/main/java/org/elasticsearch/index/shard/IndexShard.java @@ -117,6 +117,7 @@ import org.elasticsearch.index.refresh.RefreshStats; import org.elasticsearch.index.search.stats.FieldUsageStats; import org.elasticsearch.index.search.stats.SearchStats; +import org.elasticsearch.index.search.stats.SearchStatsSettings; import org.elasticsearch.index.search.stats.ShardFieldUsageTracker; import org.elasticsearch.index.search.stats.ShardSearchStats; import org.elasticsearch.index.seqno.ReplicationTracker; @@ -203,7 +204,7 @@ public class IndexShard extends AbstractIndexShardComponent implements IndicesCl private final IndexCache indexCache; private final Store store; private final InternalIndexingStats internalIndexingStats; - private final ShardSearchStats searchStats = new ShardSearchStats(); + private final ShardSearchStats searchStats; private final ShardFieldUsageTracker fieldUsageTracker; private final String shardUuid = UUIDs.randomBase64UUID(); private final long shardCreationTime; @@ -341,7 +342,8 @@ public IndexShard( final LongSupplier relativeTimeInNanosSupplier, final Engine.IndexCommitListener indexCommitListener, final MapperMetrics mapperMetrics, - final IndexingStatsSettings indexingStatsSettings + final IndexingStatsSettings indexingStatsSettings, + final SearchStatsSettings searchStatsSettings ) throws IOException { super(shardRouting.shardId(), indexSettings); assert shardRouting.initializing(); @@ -369,6 +371,7 @@ public IndexShard( this.bulkOperationListener = new ShardBulkStats(); this.globalCheckpointSyncer = globalCheckpointSyncer; this.retentionLeaseSyncer = Objects.requireNonNull(retentionLeaseSyncer); + this.searchStats = new ShardSearchStats(searchStatsSettings); this.searchOperationListener = new SearchOperationListener.CompositeListener( CollectionUtils.appendToCopyNoNullElements(searchOperationListener, searchStats), logger diff --git a/server/src/main/java/org/elasticsearch/indices/IndicesService.java b/server/src/main/java/org/elasticsearch/indices/IndicesService.java index 7825b3513a6b3..ed94017a600ed 100644 --- a/server/src/main/java/org/elasticsearch/indices/IndicesService.java +++ b/server/src/main/java/org/elasticsearch/indices/IndicesService.java @@ -118,6 +118,7 @@ import org.elasticsearch.index.recovery.RecoveryStats; import org.elasticsearch.index.refresh.RefreshStats; import org.elasticsearch.index.search.stats.SearchStats; +import org.elasticsearch.index.search.stats.SearchStatsSettings; import org.elasticsearch.index.seqno.RetentionLeaseStats; import org.elasticsearch.index.seqno.RetentionLeaseSyncer; import org.elasticsearch.index.seqno.SeqNoStats; @@ -281,6 +282,7 @@ public class IndicesService extends AbstractLifecycleComponent private final QueryRewriteInterceptor queryRewriteInterceptor; final SlowLogFieldProvider slowLogFieldProvider; // pkg-private for testingå private final IndexingStatsSettings indexStatsSettings; + private final SearchStatsSettings searchStatsSettings; @Override protected void doStart() { @@ -408,6 +410,7 @@ public void onRemoval(ShardId shardId, String fieldName, boolean wasEvicted, lon this.searchOperationListeners = builder.searchOperationListener; this.slowLogFieldProvider = builder.slowLogFieldProvider; this.indexStatsSettings = new IndexingStatsSettings(clusterService.getClusterSettings()); + this.searchStatsSettings = new SearchStatsSettings(clusterService.getClusterSettings()); } private static final String DANGLING_INDICES_UPDATE_THREAD_NAME = "DanglingIndices#updateTask"; @@ -797,7 +800,8 @@ private synchronized IndexService createIndexService( slowLogFieldProvider, mapperMetrics, searchOperationListeners, - indexStatsSettings + indexStatsSettings, + searchStatsSettings ); for (IndexingOperationListener operationListener : indexingOperationListeners) { indexModule.addIndexOperationListener(operationListener); @@ -895,7 +899,8 @@ public synchronized MapperService createIndexMapperServiceForValidation(IndexMet slowLogFieldProvider, mapperMetrics, searchOperationListeners, - indexStatsSettings + indexStatsSettings, + searchStatsSettings ); pluginsService.forEach(p -> p.onIndexModule(indexModule)); return indexModule.newIndexMapperService(clusterService, parserConfig, mapperRegistry, scriptService); diff --git a/server/src/test/java/org/elasticsearch/action/admin/cluster/node/stats/NodeStatsTests.java b/server/src/test/java/org/elasticsearch/action/admin/cluster/node/stats/NodeStatsTests.java index 70f278bd8d369..e8bbe412cbbab 100644 --- a/server/src/test/java/org/elasticsearch/action/admin/cluster/node/stats/NodeStatsTests.java +++ b/server/src/test/java/org/elasticsearch/action/admin/cluster/node/stats/NodeStatsTests.java @@ -619,6 +619,7 @@ private static CommonStats createShardLevelCommonStats() { ++iota, ++iota, ++iota, + ++iota, ++iota ); Map groupStats = new HashMap<>(); diff --git a/server/src/test/java/org/elasticsearch/index/IndexModuleTests.java b/server/src/test/java/org/elasticsearch/index/IndexModuleTests.java index f53d01d4772a1..fcd20894b5a3c 100644 --- a/server/src/test/java/org/elasticsearch/index/IndexModuleTests.java +++ b/server/src/test/java/org/elasticsearch/index/IndexModuleTests.java @@ -67,6 +67,7 @@ import org.elasticsearch.index.mapper.MapperRegistry; import org.elasticsearch.index.mapper.ParsedDocument; import org.elasticsearch.index.mapper.Uid; +import org.elasticsearch.index.search.stats.SearchStatsSettings; import org.elasticsearch.index.seqno.RetentionLeaseSyncer; import org.elasticsearch.index.shard.IndexEventListener; import org.elasticsearch.index.shard.IndexShard; @@ -254,7 +255,8 @@ public void testWrapperIsBound() throws IOException { mock(SlowLogFieldProvider.class), MapperMetrics.NOOP, emptyList(), - new IndexingStatsSettings(ClusterSettings.createBuiltInClusterSettings()) + new IndexingStatsSettings(ClusterSettings.createBuiltInClusterSettings()), + new SearchStatsSettings(ClusterSettings.createBuiltInClusterSettings()) ); module.setReaderWrapper(s -> new Wrapper()); @@ -283,7 +285,8 @@ public void testRegisterIndexStore() throws IOException { mock(SlowLogFieldProvider.class), MapperMetrics.NOOP, emptyList(), - new IndexingStatsSettings(ClusterSettings.createBuiltInClusterSettings()) + new IndexingStatsSettings(ClusterSettings.createBuiltInClusterSettings()), + new SearchStatsSettings(ClusterSettings.createBuiltInClusterSettings()) ); final IndexService indexService = newIndexService(module); @@ -310,7 +313,8 @@ public void testDirectoryWrapper() throws IOException { mock(SlowLogFieldProvider.class), MapperMetrics.NOOP, emptyList(), - new IndexingStatsSettings(ClusterSettings.createBuiltInClusterSettings()) + new IndexingStatsSettings(ClusterSettings.createBuiltInClusterSettings()), + new SearchStatsSettings(ClusterSettings.createBuiltInClusterSettings()) ); module.setDirectoryWrapper(new TestDirectoryWrapper()); @@ -665,7 +669,8 @@ public void testRegisterCustomRecoveryStateFactory() throws IOException { mock(SlowLogFieldProvider.class), MapperMetrics.NOOP, emptyList(), - new IndexingStatsSettings(ClusterSettings.createBuiltInClusterSettings()) + new IndexingStatsSettings(ClusterSettings.createBuiltInClusterSettings()), + new SearchStatsSettings(ClusterSettings.createBuiltInClusterSettings()) ); final IndexService indexService = newIndexService(module); @@ -689,7 +694,8 @@ public void testIndexCommitListenerIsBound() throws IOException, ExecutionExcept mock(SlowLogFieldProvider.class), MapperMetrics.NOOP, emptyList(), - new IndexingStatsSettings(ClusterSettings.createBuiltInClusterSettings()) + new IndexingStatsSettings(ClusterSettings.createBuiltInClusterSettings()), + new SearchStatsSettings(ClusterSettings.createBuiltInClusterSettings()) ); final AtomicLong lastAcquiredPrimaryTerm = new AtomicLong(); @@ -793,7 +799,8 @@ private static IndexModule createIndexModule( mock(SlowLogFieldProvider.class), MapperMetrics.NOOP, emptyList(), - new IndexingStatsSettings(ClusterSettings.createBuiltInClusterSettings()) + new IndexingStatsSettings(ClusterSettings.createBuiltInClusterSettings()), + new SearchStatsSettings(ClusterSettings.createBuiltInClusterSettings()) ); } diff --git a/server/src/test/java/org/elasticsearch/index/search/stats/SearchStatsTests.java b/server/src/test/java/org/elasticsearch/index/search/stats/SearchStatsTests.java index a4430e1c1499d..9bd518ae50475 100644 --- a/server/src/test/java/org/elasticsearch/index/search/stats/SearchStatsTests.java +++ b/server/src/test/java/org/elasticsearch/index/search/stats/SearchStatsTests.java @@ -22,9 +22,9 @@ public void testShardLevelSearchGroupStats() throws Exception { // let's create two dummy search stats with groups Map groupStats1 = new HashMap<>(); Map groupStats2 = new HashMap<>(); - groupStats2.put("group1", new Stats(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1)); - SearchStats searchStats1 = new SearchStats(new Stats(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1), 0, groupStats1); - SearchStats searchStats2 = new SearchStats(new Stats(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1), 0, groupStats2); + groupStats2.put("group1", new Stats(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1.0)); + SearchStats searchStats1 = new SearchStats(new Stats(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1.0), 0, groupStats1); + SearchStats searchStats2 = new SearchStats(new Stats(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1.0), 0, groupStats2); // adding these two search stats and checking group stats are correct searchStats1.add(searchStats2); diff --git a/server/src/test/java/org/elasticsearch/search/stats/ShardSearchStatsTests.java b/server/src/test/java/org/elasticsearch/search/stats/ShardSearchStatsTests.java new file mode 100644 index 0000000000000..b1a33893f26c8 --- /dev/null +++ b/server/src/test/java/org/elasticsearch/search/stats/ShardSearchStatsTests.java @@ -0,0 +1,216 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +package org.elasticsearch.search.stats; + +import org.elasticsearch.action.OriginalIndices; +import org.elasticsearch.action.search.SearchRequest; +import org.elasticsearch.cluster.metadata.IndexMetadata; +import org.elasticsearch.common.settings.ClusterSettings; +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.index.IndexSettings; +import org.elasticsearch.index.IndexVersion; +import org.elasticsearch.index.mapper.MapperMetrics; +import org.elasticsearch.index.mapper.MappingLookup; +import org.elasticsearch.index.query.SearchExecutionContext; +import org.elasticsearch.index.search.stats.SearchStats; +import org.elasticsearch.index.search.stats.SearchStatsSettings; +import org.elasticsearch.index.search.stats.ShardSearchStats; +import org.elasticsearch.index.shard.ShardId; +import org.elasticsearch.search.builder.SearchSourceBuilder; +import org.elasticsearch.search.internal.AliasFilter; +import org.elasticsearch.search.internal.SearchContext; +import org.elasticsearch.search.internal.ShardSearchRequest; +import org.elasticsearch.search.suggest.SuggestBuilder; +import org.elasticsearch.test.ESTestCase; +import org.elasticsearch.test.TestSearchContext; +import org.junit.Before; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Objects; +import java.util.concurrent.TimeUnit; + +public class ShardSearchStatsTests extends ESTestCase { + + private static final long TEN_MILLIS = 10; + + private ShardSearchStats shardSearchStatsListener; + + @Before + public void setup() { + ClusterSettings clusterSettings = ClusterSettings.createBuiltInClusterSettings(); + SearchStatsSettings searchStatsSettings = new SearchStatsSettings(clusterSettings); + this.shardSearchStatsListener = new ShardSearchStats(searchStatsSettings); + } + + public void testQueryPhase() { + try (SearchContext sc = createSearchContext(false)) { + shardSearchStatsListener.onPreQueryPhase(sc); + shardSearchStatsListener.onQueryPhase(sc, TimeUnit.MILLISECONDS.toNanos(TEN_MILLIS)); + + SearchStats.Stats stats = shardSearchStatsListener.stats().getTotal(); + assertTrue(stats.getSearchLoadRate() > 0.0); + } + } + + public void testQueryPhase_SuggestOnly() { + try (SearchContext sc = createSearchContext(true)) { + shardSearchStatsListener.onPreQueryPhase(sc); + shardSearchStatsListener.onQueryPhase(sc, TimeUnit.MILLISECONDS.toNanos(TEN_MILLIS)); + + SearchStats.Stats stats = shardSearchStatsListener.stats().getTotal(); + assertTrue(stats.getSearchLoadRate() > 0.0); + } + } + + public void testQueryPhase_withGroup() { + try (SearchContext sc = createSearchContext(false)) { + shardSearchStatsListener.onPreQueryPhase(sc); + shardSearchStatsListener.onQueryPhase(sc, TimeUnit.MILLISECONDS.toNanos(TEN_MILLIS)); + + SearchStats searchStats = shardSearchStatsListener.stats("_all"); + SearchStats.Stats stats = shardSearchStatsListener.stats().getTotal(); + assertTrue(stats.getSearchLoadRate() > 0.0); + + stats = Objects.requireNonNull(searchStats.getGroupStats()).get("group1"); + assertTrue(stats.getSearchLoadRate() > 0.0); + } + } + + public void testQueryPhase_withGroup_SuggestOnly() { + try (SearchContext sc = createSearchContext(true)) { + + shardSearchStatsListener.onPreQueryPhase(sc); + shardSearchStatsListener.onQueryPhase(sc, TimeUnit.MILLISECONDS.toNanos(TEN_MILLIS)); + + SearchStats searchStats = shardSearchStatsListener.stats("_all"); + SearchStats.Stats stats = shardSearchStatsListener.stats().getTotal(); + assertTrue(stats.getSearchLoadRate() > 0.0); + + stats = Objects.requireNonNull(searchStats.getGroupStats()).get("group1"); + assertTrue(stats.getSearchLoadRate() > 0.0); + } + } + + public void testQueryPhase_SuggestOnly_Failure() { + try (SearchContext sc = createSearchContext(true)) { + shardSearchStatsListener.onPreQueryPhase(sc); + shardSearchStatsListener.onFailedQueryPhase(sc); + + SearchStats.Stats stats = shardSearchStatsListener.stats().getTotal(); + assertEquals(0.0, stats.getSearchLoadRate(), 0); + } + } + + public void testQueryPhase_Failure() { + try (SearchContext sc = createSearchContext(false)) { + shardSearchStatsListener.onPreQueryPhase(sc); + shardSearchStatsListener.onFailedQueryPhase(sc); + + SearchStats.Stats stats = shardSearchStatsListener.stats().getTotal(); + assertEquals(0.0, stats.getSearchLoadRate(), 0); + } + } + + public void testFetchPhase() { + try (SearchContext sc = createSearchContext(false)) { + shardSearchStatsListener.onPreFetchPhase(sc); + shardSearchStatsListener.onFetchPhase(sc, TimeUnit.MILLISECONDS.toNanos(TEN_MILLIS)); + + SearchStats.Stats stats = shardSearchStatsListener.stats().getTotal(); + assertTrue(stats.getSearchLoadRate() > 0.0); + } + } + + public void testFetchPhase_withGroup() { + try (SearchContext sc = createSearchContext(false)) { + shardSearchStatsListener.onPreFetchPhase(sc); + shardSearchStatsListener.onFetchPhase(sc, TimeUnit.MILLISECONDS.toNanos(TEN_MILLIS)); + + SearchStats searchStats = shardSearchStatsListener.stats("_all"); + SearchStats.Stats stats = shardSearchStatsListener.stats().getTotal(); + assertTrue(stats.getSearchLoadRate() > 0.0); + + stats = Objects.requireNonNull(searchStats.getGroupStats()).get("group1"); + assertTrue(stats.getSearchLoadRate() > 0.0); + } + } + + public void testFetchPhase_Failure() { + try (SearchContext sc = createSearchContext(false)) { + shardSearchStatsListener.onPreFetchPhase(sc); + shardSearchStatsListener.onFailedFetchPhase(sc); + + SearchStats.Stats stats = shardSearchStatsListener.stats().getTotal(); + assertEquals(0.0, stats.getSearchLoadRate(), 0); + } + } + + private static SearchContext createSearchContext(boolean suggested) { + IndexSettings indexSettings = new IndexSettings( + IndexMetadata.builder("index") + .settings(Settings.builder().put(IndexMetadata.SETTING_VERSION_CREATED, IndexVersion.current())) + .numberOfShards(1) + .numberOfReplicas(0) + .creationDate(System.currentTimeMillis()) + .build(), + Settings.EMPTY + ); + + SearchExecutionContext searchExecutionContext = new SearchExecutionContext( + 0, + 0, + indexSettings, + null, + null, + null, + MappingLookup.EMPTY, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + Collections.emptyMap(), + null, + MapperMetrics.NOOP + ); + return new TestSearchContext(searchExecutionContext) { + private final SearchRequest searchquest = new SearchRequest().allowPartialSearchResults(true); + private final ShardSearchRequest request = new ShardSearchRequest( + OriginalIndices.NONE, + suggested ? searchquest.source(new SearchSourceBuilder().suggest(new SuggestBuilder())) : searchquest, + new ShardId("index", "indexUUID", 0), + 0, + 1, + AliasFilter.EMPTY, + 1f, + 0L, + null + ); + + @Override + public ShardSearchRequest request() { + return request; + } + + @Override + public List groupStats() { + return Arrays.asList("group1"); + } + }; + } +} diff --git a/test/framework/src/main/java/org/elasticsearch/index/shard/IndexShardTestCase.java b/test/framework/src/main/java/org/elasticsearch/index/shard/IndexShardTestCase.java index 47d9520c5aabb..6ea9f29a5b6b2 100644 --- a/test/framework/src/main/java/org/elasticsearch/index/shard/IndexShardTestCase.java +++ b/test/framework/src/main/java/org/elasticsearch/index/shard/IndexShardTestCase.java @@ -59,6 +59,7 @@ import org.elasticsearch.index.mapper.MapperMetrics; import org.elasticsearch.index.mapper.MapperService; import org.elasticsearch.index.mapper.SourceToParse; +import org.elasticsearch.index.search.stats.SearchStatsSettings; import org.elasticsearch.index.seqno.ReplicationTracker; import org.elasticsearch.index.seqno.RetentionLeaseSyncer; import org.elasticsearch.index.seqno.SequenceNumbers; @@ -560,7 +561,8 @@ protected IndexShard newShard( relativeTimeSupplier, null, MapperMetrics.NOOP, - new IndexingStatsSettings(ClusterSettings.createBuiltInClusterSettings()) + new IndexingStatsSettings(ClusterSettings.createBuiltInClusterSettings()), + new SearchStatsSettings(ClusterSettings.createBuiltInClusterSettings()) ); indexShard.addShardFailureCallback(DEFAULT_SHARD_FAILURE_HANDLER); success = true; diff --git a/x-pack/plugin/monitoring/src/test/java/org/elasticsearch/xpack/monitoring/collector/indices/IndexStatsMonitoringDocTests.java b/x-pack/plugin/monitoring/src/test/java/org/elasticsearch/xpack/monitoring/collector/indices/IndexStatsMonitoringDocTests.java index fbea440c81e58..b501591ea37d4 100644 --- a/x-pack/plugin/monitoring/src/test/java/org/elasticsearch/xpack/monitoring/collector/indices/IndexStatsMonitoringDocTests.java +++ b/x-pack/plugin/monitoring/src/test/java/org/elasticsearch/xpack/monitoring/collector/indices/IndexStatsMonitoringDocTests.java @@ -409,7 +409,23 @@ private static CommonStats mockCommonStats() { ); commonStats.getIndexing().add(new IndexingStats(indexingStats)); - final SearchStats.Stats searchStats = new SearchStats.Stats(++iota, ++iota, no, no, no, no, no, no, no, no, no, no, no, no); + final SearchStats.Stats searchStats = new SearchStats.Stats( + ++iota, + ++iota, + no, + no, + no, + no, + no, + no, + no, + no, + no, + no, + no, + no, + Double.valueOf(no) + ); commonStats.getSearch().add(new SearchStats(searchStats, no, null)); final SegmentsStats segmentsStats = new SegmentsStats(); diff --git a/x-pack/plugin/monitoring/src/test/java/org/elasticsearch/xpack/monitoring/collector/indices/IndicesStatsMonitoringDocTests.java b/x-pack/plugin/monitoring/src/test/java/org/elasticsearch/xpack/monitoring/collector/indices/IndicesStatsMonitoringDocTests.java index 4ff3895551b96..d01f2e0168a72 100644 --- a/x-pack/plugin/monitoring/src/test/java/org/elasticsearch/xpack/monitoring/collector/indices/IndicesStatsMonitoringDocTests.java +++ b/x-pack/plugin/monitoring/src/test/java/org/elasticsearch/xpack/monitoring/collector/indices/IndicesStatsMonitoringDocTests.java @@ -186,7 +186,7 @@ private CommonStats mockCommonStats() { final IndexingStats.Stats indexingStats = new IndexingStats.Stats(3L, 4L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, true, 5L, 0, 0, 0, 0.0, 0.0); commonStats.getIndexing().add(new IndexingStats(indexingStats)); - final SearchStats.Stats searchStats = new SearchStats.Stats(6L, 7L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L); + final SearchStats.Stats searchStats = new SearchStats.Stats(6L, 7L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0.0); commonStats.getSearch().add(new SearchStats(searchStats, 0L, null)); final BulkStats bulkStats = new BulkStats(0L, 0L, 0L, 0L, 0L); diff --git a/x-pack/plugin/monitoring/src/test/java/org/elasticsearch/xpack/monitoring/collector/node/NodeStatsMonitoringDocTests.java b/x-pack/plugin/monitoring/src/test/java/org/elasticsearch/xpack/monitoring/collector/node/NodeStatsMonitoringDocTests.java index f58059c288d3d..dc64365a503d0 100644 --- a/x-pack/plugin/monitoring/src/test/java/org/elasticsearch/xpack/monitoring/collector/node/NodeStatsMonitoringDocTests.java +++ b/x-pack/plugin/monitoring/src/test/java/org/elasticsearch/xpack/monitoring/collector/node/NodeStatsMonitoringDocTests.java @@ -357,7 +357,23 @@ private static NodeStats mockNodeStats() { indicesCommonStats.getQueryCache().add(new QueryCacheStats(++iota, ++iota, ++iota, ++iota, no)); indicesCommonStats.getRequestCache().add(new RequestCacheStats(++iota, ++iota, ++iota, ++iota)); - final SearchStats.Stats searchStats = new SearchStats.Stats(++iota, ++iota, no, no, no, no, no, no, no, no, no, no, no, no); + final SearchStats.Stats searchStats = new SearchStats.Stats( + ++iota, + ++iota, + no, + no, + no, + no, + no, + no, + no, + no, + no, + no, + no, + no, + Double.valueOf(no) + ); indicesCommonStats.getSearch().add(new SearchStats(searchStats, no, null)); final SegmentsStats segmentsStats = new SegmentsStats(); diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/SecurityTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/SecurityTests.java index 4eac3ddf85f1b..fd590630094f2 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/SecurityTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/SecurityTests.java @@ -45,6 +45,7 @@ import org.elasticsearch.index.analysis.AnalysisRegistry; import org.elasticsearch.index.engine.InternalEngineFactory; import org.elasticsearch.index.mapper.MapperMetrics; +import org.elasticsearch.index.search.stats.SearchStatsSettings; import org.elasticsearch.index.shard.IndexingStatsSettings; import org.elasticsearch.indices.TestIndexNameExpressionResolver; import org.elasticsearch.license.ClusterStateLicenseService; @@ -461,7 +462,8 @@ public void testOnIndexModuleIsNoOpWithSecurityDisabled() throws Exception { mock(SlowLogFieldProvider.class), MapperMetrics.NOOP, List.of(), - new IndexingStatsSettings(ClusterSettings.createBuiltInClusterSettings()) + new IndexingStatsSettings(ClusterSettings.createBuiltInClusterSettings()), + new SearchStatsSettings(ClusterSettings.createBuiltInClusterSettings()) ); security.onIndexModule(indexModule); // indexReaderWrapper is a SetOnce so if Security#onIndexModule had already set an ReaderWrapper we would get an exception here diff --git a/x-pack/plugin/watcher/src/test/java/org/elasticsearch/xpack/watcher/WatcherPluginTests.java b/x-pack/plugin/watcher/src/test/java/org/elasticsearch/xpack/watcher/WatcherPluginTests.java index bd8d15ea809fe..1cfa5669363c4 100644 --- a/x-pack/plugin/watcher/src/test/java/org/elasticsearch/xpack/watcher/WatcherPluginTests.java +++ b/x-pack/plugin/watcher/src/test/java/org/elasticsearch/xpack/watcher/WatcherPluginTests.java @@ -15,6 +15,7 @@ import org.elasticsearch.index.analysis.AnalysisRegistry; import org.elasticsearch.index.engine.InternalEngineFactory; import org.elasticsearch.index.mapper.MapperMetrics; +import org.elasticsearch.index.search.stats.SearchStatsSettings; import org.elasticsearch.index.shard.IndexingStatsSettings; import org.elasticsearch.indices.SystemIndexDescriptor; import org.elasticsearch.indices.TestIndexNameExpressionResolver; @@ -74,7 +75,8 @@ public void testWatcherDisabledTests() throws Exception { mock(SlowLogFieldProvider.class), MapperMetrics.NOOP, List.of(), - new IndexingStatsSettings(ClusterSettings.createBuiltInClusterSettings()) + new IndexingStatsSettings(ClusterSettings.createBuiltInClusterSettings()), + new SearchStatsSettings(ClusterSettings.createBuiltInClusterSettings()) ); // this will trip an assertion if the watcher indexing operation listener is null (which it is) but we try to add it watcher.onIndexModule(indexModule);