From 3851abdcfa1863da83aab064f3a8ab4a1e85d68d Mon Sep 17 00:00:00 2001 From: Peng Huo Date: Mon, 22 Dec 2025 10:25:28 -0800 Subject: [PATCH 1/9] Init Signed-off-by: Peng Huo --- .../sql/common/utils/QueryContext.java | 18 ++++ .../sql/calcite/utils/CalciteToolsHelper.java | 9 +- .../opensearch/sql/executor/QueryService.java | 11 +++ .../monitor/profile/DefaultMetricImpl.java | 45 +++++++++ .../profile/DefaultProfileContext.java | 59 +++++++++++ .../sql/monitor/profile/MetricName.java | 15 +++ .../monitor/profile/NoopProfileContext.java | 41 ++++++++ .../monitor/profile/NoopProfileMetric.java | 34 +++++++ .../sql/monitor/profile/ProfileContext.java | 34 +++++++ .../sql/monitor/profile/ProfileMetric.java | 33 +++++++ .../sql/monitor/profile/QueryProfile.java | 44 +++++++++ .../sql/monitor/profile/QueryProfiling.java | 76 ++++++++++++++ docs/user/ppl/interfaces/endpoint.md | 35 ++++++- .../resources/rest-api-spec/api/ppl.json | 7 +- .../rest-api-spec/test/api/profile.yml | 99 +++++++++++++++++++ .../executor/OpenSearchExecutionEngine.java | 6 ++ .../request/OpenSearchQueryRequest.java | 10 +- .../storage/scan/BackgroundSearchScanner.java | 7 +- .../request/PPLQueryRequestFactory.java | 16 ++- .../transport/TransportPPLQueryAction.java | 1 + .../transport/TransportPPLQueryRequest.java | 11 ++- .../sql/ppl/domain/PPLQueryRequest.java | 13 ++- .../format/SimpleJsonResponseFormatter.java | 19 ++++ 23 files changed, 635 insertions(+), 8 deletions(-) create mode 100644 core/src/main/java/org/opensearch/sql/monitor/profile/DefaultMetricImpl.java create mode 100644 core/src/main/java/org/opensearch/sql/monitor/profile/DefaultProfileContext.java create mode 100644 core/src/main/java/org/opensearch/sql/monitor/profile/MetricName.java create mode 100644 core/src/main/java/org/opensearch/sql/monitor/profile/NoopProfileContext.java create mode 100644 core/src/main/java/org/opensearch/sql/monitor/profile/NoopProfileMetric.java create mode 100644 core/src/main/java/org/opensearch/sql/monitor/profile/ProfileContext.java create mode 100644 core/src/main/java/org/opensearch/sql/monitor/profile/ProfileMetric.java create mode 100644 core/src/main/java/org/opensearch/sql/monitor/profile/QueryProfile.java create mode 100644 core/src/main/java/org/opensearch/sql/monitor/profile/QueryProfiling.java create mode 100644 integ-test/src/yamlRestTest/resources/rest-api-spec/test/api/profile.yml diff --git a/common/src/main/java/org/opensearch/sql/common/utils/QueryContext.java b/common/src/main/java/org/opensearch/sql/common/utils/QueryContext.java index 686263238aa..4d4301df473 100644 --- a/common/src/main/java/org/opensearch/sql/common/utils/QueryContext.java +++ b/common/src/main/java/org/opensearch/sql/common/utils/QueryContext.java @@ -20,6 +20,8 @@ public class QueryContext { /** The key of the request id in the context map. */ private static final String REQUEST_ID_KEY = "request_id"; + private static final String PROFILE_KEY = "profile"; + /** * Generates a random UUID and adds to the {@link ThreadContext} as the request id. * @@ -66,4 +68,20 @@ private QueryContext() { throw new AssertionError( getClass().getCanonicalName() + " is a utility class and must not be initialized"); } + + /** + * Store the profile flag in thread context. + * + * @param profileEnabled whether profiling is enabled + */ + public static void setProfile(boolean profileEnabled) { + ThreadContext.put(PROFILE_KEY, Boolean.toString(profileEnabled)); + } + + /** + * @return true if profiling flag is set in the thread context. + */ + public static boolean isProfileEnabled() { + return Boolean.parseBoolean(ThreadContext.get(PROFILE_KEY)); + } } diff --git a/core/src/main/java/org/opensearch/sql/calcite/utils/CalciteToolsHelper.java b/core/src/main/java/org/opensearch/sql/calcite/utils/CalciteToolsHelper.java index a5cdf0f45f0..a1a70f9917e 100644 --- a/core/src/main/java/org/opensearch/sql/calcite/utils/CalciteToolsHelper.java +++ b/core/src/main/java/org/opensearch/sql/calcite/utils/CalciteToolsHelper.java @@ -28,6 +28,7 @@ package org.opensearch.sql.calcite.utils; import static java.util.Objects.requireNonNull; +import static org.opensearch.sql.monitor.profile.MetricName.OPTIMIZE_TIME; import com.google.common.collect.ImmutableList; import java.lang.reflect.Type; @@ -91,6 +92,8 @@ import org.opensearch.sql.calcite.plan.OpenSearchRules; import org.opensearch.sql.calcite.plan.Scannable; import org.opensearch.sql.expression.function.PPLBuiltinOperators; +import org.opensearch.sql.monitor.profile.ProfileMetric; +import org.opensearch.sql.monitor.profile.QueryProfiling; /** * Calcite Tools Helper. This class is used to create customized: 1. Connection 2. JavaTypeFactory @@ -340,6 +343,8 @@ public static class OpenSearchRelRunners { * org.apache.calcite.tools.RelRunners#run(RelNode)} */ public static PreparedStatement run(CalcitePlanContext context, RelNode rel) { + ProfileMetric optimizeTime = QueryProfiling.current().getOrCreateMetric(OPTIMIZE_TIME); + long startTime = System.nanoTime(); final RelShuttle shuttle = new RelHomogeneousShuttle() { @Override @@ -358,7 +363,9 @@ public RelNode visit(TableScan scan) { // the line we changed here try (Connection connection = context.connection) { final RelRunner runner = connection.unwrap(RelRunner.class); - return runner.prepareStatement(rel); + PreparedStatement preparedStatement = runner.prepareStatement(rel); + optimizeTime.set(System.nanoTime() - startTime); + return preparedStatement; } catch (SQLException e) { // Detect if error is due to window functions in unsupported context (bins on time fields) String errorMsg = e.getMessage(); diff --git a/core/src/main/java/org/opensearch/sql/executor/QueryService.java b/core/src/main/java/org/opensearch/sql/executor/QueryService.java index c85849df725..f46f58b14cb 100644 --- a/core/src/main/java/org/opensearch/sql/executor/QueryService.java +++ b/core/src/main/java/org/opensearch/sql/executor/QueryService.java @@ -40,9 +40,14 @@ import org.opensearch.sql.calcite.plan.LogicalSystemLimit.SystemLimitType; import org.opensearch.sql.common.response.ResponseListener; import org.opensearch.sql.common.setting.Settings; +import org.opensearch.sql.common.utils.QueryContext; import org.opensearch.sql.datasource.DataSourceService; import org.opensearch.sql.exception.CalciteUnsupportedException; import org.opensearch.sql.exception.NonFallbackCalciteException; +import org.opensearch.sql.monitor.profile.MetricName; +import org.opensearch.sql.monitor.profile.ProfileContext; +import org.opensearch.sql.monitor.profile.ProfileMetric; +import org.opensearch.sql.monitor.profile.QueryProfiling; import org.opensearch.sql.planner.PlanContext; import org.opensearch.sql.planner.Planner; import org.opensearch.sql.planner.logical.LogicalPaginate; @@ -98,6 +103,10 @@ public void executeWithCalcite( CalcitePlanContext.run( () -> { try { + ProfileContext profileContext = + QueryProfiling.activate(QueryContext.isProfileEnabled()); + ProfileMetric metric = profileContext.getOrCreateMetric(MetricName.ANALYZE_TIME); + long analyzeStart = System.nanoTime(); CalcitePlanContext context = CalcitePlanContext.create( buildFrameworkConfig(), SysLimit.fromSettings(settings), queryType); @@ -105,6 +114,7 @@ public void executeWithCalcite( relNode = mergeAdjacentFilters(relNode); RelNode optimized = optimize(relNode, context); RelNode calcitePlan = convertToCalcitePlan(optimized); + metric.set(System.nanoTime() - analyzeStart); executionEngine.execute(calcitePlan, context, listener); } catch (Throwable t) { if (isCalciteFallbackAllowed(t) && !(t instanceof NonFallbackCalciteException)) { @@ -137,6 +147,7 @@ public void explainWithCalcite( CalcitePlanContext.run( () -> { try { + QueryProfiling.noop(); CalcitePlanContext context = CalcitePlanContext.create( buildFrameworkConfig(), SysLimit.fromSettings(settings), queryType); diff --git a/core/src/main/java/org/opensearch/sql/monitor/profile/DefaultMetricImpl.java b/core/src/main/java/org/opensearch/sql/monitor/profile/DefaultMetricImpl.java new file mode 100644 index 00000000000..511bfaac3a8 --- /dev/null +++ b/core/src/main/java/org/opensearch/sql/monitor/profile/DefaultMetricImpl.java @@ -0,0 +1,45 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.sql.monitor.profile; + +import java.util.concurrent.atomic.LongAdder; + +/** Concrete metric backed by {@link LongAdder}. */ +final class DefaultMetricImpl implements ProfileMetric { + + private final String name; + private final LongAdder value = new LongAdder(); + + /** + * Construct a metric with the provided name. + * + * @param name metric name + */ + DefaultMetricImpl(String name) { + this.name = name; + } + + @Override + public String name() { + return name; + } + + @Override + public long value() { + return value.sum(); + } + + @Override + public void add(long delta) { + value.add(delta); + } + + @Override + public void set(long value) { + this.value.reset(); + this.value.add(value); + } +} diff --git a/core/src/main/java/org/opensearch/sql/monitor/profile/DefaultProfileContext.java b/core/src/main/java/org/opensearch/sql/monitor/profile/DefaultProfileContext.java new file mode 100644 index 00000000000..597e7224d1a --- /dev/null +++ b/core/src/main/java/org/opensearch/sql/monitor/profile/DefaultProfileContext.java @@ -0,0 +1,59 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.sql.monitor.profile; + +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.ConcurrentHashMap; + +/** Default implementation that records profiling metrics. */ +public class DefaultProfileContext implements ProfileContext { + + private final long startNanos = System.nanoTime(); + private long endNanos; + private boolean finished; + private final Map metrics = new ConcurrentHashMap<>(); + private QueryProfile profile; + + public DefaultProfileContext() {} + + /** {@inheritDoc} */ + @Override + public boolean isEnabled() { + return true; + } + + /** {@inheritDoc} */ + @Override + public ProfileMetric getOrCreateMetric(MetricName name) { + Objects.requireNonNull(name, "name"); + return metrics.computeIfAbsent(name, key -> new DefaultMetricImpl(key.name())); + } + + /** {@inheritDoc} */ + @Override + public synchronized QueryProfile finish() { + if (finished) { + return profile; + } + finished = true; + endNanos = System.nanoTime(); + Map snapshot = new LinkedHashMap<>(MetricName.values().length); + for (MetricName metricName : MetricName.values()) { + DefaultMetricImpl metric = metrics.get(metricName); + double millis = metric == null ? 0d : roundToMillis(metric.value()); + snapshot.put(metricName, millis); + } + double totalMillis = roundToMillis(endNanos - startNanos); + profile = new QueryProfile(totalMillis, snapshot); + return profile; + } + + private double roundToMillis(long nanos) { + return Math.round((nanos / 1_000_000.0d) * 100.0d) / 100.0d; + } +} diff --git a/core/src/main/java/org/opensearch/sql/monitor/profile/MetricName.java b/core/src/main/java/org/opensearch/sql/monitor/profile/MetricName.java new file mode 100644 index 00000000000..005c17bdd00 --- /dev/null +++ b/core/src/main/java/org/opensearch/sql/monitor/profile/MetricName.java @@ -0,0 +1,15 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.sql.monitor.profile; + +/** Named metrics used by query profiling. */ +public enum MetricName { + ANALYZE_TIME, + OPTIMIZE_TIME, + OPENSEARCH_TIME, + POST_EXEC_TIME, + FORMAT_TIME +} diff --git a/core/src/main/java/org/opensearch/sql/monitor/profile/NoopProfileContext.java b/core/src/main/java/org/opensearch/sql/monitor/profile/NoopProfileContext.java new file mode 100644 index 00000000000..07b2832772b --- /dev/null +++ b/core/src/main/java/org/opensearch/sql/monitor/profile/NoopProfileContext.java @@ -0,0 +1,41 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.sql.monitor.profile; + +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Objects; + +/** Disabled profiling context. */ +public final class NoopProfileContext implements ProfileContext { + + public static final NoopProfileContext INSTANCE = new NoopProfileContext(); + + private NoopProfileContext() {} + + /** {@inheritDoc} */ + @Override + public boolean isEnabled() { + return false; + } + + /** {@inheritDoc} */ + @Override + public ProfileMetric getOrCreateMetric(MetricName name) { + Objects.requireNonNull(name, "name"); + return NoopProfileMetric.INSTANCE; + } + + /** {@inheritDoc} */ + @Override + public QueryProfile finish() { + Map metrics = new LinkedHashMap<>(); + for (MetricName metricName : MetricName.values()) { + metrics.put(metricName, 0d); + } + return new QueryProfile(0d, metrics); + } +} diff --git a/core/src/main/java/org/opensearch/sql/monitor/profile/NoopProfileMetric.java b/core/src/main/java/org/opensearch/sql/monitor/profile/NoopProfileMetric.java new file mode 100644 index 00000000000..c75e2f8f2ad --- /dev/null +++ b/core/src/main/java/org/opensearch/sql/monitor/profile/NoopProfileMetric.java @@ -0,0 +1,34 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.sql.monitor.profile; + +/** No-op metric implementation. */ +final class NoopProfileMetric implements ProfileMetric { + + static final NoopProfileMetric INSTANCE = new NoopProfileMetric(); + + private NoopProfileMetric() {} + + /** {@inheritDoc} */ + @Override + public String name() { + return ""; + } + + /** {@inheritDoc} */ + @Override + public long value() { + return 0; + } + + /** {@inheritDoc} */ + @Override + public void add(long delta) {} + + /** {@inheritDoc} */ + @Override + public void set(long value) {} +} diff --git a/core/src/main/java/org/opensearch/sql/monitor/profile/ProfileContext.java b/core/src/main/java/org/opensearch/sql/monitor/profile/ProfileContext.java new file mode 100644 index 00000000000..b3e900bcab1 --- /dev/null +++ b/core/src/main/java/org/opensearch/sql/monitor/profile/ProfileContext.java @@ -0,0 +1,34 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.sql.monitor.profile; + +/** Context for collecting profiling metrics during query execution. */ +public interface ProfileContext extends AutoCloseable { + /** + * @return true when profiling is enabled. + */ + boolean isEnabled(); + + /** + * Obtain or create a metric with the provided name. + * + * @param name fully qualified metric name + * @return metric instance + */ + ProfileMetric getOrCreateMetric(MetricName name); + + /** + * Finalize profiling and return a snapshot. + * + * @return immutable query profile snapshot + */ + QueryProfile finish(); + + @Override + default void close() { + finish(); + } +} diff --git a/core/src/main/java/org/opensearch/sql/monitor/profile/ProfileMetric.java b/core/src/main/java/org/opensearch/sql/monitor/profile/ProfileMetric.java new file mode 100644 index 00000000000..ba3e85a0953 --- /dev/null +++ b/core/src/main/java/org/opensearch/sql/monitor/profile/ProfileMetric.java @@ -0,0 +1,33 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.sql.monitor.profile; + +/** Metric for query profiling. */ +public interface ProfileMetric { + /** + * @return metric name. + */ + String name(); + + /** + * @return current metric value. + */ + long value(); + + /** + * Increment the metric by the given delta. + * + * @param delta amount to add + */ + void add(long delta); + + /** + * Set the metric to the provided value. + * + * @param value new metric value + */ + void set(long value); +} diff --git a/core/src/main/java/org/opensearch/sql/monitor/profile/QueryProfile.java b/core/src/main/java/org/opensearch/sql/monitor/profile/QueryProfile.java new file mode 100644 index 00000000000..3b644f83fc4 --- /dev/null +++ b/core/src/main/java/org/opensearch/sql/monitor/profile/QueryProfile.java @@ -0,0 +1,44 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.sql.monitor.profile; + +import com.google.gson.annotations.SerializedName; +import java.util.Map; +import java.util.Objects; +import lombok.Getter; + +/** Immutable snapshot of query profiling metrics. */ +@Getter +public final class QueryProfile { + + /** Total elapsed milliseconds for the profiled query (rounded to two decimals). */ + @SerializedName("total_ms") + private final double totalMillis; + + /** Immutable metric values keyed by metric name in milliseconds (rounded to two decimals). */ + private final Map metrics; + + /** + * Create a new query profile snapshot. + * + * @param totalMillis total elapsed milliseconds for the query (rounded to two decimals) + * @param metrics metric values keyed by {@link MetricName} + */ + public QueryProfile(double totalMillis, Map metrics) { + this.totalMillis = totalMillis; + this.metrics = buildMetrics(metrics); + } + + private Map buildMetrics(Map metrics) { + Objects.requireNonNull(metrics, "metrics"); + Map ordered = new java.util.LinkedHashMap<>(metrics.size()); + for (MetricName metricName : MetricName.values()) { + Double value = metrics.getOrDefault(metricName, 0d); + ordered.put(metricName.name() + "_MS", value); + } + return ordered; + } +} diff --git a/core/src/main/java/org/opensearch/sql/monitor/profile/QueryProfiling.java b/core/src/main/java/org/opensearch/sql/monitor/profile/QueryProfiling.java new file mode 100644 index 00000000000..22e493e57b4 --- /dev/null +++ b/core/src/main/java/org/opensearch/sql/monitor/profile/QueryProfiling.java @@ -0,0 +1,76 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.sql.monitor.profile; + +import java.util.Objects; +import java.util.function.Supplier; + +/** + * Thread-local holder for query profiling contexts. + * + *

Callers can enable or disable profiling per-thread via {@link #activate(boolean)} and obtain + * the active context with {@link #current()}. + */ +public final class QueryProfiling { + + private static final ThreadLocal CURRENT = new ThreadLocal<>(); + + private QueryProfiling() {} + + /** + * @return the profiling context bound to this thread, or a no-op context if not activated. + */ + public static ProfileContext current() { + ProfileContext ctx = CURRENT.get(); + return ctx == null ? NoopProfileContext.INSTANCE : ctx; + } + + /** + * Create noop profiling for the current thread. + * + * @return newly activated profiling context + */ + public static ProfileContext noop() { + return activate(false); + } + + /** + * Activate profiling for the current thread. + * + * @param profilingEnabled whether profiling should be enabled + * @return newly activated profiling context + */ + public static ProfileContext activate(boolean profilingEnabled) { + if (profilingEnabled) { + CURRENT.set(new DefaultProfileContext()); + } else { + CURRENT.set(NoopProfileContext.INSTANCE); + } + return CURRENT.get(); + } + + /** + * Run a supplier with the provided profiling context bound to the current thread, restoring the + * previous context afterward. + * + * @param ctx context to activate + * @param action supplier to execute + * @return supplier result + */ + public static T withContext(ProfileContext ctx, Supplier action) { + ProfileContext previous = CURRENT.get(); + CURRENT.set(Objects.requireNonNull(ctx, "ctx")); + try { + return action.get(); + } finally { + if (previous == null) { + CURRENT.remove(); + } else { + CURRENT.set(previous); + } + } + } +} diff --git a/docs/user/ppl/interfaces/endpoint.md b/docs/user/ppl/interfaces/endpoint.md index e1e9cf705bf..2e1b4b21a72 100644 --- a/docs/user/ppl/interfaces/endpoint.md +++ b/docs/user/ppl/interfaces/endpoint.md @@ -151,4 +151,37 @@ calcite: physical: | CalciteEnumerableIndexScan(table=[[OpenSearch, state_country]], PushDownContext=[[PROJECT->[name, country, state, month, year, age], FILTER->>($5, 30), LIMIT->10000], OpenSearchRequestBuilder(sourceBuilder={"from":0,"size":10000,"timeout":"1m","query":{"range":{"age":{"from":30,"to":null,"include_lower":false,"include_upper":true,"boost":1.0}}},"_source":{"includes":["name","country","state","month","year","age"],"excludes":[]}}, requestedTotalSize=10000, pageSize=null, startFrom=0)]) ``` - \ No newline at end of file + +## Profile + +You can enable profiling on the PPL endpoint to capture per-stage timings in milliseconds. Profiling is returned only for regular query execution (not explain) and only when using the default `format=jdbc`. + +### Example + +```bash ppl ignore +curl -sS -H 'Content-Type: application/json' \ + -X POST localhost:9200/_plugins/_ppl \ + -d '{ + "profile": true, + "query" : "source=accounts | fields firstname, lastname" + }' +``` + +Expected output (trimmed): + +```json +{ + "profile": { + "total_ms": 822.79, + "metrics": { + "PARSE_TIME_MS": 0.0, + "ANALYZE_TIME_MS": 557.71, + "OPTIMIZE_TIME_MS": 198.87, + "OPENSEARCH_TIME_MS": 40.54, + "POST_EXEC_TIME_MS": 2.09, + "FORMAT_TIME_MS": 0.0 + } + }, + ... +} +``` diff --git a/integ-test/src/yamlRestTest/resources/rest-api-spec/api/ppl.json b/integ-test/src/yamlRestTest/resources/rest-api-spec/api/ppl.json index 6b6e56307c4..61c5c139224 100644 --- a/integ-test/src/yamlRestTest/resources/rest-api-spec/api/ppl.json +++ b/integ-test/src/yamlRestTest/resources/rest-api-spec/api/ppl.json @@ -13,7 +13,12 @@ } ] }, - "params": {}, + "params": { + "format":{ + "type":"string", + "description":"response format: jdbc, csv, raw, viz" + } + }, "body": { "description": "PPL Query", "required":true diff --git a/integ-test/src/yamlRestTest/resources/rest-api-spec/test/api/profile.yml b/integ-test/src/yamlRestTest/resources/rest-api-spec/test/api/profile.yml new file mode 100644 index 00000000000..47fd65ffcb2 --- /dev/null +++ b/integ-test/src/yamlRestTest/resources/rest-api-spec/test/api/profile.yml @@ -0,0 +1,99 @@ +setup: + - do: + query.settings: + body: + transient: + plugins.calcite.enabled : true + - do: + indices.create: + index: ppl_profile + body: + settings: + number_of_shards: 1 + number_of_replicas: 0 + mappings: + properties: + message: + type: keyword + - do: + bulk: + refresh: true + body: + - '{"index": {"_index": "ppl_profile", "_id": 1}}' + - '{"message": "hello"}' + - '{"index": {"_index": "ppl_profile", "_id": 2}}' + - '{"message": "world"}' + +--- +teardown: + - do: + indices.delete: + index: ppl_profile + ignore_unavailable: true + - do: + query.settings: + body: + transient: + plugins.calcite.enabled : false + +--- +"Profile metrics returned for ppl query": + - skip: + features: + - headers + - allowed_warnings + - do: + headers: + Content-Type: 'application/json' + ppl: + body: + query: 'source=ppl_profile | fields message' + profile: true + - gt: {profile.total_ms: 0.0} + - gt: {profile.metrics.ANALYZE_TIME_MS: 0.0} + +--- +"Profile ignored for explain api": + - skip: + features: + - headers + - allowed_warnings + - do: + headers: + Content-Type: 'application/json' + ppl.explain: + body: + query: 'source=ppl_profile | fields message' + profile: true + - match: {profile: null} + +--- +"Profile ignored for explain query": + - skip: + features: + - headers + - allowed_warnings + - do: + headers: + Content-Type: 'application/json' + ppl: + body: + query: 'explain source=ppl_profile | fields message' + profile: true + - match: {profile: null} + +--- +"Profile ignored for viz format": + - skip: + features: + - headers + - allowed_warnings + - do: + headers: + Content-Type: 'application/json' + ppl: + body: + query: 'source=ppl_profile | stats count()' + profile: true + format: 'viz' + - match: {profile: null} diff --git a/opensearch/src/main/java/org/opensearch/sql/opensearch/executor/OpenSearchExecutionEngine.java b/opensearch/src/main/java/org/opensearch/sql/opensearch/executor/OpenSearchExecutionEngine.java index 71f0c8667ff..2477f95beac 100644 --- a/opensearch/src/main/java/org/opensearch/sql/opensearch/executor/OpenSearchExecutionEngine.java +++ b/opensearch/src/main/java/org/opensearch/sql/opensearch/executor/OpenSearchExecutionEngine.java @@ -51,6 +51,9 @@ import org.opensearch.sql.executor.pagination.PlanSerializer; import org.opensearch.sql.expression.function.BuiltinFunctionName; import org.opensearch.sql.expression.function.PPLFuncImpTable; +import org.opensearch.sql.monitor.profile.MetricName; +import org.opensearch.sql.monitor.profile.ProfileMetric; +import org.opensearch.sql.monitor.profile.QueryProfiling; import org.opensearch.sql.opensearch.client.OpenSearchClient; import org.opensearch.sql.opensearch.executor.protector.ExecutionProtector; import org.opensearch.sql.opensearch.functions.DistinctCountApproxAggFunction; @@ -218,6 +221,8 @@ private void buildResultSet( Integer querySizeLimit, ResponseListener listener) throws SQLException { + ProfileMetric metric = QueryProfiling.current().getOrCreateMetric(MetricName.POST_EXEC_TIME); + long postExecTime = System.nanoTime(); // Get the ResultSet metadata to know about columns ResultSetMetaData metaData = resultSet.getMetaData(); int columnCount = metaData.getColumnCount(); @@ -262,6 +267,7 @@ private void buildResultSet( } Schema schema = new Schema(columns); QueryResponse response = new QueryResponse(schema, values, null); + metric.set(System.nanoTime() - postExecTime); listener.onResponse(response); } diff --git a/opensearch/src/main/java/org/opensearch/sql/opensearch/request/OpenSearchQueryRequest.java b/opensearch/src/main/java/org/opensearch/sql/opensearch/request/OpenSearchQueryRequest.java index 04af888cfdd..7207c06ecbf 100644 --- a/opensearch/src/main/java/org/opensearch/sql/opensearch/request/OpenSearchQueryRequest.java +++ b/opensearch/src/main/java/org/opensearch/sql/opensearch/request/OpenSearchQueryRequest.java @@ -21,7 +21,9 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.jetbrains.annotations.TestOnly; -import org.opensearch.action.search.*; +import org.opensearch.action.search.SearchRequest; +import org.opensearch.action.search.SearchResponse; +import org.opensearch.action.search.SearchScrollRequest; import org.opensearch.common.settings.Settings; import org.opensearch.common.unit.TimeValue; import org.opensearch.common.xcontent.XContentType; @@ -39,6 +41,9 @@ import org.opensearch.search.sort.FieldSortBuilder; import org.opensearch.search.sort.ShardDocSortBuilder; import org.opensearch.search.sort.SortBuilders; +import org.opensearch.sql.monitor.profile.MetricName; +import org.opensearch.sql.monitor.profile.ProfileMetric; +import org.opensearch.sql.monitor.profile.QueryProfiling; import org.opensearch.sql.opensearch.data.value.OpenSearchExprValueFactory; import org.opensearch.sql.opensearch.response.OpenSearchResponse; import org.opensearch.sql.opensearch.storage.OpenSearchIndex; @@ -204,6 +209,8 @@ private OpenSearchResponse search(Function search new OpenSearchResponse( SearchHits.empty(), exprValueFactory, includes, isCountAggRequest()); } else { + ProfileMetric metric = QueryProfiling.current().getOrCreateMetric(MetricName.OPENSEARCH_TIME); + long engineStartTime = System.nanoTime(); // Set afterKey to request, null for first round (afterKey is null in the beginning). if (this.sourceBuilder.aggregations() != null) { this.sourceBuilder.aggregations().getAggregatorFactories().stream() @@ -236,6 +243,7 @@ private OpenSearchResponse search(Function search searchDone = true; } needClean = searchDone; + metric.set(System.nanoTime() - engineStartTime); } return openSearchResponse; } diff --git a/opensearch/src/main/java/org/opensearch/sql/opensearch/storage/scan/BackgroundSearchScanner.java b/opensearch/src/main/java/org/opensearch/sql/opensearch/storage/scan/BackgroundSearchScanner.java index a0b33f3b541..5611d1396db 100644 --- a/opensearch/src/main/java/org/opensearch/sql/opensearch/storage/scan/BackgroundSearchScanner.java +++ b/opensearch/src/main/java/org/opensearch/sql/opensearch/storage/scan/BackgroundSearchScanner.java @@ -17,6 +17,8 @@ import org.opensearch.OpenSearchSecurityException; import org.opensearch.sql.data.model.ExprValue; import org.opensearch.sql.exception.NonFallbackCalciteException; +import org.opensearch.sql.monitor.profile.ProfileContext; +import org.opensearch.sql.monitor.profile.QueryProfiling; import org.opensearch.sql.opensearch.client.OpenSearchClient; import org.opensearch.sql.opensearch.request.OpenSearchRequest; import org.opensearch.sql.opensearch.response.OpenSearchResponse; @@ -102,8 +104,11 @@ public boolean isScanDone() { */ public void startScanning(OpenSearchRequest request) { if (isAsync()) { + ProfileContext ctx = QueryProfiling.current(); nextBatchFuture = - CompletableFuture.supplyAsync(() -> client.search(request), backgroundExecutor); + CompletableFuture.supplyAsync( + () -> QueryProfiling.withContext(ctx, () -> client.search(request)), + backgroundExecutor); } } diff --git a/plugin/src/main/java/org/opensearch/sql/plugin/request/PPLQueryRequestFactory.java b/plugin/src/main/java/org/opensearch/sql/plugin/request/PPLQueryRequestFactory.java index ec09d8b5bde..0908d27b491 100644 --- a/plugin/src/main/java/org/opensearch/sql/plugin/request/PPLQueryRequestFactory.java +++ b/plugin/src/main/java/org/opensearch/sql/plugin/request/PPLQueryRequestFactory.java @@ -23,6 +23,7 @@ public class PPLQueryRequestFactory { private static final String DEFAULT_RESPONSE_FORMAT = "jdbc"; private static final String DEFAULT_EXPLAIN_FORMAT = "standard"; private static final String QUERY_PARAMS_PRETTY = "pretty"; + private static final String QUERY_PARAMS_PROFILE = "profile"; /** * Build {@link PPLQueryRequest} from {@link RestRequest}. @@ -59,12 +60,17 @@ private static PPLQueryRequest parsePPLRequestFromPayload(RestRequest restReques boolean pretty = getPrettyOption(restRequest.params()); try { jsonContent = new JSONObject(content); + boolean profileRequested = jsonContent.optBoolean(QUERY_PARAMS_PROFILE, false); + String queryString = jsonContent.optString(PPL_FIELD_NAME, ""); + boolean enableProfile = + profileRequested && isProfileSupported(restRequest.path(), format, queryString); PPLQueryRequest pplRequest = new PPLQueryRequest( jsonContent.getString(PPL_FIELD_NAME), jsonContent, restRequest.path(), - format.getFormatName()); + format.getFormatName(), + enableProfile); // set sanitize option if csv format if (format.equals(Format.CSV)) { pplRequest.sanitize(getSanitizeOption(restRequest.params())); @@ -115,4 +121,12 @@ private static boolean getPrettyOption(Map requestParams) { } return false; } + + private static boolean isProfileSupported(String path, Format format, String query) { + boolean explainPath = isExplainRequest(path); + boolean explainQuery = query != null && query.trim().toLowerCase().startsWith("explain"); + boolean isJdbcFormat = + format != null && DEFAULT_RESPONSE_FORMAT.equalsIgnoreCase(format.getFormatName()); + return !explainPath && !explainQuery && isJdbcFormat; + } } diff --git a/plugin/src/main/java/org/opensearch/sql/plugin/transport/TransportPPLQueryAction.java b/plugin/src/main/java/org/opensearch/sql/plugin/transport/TransportPPLQueryAction.java index 83a419cca66..e2ad8531a0d 100644 --- a/plugin/src/main/java/org/opensearch/sql/plugin/transport/TransportPPLQueryAction.java +++ b/plugin/src/main/java/org/opensearch/sql/plugin/transport/TransportPPLQueryAction.java @@ -108,6 +108,7 @@ protected void doExecute( TransportPPLQueryRequest transportRequest = TransportPPLQueryRequest.fromActionRequest(request); // in order to use PPL service, we need to convert TransportPPLQueryRequest to PPLQueryRequest PPLQueryRequest transformedRequest = transportRequest.toPPLQueryRequest(); + QueryContext.setProfile(transformedRequest.profile()); if (transformedRequest.isExplainRequest()) { pplService.explain( diff --git a/plugin/src/main/java/org/opensearch/sql/plugin/transport/TransportPPLQueryRequest.java b/plugin/src/main/java/org/opensearch/sql/plugin/transport/TransportPPLQueryRequest.java index 8cdf27ef3c8..65663bbec00 100644 --- a/plugin/src/main/java/org/opensearch/sql/plugin/transport/TransportPPLQueryRequest.java +++ b/plugin/src/main/java/org/opensearch/sql/plugin/transport/TransportPPLQueryRequest.java @@ -45,6 +45,11 @@ public class TransportPPLQueryRequest extends ActionRequest { @Accessors(fluent = true) private JsonResponseFormatter.Style style = JsonResponseFormatter.Style.COMPACT; + @Setter + @Getter + @Accessors(fluent = true) + private boolean profile = false; + /** Constructor of TransportPPLQueryRequest from PPLQueryRequest. */ public TransportPPLQueryRequest(PPLQueryRequest pplQueryRequest) { pplQuery = pplQueryRequest.getRequest(); @@ -53,6 +58,7 @@ public TransportPPLQueryRequest(PPLQueryRequest pplQueryRequest) { format = pplQueryRequest.getFormat(); sanitize = pplQueryRequest.sanitize(); style = pplQueryRequest.style(); + profile = pplQueryRequest.profile(); } /** Constructor of TransportPPLQueryRequest from StreamInput. */ @@ -65,6 +71,7 @@ public TransportPPLQueryRequest(StreamInput in) throws IOException { path = in.readOptionalString(); sanitize = in.readBoolean(); style = in.readEnum(JsonResponseFormatter.Style.class); + profile = in.readBoolean(); } /** Re-create the object from the actionRequest. */ @@ -95,6 +102,7 @@ public void writeTo(StreamOutput out) throws IOException { out.writeOptionalString(path); out.writeBoolean(sanitize); out.writeEnum(style); + out.writeBoolean(profile); } public String getRequest() { @@ -128,7 +136,8 @@ public ActionRequestValidationException validate() { /** Convert to PPLQueryRequest. */ public PPLQueryRequest toPPLQueryRequest() { - PPLQueryRequest pplQueryRequest = new PPLQueryRequest(pplQuery, jsonContent, path, format); + PPLQueryRequest pplQueryRequest = + new PPLQueryRequest(pplQuery, jsonContent, path, format, profile); pplQueryRequest.sanitize(sanitize); pplQueryRequest.style(style); return pplQueryRequest; diff --git a/ppl/src/main/java/org/opensearch/sql/ppl/domain/PPLQueryRequest.java b/ppl/src/main/java/org/opensearch/sql/ppl/domain/PPLQueryRequest.java index ca351fcc0a7..321e1d410c4 100644 --- a/ppl/src/main/java/org/opensearch/sql/ppl/domain/PPLQueryRequest.java +++ b/ppl/src/main/java/org/opensearch/sql/ppl/domain/PPLQueryRequest.java @@ -35,16 +35,27 @@ public class PPLQueryRequest { @Accessors(fluent = true) private JsonResponseFormatter.Style style = JsonResponseFormatter.Style.COMPACT; + @Setter + @Getter + @Accessors(fluent = true) + private boolean profile = false; + public PPLQueryRequest(String pplQuery, JSONObject jsonContent, String path) { this(pplQuery, jsonContent, path, ""); } - /** Constructor of PPLQueryRequest. */ public PPLQueryRequest(String pplQuery, JSONObject jsonContent, String path, String format) { + this(pplQuery, jsonContent, path, format, false); + } + + /** Constructor of PPLQueryRequest. */ + public PPLQueryRequest( + String pplQuery, JSONObject jsonContent, String path, String format, boolean profile) { this.pplQuery = pplQuery; this.jsonContent = jsonContent; this.path = Optional.ofNullable(path).orElse(DEFAULT_PPL_PATH); this.format = format; + this.profile = profile; } public String getRequest() { diff --git a/protocol/src/main/java/org/opensearch/sql/protocol/response/format/SimpleJsonResponseFormatter.java b/protocol/src/main/java/org/opensearch/sql/protocol/response/format/SimpleJsonResponseFormatter.java index c00174dc9fa..9ebc5437fcf 100644 --- a/protocol/src/main/java/org/opensearch/sql/protocol/response/format/SimpleJsonResponseFormatter.java +++ b/protocol/src/main/java/org/opensearch/sql/protocol/response/format/SimpleJsonResponseFormatter.java @@ -10,6 +10,11 @@ import lombok.Getter; import lombok.RequiredArgsConstructor; import lombok.Singular; +import org.opensearch.sql.monitor.profile.MetricName; +import org.opensearch.sql.monitor.profile.ProfileContext; +import org.opensearch.sql.monitor.profile.ProfileMetric; +import org.opensearch.sql.monitor.profile.QueryProfile; +import org.opensearch.sql.monitor.profile.QueryProfiling; import org.opensearch.sql.protocol.response.QueryResult; /** @@ -40,6 +45,9 @@ public SimpleJsonResponseFormatter(Style style) { @Override public Object buildJsonObject(QueryResult response) { + ProfileMetric formatMetric = QueryProfiling.current().getOrCreateMetric(MetricName.FORMAT_TIME); + long formatTime = System.nanoTime(); + JsonResponse.JsonResponseBuilder json = JsonResponse.builder(); json.total(response.size()).size(response.size()); @@ -47,6 +55,15 @@ public Object buildJsonObject(QueryResult response) { response.columnNameTypes().forEach((name, type) -> json.column(new Column(name, type))); json.datarows(fetchDataRows(response)); + formatMetric.set(System.nanoTime() - formatTime); + + ProfileContext profileContext = QueryProfiling.current(); + if (profileContext.isEnabled()) { + QueryProfile finish = profileContext.finish(); + json.profile(finish); + } else { + json.profile(null); + } return json.build(); } @@ -63,6 +80,8 @@ private Object[][] fetchDataRows(QueryResult response) { @Builder @Getter public static class JsonResponse { + private final QueryProfile profile; + @Singular("column") private final List schema; From edbb6e05f445dc39f68c00e719f4072af2def4bc Mon Sep 17 00:00:00 2001 From: Peng Huo Date: Mon, 22 Dec 2025 11:07:46 -0800 Subject: [PATCH 2/9] Cleanup ThreadLocal Signed-off-by: Peng Huo --- .../sql/monitor/profile/QueryProfiling.java | 15 +++++---- .../test/api/{profile.yml => ppl.profile.yml} | 1 + .../storage/scan/BackgroundSearchScanner.java | 2 +- .../transport/TransportPPLQueryAction.java | 31 +++++++++++++++++-- 4 files changed, 37 insertions(+), 12 deletions(-) rename integ-test/src/yamlRestTest/resources/rest-api-spec/test/api/{profile.yml => ppl.profile.yml} (97%) diff --git a/core/src/main/java/org/opensearch/sql/monitor/profile/QueryProfiling.java b/core/src/main/java/org/opensearch/sql/monitor/profile/QueryProfiling.java index 22e493e57b4..d7adaff3bab 100644 --- a/core/src/main/java/org/opensearch/sql/monitor/profile/QueryProfiling.java +++ b/core/src/main/java/org/opensearch/sql/monitor/profile/QueryProfiling.java @@ -52,25 +52,24 @@ public static ProfileContext activate(boolean profilingEnabled) { return CURRENT.get(); } + /** Clear any profiling context bound to the current thread. */ + public static void clear() { + CURRENT.remove(); + } + /** * Run a supplier with the provided profiling context bound to the current thread, restoring the * previous context afterward. * - * @param ctx context to activate * @param action supplier to execute * @return supplier result */ - public static T withContext(ProfileContext ctx, Supplier action) { - ProfileContext previous = CURRENT.get(); + public static T withCurrentContext(ProfileContext ctx, Supplier action) { CURRENT.set(Objects.requireNonNull(ctx, "ctx")); try { return action.get(); } finally { - if (previous == null) { - CURRENT.remove(); - } else { - CURRENT.set(previous); - } + clear(); } } } diff --git a/integ-test/src/yamlRestTest/resources/rest-api-spec/test/api/profile.yml b/integ-test/src/yamlRestTest/resources/rest-api-spec/test/api/ppl.profile.yml similarity index 97% rename from integ-test/src/yamlRestTest/resources/rest-api-spec/test/api/profile.yml rename to integ-test/src/yamlRestTest/resources/rest-api-spec/test/api/ppl.profile.yml index 47fd65ffcb2..29762d3647a 100644 --- a/integ-test/src/yamlRestTest/resources/rest-api-spec/test/api/profile.yml +++ b/integ-test/src/yamlRestTest/resources/rest-api-spec/test/api/ppl.profile.yml @@ -51,6 +51,7 @@ teardown: profile: true - gt: {profile.total_ms: 0.0} - gt: {profile.metrics.ANALYZE_TIME_MS: 0.0} + - gt: {profile.metrics.OPENSEARCH_TIME_MS: 0.0} --- "Profile ignored for explain api": diff --git a/opensearch/src/main/java/org/opensearch/sql/opensearch/storage/scan/BackgroundSearchScanner.java b/opensearch/src/main/java/org/opensearch/sql/opensearch/storage/scan/BackgroundSearchScanner.java index 5611d1396db..65ba189b0d9 100644 --- a/opensearch/src/main/java/org/opensearch/sql/opensearch/storage/scan/BackgroundSearchScanner.java +++ b/opensearch/src/main/java/org/opensearch/sql/opensearch/storage/scan/BackgroundSearchScanner.java @@ -107,7 +107,7 @@ public void startScanning(OpenSearchRequest request) { ProfileContext ctx = QueryProfiling.current(); nextBatchFuture = CompletableFuture.supplyAsync( - () -> QueryProfiling.withContext(ctx, () -> client.search(request)), + () -> QueryProfiling.withCurrentContext(ctx, () -> client.search(request)), backgroundExecutor); } } diff --git a/plugin/src/main/java/org/opensearch/sql/plugin/transport/TransportPPLQueryAction.java b/plugin/src/main/java/org/opensearch/sql/plugin/transport/TransportPPLQueryAction.java index e2ad8531a0d..27bfe2084f7 100644 --- a/plugin/src/main/java/org/opensearch/sql/plugin/transport/TransportPPLQueryAction.java +++ b/plugin/src/main/java/org/opensearch/sql/plugin/transport/TransportPPLQueryAction.java @@ -30,6 +30,7 @@ import org.opensearch.sql.executor.ExecutionEngine; import org.opensearch.sql.legacy.metrics.MetricName; import org.opensearch.sql.legacy.metrics.Metrics; +import org.opensearch.sql.monitor.profile.QueryProfiling; import org.opensearch.sql.opensearch.setting.OpenSearchSettings; import org.opensearch.sql.plugin.config.OpenSearchPluginModule; import org.opensearch.sql.ppl.PPLService; @@ -109,15 +110,16 @@ protected void doExecute( // in order to use PPL service, we need to convert TransportPPLQueryRequest to PPLQueryRequest PPLQueryRequest transformedRequest = transportRequest.toPPLQueryRequest(); QueryContext.setProfile(transformedRequest.profile()); + ActionListener clearingListener = wrapWithProfilingClear(listener); if (transformedRequest.isExplainRequest()) { pplService.explain( - transformedRequest, createExplainResponseListener(transformedRequest, listener)); + transformedRequest, createExplainResponseListener(transformedRequest, clearingListener)); } else { pplService.execute( transformedRequest, - createListener(transformedRequest, listener), - createExplainResponseListener(transformedRequest, listener)); + createListener(transformedRequest, clearingListener), + createExplainResponseListener(transformedRequest, clearingListener)); } } @@ -203,4 +205,27 @@ private Format format(PPLQueryRequest pplRequest) { String.format(Locale.ROOT, "response in %s format is not supported.", format)); } } + + private ActionListener wrapWithProfilingClear( + ActionListener delegate) { + return new ActionListener<>() { + @Override + public void onResponse(TransportPPLQueryResponse transportPPLQueryResponse) { + try { + delegate.onResponse(transportPPLQueryResponse); + } finally { + QueryProfiling.clear(); + } + } + + @Override + public void onFailure(Exception e) { + try { + delegate.onFailure(e); + } finally { + QueryProfiling.clear(); + } + } + }; + } } From 079072a752a4be7ffe55ab5eab628ea92030b658 Mon Sep 17 00:00:00 2001 From: Peng Huo Date: Mon, 22 Dec 2025 11:32:08 -0800 Subject: [PATCH 3/9] Update doc Signed-off-by: Peng Huo --- .../sql/monitor/profile/QueryProfiling.java | 3 +-- docs/user/ppl/interfaces/endpoint.md | 22 +++++++++---------- 2 files changed, 11 insertions(+), 14 deletions(-) diff --git a/core/src/main/java/org/opensearch/sql/monitor/profile/QueryProfiling.java b/core/src/main/java/org/opensearch/sql/monitor/profile/QueryProfiling.java index d7adaff3bab..3ef32dac748 100644 --- a/core/src/main/java/org/opensearch/sql/monitor/profile/QueryProfiling.java +++ b/core/src/main/java/org/opensearch/sql/monitor/profile/QueryProfiling.java @@ -58,8 +58,7 @@ public static void clear() { } /** - * Run a supplier with the provided profiling context bound to the current thread, restoring the - * previous context afterward. + * Run a supplier with the provided profiling context bound to the current thread. * * @param action supplier to execute * @return supplier result diff --git a/docs/user/ppl/interfaces/endpoint.md b/docs/user/ppl/interfaces/endpoint.md index 2e1b4b21a72..0878b06f550 100644 --- a/docs/user/ppl/interfaces/endpoint.md +++ b/docs/user/ppl/interfaces/endpoint.md @@ -171,17 +171,15 @@ Expected output (trimmed): ```json { - "profile": { - "total_ms": 822.79, - "metrics": { - "PARSE_TIME_MS": 0.0, - "ANALYZE_TIME_MS": 557.71, - "OPTIMIZE_TIME_MS": 198.87, - "OPENSEARCH_TIME_MS": 40.54, - "POST_EXEC_TIME_MS": 2.09, - "FORMAT_TIME_MS": 0.0 - } - }, - ... + "profile": { + "total_ms": 25.77, + "metrics": { + "ANALYZE_TIME_MS": 5.77, + "OPTIMIZE_TIME_MS": 13.51, + "OPENSEARCH_TIME_MS": 4.31, + "POST_EXEC_TIME_MS": 0.77, + "FORMAT_TIME_MS": 0.04 + } + } } ``` From 154a70d952a44270ebad075cf8740c1045ccb23e Mon Sep 17 00:00:00 2001 From: Peng Huo Date: Mon, 22 Dec 2025 13:15:31 -0800 Subject: [PATCH 4/9] Update Doc Signed-off-by: Peng Huo --- docs/user/ppl/interfaces/endpoint.md | 5 +++++ .../sql/opensearch/request/OpenSearchQueryRequest.java | 3 +++ 2 files changed, 8 insertions(+) diff --git a/docs/user/ppl/interfaces/endpoint.md b/docs/user/ppl/interfaces/endpoint.md index 0878b06f550..f17574e3948 100644 --- a/docs/user/ppl/interfaces/endpoint.md +++ b/docs/user/ppl/interfaces/endpoint.md @@ -183,3 +183,8 @@ Expected output (trimmed): } } ``` + +### Notes + +- Profile output is only returned when the query finishes successfully. +- Profiling runs only when Calcite is enabled. diff --git a/opensearch/src/main/java/org/opensearch/sql/opensearch/request/OpenSearchQueryRequest.java b/opensearch/src/main/java/org/opensearch/sql/opensearch/request/OpenSearchQueryRequest.java index 7207c06ecbf..2f12b1c6da8 100644 --- a/opensearch/src/main/java/org/opensearch/sql/opensearch/request/OpenSearchQueryRequest.java +++ b/opensearch/src/main/java/org/opensearch/sql/opensearch/request/OpenSearchQueryRequest.java @@ -255,6 +255,8 @@ public OpenSearchResponse searchWithPIT(Function new OpenSearchResponse( SearchHits.empty(), exprValueFactory, includes, isCountAggRequest()); } else { + ProfileMetric metric = QueryProfiling.current().getOrCreateMetric(MetricName.OPENSEARCH_TIME); + long engineStartTime = System.nanoTime(); this.sourceBuilder.pointInTimeBuilder(new PointInTimeBuilder(this.pitId)); this.sourceBuilder.timeout(cursorKeepAlive); // check for search after @@ -297,6 +299,7 @@ public OpenSearchResponse searchWithPIT(Function LOG.debug(sourceBuilder); } } + metric.add(System.nanoTime() - engineStartTime); } return openSearchResponse; } From 53b75896f7f154505b176c093a9a179cf63978ca Mon Sep 17 00:00:00 2001 From: Peng Huo Date: Mon, 5 Jan 2026 09:08:58 -0800 Subject: [PATCH 5/9] Refactor Code Signed-off-by: Peng Huo --- .../sql/monitor/profile/DefaultProfileContext.java | 6 ------ .../sql/monitor/profile/NoopProfileContext.java | 14 +------------- .../sql/monitor/profile/ProfileContext.java | 12 +----------- .../format/SimpleJsonResponseFormatter.java | 9 +-------- 4 files changed, 3 insertions(+), 38 deletions(-) diff --git a/core/src/main/java/org/opensearch/sql/monitor/profile/DefaultProfileContext.java b/core/src/main/java/org/opensearch/sql/monitor/profile/DefaultProfileContext.java index 597e7224d1a..2eb2062cb63 100644 --- a/core/src/main/java/org/opensearch/sql/monitor/profile/DefaultProfileContext.java +++ b/core/src/main/java/org/opensearch/sql/monitor/profile/DefaultProfileContext.java @@ -21,12 +21,6 @@ public class DefaultProfileContext implements ProfileContext { public DefaultProfileContext() {} - /** {@inheritDoc} */ - @Override - public boolean isEnabled() { - return true; - } - /** {@inheritDoc} */ @Override public ProfileMetric getOrCreateMetric(MetricName name) { diff --git a/core/src/main/java/org/opensearch/sql/monitor/profile/NoopProfileContext.java b/core/src/main/java/org/opensearch/sql/monitor/profile/NoopProfileContext.java index 07b2832772b..b3f2a362e38 100644 --- a/core/src/main/java/org/opensearch/sql/monitor/profile/NoopProfileContext.java +++ b/core/src/main/java/org/opensearch/sql/monitor/profile/NoopProfileContext.java @@ -5,8 +5,6 @@ package org.opensearch.sql.monitor.profile; -import java.util.LinkedHashMap; -import java.util.Map; import java.util.Objects; /** Disabled profiling context. */ @@ -16,12 +14,6 @@ public final class NoopProfileContext implements ProfileContext { private NoopProfileContext() {} - /** {@inheritDoc} */ - @Override - public boolean isEnabled() { - return false; - } - /** {@inheritDoc} */ @Override public ProfileMetric getOrCreateMetric(MetricName name) { @@ -32,10 +24,6 @@ public ProfileMetric getOrCreateMetric(MetricName name) { /** {@inheritDoc} */ @Override public QueryProfile finish() { - Map metrics = new LinkedHashMap<>(); - for (MetricName metricName : MetricName.values()) { - metrics.put(metricName, 0d); - } - return new QueryProfile(0d, metrics); + return null; } } diff --git a/core/src/main/java/org/opensearch/sql/monitor/profile/ProfileContext.java b/core/src/main/java/org/opensearch/sql/monitor/profile/ProfileContext.java index b3e900bcab1..044a71c7d46 100644 --- a/core/src/main/java/org/opensearch/sql/monitor/profile/ProfileContext.java +++ b/core/src/main/java/org/opensearch/sql/monitor/profile/ProfileContext.java @@ -6,12 +6,7 @@ package org.opensearch.sql.monitor.profile; /** Context for collecting profiling metrics during query execution. */ -public interface ProfileContext extends AutoCloseable { - /** - * @return true when profiling is enabled. - */ - boolean isEnabled(); - +public interface ProfileContext { /** * Obtain or create a metric with the provided name. * @@ -26,9 +21,4 @@ public interface ProfileContext extends AutoCloseable { * @return immutable query profile snapshot */ QueryProfile finish(); - - @Override - default void close() { - finish(); - } } diff --git a/protocol/src/main/java/org/opensearch/sql/protocol/response/format/SimpleJsonResponseFormatter.java b/protocol/src/main/java/org/opensearch/sql/protocol/response/format/SimpleJsonResponseFormatter.java index 9ebc5437fcf..8b48294caca 100644 --- a/protocol/src/main/java/org/opensearch/sql/protocol/response/format/SimpleJsonResponseFormatter.java +++ b/protocol/src/main/java/org/opensearch/sql/protocol/response/format/SimpleJsonResponseFormatter.java @@ -11,7 +11,6 @@ import lombok.RequiredArgsConstructor; import lombok.Singular; import org.opensearch.sql.monitor.profile.MetricName; -import org.opensearch.sql.monitor.profile.ProfileContext; import org.opensearch.sql.monitor.profile.ProfileMetric; import org.opensearch.sql.monitor.profile.QueryProfile; import org.opensearch.sql.monitor.profile.QueryProfiling; @@ -57,13 +56,7 @@ public Object buildJsonObject(QueryResult response) { json.datarows(fetchDataRows(response)); formatMetric.set(System.nanoTime() - formatTime); - ProfileContext profileContext = QueryProfiling.current(); - if (profileContext.isEnabled()) { - QueryProfile finish = profileContext.finish(); - json.profile(finish); - } else { - json.profile(null); - } + json.profile(QueryProfiling.current().finish()); return json.build(); } From ee0d477cd56cdeddec6da15a334b82f606245ac5 Mon Sep 17 00:00:00 2001 From: Peng Huo Date: Mon, 5 Jan 2026 10:15:43 -0800 Subject: [PATCH 6/9] Remove unused code Signed-off-by: Peng Huo --- .../opensearch/sql/monitor/profile/DefaultProfileContext.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/core/src/main/java/org/opensearch/sql/monitor/profile/DefaultProfileContext.java b/core/src/main/java/org/opensearch/sql/monitor/profile/DefaultProfileContext.java index 2eb2062cb63..c69a4e38a2b 100644 --- a/core/src/main/java/org/opensearch/sql/monitor/profile/DefaultProfileContext.java +++ b/core/src/main/java/org/opensearch/sql/monitor/profile/DefaultProfileContext.java @@ -14,7 +14,6 @@ public class DefaultProfileContext implements ProfileContext { private final long startNanos = System.nanoTime(); - private long endNanos; private boolean finished; private final Map metrics = new ConcurrentHashMap<>(); private QueryProfile profile; @@ -35,7 +34,7 @@ public synchronized QueryProfile finish() { return profile; } finished = true; - endNanos = System.nanoTime(); + long endNanos = System.nanoTime(); Map snapshot = new LinkedHashMap<>(MetricName.values().length); for (MetricName metricName : MetricName.values()) { DefaultMetricImpl metric = metrics.get(metricName); From 820d75783fcc9b1ab14f9366bd42dbc9b6929d9e Mon Sep 17 00:00:00 2001 From: Peng Huo Date: Mon, 5 Jan 2026 16:37:57 -0800 Subject: [PATCH 7/9] Address comments Signed-off-by: Peng Huo --- .../sql/monitor/profile/DefaultMetricImpl.java | 10 +++++----- docs/user/ppl/interfaces/endpoint.md | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/core/src/main/java/org/opensearch/sql/monitor/profile/DefaultMetricImpl.java b/core/src/main/java/org/opensearch/sql/monitor/profile/DefaultMetricImpl.java index 511bfaac3a8..214628c40cc 100644 --- a/core/src/main/java/org/opensearch/sql/monitor/profile/DefaultMetricImpl.java +++ b/core/src/main/java/org/opensearch/sql/monitor/profile/DefaultMetricImpl.java @@ -11,7 +11,7 @@ final class DefaultMetricImpl implements ProfileMetric { private final String name; - private final LongAdder value = new LongAdder(); + private long value = 0; /** * Construct a metric with the provided name. @@ -29,17 +29,17 @@ public String name() { @Override public long value() { - return value.sum(); + return value; } @Override public void add(long delta) { - value.add(delta); + value += delta; } @Override public void set(long value) { - this.value.reset(); - this.value.add(value); + this.value = 0; + add(value); } } diff --git a/docs/user/ppl/interfaces/endpoint.md b/docs/user/ppl/interfaces/endpoint.md index f17574e3948..888651d3e9b 100644 --- a/docs/user/ppl/interfaces/endpoint.md +++ b/docs/user/ppl/interfaces/endpoint.md @@ -152,7 +152,7 @@ calcite: CalciteEnumerableIndexScan(table=[[OpenSearch, state_country]], PushDownContext=[[PROJECT->[name, country, state, month, year, age], FILTER->>($5, 30), LIMIT->10000], OpenSearchRequestBuilder(sourceBuilder={"from":0,"size":10000,"timeout":"1m","query":{"range":{"age":{"from":30,"to":null,"include_lower":false,"include_upper":true,"boost":1.0}}},"_source":{"includes":["name","country","state","month","year","age"],"excludes":[]}}, requestedTotalSize=10000, pageSize=null, startFrom=0)]) ``` -## Profile +## Profile (Experimental) You can enable profiling on the PPL endpoint to capture per-stage timings in milliseconds. Profiling is returned only for regular query execution (not explain) and only when using the default `format=jdbc`. From 7ff2bb25dc455bdfbd8154c57b204eb4b7c26e9d Mon Sep 17 00:00:00 2001 From: Peng Huo Date: Thu, 8 Jan 2026 15:57:17 -0800 Subject: [PATCH 8/9] Add Task 1 - Add Phases level metrics Signed-off-by: Peng Huo --- .../sql/calcite/utils/CalciteToolsHelper.java | 4 +- .../opensearch/sql/executor/QueryService.java | 4 +- .../monitor/profile/DefaultMetricImpl.java | 10 ++-- .../sql/monitor/profile/MetricName.java | 9 ++-- .../sql/monitor/profile/QueryProfile.java | 51 +++++++++++++------ docs/user/ppl/interfaces/endpoint.md | 15 +++--- .../rest-api-spec/test/api/ppl.profile.yml | 8 +-- .../executor/OpenSearchExecutionEngine.java | 6 +-- .../request/OpenSearchQueryRequest.java | 12 ++--- .../format/SimpleJsonResponseFormatter.java | 2 +- 10 files changed, 72 insertions(+), 49 deletions(-) diff --git a/core/src/main/java/org/opensearch/sql/calcite/utils/CalciteToolsHelper.java b/core/src/main/java/org/opensearch/sql/calcite/utils/CalciteToolsHelper.java index a1a70f9917e..d7a02cd7f28 100644 --- a/core/src/main/java/org/opensearch/sql/calcite/utils/CalciteToolsHelper.java +++ b/core/src/main/java/org/opensearch/sql/calcite/utils/CalciteToolsHelper.java @@ -28,7 +28,7 @@ package org.opensearch.sql.calcite.utils; import static java.util.Objects.requireNonNull; -import static org.opensearch.sql.monitor.profile.MetricName.OPTIMIZE_TIME; +import static org.opensearch.sql.monitor.profile.MetricName.OPTIMIZE; import com.google.common.collect.ImmutableList; import java.lang.reflect.Type; @@ -343,7 +343,7 @@ public static class OpenSearchRelRunners { * org.apache.calcite.tools.RelRunners#run(RelNode)} */ public static PreparedStatement run(CalcitePlanContext context, RelNode rel) { - ProfileMetric optimizeTime = QueryProfiling.current().getOrCreateMetric(OPTIMIZE_TIME); + ProfileMetric optimizeTime = QueryProfiling.current().getOrCreateMetric(OPTIMIZE); long startTime = System.nanoTime(); final RelShuttle shuttle = new RelHomogeneousShuttle() { diff --git a/core/src/main/java/org/opensearch/sql/executor/QueryService.java b/core/src/main/java/org/opensearch/sql/executor/QueryService.java index f46f58b14cb..27539c0d46c 100644 --- a/core/src/main/java/org/opensearch/sql/executor/QueryService.java +++ b/core/src/main/java/org/opensearch/sql/executor/QueryService.java @@ -105,7 +105,7 @@ public void executeWithCalcite( try { ProfileContext profileContext = QueryProfiling.activate(QueryContext.isProfileEnabled()); - ProfileMetric metric = profileContext.getOrCreateMetric(MetricName.ANALYZE_TIME); + ProfileMetric analyzeMetric = profileContext.getOrCreateMetric(MetricName.ANALYZE); long analyzeStart = System.nanoTime(); CalcitePlanContext context = CalcitePlanContext.create( @@ -114,7 +114,7 @@ public void executeWithCalcite( relNode = mergeAdjacentFilters(relNode); RelNode optimized = optimize(relNode, context); RelNode calcitePlan = convertToCalcitePlan(optimized); - metric.set(System.nanoTime() - analyzeStart); + analyzeMetric.set(System.nanoTime() - analyzeStart); executionEngine.execute(calcitePlan, context, listener); } catch (Throwable t) { if (isCalciteFallbackAllowed(t) && !(t instanceof NonFallbackCalciteException)) { diff --git a/core/src/main/java/org/opensearch/sql/monitor/profile/DefaultMetricImpl.java b/core/src/main/java/org/opensearch/sql/monitor/profile/DefaultMetricImpl.java index 214628c40cc..511bfaac3a8 100644 --- a/core/src/main/java/org/opensearch/sql/monitor/profile/DefaultMetricImpl.java +++ b/core/src/main/java/org/opensearch/sql/monitor/profile/DefaultMetricImpl.java @@ -11,7 +11,7 @@ final class DefaultMetricImpl implements ProfileMetric { private final String name; - private long value = 0; + private final LongAdder value = new LongAdder(); /** * Construct a metric with the provided name. @@ -29,17 +29,17 @@ public String name() { @Override public long value() { - return value; + return value.sum(); } @Override public void add(long delta) { - value += delta; + value.add(delta); } @Override public void set(long value) { - this.value = 0; - add(value); + this.value.reset(); + this.value.add(value); } } diff --git a/core/src/main/java/org/opensearch/sql/monitor/profile/MetricName.java b/core/src/main/java/org/opensearch/sql/monitor/profile/MetricName.java index 005c17bdd00..987d7dfb647 100644 --- a/core/src/main/java/org/opensearch/sql/monitor/profile/MetricName.java +++ b/core/src/main/java/org/opensearch/sql/monitor/profile/MetricName.java @@ -7,9 +7,8 @@ /** Named metrics used by query profiling. */ public enum MetricName { - ANALYZE_TIME, - OPTIMIZE_TIME, - OPENSEARCH_TIME, - POST_EXEC_TIME, - FORMAT_TIME + ANALYZE, + OPTIMIZE, + EXECUTE, + FORMAT } diff --git a/core/src/main/java/org/opensearch/sql/monitor/profile/QueryProfile.java b/core/src/main/java/org/opensearch/sql/monitor/profile/QueryProfile.java index 3b644f83fc4..c766308a122 100644 --- a/core/src/main/java/org/opensearch/sql/monitor/profile/QueryProfile.java +++ b/core/src/main/java/org/opensearch/sql/monitor/profile/QueryProfile.java @@ -6,6 +6,8 @@ package org.opensearch.sql.monitor.profile; import com.google.gson.annotations.SerializedName; +import java.util.LinkedHashMap; +import java.util.Locale; import java.util.Map; import java.util.Objects; import lombok.Getter; @@ -14,31 +16,50 @@ @Getter public final class QueryProfile { - /** Total elapsed milliseconds for the profiled query (rounded to two decimals). */ - @SerializedName("total_ms") - private final double totalMillis; + private final Summary summary; - /** Immutable metric values keyed by metric name in milliseconds (rounded to two decimals). */ - private final Map metrics; + private final Map phases; /** * Create a new query profile snapshot. * - * @param totalMillis total elapsed milliseconds for the query (rounded to two decimals) - * @param metrics metric values keyed by {@link MetricName} + * @param totalTimeMillis total elapsed milliseconds for the query (rounded to two decimals) + * @param phases metric values keyed by {@link MetricName} */ - public QueryProfile(double totalMillis, Map metrics) { - this.totalMillis = totalMillis; - this.metrics = buildMetrics(metrics); + public QueryProfile(double totalTimeMillis, Map phases) { + this.summary = new Summary(totalTimeMillis); + this.phases = buildPhases(phases); } - private Map buildMetrics(Map metrics) { - Objects.requireNonNull(metrics, "metrics"); - Map ordered = new java.util.LinkedHashMap<>(metrics.size()); + private Map buildPhases(Map phases) { + Objects.requireNonNull(phases, "phases"); + Map ordered = new LinkedHashMap<>(MetricName.values().length); for (MetricName metricName : MetricName.values()) { - Double value = metrics.getOrDefault(metricName, 0d); - ordered.put(metricName.name() + "_MS", value); + Double value = phases.getOrDefault(metricName, 0d); + ordered.put(metricName.name().toLowerCase(Locale.ROOT), new Phase(value)); } return ordered; } + + @Getter + public static final class Summary { + + @SerializedName("total_time_ms") + private final double totalTimeMillis; + + private Summary(double totalTimeMillis) { + this.totalTimeMillis = totalTimeMillis; + } + } + + @Getter + public static final class Phase { + + @SerializedName("time_ms") + private final double timeMillis; + + private Phase(double timeMillis) { + this.timeMillis = timeMillis; + } + } } diff --git a/docs/user/ppl/interfaces/endpoint.md b/docs/user/ppl/interfaces/endpoint.md index 888651d3e9b..bc2a14d19d3 100644 --- a/docs/user/ppl/interfaces/endpoint.md +++ b/docs/user/ppl/interfaces/endpoint.md @@ -172,13 +172,14 @@ Expected output (trimmed): ```json { "profile": { - "total_ms": 25.77, - "metrics": { - "ANALYZE_TIME_MS": 5.77, - "OPTIMIZE_TIME_MS": 13.51, - "OPENSEARCH_TIME_MS": 4.31, - "POST_EXEC_TIME_MS": 0.77, - "FORMAT_TIME_MS": 0.04 + "summary": { + "total_time_ms": 25.77 + }, + "phases": { + "analyze": { "time_ms": 5.77 }, + "optimize": { "time_ms": 13.51 }, + "execute": { "time_ms": 0.77 }, + "format": { "time_ms": 0.04 } } } } diff --git a/integ-test/src/yamlRestTest/resources/rest-api-spec/test/api/ppl.profile.yml b/integ-test/src/yamlRestTest/resources/rest-api-spec/test/api/ppl.profile.yml index 29762d3647a..8a9ff9b23a5 100644 --- a/integ-test/src/yamlRestTest/resources/rest-api-spec/test/api/ppl.profile.yml +++ b/integ-test/src/yamlRestTest/resources/rest-api-spec/test/api/ppl.profile.yml @@ -49,9 +49,11 @@ teardown: body: query: 'source=ppl_profile | fields message' profile: true - - gt: {profile.total_ms: 0.0} - - gt: {profile.metrics.ANALYZE_TIME_MS: 0.0} - - gt: {profile.metrics.OPENSEARCH_TIME_MS: 0.0} + - gt: {profile.summary.total_time_ms: 0.0} + - gt: {profile.phases.analyze.time_ms: 0.0} + - gt: {profile.phases.optimize.time_ms: 0.0} + - gt: {profile.phases.execute.time_ms: 0.0} + - gt: {profile.phases.format.time_ms: 0.0} --- "Profile ignored for explain api": diff --git a/opensearch/src/main/java/org/opensearch/sql/opensearch/executor/OpenSearchExecutionEngine.java b/opensearch/src/main/java/org/opensearch/sql/opensearch/executor/OpenSearchExecutionEngine.java index 2477f95beac..dead64899bc 100644 --- a/opensearch/src/main/java/org/opensearch/sql/opensearch/executor/OpenSearchExecutionEngine.java +++ b/opensearch/src/main/java/org/opensearch/sql/opensearch/executor/OpenSearchExecutionEngine.java @@ -221,8 +221,8 @@ private void buildResultSet( Integer querySizeLimit, ResponseListener listener) throws SQLException { - ProfileMetric metric = QueryProfiling.current().getOrCreateMetric(MetricName.POST_EXEC_TIME); - long postExecTime = System.nanoTime(); + ProfileMetric metric = QueryProfiling.current().getOrCreateMetric(MetricName.EXECUTE); + long execTime = System.nanoTime(); // Get the ResultSet metadata to know about columns ResultSetMetaData metaData = resultSet.getMetaData(); int columnCount = metaData.getColumnCount(); @@ -267,7 +267,7 @@ private void buildResultSet( } Schema schema = new Schema(columns); QueryResponse response = new QueryResponse(schema, values, null); - metric.set(System.nanoTime() - postExecTime); + metric.add(System.nanoTime() - execTime); listener.onResponse(response); } diff --git a/opensearch/src/main/java/org/opensearch/sql/opensearch/request/OpenSearchQueryRequest.java b/opensearch/src/main/java/org/opensearch/sql/opensearch/request/OpenSearchQueryRequest.java index 2f12b1c6da8..1d05f82fc2a 100644 --- a/opensearch/src/main/java/org/opensearch/sql/opensearch/request/OpenSearchQueryRequest.java +++ b/opensearch/src/main/java/org/opensearch/sql/opensearch/request/OpenSearchQueryRequest.java @@ -209,8 +209,8 @@ private OpenSearchResponse search(Function search new OpenSearchResponse( SearchHits.empty(), exprValueFactory, includes, isCountAggRequest()); } else { - ProfileMetric metric = QueryProfiling.current().getOrCreateMetric(MetricName.OPENSEARCH_TIME); - long engineStartTime = System.nanoTime(); + ProfileMetric metric = QueryProfiling.current().getOrCreateMetric(MetricName.EXECUTE); + long executionStartTime = System.nanoTime(); // Set afterKey to request, null for first round (afterKey is null in the beginning). if (this.sourceBuilder.aggregations() != null) { this.sourceBuilder.aggregations().getAggregatorFactories().stream() @@ -243,7 +243,7 @@ private OpenSearchResponse search(Function search searchDone = true; } needClean = searchDone; - metric.set(System.nanoTime() - engineStartTime); + metric.add(System.nanoTime() - executionStartTime); } return openSearchResponse; } @@ -255,8 +255,8 @@ public OpenSearchResponse searchWithPIT(Function new OpenSearchResponse( SearchHits.empty(), exprValueFactory, includes, isCountAggRequest()); } else { - ProfileMetric metric = QueryProfiling.current().getOrCreateMetric(MetricName.OPENSEARCH_TIME); - long engineStartTime = System.nanoTime(); + ProfileMetric metric = QueryProfiling.current().getOrCreateMetric(MetricName.EXECUTE); + long executionStartTime = System.nanoTime(); this.sourceBuilder.pointInTimeBuilder(new PointInTimeBuilder(this.pitId)); this.sourceBuilder.timeout(cursorKeepAlive); // check for search after @@ -299,7 +299,7 @@ public OpenSearchResponse searchWithPIT(Function LOG.debug(sourceBuilder); } } - metric.add(System.nanoTime() - engineStartTime); + metric.add(System.nanoTime() - executionStartTime); } return openSearchResponse; } diff --git a/protocol/src/main/java/org/opensearch/sql/protocol/response/format/SimpleJsonResponseFormatter.java b/protocol/src/main/java/org/opensearch/sql/protocol/response/format/SimpleJsonResponseFormatter.java index 8b48294caca..ff59ce4cddc 100644 --- a/protocol/src/main/java/org/opensearch/sql/protocol/response/format/SimpleJsonResponseFormatter.java +++ b/protocol/src/main/java/org/opensearch/sql/protocol/response/format/SimpleJsonResponseFormatter.java @@ -44,7 +44,7 @@ public SimpleJsonResponseFormatter(Style style) { @Override public Object buildJsonObject(QueryResult response) { - ProfileMetric formatMetric = QueryProfiling.current().getOrCreateMetric(MetricName.FORMAT_TIME); + ProfileMetric formatMetric = QueryProfiling.current().getOrCreateMetric(MetricName.FORMAT); long formatTime = System.nanoTime(); JsonResponse.JsonResponseBuilder json = JsonResponse.builder(); From 0da0c4bf043200bc017207000ab43443f32f1f33 Mon Sep 17 00:00:00 2001 From: Peng Huo Date: Thu, 8 Jan 2026 16:04:22 -0800 Subject: [PATCH 9/9] Reformat doc Signed-off-by: Peng Huo --- docs/user/ppl/interfaces/endpoint.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/user/ppl/interfaces/endpoint.md b/docs/user/ppl/interfaces/endpoint.md index bc2a14d19d3..8b4e19ea687 100644 --- a/docs/user/ppl/interfaces/endpoint.md +++ b/docs/user/ppl/interfaces/endpoint.md @@ -173,13 +173,13 @@ Expected output (trimmed): { "profile": { "summary": { - "total_time_ms": 25.77 + "total_time_ms": 33.34 }, "phases": { - "analyze": { "time_ms": 5.77 }, - "optimize": { "time_ms": 13.51 }, - "execute": { "time_ms": 0.77 }, - "format": { "time_ms": 0.04 } + "analyze": { "time_ms": 8.68 }, + "optimize": { "time_ms": 18.2 }, + "execute": { "time_ms": 4.87 }, + "format": { "time_ms": 0.05 } } } }