From 43bdac78156701d2562feed7f9eb0b4b04c481ab Mon Sep 17 00:00:00 2001 From: Kathleen DeRusso Date: Mon, 22 Sep 2025 15:43:52 -0400 Subject: [PATCH 01/26] Add extended telemetry --- .../admin/cluster/stats/SearchUsageStats.java | 26 ++++- .../cluster/stats/extended/ExtendedData.java | 101 ++++++++++++++++++ .../admin/cluster/RestClusterStatsAction.java | 3 +- .../action/search/SearchCapabilities.java | 2 + .../search/builder/SearchSourceBuilder.java | 2 + .../search/retriever/RetrieverBuilder.java | 6 ++ .../retriever/RetrieverParserContext.java | 5 + .../org/elasticsearch/usage/SearchUsage.java | 34 +++++- .../usage/SearchUsageHolder.java | 29 +++++ .../extended_search_usage_telemetry.csv | 1 + .../resources/transport/upper_bounds/9.2.csv | 2 +- .../TextSimilarityRankRetrieverBuilder.java | 14 +++ .../ClusterStatsMonitoringDocTests.java | 3 +- 13 files changed, 220 insertions(+), 8 deletions(-) create mode 100644 server/src/main/java/org/elasticsearch/action/admin/cluster/stats/extended/ExtendedData.java create mode 100644 server/src/main/resources/transport/definitions/referable/extended_search_usage_telemetry.csv diff --git a/server/src/main/java/org/elasticsearch/action/admin/cluster/stats/SearchUsageStats.java b/server/src/main/java/org/elasticsearch/action/admin/cluster/stats/SearchUsageStats.java index a6e80b5efd08c..7023545a8f54f 100644 --- a/server/src/main/java/org/elasticsearch/action/admin/cluster/stats/SearchUsageStats.java +++ b/server/src/main/java/org/elasticsearch/action/admin/cluster/stats/SearchUsageStats.java @@ -9,6 +9,8 @@ package org.elasticsearch.action.admin.cluster.stats; +import org.elasticsearch.TransportVersion; +import org.elasticsearch.action.admin.cluster.stats.extended.ExtendedData; import org.elasticsearch.common.Strings; import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.io.stream.StreamOutput; @@ -31,11 +33,15 @@ * accumulate stats for the entire cluster and return them as part of the {@link ClusterStatsResponse}. */ public final class SearchUsageStats implements Writeable, ToXContentFragment { + + private static final TransportVersion EXTENDED_SEARCH_USAGE_TELEMETRY = TransportVersion.fromName("extended_search_usage_telemetry"); + private long totalSearchCount; private final Map queries; private final Map rescorers; private final Map sections; private final Map retrievers; + private final ExtendedData extendedData; /** * Creates a new empty stats instance, that will get additional stats added through {@link #add(SearchUsageStats)} @@ -46,6 +52,7 @@ public SearchUsageStats() { this.sections = new HashMap<>(); this.rescorers = new HashMap<>(); this.retrievers = new HashMap<>(); + this.extendedData = new ExtendedData(); } /** @@ -57,6 +64,7 @@ public SearchUsageStats( Map rescorers, Map sections, Map retrievers, + ExtendedData extendedData, long totalSearchCount ) { this.totalSearchCount = totalSearchCount; @@ -64,6 +72,7 @@ public SearchUsageStats( this.sections = sections; this.rescorers = rescorers; this.retrievers = retrievers; + this.extendedData = extendedData; } public SearchUsageStats(StreamInput in) throws IOException { @@ -72,6 +81,8 @@ public SearchUsageStats(StreamInput in) throws IOException { this.totalSearchCount = in.readVLong(); this.rescorers = in.getTransportVersion().onOrAfter(V_8_12_0) ? in.readMap(StreamInput::readLong) : Map.of(); this.retrievers = in.getTransportVersion().onOrAfter(V_8_16_0) ? in.readMap(StreamInput::readLong) : Map.of(); + this.extendedData = + in.getTransportVersion().supports(EXTENDED_SEARCH_USAGE_TELEMETRY) ? new ExtendedData(in) : new ExtendedData(); } @Override @@ -86,6 +97,9 @@ public void writeTo(StreamOutput out) throws IOException { if (out.getTransportVersion().onOrAfter(V_8_16_0)) { out.writeMap(retrievers, StreamOutput::writeLong); } + if (out.getTransportVersion().supports(EXTENDED_SEARCH_USAGE_TELEMETRY)) { + extendedData.writeTo(out); + } } /** @@ -96,6 +110,7 @@ public void add(SearchUsageStats stats) { stats.rescorers.forEach((rescorer, count) -> rescorers.merge(rescorer, count, Long::sum)); stats.sections.forEach((query, count) -> sections.merge(query, count, Long::sum)); stats.retrievers.forEach((query, count) -> retrievers.merge(query, count, Long::sum)); + this.extendedData.merge(stats.extendedData); this.totalSearchCount += stats.totalSearchCount; } @@ -112,6 +127,8 @@ public XContentBuilder toXContent(XContentBuilder builder, Params params) throws builder.map(sections); builder.field("retrievers"); builder.map(retrievers); + builder.field("extended"); + extendedData.toXContent(builder, params); } builder.endObject(); return builder; @@ -133,6 +150,10 @@ public Map getRetrieversUsage() { return Collections.unmodifiableMap(retrievers); } + public ExtendedData getExtendedData() { + return extendedData; + } + public long getTotalSearchCount() { return totalSearchCount; } @@ -150,12 +171,13 @@ public boolean equals(Object o) { && queries.equals(that.queries) && rescorers.equals(that.rescorers) && sections.equals(that.sections) - && retrievers.equals(that.retrievers); + && retrievers.equals(that.retrievers) + && extendedData.equals(that.extendedData); } @Override public int hashCode() { - return Objects.hash(totalSearchCount, queries, rescorers, sections, retrievers); + return Objects.hash(totalSearchCount, queries, rescorers, sections, retrievers, extendedData); } @Override diff --git a/server/src/main/java/org/elasticsearch/action/admin/cluster/stats/extended/ExtendedData.java b/server/src/main/java/org/elasticsearch/action/admin/cluster/stats/extended/ExtendedData.java new file mode 100644 index 0000000000000..5ee77ef5bff50 --- /dev/null +++ b/server/src/main/java/org/elasticsearch/action/admin/cluster/stats/extended/ExtendedData.java @@ -0,0 +1,101 @@ +/* + * 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.admin.cluster.stats.extended; + +import org.elasticsearch.common.io.stream.StreamInput; +import org.elasticsearch.common.io.stream.StreamOutput; +import org.elasticsearch.common.io.stream.Writeable; +import org.elasticsearch.xcontent.ToXContent; +import org.elasticsearch.xcontent.XContentBuilder; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; + +public class ExtendedData implements Writeable, ToXContent { + + private final Map>> extendedData; + + public ExtendedData() { + this.extendedData = new HashMap<>(); + } + + public ExtendedData(Map>> extendedData) { + this.extendedData = extendedData; + } + + public ExtendedData(StreamInput in) throws IOException { + this.extendedData = in.readMap(StreamInput::readString, i -> i.readMap(StreamInput::readString, + j -> j.readMap(StreamInput::readString, StreamInput::readLong))); + } + + @Override + public void writeTo(StreamOutput out) throws IOException { + out.writeMap(extendedData, StreamOutput::writeString, (o, v) + -> o.writeMap(v, StreamOutput::writeString, (p, q) -> p.writeMap(q, StreamOutput::writeString, StreamOutput::writeLong))); + } + + public void merge(ExtendedData other) { +// other.extendedData.forEach((key, otherMap) -> { +// extendedData.merge(key, otherMap, (existingMap, newMap) -> { +// Map> mergedMap = new HashMap<>(existingMap); +// newMap.extendedData((innerKey, innerValue) -> mergedMap.merge(innerKey, innerValue, Long::sum)); +// return mergedMap; +// }); +// }); + other.extendedData.forEach((key, otherMap) -> { + extendedData.merge(key, otherMap, (existingMap, newMap) -> { + Map> mergedMap = new HashMap<>(existingMap); + newMap.forEach((innerKey, innerValue) -> { + mergedMap.merge(innerKey, innerValue, (existingInnerMap, newInnerMap) -> { + Map mergedInnerMap = new HashMap<>(existingInnerMap); + newInnerMap.forEach((propertyKey, propertyValue) -> { + mergedInnerMap.merge(propertyKey, propertyValue, Long::sum); + }); + return mergedInnerMap; + }); + }); + return mergedMap; + }); + }); + } + + @Override + public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { + + builder.startObject(); + for (String category : extendedData.keySet()) { + builder.startObject(category); + Map> names = extendedData.get(category); + for (String name : names.keySet()) { + for (String property : names.get(name).keySet()) { + builder.field(property, names.get(name).get(property)); + } + } + builder.endObject(); + } + builder.endObject(); + return builder; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + ExtendedData that = (ExtendedData) o; + return Objects.equals(extendedData, that.extendedData); + } + + @Override + public int hashCode() { + return Objects.hash(extendedData); + } +} diff --git a/server/src/main/java/org/elasticsearch/rest/action/admin/cluster/RestClusterStatsAction.java b/server/src/main/java/org/elasticsearch/rest/action/admin/cluster/RestClusterStatsAction.java index 690f3155971ca..8c0384abd7197 100644 --- a/server/src/main/java/org/elasticsearch/rest/action/admin/cluster/RestClusterStatsAction.java +++ b/server/src/main/java/org/elasticsearch/rest/action/admin/cluster/RestClusterStatsAction.java @@ -34,7 +34,8 @@ public class RestClusterStatsAction extends BaseRestHandler { "verbose-dense-vector-mapping-stats", "ccs-stats", "retrievers-usage-stats", - "esql-stats" + "esql-stats", + "extended-search-usage-stats" ); private static final Set SUPPORTED_QUERY_PARAMETERS = Set.of("include_remotes", "nodeId", REST_TIMEOUT_PARAM); diff --git a/server/src/main/java/org/elasticsearch/rest/action/search/SearchCapabilities.java b/server/src/main/java/org/elasticsearch/rest/action/search/SearchCapabilities.java index e9b414aec86f2..f35ecb0b369fb 100644 --- a/server/src/main/java/org/elasticsearch/rest/action/search/SearchCapabilities.java +++ b/server/src/main/java/org/elasticsearch/rest/action/search/SearchCapabilities.java @@ -59,6 +59,7 @@ private SearchCapabilities() {} private static final String KNN_FILTER_ON_NESTED_FIELDS_CAPABILITY = "knn_filter_on_nested_fields"; private static final String BUCKET_SCRIPT_PARENT_MULTI_BUCKET_ERROR = "bucket_script_parent_multi_bucket_error"; private static final String EXCLUDE_SOURCE_VECTORS_SETTING = "exclude_source_vectors_setting"; + private static final String CLUSTER_STATS_EXTENDED_USAGE = "extended-search-usage-stats"; public static final Set CAPABILITIES; static { @@ -88,6 +89,7 @@ private SearchCapabilities() {} capabilities.add(KNN_FILTER_ON_NESTED_FIELDS_CAPABILITY); capabilities.add(BUCKET_SCRIPT_PARENT_MULTI_BUCKET_ERROR); capabilities.add(EXCLUDE_SOURCE_VECTORS_SETTING); + capabilities.add(CLUSTER_STATS_EXTENDED_USAGE); CAPABILITIES = Set.copyOf(capabilities); } } diff --git a/server/src/main/java/org/elasticsearch/search/builder/SearchSourceBuilder.java b/server/src/main/java/org/elasticsearch/search/builder/SearchSourceBuilder.java index a4f56d5f4a6dc..0cc45742dad5b 100644 --- a/server/src/main/java/org/elasticsearch/search/builder/SearchSourceBuilder.java +++ b/server/src/main/java/org/elasticsearch/search/builder/SearchSourceBuilder.java @@ -129,6 +129,7 @@ public final class SearchSourceBuilder implements Writeable, ToXContentObject, R public static final ParseField POINT_IN_TIME = new ParseField("pit"); public static final ParseField RUNTIME_MAPPINGS_FIELD = new ParseField("runtime_mappings"); public static final ParseField RETRIEVER = new ParseField("retriever"); + public static final ParseField EXTENDED = new ParseField("extended"); private static final boolean RANK_SUPPORTED = Booleans.parseBoolean(System.getProperty("es.search.rank_supported"), true); @@ -1441,6 +1442,7 @@ private SearchSourceBuilder parseXContent( new RetrieverParserContext(searchUsage, clusterSupportsFeature) ); searchUsage.trackSectionUsage(RETRIEVER.getPreferredName()); + searchUsage.trackSectionUsage(EXTENDED.getPreferredName()); } else if (QUERY_FIELD.match(currentFieldName, parser.getDeprecationHandler())) { if (subSearchSourceBuilders.isEmpty() == false) { throw new IllegalArgumentException( diff --git a/server/src/main/java/org/elasticsearch/search/retriever/RetrieverBuilder.java b/server/src/main/java/org/elasticsearch/search/retriever/RetrieverBuilder.java index 0a62e9f968e4f..a4b3aab078a81 100644 --- a/server/src/main/java/org/elasticsearch/search/retriever/RetrieverBuilder.java +++ b/server/src/main/java/org/elasticsearch/search/retriever/RetrieverBuilder.java @@ -33,6 +33,7 @@ import java.util.List; import java.util.Locale; import java.util.Objects; +import java.util.Set; /** * A retriever represents an API element that returns an ordered list of top @@ -133,6 +134,7 @@ protected static RetrieverBuilder parseInnerRetrieverBuilder(XContentParser pars } context.trackRetrieverUsage(retrieverName); + context.trackRetrieverExtendedDataUsage(retrieverName, retrieverBuilder.getExtendedFields()); if (parser.currentToken() != XContentParser.Token.END_OBJECT) { throw new ParsingException( @@ -243,6 +245,10 @@ public ActionRequestValidationException validate( return validationException; } + public Set getExtendedFields() { + return Set.of(); + } + // ---- FOR TESTING XCONTENT PARSING ---- public abstract String getName(); diff --git a/server/src/main/java/org/elasticsearch/search/retriever/RetrieverParserContext.java b/server/src/main/java/org/elasticsearch/search/retriever/RetrieverParserContext.java index bdf3f8a194546..078077fddb503 100644 --- a/server/src/main/java/org/elasticsearch/search/retriever/RetrieverParserContext.java +++ b/server/src/main/java/org/elasticsearch/search/retriever/RetrieverParserContext.java @@ -13,6 +13,7 @@ import org.elasticsearch.usage.SearchUsage; import java.util.Objects; +import java.util.Set; import java.util.function.Predicate; public class RetrieverParserContext { @@ -41,6 +42,10 @@ public void trackRetrieverUsage(String name) { searchUsage.trackRetrieverUsage(name); } + public void trackRetrieverExtendedDataUsage(String retrieverName, Set extendedData) { + searchUsage.trackRetrieverExtendedDataUsage(retrieverName, extendedData); + } + public boolean clusterSupportsFeature(NodeFeature nodeFeature) { return clusterSupportsFeature != null && clusterSupportsFeature.test(nodeFeature); } diff --git a/server/src/main/java/org/elasticsearch/usage/SearchUsage.java b/server/src/main/java/org/elasticsearch/usage/SearchUsage.java index e35594fb161ac..2e7e4940bc4f8 100644 --- a/server/src/main/java/org/elasticsearch/usage/SearchUsage.java +++ b/server/src/main/java/org/elasticsearch/usage/SearchUsage.java @@ -10,17 +10,23 @@ package org.elasticsearch.usage; import java.util.Collections; +import java.util.HashMap; import java.util.HashSet; +import java.util.Map; import java.util.Set; /** * Holds usage statistics for an incoming search request */ public final class SearchUsage { + + public final String RETRIEVERS_NAME = "retrievers"; + private final Set queries = new HashSet<>(); private final Set rescorers = new HashSet<>(); private final Set sections = new HashSet<>(); private final Set retrievers = new HashSet<>(); + private final Map>> extendedData = new HashMap<>(); /** * Track the usage of the provided query @@ -43,13 +49,28 @@ public void trackRescorerUsage(String name) { rescorers.add(name); } - /** - * Track retrieve usage - */ public void trackRetrieverUsage(String retriever) { retrievers.add(retriever); + extendedData + .computeIfAbsent(RETRIEVERS_NAME, k -> new HashMap<>()) + .put(retriever, new HashSet<>()); } + /** + * Track the usage of extended data for a specific category + */ + private void trackExtendedDataUsage(String category, String name, Set values) { + extendedData + .computeIfAbsent(RETRIEVERS_NAME, k -> new HashMap>()) + .computeIfAbsent(name, k -> new HashSet<>()) + .addAll(values); + } + + public void trackRetrieverExtendedDataUsage(String name, Set values) { + trackExtendedDataUsage(RETRIEVERS_NAME, name, values); + } + + /** * Returns the query types that have been used at least once in the tracked search request */ @@ -77,4 +98,11 @@ public Set getSectionsUsage() { public Set getRetrieverUsage() { return Collections.unmodifiableSet(retrievers); } + + /** + * Returns the extended data that has been tracked for the search request + */ + public Map>> getExtendedDataUsage() { + return Collections.unmodifiableMap(extendedData); + } } diff --git a/server/src/main/java/org/elasticsearch/usage/SearchUsageHolder.java b/server/src/main/java/org/elasticsearch/usage/SearchUsageHolder.java index ef802723cf164..6b84f8420e60c 100644 --- a/server/src/main/java/org/elasticsearch/usage/SearchUsageHolder.java +++ b/server/src/main/java/org/elasticsearch/usage/SearchUsageHolder.java @@ -10,10 +10,12 @@ package org.elasticsearch.usage; import org.elasticsearch.action.admin.cluster.stats.SearchUsageStats; +import org.elasticsearch.action.admin.cluster.stats.extended.ExtendedData; import org.elasticsearch.common.util.Maps; import java.util.Collections; import java.util.Map; +import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.LongAdder; @@ -28,6 +30,7 @@ public final class SearchUsageHolder { private final Map rescorersUsage = new ConcurrentHashMap<>(); private final Map sectionsUsage = new ConcurrentHashMap<>(); private final Map retrieversUsage = new ConcurrentHashMap<>(); + private final Map>> extendedDataUsage = new ConcurrentHashMap<>(); SearchUsageHolder() {} @@ -48,6 +51,18 @@ public void updateUsage(SearchUsage searchUsage) { for (String retriever : searchUsage.getRetrieverUsage()) { retrieversUsage.computeIfAbsent(retriever, q -> new LongAdder()).increment(); } + for (Map.Entry>> entry : searchUsage.getExtendedDataUsage().entrySet()) { + String category = entry.getKey(); + for (Map.Entry> innerEntry : entry.getValue().entrySet()) { + String name = innerEntry.getKey(); + for (String value : innerEntry.getValue()) { + extendedDataUsage.computeIfAbsent(category, k -> new ConcurrentHashMap<>()) + .computeIfAbsent(name, k -> new ConcurrentHashMap<>()) + .computeIfAbsent(value, k -> new LongAdder()) + .increment(); + } + } + } } /** @@ -62,11 +77,25 @@ public SearchUsageStats getSearchUsageStats() { rescorersUsage.forEach((query, adder) -> rescorersUsageMap.put(query, adder.longValue())); Map retrieversUsageMap = Maps.newMapWithExpectedSize(retrieversUsage.size()); retrieversUsage.forEach((retriever, adder) -> retrieversUsageMap.put(retriever, adder.longValue())); + + Map>> extendedDataMap = Maps.newMapWithExpectedSize(extendedDataUsage.size()); + extendedDataUsage.forEach((category, innerMap) -> { + Map> nameMap = Maps.newMapWithExpectedSize(innerMap.size()); + innerMap.forEach((name, valueMap) -> { + Map valueCountMap = Maps.newMapWithExpectedSize(valueMap.size()); + valueMap.forEach((value, adder) -> valueCountMap.put(value, adder.longValue())); + nameMap.put(name, Collections.unmodifiableMap(valueCountMap)); + }); + extendedDataMap.put(category, Collections.unmodifiableMap(nameMap)); + }); + ExtendedData extendedData = new ExtendedData(extendedDataMap); + return new SearchUsageStats( Collections.unmodifiableMap(queriesUsageMap), Collections.unmodifiableMap(rescorersUsageMap), Collections.unmodifiableMap(sectionsUsageMap), Collections.unmodifiableMap(retrieversUsageMap), + extendedData, totalSearchCount.longValue() ); } diff --git a/server/src/main/resources/transport/definitions/referable/extended_search_usage_telemetry.csv b/server/src/main/resources/transport/definitions/referable/extended_search_usage_telemetry.csv new file mode 100644 index 0000000000000..cec6381023b6b --- /dev/null +++ b/server/src/main/resources/transport/definitions/referable/extended_search_usage_telemetry.csv @@ -0,0 +1 @@ +9169000 diff --git a/server/src/main/resources/transport/upper_bounds/9.2.csv b/server/src/main/resources/transport/upper_bounds/9.2.csv index bf1a90e5be4e9..f98505983c2b6 100644 --- a/server/src/main/resources/transport/upper_bounds/9.2.csv +++ b/server/src/main/resources/transport/upper_bounds/9.2.csv @@ -1 +1 @@ -index_request_include_tsid,9167000 +extended_search_usage_telemetry,9169000 diff --git a/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/rank/textsimilarity/TextSimilarityRankRetrieverBuilder.java b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/rank/textsimilarity/TextSimilarityRankRetrieverBuilder.java index 74e8ff2bd4042..b39489b28e7df 100644 --- a/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/rank/textsimilarity/TextSimilarityRankRetrieverBuilder.java +++ b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/rank/textsimilarity/TextSimilarityRankRetrieverBuilder.java @@ -26,9 +26,11 @@ import java.io.IOException; import java.util.ArrayList; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.Set; import static org.elasticsearch.search.rank.RankBuilder.DEFAULT_RANK_WINDOW_SIZE; import static org.elasticsearch.xcontent.ConstructingObjectParser.constructorArg; @@ -90,6 +92,7 @@ public class TextSimilarityRankRetrieverBuilder extends CompoundRetrieverBuilder PARSER.declareNamedObject(constructorArg(), (p, c, n) -> { RetrieverBuilder innerRetriever = p.namedObject(RetrieverBuilder.class, n, c); c.trackRetrieverUsage(innerRetriever.getName()); + c.trackRetrieverExtendedDataUsage(innerRetriever.retrieverName(), innerRetriever.getExtendedFields()); return innerRetriever; }, RETRIEVER_FIELD); PARSER.declareString(optionalConstructorArg(), INFERENCE_ID_FIELD); @@ -227,6 +230,17 @@ protected SearchSourceBuilder finalizeSourceBuilder(SearchSourceBuilder sourceBu return sourceBuilder; } + @Override + public Set getExtendedFields() { + Set extendedFields = new HashSet<>(); + + if (chunkScorerConfig != null) { + extendedFields.add(CHUNK_RESCORER_FIELD.getPreferredName()); + } + + return extendedFields; + } + @Override public String getName() { return TextSimilarityRankBuilder.NAME; diff --git a/x-pack/plugin/monitoring/src/test/java/org/elasticsearch/xpack/monitoring/collector/cluster/ClusterStatsMonitoringDocTests.java b/x-pack/plugin/monitoring/src/test/java/org/elasticsearch/xpack/monitoring/collector/cluster/ClusterStatsMonitoringDocTests.java index c1c618412e110..806b82088101d 100644 --- a/x-pack/plugin/monitoring/src/test/java/org/elasticsearch/xpack/monitoring/collector/cluster/ClusterStatsMonitoringDocTests.java +++ b/x-pack/plugin/monitoring/src/test/java/org/elasticsearch/xpack/monitoring/collector/cluster/ClusterStatsMonitoringDocTests.java @@ -592,7 +592,8 @@ public void testToXContent() throws IOException { "queries": {}, "rescorers": {}, "sections": {}, - "retrievers": {} + "retrievers": {}, + "extended": {} }, "dense_vector": { "value_count": 0 From 6d8ef33a76dc05d008906e96ef7594bea7482338 Mon Sep 17 00:00:00 2001 From: Kathleen DeRusso Date: Mon, 22 Sep 2025 16:07:14 -0400 Subject: [PATCH 02/26] Cleanup --- ...ata.java => ExtendedSearchUsageStats.java} | 59 ++++++++++-------- .../admin/cluster/stats/SearchUsageStats.java | 27 ++++---- .../retriever/RescorerRetrieverBuilder.java | 2 +- .../search/retriever/RetrieverBuilder.java | 11 +++- .../retriever/RetrieverParserContext.java | 10 +-- .../org/elasticsearch/usage/SearchUsage.java | 49 ++++++++++++--- .../usage/SearchUsageHolder.java | 62 +++++++++++-------- .../retriever/QueryRuleRetrieverBuilder.java | 2 +- .../TextSimilarityRankRetrieverBuilder.java | 5 +- .../rank/linear/LinearRetrieverComponent.java | 2 +- .../xpack/rank/rrf/RRFRetrieverComponent.java | 4 +- .../retriever/PinnedRetrieverBuilder.java | 2 +- 12 files changed, 141 insertions(+), 94 deletions(-) rename server/src/main/java/org/elasticsearch/action/admin/cluster/stats/{extended/ExtendedData.java => ExtendedSearchUsageStats.java} (56%) diff --git a/server/src/main/java/org/elasticsearch/action/admin/cluster/stats/extended/ExtendedData.java b/server/src/main/java/org/elasticsearch/action/admin/cluster/stats/ExtendedSearchUsageStats.java similarity index 56% rename from server/src/main/java/org/elasticsearch/action/admin/cluster/stats/extended/ExtendedData.java rename to server/src/main/java/org/elasticsearch/action/admin/cluster/stats/ExtendedSearchUsageStats.java index 5ee77ef5bff50..1307c3774313e 100644 --- a/server/src/main/java/org/elasticsearch/action/admin/cluster/stats/extended/ExtendedData.java +++ b/server/src/main/java/org/elasticsearch/action/admin/cluster/stats/ExtendedSearchUsageStats.java @@ -7,52 +7,61 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -package org.elasticsearch.action.admin.cluster.stats.extended; +package org.elasticsearch.action.admin.cluster.stats; import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.io.stream.StreamOutput; import org.elasticsearch.common.io.stream.Writeable; +import org.elasticsearch.usage.SearchUsage; import org.elasticsearch.xcontent.ToXContent; import org.elasticsearch.xcontent.XContentBuilder; import java.io.IOException; +import java.util.Collections; import java.util.HashMap; +import java.util.HashSet; import java.util.Map; import java.util.Objects; +import java.util.Set; -public class ExtendedData implements Writeable, ToXContent { +/** + * Provides extended statistics for {@link SearchUsage} beyond the basic counts provided in {@link SearchUsageStats}. + */ +public class ExtendedSearchUsageStats implements Writeable, ToXContent { - private final Map>> extendedData; + /** + * A map of categories to extended data. Categories correspond to a high-level search usage statistic, + * e.g. `queries`, `rescorers`, `sections`, `retrievers`. + * + * Extended data is further segmented by name, for example collecting specific statistics for certain retrievers only. + * + * Finally, we have string:count pairs that track each individual metric we wish to track. + */ + private final Map>> categoriesToExtendedData; - public ExtendedData() { - this.extendedData = new HashMap<>(); + public ExtendedSearchUsageStats() { + this.categoriesToExtendedData = new HashMap<>(); } - public ExtendedData(Map>> extendedData) { - this.extendedData = extendedData; + public ExtendedSearchUsageStats(Map>> categoriesToExtendedData) { + this.categoriesToExtendedData = categoriesToExtendedData; } - public ExtendedData(StreamInput in) throws IOException { - this.extendedData = in.readMap(StreamInput::readString, i -> i.readMap(StreamInput::readString, + public ExtendedSearchUsageStats(StreamInput in) throws IOException { + this.categoriesToExtendedData = in.readMap(StreamInput::readString, i -> i.readMap(StreamInput::readString, j -> j.readMap(StreamInput::readString, StreamInput::readLong))); } @Override public void writeTo(StreamOutput out) throws IOException { - out.writeMap(extendedData, StreamOutput::writeString, (o, v) + out.writeMap( + categoriesToExtendedData, StreamOutput::writeString, (o, v) -> o.writeMap(v, StreamOutput::writeString, (p, q) -> p.writeMap(q, StreamOutput::writeString, StreamOutput::writeLong))); } - public void merge(ExtendedData other) { -// other.extendedData.forEach((key, otherMap) -> { -// extendedData.merge(key, otherMap, (existingMap, newMap) -> { -// Map> mergedMap = new HashMap<>(existingMap); -// newMap.extendedData((innerKey, innerValue) -> mergedMap.merge(innerKey, innerValue, Long::sum)); -// return mergedMap; -// }); -// }); - other.extendedData.forEach((key, otherMap) -> { - extendedData.merge(key, otherMap, (existingMap, newMap) -> { + public void merge(ExtendedSearchUsageStats other) { + other.categoriesToExtendedData.forEach((key, otherMap) -> { + categoriesToExtendedData.merge(key, otherMap, (existingMap, newMap) -> { Map> mergedMap = new HashMap<>(existingMap); newMap.forEach((innerKey, innerValue) -> { mergedMap.merge(innerKey, innerValue, (existingInnerMap, newInnerMap) -> { @@ -72,9 +81,9 @@ public void merge(ExtendedData other) { public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { builder.startObject(); - for (String category : extendedData.keySet()) { + for (String category : categoriesToExtendedData.keySet()) { builder.startObject(category); - Map> names = extendedData.get(category); + Map> names = categoriesToExtendedData.get(category); for (String name : names.keySet()) { for (String property : names.get(name).keySet()) { builder.field(property, names.get(name).get(property)); @@ -90,12 +99,12 @@ public XContentBuilder toXContent(XContentBuilder builder, Params params) throws public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; - ExtendedData that = (ExtendedData) o; - return Objects.equals(extendedData, that.extendedData); + ExtendedSearchUsageStats that = (ExtendedSearchUsageStats) o; + return Objects.equals(categoriesToExtendedData, that.categoriesToExtendedData); } @Override public int hashCode() { - return Objects.hash(extendedData); + return Objects.hash(categoriesToExtendedData); } } diff --git a/server/src/main/java/org/elasticsearch/action/admin/cluster/stats/SearchUsageStats.java b/server/src/main/java/org/elasticsearch/action/admin/cluster/stats/SearchUsageStats.java index 7023545a8f54f..ac341681b9457 100644 --- a/server/src/main/java/org/elasticsearch/action/admin/cluster/stats/SearchUsageStats.java +++ b/server/src/main/java/org/elasticsearch/action/admin/cluster/stats/SearchUsageStats.java @@ -10,7 +10,6 @@ package org.elasticsearch.action.admin.cluster.stats; import org.elasticsearch.TransportVersion; -import org.elasticsearch.action.admin.cluster.stats.extended.ExtendedData; import org.elasticsearch.common.Strings; import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.io.stream.StreamOutput; @@ -41,7 +40,7 @@ public final class SearchUsageStats implements Writeable, ToXContentFragment { private final Map rescorers; private final Map sections; private final Map retrievers; - private final ExtendedData extendedData; + private final ExtendedSearchUsageStats extendedSearchUsageStats; /** * Creates a new empty stats instance, that will get additional stats added through {@link #add(SearchUsageStats)} @@ -52,7 +51,7 @@ public SearchUsageStats() { this.sections = new HashMap<>(); this.rescorers = new HashMap<>(); this.retrievers = new HashMap<>(); - this.extendedData = new ExtendedData(); + this.extendedSearchUsageStats = new ExtendedSearchUsageStats(); } /** @@ -64,7 +63,7 @@ public SearchUsageStats( Map rescorers, Map sections, Map retrievers, - ExtendedData extendedData, + ExtendedSearchUsageStats extendedSearchUsageStats, long totalSearchCount ) { this.totalSearchCount = totalSearchCount; @@ -72,7 +71,7 @@ public SearchUsageStats( this.sections = sections; this.rescorers = rescorers; this.retrievers = retrievers; - this.extendedData = extendedData; + this.extendedSearchUsageStats = extendedSearchUsageStats; } public SearchUsageStats(StreamInput in) throws IOException { @@ -81,8 +80,8 @@ public SearchUsageStats(StreamInput in) throws IOException { this.totalSearchCount = in.readVLong(); this.rescorers = in.getTransportVersion().onOrAfter(V_8_12_0) ? in.readMap(StreamInput::readLong) : Map.of(); this.retrievers = in.getTransportVersion().onOrAfter(V_8_16_0) ? in.readMap(StreamInput::readLong) : Map.of(); - this.extendedData = - in.getTransportVersion().supports(EXTENDED_SEARCH_USAGE_TELEMETRY) ? new ExtendedData(in) : new ExtendedData(); + this.extendedSearchUsageStats = + in.getTransportVersion().supports(EXTENDED_SEARCH_USAGE_TELEMETRY) ? new ExtendedSearchUsageStats(in) : new ExtendedSearchUsageStats(); } @Override @@ -98,7 +97,7 @@ public void writeTo(StreamOutput out) throws IOException { out.writeMap(retrievers, StreamOutput::writeLong); } if (out.getTransportVersion().supports(EXTENDED_SEARCH_USAGE_TELEMETRY)) { - extendedData.writeTo(out); + extendedSearchUsageStats.writeTo(out); } } @@ -110,7 +109,7 @@ public void add(SearchUsageStats stats) { stats.rescorers.forEach((rescorer, count) -> rescorers.merge(rescorer, count, Long::sum)); stats.sections.forEach((query, count) -> sections.merge(query, count, Long::sum)); stats.retrievers.forEach((query, count) -> retrievers.merge(query, count, Long::sum)); - this.extendedData.merge(stats.extendedData); + this.extendedSearchUsageStats.merge(stats.extendedSearchUsageStats); this.totalSearchCount += stats.totalSearchCount; } @@ -128,7 +127,7 @@ public XContentBuilder toXContent(XContentBuilder builder, Params params) throws builder.field("retrievers"); builder.map(retrievers); builder.field("extended"); - extendedData.toXContent(builder, params); + extendedSearchUsageStats.toXContent(builder, params); } builder.endObject(); return builder; @@ -150,8 +149,8 @@ public Map getRetrieversUsage() { return Collections.unmodifiableMap(retrievers); } - public ExtendedData getExtendedData() { - return extendedData; + public ExtendedSearchUsageStats getExtendedSearchUsage() { + return extendedSearchUsageStats; } public long getTotalSearchCount() { @@ -172,12 +171,12 @@ public boolean equals(Object o) { && rescorers.equals(that.rescorers) && sections.equals(that.sections) && retrievers.equals(that.retrievers) - && extendedData.equals(that.extendedData); + && extendedSearchUsageStats.equals(that.extendedSearchUsageStats); } @Override public int hashCode() { - return Objects.hash(totalSearchCount, queries, rescorers, sections, retrievers, extendedData); + return Objects.hash(totalSearchCount, queries, rescorers, sections, retrievers, extendedSearchUsageStats); } @Override diff --git a/server/src/main/java/org/elasticsearch/search/retriever/RescorerRetrieverBuilder.java b/server/src/main/java/org/elasticsearch/search/retriever/RescorerRetrieverBuilder.java index c2bda5587e1bb..aa016d7909c4a 100644 --- a/server/src/main/java/org/elasticsearch/search/retriever/RescorerRetrieverBuilder.java +++ b/server/src/main/java/org/elasticsearch/search/retriever/RescorerRetrieverBuilder.java @@ -46,7 +46,7 @@ public final class RescorerRetrieverBuilder extends CompoundRetrieverBuilder { RetrieverBuilder innerRetriever = parser.namedObject(RetrieverBuilder.class, n, context); - context.trackRetrieverUsage(innerRetriever.getName()); + context.trackRetrieverUsage(innerRetriever); return innerRetriever; }, RETRIEVER_FIELD); PARSER.declareField(constructorArg(), (parser, context) -> { diff --git a/server/src/main/java/org/elasticsearch/search/retriever/RetrieverBuilder.java b/server/src/main/java/org/elasticsearch/search/retriever/RetrieverBuilder.java index a4b3aab078a81..bfdd9a341fb53 100644 --- a/server/src/main/java/org/elasticsearch/search/retriever/RetrieverBuilder.java +++ b/server/src/main/java/org/elasticsearch/search/retriever/RetrieverBuilder.java @@ -133,8 +133,7 @@ protected static RetrieverBuilder parseInnerRetrieverBuilder(XContentParser pars throw new ParsingException(new XContentLocation(nonfe.getLineNumber(), nonfe.getColumnNumber()), message, nonfe); } - context.trackRetrieverUsage(retrieverName); - context.trackRetrieverExtendedDataUsage(retrieverName, retrieverBuilder.getExtendedFields()); + context.trackRetrieverUsage(retrieverBuilder); if (parser.currentToken() != XContentParser.Token.END_OBJECT) { throw new ParsingException( @@ -245,7 +244,13 @@ public ActionRequestValidationException validate( return validationException; } - public Set getExtendedFields() { + /** + * @return Additional fields associated with this retriever that we want to track in + * {@link org.elasticsearch.action.admin.cluster.stats.SearchUsageStats}. + * + * Individual retrievers should override this to add their own specific custom fields. + */ + public Set getExtendedUsageFields() { return Set.of(); } diff --git a/server/src/main/java/org/elasticsearch/search/retriever/RetrieverParserContext.java b/server/src/main/java/org/elasticsearch/search/retriever/RetrieverParserContext.java index 078077fddb503..61f019f9f6e6d 100644 --- a/server/src/main/java/org/elasticsearch/search/retriever/RetrieverParserContext.java +++ b/server/src/main/java/org/elasticsearch/search/retriever/RetrieverParserContext.java @@ -13,7 +13,6 @@ import org.elasticsearch.usage.SearchUsage; import java.util.Objects; -import java.util.Set; import java.util.function.Predicate; public class RetrieverParserContext { @@ -38,12 +37,9 @@ public void trackRescorerUsage(String name) { searchUsage.trackRescorerUsage(name); } - public void trackRetrieverUsage(String name) { - searchUsage.trackRetrieverUsage(name); - } - - public void trackRetrieverExtendedDataUsage(String retrieverName, Set extendedData) { - searchUsage.trackRetrieverExtendedDataUsage(retrieverName, extendedData); + public void trackRetrieverUsage(RetrieverBuilder retriever) { + searchUsage.trackRetrieverUsage(retriever.getName()); + searchUsage.trackRetrieverExtendedDataUsage(retriever.getName(), retriever.getExtendedUsageFields()); } public boolean clusterSupportsFeature(NodeFeature nodeFeature) { diff --git a/server/src/main/java/org/elasticsearch/usage/SearchUsage.java b/server/src/main/java/org/elasticsearch/usage/SearchUsage.java index 2e7e4940bc4f8..963999e532544 100644 --- a/server/src/main/java/org/elasticsearch/usage/SearchUsage.java +++ b/server/src/main/java/org/elasticsearch/usage/SearchUsage.java @@ -26,7 +26,7 @@ public final class SearchUsage { private final Set rescorers = new HashSet<>(); private final Set sections = new HashSet<>(); private final Set retrievers = new HashSet<>(); - private final Map>> extendedData = new HashMap<>(); + private final ExtendedUsageTracker extendedUsage = new ExtendedUsageTracker(); /** * Track the usage of the provided query @@ -51,19 +51,14 @@ public void trackRescorerUsage(String name) { public void trackRetrieverUsage(String retriever) { retrievers.add(retriever); - extendedData - .computeIfAbsent(RETRIEVERS_NAME, k -> new HashMap<>()) - .put(retriever, new HashSet<>()); + extendedUsage.initialize(RETRIEVERS_NAME, retriever); } /** * Track the usage of extended data for a specific category */ private void trackExtendedDataUsage(String category, String name, Set values) { - extendedData - .computeIfAbsent(RETRIEVERS_NAME, k -> new HashMap>()) - .computeIfAbsent(name, k -> new HashSet<>()) - .addAll(values); + extendedUsage.track(category, name, values); } public void trackRetrieverExtendedDataUsage(String name, Set values) { @@ -103,6 +98,42 @@ public Set getRetrieverUsage() { * Returns the extended data that has been tracked for the search request */ public Map>> getExtendedDataUsage() { - return Collections.unmodifiableMap(extendedData); + return extendedUsage.getUsage(); + } + + private static final class ExtendedUsageTracker { + + /** + * A map of categories to extended data. Categories correspond to a high-level search usage statistic, + * e.g. `queries`, `rescorers`, `sections`, `retrievers`. + * + * Extended data is further segmented by name, for example collecting specific statistics for certain retrievers only. + * Finally we keep track of the set of values we are tracking for each category and name. + */ + private final Map>> categoriesToExtendedUsage = new HashMap<>(); + + public void initialize(String category, String name) { + categoriesToExtendedUsage + .computeIfAbsent(category, k -> new HashMap<>()) + .computeIfAbsent(name, k -> new HashSet<>()); + } + + public void track(String category, String name, String value) { + categoriesToExtendedUsage + .computeIfAbsent(category, k -> new HashMap<>()) + .computeIfAbsent(name, k -> new HashSet<>()) + .add(value); + } + + public void track(String category, String name, Set values) { + categoriesToExtendedUsage + .computeIfAbsent(category, k -> new HashMap<>()) + .computeIfAbsent(name, k -> new HashSet<>()) + .addAll(values); + } + + public Map>> getUsage() { + return Collections.unmodifiableMap(categoriesToExtendedUsage); + } } } diff --git a/server/src/main/java/org/elasticsearch/usage/SearchUsageHolder.java b/server/src/main/java/org/elasticsearch/usage/SearchUsageHolder.java index 6b84f8420e60c..ee5f996471ff8 100644 --- a/server/src/main/java/org/elasticsearch/usage/SearchUsageHolder.java +++ b/server/src/main/java/org/elasticsearch/usage/SearchUsageHolder.java @@ -10,14 +10,16 @@ package org.elasticsearch.usage; import org.elasticsearch.action.admin.cluster.stats.SearchUsageStats; -import org.elasticsearch.action.admin.cluster.stats.extended.ExtendedData; +import org.elasticsearch.action.admin.cluster.stats.ExtendedSearchUsageStats; import org.elasticsearch.common.util.Maps; import java.util.Collections; +import java.util.HashMap; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.LongAdder; +import java.util.function.Function; /** * Service responsible for holding search usage statistics, like the number of used search sections and queries. @@ -30,7 +32,7 @@ public final class SearchUsageHolder { private final Map rescorersUsage = new ConcurrentHashMap<>(); private final Map sectionsUsage = new ConcurrentHashMap<>(); private final Map retrieversUsage = new ConcurrentHashMap<>(); - private final Map>> extendedDataUsage = new ConcurrentHashMap<>(); + private final Map>> extendedSearchUsage = new ConcurrentHashMap<>(); SearchUsageHolder() {} @@ -51,18 +53,7 @@ public void updateUsage(SearchUsage searchUsage) { for (String retriever : searchUsage.getRetrieverUsage()) { retrieversUsage.computeIfAbsent(retriever, q -> new LongAdder()).increment(); } - for (Map.Entry>> entry : searchUsage.getExtendedDataUsage().entrySet()) { - String category = entry.getKey(); - for (Map.Entry> innerEntry : entry.getValue().entrySet()) { - String name = innerEntry.getKey(); - for (String value : innerEntry.getValue()) { - extendedDataUsage.computeIfAbsent(category, k -> new ConcurrentHashMap<>()) - .computeIfAbsent(name, k -> new ConcurrentHashMap<>()) - .computeIfAbsent(value, k -> new LongAdder()) - .increment(); - } - } - } + updateExtendedUsage(searchUsage.getExtendedDataUsage()); } /** @@ -77,26 +68,43 @@ public SearchUsageStats getSearchUsageStats() { rescorersUsage.forEach((query, adder) -> rescorersUsageMap.put(query, adder.longValue())); Map retrieversUsageMap = Maps.newMapWithExpectedSize(retrieversUsage.size()); retrieversUsage.forEach((retriever, adder) -> retrieversUsageMap.put(retriever, adder.longValue())); - - Map>> extendedDataMap = Maps.newMapWithExpectedSize(extendedDataUsage.size()); - extendedDataUsage.forEach((category, innerMap) -> { - Map> nameMap = Maps.newMapWithExpectedSize(innerMap.size()); - innerMap.forEach((name, valueMap) -> { - Map valueCountMap = Maps.newMapWithExpectedSize(valueMap.size()); - valueMap.forEach((value, adder) -> valueCountMap.put(value, adder.longValue())); - nameMap.put(name, Collections.unmodifiableMap(valueCountMap)); - }); - extendedDataMap.put(category, Collections.unmodifiableMap(nameMap)); - }); - ExtendedData extendedData = new ExtendedData(extendedDataMap); + ExtendedSearchUsageStats extendedSearchUsageStats = new ExtendedSearchUsageStats(getExtendedSearchUsage()); return new SearchUsageStats( Collections.unmodifiableMap(queriesUsageMap), Collections.unmodifiableMap(rescorersUsageMap), Collections.unmodifiableMap(sectionsUsageMap), Collections.unmodifiableMap(retrieversUsageMap), - extendedData, + extendedSearchUsageStats, totalSearchCount.longValue() ); } + + private Map>> getExtendedSearchUsage() { + return unmodifiableMap(extendedSearchUsage, nameMap -> + unmodifiableMap(nameMap, valueMap -> + unmodifiableMap(valueMap, LongAdder::longValue) + ) + ); + } + + private void updateExtendedUsage(Map>> extendedDataUsage) { + extendedDataUsage.forEach((category, nameMap) -> + nameMap.forEach((name, valueSet) -> + valueSet.forEach(value -> + extendedSearchUsage.computeIfAbsent(category, k -> new ConcurrentHashMap<>()) + .computeIfAbsent(name, k -> new ConcurrentHashMap<>()) + .computeIfAbsent(value, k -> new LongAdder()) + .increment() + ) + ) + ); + } + + private static Map unmodifiableMap(Map in, Function valueMapper) { + Map map = new HashMap<>(in.size()); + in.forEach((k, v) -> map.put(k, valueMapper.apply(v))); + return Collections.unmodifiableMap(map); + } + } diff --git a/x-pack/plugin/ent-search/src/main/java/org/elasticsearch/xpack/application/rules/retriever/QueryRuleRetrieverBuilder.java b/x-pack/plugin/ent-search/src/main/java/org/elasticsearch/xpack/application/rules/retriever/QueryRuleRetrieverBuilder.java index b724cd133f0c5..491e937337db2 100644 --- a/x-pack/plugin/ent-search/src/main/java/org/elasticsearch/xpack/application/rules/retriever/QueryRuleRetrieverBuilder.java +++ b/x-pack/plugin/ent-search/src/main/java/org/elasticsearch/xpack/application/rules/retriever/QueryRuleRetrieverBuilder.java @@ -66,7 +66,7 @@ public final class QueryRuleRetrieverBuilder extends CompoundRetrieverBuilder p.map(), MATCH_CRITERIA_FIELD); PARSER.declareNamedObject(constructorArg(), (p, c, n) -> { RetrieverBuilder innerRetriever = p.namedObject(RetrieverBuilder.class, n, c); - c.trackRetrieverUsage(innerRetriever.getName()); + c.trackRetrieverUsage(innerRetriever); return innerRetriever; }, RETRIEVER_FIELD); PARSER.declareInt(optionalConstructorArg(), RANK_WINDOW_SIZE_FIELD); diff --git a/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/rank/textsimilarity/TextSimilarityRankRetrieverBuilder.java b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/rank/textsimilarity/TextSimilarityRankRetrieverBuilder.java index b39489b28e7df..f7c1ca8312de8 100644 --- a/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/rank/textsimilarity/TextSimilarityRankRetrieverBuilder.java +++ b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/rank/textsimilarity/TextSimilarityRankRetrieverBuilder.java @@ -91,8 +91,7 @@ public class TextSimilarityRankRetrieverBuilder extends CompoundRetrieverBuilder static { PARSER.declareNamedObject(constructorArg(), (p, c, n) -> { RetrieverBuilder innerRetriever = p.namedObject(RetrieverBuilder.class, n, c); - c.trackRetrieverUsage(innerRetriever.getName()); - c.trackRetrieverExtendedDataUsage(innerRetriever.retrieverName(), innerRetriever.getExtendedFields()); + c.trackRetrieverUsage(innerRetriever); return innerRetriever; }, RETRIEVER_FIELD); PARSER.declareString(optionalConstructorArg(), INFERENCE_ID_FIELD); @@ -231,7 +230,7 @@ protected SearchSourceBuilder finalizeSourceBuilder(SearchSourceBuilder sourceBu } @Override - public Set getExtendedFields() { + public Set getExtendedUsageFields() { Set extendedFields = new HashSet<>(); if (chunkScorerConfig != null) { diff --git a/x-pack/plugin/rank-rrf/src/main/java/org/elasticsearch/xpack/rank/linear/LinearRetrieverComponent.java b/x-pack/plugin/rank-rrf/src/main/java/org/elasticsearch/xpack/rank/linear/LinearRetrieverComponent.java index 963ba6883e7c9..3fe03237125e1 100644 --- a/x-pack/plugin/rank-rrf/src/main/java/org/elasticsearch/xpack/rank/linear/LinearRetrieverComponent.java +++ b/x-pack/plugin/rank-rrf/src/main/java/org/elasticsearch/xpack/rank/linear/LinearRetrieverComponent.java @@ -67,7 +67,7 @@ public XContentBuilder toXContent(XContentBuilder builder, Params params) throws static { PARSER.declareNamedObject(constructorArg(), (p, c, n) -> { RetrieverBuilder innerRetriever = p.namedObject(RetrieverBuilder.class, n, c); - c.trackRetrieverUsage(innerRetriever.getName()); + c.trackRetrieverUsage(innerRetriever); return innerRetriever; }, RETRIEVER_FIELD); PARSER.declareFloat(optionalConstructorArg(), WEIGHT_FIELD); diff --git a/x-pack/plugin/rank-rrf/src/main/java/org/elasticsearch/xpack/rank/rrf/RRFRetrieverComponent.java b/x-pack/plugin/rank-rrf/src/main/java/org/elasticsearch/xpack/rank/rrf/RRFRetrieverComponent.java index 4946407fb19fb..15dfee43976eb 100644 --- a/x-pack/plugin/rank-rrf/src/main/java/org/elasticsearch/xpack/rank/rrf/RRFRetrieverComponent.java +++ b/x-pack/plugin/rank-rrf/src/main/java/org/elasticsearch/xpack/rank/rrf/RRFRetrieverComponent.java @@ -88,7 +88,7 @@ public static RRFRetrieverComponent fromXContent(XContentParser parser, Retrieve parser.nextToken(); String retrieverType = parser.currentName(); retriever = parser.namedObject(RetrieverBuilder.class, retrieverType, context); - context.trackRetrieverUsage(retriever.getName()); + context.trackRetrieverUsage(retriever); parser.nextToken(); } else if (WEIGHT_FIELD.match(fieldName, parser.getDeprecationHandler())) { if (weight != null) { @@ -114,7 +114,7 @@ public static RRFRetrieverComponent fromXContent(XContentParser parser, Retrieve return new RRFRetrieverComponent(retriever, weight); } else { RetrieverBuilder retriever = parser.namedObject(RetrieverBuilder.class, firstFieldName, context); - context.trackRetrieverUsage(retriever.getName()); + context.trackRetrieverUsage(retriever); if (parser.nextToken() != XContentParser.Token.END_OBJECT) { throw new ParsingException(parser.getTokenLocation(), "unknown field [{}] after retriever", parser.currentName()); } diff --git a/x-pack/plugin/search-business-rules/src/main/java/org/elasticsearch/xpack/searchbusinessrules/retriever/PinnedRetrieverBuilder.java b/x-pack/plugin/search-business-rules/src/main/java/org/elasticsearch/xpack/searchbusinessrules/retriever/PinnedRetrieverBuilder.java index 0e254b4fcd5b3..a876d520e4b6b 100644 --- a/x-pack/plugin/search-business-rules/src/main/java/org/elasticsearch/xpack/searchbusinessrules/retriever/PinnedRetrieverBuilder.java +++ b/x-pack/plugin/search-business-rules/src/main/java/org/elasticsearch/xpack/searchbusinessrules/retriever/PinnedRetrieverBuilder.java @@ -68,7 +68,7 @@ public final class PinnedRetrieverBuilder extends CompoundRetrieverBuilder SpecifiedDocument.PARSER.apply(p, null), DOCS_FIELD); PARSER.declareNamedObject(constructorArg(), (p, c, n) -> { RetrieverBuilder innerRetriever = p.namedObject(RetrieverBuilder.class, n, c); - c.trackRetrieverUsage(innerRetriever.getName()); + c.trackRetrieverUsage(innerRetriever); return innerRetriever; }, RETRIEVER_FIELD); PARSER.declareInt(optionalConstructorArg(), RANK_WINDOW_SIZE_FIELD); From 7ba47b84b424efb4945124d8855dacd21c62e150 Mon Sep 17 00:00:00 2001 From: Kathleen DeRusso Date: Tue, 23 Sep 2025 15:03:25 -0400 Subject: [PATCH 03/26] Update docs/changelog/135306.yaml --- docs/changelog/135306.yaml | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 docs/changelog/135306.yaml diff --git a/docs/changelog/135306.yaml b/docs/changelog/135306.yaml new file mode 100644 index 0000000000000..bfc422d309e6a --- /dev/null +++ b/docs/changelog/135306.yaml @@ -0,0 +1,5 @@ +pr: 135306 +summary: Add support for extended search usage telemetry +area: Relevance +type: enhancement +issues: [] From ee69131d62374375f7f12ed483e01ae1bb965614 Mon Sep 17 00:00:00 2001 From: elasticsearchmachine Date: Tue, 23 Sep 2025 19:11:05 +0000 Subject: [PATCH 04/26] [CI] Auto commit changes from spotless --- .../stats/ExtendedSearchUsageStats.java | 51 ++++++++++--------- .../admin/cluster/stats/SearchUsageStats.java | 5 +- .../org/elasticsearch/usage/SearchUsage.java | 13 ++--- .../usage/SearchUsageHolder.java | 19 ++++--- 4 files changed, 42 insertions(+), 46 deletions(-) diff --git a/server/src/main/java/org/elasticsearch/action/admin/cluster/stats/ExtendedSearchUsageStats.java b/server/src/main/java/org/elasticsearch/action/admin/cluster/stats/ExtendedSearchUsageStats.java index 1307c3774313e..b52fa4d38388c 100644 --- a/server/src/main/java/org/elasticsearch/action/admin/cluster/stats/ExtendedSearchUsageStats.java +++ b/server/src/main/java/org/elasticsearch/action/admin/cluster/stats/ExtendedSearchUsageStats.java @@ -17,12 +17,9 @@ import org.elasticsearch.xcontent.XContentBuilder; import java.io.IOException; -import java.util.Collections; import java.util.HashMap; -import java.util.HashSet; import java.util.Map; import java.util.Objects; -import java.util.Set; /** * Provides extended statistics for {@link SearchUsage} beyond the basic counts provided in {@link SearchUsageStats}. @@ -37,38 +34,42 @@ public class ExtendedSearchUsageStats implements Writeable, ToXContent { * * Finally, we have string:count pairs that track each individual metric we wish to track. */ - private final Map>> categoriesToExtendedData; + private final Map>> categoriesToExtendedData; public ExtendedSearchUsageStats() { this.categoriesToExtendedData = new HashMap<>(); } - public ExtendedSearchUsageStats(Map>> categoriesToExtendedData) { + public ExtendedSearchUsageStats(Map>> categoriesToExtendedData) { this.categoriesToExtendedData = categoriesToExtendedData; } public ExtendedSearchUsageStats(StreamInput in) throws IOException { - this.categoriesToExtendedData = in.readMap(StreamInput::readString, i -> i.readMap(StreamInput::readString, - j -> j.readMap(StreamInput::readString, StreamInput::readLong))); + this.categoriesToExtendedData = in.readMap( + StreamInput::readString, + i -> i.readMap(StreamInput::readString, j -> j.readMap(StreamInput::readString, StreamInput::readLong)) + ); } @Override public void writeTo(StreamOutput out) throws IOException { out.writeMap( - categoriesToExtendedData, StreamOutput::writeString, (o, v) - -> o.writeMap(v, StreamOutput::writeString, (p, q) -> p.writeMap(q, StreamOutput::writeString, StreamOutput::writeLong))); + categoriesToExtendedData, + StreamOutput::writeString, + (o, v) -> o.writeMap(v, StreamOutput::writeString, (p, q) -> p.writeMap(q, StreamOutput::writeString, StreamOutput::writeLong)) + ); } public void merge(ExtendedSearchUsageStats other) { other.categoriesToExtendedData.forEach((key, otherMap) -> { categoriesToExtendedData.merge(key, otherMap, (existingMap, newMap) -> { - Map> mergedMap = new HashMap<>(existingMap); + Map> mergedMap = new HashMap<>(existingMap); newMap.forEach((innerKey, innerValue) -> { mergedMap.merge(innerKey, innerValue, (existingInnerMap, newInnerMap) -> { Map mergedInnerMap = new HashMap<>(existingInnerMap); - newInnerMap.forEach((propertyKey, propertyValue) -> { - mergedInnerMap.merge(propertyKey, propertyValue, Long::sum); - }); + newInnerMap.forEach( + (propertyKey, propertyValue) -> { mergedInnerMap.merge(propertyKey, propertyValue, Long::sum); } + ); return mergedInnerMap; }); }); @@ -81,18 +82,18 @@ public void merge(ExtendedSearchUsageStats other) { public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { builder.startObject(); - for (String category : categoriesToExtendedData.keySet()) { - builder.startObject(category); - Map> names = categoriesToExtendedData.get(category); - for (String name : names.keySet()) { - for (String property : names.get(name).keySet()) { - builder.field(property, names.get(name).get(property)); - } - } - builder.endObject(); - } - builder.endObject(); - return builder; + for (String category : categoriesToExtendedData.keySet()) { + builder.startObject(category); + Map> names = categoriesToExtendedData.get(category); + for (String name : names.keySet()) { + for (String property : names.get(name).keySet()) { + builder.field(property, names.get(name).get(property)); + } + } + builder.endObject(); + } + builder.endObject(); + return builder; } @Override diff --git a/server/src/main/java/org/elasticsearch/action/admin/cluster/stats/SearchUsageStats.java b/server/src/main/java/org/elasticsearch/action/admin/cluster/stats/SearchUsageStats.java index ac341681b9457..735c51733e2e9 100644 --- a/server/src/main/java/org/elasticsearch/action/admin/cluster/stats/SearchUsageStats.java +++ b/server/src/main/java/org/elasticsearch/action/admin/cluster/stats/SearchUsageStats.java @@ -80,8 +80,9 @@ public SearchUsageStats(StreamInput in) throws IOException { this.totalSearchCount = in.readVLong(); this.rescorers = in.getTransportVersion().onOrAfter(V_8_12_0) ? in.readMap(StreamInput::readLong) : Map.of(); this.retrievers = in.getTransportVersion().onOrAfter(V_8_16_0) ? in.readMap(StreamInput::readLong) : Map.of(); - this.extendedSearchUsageStats = - in.getTransportVersion().supports(EXTENDED_SEARCH_USAGE_TELEMETRY) ? new ExtendedSearchUsageStats(in) : new ExtendedSearchUsageStats(); + this.extendedSearchUsageStats = in.getTransportVersion().supports(EXTENDED_SEARCH_USAGE_TELEMETRY) + ? new ExtendedSearchUsageStats(in) + : new ExtendedSearchUsageStats(); } @Override diff --git a/server/src/main/java/org/elasticsearch/usage/SearchUsage.java b/server/src/main/java/org/elasticsearch/usage/SearchUsage.java index 963999e532544..8e5ce86fb05f3 100644 --- a/server/src/main/java/org/elasticsearch/usage/SearchUsage.java +++ b/server/src/main/java/org/elasticsearch/usage/SearchUsage.java @@ -65,7 +65,6 @@ public void trackRetrieverExtendedDataUsage(String name, Set values) { trackExtendedDataUsage(RETRIEVERS_NAME, name, values); } - /** * Returns the query types that have been used at least once in the tracked search request */ @@ -97,7 +96,7 @@ public Set getRetrieverUsage() { /** * Returns the extended data that has been tracked for the search request */ - public Map>> getExtendedDataUsage() { + public Map>> getExtendedDataUsage() { return extendedUsage.getUsage(); } @@ -113,21 +112,17 @@ private static final class ExtendedUsageTracker { private final Map>> categoriesToExtendedUsage = new HashMap<>(); public void initialize(String category, String name) { - categoriesToExtendedUsage - .computeIfAbsent(category, k -> new HashMap<>()) - .computeIfAbsent(name, k -> new HashSet<>()); + categoriesToExtendedUsage.computeIfAbsent(category, k -> new HashMap<>()).computeIfAbsent(name, k -> new HashSet<>()); } public void track(String category, String name, String value) { - categoriesToExtendedUsage - .computeIfAbsent(category, k -> new HashMap<>()) + categoriesToExtendedUsage.computeIfAbsent(category, k -> new HashMap<>()) .computeIfAbsent(name, k -> new HashSet<>()) .add(value); } public void track(String category, String name, Set values) { - categoriesToExtendedUsage - .computeIfAbsent(category, k -> new HashMap<>()) + categoriesToExtendedUsage.computeIfAbsent(category, k -> new HashMap<>()) .computeIfAbsent(name, k -> new HashSet<>()) .addAll(values); } diff --git a/server/src/main/java/org/elasticsearch/usage/SearchUsageHolder.java b/server/src/main/java/org/elasticsearch/usage/SearchUsageHolder.java index ee5f996471ff8..d851190cc9271 100644 --- a/server/src/main/java/org/elasticsearch/usage/SearchUsageHolder.java +++ b/server/src/main/java/org/elasticsearch/usage/SearchUsageHolder.java @@ -9,8 +9,8 @@ package org.elasticsearch.usage; -import org.elasticsearch.action.admin.cluster.stats.SearchUsageStats; import org.elasticsearch.action.admin.cluster.stats.ExtendedSearchUsageStats; +import org.elasticsearch.action.admin.cluster.stats.SearchUsageStats; import org.elasticsearch.common.util.Maps; import java.util.Collections; @@ -32,7 +32,7 @@ public final class SearchUsageHolder { private final Map rescorersUsage = new ConcurrentHashMap<>(); private final Map sectionsUsage = new ConcurrentHashMap<>(); private final Map retrieversUsage = new ConcurrentHashMap<>(); - private final Map>> extendedSearchUsage = new ConcurrentHashMap<>(); + private final Map>> extendedSearchUsage = new ConcurrentHashMap<>(); SearchUsageHolder() {} @@ -81,18 +81,17 @@ public SearchUsageStats getSearchUsageStats() { } private Map>> getExtendedSearchUsage() { - return unmodifiableMap(extendedSearchUsage, nameMap -> - unmodifiableMap(nameMap, valueMap -> - unmodifiableMap(valueMap, LongAdder::longValue) - ) + return unmodifiableMap( + extendedSearchUsage, + nameMap -> unmodifiableMap(nameMap, valueMap -> unmodifiableMap(valueMap, LongAdder::longValue)) ); } private void updateExtendedUsage(Map>> extendedDataUsage) { - extendedDataUsage.forEach((category, nameMap) -> - nameMap.forEach((name, valueSet) -> - valueSet.forEach(value -> - extendedSearchUsage.computeIfAbsent(category, k -> new ConcurrentHashMap<>()) + extendedDataUsage.forEach( + (category, nameMap) -> nameMap.forEach( + (name, valueSet) -> valueSet.forEach( + value -> extendedSearchUsage.computeIfAbsent(category, k -> new ConcurrentHashMap<>()) .computeIfAbsent(name, k -> new ConcurrentHashMap<>()) .computeIfAbsent(value, k -> new LongAdder()) .increment() From e6521dc9f02636a6659dc32530e07ea0ca0ce9fb Mon Sep 17 00:00:00 2001 From: elasticsearchmachine Date: Tue, 23 Sep 2025 19:11:32 +0000 Subject: [PATCH 05/26] [CI] Update transport version definitions --- server/src/main/resources/transport/upper_bounds/9.2.csv | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/main/resources/transport/upper_bounds/9.2.csv b/server/src/main/resources/transport/upper_bounds/9.2.csv index f98505983c2b6..b1209b927d8a5 100644 --- a/server/src/main/resources/transport/upper_bounds/9.2.csv +++ b/server/src/main/resources/transport/upper_bounds/9.2.csv @@ -1 +1 @@ -extended_search_usage_telemetry,9169000 +inference_api_openai_embeddings_headers,9169000 From 991b017d35d04befa0c33043fea0eeacb52d2983 Mon Sep 17 00:00:00 2001 From: Kathleen DeRusso Date: Tue, 23 Sep 2025 15:18:07 -0400 Subject: [PATCH 06/26] Fix serialization --- .../action/admin/cluster/stats/ExtendedSearchUsageStats.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/server/src/main/java/org/elasticsearch/action/admin/cluster/stats/ExtendedSearchUsageStats.java b/server/src/main/java/org/elasticsearch/action/admin/cluster/stats/ExtendedSearchUsageStats.java index b52fa4d38388c..6151722739589 100644 --- a/server/src/main/java/org/elasticsearch/action/admin/cluster/stats/ExtendedSearchUsageStats.java +++ b/server/src/main/java/org/elasticsearch/action/admin/cluster/stats/ExtendedSearchUsageStats.java @@ -86,9 +86,11 @@ public XContentBuilder toXContent(XContentBuilder builder, Params params) throws builder.startObject(category); Map> names = categoriesToExtendedData.get(category); for (String name : names.keySet()) { + builder.startObject(name); for (String property : names.get(name).keySet()) { builder.field(property, names.get(name).get(property)); } + builder.endObject(); } builder.endObject(); } From d5ecba0e35e4562876505043b267d0cdf0216911 Mon Sep 17 00:00:00 2001 From: Kathleen DeRusso Date: Tue, 23 Sep 2025 16:00:02 -0400 Subject: [PATCH 07/26] Add tests --- .../stats/ExtendedSearchUsageStats.java | 6 +++ .../admin/cluster/stats/SearchUsageStats.java | 2 +- .../cluster/stats/SearchUsageStatsTests.java | 39 ++++++++++++++++++- 3 files changed, 44 insertions(+), 3 deletions(-) diff --git a/server/src/main/java/org/elasticsearch/action/admin/cluster/stats/ExtendedSearchUsageStats.java b/server/src/main/java/org/elasticsearch/action/admin/cluster/stats/ExtendedSearchUsageStats.java index 6151722739589..c63603af9ecb2 100644 --- a/server/src/main/java/org/elasticsearch/action/admin/cluster/stats/ExtendedSearchUsageStats.java +++ b/server/src/main/java/org/elasticsearch/action/admin/cluster/stats/ExtendedSearchUsageStats.java @@ -9,6 +9,7 @@ package org.elasticsearch.action.admin.cluster.stats; +import org.elasticsearch.common.Strings; import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.io.stream.StreamOutput; import org.elasticsearch.common.io.stream.Writeable; @@ -110,4 +111,9 @@ public boolean equals(Object o) { public int hashCode() { return Objects.hash(categoriesToExtendedData); } + + @Override + public String toString() { + return Strings.toString(this, true, true); + } } diff --git a/server/src/main/java/org/elasticsearch/action/admin/cluster/stats/SearchUsageStats.java b/server/src/main/java/org/elasticsearch/action/admin/cluster/stats/SearchUsageStats.java index 735c51733e2e9..797d2714aead9 100644 --- a/server/src/main/java/org/elasticsearch/action/admin/cluster/stats/SearchUsageStats.java +++ b/server/src/main/java/org/elasticsearch/action/admin/cluster/stats/SearchUsageStats.java @@ -33,7 +33,7 @@ */ public final class SearchUsageStats implements Writeable, ToXContentFragment { - private static final TransportVersion EXTENDED_SEARCH_USAGE_TELEMETRY = TransportVersion.fromName("extended_search_usage_telemetry"); + static final TransportVersion EXTENDED_SEARCH_USAGE_TELEMETRY = TransportVersion.fromName("extended_search_usage_telemetry"); private long totalSearchCount; private final Map queries; diff --git a/server/src/test/java/org/elasticsearch/action/admin/cluster/stats/SearchUsageStatsTests.java b/server/src/test/java/org/elasticsearch/action/admin/cluster/stats/SearchUsageStatsTests.java index 46b757407e6a9..90e1e374d7aa6 100644 --- a/server/src/test/java/org/elasticsearch/action/admin/cluster/stats/SearchUsageStatsTests.java +++ b/server/src/test/java/org/elasticsearch/action/admin/cluster/stats/SearchUsageStatsTests.java @@ -21,6 +21,8 @@ import java.util.List; import java.util.Map; +import static org.elasticsearch.action.admin.cluster.stats.SearchUsageStats.EXTENDED_SEARCH_USAGE_TELEMETRY; + public class SearchUsageStatsTests extends AbstractWireSerializingTestCase { private static final List QUERY_TYPES = List.of( @@ -87,6 +89,19 @@ private static Map randomRetrieversUsage(int size) { return retrieversUsage; } + private static ExtendedSearchUsageStats randomExtendedSearchUsage() { + + Map>> categoriesToExtendedData = new HashMap<>(); + if (randomBoolean()) { + categoriesToExtendedData.put( + "retrievers", + Map.of("text_similarity_reranker", Map.of("chunk_rescorer", randomLongBetween(1, 10))) + ); + } + + return new ExtendedSearchUsageStats(categoriesToExtendedData); + } + @Override protected SearchUsageStats createTestInstance() { if (randomBoolean()) { @@ -97,6 +112,7 @@ protected SearchUsageStats createTestInstance() { randomRescorerUsage(randomIntBetween(0, RESCORER_TYPES.size())), randomSectionsUsage(randomIntBetween(0, SECTIONS.size())), randomRetrieversUsage(randomIntBetween(0, RETRIEVERS.size())), + randomExtendedSearchUsage(), randomLongBetween(10, Long.MAX_VALUE) ); } @@ -110,6 +126,7 @@ protected SearchUsageStats mutateInstance(SearchUsageStats instance) { instance.getRescorerUsage(), instance.getSectionsUsage(), instance.getRetrieversUsage(), + instance.getExtendedSearchUsage(), instance.getTotalSearchCount() ); case 1 -> new SearchUsageStats( @@ -117,6 +134,7 @@ protected SearchUsageStats mutateInstance(SearchUsageStats instance) { randomValueOtherThan(instance.getRescorerUsage(), () -> randomRescorerUsage(randomIntBetween(0, RESCORER_TYPES.size()))), instance.getSectionsUsage(), instance.getRetrieversUsage(), + instance.getExtendedSearchUsage(), instance.getTotalSearchCount() ); case 2 -> new SearchUsageStats( @@ -124,6 +142,7 @@ protected SearchUsageStats mutateInstance(SearchUsageStats instance) { instance.getRescorerUsage(), randomValueOtherThan(instance.getSectionsUsage(), () -> randomSectionsUsage(randomIntBetween(0, SECTIONS.size()))), instance.getRetrieversUsage(), + instance.getExtendedSearchUsage(), instance.getTotalSearchCount() ); case 3 -> new SearchUsageStats( @@ -131,6 +150,7 @@ protected SearchUsageStats mutateInstance(SearchUsageStats instance) { instance.getRescorerUsage(), instance.getSectionsUsage(), randomValueOtherThan(instance.getRetrieversUsage(), () -> randomSectionsUsage(randomIntBetween(0, SECTIONS.size()))), + instance.getExtendedSearchUsage(), instance.getTotalSearchCount() ); case 4 -> new SearchUsageStats( @@ -138,6 +158,7 @@ protected SearchUsageStats mutateInstance(SearchUsageStats instance) { instance.getRescorerUsage(), instance.getSectionsUsage(), instance.getRetrieversUsage(), + instance.getExtendedSearchUsage(), randomValueOtherThan(instance.getTotalSearchCount(), () -> randomLongBetween(10, Long.MAX_VALUE)) ); default -> throw new IllegalStateException("Unexpected value: " + i); @@ -150,14 +171,24 @@ public void testAdd() { assertEquals(Map.of(), searchUsageStats.getRescorerUsage()); assertEquals(Map.of(), searchUsageStats.getSectionsUsage()); assertEquals(0, searchUsageStats.getTotalSearchCount()); + assertEquals(new ExtendedSearchUsageStats(), searchUsageStats.getExtendedSearchUsage()); + ExtendedSearchUsageStats extendedSearchUsageStats = randomExtendedSearchUsage(); searchUsageStats.add( - new SearchUsageStats(Map.of("match", 10L), Map.of("query", 5L), Map.of("query", 10L), Map.of("knn", 10L), 10L) + new SearchUsageStats( + Map.of("match", 10L), + Map.of("query", 5L), + Map.of("query", 10L), + Map.of("knn", 10L), + extendedSearchUsageStats, + 10L + ) ); assertEquals(Map.of("match", 10L), searchUsageStats.getQueryUsage()); assertEquals(Map.of("query", 10L), searchUsageStats.getSectionsUsage()); assertEquals(Map.of("query", 5L), searchUsageStats.getRescorerUsage()); assertEquals(10L, searchUsageStats.getTotalSearchCount()); + assertEquals(extendedSearchUsageStats, searchUsageStats.getExtendedSearchUsage()); searchUsageStats.add( new SearchUsageStats( @@ -165,6 +196,7 @@ public void testAdd() { Map.of("query", 5L, "learning_to_rank", 2L), Map.of("query", 10L, "knn", 1L), Map.of("knn", 10L, "rrf", 2L), + extendedSearchUsageStats, 10L ) ); @@ -172,6 +204,7 @@ public void testAdd() { assertEquals(Map.of("query", 20L, "knn", 1L), searchUsageStats.getSectionsUsage()); assertEquals(Map.of("query", 10L, "learning_to_rank", 2L), searchUsageStats.getRescorerUsage()); assertEquals(Map.of("knn", 20L, "rrf", 2L), searchUsageStats.getRetrieversUsage()); + assertEquals(extendedSearchUsageStats, searchUsageStats.getExtendedSearchUsage()); assertEquals(20L, searchUsageStats.getTotalSearchCount()); } @@ -181,11 +214,12 @@ public void testToXContent() throws IOException { Map.of("query", 2L), Map.of("query", 10L), Map.of("knn", 10L), + new ExtendedSearchUsageStats(), 10L ); assertEquals( "{\"search\":{\"total\":10,\"queries\":{\"term\":1},\"rescorers\":{\"query\":2}," - + "\"sections\":{\"query\":10},\"retrievers\":{\"knn\":10}}}", + + "\"sections\":{\"query\":10},\"retrievers\":{\"knn\":10},\"extended\":{}}}", Strings.toString(searchUsageStats) ); } @@ -200,6 +234,7 @@ public void testSerializationBWC() throws IOException { version.onOrAfter(TransportVersions.V_8_12_0) ? randomRescorerUsage(RESCORER_TYPES.size()) : Map.of(), randomSectionsUsage(SECTIONS.size()), version.onOrAfter(TransportVersions.V_8_16_0) ? randomRetrieversUsage(RETRIEVERS.size()) : Map.of(), + version.supports(EXTENDED_SEARCH_USAGE_TELEMETRY) ? randomExtendedSearchUsage() : new ExtendedSearchUsageStats(), randomLongBetween(0, Long.MAX_VALUE) ); assertSerialization(testInstance, version); From 8dc43b8dd62452327c9bc62df0a426b3fc0b7163 Mon Sep 17 00:00:00 2001 From: Kathleen DeRusso Date: Tue, 23 Sep 2025 16:24:32 -0400 Subject: [PATCH 08/26] Update transport --- .../definitions/referable/extended_search_usage_telemetry.csv | 2 +- server/src/main/resources/transport/upper_bounds/9.2.csv | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/server/src/main/resources/transport/definitions/referable/extended_search_usage_telemetry.csv b/server/src/main/resources/transport/definitions/referable/extended_search_usage_telemetry.csv index cec6381023b6b..e9954a5466961 100644 --- a/server/src/main/resources/transport/definitions/referable/extended_search_usage_telemetry.csv +++ b/server/src/main/resources/transport/definitions/referable/extended_search_usage_telemetry.csv @@ -1 +1 @@ -9169000 +9170000 diff --git a/server/src/main/resources/transport/upper_bounds/9.2.csv b/server/src/main/resources/transport/upper_bounds/9.2.csv index b1209b927d8a5..b2577c4ad202a 100644 --- a/server/src/main/resources/transport/upper_bounds/9.2.csv +++ b/server/src/main/resources/transport/upper_bounds/9.2.csv @@ -1 +1 @@ -inference_api_openai_embeddings_headers,9169000 +extended_search_usage_telemetry,9170000 From b6eeb4d690b61a546f93f5d961ace21b80a207ee Mon Sep 17 00:00:00 2001 From: Kathleen DeRusso Date: Tue, 23 Sep 2025 16:45:10 -0400 Subject: [PATCH 09/26] Tests --- .../stats/ExtendedSearchUsageStats.java | 5 ++ .../search/builder/SearchSourceBuilder.java | 1 - .../org/elasticsearch/usage/SearchUsage.java | 3 + .../stats/ExtendedSearchUsageStatsTests.java | 76 +++++++++++++++++++ .../cluster/stats/SearchUsageStatsTests.java | 14 +--- 5 files changed, 85 insertions(+), 14 deletions(-) create mode 100644 server/src/test/java/org/elasticsearch/action/admin/cluster/stats/ExtendedSearchUsageStatsTests.java diff --git a/server/src/main/java/org/elasticsearch/action/admin/cluster/stats/ExtendedSearchUsageStats.java b/server/src/main/java/org/elasticsearch/action/admin/cluster/stats/ExtendedSearchUsageStats.java index c63603af9ecb2..9483f89fc315d 100644 --- a/server/src/main/java/org/elasticsearch/action/admin/cluster/stats/ExtendedSearchUsageStats.java +++ b/server/src/main/java/org/elasticsearch/action/admin/cluster/stats/ExtendedSearchUsageStats.java @@ -18,6 +18,7 @@ import org.elasticsearch.xcontent.XContentBuilder; import java.io.IOException; +import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.Objects; @@ -52,6 +53,10 @@ public ExtendedSearchUsageStats(StreamInput in) throws IOException { ); } + public Map>> getCategoriesToExtendedData() { + return Collections.unmodifiableMap(categoriesToExtendedData); + } + @Override public void writeTo(StreamOutput out) throws IOException { out.writeMap( diff --git a/server/src/main/java/org/elasticsearch/search/builder/SearchSourceBuilder.java b/server/src/main/java/org/elasticsearch/search/builder/SearchSourceBuilder.java index 0cc45742dad5b..f000848e60135 100644 --- a/server/src/main/java/org/elasticsearch/search/builder/SearchSourceBuilder.java +++ b/server/src/main/java/org/elasticsearch/search/builder/SearchSourceBuilder.java @@ -1442,7 +1442,6 @@ private SearchSourceBuilder parseXContent( new RetrieverParserContext(searchUsage, clusterSupportsFeature) ); searchUsage.trackSectionUsage(RETRIEVER.getPreferredName()); - searchUsage.trackSectionUsage(EXTENDED.getPreferredName()); } else if (QUERY_FIELD.match(currentFieldName, parser.getDeprecationHandler())) { if (subSearchSourceBuilders.isEmpty() == false) { throw new IllegalArgumentException( diff --git a/server/src/main/java/org/elasticsearch/usage/SearchUsage.java b/server/src/main/java/org/elasticsearch/usage/SearchUsage.java index 8e5ce86fb05f3..3ce3b82e7f8fb 100644 --- a/server/src/main/java/org/elasticsearch/usage/SearchUsage.java +++ b/server/src/main/java/org/elasticsearch/usage/SearchUsage.java @@ -40,6 +40,9 @@ public void trackQueryUsage(String query) { */ public void trackSectionUsage(String section) { sections.add(section); + if (section.equals("extended")) { + throw new IllegalStateException("foo"); + } } /** diff --git a/server/src/test/java/org/elasticsearch/action/admin/cluster/stats/ExtendedSearchUsageStatsTests.java b/server/src/test/java/org/elasticsearch/action/admin/cluster/stats/ExtendedSearchUsageStatsTests.java new file mode 100644 index 0000000000000..6142000240925 --- /dev/null +++ b/server/src/test/java/org/elasticsearch/action/admin/cluster/stats/ExtendedSearchUsageStatsTests.java @@ -0,0 +1,76 @@ +/* + * 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.admin.cluster.stats; + +import org.elasticsearch.common.io.stream.Writeable.Reader; +import org.elasticsearch.test.AbstractWireSerializingTestCase; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +public class ExtendedSearchUsageStatsTests extends AbstractWireSerializingTestCase { + + @Override + protected Reader instanceReader() { + return ExtendedSearchUsageStats::new; + } + + public static ExtendedSearchUsageStats randomExtendedSearchUsage() { + return randomExtendedSearchUsage(randomBoolean()); + } + + public static ExtendedSearchUsageStats randomExtendedSearchUsage(boolean empty) { + if (empty) { + return new ExtendedSearchUsageStats(); + } + Map>> categoriesToExtendedData = new HashMap<>(); + + // TODO: Gate this behind a randomBoolean() in the future when we have other categories to add. + categoriesToExtendedData.put("retrievers", randomExtendedRetrieversData()); + + return new ExtendedSearchUsageStats(categoriesToExtendedData); + } + + private static Map> randomExtendedRetrieversData() { + Map> retrieversData = new HashMap<>(); + + // TODO: Gate this behind a randomBoolean() in the future when we have other values to add. + Map values = Map.of("chunk_rescorer", randomLongBetween(1, 10)); + retrieversData.put("text_similarity_reranker", values); + + return retrieversData; + } + + @Override + protected ExtendedSearchUsageStats createTestInstance() { + return randomExtendedSearchUsage(); + } + + @Override + protected ExtendedSearchUsageStats mutateInstance(ExtendedSearchUsageStats instance) throws IOException { + Map>> current = instance.getCategoriesToExtendedData(); + Map>> modified = new HashMap<>(); + if (current.isEmpty()) { + modified.put( + "retrievers", + Map.of("text_similarity_reranker", Map.of("chunk_rescorer", randomLongBetween(1, 10))) + ); + } else { + if (randomBoolean()) { + modified.put("retrievers", + Map.of("text_similarity_reranker", + Map.of("chunk_rescorer", (Long) randomValueOtherThan(current, () -> randomLongBetween(1, 10))))); + } + } + return new ExtendedSearchUsageStats(modified); + } + +} diff --git a/server/src/test/java/org/elasticsearch/action/admin/cluster/stats/SearchUsageStatsTests.java b/server/src/test/java/org/elasticsearch/action/admin/cluster/stats/SearchUsageStatsTests.java index 90e1e374d7aa6..d291de1b3d975 100644 --- a/server/src/test/java/org/elasticsearch/action/admin/cluster/stats/SearchUsageStatsTests.java +++ b/server/src/test/java/org/elasticsearch/action/admin/cluster/stats/SearchUsageStatsTests.java @@ -21,6 +21,7 @@ import java.util.List; import java.util.Map; +import static org.elasticsearch.action.admin.cluster.stats.ExtendedSearchUsageStatsTests.randomExtendedSearchUsage; import static org.elasticsearch.action.admin.cluster.stats.SearchUsageStats.EXTENDED_SEARCH_USAGE_TELEMETRY; public class SearchUsageStatsTests extends AbstractWireSerializingTestCase { @@ -89,19 +90,6 @@ private static Map randomRetrieversUsage(int size) { return retrieversUsage; } - private static ExtendedSearchUsageStats randomExtendedSearchUsage() { - - Map>> categoriesToExtendedData = new HashMap<>(); - if (randomBoolean()) { - categoriesToExtendedData.put( - "retrievers", - Map.of("text_similarity_reranker", Map.of("chunk_rescorer", randomLongBetween(1, 10))) - ); - } - - return new ExtendedSearchUsageStats(categoriesToExtendedData); - } - @Override protected SearchUsageStats createTestInstance() { if (randomBoolean()) { From 3c1b802c5a51af016a944ba3d4f1079189045dca Mon Sep 17 00:00:00 2001 From: elasticsearchmachine Date: Wed, 24 Sep 2025 15:02:57 +0000 Subject: [PATCH 10/26] [CI] Auto commit changes from spotless --- .../stats/ExtendedSearchUsageStatsTests.java | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/server/src/test/java/org/elasticsearch/action/admin/cluster/stats/ExtendedSearchUsageStatsTests.java b/server/src/test/java/org/elasticsearch/action/admin/cluster/stats/ExtendedSearchUsageStatsTests.java index 6142000240925..3df02e73dd6bc 100644 --- a/server/src/test/java/org/elasticsearch/action/admin/cluster/stats/ExtendedSearchUsageStatsTests.java +++ b/server/src/test/java/org/elasticsearch/action/admin/cluster/stats/ExtendedSearchUsageStatsTests.java @@ -43,7 +43,7 @@ private static Map> randomExtendedRetrieversData() { Map> retrieversData = new HashMap<>(); // TODO: Gate this behind a randomBoolean() in the future when we have other values to add. - Map values = Map.of("chunk_rescorer", randomLongBetween(1, 10)); + Map values = Map.of("chunk_rescorer", randomLongBetween(1, 10)); retrieversData.put("text_similarity_reranker", values); return retrieversData; @@ -59,15 +59,16 @@ protected ExtendedSearchUsageStats mutateInstance(ExtendedSearchUsageStats insta Map>> current = instance.getCategoriesToExtendedData(); Map>> modified = new HashMap<>(); if (current.isEmpty()) { - modified.put( - "retrievers", - Map.of("text_similarity_reranker", Map.of("chunk_rescorer", randomLongBetween(1, 10))) - ); + modified.put("retrievers", Map.of("text_similarity_reranker", Map.of("chunk_rescorer", randomLongBetween(1, 10)))); } else { if (randomBoolean()) { - modified.put("retrievers", - Map.of("text_similarity_reranker", - Map.of("chunk_rescorer", (Long) randomValueOtherThan(current, () -> randomLongBetween(1, 10))))); + modified.put( + "retrievers", + Map.of( + "text_similarity_reranker", + Map.of("chunk_rescorer", (Long) randomValueOtherThan(current, () -> randomLongBetween(1, 10))) + ) + ); } } return new ExtendedSearchUsageStats(modified); From 9f4e40dccb27e117d0a391064c277be26e25bb8a Mon Sep 17 00:00:00 2001 From: Kathleen DeRusso Date: Wed, 24 Sep 2025 11:20:01 -0400 Subject: [PATCH 11/26] Yaml --- x-pack/plugin/inference/build.gradle | 2 +- .../xpack/inference/InferenceFeatures.java | 7 +-- .../70_text_similarity_rank_retriever.yml | 44 +++++++++++++++++++ 3 files changed, 49 insertions(+), 4 deletions(-) diff --git a/x-pack/plugin/inference/build.gradle b/x-pack/plugin/inference/build.gradle index 9486d239e5de5..b517860591193 100644 --- a/x-pack/plugin/inference/build.gradle +++ b/x-pack/plugin/inference/build.gradle @@ -12,7 +12,7 @@ apply plugin: 'elasticsearch.internal-test-artifact' restResources { restApi { - include '_common', 'bulk', 'indices', 'inference', 'index', 'get', 'update', 'reindex', 'search', 'field_caps', 'capabilities' + include '_common', 'bulk', 'cluster', 'indices', 'inference', 'index', 'get', 'update', 'reindex', 'search', 'field_caps', 'capabilities' } } diff --git a/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/InferenceFeatures.java b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/InferenceFeatures.java index 1c4543f170c9b..fc7611eebe746 100644 --- a/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/InferenceFeatures.java +++ b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/InferenceFeatures.java @@ -49,10 +49,11 @@ public class InferenceFeatures implements FeatureSpecification { private static final NodeFeature COHERE_V2_API = new NodeFeature("inference.cohere.v2"); public static final NodeFeature SEMANTIC_TEXT_HIGHLIGHTING_FLAT = new NodeFeature("semantic_text.highlighter.flat_index_options"); private static final NodeFeature SEMANTIC_TEXT_FIELDS_CHUNKS_FORMAT = new NodeFeature("semantic_text.fields_chunks_format"); + private static final NodeFeature CHUNK_RESCORER_TELEMETRY = new NodeFeature("retrievers.extended_telemetry_chunk_rescorer"); @Override public Set getTestFeatures() { - var testFeatures = new HashSet<>( + return new HashSet<>( Set.of( SemanticTextFieldMapper.SEMANTIC_TEXT_IN_OBJECT_FIELD_FIX, SemanticTextFieldMapper.SEMANTIC_TEXT_SINGLE_FIELD_UPDATE_FIX, @@ -87,9 +88,9 @@ public Set getTestFeatures() { SemanticQueryBuilder.SEMANTIC_QUERY_MULTIPLE_INFERENCE_IDS, SemanticQueryBuilder.SEMANTIC_QUERY_FILTER_FIELD_CAPS_FIX, InterceptedInferenceQueryBuilder.NEW_SEMANTIC_QUERY_INTERCEPTORS, - TEXT_SIMILARITY_RERANKER_SNIPPETS + TEXT_SIMILARITY_RERANKER_SNIPPETS, + CHUNK_RESCORER_TELEMETRY ) ); - return testFeatures; } } diff --git a/x-pack/plugin/inference/src/yamlRestTest/resources/rest-api-spec/test/inference/70_text_similarity_rank_retriever.yml b/x-pack/plugin/inference/src/yamlRestTest/resources/rest-api-spec/test/inference/70_text_similarity_rank_retriever.yml index d971aad2bbc4b..88d230043613b 100644 --- a/x-pack/plugin/inference/src/yamlRestTest/resources/rest-api-spec/test/inference/70_text_similarity_rank_retriever.yml +++ b/x-pack/plugin/inference/src/yamlRestTest/resources/rest-api-spec/test/inference/70_text_similarity_rank_retriever.yml @@ -859,3 +859,47 @@ setup: - match: { hits.hits.0._id: "doc_1" } - match: { hits.hits.1._id: "doc_2" } + +--- +"Chunk rescorer usage is accurately reflected in cluster stats": + + - requires: + cluster_features: "retrievers.extended_telemetry_chunk_rescorer" + reason: rescore_chunks introduced in 9.2.0 + + # Start with empty cluster stats + - do: + cluster.stats: {} + + - match: { indices.search: {total: 0, queries: {}, rescorers: {}, sections: {}, retrievers: {}, extended: {}}} + + - do: + search: + index: test-index + body: + track_total_hits: true + fields: [ "text", "semantic_text_field", "topic" ] + retriever: + text_similarity_reranker: + retriever: + standard: + query: + match: + topic: + query: "science" + rank_window_size: 10 + inference_id: my-rerank-model + inference_text: "how often does the moon hide the sun?" + field: semantic_text_field + chunk_rescorer: { } + size: 10 + + # Should now be populated with retriever usage from the query we just ran + - do: + cluster.stats: { } + + - match: { indices.search.total: 1 } + - match: { indices.search.queries.match: 1 } + - match: { indices.search.retrievers.text_similarity_reranker: 1 } + - match: { indices.search.retrievers.standard: 1 } + - match: { indices.search.extended.retrievers.text_similarity_reranker.chunk_rescorer: 1 } From 269cf3708ca17b147858887c08ca3750af9a039e Mon Sep 17 00:00:00 2001 From: Kathleen DeRusso Date: Wed, 24 Sep 2025 14:55:07 -0400 Subject: [PATCH 12/26] Fix error in SearchUsageStatsTests due to randomization --- .../cluster/stats/SearchUsageStatsTests.java | 20 ++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/server/src/test/java/org/elasticsearch/action/admin/cluster/stats/SearchUsageStatsTests.java b/server/src/test/java/org/elasticsearch/action/admin/cluster/stats/SearchUsageStatsTests.java index d291de1b3d975..f383dca525217 100644 --- a/server/src/test/java/org/elasticsearch/action/admin/cluster/stats/SearchUsageStatsTests.java +++ b/server/src/test/java/org/elasticsearch/action/admin/cluster/stats/SearchUsageStatsTests.java @@ -161,7 +161,16 @@ public void testAdd() { assertEquals(0, searchUsageStats.getTotalSearchCount()); assertEquals(new ExtendedSearchUsageStats(), searchUsageStats.getExtendedSearchUsage()); - ExtendedSearchUsageStats extendedSearchUsageStats = randomExtendedSearchUsage(); + ExtendedSearchUsageStats extendedSearchUsageStats = new ExtendedSearchUsageStats( + Map.of("retrievers", Map.of("text_similarity_reranker", Map.of("chunk_rescorer", 10L))) + ); + ExtendedSearchUsageStats anotherExtendedSearchUsageStats = new ExtendedSearchUsageStats( + Map.of("retrievers", Map.of("text_similarity_reranker", Map.of("chunk_rescorer", 5L))) + ); + ExtendedSearchUsageStats combinedExtendedSearchUsageStats = new ExtendedSearchUsageStats( + Map.of("retrievers", Map.of("text_similarity_reranker", Map.of("chunk_rescorer", 15L))) + ); + searchUsageStats.add( new SearchUsageStats( Map.of("match", 10L), @@ -178,21 +187,22 @@ public void testAdd() { assertEquals(10L, searchUsageStats.getTotalSearchCount()); assertEquals(extendedSearchUsageStats, searchUsageStats.getExtendedSearchUsage()); + + searchUsageStats.add( new SearchUsageStats( Map.of("term", 1L, "match", 1L), Map.of("query", 5L, "learning_to_rank", 2L), Map.of("query", 10L, "knn", 1L), Map.of("knn", 10L, "rrf", 2L), - extendedSearchUsageStats, - 10L - ) + anotherExtendedSearchUsageStats, + 10L) ); assertEquals(Map.of("match", 11L, "term", 1L), searchUsageStats.getQueryUsage()); assertEquals(Map.of("query", 20L, "knn", 1L), searchUsageStats.getSectionsUsage()); assertEquals(Map.of("query", 10L, "learning_to_rank", 2L), searchUsageStats.getRescorerUsage()); assertEquals(Map.of("knn", 20L, "rrf", 2L), searchUsageStats.getRetrieversUsage()); - assertEquals(extendedSearchUsageStats, searchUsageStats.getExtendedSearchUsage()); + assertEquals(combinedExtendedSearchUsageStats, searchUsageStats.getExtendedSearchUsage()); assertEquals(20L, searchUsageStats.getTotalSearchCount()); } From 66020f8ddcad12ef89b177bce63d638a6078279b Mon Sep 17 00:00:00 2001 From: Kathleen DeRusso Date: Wed, 24 Sep 2025 15:00:16 -0400 Subject: [PATCH 13/26] Ensure mutation in serialization tests --- .../admin/cluster/stats/ExtendedSearchUsageStatsTests.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/test/java/org/elasticsearch/action/admin/cluster/stats/ExtendedSearchUsageStatsTests.java b/server/src/test/java/org/elasticsearch/action/admin/cluster/stats/ExtendedSearchUsageStatsTests.java index 3df02e73dd6bc..90fa6165a4d1b 100644 --- a/server/src/test/java/org/elasticsearch/action/admin/cluster/stats/ExtendedSearchUsageStatsTests.java +++ b/server/src/test/java/org/elasticsearch/action/admin/cluster/stats/ExtendedSearchUsageStatsTests.java @@ -66,7 +66,7 @@ protected ExtendedSearchUsageStats mutateInstance(ExtendedSearchUsageStats insta "retrievers", Map.of( "text_similarity_reranker", - Map.of("chunk_rescorer", (Long) randomValueOtherThan(current, () -> randomLongBetween(1, 10))) + Map.of("chunk_rescorer", randomLongBetween(11, 20)) ) ); } From 70150fa8055ac8fc6dfa0438eba3b273e1319796 Mon Sep 17 00:00:00 2001 From: elasticsearchmachine Date: Wed, 24 Sep 2025 19:07:27 +0000 Subject: [PATCH 14/26] [CI] Auto commit changes from spotless --- .../cluster/stats/ExtendedSearchUsageStatsTests.java | 8 +------- .../action/admin/cluster/stats/SearchUsageStatsTests.java | 5 ++--- 2 files changed, 3 insertions(+), 10 deletions(-) diff --git a/server/src/test/java/org/elasticsearch/action/admin/cluster/stats/ExtendedSearchUsageStatsTests.java b/server/src/test/java/org/elasticsearch/action/admin/cluster/stats/ExtendedSearchUsageStatsTests.java index 90fa6165a4d1b..1983a07f8aeae 100644 --- a/server/src/test/java/org/elasticsearch/action/admin/cluster/stats/ExtendedSearchUsageStatsTests.java +++ b/server/src/test/java/org/elasticsearch/action/admin/cluster/stats/ExtendedSearchUsageStatsTests.java @@ -62,13 +62,7 @@ protected ExtendedSearchUsageStats mutateInstance(ExtendedSearchUsageStats insta modified.put("retrievers", Map.of("text_similarity_reranker", Map.of("chunk_rescorer", randomLongBetween(1, 10)))); } else { if (randomBoolean()) { - modified.put( - "retrievers", - Map.of( - "text_similarity_reranker", - Map.of("chunk_rescorer", randomLongBetween(11, 20)) - ) - ); + modified.put("retrievers", Map.of("text_similarity_reranker", Map.of("chunk_rescorer", randomLongBetween(11, 20)))); } } return new ExtendedSearchUsageStats(modified); diff --git a/server/src/test/java/org/elasticsearch/action/admin/cluster/stats/SearchUsageStatsTests.java b/server/src/test/java/org/elasticsearch/action/admin/cluster/stats/SearchUsageStatsTests.java index f383dca525217..eb03ae08a5fcc 100644 --- a/server/src/test/java/org/elasticsearch/action/admin/cluster/stats/SearchUsageStatsTests.java +++ b/server/src/test/java/org/elasticsearch/action/admin/cluster/stats/SearchUsageStatsTests.java @@ -187,8 +187,6 @@ public void testAdd() { assertEquals(10L, searchUsageStats.getTotalSearchCount()); assertEquals(extendedSearchUsageStats, searchUsageStats.getExtendedSearchUsage()); - - searchUsageStats.add( new SearchUsageStats( Map.of("term", 1L, "match", 1L), @@ -196,7 +194,8 @@ public void testAdd() { Map.of("query", 10L, "knn", 1L), Map.of("knn", 10L, "rrf", 2L), anotherExtendedSearchUsageStats, - 10L) + 10L + ) ); assertEquals(Map.of("match", 11L, "term", 1L), searchUsageStats.getQueryUsage()); assertEquals(Map.of("query", 20L, "knn", 1L), searchUsageStats.getSectionsUsage()); From 89e911b04237a1984d1cdd1f13558db10b923ca8 Mon Sep 17 00:00:00 2001 From: Kathleen DeRusso Date: Wed, 24 Sep 2025 15:44:04 -0400 Subject: [PATCH 15/26] Revert "Yaml" This reverts commit 9f4e40dccb27e117d0a391064c277be26e25bb8a. --- x-pack/plugin/inference/build.gradle | 2 +- .../xpack/inference/InferenceFeatures.java | 7 ++- .../70_text_similarity_rank_retriever.yml | 44 ------------------- 3 files changed, 4 insertions(+), 49 deletions(-) diff --git a/x-pack/plugin/inference/build.gradle b/x-pack/plugin/inference/build.gradle index b517860591193..9486d239e5de5 100644 --- a/x-pack/plugin/inference/build.gradle +++ b/x-pack/plugin/inference/build.gradle @@ -12,7 +12,7 @@ apply plugin: 'elasticsearch.internal-test-artifact' restResources { restApi { - include '_common', 'bulk', 'cluster', 'indices', 'inference', 'index', 'get', 'update', 'reindex', 'search', 'field_caps', 'capabilities' + include '_common', 'bulk', 'indices', 'inference', 'index', 'get', 'update', 'reindex', 'search', 'field_caps', 'capabilities' } } diff --git a/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/InferenceFeatures.java b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/InferenceFeatures.java index fc7611eebe746..1c4543f170c9b 100644 --- a/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/InferenceFeatures.java +++ b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/InferenceFeatures.java @@ -49,11 +49,10 @@ public class InferenceFeatures implements FeatureSpecification { private static final NodeFeature COHERE_V2_API = new NodeFeature("inference.cohere.v2"); public static final NodeFeature SEMANTIC_TEXT_HIGHLIGHTING_FLAT = new NodeFeature("semantic_text.highlighter.flat_index_options"); private static final NodeFeature SEMANTIC_TEXT_FIELDS_CHUNKS_FORMAT = new NodeFeature("semantic_text.fields_chunks_format"); - private static final NodeFeature CHUNK_RESCORER_TELEMETRY = new NodeFeature("retrievers.extended_telemetry_chunk_rescorer"); @Override public Set getTestFeatures() { - return new HashSet<>( + var testFeatures = new HashSet<>( Set.of( SemanticTextFieldMapper.SEMANTIC_TEXT_IN_OBJECT_FIELD_FIX, SemanticTextFieldMapper.SEMANTIC_TEXT_SINGLE_FIELD_UPDATE_FIX, @@ -88,9 +87,9 @@ public Set getTestFeatures() { SemanticQueryBuilder.SEMANTIC_QUERY_MULTIPLE_INFERENCE_IDS, SemanticQueryBuilder.SEMANTIC_QUERY_FILTER_FIELD_CAPS_FIX, InterceptedInferenceQueryBuilder.NEW_SEMANTIC_QUERY_INTERCEPTORS, - TEXT_SIMILARITY_RERANKER_SNIPPETS, - CHUNK_RESCORER_TELEMETRY + TEXT_SIMILARITY_RERANKER_SNIPPETS ) ); + return testFeatures; } } diff --git a/x-pack/plugin/inference/src/yamlRestTest/resources/rest-api-spec/test/inference/70_text_similarity_rank_retriever.yml b/x-pack/plugin/inference/src/yamlRestTest/resources/rest-api-spec/test/inference/70_text_similarity_rank_retriever.yml index 88d230043613b..d971aad2bbc4b 100644 --- a/x-pack/plugin/inference/src/yamlRestTest/resources/rest-api-spec/test/inference/70_text_similarity_rank_retriever.yml +++ b/x-pack/plugin/inference/src/yamlRestTest/resources/rest-api-spec/test/inference/70_text_similarity_rank_retriever.yml @@ -859,47 +859,3 @@ setup: - match: { hits.hits.0._id: "doc_1" } - match: { hits.hits.1._id: "doc_2" } - ---- -"Chunk rescorer usage is accurately reflected in cluster stats": - - - requires: - cluster_features: "retrievers.extended_telemetry_chunk_rescorer" - reason: rescore_chunks introduced in 9.2.0 - - # Start with empty cluster stats - - do: - cluster.stats: {} - - - match: { indices.search: {total: 0, queries: {}, rescorers: {}, sections: {}, retrievers: {}, extended: {}}} - - - do: - search: - index: test-index - body: - track_total_hits: true - fields: [ "text", "semantic_text_field", "topic" ] - retriever: - text_similarity_reranker: - retriever: - standard: - query: - match: - topic: - query: "science" - rank_window_size: 10 - inference_id: my-rerank-model - inference_text: "how often does the moon hide the sun?" - field: semantic_text_field - chunk_rescorer: { } - size: 10 - - # Should now be populated with retriever usage from the query we just ran - - do: - cluster.stats: { } - - - match: { indices.search.total: 1 } - - match: { indices.search.queries.match: 1 } - - match: { indices.search.retrievers.text_similarity_reranker: 1 } - - match: { indices.search.retrievers.standard: 1 } - - match: { indices.search.extended.retrievers.text_similarity_reranker.chunk_rescorer: 1 } From 9843a412ed53cb1e27a21749e61e93f84a7bd800 Mon Sep 17 00:00:00 2001 From: elasticsearchmachine Date: Wed, 24 Sep 2025 19:51:25 +0000 Subject: [PATCH 16/26] [CI] Update transport version definitions --- .../definitions/referable/extended_search_usage_telemetry.csv | 2 +- server/src/main/resources/transport/upper_bounds/8.18.csv | 2 +- server/src/main/resources/transport/upper_bounds/8.19.csv | 2 +- server/src/main/resources/transport/upper_bounds/9.0.csv | 2 +- server/src/main/resources/transport/upper_bounds/9.1.csv | 2 +- server/src/main/resources/transport/upper_bounds/9.2.csv | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/server/src/main/resources/transport/definitions/referable/extended_search_usage_telemetry.csv b/server/src/main/resources/transport/definitions/referable/extended_search_usage_telemetry.csv index e9954a5466961..833feabb55a10 100644 --- a/server/src/main/resources/transport/definitions/referable/extended_search_usage_telemetry.csv +++ b/server/src/main/resources/transport/definitions/referable/extended_search_usage_telemetry.csv @@ -1 +1 @@ -9170000 +9171000 diff --git a/server/src/main/resources/transport/upper_bounds/8.18.csv b/server/src/main/resources/transport/upper_bounds/8.18.csv index ffc592e1809ee..266bfbbd3bf78 100644 --- a/server/src/main/resources/transport/upper_bounds/8.18.csv +++ b/server/src/main/resources/transport/upper_bounds/8.18.csv @@ -1 +1 @@ -initial_elasticsearch_8_18_8,8840010 +transform_check_for_dangling_tasks,8840011 diff --git a/server/src/main/resources/transport/upper_bounds/8.19.csv b/server/src/main/resources/transport/upper_bounds/8.19.csv index 3cc6f439c5ea5..3600b3f8c633a 100644 --- a/server/src/main/resources/transport/upper_bounds/8.19.csv +++ b/server/src/main/resources/transport/upper_bounds/8.19.csv @@ -1 +1 @@ -initial_elasticsearch_8_19_5,8841069 +transform_check_for_dangling_tasks,8841070 diff --git a/server/src/main/resources/transport/upper_bounds/9.0.csv b/server/src/main/resources/transport/upper_bounds/9.0.csv index 8ad2ed1a4cacf..c11e6837bb813 100644 --- a/server/src/main/resources/transport/upper_bounds/9.0.csv +++ b/server/src/main/resources/transport/upper_bounds/9.0.csv @@ -1 +1 @@ -initial_elasticsearch_9_0_8,9000017 +transform_check_for_dangling_tasks,9000018 diff --git a/server/src/main/resources/transport/upper_bounds/9.1.csv b/server/src/main/resources/transport/upper_bounds/9.1.csv index 1cea5dc4d929b..80b97d85f7511 100644 --- a/server/src/main/resources/transport/upper_bounds/9.1.csv +++ b/server/src/main/resources/transport/upper_bounds/9.1.csv @@ -1 +1 @@ -initial_elasticsearch_9_1_5,9112008 +transform_check_for_dangling_tasks,9112009 diff --git a/server/src/main/resources/transport/upper_bounds/9.2.csv b/server/src/main/resources/transport/upper_bounds/9.2.csv index b2577c4ad202a..06838e0edee9e 100644 --- a/server/src/main/resources/transport/upper_bounds/9.2.csv +++ b/server/src/main/resources/transport/upper_bounds/9.2.csv @@ -1 +1 @@ -extended_search_usage_telemetry,9170000 +extended_search_usage_telemetry,9171000 From c6da79e9aa06e10d9c8fd91efc52e0597cceed84 Mon Sep 17 00:00:00 2001 From: Kathleen DeRusso Date: Wed, 24 Sep 2025 16:15:01 -0400 Subject: [PATCH 17/26] Remove old debug breakpoint --- server/src/main/java/org/elasticsearch/usage/SearchUsage.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/server/src/main/java/org/elasticsearch/usage/SearchUsage.java b/server/src/main/java/org/elasticsearch/usage/SearchUsage.java index 3ce3b82e7f8fb..8e5ce86fb05f3 100644 --- a/server/src/main/java/org/elasticsearch/usage/SearchUsage.java +++ b/server/src/main/java/org/elasticsearch/usage/SearchUsage.java @@ -40,9 +40,6 @@ public void trackQueryUsage(String query) { */ public void trackSectionUsage(String section) { sections.add(section); - if (section.equals("extended")) { - throw new IllegalStateException("foo"); - } } /** From 5290e096d44eb549a1cc36f18e1c3d7a2fe32862 Mon Sep 17 00:00:00 2001 From: Kathleen DeRusso Date: Thu, 25 Sep 2025 10:29:25 -0400 Subject: [PATCH 18/26] PR feedback: Refactor extended stats into objects instead of forcing to Map --- .../stats/ExtendedSearchUsageLongCounter.java | 72 +++++++++++++++++++ .../stats/ExtendedSearchUsageMetric.java | 26 +++++++ .../stats/ExtendedSearchUsageStats.java | 32 +++------ .../usage/SearchUsageHolder.java | 9 ++- .../stats/ExtendedSearchUsageStatsTests.java | 26 ++++--- .../cluster/stats/SearchUsageStatsTests.java | 6 +- 6 files changed, 133 insertions(+), 38 deletions(-) create mode 100644 server/src/main/java/org/elasticsearch/action/admin/cluster/stats/ExtendedSearchUsageLongCounter.java create mode 100644 server/src/main/java/org/elasticsearch/action/admin/cluster/stats/ExtendedSearchUsageMetric.java diff --git a/server/src/main/java/org/elasticsearch/action/admin/cluster/stats/ExtendedSearchUsageLongCounter.java b/server/src/main/java/org/elasticsearch/action/admin/cluster/stats/ExtendedSearchUsageLongCounter.java new file mode 100644 index 0000000000000..fac59519b570f --- /dev/null +++ b/server/src/main/java/org/elasticsearch/action/admin/cluster/stats/ExtendedSearchUsageLongCounter.java @@ -0,0 +1,72 @@ +/* + * 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.admin.cluster.stats; + +import org.elasticsearch.common.io.stream.StreamInput; +import org.elasticsearch.common.io.stream.StreamOutput; +import org.elasticsearch.xcontent.XContentBuilder; + +import java.io.IOException; +import java.util.Map; +import java.util.Objects; + +/** + * An {@link ExtendedSearchUsageMetric} implementation that holds a map of values to counts. + */ +public class ExtendedSearchUsageLongCounter implements ExtendedSearchUsageMetric { + + private final Map values; + + public ExtendedSearchUsageLongCounter(Map values) { + this.values = values; + } + + public ExtendedSearchUsageLongCounter(StreamInput in) throws IOException { + this.values = in.readMap(StreamInput::readString, StreamInput::readLong); + } + + public Map getValues() { + return values; + } + + public ExtendedSearchUsageMetric merge(ExtendedSearchUsageMetric other) { + assert other instanceof ExtendedSearchUsageLongCounter; + Map values = new java.util.HashMap<>(this.values); + ExtendedSearchUsageLongCounter otherLongCounter = (ExtendedSearchUsageLongCounter) other; + otherLongCounter.getValues().forEach((key, otherValue) -> { values.merge(key, otherValue, Long::sum); }); + return new ExtendedSearchUsageLongCounter(values); + } + + @Override + public void writeTo(StreamOutput out) throws IOException { + out.writeMap(values, StreamOutput::writeString, StreamOutput::writeLong); + } + + @Override + public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { + for (String key : values.keySet()) { + builder.field(key, values.get(key)); + } + return builder; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + ExtendedSearchUsageLongCounter that = (ExtendedSearchUsageLongCounter) o; + return Objects.equals(values, that.values); + } + + @Override + public int hashCode() { + return values.hashCode(); + } +} diff --git a/server/src/main/java/org/elasticsearch/action/admin/cluster/stats/ExtendedSearchUsageMetric.java b/server/src/main/java/org/elasticsearch/action/admin/cluster/stats/ExtendedSearchUsageMetric.java new file mode 100644 index 0000000000000..c02ee04db368a --- /dev/null +++ b/server/src/main/java/org/elasticsearch/action/admin/cluster/stats/ExtendedSearchUsageMetric.java @@ -0,0 +1,26 @@ +/* + * 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.admin.cluster.stats; + +import org.elasticsearch.common.io.stream.Writeable; +import org.elasticsearch.xcontent.ToXContent; + +/** + * Represents a metric colleged as part of {@link ExtendedSearchUsageStats}. + */ +public interface ExtendedSearchUsageMetric extends Writeable, ToXContent { + + /** + * Merges two equivalent metrics together for statistical reporting. + * @param other Another {@link ExtendedSearchUsageMetric}. + * @return ExtendedSearchUsageMetric The merged metric. + */ + ExtendedSearchUsageMetric merge(ExtendedSearchUsageMetric other); +} diff --git a/server/src/main/java/org/elasticsearch/action/admin/cluster/stats/ExtendedSearchUsageStats.java b/server/src/main/java/org/elasticsearch/action/admin/cluster/stats/ExtendedSearchUsageStats.java index 9483f89fc315d..8904f363e8838 100644 --- a/server/src/main/java/org/elasticsearch/action/admin/cluster/stats/ExtendedSearchUsageStats.java +++ b/server/src/main/java/org/elasticsearch/action/admin/cluster/stats/ExtendedSearchUsageStats.java @@ -32,28 +32,26 @@ public class ExtendedSearchUsageStats implements Writeable, ToXContent { * A map of categories to extended data. Categories correspond to a high-level search usage statistic, * e.g. `queries`, `rescorers`, `sections`, `retrievers`. * - * Extended data is further segmented by name, for example collecting specific statistics for certain retrievers only. - * - * Finally, we have string:count pairs that track each individual metric we wish to track. + * Extended data is further segmented by name, e.g., collecting specific statistics for certain retrievers only. */ - private final Map>> categoriesToExtendedData; + private final Map> categoriesToExtendedData; public ExtendedSearchUsageStats() { this.categoriesToExtendedData = new HashMap<>(); } - public ExtendedSearchUsageStats(Map>> categoriesToExtendedData) { + public ExtendedSearchUsageStats(Map> categoriesToExtendedData) { this.categoriesToExtendedData = categoriesToExtendedData; } public ExtendedSearchUsageStats(StreamInput in) throws IOException { this.categoriesToExtendedData = in.readMap( StreamInput::readString, - i -> i.readMap(StreamInput::readString, j -> j.readMap(StreamInput::readString, StreamInput::readLong)) + i -> i.readMap(StreamInput::readString, ExtendedSearchUsageLongCounter::new) ); } - public Map>> getCategoriesToExtendedData() { + public Map> getCategoriesToExtendedData() { return Collections.unmodifiableMap(categoriesToExtendedData); } @@ -62,23 +60,15 @@ public void writeTo(StreamOutput out) throws IOException { out.writeMap( categoriesToExtendedData, StreamOutput::writeString, - (o, v) -> o.writeMap(v, StreamOutput::writeString, (p, q) -> p.writeMap(q, StreamOutput::writeString, StreamOutput::writeLong)) + (o, v) -> o.writeMap(v, StreamOutput::writeString, (p, q) -> q.writeTo(p)) ); } public void merge(ExtendedSearchUsageStats other) { other.categoriesToExtendedData.forEach((key, otherMap) -> { categoriesToExtendedData.merge(key, otherMap, (existingMap, newMap) -> { - Map> mergedMap = new HashMap<>(existingMap); - newMap.forEach((innerKey, innerValue) -> { - mergedMap.merge(innerKey, innerValue, (existingInnerMap, newInnerMap) -> { - Map mergedInnerMap = new HashMap<>(existingInnerMap); - newInnerMap.forEach( - (propertyKey, propertyValue) -> { mergedInnerMap.merge(propertyKey, propertyValue, Long::sum); } - ); - return mergedInnerMap; - }); - }); + Map mergedMap = new HashMap<>(existingMap); + newMap.forEach((innerKey, innerValue) -> { mergedMap.merge(innerKey, innerValue, ExtendedSearchUsageMetric::merge); }); return mergedMap; }); }); @@ -90,12 +80,10 @@ public XContentBuilder toXContent(XContentBuilder builder, Params params) throws builder.startObject(); for (String category : categoriesToExtendedData.keySet()) { builder.startObject(category); - Map> names = categoriesToExtendedData.get(category); + Map names = categoriesToExtendedData.get(category); for (String name : names.keySet()) { builder.startObject(name); - for (String property : names.get(name).keySet()) { - builder.field(property, names.get(name).get(property)); - } + names.get(name).toXContent(builder, params); builder.endObject(); } builder.endObject(); diff --git a/server/src/main/java/org/elasticsearch/usage/SearchUsageHolder.java b/server/src/main/java/org/elasticsearch/usage/SearchUsageHolder.java index d851190cc9271..6c6135b92a794 100644 --- a/server/src/main/java/org/elasticsearch/usage/SearchUsageHolder.java +++ b/server/src/main/java/org/elasticsearch/usage/SearchUsageHolder.java @@ -9,6 +9,8 @@ package org.elasticsearch.usage; +import org.elasticsearch.action.admin.cluster.stats.ExtendedSearchUsageLongCounter; +import org.elasticsearch.action.admin.cluster.stats.ExtendedSearchUsageMetric; import org.elasticsearch.action.admin.cluster.stats.ExtendedSearchUsageStats; import org.elasticsearch.action.admin.cluster.stats.SearchUsageStats; import org.elasticsearch.common.util.Maps; @@ -80,10 +82,13 @@ public SearchUsageStats getSearchUsageStats() { ); } - private Map>> getExtendedSearchUsage() { + private Map> getExtendedSearchUsage() { return unmodifiableMap( extendedSearchUsage, - nameMap -> unmodifiableMap(nameMap, valueMap -> unmodifiableMap(valueMap, LongAdder::longValue)) + nameMap -> unmodifiableMap( + nameMap, + valueMap -> new ExtendedSearchUsageLongCounter(unmodifiableMap(valueMap, LongAdder::longValue)) + ) ); } diff --git a/server/src/test/java/org/elasticsearch/action/admin/cluster/stats/ExtendedSearchUsageStatsTests.java b/server/src/test/java/org/elasticsearch/action/admin/cluster/stats/ExtendedSearchUsageStatsTests.java index 1983a07f8aeae..315e4f1284922 100644 --- a/server/src/test/java/org/elasticsearch/action/admin/cluster/stats/ExtendedSearchUsageStatsTests.java +++ b/server/src/test/java/org/elasticsearch/action/admin/cluster/stats/ExtendedSearchUsageStatsTests.java @@ -31,7 +31,7 @@ public static ExtendedSearchUsageStats randomExtendedSearchUsage(boolean empty) if (empty) { return new ExtendedSearchUsageStats(); } - Map>> categoriesToExtendedData = new HashMap<>(); + Map> categoriesToExtendedData = new HashMap<>(); // TODO: Gate this behind a randomBoolean() in the future when we have other categories to add. categoriesToExtendedData.put("retrievers", randomExtendedRetrieversData()); @@ -39,11 +39,11 @@ public static ExtendedSearchUsageStats randomExtendedSearchUsage(boolean empty) return new ExtendedSearchUsageStats(categoriesToExtendedData); } - private static Map> randomExtendedRetrieversData() { - Map> retrieversData = new HashMap<>(); + private static Map randomExtendedRetrieversData() { + Map retrieversData = new HashMap<>(); // TODO: Gate this behind a randomBoolean() in the future when we have other values to add. - Map values = Map.of("chunk_rescorer", randomLongBetween(1, 10)); + ExtendedSearchUsageMetric values = new ExtendedSearchUsageLongCounter(Map.of("chunk_rescorer", randomLongBetween(1, 10))); retrieversData.put("text_similarity_reranker", values); return retrieversData; @@ -56,14 +56,18 @@ protected ExtendedSearchUsageStats createTestInstance() { @Override protected ExtendedSearchUsageStats mutateInstance(ExtendedSearchUsageStats instance) throws IOException { - Map>> current = instance.getCategoriesToExtendedData(); - Map>> modified = new HashMap<>(); + Map> current = instance.getCategoriesToExtendedData(); + Map> modified = new HashMap<>(); if (current.isEmpty()) { - modified.put("retrievers", Map.of("text_similarity_reranker", Map.of("chunk_rescorer", randomLongBetween(1, 10)))); - } else { - if (randomBoolean()) { - modified.put("retrievers", Map.of("text_similarity_reranker", Map.of("chunk_rescorer", randomLongBetween(11, 20)))); - } + modified.put( + "retrievers", + Map.of("text_similarity_reranker", new ExtendedSearchUsageLongCounter(Map.of("chunk_rescorer", randomLongBetween(1, 10)))) + ); + } else if (randomBoolean()) { + modified.put( + "retrivers", + Map.of("text_similarity_reranker", new ExtendedSearchUsageLongCounter(Map.of("chunk_rescorer", randomLongBetween(11, 20)))) + ); } return new ExtendedSearchUsageStats(modified); } diff --git a/server/src/test/java/org/elasticsearch/action/admin/cluster/stats/SearchUsageStatsTests.java b/server/src/test/java/org/elasticsearch/action/admin/cluster/stats/SearchUsageStatsTests.java index eb03ae08a5fcc..027e0b0a340d3 100644 --- a/server/src/test/java/org/elasticsearch/action/admin/cluster/stats/SearchUsageStatsTests.java +++ b/server/src/test/java/org/elasticsearch/action/admin/cluster/stats/SearchUsageStatsTests.java @@ -162,13 +162,13 @@ public void testAdd() { assertEquals(new ExtendedSearchUsageStats(), searchUsageStats.getExtendedSearchUsage()); ExtendedSearchUsageStats extendedSearchUsageStats = new ExtendedSearchUsageStats( - Map.of("retrievers", Map.of("text_similarity_reranker", Map.of("chunk_rescorer", 10L))) + Map.of("retrievers", Map.of("text_similarity_reranker", new ExtendedSearchUsageLongCounter(Map.of("chunk_rescorer", 10L)))) ); ExtendedSearchUsageStats anotherExtendedSearchUsageStats = new ExtendedSearchUsageStats( - Map.of("retrievers", Map.of("text_similarity_reranker", Map.of("chunk_rescorer", 5L))) + Map.of("retrievers", Map.of("text_similarity_reranker", new ExtendedSearchUsageLongCounter(Map.of("chunk_rescorer", 5L)))) ); ExtendedSearchUsageStats combinedExtendedSearchUsageStats = new ExtendedSearchUsageStats( - Map.of("retrievers", Map.of("text_similarity_reranker", Map.of("chunk_rescorer", 15L))) + Map.of("retrievers", Map.of("text_similarity_reranker", new ExtendedSearchUsageLongCounter(Map.of("chunk_rescorer", 15L)))) ); searchUsageStats.add( From 636104ed3fc977b58221aca701c5384e5d3c754b Mon Sep 17 00:00:00 2001 From: Kathleen DeRusso Date: Thu, 25 Sep 2025 13:26:10 -0400 Subject: [PATCH 19/26] Make ExtendedSeearchUsageMetric a NamedWriteable --- .../java/org/elasticsearch/action/ActionModule.java | 11 +++++++++++ .../cluster/stats/ExtendedSearchUsageLongCounter.java | 7 +++++++ .../cluster/stats/ExtendedSearchUsageMetric.java | 4 ++-- 3 files changed, 20 insertions(+), 2 deletions(-) diff --git a/server/src/main/java/org/elasticsearch/action/ActionModule.java b/server/src/main/java/org/elasticsearch/action/ActionModule.java index 9173c9f120964..20fd270a9163a 100644 --- a/server/src/main/java/org/elasticsearch/action/ActionModule.java +++ b/server/src/main/java/org/elasticsearch/action/ActionModule.java @@ -69,6 +69,7 @@ import org.elasticsearch.action.admin.cluster.snapshots.status.TransportSnapshotsStatusAction; import org.elasticsearch.action.admin.cluster.state.ClusterStateAction; import org.elasticsearch.action.admin.cluster.state.TransportClusterStateAction; +import org.elasticsearch.action.admin.cluster.stats.ExtendedSearchUsageLongCounter; import org.elasticsearch.action.admin.cluster.stats.TransportClusterStatsAction; import org.elasticsearch.action.admin.cluster.storedscripts.GetScriptContextAction; import org.elasticsearch.action.admin.cluster.storedscripts.GetScriptLanguageAction; @@ -1078,4 +1079,14 @@ public RestController getRestController() { public ReservedClusterStateService getReservedClusterStateService() { return reservedClusterStateService; } + + public List getNamedWriteables() { + return List.of( + new NamedWriteableRegistry.Entry( + ExtendedSearchUsageLongCounter.class, + ExtendedSearchUsageLongCounter.NAME, + ExtendedSearchUsageLongCounter::new + ) + ); + } } diff --git a/server/src/main/java/org/elasticsearch/action/admin/cluster/stats/ExtendedSearchUsageLongCounter.java b/server/src/main/java/org/elasticsearch/action/admin/cluster/stats/ExtendedSearchUsageLongCounter.java index fac59519b570f..fe4a402657f42 100644 --- a/server/src/main/java/org/elasticsearch/action/admin/cluster/stats/ExtendedSearchUsageLongCounter.java +++ b/server/src/main/java/org/elasticsearch/action/admin/cluster/stats/ExtendedSearchUsageLongCounter.java @@ -22,6 +22,8 @@ */ public class ExtendedSearchUsageLongCounter implements ExtendedSearchUsageMetric { + public static final String NAME = "extended_search_usage_long_counter"; + private final Map values; public ExtendedSearchUsageLongCounter(Map values) { @@ -69,4 +71,9 @@ public boolean equals(Object o) { public int hashCode() { return values.hashCode(); } + + @Override + public String getWriteableName() { + return NAME; + } } diff --git a/server/src/main/java/org/elasticsearch/action/admin/cluster/stats/ExtendedSearchUsageMetric.java b/server/src/main/java/org/elasticsearch/action/admin/cluster/stats/ExtendedSearchUsageMetric.java index c02ee04db368a..1d05b813b2ff7 100644 --- a/server/src/main/java/org/elasticsearch/action/admin/cluster/stats/ExtendedSearchUsageMetric.java +++ b/server/src/main/java/org/elasticsearch/action/admin/cluster/stats/ExtendedSearchUsageMetric.java @@ -9,13 +9,13 @@ package org.elasticsearch.action.admin.cluster.stats; -import org.elasticsearch.common.io.stream.Writeable; +import org.elasticsearch.common.io.stream.NamedWriteable; import org.elasticsearch.xcontent.ToXContent; /** * Represents a metric colleged as part of {@link ExtendedSearchUsageStats}. */ -public interface ExtendedSearchUsageMetric extends Writeable, ToXContent { +public interface ExtendedSearchUsageMetric extends NamedWriteable, ToXContent { /** * Merges two equivalent metrics together for statistical reporting. From c6e03dd8709a6599bb9898ae0a046cd7ddadfb0a Mon Sep 17 00:00:00 2001 From: Kathleen DeRusso Date: Thu, 25 Sep 2025 15:57:40 -0400 Subject: [PATCH 20/26] Regenerate transport version --- .../definitions/referable/extended_search_usage_telemetry.csv | 2 +- server/src/main/resources/transport/upper_bounds/9.2.csv | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/server/src/main/resources/transport/definitions/referable/extended_search_usage_telemetry.csv b/server/src/main/resources/transport/definitions/referable/extended_search_usage_telemetry.csv index 833feabb55a10..35154103cd0da 100644 --- a/server/src/main/resources/transport/definitions/referable/extended_search_usage_telemetry.csv +++ b/server/src/main/resources/transport/definitions/referable/extended_search_usage_telemetry.csv @@ -1 +1 @@ -9171000 +9174000 diff --git a/server/src/main/resources/transport/upper_bounds/9.2.csv b/server/src/main/resources/transport/upper_bounds/9.2.csv index e60434a3e2189..00e640969d8ba 100644 --- a/server/src/main/resources/transport/upper_bounds/9.2.csv +++ b/server/src/main/resources/transport/upper_bounds/9.2.csv @@ -1 +1 @@ -sampling_configuration,9173000 +extended_search_usage_telemetry,9174000 From 788fe03a7542faefe594afc2ac584294850ec02b Mon Sep 17 00:00:00 2001 From: Kathleen DeRusso Date: Fri, 26 Sep 2025 08:38:37 -0400 Subject: [PATCH 21/26] Fix serialization --- .../java/org/elasticsearch/action/ActionModule.java | 6 +++--- .../cluster/stats/ExtendedSearchUsageMetric.java | 1 + .../cluster/stats/ExtendedSearchUsageStats.java | 4 ++-- .../stats/ExtendedSearchUsageStatsTests.java | 13 +++++++++++++ .../admin/cluster/stats/SearchUsageStatsTests.java | 11 +++++++++++ 5 files changed, 30 insertions(+), 5 deletions(-) diff --git a/server/src/main/java/org/elasticsearch/action/ActionModule.java b/server/src/main/java/org/elasticsearch/action/ActionModule.java index 20fd270a9163a..5bc6c067aee8c 100644 --- a/server/src/main/java/org/elasticsearch/action/ActionModule.java +++ b/server/src/main/java/org/elasticsearch/action/ActionModule.java @@ -70,6 +70,7 @@ import org.elasticsearch.action.admin.cluster.state.ClusterStateAction; import org.elasticsearch.action.admin.cluster.state.TransportClusterStateAction; import org.elasticsearch.action.admin.cluster.stats.ExtendedSearchUsageLongCounter; +import org.elasticsearch.action.admin.cluster.stats.ExtendedSearchUsageMetric; import org.elasticsearch.action.admin.cluster.stats.TransportClusterStatsAction; import org.elasticsearch.action.admin.cluster.storedscripts.GetScriptContextAction; import org.elasticsearch.action.admin.cluster.storedscripts.GetScriptLanguageAction; @@ -1083,10 +1084,9 @@ public ReservedClusterStateService getReservedClusterStateService() { public List getNamedWriteables() { return List.of( new NamedWriteableRegistry.Entry( - ExtendedSearchUsageLongCounter.class, + ExtendedSearchUsageMetric.class, ExtendedSearchUsageLongCounter.NAME, - ExtendedSearchUsageLongCounter::new - ) + ExtendedSearchUsageLongCounter::new) ); } } diff --git a/server/src/main/java/org/elasticsearch/action/admin/cluster/stats/ExtendedSearchUsageMetric.java b/server/src/main/java/org/elasticsearch/action/admin/cluster/stats/ExtendedSearchUsageMetric.java index 1d05b813b2ff7..96ab8acf32684 100644 --- a/server/src/main/java/org/elasticsearch/action/admin/cluster/stats/ExtendedSearchUsageMetric.java +++ b/server/src/main/java/org/elasticsearch/action/admin/cluster/stats/ExtendedSearchUsageMetric.java @@ -10,6 +10,7 @@ package org.elasticsearch.action.admin.cluster.stats; import org.elasticsearch.common.io.stream.NamedWriteable; +import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.xcontent.ToXContent; /** diff --git a/server/src/main/java/org/elasticsearch/action/admin/cluster/stats/ExtendedSearchUsageStats.java b/server/src/main/java/org/elasticsearch/action/admin/cluster/stats/ExtendedSearchUsageStats.java index 8904f363e8838..656685d5a8f4b 100644 --- a/server/src/main/java/org/elasticsearch/action/admin/cluster/stats/ExtendedSearchUsageStats.java +++ b/server/src/main/java/org/elasticsearch/action/admin/cluster/stats/ExtendedSearchUsageStats.java @@ -47,7 +47,7 @@ public ExtendedSearchUsageStats(Map i.readMap(StreamInput::readString, ExtendedSearchUsageLongCounter::new) + i -> i.readMap(StreamInput::readString, p -> p.readNamedWriteable(ExtendedSearchUsageMetric.class)) ); } @@ -60,7 +60,7 @@ public void writeTo(StreamOutput out) throws IOException { out.writeMap( categoriesToExtendedData, StreamOutput::writeString, - (o, v) -> o.writeMap(v, StreamOutput::writeString, (p, q) -> q.writeTo(p)) + (o, v) -> o.writeMap(v, StreamOutput::writeString, (p, q) -> out.writeNamedWriteable(q)) ); } diff --git a/server/src/test/java/org/elasticsearch/action/admin/cluster/stats/ExtendedSearchUsageStatsTests.java b/server/src/test/java/org/elasticsearch/action/admin/cluster/stats/ExtendedSearchUsageStatsTests.java index 315e4f1284922..93418aaa1216c 100644 --- a/server/src/test/java/org/elasticsearch/action/admin/cluster/stats/ExtendedSearchUsageStatsTests.java +++ b/server/src/test/java/org/elasticsearch/action/admin/cluster/stats/ExtendedSearchUsageStatsTests.java @@ -9,11 +9,14 @@ package org.elasticsearch.action.admin.cluster.stats; +import org.elasticsearch.common.io.stream.NamedWriteableRegistry; import org.elasticsearch.common.io.stream.Writeable.Reader; import org.elasticsearch.test.AbstractWireSerializingTestCase; import java.io.IOException; +import java.util.Collections; import java.util.HashMap; +import java.util.List; import java.util.Map; public class ExtendedSearchUsageStatsTests extends AbstractWireSerializingTestCase { @@ -23,6 +26,16 @@ protected Reader instanceReader() { return ExtendedSearchUsageStats::new; } + @Override + protected NamedWriteableRegistry getNamedWriteableRegistry() { + return new NamedWriteableRegistry(List.of( + new NamedWriteableRegistry.Entry( + ExtendedSearchUsageMetric.class, + ExtendedSearchUsageLongCounter.NAME, + ExtendedSearchUsageLongCounter::new) + )); + } + public static ExtendedSearchUsageStats randomExtendedSearchUsage() { return randomExtendedSearchUsage(randomBoolean()); } diff --git a/server/src/test/java/org/elasticsearch/action/admin/cluster/stats/SearchUsageStatsTests.java b/server/src/test/java/org/elasticsearch/action/admin/cluster/stats/SearchUsageStatsTests.java index 027e0b0a340d3..6990152e6f4ff 100644 --- a/server/src/test/java/org/elasticsearch/action/admin/cluster/stats/SearchUsageStatsTests.java +++ b/server/src/test/java/org/elasticsearch/action/admin/cluster/stats/SearchUsageStatsTests.java @@ -12,6 +12,7 @@ import org.elasticsearch.TransportVersion; import org.elasticsearch.TransportVersions; import org.elasticsearch.common.Strings; +import org.elasticsearch.common.io.stream.NamedWriteableRegistry; import org.elasticsearch.common.io.stream.Writeable.Reader; import org.elasticsearch.test.AbstractWireSerializingTestCase; import org.elasticsearch.test.TransportVersionUtils; @@ -26,6 +27,16 @@ public class SearchUsageStatsTests extends AbstractWireSerializingTestCase { + @Override + protected NamedWriteableRegistry getNamedWriteableRegistry() { + return new NamedWriteableRegistry(List.of( + new NamedWriteableRegistry.Entry( + ExtendedSearchUsageMetric.class, + ExtendedSearchUsageLongCounter.NAME, + ExtendedSearchUsageLongCounter::new) + )); + } + private static final List QUERY_TYPES = List.of( "match", "bool", From 427df05c21777809e810e77ef342e7b774aa43aa Mon Sep 17 00:00:00 2001 From: elasticsearchmachine Date: Fri, 26 Sep 2025 12:46:12 +0000 Subject: [PATCH 22/26] [CI] Auto commit changes from spotless --- .../org/elasticsearch/action/ActionModule.java | 3 ++- .../cluster/stats/ExtendedSearchUsageMetric.java | 1 - .../stats/ExtendedSearchUsageStatsTests.java | 16 +++++++++------- .../cluster/stats/SearchUsageStatsTests.java | 15 +++++++++------ 4 files changed, 20 insertions(+), 15 deletions(-) diff --git a/server/src/main/java/org/elasticsearch/action/ActionModule.java b/server/src/main/java/org/elasticsearch/action/ActionModule.java index 5bc6c067aee8c..5d4acd0a3f691 100644 --- a/server/src/main/java/org/elasticsearch/action/ActionModule.java +++ b/server/src/main/java/org/elasticsearch/action/ActionModule.java @@ -1086,7 +1086,8 @@ public List getNamedWriteables() { new NamedWriteableRegistry.Entry( ExtendedSearchUsageMetric.class, ExtendedSearchUsageLongCounter.NAME, - ExtendedSearchUsageLongCounter::new) + ExtendedSearchUsageLongCounter::new + ) ); } } diff --git a/server/src/main/java/org/elasticsearch/action/admin/cluster/stats/ExtendedSearchUsageMetric.java b/server/src/main/java/org/elasticsearch/action/admin/cluster/stats/ExtendedSearchUsageMetric.java index 96ab8acf32684..1d05b813b2ff7 100644 --- a/server/src/main/java/org/elasticsearch/action/admin/cluster/stats/ExtendedSearchUsageMetric.java +++ b/server/src/main/java/org/elasticsearch/action/admin/cluster/stats/ExtendedSearchUsageMetric.java @@ -10,7 +10,6 @@ package org.elasticsearch.action.admin.cluster.stats; import org.elasticsearch.common.io.stream.NamedWriteable; -import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.xcontent.ToXContent; /** diff --git a/server/src/test/java/org/elasticsearch/action/admin/cluster/stats/ExtendedSearchUsageStatsTests.java b/server/src/test/java/org/elasticsearch/action/admin/cluster/stats/ExtendedSearchUsageStatsTests.java index 93418aaa1216c..fd108ea86f4bf 100644 --- a/server/src/test/java/org/elasticsearch/action/admin/cluster/stats/ExtendedSearchUsageStatsTests.java +++ b/server/src/test/java/org/elasticsearch/action/admin/cluster/stats/ExtendedSearchUsageStatsTests.java @@ -14,7 +14,6 @@ import org.elasticsearch.test.AbstractWireSerializingTestCase; import java.io.IOException; -import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -28,12 +27,15 @@ protected Reader instanceReader() { @Override protected NamedWriteableRegistry getNamedWriteableRegistry() { - return new NamedWriteableRegistry(List.of( - new NamedWriteableRegistry.Entry( - ExtendedSearchUsageMetric.class, - ExtendedSearchUsageLongCounter.NAME, - ExtendedSearchUsageLongCounter::new) - )); + return new NamedWriteableRegistry( + List.of( + new NamedWriteableRegistry.Entry( + ExtendedSearchUsageMetric.class, + ExtendedSearchUsageLongCounter.NAME, + ExtendedSearchUsageLongCounter::new + ) + ) + ); } public static ExtendedSearchUsageStats randomExtendedSearchUsage() { diff --git a/server/src/test/java/org/elasticsearch/action/admin/cluster/stats/SearchUsageStatsTests.java b/server/src/test/java/org/elasticsearch/action/admin/cluster/stats/SearchUsageStatsTests.java index 6990152e6f4ff..a0e92cfbc103a 100644 --- a/server/src/test/java/org/elasticsearch/action/admin/cluster/stats/SearchUsageStatsTests.java +++ b/server/src/test/java/org/elasticsearch/action/admin/cluster/stats/SearchUsageStatsTests.java @@ -29,12 +29,15 @@ public class SearchUsageStatsTests extends AbstractWireSerializingTestCase QUERY_TYPES = List.of( From 06b6c3f6064684d27890c48ca7ea376686155c20 Mon Sep 17 00:00:00 2001 From: Kathleen DeRusso Date: Fri, 26 Sep 2025 09:25:55 -0400 Subject: [PATCH 23/26] PR Feedback --- .../stats/ExtendedSearchUsageLongCounter.java | 11 +++--- .../stats/ExtendedSearchUsageMetric.java | 6 ++-- .../stats/ExtendedSearchUsageStats.java | 36 ++++++++++--------- .../usage/SearchUsageHolder.java | 2 +- .../stats/ExtendedSearchUsageStatsTests.java | 12 +++---- 5 files changed, 35 insertions(+), 32 deletions(-) diff --git a/server/src/main/java/org/elasticsearch/action/admin/cluster/stats/ExtendedSearchUsageLongCounter.java b/server/src/main/java/org/elasticsearch/action/admin/cluster/stats/ExtendedSearchUsageLongCounter.java index fe4a402657f42..eb49335aeb3f5 100644 --- a/server/src/main/java/org/elasticsearch/action/admin/cluster/stats/ExtendedSearchUsageLongCounter.java +++ b/server/src/main/java/org/elasticsearch/action/admin/cluster/stats/ExtendedSearchUsageLongCounter.java @@ -14,13 +14,14 @@ import org.elasticsearch.xcontent.XContentBuilder; import java.io.IOException; +import java.util.Collections; import java.util.Map; import java.util.Objects; /** * An {@link ExtendedSearchUsageMetric} implementation that holds a map of values to counts. */ -public class ExtendedSearchUsageLongCounter implements ExtendedSearchUsageMetric { +public class ExtendedSearchUsageLongCounter implements ExtendedSearchUsageMetric { public static final String NAME = "extended_search_usage_long_counter"; @@ -35,14 +36,12 @@ public ExtendedSearchUsageLongCounter(StreamInput in) throws IOException { } public Map getValues() { - return values; + return Collections.unmodifiableMap(values); } - public ExtendedSearchUsageMetric merge(ExtendedSearchUsageMetric other) { - assert other instanceof ExtendedSearchUsageLongCounter; + public ExtendedSearchUsageLongCounter merge(ExtendedSearchUsageLongCounter other) { Map values = new java.util.HashMap<>(this.values); - ExtendedSearchUsageLongCounter otherLongCounter = (ExtendedSearchUsageLongCounter) other; - otherLongCounter.getValues().forEach((key, otherValue) -> { values.merge(key, otherValue, Long::sum); }); + other.getValues().forEach((key, otherValue) -> { values.merge(key, otherValue, Long::sum); }); return new ExtendedSearchUsageLongCounter(values); } diff --git a/server/src/main/java/org/elasticsearch/action/admin/cluster/stats/ExtendedSearchUsageMetric.java b/server/src/main/java/org/elasticsearch/action/admin/cluster/stats/ExtendedSearchUsageMetric.java index 1d05b813b2ff7..a4ece806ac04d 100644 --- a/server/src/main/java/org/elasticsearch/action/admin/cluster/stats/ExtendedSearchUsageMetric.java +++ b/server/src/main/java/org/elasticsearch/action/admin/cluster/stats/ExtendedSearchUsageMetric.java @@ -10,17 +10,17 @@ package org.elasticsearch.action.admin.cluster.stats; import org.elasticsearch.common.io.stream.NamedWriteable; -import org.elasticsearch.xcontent.ToXContent; +import org.elasticsearch.xcontent.ToXContentFragment; /** * Represents a metric colleged as part of {@link ExtendedSearchUsageStats}. */ -public interface ExtendedSearchUsageMetric extends NamedWriteable, ToXContent { +public interface ExtendedSearchUsageMetric> extends NamedWriteable, ToXContentFragment { /** * Merges two equivalent metrics together for statistical reporting. * @param other Another {@link ExtendedSearchUsageMetric}. * @return ExtendedSearchUsageMetric The merged metric. */ - ExtendedSearchUsageMetric merge(ExtendedSearchUsageMetric other); + T merge(T other); } diff --git a/server/src/main/java/org/elasticsearch/action/admin/cluster/stats/ExtendedSearchUsageStats.java b/server/src/main/java/org/elasticsearch/action/admin/cluster/stats/ExtendedSearchUsageStats.java index 656685d5a8f4b..9f7b11c11d8bb 100644 --- a/server/src/main/java/org/elasticsearch/action/admin/cluster/stats/ExtendedSearchUsageStats.java +++ b/server/src/main/java/org/elasticsearch/action/admin/cluster/stats/ExtendedSearchUsageStats.java @@ -34,41 +34,45 @@ public class ExtendedSearchUsageStats implements Writeable, ToXContent { * * Extended data is further segmented by name, e.g., collecting specific statistics for certain retrievers only. */ - private final Map> categoriesToExtendedData; + private final Map>> categorizedExtendedData; public ExtendedSearchUsageStats() { - this.categoriesToExtendedData = new HashMap<>(); + this.categorizedExtendedData = new HashMap<>(); } - public ExtendedSearchUsageStats(Map> categoriesToExtendedData) { - this.categoriesToExtendedData = categoriesToExtendedData; + public ExtendedSearchUsageStats(Map>> categorizedExtendedData) { + this.categorizedExtendedData = categorizedExtendedData; } public ExtendedSearchUsageStats(StreamInput in) throws IOException { - this.categoriesToExtendedData = in.readMap( + this.categorizedExtendedData = in.readMap( StreamInput::readString, i -> i.readMap(StreamInput::readString, p -> p.readNamedWriteable(ExtendedSearchUsageMetric.class)) ); } - public Map> getCategoriesToExtendedData() { - return Collections.unmodifiableMap(categoriesToExtendedData); + public Map>> getCategorizedExtendedData() { + return Collections.unmodifiableMap(categorizedExtendedData); } @Override public void writeTo(StreamOutput out) throws IOException { out.writeMap( - categoriesToExtendedData, + categorizedExtendedData, StreamOutput::writeString, (o, v) -> o.writeMap(v, StreamOutput::writeString, (p, q) -> out.writeNamedWriteable(q)) ); } + @SuppressWarnings({"unchecked", "rawtypes"}) public void merge(ExtendedSearchUsageStats other) { - other.categoriesToExtendedData.forEach((key, otherMap) -> { - categoriesToExtendedData.merge(key, otherMap, (existingMap, newMap) -> { - Map mergedMap = new HashMap<>(existingMap); - newMap.forEach((innerKey, innerValue) -> { mergedMap.merge(innerKey, innerValue, ExtendedSearchUsageMetric::merge); }); + other.categorizedExtendedData.forEach((key, otherMap) -> { + categorizedExtendedData.merge(key, otherMap, (existingMap, newMap) -> { + Map> mergedMap = new HashMap<>(existingMap); + newMap.forEach((innerKey, innerValue) -> { + mergedMap.merge(innerKey, innerValue, (existing, incoming) -> + ((ExtendedSearchUsageMetric) existing).merge(incoming)); + }); return mergedMap; }); }); @@ -78,9 +82,9 @@ public void merge(ExtendedSearchUsageStats other) { public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { builder.startObject(); - for (String category : categoriesToExtendedData.keySet()) { + for (String category : categorizedExtendedData.keySet()) { builder.startObject(category); - Map names = categoriesToExtendedData.get(category); + Map> names = categorizedExtendedData.get(category); for (String name : names.keySet()) { builder.startObject(name); names.get(name).toXContent(builder, params); @@ -97,12 +101,12 @@ public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; ExtendedSearchUsageStats that = (ExtendedSearchUsageStats) o; - return Objects.equals(categoriesToExtendedData, that.categoriesToExtendedData); + return Objects.equals(categorizedExtendedData, that.categorizedExtendedData); } @Override public int hashCode() { - return Objects.hash(categoriesToExtendedData); + return Objects.hash(categorizedExtendedData); } @Override diff --git a/server/src/main/java/org/elasticsearch/usage/SearchUsageHolder.java b/server/src/main/java/org/elasticsearch/usage/SearchUsageHolder.java index 6c6135b92a794..68b9b55bc6d1c 100644 --- a/server/src/main/java/org/elasticsearch/usage/SearchUsageHolder.java +++ b/server/src/main/java/org/elasticsearch/usage/SearchUsageHolder.java @@ -82,7 +82,7 @@ public SearchUsageStats getSearchUsageStats() { ); } - private Map> getExtendedSearchUsage() { + private Map>> getExtendedSearchUsage() { return unmodifiableMap( extendedSearchUsage, nameMap -> unmodifiableMap( diff --git a/server/src/test/java/org/elasticsearch/action/admin/cluster/stats/ExtendedSearchUsageStatsTests.java b/server/src/test/java/org/elasticsearch/action/admin/cluster/stats/ExtendedSearchUsageStatsTests.java index fd108ea86f4bf..8f45eea71a44a 100644 --- a/server/src/test/java/org/elasticsearch/action/admin/cluster/stats/ExtendedSearchUsageStatsTests.java +++ b/server/src/test/java/org/elasticsearch/action/admin/cluster/stats/ExtendedSearchUsageStatsTests.java @@ -46,7 +46,7 @@ public static ExtendedSearchUsageStats randomExtendedSearchUsage(boolean empty) if (empty) { return new ExtendedSearchUsageStats(); } - Map> categoriesToExtendedData = new HashMap<>(); + Map>> categoriesToExtendedData = new HashMap<>(); // TODO: Gate this behind a randomBoolean() in the future when we have other categories to add. categoriesToExtendedData.put("retrievers", randomExtendedRetrieversData()); @@ -54,11 +54,11 @@ public static ExtendedSearchUsageStats randomExtendedSearchUsage(boolean empty) return new ExtendedSearchUsageStats(categoriesToExtendedData); } - private static Map randomExtendedRetrieversData() { - Map retrieversData = new HashMap<>(); + private static Map> randomExtendedRetrieversData() { + Map> retrieversData = new HashMap<>(); // TODO: Gate this behind a randomBoolean() in the future when we have other values to add. - ExtendedSearchUsageMetric values = new ExtendedSearchUsageLongCounter(Map.of("chunk_rescorer", randomLongBetween(1, 10))); + ExtendedSearchUsageMetric values = new ExtendedSearchUsageLongCounter(Map.of("chunk_rescorer", randomLongBetween(1, 10))); retrieversData.put("text_similarity_reranker", values); return retrieversData; @@ -71,8 +71,8 @@ protected ExtendedSearchUsageStats createTestInstance() { @Override protected ExtendedSearchUsageStats mutateInstance(ExtendedSearchUsageStats instance) throws IOException { - Map> current = instance.getCategoriesToExtendedData(); - Map> modified = new HashMap<>(); + Map>> current = instance.getCategorizedExtendedData(); + Map>> modified = new HashMap<>(); if (current.isEmpty()) { modified.put( "retrievers", From 18e663c7c01b6d5c34f897a5278218d0a650fdd4 Mon Sep 17 00:00:00 2001 From: elasticsearchmachine Date: Fri, 26 Sep 2025 14:31:31 +0000 Subject: [PATCH 24/26] [CI] Auto commit changes from spotless --- .../action/admin/cluster/stats/ExtendedSearchUsageStats.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/server/src/main/java/org/elasticsearch/action/admin/cluster/stats/ExtendedSearchUsageStats.java b/server/src/main/java/org/elasticsearch/action/admin/cluster/stats/ExtendedSearchUsageStats.java index 9f7b11c11d8bb..2152536f133a1 100644 --- a/server/src/main/java/org/elasticsearch/action/admin/cluster/stats/ExtendedSearchUsageStats.java +++ b/server/src/main/java/org/elasticsearch/action/admin/cluster/stats/ExtendedSearchUsageStats.java @@ -64,14 +64,13 @@ public void writeTo(StreamOutput out) throws IOException { ); } - @SuppressWarnings({"unchecked", "rawtypes"}) + @SuppressWarnings({ "unchecked", "rawtypes" }) public void merge(ExtendedSearchUsageStats other) { other.categorizedExtendedData.forEach((key, otherMap) -> { categorizedExtendedData.merge(key, otherMap, (existingMap, newMap) -> { Map> mergedMap = new HashMap<>(existingMap); newMap.forEach((innerKey, innerValue) -> { - mergedMap.merge(innerKey, innerValue, (existing, incoming) -> - ((ExtendedSearchUsageMetric) existing).merge(incoming)); + mergedMap.merge(innerKey, innerValue, (existing, incoming) -> ((ExtendedSearchUsageMetric) existing).merge(incoming)); }); return mergedMap; }); From d3b7ecc461a1f9b26e36db11236c49e4438c81bf Mon Sep 17 00:00:00 2001 From: Kathleen DeRusso Date: Fri, 26 Sep 2025 13:32:12 -0400 Subject: [PATCH 25/26] PR feedback --- .../cluster/stats/ExtendedSearchUsageLongCounter.java | 7 +++++-- .../admin/cluster/stats/ExtendedSearchUsageMetric.java | 2 +- .../admin/cluster/stats/ExtendedSearchUsageStats.java | 9 +++++---- .../action/admin/cluster/stats/SearchUsageStats.java | 4 ++-- .../search/builder/SearchSourceBuilder.java | 1 - .../cluster/stats/ExtendedSearchUsageStatsTests.java | 4 +++- .../admin/cluster/stats/SearchUsageStatsTests.java | 7 ++++--- 7 files changed, 20 insertions(+), 14 deletions(-) diff --git a/server/src/main/java/org/elasticsearch/action/admin/cluster/stats/ExtendedSearchUsageLongCounter.java b/server/src/main/java/org/elasticsearch/action/admin/cluster/stats/ExtendedSearchUsageLongCounter.java index eb49335aeb3f5..d2968cb78d62c 100644 --- a/server/src/main/java/org/elasticsearch/action/admin/cluster/stats/ExtendedSearchUsageLongCounter.java +++ b/server/src/main/java/org/elasticsearch/action/admin/cluster/stats/ExtendedSearchUsageLongCounter.java @@ -39,9 +39,12 @@ public Map getValues() { return Collections.unmodifiableMap(values); } - public ExtendedSearchUsageLongCounter merge(ExtendedSearchUsageLongCounter other) { + @Override + public ExtendedSearchUsageLongCounter merge(ExtendedSearchUsageMetric other) { + assert other instanceof ExtendedSearchUsageLongCounter; + ExtendedSearchUsageLongCounter otherLongCounter = (ExtendedSearchUsageLongCounter) other; Map values = new java.util.HashMap<>(this.values); - other.getValues().forEach((key, otherValue) -> { values.merge(key, otherValue, Long::sum); }); + otherLongCounter.getValues().forEach((key, otherValue) -> { values.merge(key, otherValue, Long::sum); }); return new ExtendedSearchUsageLongCounter(values); } diff --git a/server/src/main/java/org/elasticsearch/action/admin/cluster/stats/ExtendedSearchUsageMetric.java b/server/src/main/java/org/elasticsearch/action/admin/cluster/stats/ExtendedSearchUsageMetric.java index a4ece806ac04d..354dd306b08dd 100644 --- a/server/src/main/java/org/elasticsearch/action/admin/cluster/stats/ExtendedSearchUsageMetric.java +++ b/server/src/main/java/org/elasticsearch/action/admin/cluster/stats/ExtendedSearchUsageMetric.java @@ -22,5 +22,5 @@ public interface ExtendedSearchUsageMetric other); } diff --git a/server/src/main/java/org/elasticsearch/action/admin/cluster/stats/ExtendedSearchUsageStats.java b/server/src/main/java/org/elasticsearch/action/admin/cluster/stats/ExtendedSearchUsageStats.java index 2152536f133a1..d39b46bea3049 100644 --- a/server/src/main/java/org/elasticsearch/action/admin/cluster/stats/ExtendedSearchUsageStats.java +++ b/server/src/main/java/org/elasticsearch/action/admin/cluster/stats/ExtendedSearchUsageStats.java @@ -36,6 +36,8 @@ public class ExtendedSearchUsageStats implements Writeable, ToXContent { */ private final Map>> categorizedExtendedData; + public static final ExtendedSearchUsageStats EMPTY = new ExtendedSearchUsageStats(); + public ExtendedSearchUsageStats() { this.categorizedExtendedData = new HashMap<>(); } @@ -64,14 +66,13 @@ public void writeTo(StreamOutput out) throws IOException { ); } - @SuppressWarnings({ "unchecked", "rawtypes" }) public void merge(ExtendedSearchUsageStats other) { other.categorizedExtendedData.forEach((key, otherMap) -> { categorizedExtendedData.merge(key, otherMap, (existingMap, newMap) -> { Map> mergedMap = new HashMap<>(existingMap); - newMap.forEach((innerKey, innerValue) -> { - mergedMap.merge(innerKey, innerValue, (existing, incoming) -> ((ExtendedSearchUsageMetric) existing).merge(incoming)); - }); + newMap.forEach( + (innerKey, innerValue) -> { mergedMap.merge(innerKey, innerValue, (existing, incoming) -> (existing).merge(incoming)); } + ); return mergedMap; }); }); diff --git a/server/src/main/java/org/elasticsearch/action/admin/cluster/stats/SearchUsageStats.java b/server/src/main/java/org/elasticsearch/action/admin/cluster/stats/SearchUsageStats.java index 797d2714aead9..081a21e3742ea 100644 --- a/server/src/main/java/org/elasticsearch/action/admin/cluster/stats/SearchUsageStats.java +++ b/server/src/main/java/org/elasticsearch/action/admin/cluster/stats/SearchUsageStats.java @@ -51,7 +51,7 @@ public SearchUsageStats() { this.sections = new HashMap<>(); this.rescorers = new HashMap<>(); this.retrievers = new HashMap<>(); - this.extendedSearchUsageStats = new ExtendedSearchUsageStats(); + this.extendedSearchUsageStats = ExtendedSearchUsageStats.EMPTY; } /** @@ -82,7 +82,7 @@ public SearchUsageStats(StreamInput in) throws IOException { this.retrievers = in.getTransportVersion().onOrAfter(V_8_16_0) ? in.readMap(StreamInput::readLong) : Map.of(); this.extendedSearchUsageStats = in.getTransportVersion().supports(EXTENDED_SEARCH_USAGE_TELEMETRY) ? new ExtendedSearchUsageStats(in) - : new ExtendedSearchUsageStats(); + : ExtendedSearchUsageStats.EMPTY; } @Override diff --git a/server/src/main/java/org/elasticsearch/search/builder/SearchSourceBuilder.java b/server/src/main/java/org/elasticsearch/search/builder/SearchSourceBuilder.java index f000848e60135..a4f56d5f4a6dc 100644 --- a/server/src/main/java/org/elasticsearch/search/builder/SearchSourceBuilder.java +++ b/server/src/main/java/org/elasticsearch/search/builder/SearchSourceBuilder.java @@ -129,7 +129,6 @@ public final class SearchSourceBuilder implements Writeable, ToXContentObject, R public static final ParseField POINT_IN_TIME = new ParseField("pit"); public static final ParseField RUNTIME_MAPPINGS_FIELD = new ParseField("runtime_mappings"); public static final ParseField RETRIEVER = new ParseField("retriever"); - public static final ParseField EXTENDED = new ParseField("extended"); private static final boolean RANK_SUPPORTED = Booleans.parseBoolean(System.getProperty("es.search.rank_supported"), true); diff --git a/server/src/test/java/org/elasticsearch/action/admin/cluster/stats/ExtendedSearchUsageStatsTests.java b/server/src/test/java/org/elasticsearch/action/admin/cluster/stats/ExtendedSearchUsageStatsTests.java index 8f45eea71a44a..24d3ffdf411c2 100644 --- a/server/src/test/java/org/elasticsearch/action/admin/cluster/stats/ExtendedSearchUsageStatsTests.java +++ b/server/src/test/java/org/elasticsearch/action/admin/cluster/stats/ExtendedSearchUsageStatsTests.java @@ -18,6 +18,8 @@ import java.util.List; import java.util.Map; +import static org.elasticsearch.action.admin.cluster.stats.ExtendedSearchUsageStats.EMPTY; + public class ExtendedSearchUsageStatsTests extends AbstractWireSerializingTestCase { @Override @@ -44,7 +46,7 @@ public static ExtendedSearchUsageStats randomExtendedSearchUsage() { public static ExtendedSearchUsageStats randomExtendedSearchUsage(boolean empty) { if (empty) { - return new ExtendedSearchUsageStats(); + return EMPTY; } Map>> categoriesToExtendedData = new HashMap<>(); diff --git a/server/src/test/java/org/elasticsearch/action/admin/cluster/stats/SearchUsageStatsTests.java b/server/src/test/java/org/elasticsearch/action/admin/cluster/stats/SearchUsageStatsTests.java index a0e92cfbc103a..5eec89d6e4402 100644 --- a/server/src/test/java/org/elasticsearch/action/admin/cluster/stats/SearchUsageStatsTests.java +++ b/server/src/test/java/org/elasticsearch/action/admin/cluster/stats/SearchUsageStatsTests.java @@ -22,6 +22,7 @@ import java.util.List; import java.util.Map; +import static org.elasticsearch.action.admin.cluster.stats.ExtendedSearchUsageStats.EMPTY; import static org.elasticsearch.action.admin.cluster.stats.ExtendedSearchUsageStatsTests.randomExtendedSearchUsage; import static org.elasticsearch.action.admin.cluster.stats.SearchUsageStats.EXTENDED_SEARCH_USAGE_TELEMETRY; @@ -173,7 +174,7 @@ public void testAdd() { assertEquals(Map.of(), searchUsageStats.getRescorerUsage()); assertEquals(Map.of(), searchUsageStats.getSectionsUsage()); assertEquals(0, searchUsageStats.getTotalSearchCount()); - assertEquals(new ExtendedSearchUsageStats(), searchUsageStats.getExtendedSearchUsage()); + assertEquals(EMPTY, searchUsageStats.getExtendedSearchUsage()); ExtendedSearchUsageStats extendedSearchUsageStats = new ExtendedSearchUsageStats( Map.of("retrievers", Map.of("text_similarity_reranker", new ExtendedSearchUsageLongCounter(Map.of("chunk_rescorer", 10L)))) @@ -225,7 +226,7 @@ public void testToXContent() throws IOException { Map.of("query", 2L), Map.of("query", 10L), Map.of("knn", 10L), - new ExtendedSearchUsageStats(), + EMPTY, 10L ); assertEquals( @@ -245,7 +246,7 @@ public void testSerializationBWC() throws IOException { version.onOrAfter(TransportVersions.V_8_12_0) ? randomRescorerUsage(RESCORER_TYPES.size()) : Map.of(), randomSectionsUsage(SECTIONS.size()), version.onOrAfter(TransportVersions.V_8_16_0) ? randomRetrieversUsage(RETRIEVERS.size()) : Map.of(), - version.supports(EXTENDED_SEARCH_USAGE_TELEMETRY) ? randomExtendedSearchUsage() : new ExtendedSearchUsageStats(), + version.supports(EXTENDED_SEARCH_USAGE_TELEMETRY) ? randomExtendedSearchUsage() : EMPTY, randomLongBetween(0, Long.MAX_VALUE) ); assertSerialization(testInstance, version); From 49e1ef8376d7854236b887e0fde0a99b635ed7c2 Mon Sep 17 00:00:00 2001 From: Kathleen DeRusso Date: Fri, 26 Sep 2025 13:40:40 -0400 Subject: [PATCH 26/26] Regenerate transport version --- .../definitions/referable/extended_search_usage_telemetry.csv | 2 +- server/src/main/resources/transport/upper_bounds/9.2.csv | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/server/src/main/resources/transport/definitions/referable/extended_search_usage_telemetry.csv b/server/src/main/resources/transport/definitions/referable/extended_search_usage_telemetry.csv index 35154103cd0da..4b012195cac35 100644 --- a/server/src/main/resources/transport/definitions/referable/extended_search_usage_telemetry.csv +++ b/server/src/main/resources/transport/definitions/referable/extended_search_usage_telemetry.csv @@ -1 +1 @@ -9174000 +9177000 diff --git a/server/src/main/resources/transport/upper_bounds/9.2.csv b/server/src/main/resources/transport/upper_bounds/9.2.csv index 78180d915cd67..980b8e87c6329 100644 --- a/server/src/main/resources/transport/upper_bounds/9.2.csv +++ b/server/src/main/resources/transport/upper_bounds/9.2.csv @@ -1 +1 @@ -roles_security_stats,9176000 +extended_search_usage_telemetry,9177000