Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
05a6412
Add analysis and preanalysis timing information to profiling and quer…
carlosdelest Dec 15, 2025
5990a3e
Update docs/changelog/139540.yaml
carlosdelest Dec 15, 2025
bf07e13
Fix preanalysis and analysis as methods may be called multiple times
carlosdelest Dec 15, 2025
3f600f6
Merge remote-tracking branch 'carlosdelest/enhancement/esql-analysis-…
carlosdelest Dec 15, 2025
eb429ac
Fix tests
carlosdelest Dec 16, 2025
a12ecbc
Add parsing information
carlosdelest Dec 16, 2025
313d05b
[CI] Auto commit changes from spotless
Dec 16, 2025
4969ca9
Add parsing profile info
carlosdelest Dec 16, 2025
e42133a
Merge remote-tracking branch 'carlosdelest/enhancement/esql-analysis-…
carlosdelest Dec 16, 2025
1004b33
Merge branch 'main' into enhancement/esql-analysis-preanalysis-profiling
carlosdelest Dec 16, 2025
309263c
Add parsing profile info to tests
carlosdelest Dec 16, 2025
c7c3e48
Fix comments
carlosdelest Dec 16, 2025
c1e4c0a
Merge remote-tracking branch 'carlosdelest/enhancement/esql-analysis-…
carlosdelest Dec 16, 2025
029ce1b
Fix changelog
carlosdelest Dec 16, 2025
b81841e
Add PlanningProfile class
carlosdelest Dec 17, 2025
90f6360
Some changes to make serialization easier
carlosdelest Dec 17, 2025
e3fbf19
Add javadoc
carlosdelest Dec 17, 2025
8fe07d0
Improve testing
carlosdelest Dec 17, 2025
a34b879
Add transport version
carlosdelest Dec 17, 2025
27a0f28
Fix IT
carlosdelest Dec 17, 2025
2920c83
Fix Csv tests
carlosdelest Dec 18, 2025
2dcb361
Merge remote-tracking branch 'origin/main' into enhancement/esql-anal…
carlosdelest Dec 18, 2025
c69d740
Add again transport versions
carlosdelest Dec 18, 2025
c08ea3d
Fix test
carlosdelest Dec 18, 2025
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/139540.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
pr: 139540
summary: ES|QL - Add parsing, preanalysis and analysis timing information to profile
area: ES|QL
type: enhancement
issues: []
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
9251000
2 changes: 1 addition & 1 deletion server/src/main/resources/transport/upper_bounds/9.4.csv
Original file line number Diff line number Diff line change
@@ -1 +1 @@
initial_9.3.0,9250000
esql_query_planning_profile,9251000
Original file line number Diff line number Diff line change
Expand Up @@ -2763,6 +2763,10 @@ protected static MapMatcher getProfileMatcher() {
return matchesMap() //
.entry("query", instanceOf(Map.class))
.entry("planning", instanceOf(Map.class))
.entry("parsing", instanceOf(Map.class))
.entry("preanalysis", instanceOf(Map.class))
.entry("dependency_resolution", instanceOf(Map.class))
.entry("analysis", instanceOf(Map.class))
.entry("drivers", instanceOf(List.class))
.entry("plans", instanceOf(List.class))
.entry("minimumTransportVersion", instanceOf(Integer.class));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -763,6 +763,10 @@ private void test(
.entry("drivers", instanceOf(List.class))
.entry("plans", instanceOf(List.class))
.entry("planning", matchesMap().extraOk())
.entry("parsing", matchesMap().extraOk())
.entry("preanalysis", matchesMap().extraOk())
.entry("dependency_resolution", matchesMap().extraOk())
.entry("analysis", matchesMap().extraOk())
.entry("query", matchesMap().extraOk())
.entry("minimumTransportVersion", instanceOf(Integer.class))
),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -365,6 +365,10 @@ private void testPushQuery(
.entry("drivers", instanceOf(List.class))
.entry("plans", instanceOf(List.class))
.entry("planning", matchesMap().extraOk())
.entry("parsing", matchesMap().extraOk())
.entry("preanalysis", matchesMap().extraOk())
.entry("dependency_resolution", matchesMap().extraOk())
.entry("analysis", matchesMap().extraOk())
.entry("query", matchesMap().extraOk())
.entry("minimumTransportVersion", instanceOf(Integer.class))
),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,10 @@ private void testQuery(Double percent, String query, int documentsFound, boolean
.entry("drivers", instanceOf(List.class))
.entry("plans", instanceOf(List.class))
.entry("planning", matchesMap().extraOk())
.entry("parsing", matchesMap().extraOk())
.entry("preanalysis", matchesMap().extraOk())
.entry("dependency_resolution", matchesMap().extraOk())
.entry("analysis", matchesMap().extraOk())
.entry("query", matchesMap().extraOk())
.entry("minimumTransportVersion", instanceOf(Integer.class))
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,20 +26,27 @@
import org.junit.AfterClass;
import org.junit.BeforeClass;

import java.util.List;
import java.util.Map;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;

import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertAcked;
import static org.elasticsearch.xpack.esql.action.EsqlQueryRequest.syncEsqlQueryRequest;
import static org.elasticsearch.xpack.esql.action.PlanningProfile.ANALYSIS;
import static org.elasticsearch.xpack.esql.action.PlanningProfile.DEPENDENCY_RESOLUTION;
import static org.elasticsearch.xpack.esql.action.PlanningProfile.PARSING;
import static org.elasticsearch.xpack.esql.action.PlanningProfile.PLANNING;
import static org.elasticsearch.xpack.esql.action.PlanningProfile.PRE_ANALYSIS;
import static org.elasticsearch.xpack.esql.querylog.EsqlQueryLog.ELASTICSEARCH_QUERYLOG_ERROR_MESSAGE;
import static org.elasticsearch.xpack.esql.querylog.EsqlQueryLog.ELASTICSEARCH_QUERYLOG_ERROR_TYPE;
import static org.elasticsearch.xpack.esql.querylog.EsqlQueryLog.ELASTICSEARCH_QUERYLOG_PLANNING_TOOK;
import static org.elasticsearch.xpack.esql.querylog.EsqlQueryLog.ELASTICSEARCH_QUERYLOG_PLANNING_TOOK_MILLIS;
import static org.elasticsearch.xpack.esql.querylog.EsqlQueryLog.ELASTICSEARCH_QUERYLOG_PREFIX;
import static org.elasticsearch.xpack.esql.querylog.EsqlQueryLog.ELASTICSEARCH_QUERYLOG_QUERY;
import static org.elasticsearch.xpack.esql.querylog.EsqlQueryLog.ELASTICSEARCH_QUERYLOG_SUCCESS;
import static org.elasticsearch.xpack.esql.querylog.EsqlQueryLog.ELASTICSEARCH_QUERYLOG_TOOK;
import static org.elasticsearch.xpack.esql.querylog.EsqlQueryLog.ELASTICSEARCH_QUERYLOG_TOOK_MILLIS;
import static org.elasticsearch.xpack.esql.querylog.EsqlQueryLog.ELASTICSEARCH_QUERYLOG_TOOK_MILLIS_SUFFIX;
import static org.elasticsearch.xpack.esql.querylog.EsqlQueryLog.ELASTICSEARCH_QUERYLOG_TOOK_SUFFIX;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.greaterThan;
Expand Down Expand Up @@ -184,12 +191,18 @@ private static void testAllLevels(
assertThat(tookMillis, is(tookMillisExpected));

if (expectedException == null) {
long planningTook = Long.valueOf(msg.get(ELASTICSEARCH_QUERYLOG_PLANNING_TOOK));
long planningTookMillisExpected = planningTook / 1_000_000;
long planningTookMillis = Long.valueOf(msg.get(ELASTICSEARCH_QUERYLOG_PLANNING_TOOK_MILLIS));
assertThat(planningTook, greaterThanOrEqualTo(0L));
assertThat(planningTookMillis, is(planningTookMillisExpected));
assertThat(took, greaterThan(planningTook));
for (String timing : List.of(PLANNING, PARSING, PRE_ANALYSIS, DEPENDENCY_RESOLUTION, ANALYSIS)) {
long timingTook = Long.valueOf(
msg.get(ELASTICSEARCH_QUERYLOG_PREFIX + timing + ELASTICSEARCH_QUERYLOG_TOOK_SUFFIX)
);
long timingTookMillisExpected = timingTook / 1_000_000;
long timingTookMillis = Long.valueOf(
msg.get(ELASTICSEARCH_QUERYLOG_PREFIX + timing + ELASTICSEARCH_QUERYLOG_TOOK_MILLIS_SUFFIX)
);
assertThat(timingTook, greaterThanOrEqualTo(0L));
assertThat(timingTookMillis, is(timingTookMillisExpected));
assertThat(took, greaterThan(timingTook));
}
}

assertThat(msg.get(ELASTICSEARCH_QUERYLOG_QUERY), is(query));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,11 +90,10 @@ public enum IncludeExecutionMetadata {
private volatile boolean isPartial; // Does this request have partial results?
private transient volatile boolean isStopped; // Have we received stop command?

// start time for the ESQL query for calculating time spans relative to the beginning of the query
private final transient TimeSpan.Builder relativeStart;
private transient TimeSpan overallTimeSpan;
private transient TimeSpan planningTimeSpan; // time elapsed since start of query to calling ComputeService.execute
private final PlanningProfile planningProfile;
private TimeValue overallTook;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

EsqlExecutionInfo has grown to almost 800 lines and has multiple responsibilities now (such as ccs info tracking, profiling, status reportin, isPartial, isStopped). Currently we are thinking about a ways to manage that.

I wonder if that would make sense to group all transient timers and define them in a new private final transient PlanningProfile field? This would allow to move some of this class complexity elsewhere.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense - I've created a PlanningProfile class and moved planning related profiling there.

private transient TimeSpan overallTimeSpan;

// Are we doing subplans? No need to serialize this because it is only relevant for the coordinator node.
private transient boolean inSubplan = false;
Expand All @@ -118,6 +117,7 @@ public EsqlExecutionInfo(Predicate<String> skipOnPlanTimeFailurePredicate, Inclu
this.skipOnFailurePredicate = skipOnPlanTimeFailurePredicate;
this.includeExecutionMetadata = includeExecutionMetadata;
this.relativeStart = relativeStart;
this.planningProfile = new PlanningProfile();
}

public EsqlExecutionInfo(StreamInput in) throws IOException {
Expand All @@ -133,7 +133,9 @@ public EsqlExecutionInfo(StreamInput in) throws IOException {
this.relativeStart = null;
if (in.getTransportVersion().supports(ESQL_QUERY_PLANNING_DURATION)) {
this.overallTimeSpan = in.readOptional(TimeSpan::readFrom);
this.planningTimeSpan = in.readOptional(TimeSpan::readFrom);
this.planningProfile = PlanningProfile.readFrom(in);
} else {
this.planningProfile = new PlanningProfile();
}
}

Expand All @@ -153,8 +155,9 @@ public void writeTo(StreamOutput out) throws IOException {
out.writeBoolean(isPartial);
if (out.getTransportVersion().supports(ESQL_QUERY_PLANNING_DURATION)) {
out.writeOptionalWriteable(overallTimeSpan);
out.writeOptionalWriteable(planningTimeSpan);
planningProfile.writeTo(out);
}

assert inSubplan == false : "Should not be serializing execution info while in subplans";
}

Expand All @@ -167,26 +170,10 @@ public IncludeExecutionMetadata includeExecutionMetadata() {
return includeExecutionMetadata;
}

/**
* Call when ES|QL "planning" phase is complete and query execution (in ComputeService) is about to start.
* Note this is currently only built for a single phase planning/execution model. When INLINE STATS
* moves towards GA we may need to revisit this model. Currently, it should never be called more than once.
*/
public void markEndPlanning() {
assert planningTimeSpan == null : "markEndPlanning should only be called once";
assert relativeStart != null : "Relative start time must be set when markEndPlanning is called";
planningTimeSpan = relativeStart.stop();
}

public TimeValue planningTookTime() {
return planningTimeSpan != null ? planningTimeSpan.toTimeValue() : null;
}

/**
* Call when ES|QL execution is complete in order to set the overall took time for an ES|QL query.
*/
public void markEndQuery() {
assert relativeStart != null : "Relative start time must be set when markEndQuery is called";
if (isMainPlan()) {
overallTimeSpan = relativeStart.stop();
overallTook = overallTimeSpan.toTimeValue();
Expand All @@ -212,14 +199,14 @@ public TimeSpan overallTimeSpan() {
return overallTimeSpan;
}

public TimeSpan planningTimeSpan() {
return planningTimeSpan;
}

public Set<String> clusterAliases() {
return clusterInfo.keySet();
}

public PlanningProfile planningProfile() {
return planningProfile;
}

/**
* @param clusterAlias to check if we should skip this cluster on failure
* @return whether it's OK to skip the cluster on failure.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -325,7 +325,7 @@ public Iterator<? extends ToXContent> toXContentChunked(ToXContent.Params params
content.add(ChunkedToXContentHelper.chunk((b, p) -> {
if (executionInfo != null) {
b.field("query", executionInfo.overallTimeSpan());
b.field("planning", executionInfo.planningTimeSpan());
executionInfo.planningProfile().toXContent(b, p);
}
return b;
}));
Expand Down
Loading