diff --git a/docs/changelog/113194.yaml b/docs/changelog/113194.yaml new file mode 100644 index 0000000000000..132659321c65e --- /dev/null +++ b/docs/changelog/113194.yaml @@ -0,0 +1,5 @@ +pr: 113194 +summary: Add Search Phase APM metrics +area: Search +type: enhancement +issues: [] diff --git a/docs/changelog/116357.yaml b/docs/changelog/116357.yaml new file mode 100644 index 0000000000000..a1a7831eab9ca --- /dev/null +++ b/docs/changelog/116357.yaml @@ -0,0 +1,5 @@ +pr: 116357 +summary: Add tracking for query rule types +area: Relevance +type: enhancement +issues: [] diff --git a/docs/reference/query-rules/apis/list-query-rulesets.asciidoc b/docs/reference/query-rules/apis/list-query-rulesets.asciidoc index 6832934f6985c..304b8c7745007 100644 --- a/docs/reference/query-rules/apis/list-query-rulesets.asciidoc +++ b/docs/reference/query-rules/apis/list-query-rulesets.asciidoc @@ -124,7 +124,7 @@ PUT _query_rules/ruleset-3 }, { "rule_id": "rule-3", - "type": "pinned", + "type": "exclude", "criteria": [ { "type": "fuzzy", @@ -178,6 +178,9 @@ A sample response: "rule_total_count": 1, "rule_criteria_types_counts": { "exact": 1 + }, + "rule_type_counts": { + "pinned": 1 } }, { @@ -186,6 +189,9 @@ A sample response: "rule_criteria_types_counts": { "exact": 1, "fuzzy": 1 + }, + "rule_type_counts": { + "pinned": 2 } }, { @@ -194,6 +200,10 @@ A sample response: "rule_criteria_types_counts": { "exact": 1, "fuzzy": 2 + }, + "rule_type_counts": { + "pinned": 2, + "exclude": 1 } } ] diff --git a/muted-tests.yml b/muted-tests.yml index fa7ce1509574e..f697bd34c9a8d 100644 --- a/muted-tests.yml +++ b/muted-tests.yml @@ -239,6 +239,11 @@ tests: - class: org.elasticsearch.snapshots.SnapshotShutdownIT method: testRestartNodeDuringSnapshot issue: https://github.com/elastic/elasticsearch/issues/116730 +- class: org.elasticsearch.action.search.SearchRequestTests + method: testSerializationConstants + issue: https://github.com/elastic/elasticsearch/issues/116752 +- class: org.elasticsearch.xpack.security.authc.ldap.ActiveDirectoryGroupsResolverTests + issue: https://github.com/elastic/elasticsearch/issues/116182 # Examples: # diff --git a/server/src/internalClusterTest/java/org/elasticsearch/cluster/routing/allocation/allocator/DesiredBalanceReconcilerMetricsIT.java b/server/src/internalClusterTest/java/org/elasticsearch/cluster/routing/allocation/allocator/DesiredBalanceReconcilerMetricsIT.java index 36374f7a3a8eb..9a71bf86388a4 100644 --- a/server/src/internalClusterTest/java/org/elasticsearch/cluster/routing/allocation/allocator/DesiredBalanceReconcilerMetricsIT.java +++ b/server/src/internalClusterTest/java/org/elasticsearch/cluster/routing/allocation/allocator/DesiredBalanceReconcilerMetricsIT.java @@ -9,8 +9,12 @@ package org.elasticsearch.cluster.routing.allocation.allocator; +import org.elasticsearch.cluster.ClusterInfoService; +import org.elasticsearch.cluster.ClusterInfoServiceUtils; +import org.elasticsearch.cluster.InternalClusterInfoService; import org.elasticsearch.cluster.node.DiscoveryNode; import org.elasticsearch.common.util.CollectionUtils; +import org.elasticsearch.core.TimeValue; import org.elasticsearch.plugins.Plugin; import org.elasticsearch.plugins.PluginsService; import org.elasticsearch.telemetry.TestTelemetryPlugin; @@ -56,8 +60,15 @@ public void testDesiredBalanceGaugeMetricsAreOnlyPublishedByCurrentMaster() thro public void testDesiredBalanceMetrics() { internalCluster().startNodes(2); prepareCreate("test").setSettings(indexSettings(2, 1)).get(); - indexRandom(randomBoolean(), "test", between(50, 100)); ensureGreen(); + + indexRandom(randomBoolean(), "test", between(50, 100)); + flush("test"); + // Make sure new cluster info is available + final var infoService = (InternalClusterInfoService) internalCluster().getCurrentMasterNodeInstance(ClusterInfoService.class); + ClusterInfoServiceUtils.setUpdateFrequency(infoService, TimeValue.timeValueMillis(200)); + assertNotNull("info should not be null", ClusterInfoServiceUtils.refresh(infoService)); + final var telemetryPlugin = getTelemetryPlugin(internalCluster().getMasterName()); telemetryPlugin.collect(); assertThat(telemetryPlugin.getLongGaugeMeasurement(DesiredBalanceMetrics.UNASSIGNED_SHARDS_METRIC_NAME), not(empty())); @@ -73,7 +84,7 @@ public void testDesiredBalanceMetrics() { ); assertThat(desiredBalanceNodeWeightsMetrics.size(), equalTo(2)); for (var nodeStat : desiredBalanceNodeWeightsMetrics) { - assertThat(nodeStat.value().doubleValue(), greaterThanOrEqualTo(0.0)); + assertTrue(nodeStat.isDouble()); assertThat((String) nodeStat.attributes().get("node_id"), is(in(nodeIds))); assertThat((String) nodeStat.attributes().get("node_name"), is(in(nodeNames))); } @@ -122,15 +133,16 @@ public void testDesiredBalanceMetrics() { assertThat((String) nodeStat.attributes().get("node_id"), is(in(nodeIds))); assertThat((String) nodeStat.attributes().get("node_name"), is(in(nodeNames))); } - final var currentNodeDiskUsageMetrics = telemetryPlugin.getDoubleGaugeMeasurement( + final var currentNodeDiskUsageMetrics = telemetryPlugin.getLongGaugeMeasurement( DesiredBalanceMetrics.CURRENT_NODE_DISK_USAGE_METRIC_NAME ); assertThat(currentNodeDiskUsageMetrics.size(), equalTo(2)); for (var nodeStat : currentNodeDiskUsageMetrics) { - assertThat(nodeStat.value().doubleValue(), greaterThanOrEqualTo(0.0)); + assertThat(nodeStat.value().longValue(), greaterThanOrEqualTo(0L)); assertThat((String) nodeStat.attributes().get("node_id"), is(in(nodeIds))); assertThat((String) nodeStat.attributes().get("node_name"), is(in(nodeNames))); } + assertTrue(currentNodeDiskUsageMetrics.stream().anyMatch(m -> m.getLong() > 0L)); final var currentNodeUndesiredShardCountMetrics = telemetryPlugin.getLongGaugeMeasurement( DesiredBalanceMetrics.CURRENT_NODE_UNDESIRED_SHARD_COUNT_METRIC_NAME ); @@ -140,15 +152,16 @@ public void testDesiredBalanceMetrics() { assertThat((String) nodeStat.attributes().get("node_id"), is(in(nodeIds))); assertThat((String) nodeStat.attributes().get("node_name"), is(in(nodeNames))); } - final var currentNodeForecastedDiskUsageMetrics = telemetryPlugin.getDoubleGaugeMeasurement( + final var currentNodeForecastedDiskUsageMetrics = telemetryPlugin.getLongGaugeMeasurement( DesiredBalanceMetrics.CURRENT_NODE_FORECASTED_DISK_USAGE_METRIC_NAME ); assertThat(currentNodeForecastedDiskUsageMetrics.size(), equalTo(2)); for (var nodeStat : currentNodeForecastedDiskUsageMetrics) { - assertThat(nodeStat.value().doubleValue(), greaterThanOrEqualTo(0.0)); + assertThat(nodeStat.value().longValue(), greaterThanOrEqualTo(0L)); assertThat((String) nodeStat.attributes().get("node_id"), is(in(nodeIds))); assertThat((String) nodeStat.attributes().get("node_name"), is(in(nodeNames))); } + assertTrue(currentNodeForecastedDiskUsageMetrics.stream().anyMatch(m -> m.getLong() > 0L)); } private static void assertOnlyMasterIsPublishingMetrics() { @@ -182,10 +195,10 @@ private static void assertMetricsAreBeingPublished(String nodeName, boolean shou matcher ); assertThat(testTelemetryPlugin.getDoubleGaugeMeasurement(DesiredBalanceMetrics.CURRENT_NODE_WRITE_LOAD_METRIC_NAME), matcher); - assertThat(testTelemetryPlugin.getDoubleGaugeMeasurement(DesiredBalanceMetrics.CURRENT_NODE_DISK_USAGE_METRIC_NAME), matcher); + assertThat(testTelemetryPlugin.getLongGaugeMeasurement(DesiredBalanceMetrics.CURRENT_NODE_DISK_USAGE_METRIC_NAME), matcher); assertThat(testTelemetryPlugin.getLongGaugeMeasurement(DesiredBalanceMetrics.CURRENT_NODE_SHARD_COUNT_METRIC_NAME), matcher); assertThat( - testTelemetryPlugin.getDoubleGaugeMeasurement(DesiredBalanceMetrics.CURRENT_NODE_FORECASTED_DISK_USAGE_METRIC_NAME), + testTelemetryPlugin.getLongGaugeMeasurement(DesiredBalanceMetrics.CURRENT_NODE_FORECASTED_DISK_USAGE_METRIC_NAME), matcher ); assertThat( diff --git a/server/src/main/java/org/elasticsearch/TransportVersions.java b/server/src/main/java/org/elasticsearch/TransportVersions.java index 661f057bfc5ff..b7da6115a1a48 100644 --- a/server/src/main/java/org/elasticsearch/TransportVersions.java +++ b/server/src/main/java/org/elasticsearch/TransportVersions.java @@ -196,6 +196,8 @@ static TransportVersion def(int id) { public static final TransportVersion ADD_COMPATIBILITY_VERSIONS_TO_NODE_INFO = def(8_789_00_0); public static final TransportVersion VERTEX_AI_INPUT_TYPE_ADDED = def(8_790_00_0); public static final TransportVersion SKIP_INNER_HITS_SEARCH_SOURCE = def(8_791_00_0); + public static final TransportVersion QUERY_RULES_LIST_INCLUDES_TYPES = def(8_792_00_0); + /* * STOP! READ THIS FIRST! No, really, * ____ _____ ___ ____ _ ____ _____ _ ____ _____ _ _ ___ ____ _____ ___ ____ ____ _____ _ diff --git a/server/src/main/java/org/elasticsearch/action/search/SearchTransportAPMMetrics.java b/server/src/main/java/org/elasticsearch/action/search/SearchTransportAPMMetrics.java deleted file mode 100644 index 6141e1704969b..0000000000000 --- a/server/src/main/java/org/elasticsearch/action/search/SearchTransportAPMMetrics.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * 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.action.search; - -import org.elasticsearch.telemetry.metric.LongHistogram; -import org.elasticsearch.telemetry.metric.MeterRegistry; - -public class SearchTransportAPMMetrics { - public static final String SEARCH_ACTION_LATENCY_BASE_METRIC = "es.search.nodes.transport_actions.latency.histogram"; - public static final String ACTION_ATTRIBUTE_NAME = "action"; - - public static final String QUERY_CAN_MATCH_NODE_METRIC = "shards_can_match"; - public static final String DFS_ACTION_METRIC = "dfs_query_then_fetch/shard_dfs_phase"; - public static final String QUERY_ID_ACTION_METRIC = "dfs_query_then_fetch/shard_query_phase"; - public static final String QUERY_ACTION_METRIC = "query_then_fetch/shard_query_phase"; - public static final String RANK_SHARD_FEATURE_ACTION_METRIC = "rank/shard_feature_phase"; - public static final String FREE_CONTEXT_ACTION_METRIC = "shard_release_context"; - public static final String FETCH_ID_ACTION_METRIC = "shard_fetch_phase"; - public static final String QUERY_SCROLL_ACTION_METRIC = "scroll/shard_query_phase"; - public static final String FETCH_ID_SCROLL_ACTION_METRIC = "scroll/shard_fetch_phase"; - public static final String QUERY_FETCH_SCROLL_ACTION_METRIC = "scroll/shard_query_and_fetch_phase"; - public static final String FREE_CONTEXT_SCROLL_ACTION_METRIC = "scroll/shard_release_context"; - public static final String CLEAR_SCROLL_CONTEXTS_ACTION_METRIC = "scroll/shard_release_contexts"; - - private final LongHistogram actionLatencies; - - public SearchTransportAPMMetrics(MeterRegistry meterRegistry) { - this( - meterRegistry.registerLongHistogram( - SEARCH_ACTION_LATENCY_BASE_METRIC, - "Transport action execution times at the node level, expressed as a histogram", - "millis" - ) - ); - } - - private SearchTransportAPMMetrics(LongHistogram actionLatencies) { - this.actionLatencies = actionLatencies; - } - - public LongHistogram getActionLatencies() { - return actionLatencies; - } -} diff --git a/server/src/main/java/org/elasticsearch/action/search/SearchTransportService.java b/server/src/main/java/org/elasticsearch/action/search/SearchTransportService.java index 604cf950f083b..8444a92b24432 100644 --- a/server/src/main/java/org/elasticsearch/action/search/SearchTransportService.java +++ b/server/src/main/java/org/elasticsearch/action/search/SearchTransportService.java @@ -67,20 +67,6 @@ import java.util.concurrent.Executor; import java.util.function.BiFunction; -import static org.elasticsearch.action.search.SearchTransportAPMMetrics.ACTION_ATTRIBUTE_NAME; -import static org.elasticsearch.action.search.SearchTransportAPMMetrics.CLEAR_SCROLL_CONTEXTS_ACTION_METRIC; -import static org.elasticsearch.action.search.SearchTransportAPMMetrics.DFS_ACTION_METRIC; -import static org.elasticsearch.action.search.SearchTransportAPMMetrics.FETCH_ID_ACTION_METRIC; -import static org.elasticsearch.action.search.SearchTransportAPMMetrics.FETCH_ID_SCROLL_ACTION_METRIC; -import static org.elasticsearch.action.search.SearchTransportAPMMetrics.FREE_CONTEXT_ACTION_METRIC; -import static org.elasticsearch.action.search.SearchTransportAPMMetrics.FREE_CONTEXT_SCROLL_ACTION_METRIC; -import static org.elasticsearch.action.search.SearchTransportAPMMetrics.QUERY_ACTION_METRIC; -import static org.elasticsearch.action.search.SearchTransportAPMMetrics.QUERY_CAN_MATCH_NODE_METRIC; -import static org.elasticsearch.action.search.SearchTransportAPMMetrics.QUERY_FETCH_SCROLL_ACTION_METRIC; -import static org.elasticsearch.action.search.SearchTransportAPMMetrics.QUERY_ID_ACTION_METRIC; -import static org.elasticsearch.action.search.SearchTransportAPMMetrics.QUERY_SCROLL_ACTION_METRIC; -import static org.elasticsearch.action.search.SearchTransportAPMMetrics.RANK_SHARD_FEATURE_ACTION_METRIC; - /** * An encapsulation of {@link org.elasticsearch.search.SearchService} operations exposed through * transport. @@ -450,11 +436,7 @@ public void writeTo(StreamOutput out) throws IOException { } } - public static void registerRequestHandler( - TransportService transportService, - SearchService searchService, - SearchTransportAPMMetrics searchTransportMetrics - ) { + public static void registerRequestHandler(TransportService transportService, SearchService searchService) { final TransportRequestHandler freeContextHandler = (request, channel, task) -> { logger.trace("releasing search context [{}]", request.id()); boolean freed = searchService.freeReaderContext(request.id()); @@ -465,7 +447,7 @@ public static void registerRequestHandler( FREE_CONTEXT_SCROLL_ACTION_NAME, freeContextExecutor, ScrollFreeContextRequest::new, - instrumentedHandler(FREE_CONTEXT_SCROLL_ACTION_METRIC, transportService, searchTransportMetrics, freeContextHandler) + freeContextHandler ); TransportActionProxy.registerProxyAction( transportService, @@ -478,7 +460,7 @@ public static void registerRequestHandler( FREE_CONTEXT_ACTION_NAME, freeContextExecutor, SearchFreeContextRequest::new, - instrumentedHandler(FREE_CONTEXT_ACTION_METRIC, transportService, searchTransportMetrics, freeContextHandler) + freeContextHandler ); TransportActionProxy.registerProxyAction(transportService, FREE_CONTEXT_ACTION_NAME, false, SearchFreeContextResponse::readFrom); @@ -486,10 +468,10 @@ public static void registerRequestHandler( CLEAR_SCROLL_CONTEXTS_ACTION_NAME, freeContextExecutor, ClearScrollContextsRequest::new, - instrumentedHandler(CLEAR_SCROLL_CONTEXTS_ACTION_METRIC, transportService, searchTransportMetrics, (request, channel, task) -> { + (request, channel, task) -> { searchService.freeAllScrollContexts(); channel.sendResponse(TransportResponse.Empty.INSTANCE); - }) + } ); TransportActionProxy.registerProxyAction( transportService, @@ -502,16 +484,7 @@ public static void registerRequestHandler( DFS_ACTION_NAME, EsExecutors.DIRECT_EXECUTOR_SERVICE, ShardSearchRequest::new, - instrumentedHandler( - DFS_ACTION_METRIC, - transportService, - searchTransportMetrics, - (request, channel, task) -> searchService.executeDfsPhase( - request, - (SearchShardTask) task, - new ChannelActionListener<>(channel) - ) - ) + (request, channel, task) -> searchService.executeDfsPhase(request, (SearchShardTask) task, new ChannelActionListener<>(channel)) ); TransportActionProxy.registerProxyAction(transportService, DFS_ACTION_NAME, true, DfsSearchResult::new); @@ -519,15 +492,10 @@ public static void registerRequestHandler( QUERY_ACTION_NAME, EsExecutors.DIRECT_EXECUTOR_SERVICE, ShardSearchRequest::new, - instrumentedHandler( - QUERY_ACTION_METRIC, - transportService, - searchTransportMetrics, - (request, channel, task) -> searchService.executeQueryPhase( - request, - (SearchShardTask) task, - new ChannelActionListener<>(channel) - ) + (request, channel, task) -> searchService.executeQueryPhase( + request, + (SearchShardTask) task, + new ChannelActionListener<>(channel) ) ); TransportActionProxy.registerProxyActionWithDynamicResponseType( @@ -541,15 +509,10 @@ public static void registerRequestHandler( QUERY_ID_ACTION_NAME, EsExecutors.DIRECT_EXECUTOR_SERVICE, QuerySearchRequest::new, - instrumentedHandler( - QUERY_ID_ACTION_METRIC, - transportService, - searchTransportMetrics, - (request, channel, task) -> searchService.executeQueryPhase( - request, - (SearchShardTask) task, - new ChannelActionListener<>(channel) - ) + (request, channel, task) -> searchService.executeQueryPhase( + request, + (SearchShardTask) task, + new ChannelActionListener<>(channel) ) ); TransportActionProxy.registerProxyAction(transportService, QUERY_ID_ACTION_NAME, true, QuerySearchResult::new); @@ -558,15 +521,10 @@ public static void registerRequestHandler( QUERY_SCROLL_ACTION_NAME, EsExecutors.DIRECT_EXECUTOR_SERVICE, InternalScrollSearchRequest::new, - instrumentedHandler( - QUERY_SCROLL_ACTION_METRIC, - transportService, - searchTransportMetrics, - (request, channel, task) -> searchService.executeQueryPhase( - request, - (SearchShardTask) task, - new ChannelActionListener<>(channel) - ) + (request, channel, task) -> searchService.executeQueryPhase( + request, + (SearchShardTask) task, + new ChannelActionListener<>(channel) ) ); TransportActionProxy.registerProxyAction(transportService, QUERY_SCROLL_ACTION_NAME, true, ScrollQuerySearchResult::new); @@ -575,15 +533,10 @@ public static void registerRequestHandler( QUERY_FETCH_SCROLL_ACTION_NAME, EsExecutors.DIRECT_EXECUTOR_SERVICE, InternalScrollSearchRequest::new, - instrumentedHandler( - QUERY_FETCH_SCROLL_ACTION_METRIC, - transportService, - searchTransportMetrics, - (request, channel, task) -> searchService.executeFetchPhase( - request, - (SearchShardTask) task, - new ChannelActionListener<>(channel) - ) + (request, channel, task) -> searchService.executeFetchPhase( + request, + (SearchShardTask) task, + new ChannelActionListener<>(channel) ) ); TransportActionProxy.registerProxyAction(transportService, QUERY_FETCH_SCROLL_ACTION_NAME, true, ScrollQueryFetchSearchResult::new); @@ -594,7 +547,7 @@ public static void registerRequestHandler( RANK_FEATURE_SHARD_ACTION_NAME, EsExecutors.DIRECT_EXECUTOR_SERVICE, RankFeatureShardRequest::new, - instrumentedHandler(RANK_SHARD_FEATURE_ACTION_METRIC, transportService, searchTransportMetrics, rankShardFeatureRequest) + rankShardFeatureRequest ); TransportActionProxy.registerProxyAction(transportService, RANK_FEATURE_SHARD_ACTION_NAME, true, RankFeatureResult::new); @@ -604,7 +557,7 @@ public static void registerRequestHandler( FETCH_ID_SCROLL_ACTION_NAME, EsExecutors.DIRECT_EXECUTOR_SERVICE, ShardFetchRequest::new, - instrumentedHandler(FETCH_ID_SCROLL_ACTION_METRIC, transportService, searchTransportMetrics, shardFetchRequestHandler) + shardFetchRequestHandler ); TransportActionProxy.registerProxyAction(transportService, FETCH_ID_SCROLL_ACTION_NAME, true, FetchSearchResult::new); @@ -614,7 +567,7 @@ public static void registerRequestHandler( true, true, ShardFetchSearchRequest::new, - instrumentedHandler(FETCH_ID_ACTION_METRIC, transportService, searchTransportMetrics, shardFetchRequestHandler) + shardFetchRequestHandler ); TransportActionProxy.registerProxyAction(transportService, FETCH_ID_ACTION_NAME, true, FetchSearchResult::new); @@ -622,12 +575,7 @@ public static void registerRequestHandler( QUERY_CAN_MATCH_NODE_NAME, transportService.getThreadPool().executor(ThreadPool.Names.SEARCH_COORDINATION), CanMatchNodeRequest::new, - instrumentedHandler( - QUERY_CAN_MATCH_NODE_METRIC, - transportService, - searchTransportMetrics, - (request, channel, task) -> searchService.canMatch(request, new ChannelActionListener<>(channel)) - ) + (request, channel, task) -> searchService.canMatch(request, new ChannelActionListener<>(channel)) ); TransportActionProxy.registerProxyAction(transportService, QUERY_CAN_MATCH_NODE_NAME, true, CanMatchNodeResponse::new); } @@ -658,26 +606,6 @@ public void onFailure(Exception e) { }); } - private static TransportRequestHandler instrumentedHandler( - String actionQualifier, - TransportService transportService, - SearchTransportAPMMetrics searchTransportMetrics, - TransportRequestHandler transportRequestHandler - ) { - var threadPool = transportService.getThreadPool(); - var latencies = searchTransportMetrics.getActionLatencies(); - Map attributes = Map.of(ACTION_ATTRIBUTE_NAME, actionQualifier); - return (request, channel, task) -> { - var startTime = threadPool.relativeTimeInMillis(); - try { - transportRequestHandler.messageReceived(request, channel, task); - } finally { - var elapsedTime = threadPool.relativeTimeInMillis() - startTime; - latencies.record(elapsedTime, attributes); - } - }; - } - /** * Returns a connection to the given node on the provided cluster. If the cluster alias is null the node will be resolved * against the local cluster. diff --git a/server/src/main/java/org/elasticsearch/action/search/TransportSearchAction.java b/server/src/main/java/org/elasticsearch/action/search/TransportSearchAction.java index 35f106ab58cbc..9aab5d005b1bb 100644 --- a/server/src/main/java/org/elasticsearch/action/search/TransportSearchAction.java +++ b/server/src/main/java/org/elasticsearch/action/search/TransportSearchAction.java @@ -175,7 +175,6 @@ public TransportSearchAction( IndexNameExpressionResolver indexNameExpressionResolver, NamedWriteableRegistry namedWriteableRegistry, ExecutorSelector executorSelector, - SearchTransportAPMMetrics searchTransportMetrics, SearchResponseMetrics searchResponseMetrics, Client client, UsageService usageService @@ -186,7 +185,7 @@ public TransportSearchAction( this.searchPhaseController = searchPhaseController; this.searchTransportService = searchTransportService; this.remoteClusterService = searchTransportService.getRemoteClusterService(); - SearchTransportService.registerRequestHandler(transportService, searchService, searchTransportMetrics); + SearchTransportService.registerRequestHandler(transportService, searchService); this.clusterService = clusterService; this.transportService = transportService; this.searchService = searchService; diff --git a/server/src/main/java/org/elasticsearch/cluster/routing/allocation/allocator/DesiredBalanceMetrics.java b/server/src/main/java/org/elasticsearch/cluster/routing/allocation/allocator/DesiredBalanceMetrics.java index 3ed5bc269e6c4..cf8840dc95724 100644 --- a/server/src/main/java/org/elasticsearch/cluster/routing/allocation/allocator/DesiredBalanceMetrics.java +++ b/server/src/main/java/org/elasticsearch/cluster/routing/allocation/allocator/DesiredBalanceMetrics.java @@ -136,7 +136,7 @@ public DesiredBalanceMetrics(MeterRegistry meterRegistry) { "threads", this::getCurrentNodeWriteLoadMetrics ); - meterRegistry.registerDoublesGauge( + meterRegistry.registerLongsGauge( CURRENT_NODE_DISK_USAGE_METRIC_NAME, "The current disk usage of nodes", "bytes", @@ -148,7 +148,7 @@ public DesiredBalanceMetrics(MeterRegistry meterRegistry) { "unit", this::getCurrentNodeShardCountMetrics ); - meterRegistry.registerDoublesGauge( + meterRegistry.registerLongsGauge( CURRENT_NODE_FORECASTED_DISK_USAGE_METRIC_NAME, "The current forecasted disk usage of nodes", "bytes", @@ -231,16 +231,16 @@ private List getDesiredBalanceNodeShardCountMetrics() { return values; } - private List getCurrentNodeDiskUsageMetrics() { + private List getCurrentNodeDiskUsageMetrics() { if (nodeIsMaster == false) { return List.of(); } var stats = allocationStatsPerNodeRef.get(); - List doubles = new ArrayList<>(stats.size()); + List values = new ArrayList<>(stats.size()); for (var node : stats.keySet()) { - doubles.add(new DoubleWithAttributes(stats.get(node).currentDiskUsage(), getNodeAttributes(node))); + values.add(new LongWithAttributes(stats.get(node).currentDiskUsage(), getNodeAttributes(node))); } - return doubles; + return values; } private List getCurrentNodeWriteLoadMetrics() { @@ -267,16 +267,16 @@ private List getCurrentNodeShardCountMetrics() { return values; } - private List getCurrentNodeForecastedDiskUsageMetrics() { + private List getCurrentNodeForecastedDiskUsageMetrics() { if (nodeIsMaster == false) { return List.of(); } var stats = allocationStatsPerNodeRef.get(); - List doubles = new ArrayList<>(stats.size()); + List values = new ArrayList<>(stats.size()); for (var node : stats.keySet()) { - doubles.add(new DoubleWithAttributes(stats.get(node).forecastedDiskUsage(), getNodeAttributes(node))); + values.add(new LongWithAttributes(stats.get(node).forecastedDiskUsage(), getNodeAttributes(node))); } - return doubles; + return values; } private List getCurrentNodeUndesiredShardCountMetrics() { diff --git a/server/src/main/java/org/elasticsearch/index/IndexModule.java b/server/src/main/java/org/elasticsearch/index/IndexModule.java index 4ff7ef60cc0a2..64182b000827d 100644 --- a/server/src/main/java/org/elasticsearch/index/IndexModule.java +++ b/server/src/main/java/org/elasticsearch/index/IndexModule.java @@ -167,7 +167,7 @@ public interface DirectoryWrapper { private final Map> similarities = new HashMap<>(); private final Map directoryFactories; private final SetOnce> forceQueryCacheProvider = new SetOnce<>(); - private final List searchOperationListeners = new ArrayList<>(); + private final List searchOperationListeners; private final List indexOperationListeners = new ArrayList<>(); private final IndexNameExpressionResolver expressionResolver; private final AtomicBoolean frozen = new AtomicBoolean(false); @@ -194,11 +194,14 @@ public IndexModule( final IndexNameExpressionResolver expressionResolver, final Map recoveryStateFactories, final SlowLogFieldProvider slowLogFieldProvider, - final MapperMetrics mapperMetrics + final MapperMetrics mapperMetrics, + final List searchOperationListeners ) { this.indexSettings = indexSettings; this.analysisRegistry = analysisRegistry; this.engineFactory = Objects.requireNonNull(engineFactory); + // Need to have a mutable arraylist for plugins to add listeners to it + this.searchOperationListeners = new ArrayList<>(searchOperationListeners); this.searchOperationListeners.add(new SearchSlowLog(indexSettings, slowLogFieldProvider)); this.indexOperationListeners.add(new IndexingSlowLog(indexSettings, slowLogFieldProvider)); this.directoryFactories = Collections.unmodifiableMap(directoryFactories); diff --git a/server/src/main/java/org/elasticsearch/index/search/stats/ShardSearchPhaseAPMMetrics.java b/server/src/main/java/org/elasticsearch/index/search/stats/ShardSearchPhaseAPMMetrics.java new file mode 100644 index 0000000000000..6b523a154379e --- /dev/null +++ b/server/src/main/java/org/elasticsearch/index/search/stats/ShardSearchPhaseAPMMetrics.java @@ -0,0 +1,64 @@ +/* + * 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.util.concurrent.EsExecutors; +import org.elasticsearch.index.shard.SearchOperationListener; +import org.elasticsearch.search.internal.SearchContext; +import org.elasticsearch.telemetry.metric.LongHistogram; +import org.elasticsearch.telemetry.metric.MeterRegistry; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +public final class ShardSearchPhaseAPMMetrics implements SearchOperationListener { + + public static final String QUERY_SEARCH_PHASE_METRIC = "es.search.shards.phases.query.duration.histogram"; + public static final String FETCH_SEARCH_PHASE_METRIC = "es.search.shards.phases.fetch.duration.histogram"; + + public static final String SYSTEM_THREAD_ATTRIBUTE_NAME = "system_thread"; + + private final LongHistogram queryPhaseMetric; + private final LongHistogram fetchPhaseMetric; + + // Avoid allocating objects in the search path and multithreading clashes + private static final ThreadLocal> THREAD_LOCAL_ATTRS = ThreadLocal.withInitial(() -> new HashMap<>(1)); + + public ShardSearchPhaseAPMMetrics(MeterRegistry meterRegistry) { + this.queryPhaseMetric = meterRegistry.registerLongHistogram( + QUERY_SEARCH_PHASE_METRIC, + "Query search phase execution times at the shard level, expressed as a histogram", + "ms" + ); + this.fetchPhaseMetric = meterRegistry.registerLongHistogram( + FETCH_SEARCH_PHASE_METRIC, + "Fetch search phase execution times at the shard level, expressed as a histogram", + "ms" + ); + } + + @Override + public void onQueryPhase(SearchContext searchContext, long tookInNanos) { + recordPhaseLatency(queryPhaseMetric, tookInNanos); + } + + @Override + public void onFetchPhase(SearchContext searchContext, long tookInNanos) { + recordPhaseLatency(fetchPhaseMetric, tookInNanos); + } + + private static void recordPhaseLatency(LongHistogram histogramMetric, long tookInNanos) { + Map attrs = ShardSearchPhaseAPMMetrics.THREAD_LOCAL_ATTRS.get(); + boolean isSystem = ((EsExecutors.EsThread) Thread.currentThread()).isSystem(); + attrs.put(SYSTEM_THREAD_ATTRIBUTE_NAME, isSystem); + histogramMetric.record(TimeUnit.NANOSECONDS.toMillis(tookInNanos), attrs); + } +} diff --git a/server/src/main/java/org/elasticsearch/indices/IndicesService.java b/server/src/main/java/org/elasticsearch/indices/IndicesService.java index 706f788e8a310..3ac61bbca1a21 100644 --- a/server/src/main/java/org/elasticsearch/indices/IndicesService.java +++ b/server/src/main/java/org/elasticsearch/indices/IndicesService.java @@ -124,6 +124,7 @@ import org.elasticsearch.index.shard.IndexShardState; import org.elasticsearch.index.shard.IndexingOperationListener; import org.elasticsearch.index.shard.IndexingStats; +import org.elasticsearch.index.shard.SearchOperationListener; import org.elasticsearch.index.shard.ShardId; import org.elasticsearch.indices.breaker.CircuitBreakerService; import org.elasticsearch.indices.cluster.IndicesClusterStateService; @@ -263,6 +264,7 @@ public class IndicesService extends AbstractLifecycleComponent private final CheckedBiConsumer requestCacheKeyDifferentiator; private final MapperMetrics mapperMetrics; private final PostRecoveryMerger postRecoveryMerger; + private final List searchOperationListeners; @Override protected void doStart() { @@ -379,8 +381,8 @@ public void onRemoval(ShardId shardId, String fieldName, boolean wasEvicted, lon clusterService.getClusterSettings().addSettingsUpdateConsumer(ALLOW_EXPENSIVE_QUERIES, this::setAllowExpensiveQueries); this.timestampFieldMapperService = new TimestampFieldMapperService(settings, threadPool, this); - this.postRecoveryMerger = new PostRecoveryMerger(settings, threadPool.executor(ThreadPool.Names.FORCE_MERGE), this::getShardOrNull); + this.searchOperationListeners = builder.searchOperationListener; } private static final String DANGLING_INDICES_UPDATE_THREAD_NAME = "DanglingIndices#updateTask"; @@ -752,7 +754,8 @@ private synchronized IndexService createIndexService( indexNameExpressionResolver, recoveryStateFactories, loadSlowLogFieldProvider(), - mapperMetrics + mapperMetrics, + searchOperationListeners ); for (IndexingOperationListener operationListener : indexingOperationListeners) { indexModule.addIndexOperationListener(operationListener); @@ -830,7 +833,8 @@ public synchronized MapperService createIndexMapperServiceForValidation(IndexMet indexNameExpressionResolver, recoveryStateFactories, loadSlowLogFieldProvider(), - mapperMetrics + mapperMetrics, + searchOperationListeners ); pluginsService.forEach(p -> p.onIndexModule(indexModule)); return indexModule.newIndexMapperService(clusterService, parserConfig, mapperRegistry, scriptService); diff --git a/server/src/main/java/org/elasticsearch/indices/IndicesServiceBuilder.java b/server/src/main/java/org/elasticsearch/indices/IndicesServiceBuilder.java index 8fff1f5bef51f..08d1b5ce3a96c 100644 --- a/server/src/main/java/org/elasticsearch/indices/IndicesServiceBuilder.java +++ b/server/src/main/java/org/elasticsearch/indices/IndicesServiceBuilder.java @@ -27,6 +27,7 @@ import org.elasticsearch.index.engine.EngineFactory; import org.elasticsearch.index.mapper.MapperMetrics; import org.elasticsearch.index.mapper.MapperRegistry; +import org.elasticsearch.index.shard.SearchOperationListener; import org.elasticsearch.indices.breaker.CircuitBreakerService; import org.elasticsearch.plugins.EnginePlugin; import org.elasticsearch.plugins.IndexStorePlugin; @@ -74,6 +75,7 @@ public class IndicesServiceBuilder { @Nullable CheckedBiConsumer requestCacheKeyDifferentiator; MapperMetrics mapperMetrics; + List searchOperationListener = List.of(); public IndicesServiceBuilder settings(Settings settings) { this.settings = settings; @@ -177,6 +179,15 @@ public IndicesServiceBuilder mapperMetrics(MapperMetrics mapperMetrics) { return this; } + public List searchOperationListeners() { + return searchOperationListener; + } + + public IndicesServiceBuilder searchOperationListeners(List searchOperationListener) { + this.searchOperationListener = searchOperationListener; + return this; + } + public IndicesService build() { Objects.requireNonNull(settings); Objects.requireNonNull(pluginsService); @@ -201,6 +212,7 @@ public IndicesService build() { Objects.requireNonNull(indexFoldersDeletionListeners); Objects.requireNonNull(snapshotCommitSuppliers); Objects.requireNonNull(mapperMetrics); + Objects.requireNonNull(searchOperationListener); // collect engine factory providers from plugins engineFactoryProviders = pluginsService.filterPlugins(EnginePlugin.class) diff --git a/server/src/main/java/org/elasticsearch/node/NodeConstruction.java b/server/src/main/java/org/elasticsearch/node/NodeConstruction.java index c883fca8d047f..e8b9d18a1dd08 100644 --- a/server/src/main/java/org/elasticsearch/node/NodeConstruction.java +++ b/server/src/main/java/org/elasticsearch/node/NodeConstruction.java @@ -29,7 +29,6 @@ import org.elasticsearch.action.ingest.ReservedPipelineAction; import org.elasticsearch.action.search.SearchExecutionStatsCollector; import org.elasticsearch.action.search.SearchPhaseController; -import org.elasticsearch.action.search.SearchTransportAPMMetrics; import org.elasticsearch.action.search.SearchTransportService; import org.elasticsearch.action.support.TransportAction; import org.elasticsearch.action.update.UpdateHelper; @@ -117,6 +116,8 @@ import org.elasticsearch.index.analysis.AnalysisRegistry; import org.elasticsearch.index.mapper.MapperMetrics; import org.elasticsearch.index.mapper.SourceFieldMetrics; +import org.elasticsearch.index.search.stats.ShardSearchPhaseAPMMetrics; +import org.elasticsearch.index.shard.SearchOperationListener; import org.elasticsearch.indices.ExecutorSelector; import org.elasticsearch.indices.IndicesModule; import org.elasticsearch.indices.IndicesService; @@ -798,6 +799,9 @@ private void construct( threadPool::relativeTimeInMillis ); MapperMetrics mapperMetrics = new MapperMetrics(sourceFieldMetrics); + final List searchOperationListeners = List.of( + new ShardSearchPhaseAPMMetrics(telemetryProvider.getMeterRegistry()) + ); IndicesService indicesService = new IndicesServiceBuilder().settings(settings) .pluginsService(pluginsService) @@ -819,6 +823,7 @@ private void construct( .valuesSourceRegistry(searchModule.getValuesSourceRegistry()) .requestCacheKeyDifferentiator(searchModule.getRequestCacheKeyDifferentiator()) .mapperMetrics(mapperMetrics) + .searchOperationListeners(searchOperationListeners) .build(); final var parameters = new IndexSettingProvider.Parameters(indicesService::createIndexMapperServiceForValidation); @@ -1002,7 +1007,6 @@ private void construct( telemetryProvider.getTracer() ); final ResponseCollectorService responseCollectorService = new ResponseCollectorService(clusterService); - final SearchTransportAPMMetrics searchTransportAPMMetrics = new SearchTransportAPMMetrics(telemetryProvider.getMeterRegistry()); final SearchResponseMetrics searchResponseMetrics = new SearchResponseMetrics(telemetryProvider.getMeterRegistry()); final SearchTransportService searchTransportService = new SearchTransportService( transportService, @@ -1182,7 +1186,6 @@ private void construct( b.bind(MetadataCreateIndexService.class).toInstance(metadataCreateIndexService); b.bind(MetadataUpdateSettingsService.class).toInstance(metadataUpdateSettingsService); b.bind(SearchService.class).toInstance(searchService); - b.bind(SearchTransportAPMMetrics.class).toInstance(searchTransportAPMMetrics); b.bind(SearchResponseMetrics.class).toInstance(searchResponseMetrics); b.bind(SearchTransportService.class).toInstance(searchTransportService); b.bind(SearchPhaseController.class).toInstance(new SearchPhaseController(searchService::aggReduceContextBuilder)); diff --git a/server/src/main/java/org/elasticsearch/threadpool/ThreadPool.java b/server/src/main/java/org/elasticsearch/threadpool/ThreadPool.java index f55e3740aaa8f..cc5e96327b241 100644 --- a/server/src/main/java/org/elasticsearch/threadpool/ThreadPool.java +++ b/server/src/main/java/org/elasticsearch/threadpool/ThreadPool.java @@ -1062,6 +1062,13 @@ public static boolean assertCurrentThreadPool(String... permittedThreadPoolNames return true; } + public static boolean assertTestThreadPool() { + final var threadName = Thread.currentThread().getName(); + final var executorName = EsExecutors.executorName(threadName); + assert threadName.startsWith("TEST-") || threadName.startsWith("LuceneTestCase") : threadName + " is not a test thread"; + return true; + } + public static boolean assertInSystemContext(ThreadPool threadPool) { final var threadName = Thread.currentThread().getName(); assert threadName.startsWith("TEST-") || threadName.startsWith("LuceneTestCase") || threadPool.getThreadContext().isSystemContext() diff --git a/server/src/test/java/org/elasticsearch/action/search/TransportSearchActionTests.java b/server/src/test/java/org/elasticsearch/action/search/TransportSearchActionTests.java index 70682cfd41d82..a9de118c6b859 100644 --- a/server/src/test/java/org/elasticsearch/action/search/TransportSearchActionTests.java +++ b/server/src/test/java/org/elasticsearch/action/search/TransportSearchActionTests.java @@ -1765,7 +1765,6 @@ protected void doWriteTo(StreamOutput out) throws IOException { new IndexNameExpressionResolver(threadPool.getThreadContext(), EmptySystemIndices.INSTANCE), null, null, - new SearchTransportAPMMetrics(TelemetryProvider.NOOP.getMeterRegistry()), new SearchResponseMetrics(TelemetryProvider.NOOP.getMeterRegistry()), client, new UsageService() diff --git a/server/src/test/java/org/elasticsearch/index/IndexModuleTests.java b/server/src/test/java/org/elasticsearch/index/IndexModuleTests.java index 909005d228665..49a4d519c0ea4 100644 --- a/server/src/test/java/org/elasticsearch/index/IndexModuleTests.java +++ b/server/src/test/java/org/elasticsearch/index/IndexModuleTests.java @@ -111,6 +111,7 @@ import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicReference; +import static java.util.Collections.emptyList; import static java.util.Collections.emptyMap; import static java.util.Collections.singletonMap; import static org.elasticsearch.index.IndexService.IndexCreationContext.CREATE_INDEX; @@ -237,7 +238,8 @@ public void testWrapperIsBound() throws IOException { indexNameExpressionResolver, Collections.emptyMap(), mock(SlowLogFieldProvider.class), - MapperMetrics.NOOP + MapperMetrics.NOOP, + emptyList() ); module.setReaderWrapper(s -> new Wrapper()); @@ -264,7 +266,8 @@ public void testRegisterIndexStore() throws IOException { indexNameExpressionResolver, Collections.emptyMap(), mock(SlowLogFieldProvider.class), - MapperMetrics.NOOP + MapperMetrics.NOOP, + emptyList() ); final IndexService indexService = newIndexService(module); @@ -289,7 +292,8 @@ public void testDirectoryWrapper() throws IOException { indexNameExpressionResolver, Collections.emptyMap(), mock(SlowLogFieldProvider.class), - MapperMetrics.NOOP + MapperMetrics.NOOP, + emptyList() ); module.setDirectoryWrapper(new TestDirectoryWrapper()); @@ -642,7 +646,8 @@ public void testRegisterCustomRecoveryStateFactory() throws IOException { indexNameExpressionResolver, recoveryStateFactories, mock(SlowLogFieldProvider.class), - MapperMetrics.NOOP + MapperMetrics.NOOP, + emptyList() ); final IndexService indexService = newIndexService(module); @@ -664,7 +669,8 @@ public void testIndexCommitListenerIsBound() throws IOException, ExecutionExcept indexNameExpressionResolver, Collections.emptyMap(), mock(SlowLogFieldProvider.class), - MapperMetrics.NOOP + MapperMetrics.NOOP, + emptyList() ); final AtomicLong lastAcquiredPrimaryTerm = new AtomicLong(); @@ -766,7 +772,8 @@ private static IndexModule createIndexModule( indexNameExpressionResolver, Collections.emptyMap(), mock(SlowLogFieldProvider.class), - MapperMetrics.NOOP + MapperMetrics.NOOP, + emptyList() ); } diff --git a/server/src/test/java/org/elasticsearch/search/TelemetryMetrics/SearchTransportTelemetryTests.java b/server/src/test/java/org/elasticsearch/search/TelemetryMetrics/SearchTransportTelemetryTests.java deleted file mode 100644 index 15f5ed0d800d2..0000000000000 --- a/server/src/test/java/org/elasticsearch/search/TelemetryMetrics/SearchTransportTelemetryTests.java +++ /dev/null @@ -1,142 +0,0 @@ -/* - * 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.TelemetryMetrics; - -import org.elasticsearch.action.search.SearchType; -import org.elasticsearch.cluster.metadata.IndexMetadata; -import org.elasticsearch.common.settings.Settings; -import org.elasticsearch.core.TimeValue; -import org.elasticsearch.plugins.Plugin; -import org.elasticsearch.plugins.PluginsService; -import org.elasticsearch.telemetry.Measurement; -import org.elasticsearch.telemetry.TestTelemetryPlugin; -import org.elasticsearch.test.ESSingleNodeTestCase; -import org.junit.After; -import org.junit.Before; - -import java.util.Collection; -import java.util.List; - -import static org.elasticsearch.action.search.SearchTransportAPMMetrics.DFS_ACTION_METRIC; -import static org.elasticsearch.action.search.SearchTransportAPMMetrics.FETCH_ID_ACTION_METRIC; -import static org.elasticsearch.action.search.SearchTransportAPMMetrics.FETCH_ID_SCROLL_ACTION_METRIC; -import static org.elasticsearch.action.search.SearchTransportAPMMetrics.FREE_CONTEXT_SCROLL_ACTION_METRIC; -import static org.elasticsearch.action.search.SearchTransportAPMMetrics.QUERY_ACTION_METRIC; -import static org.elasticsearch.action.search.SearchTransportAPMMetrics.QUERY_ID_ACTION_METRIC; -import static org.elasticsearch.action.search.SearchTransportAPMMetrics.QUERY_SCROLL_ACTION_METRIC; -import static org.elasticsearch.action.support.WriteRequest.RefreshPolicy.IMMEDIATE; -import static org.elasticsearch.index.query.QueryBuilders.simpleQueryStringQuery; -import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertScrollResponsesAndHitCount; -import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertSearchHitsWithoutFailures; - -public class SearchTransportTelemetryTests extends ESSingleNodeTestCase { - - private static final String indexName = "test_search_metrics2"; - private final int num_primaries = randomIntBetween(2, 7); - - @Override - protected boolean resetNodeAfterTest() { - return true; - } - - @Before - private void setUpIndex() throws Exception { - createIndex( - indexName, - Settings.builder() - .put(IndexMetadata.SETTING_NUMBER_OF_SHARDS, num_primaries) - .put(IndexMetadata.SETTING_NUMBER_OF_REPLICAS, 0) - .build() - ); - ensureGreen(indexName); - - prepareIndex(indexName).setId("1").setSource("body", "doc1").setRefreshPolicy(IMMEDIATE).get(); - prepareIndex(indexName).setId("2").setSource("body", "doc2").setRefreshPolicy(IMMEDIATE).get(); - } - - @After - private void afterTest() { - resetMeter(); - } - - @Override - protected Collection> getPlugins() { - return pluginList(TestTelemetryPlugin.class); - } - - @AwaitsFix(bugUrl = "https://github.com/elastic/elasticsearch/issues/103810") - public void testSearchTransportMetricsDfsQueryThenFetch() throws InterruptedException { - assertSearchHitsWithoutFailures( - client().prepareSearch(indexName).setSearchType(SearchType.DFS_QUERY_THEN_FETCH).setQuery(simpleQueryStringQuery("doc1")), - "1" - ); - assertEquals(num_primaries, getNumberOfMeasurements(DFS_ACTION_METRIC)); - assertEquals(num_primaries, getNumberOfMeasurements(QUERY_ID_ACTION_METRIC)); - assertNotEquals(0, getNumberOfMeasurements(FETCH_ID_ACTION_METRIC)); - resetMeter(); - } - - @AwaitsFix(bugUrl = "https://github.com/elastic/elasticsearch/issues/103810") - public void testSearchTransportMetricsQueryThenFetch() throws InterruptedException { - assertSearchHitsWithoutFailures( - client().prepareSearch(indexName).setSearchType(SearchType.QUERY_THEN_FETCH).setQuery(simpleQueryStringQuery("doc1")), - "1" - ); - assertEquals(num_primaries, getNumberOfMeasurements(QUERY_ACTION_METRIC)); - assertNotEquals(0, getNumberOfMeasurements(FETCH_ID_ACTION_METRIC)); - resetMeter(); - } - - @AwaitsFix(bugUrl = "https://github.com/elastic/elasticsearch/issues/103810") - public void testSearchTransportMetricsScroll() throws InterruptedException { - assertScrollResponsesAndHitCount( - client(), - TimeValue.timeValueSeconds(60), - client().prepareSearch(indexName) - .setSearchType(SearchType.DFS_QUERY_THEN_FETCH) - .setSize(1) - .setQuery(simpleQueryStringQuery("doc1 doc2")), - 2, - (respNum, response) -> { - if (respNum == 1) { - assertEquals(num_primaries, getNumberOfMeasurements(DFS_ACTION_METRIC)); - assertEquals(num_primaries, getNumberOfMeasurements(QUERY_ID_ACTION_METRIC)); - assertNotEquals(0, getNumberOfMeasurements(FETCH_ID_ACTION_METRIC)); - } else if (respNum == 2) { - assertEquals(num_primaries, getNumberOfMeasurements(QUERY_SCROLL_ACTION_METRIC)); - assertNotEquals(0, getNumberOfMeasurements(FETCH_ID_SCROLL_ACTION_METRIC)); - } - resetMeter(); - } - ); - - assertEquals(num_primaries, getNumberOfMeasurements(FREE_CONTEXT_SCROLL_ACTION_METRIC)); - resetMeter(); - } - - private void resetMeter() { - getTestTelemetryPlugin().resetMeter(); - } - - private TestTelemetryPlugin getTestTelemetryPlugin() { - return getInstanceFromNode(PluginsService.class).filterPlugins(TestTelemetryPlugin.class).toList().get(0); - } - - private long getNumberOfMeasurements(String attributeValue) { - final List measurements = getTestTelemetryPlugin().getLongHistogramMeasurement( - org.elasticsearch.action.search.SearchTransportAPMMetrics.SEARCH_ACTION_LATENCY_BASE_METRIC - ); - return measurements.stream() - .filter( - m -> m.attributes().get(org.elasticsearch.action.search.SearchTransportAPMMetrics.ACTION_ATTRIBUTE_NAME) == attributeValue - ) - .count(); - } -} diff --git a/server/src/test/java/org/elasticsearch/search/TelemetryMetrics/ShardSearchPhaseAPMMetricsTests.java b/server/src/test/java/org/elasticsearch/search/TelemetryMetrics/ShardSearchPhaseAPMMetricsTests.java new file mode 100644 index 0000000000000..80bb7ebc8ddb8 --- /dev/null +++ b/server/src/test/java/org/elasticsearch/search/TelemetryMetrics/ShardSearchPhaseAPMMetricsTests.java @@ -0,0 +1,220 @@ +/* + * 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.TelemetryMetrics; + +import org.elasticsearch.action.search.SearchType; +import org.elasticsearch.cluster.metadata.IndexMetadata; +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.core.TimeValue; +import org.elasticsearch.indices.ExecutorNames; +import org.elasticsearch.indices.SystemIndexDescriptor; +import org.elasticsearch.plugins.Plugin; +import org.elasticsearch.plugins.PluginsService; +import org.elasticsearch.plugins.SystemIndexPlugin; +import org.elasticsearch.telemetry.Measurement; +import org.elasticsearch.telemetry.TestTelemetryPlugin; +import org.elasticsearch.test.ESSingleNodeTestCase; +import org.junit.After; +import org.junit.Before; + +import java.util.Collection; +import java.util.List; +import java.util.stream.Stream; + +import static org.elasticsearch.action.support.WriteRequest.RefreshPolicy.IMMEDIATE; +import static org.elasticsearch.index.query.QueryBuilders.simpleQueryStringQuery; +import static org.elasticsearch.index.search.stats.ShardSearchPhaseAPMMetrics.FETCH_SEARCH_PHASE_METRIC; +import static org.elasticsearch.index.search.stats.ShardSearchPhaseAPMMetrics.QUERY_SEARCH_PHASE_METRIC; +import static org.elasticsearch.index.search.stats.ShardSearchPhaseAPMMetrics.SYSTEM_THREAD_ATTRIBUTE_NAME; +import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertScrollResponsesAndHitCount; +import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertSearchHitsWithoutFailures; + +public class ShardSearchPhaseAPMMetricsTests extends ESSingleNodeTestCase { + + private static final String indexName = "test_search_metrics2"; + private final int num_primaries = randomIntBetween(2, 7); + + @Override + protected boolean resetNodeAfterTest() { + return true; + } + + @Before + private void setUpIndex() throws Exception { + createIndex( + indexName, + Settings.builder() + .put(IndexMetadata.SETTING_NUMBER_OF_SHARDS, num_primaries) + .put(IndexMetadata.SETTING_NUMBER_OF_REPLICAS, 0) + .build() + ); + ensureGreen(indexName); + + prepareIndex(indexName).setId("1").setSource("body", "doc1").setRefreshPolicy(IMMEDIATE).get(); + prepareIndex(indexName).setId("2").setSource("body", "doc2").setRefreshPolicy(IMMEDIATE).get(); + + prepareIndex(TestSystemIndexPlugin.INDEX_NAME).setId("1").setSource("body", "doc1").setRefreshPolicy(IMMEDIATE).get(); + prepareIndex(TestSystemIndexPlugin.INDEX_NAME).setId("2").setSource("body", "doc2").setRefreshPolicy(IMMEDIATE).get(); + } + + @After + private void afterTest() { + resetMeter(); + } + + @Override + protected Collection> getPlugins() { + return pluginList(TestTelemetryPlugin.class, TestSystemIndexPlugin.class); + } + + public void testMetricsDfsQueryThenFetch() throws InterruptedException { + checkMetricsDfsQueryThenFetch(indexName, false); + } + + public void testMetricsDfsQueryThenFetchSystem() throws InterruptedException { + checkMetricsDfsQueryThenFetch(TestSystemIndexPlugin.INDEX_NAME, true); + } + + private void checkMetricsDfsQueryThenFetch(String indexName, boolean isSystemIndex) throws InterruptedException { + assertSearchHitsWithoutFailures( + client().prepareSearch(indexName).setSearchType(SearchType.DFS_QUERY_THEN_FETCH).setQuery(simpleQueryStringQuery("doc1")), + "1" + ); + checkNumberOfMeasurementsForPhase(QUERY_SEARCH_PHASE_METRIC, isSystemIndex); + assertNotEquals(0, getNumberOfMeasurementsForPhase(FETCH_SEARCH_PHASE_METRIC)); + checkMetricsAttributes(isSystemIndex); + } + + public void testSearchTransportMetricsQueryThenFetch() throws InterruptedException { + checkSearchTransportMetricsQueryThenFetch(indexName, false); + } + + public void testSearchTransportMetricsQueryThenFetchSystem() throws InterruptedException { + checkSearchTransportMetricsQueryThenFetch(TestSystemIndexPlugin.INDEX_NAME, true); + } + + private void checkSearchTransportMetricsQueryThenFetch(String indexName, boolean isSystemIndex) throws InterruptedException { + assertSearchHitsWithoutFailures( + client().prepareSearch(indexName).setSearchType(SearchType.QUERY_THEN_FETCH).setQuery(simpleQueryStringQuery("doc1")), + "1" + ); + checkNumberOfMeasurementsForPhase(QUERY_SEARCH_PHASE_METRIC, isSystemIndex); + assertNotEquals(0, getNumberOfMeasurementsForPhase(FETCH_SEARCH_PHASE_METRIC)); + checkMetricsAttributes(isSystemIndex); + } + + public void testSearchTransportMetricsScroll() throws InterruptedException { + checkSearchTransportMetricsScroll(indexName, false); + } + + public void testSearchTransportMetricsScrollSystem() throws InterruptedException { + checkSearchTransportMetricsScroll(TestSystemIndexPlugin.INDEX_NAME, true); + } + + private void checkSearchTransportMetricsScroll(String indexName, boolean isSystemIndex) throws InterruptedException { + assertScrollResponsesAndHitCount( + client(), + TimeValue.timeValueSeconds(60), + client().prepareSearch(indexName) + .setSearchType(SearchType.DFS_QUERY_THEN_FETCH) + .setSize(1) + .setQuery(simpleQueryStringQuery("doc1 doc2")), + 2, + (respNum, response) -> { + // No hits, no fetching done + assertEquals(isSystemIndex ? 1 : num_primaries, getNumberOfMeasurementsForPhase(QUERY_SEARCH_PHASE_METRIC)); + if (response.getHits().getHits().length > 0) { + assertNotEquals(0, getNumberOfMeasurementsForPhase(FETCH_SEARCH_PHASE_METRIC)); + } else { + assertEquals(isSystemIndex ? 1 : 0, getNumberOfMeasurementsForPhase(FETCH_SEARCH_PHASE_METRIC)); + } + checkMetricsAttributes(isSystemIndex); + resetMeter(); + } + ); + + } + + private void resetMeter() { + getTestTelemetryPlugin().resetMeter(); + } + + private TestTelemetryPlugin getTestTelemetryPlugin() { + return getInstanceFromNode(PluginsService.class).filterPlugins(TestTelemetryPlugin.class).toList().get(0); + } + + private void checkNumberOfMeasurementsForPhase(String phase, boolean isSystemIndex) { + int numMeasurements = getNumberOfMeasurementsForPhase(phase); + assertEquals(isSystemIndex ? 1 : num_primaries, numMeasurements); + } + + private int getNumberOfMeasurementsForPhase(String phase) { + final List measurements = getTestTelemetryPlugin().getLongHistogramMeasurement(phase); + return measurements.size(); + } + + private void checkMetricsAttributes(boolean isSystem) { + final List queryMeasurements = getTestTelemetryPlugin().getLongHistogramMeasurement(QUERY_SEARCH_PHASE_METRIC); + final List fetchMeasurements = getTestTelemetryPlugin().getLongHistogramMeasurement(QUERY_SEARCH_PHASE_METRIC); + assertTrue( + Stream.concat(queryMeasurements.stream(), fetchMeasurements.stream()).allMatch(m -> checkMeasurementAttributes(m, isSystem)) + ); + } + + private boolean checkMeasurementAttributes(Measurement m, boolean isSystem) { + return ((boolean) m.attributes().get(SYSTEM_THREAD_ATTRIBUTE_NAME)) == isSystem; + } + + public static class TestSystemIndexPlugin extends Plugin implements SystemIndexPlugin { + + static final String INDEX_NAME = ".test-system-index"; + + public TestSystemIndexPlugin() {} + + @Override + public Collection getSystemIndexDescriptors(Settings settings) { + return List.of( + SystemIndexDescriptor.builder() + .setIndexPattern(INDEX_NAME + "*") + .setPrimaryIndex(INDEX_NAME) + .setSettings( + Settings.builder() + .put(IndexMetadata.SETTING_NUMBER_OF_SHARDS, 1) + .put(IndexMetadata.SETTING_NUMBER_OF_REPLICAS, 0) + .build() + ) + .setMappings(""" + { + "_meta": { + "version": "8.0.0", + "managed_index_mappings_version": 3 + }, + "properties": { + "body": { "type": "keyword" } + } + } + """) + .setThreadPools(ExecutorNames.DEFAULT_SYSTEM_INDEX_THREAD_POOLS) + .setOrigin(ShardSearchPhaseAPMMetricsTests.class.getSimpleName()) + .build() + ); + } + + @Override + public String getFeatureName() { + return ShardSearchPhaseAPMMetricsTests.class.getSimpleName(); + } + + @Override + public String getFeatureDescription() { + return "test plugin"; + } + } +} diff --git a/server/src/test/java/org/elasticsearch/snapshots/SnapshotResiliencyTests.java b/server/src/test/java/org/elasticsearch/snapshots/SnapshotResiliencyTests.java index e0363d84ea4d2..077877f713571 100644 --- a/server/src/test/java/org/elasticsearch/snapshots/SnapshotResiliencyTests.java +++ b/server/src/test/java/org/elasticsearch/snapshots/SnapshotResiliencyTests.java @@ -55,7 +55,6 @@ import org.elasticsearch.action.search.SearchPhaseController; import org.elasticsearch.action.search.SearchRequest; import org.elasticsearch.action.search.SearchResponse; -import org.elasticsearch.action.search.SearchTransportAPMMetrics; import org.elasticsearch.action.search.SearchTransportService; import org.elasticsearch.action.search.TransportSearchAction; import org.elasticsearch.action.support.ActionFilters; @@ -2492,7 +2491,6 @@ public RecyclerBytesStreamOutput newNetworkBytesStream() { indexNameExpressionResolver, namedWriteableRegistry, EmptySystemIndices.INSTANCE.getExecutorSelector(), - new SearchTransportAPMMetrics(TelemetryProvider.NOOP.getMeterRegistry()), new SearchResponseMetrics(TelemetryProvider.NOOP.getMeterRegistry()), client, usageService diff --git a/test/framework/src/main/java/org/elasticsearch/cluster/ClusterInfoServiceUtils.java b/test/framework/src/main/java/org/elasticsearch/cluster/ClusterInfoServiceUtils.java index b4b35c0487d6e..bd93700fd4137 100644 --- a/test/framework/src/main/java/org/elasticsearch/cluster/ClusterInfoServiceUtils.java +++ b/test/framework/src/main/java/org/elasticsearch/cluster/ClusterInfoServiceUtils.java @@ -13,6 +13,7 @@ import org.apache.logging.log4j.Logger; import org.elasticsearch.action.support.PlainActionFuture; import org.elasticsearch.cluster.service.ClusterApplierService; +import org.elasticsearch.core.TimeValue; import java.util.concurrent.TimeUnit; @@ -37,4 +38,8 @@ protected boolean blockingAllowed() { throw new AssertionError(e); } } + + public static void setUpdateFrequency(InternalClusterInfoService internalClusterInfoService, TimeValue updateFrequency) { + internalClusterInfoService.setUpdateFrequency(updateFrequency); + } } diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/application/EnterpriseSearchFeatureSetUsage.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/application/EnterpriseSearchFeatureSetUsage.java index b1dac4898945d..a054a18221e9b 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/application/EnterpriseSearchFeatureSetUsage.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/application/EnterpriseSearchFeatureSetUsage.java @@ -34,6 +34,7 @@ public class EnterpriseSearchFeatureSetUsage extends XPackFeatureUsage { public static final String MIN_RULE_COUNT = "min_rule_count"; public static final String MAX_RULE_COUNT = "max_rule_count"; public static final String RULE_CRITERIA_TOTAL_COUNTS = "rule_criteria_total_counts"; + public static final String RULE_TYPE_TOTAL_COUNTS = "rule_type_total_counts"; private final Map searchApplicationsUsage; private final Map analyticsCollectionsUsage; diff --git a/x-pack/plugin/ent-search/qa/rest/src/yamlRestTest/resources/rest-api-spec/test/entsearch/rules/20_query_ruleset_list.yml b/x-pack/plugin/ent-search/qa/rest/src/yamlRestTest/resources/rest-api-spec/test/entsearch/rules/20_query_ruleset_list.yml index 172d38cce5384..0b98182b39602 100644 --- a/x-pack/plugin/ent-search/qa/rest/src/yamlRestTest/resources/rest-api-spec/test/entsearch/rules/20_query_ruleset_list.yml +++ b/x-pack/plugin/ent-search/qa/rest/src/yamlRestTest/resources/rest-api-spec/test/entsearch/rules/20_query_ruleset_list.yml @@ -1,7 +1,4 @@ setup: - - requires: - cluster_features: [ "gte_v8.10.0" ] - reason: Introduced in 8.10.0 - do: query_rules.put_ruleset: ruleset_id: test-query-ruleset-3 @@ -222,7 +219,7 @@ teardown: body: rules: - rule_id: query-rule-id1 - type: pinned + type: exclude criteria: - type: exact metadata: query_string @@ -307,3 +304,89 @@ teardown: - match: { error.type: 'security_exception' } +--- +'List query rulesets - include rule types': + - requires: + cluster_features: [ "query_rule_list_types" ] + reason: 'List responses updated in 8.15.5 and 8.16.1' + + - do: + query_rules.put_ruleset: + ruleset_id: a-test-query-ruleset-with-lots-of-criteria + body: + rules: + - rule_id: query-rule-id1 + type: exclude + criteria: + - type: exact + metadata: query_string + values: [ puggles ] + - type: gt + metadata: year + values: [ 2023 ] + actions: + ids: + - 'id1' + - 'id2' + - rule_id: query-rule-id2 + type: pinned + criteria: + - type: exact + metadata: query_string + values: [ pug ] + actions: + ids: + - 'id3' + - 'id4' + - rule_id: query-rule-id3 + type: pinned + criteria: + - type: fuzzy + metadata: query_string + values: [ puggles ] + actions: + ids: + - 'id5' + - 'id6' + - rule_id: query-rule-id4 + type: pinned + criteria: + - type: always + actions: + ids: + - 'id7' + - 'id8' + - rule_id: query-rule-id5 + type: pinned + criteria: + - type: prefix + metadata: query_string + values: [ pug ] + - type: suffix + metadata: query_string + values: [ gle ] + actions: + ids: + - 'id9' + - 'id10' + + - do: + query_rules.list_rulesets: + from: 0 + size: 1 + + - match: { count: 4 } + + # Alphabetical order by ruleset_id for results + - match: { results.0.ruleset_id: "a-test-query-ruleset-with-lots-of-criteria" } + - match: { results.0.rule_total_count: 5 } + - match: + results.0.rule_criteria_types_counts: + exact: 2 + gt: 1 + fuzzy: 1 + prefix: 1 + suffix: 1 + always: 1 + - match: { results.0.rule_type_counts: { pinned: 4, exclude: 1 } } + diff --git a/x-pack/plugin/ent-search/src/main/java/org/elasticsearch/xpack/application/EnterpriseSearchFeatures.java b/x-pack/plugin/ent-search/src/main/java/org/elasticsearch/xpack/application/EnterpriseSearchFeatures.java index ae8e63bdb6420..86882a28ec39f 100644 --- a/x-pack/plugin/ent-search/src/main/java/org/elasticsearch/xpack/application/EnterpriseSearchFeatures.java +++ b/x-pack/plugin/ent-search/src/main/java/org/elasticsearch/xpack/application/EnterpriseSearchFeatures.java @@ -12,6 +12,7 @@ import org.elasticsearch.features.NodeFeature; import org.elasticsearch.xpack.application.analytics.AnalyticsTemplateRegistry; import org.elasticsearch.xpack.application.connector.ConnectorTemplateRegistry; +import org.elasticsearch.xpack.application.rules.action.ListQueryRulesetsAction; import org.elasticsearch.xpack.application.rules.retriever.QueryRuleRetrieverBuilder; import java.util.Map; @@ -23,7 +24,11 @@ public class EnterpriseSearchFeatures implements FeatureSpecification { @Override public Set getFeatures() { - return Set.of(QUERY_RULES_TEST_API, QueryRuleRetrieverBuilder.QUERY_RULE_RETRIEVERS_SUPPORTED); + return Set.of( + QUERY_RULES_TEST_API, + QueryRuleRetrieverBuilder.QUERY_RULE_RETRIEVERS_SUPPORTED, + ListQueryRulesetsAction.QUERY_RULE_LIST_TYPES + ); } @Override diff --git a/x-pack/plugin/ent-search/src/main/java/org/elasticsearch/xpack/application/EnterpriseSearchUsageTransportAction.java b/x-pack/plugin/ent-search/src/main/java/org/elasticsearch/xpack/application/EnterpriseSearchUsageTransportAction.java index c079892ccb2b6..7683ea7cb28a7 100644 --- a/x-pack/plugin/ent-search/src/main/java/org/elasticsearch/xpack/application/EnterpriseSearchUsageTransportAction.java +++ b/x-pack/plugin/ent-search/src/main/java/org/elasticsearch/xpack/application/EnterpriseSearchUsageTransportAction.java @@ -27,7 +27,6 @@ import org.elasticsearch.threadpool.ThreadPool; import org.elasticsearch.transport.TransportService; import org.elasticsearch.xpack.application.analytics.action.GetAnalyticsCollectionAction; -import org.elasticsearch.xpack.application.rules.QueryRuleCriteriaType; import org.elasticsearch.xpack.application.rules.QueryRulesIndexService; import org.elasticsearch.xpack.application.rules.QueryRulesetListItem; import org.elasticsearch.xpack.application.rules.action.ListQueryRulesetsAction; @@ -41,7 +40,6 @@ import org.elasticsearch.xpack.core.application.EnterpriseSearchFeatureSetUsage; import java.util.Collections; -import java.util.EnumMap; import java.util.HashMap; import java.util.IntSummaryStatistics; import java.util.List; @@ -226,20 +224,29 @@ private void addQueryRulesetUsage(ListQueryRulesetsAction.Response response, Map List results = response.queryPage().results(); IntSummaryStatistics ruleStats = results.stream().mapToInt(QueryRulesetListItem::ruleTotalCount).summaryStatistics(); - Map criteriaTypeCountMap = new EnumMap<>(QueryRuleCriteriaType.class); - results.stream() - .flatMap(result -> result.criteriaTypeToCountMap().entrySet().stream()) - .forEach(entry -> criteriaTypeCountMap.merge(entry.getKey(), entry.getValue(), Integer::sum)); + Map ruleCriteriaTypeCountMap = new HashMap<>(); + Map ruleTypeCountMap = new HashMap<>(); - Map rulesTypeCountMap = new HashMap<>(); - criteriaTypeCountMap.forEach((criteriaType, count) -> rulesTypeCountMap.put(criteriaType.name().toLowerCase(Locale.ROOT), count)); + results.forEach(result -> { + populateCounts(ruleCriteriaTypeCountMap, result.criteriaTypeToCountMap()); + populateCounts(ruleTypeCountMap, result.ruleTypeToCountMap()); + }); queryRulesUsage.put(TOTAL_COUNT, response.queryPage().count()); queryRulesUsage.put(TOTAL_RULE_COUNT, ruleStats.getSum()); queryRulesUsage.put(MIN_RULE_COUNT, results.isEmpty() ? 0 : ruleStats.getMin()); queryRulesUsage.put(MAX_RULE_COUNT, results.isEmpty() ? 0 : ruleStats.getMax()); - if (rulesTypeCountMap.isEmpty() == false) { - queryRulesUsage.put(RULE_CRITERIA_TOTAL_COUNTS, rulesTypeCountMap); + if (ruleCriteriaTypeCountMap.isEmpty() == false) { + queryRulesUsage.put(RULE_CRITERIA_TOTAL_COUNTS, ruleCriteriaTypeCountMap); + } + if (ruleTypeCountMap.isEmpty() == false) { + queryRulesUsage.put(EnterpriseSearchFeatureSetUsage.RULE_TYPE_TOTAL_COUNTS, ruleTypeCountMap); } } + + private void populateCounts(Map targetMap, Map, Integer> sourceMap) { + sourceMap.forEach( + (key, value) -> targetMap.merge(key.name().toLowerCase(Locale.ROOT), value, (v1, v2) -> (Integer) v1 + (Integer) v2) + ); + } } diff --git a/x-pack/plugin/ent-search/src/main/java/org/elasticsearch/xpack/application/rules/QueryRulesIndexService.java b/x-pack/plugin/ent-search/src/main/java/org/elasticsearch/xpack/application/rules/QueryRulesIndexService.java index 3ce51ae5d832d..9b264a2cc41cf 100644 --- a/x-pack/plugin/ent-search/src/main/java/org/elasticsearch/xpack/application/rules/QueryRulesIndexService.java +++ b/x-pack/plugin/ent-search/src/main/java/org/elasticsearch/xpack/application/rules/QueryRulesIndexService.java @@ -445,6 +445,7 @@ private static QueryRulesetListItem hitToQueryRulesetListItem(SearchHit searchHi final List> rules = ((List>) sourceMap.get(QueryRuleset.RULES_FIELD.getPreferredName())); final int numRules = rules.size(); final Map queryRuleCriteriaTypeToCountMap = new EnumMap<>(QueryRuleCriteriaType.class); + final Map ruleTypeToCountMap = new EnumMap<>(QueryRule.QueryRuleType.class); for (LinkedHashMap rule : rules) { @SuppressWarnings("unchecked") List> criteriaList = ((List>) rule.get(QueryRule.CRITERIA_FIELD.getPreferredName())); @@ -453,9 +454,12 @@ private static QueryRulesetListItem hitToQueryRulesetListItem(SearchHit searchHi final QueryRuleCriteriaType queryRuleCriteriaType = QueryRuleCriteriaType.type(criteriaType); queryRuleCriteriaTypeToCountMap.compute(queryRuleCriteriaType, (k, v) -> v == null ? 1 : v + 1); } + final String ruleType = ((String) rule.get(QueryRule.TYPE_FIELD.getPreferredName())); + final QueryRule.QueryRuleType queryRuleType = QueryRule.QueryRuleType.queryRuleType(ruleType); + ruleTypeToCountMap.compute(queryRuleType, (k, v) -> v == null ? 1 : v + 1); } - return new QueryRulesetListItem(rulesetId, numRules, queryRuleCriteriaTypeToCountMap); + return new QueryRulesetListItem(rulesetId, numRules, queryRuleCriteriaTypeToCountMap, ruleTypeToCountMap); } public record QueryRulesetResult(List rulesets, long totalResults) {} diff --git a/x-pack/plugin/ent-search/src/main/java/org/elasticsearch/xpack/application/rules/QueryRulesetListItem.java b/x-pack/plugin/ent-search/src/main/java/org/elasticsearch/xpack/application/rules/QueryRulesetListItem.java index f3bc07387512f..a5e2d3f79da0e 100644 --- a/x-pack/plugin/ent-search/src/main/java/org/elasticsearch/xpack/application/rules/QueryRulesetListItem.java +++ b/x-pack/plugin/ent-search/src/main/java/org/elasticsearch/xpack/application/rules/QueryRulesetListItem.java @@ -32,10 +32,12 @@ public class QueryRulesetListItem implements Writeable, ToXContentObject { public static final ParseField RULESET_ID_FIELD = new ParseField("ruleset_id"); public static final ParseField RULE_TOTAL_COUNT_FIELD = new ParseField("rule_total_count"); public static final ParseField RULE_CRITERIA_TYPE_COUNTS_FIELD = new ParseField("rule_criteria_types_counts"); + public static final ParseField RULE_TYPE_COUNTS_FIELD = new ParseField("rule_type_counts"); private final String rulesetId; private final int ruleTotalCount; private final Map criteriaTypeToCountMap; + private final Map ruleTypeToCountMap; /** * Constructs a QueryRulesetListItem. @@ -44,11 +46,17 @@ public class QueryRulesetListItem implements Writeable, ToXContentObject { * @param ruleTotalCount The number of rules contained within the ruleset. * @param criteriaTypeToCountMap A map of criteria type to the number of rules of that type. */ - public QueryRulesetListItem(String rulesetId, int ruleTotalCount, Map criteriaTypeToCountMap) { + public QueryRulesetListItem( + String rulesetId, + int ruleTotalCount, + Map criteriaTypeToCountMap, + Map ruleTypeToCountMap + ) { Objects.requireNonNull(rulesetId, "rulesetId cannot be null on a QueryRuleListItem"); this.rulesetId = rulesetId; this.ruleTotalCount = ruleTotalCount; this.criteriaTypeToCountMap = criteriaTypeToCountMap; + this.ruleTypeToCountMap = ruleTypeToCountMap; } public QueryRulesetListItem(StreamInput in) throws IOException { @@ -59,6 +67,11 @@ public QueryRulesetListItem(StreamInput in) throws IOException { } else { this.criteriaTypeToCountMap = Map.of(); } + if (in.getTransportVersion().onOrAfter(TransportVersions.QUERY_RULES_LIST_INCLUDES_TYPES)) { + this.ruleTypeToCountMap = in.readMap(m -> in.readEnum(QueryRule.QueryRuleType.class), StreamInput::readInt); + } else { + this.ruleTypeToCountMap = Map.of(); + } } @Override @@ -71,6 +84,11 @@ public XContentBuilder toXContent(XContentBuilder builder, Params params) throws builder.field(criteriaType.name().toLowerCase(Locale.ROOT), criteriaTypeToCountMap.get(criteriaType)); } builder.endObject(); + builder.startObject(RULE_TYPE_COUNTS_FIELD.getPreferredName()); + for (QueryRule.QueryRuleType ruleType : ruleTypeToCountMap.keySet()) { + builder.field(ruleType.name().toLowerCase(Locale.ROOT), ruleTypeToCountMap.get(ruleType)); + } + builder.endObject(); builder.endObject(); return builder; } @@ -82,6 +100,9 @@ public void writeTo(StreamOutput out) throws IOException { if (out.getTransportVersion().onOrAfter(EXPANDED_RULESET_COUNT_TRANSPORT_VERSION)) { out.writeMap(criteriaTypeToCountMap, StreamOutput::writeEnum, StreamOutput::writeInt); } + if (out.getTransportVersion().onOrAfter(TransportVersions.QUERY_RULES_LIST_INCLUDES_TYPES)) { + out.writeMap(ruleTypeToCountMap, StreamOutput::writeEnum, StreamOutput::writeInt); + } } /** @@ -106,6 +127,10 @@ public Map criteriaTypeToCountMap() { return criteriaTypeToCountMap; } + public Map ruleTypeToCountMap() { + return ruleTypeToCountMap; + } + @Override public boolean equals(Object o) { if (this == o) return true; @@ -113,11 +138,12 @@ public boolean equals(Object o) { QueryRulesetListItem that = (QueryRulesetListItem) o; return ruleTotalCount == that.ruleTotalCount && Objects.equals(rulesetId, that.rulesetId) - && Objects.equals(criteriaTypeToCountMap, that.criteriaTypeToCountMap); + && Objects.equals(criteriaTypeToCountMap, that.criteriaTypeToCountMap) + && Objects.equals(ruleTypeToCountMap, that.ruleTypeToCountMap); } @Override public int hashCode() { - return Objects.hash(rulesetId, ruleTotalCount, criteriaTypeToCountMap); + return Objects.hash(rulesetId, ruleTotalCount, criteriaTypeToCountMap, ruleTypeToCountMap); } } diff --git a/x-pack/plugin/ent-search/src/main/java/org/elasticsearch/xpack/application/rules/action/ListQueryRulesetsAction.java b/x-pack/plugin/ent-search/src/main/java/org/elasticsearch/xpack/application/rules/action/ListQueryRulesetsAction.java index 11397583ce5b9..62f9f3fd46cc4 100644 --- a/x-pack/plugin/ent-search/src/main/java/org/elasticsearch/xpack/application/rules/action/ListQueryRulesetsAction.java +++ b/x-pack/plugin/ent-search/src/main/java/org/elasticsearch/xpack/application/rules/action/ListQueryRulesetsAction.java @@ -13,6 +13,7 @@ import org.elasticsearch.action.ActionType; import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.io.stream.StreamOutput; +import org.elasticsearch.features.NodeFeature; import org.elasticsearch.xcontent.ConstructingObjectParser; import org.elasticsearch.xcontent.ParseField; import org.elasticsearch.xcontent.ToXContentObject; @@ -33,6 +34,8 @@ public class ListQueryRulesetsAction { public static final String NAME = "cluster:admin/xpack/query_rules/list"; public static final ActionType INSTANCE = new ActionType<>(NAME); + public static final NodeFeature QUERY_RULE_LIST_TYPES = new NodeFeature("query_rule_list_types"); + private ListQueryRulesetsAction() {/* no instances */} public static class Request extends ActionRequest implements ToXContentObject { diff --git a/x-pack/plugin/ent-search/src/test/java/org/elasticsearch/xpack/application/rules/action/ListQueryRulesetsActionResponseBWCSerializingTests.java b/x-pack/plugin/ent-search/src/test/java/org/elasticsearch/xpack/application/rules/action/ListQueryRulesetsActionResponseBWCSerializingTests.java index 5ae0f51cb6112..27ac214558f89 100644 --- a/x-pack/plugin/ent-search/src/test/java/org/elasticsearch/xpack/application/rules/action/ListQueryRulesetsActionResponseBWCSerializingTests.java +++ b/x-pack/plugin/ent-search/src/test/java/org/elasticsearch/xpack/application/rules/action/ListQueryRulesetsActionResponseBWCSerializingTests.java @@ -8,8 +8,10 @@ package org.elasticsearch.xpack.application.rules.action; import org.elasticsearch.TransportVersion; +import org.elasticsearch.TransportVersions; import org.elasticsearch.common.io.stream.Writeable; import org.elasticsearch.xpack.application.EnterpriseSearchModuleTestUtils; +import org.elasticsearch.xpack.application.rules.QueryRule; import org.elasticsearch.xpack.application.rules.QueryRuleCriteriaType; import org.elasticsearch.xpack.application.rules.QueryRuleset; import org.elasticsearch.xpack.application.rules.QueryRulesetListItem; @@ -32,9 +34,13 @@ private static ListQueryRulesetsAction.Response randomQueryRulesetListItem() { QueryRuleset queryRuleset = EnterpriseSearchModuleTestUtils.randomQueryRuleset(); Map criteriaTypeToCountMap = Map.of( randomFrom(QueryRuleCriteriaType.values()), - randomIntBetween(0, 10) + randomIntBetween(1, 10) ); - return new QueryRulesetListItem(queryRuleset.id(), queryRuleset.rules().size(), criteriaTypeToCountMap); + Map ruleTypeToCountMap = Map.of( + randomFrom(QueryRule.QueryRuleType.values()), + randomIntBetween(1, 10) + ); + return new QueryRulesetListItem(queryRuleset.id(), queryRuleset.rules().size(), criteriaTypeToCountMap, ruleTypeToCountMap); }), randomLongBetween(0, 1000)); } @@ -53,12 +59,20 @@ protected ListQueryRulesetsAction.Response mutateInstanceForVersion( ListQueryRulesetsAction.Response instance, TransportVersion version ) { - if (version.onOrAfter(QueryRulesetListItem.EXPANDED_RULESET_COUNT_TRANSPORT_VERSION)) { + if (version.onOrAfter(TransportVersions.QUERY_RULES_LIST_INCLUDES_TYPES)) { return instance; + } else if (version.onOrAfter(QueryRulesetListItem.EXPANDED_RULESET_COUNT_TRANSPORT_VERSION)) { + List updatedResults = new ArrayList<>(); + for (QueryRulesetListItem listItem : instance.queryPage.results()) { + updatedResults.add( + new QueryRulesetListItem(listItem.rulesetId(), listItem.ruleTotalCount(), listItem.criteriaTypeToCountMap(), Map.of()) + ); + } + return new ListQueryRulesetsAction.Response(updatedResults, instance.queryPage.count()); } else { List updatedResults = new ArrayList<>(); for (QueryRulesetListItem listItem : instance.queryPage.results()) { - updatedResults.add(new QueryRulesetListItem(listItem.rulesetId(), listItem.ruleTotalCount(), Map.of())); + updatedResults.add(new QueryRulesetListItem(listItem.rulesetId(), listItem.ruleTotalCount(), Map.of(), Map.of())); } return new ListQueryRulesetsAction.Response(updatedResults, instance.queryPage.count()); } 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 c0e55992df88f..5c6c3e8c7933c 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 @@ -377,7 +377,8 @@ public void testOnIndexModuleIsNoOpWithSecurityDisabled() throws Exception { TestIndexNameExpressionResolver.newInstance(threadPool.getThreadContext()), Collections.emptyMap(), mock(SlowLogFieldProvider.class), - MapperMetrics.NOOP + MapperMetrics.NOOP, + List.of() ); 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 70896a67a9468..e8d6a2868a496 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 @@ -70,7 +70,8 @@ public void testWatcherDisabledTests() throws Exception { TestIndexNameExpressionResolver.newInstance(), Collections.emptyMap(), mock(SlowLogFieldProvider.class), - MapperMetrics.NOOP + MapperMetrics.NOOP, + List.of() ); // 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);