Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions docs/changelog/114620.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
pr: 114620
summary: "ES|QL: add metrics for functions"
area: ES|QL
type: enhancement
issues: []
3 changes: 2 additions & 1 deletion docs/reference/rest-api/usage.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,10 @@ include::{es-ref-dir}/rest-api/common-parms.asciidoc[tag=master-timeout]
------------------------------------------------------------
GET /_xpack/usage
------------------------------------------------------------
// TEST[s/usage/usage?filter_path=-watcher.execution.actions.index*\,-watcher.execution.actions.logging*,-watcher.execution.actions.email*/]
// TEST[s/usage/usage?filter_path=-watcher.execution.actions.index*\,-watcher.execution.actions.logging*,-watcher.execution.actions.email*,-esql.functions*/]
// This response filter removes watcher logging results if they are included
// to avoid errors in the CI builds.
// Same for ES|QL functions, that is a long list and quickly evolving.

[source,console-result]
------------------------------------------------------------
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
import org.elasticsearch.xpack.esql.core.type.EsField;
import org.elasticsearch.xpack.esql.core.util.DateUtils;
import org.elasticsearch.xpack.esql.core.util.StringUtils;
import org.elasticsearch.xpack.esql.expression.function.EsqlFunctionRegistry;
import org.elasticsearch.xpack.esql.expression.function.scalar.string.RLike;
import org.elasticsearch.xpack.esql.expression.function.scalar.string.WildcardLike;
import org.elasticsearch.xpack.esql.expression.predicate.operator.comparison.Equals;
Expand Down Expand Up @@ -260,7 +261,7 @@ public boolean isIndexed(String field) {

public static final Configuration TEST_CFG = configuration(new QueryPragmas(Settings.EMPTY));

public static final Verifier TEST_VERIFIER = new Verifier(new Metrics());
public static final Verifier TEST_VERIFIER = new Verifier(new Metrics(new EsqlFunctionRegistry()));

private EsqlTestUtils() {}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -390,7 +390,12 @@ public enum Cap {
/**
* Fix for https://github.com/elastic/elasticsearch/issues/114714
*/
FIX_STATS_BY_FOLDABLE_EXPRESSION;
FIX_STATS_BY_FOLDABLE_EXPRESSION,

/**
* Adding stats for functions (stack telemetry)
*/
FUNCTION_STATS;

private final boolean snapshotOnly;
private final FeatureFlag featureFlag;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@
import java.util.ArrayList;
import java.util.BitSet;
import java.util.Collection;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Locale;
Expand Down Expand Up @@ -480,6 +481,9 @@ private void gatherMetrics(LogicalPlan plan, BitSet b) {
for (int i = b.nextSetBit(0); i >= 0; i = b.nextSetBit(i + 1)) {
metrics.inc(FeatureMetric.values()[i]);
}
Set<Class<?>> functions = new HashSet<>();
plan.forEachExpressionDown(Function.class, p -> functions.add(p.getClass()));
functions.forEach(f -> metrics.incFunctionMetric(f));
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ public PlanExecutor(IndexResolver indexResolver, MeterRegistry meterRegistry) {
this.preAnalyzer = new PreAnalyzer();
this.functionRegistry = new EsqlFunctionRegistry();
this.mapper = new Mapper(functionRegistry);
this.metrics = new Metrics();
this.metrics = new Metrics(functionRegistry);
this.verifier = new Verifier(metrics);
this.planningMetricsManager = new PlanningMetricsManager(meterRegistry);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,11 @@
import org.elasticsearch.common.metrics.CounterMetric;
import org.elasticsearch.common.util.Maps;
import org.elasticsearch.xpack.core.watcher.common.stats.Counters;
import org.elasticsearch.xpack.esql.expression.function.EsqlFunctionRegistry;
import org.elasticsearch.xpack.esql.expression.function.FunctionDefinition;

import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Locale;
import java.util.Map;
Expand All @@ -36,10 +39,17 @@ public String toString() {
private final Map<QueryMetric, Map<OperationType, CounterMetric>> opsByTypeMetrics;
// map that holds one counter per esql query "feature" (eval, sort, limit, where....)
private final Map<FeatureMetric, CounterMetric> featuresMetrics;
private final Map<String, CounterMetric> functionMetrics;
protected static String QPREFIX = "queries.";
protected static String FPREFIX = "features.";
protected static String FUNC_PREFIX = "functions.";

public Metrics() {
private final EsqlFunctionRegistry functionRegistry;
private final Map<Class<?>, String> classToFunctionName;

public Metrics(EsqlFunctionRegistry functionRegistry) {
this.functionRegistry = functionRegistry.snapshotRegistry();
this.classToFunctionName = initClassToFunctionType();
Map<QueryMetric, Map<OperationType, CounterMetric>> qMap = new LinkedHashMap<>();
for (QueryMetric metric : QueryMetric.values()) {
Map<OperationType, CounterMetric> metricsMap = Maps.newLinkedHashMapWithExpectedSize(OperationType.values().length);
Expand All @@ -56,6 +66,26 @@ public Metrics() {
fMap.put(featureMetric, new CounterMetric());
}
featuresMetrics = Collections.unmodifiableMap(fMap);

functionMetrics = initFunctionMetrics();
}

private Map<String, CounterMetric> initFunctionMetrics() {
Map<String, CounterMetric> result = new LinkedHashMap<>();
for (var entry : classToFunctionName.entrySet()) {
result.put(entry.getValue(), new CounterMetric());
}
return Collections.unmodifiableMap(result);
}

private Map<Class<?>, String> initClassToFunctionType() {
Map<Class<?>, String> tmp = new HashMap<>();
for (FunctionDefinition func : functionRegistry.listFunctions()) {
if (tmp.containsKey(func.clazz()) == false) {
tmp.put(func.clazz(), func.name());
}
}
return Collections.unmodifiableMap(tmp);
}

/**
Expand All @@ -81,6 +111,13 @@ public void inc(FeatureMetric metric) {
this.featuresMetrics.get(metric).inc();
}

public void incFunctionMetric(Class<?> functionType) {
String functionName = classToFunctionName.get(functionType);
if (functionName != null) {
functionMetrics.get(functionName).inc();
}
}

public Counters stats() {
Counters counters = new Counters();

Expand All @@ -102,6 +139,11 @@ public Counters stats() {
counters.inc(FPREFIX + entry.getKey().toString(), entry.getValue().count());
}

// function metrics
for (Entry<String, CounterMetric> entry : functionMetrics.entrySet()) {
counters.inc(FUNC_PREFIX + entry.getKey(), entry.getValue().count());
}

return counters;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ private Analyzer makeAnalyzer(String mappingFileName, EnrichResolution enrichRes

return new Analyzer(
new AnalyzerContext(config, new EsqlFunctionRegistry(), getIndexResult, enrichResolution),
new Verifier(new Metrics())
new Verifier(new Metrics(new EsqlFunctionRegistry()))
);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ private static Analyzer makeAnalyzer(String mappingFileName) {

return new Analyzer(
new AnalyzerContext(EsqlTestUtils.TEST_CFG, new EsqlFunctionRegistry(), getIndexResult, new EnrichResolution()),
new Verifier(new Metrics())
new Verifier(new Metrics(new EsqlFunctionRegistry()))
);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,14 @@
import org.elasticsearch.test.ESTestCase;
import org.elasticsearch.xpack.core.watcher.common.stats.Counters;
import org.elasticsearch.xpack.esql.analysis.Verifier;
import org.elasticsearch.xpack.esql.expression.function.EsqlFunctionRegistry;
import org.elasticsearch.xpack.esql.expression.function.FunctionDefinition;
import org.elasticsearch.xpack.esql.parser.EsqlParser;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import static org.elasticsearch.xpack.esql.EsqlTestUtils.withDefaultLimitWarning;
import static org.elasticsearch.xpack.esql.analysis.AnalyzerTestUtils.analyzer;
Expand All @@ -32,6 +37,7 @@
import static org.elasticsearch.xpack.esql.stats.FeatureMetric.STATS;
import static org.elasticsearch.xpack.esql.stats.FeatureMetric.WHERE;
import static org.elasticsearch.xpack.esql.stats.Metrics.FPREFIX;
import static org.elasticsearch.xpack.esql.stats.Metrics.FUNC_PREFIX;

public class VerifierMetricsTests extends ESTestCase {

Expand All @@ -54,6 +60,8 @@ public void testDissectQuery() {
assertEquals(0, drop(c));
assertEquals(0, keep(c));
assertEquals(0, rename(c));

assertEquals(1, function("concat", c));
}

public void testEvalQuery() {
Expand All @@ -73,6 +81,8 @@ public void testEvalQuery() {
assertEquals(0, drop(c));
assertEquals(0, keep(c));
assertEquals(0, rename(c));

assertEquals(1, function("length", c));
}

public void testGrokQuery() {
Expand All @@ -92,6 +102,8 @@ public void testGrokQuery() {
assertEquals(0, drop(c));
assertEquals(0, keep(c));
assertEquals(0, rename(c));

assertEquals(1, function("concat", c));
}

public void testLimitQuery() {
Expand Down Expand Up @@ -149,6 +161,8 @@ public void testStatsQuery() {
assertEquals(0, drop(c));
assertEquals(0, keep(c));
assertEquals(0, rename(c));

assertEquals(1, function("max", c));
}

public void testWhereQuery() {
Expand Down Expand Up @@ -190,7 +204,7 @@ public void testTwoWhereQuery() {
}

public void testTwoQueriesExecuted() {
Metrics metrics = new Metrics();
Metrics metrics = new Metrics(new EsqlFunctionRegistry());
Verifier verifier = new Verifier(metrics);
esqlWithVerifier("""
from employees
Expand Down Expand Up @@ -226,6 +240,64 @@ public void testTwoQueriesExecuted() {
assertEquals(0, drop(c));
assertEquals(0, keep(c));
assertEquals(0, rename(c));

assertEquals(1, function("length", c));
assertEquals(1, function("concat", c));
assertEquals(1, function("max", c));
assertEquals(1, function("min", c));

assertEquals(0, function("sin", c));
assertEquals(0, function("cos", c));
}

public void testMultipleFunctions() {
Metrics metrics = new Metrics(new EsqlFunctionRegistry());
Verifier verifier = new Verifier(metrics);
esqlWithVerifier("""
from employees
| where languages > 2
| limit 5
| eval name_len = length(first_name), surname_len = length(last_name)
| sort length(first_name)
| limit 3
""", verifier);

Counters c = metrics.stats();
assertEquals(1, function("length", c));
assertEquals(0, function("concat", c));

esqlWithVerifier("""
from employees
| where languages > 2
| sort first_name desc nulls first
| dissect concat(first_name, " ", last_name) "%{a} %{b}"
| grok concat(first_name, " ", last_name) "%{WORD:a} %{WORD:b}"
| eval name_len = length(first_name), surname_len = length(last_name)
| stats x = max(languages)
| sort x
| stats y = min(x) by x
""", verifier);
c = metrics.stats();

assertEquals(2, function("length", c));
assertEquals(1, function("concat", c));
assertEquals(1, function("max", c));
assertEquals(1, function("min", c));

EsqlFunctionRegistry fr = new EsqlFunctionRegistry().snapshotRegistry();
Map<Class<?>, String> functions = new HashMap<>();
for (FunctionDefinition func : fr.listFunctions()) {
if (functions.containsKey(func.clazz()) == false) {
functions.put(func.clazz(), func.name());
}
}
for (String value : functions.values()) {
if (Set.of("length", "concat", "max", "min").contains(value) == false) {
assertEquals(0, function(value, c));
}
}
Map<?, ?> map = (Map<?, ?>) c.toNestedMap().get("functions");
assertEquals(functions.size(), map.size());
}

public void testEnrich() {
Expand All @@ -251,6 +323,8 @@ public void testEnrich() {
assertEquals(0, drop(c));
assertEquals(1L, keep(c));
assertEquals(0, rename(c));

assertEquals(1, function("to_string", c));
}

public void testMvExpand() {
Expand Down Expand Up @@ -298,6 +372,8 @@ public void testShowInfo() {
assertEquals(0, drop(c));
assertEquals(0, keep(c));
assertEquals(0, rename(c));

assertEquals(1, function("count", c));
}

public void testRow() {
Expand Down Expand Up @@ -336,6 +412,8 @@ public void testDropAndRename() {
assertEquals(1L, drop(c));
assertEquals(0, keep(c));
assertEquals(1L, rename(c));

assertEquals(1, function("count", c));
}

public void testKeep() {
Expand Down Expand Up @@ -422,6 +500,19 @@ private long rename(Counters c) {
return c.get(FPREFIX + RENAME);
}

private long function(String function, Counters c) {
return c.get(FUNC_PREFIX + function);
}

private void assertNullFunction(String function, Counters c) {
try {
c.get(FUNC_PREFIX + function);
fail();
} catch (NullPointerException npe) {

}
}

private Counters esql(String esql) {
return esql(esql, null);
}
Expand All @@ -434,7 +525,7 @@ private Counters esql(String esql, Verifier v) {
Verifier verifier = v;
Metrics metrics = null;
if (v == null) {
metrics = new Metrics();
metrics = new Metrics(new EsqlFunctionRegistry());
verifier = new Verifier(metrics);
}
analyzer(verifier).analyze(parser.createStatement(esql));
Expand Down
Loading
Loading