Skip to content

Commit ee8f327

Browse files
authored
make explain return planner metrics as separate column (#3064)
1 parent cec2491 commit ee8f327

File tree

15 files changed

+355
-93
lines changed

15 files changed

+355
-93
lines changed

docs/ReleaseNotes.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ Our API stability annotations have been updated to reflect greater API instabili
3232
* **Feature** Feature 1 [(Issue #NNN)](https://github.com/FoundationDB/fdb-record-layer/issues/NNN)
3333
* **Feature** Feature 2 [(Issue #NNN)](https://github.com/FoundationDB/fdb-record-layer/issues/NNN)
3434
* **Feature** Feature 3 [(Issue #NNN)](https://github.com/FoundationDB/fdb-record-layer/issues/NNN)
35-
* **Feature** Feature 4 [(Issue #NNN)](https://github.com/FoundationDB/fdb-record-layer/issues/NNN)
35+
* **Feature** make EXPLAIN return a column for planner metrics [(Issue #3063)](https://github.com/FoundationDB/fdb-record-layer/issues/3063)
3636
* **Feature** Feature 5 [(Issue #NNN)](https://github.com/FoundationDB/fdb-record-layer/issues/NNN)
3737
* **Breaking change** Change 1 [(Issue #NNN)](https://github.com/FoundationDB/fdb-record-layer/issues/NNN)
3838
* **Breaking change** Change 2 [(Issue #NNN)](https://github.com/FoundationDB/fdb-record-layer/issues/NNN)

fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/QueryPlanInfo.java

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ public Builder toBuilder() {
6666
* @param <T> The type of the value (not used in this method)
6767
* @return TRUE if the key exists in the table, FALSE otherwise
6868
*/
69-
public <T> boolean containsKey(@Nonnull QueryPlanInfoKey<T> key) {
69+
public <T> boolean containsKey(@Nonnull final QueryPlanInfoKey<T> key) {
7070
return info.containsKey(key);
7171
}
7272

@@ -111,22 +111,23 @@ public static class QueryPlanInfoKey<T> {
111111
@Nonnull
112112
private final String name;
113113

114-
public QueryPlanInfoKey(@Nonnull String name) {
114+
public QueryPlanInfoKey(@Nonnull final String name) {
115115
this.name = name;
116116
}
117117

118+
@Nonnull
118119
public String getName() {
119120
return name;
120121
}
121122

122123
// Suppress Unchecked Cast exception since all put() into the map use the right type for the value from the key.
123124
@SuppressWarnings("unchecked")
124-
public T narrow(@Nonnull Object o) {
125+
public T narrow(@Nonnull final Object o) {
125126
return (T) o;
126127
}
127128

128129
@Override
129-
public boolean equals(Object o) {
130+
public boolean equals(final Object o) {
130131
if (this == o) {
131132
return true;
132133
}
@@ -159,7 +160,7 @@ private Builder() {
159160
infoMap = new HashMap<>();
160161
}
161162

162-
private Builder(QueryPlanInfo source) {
163+
private Builder(@Nonnull final QueryPlanInfo source) {
163164
infoMap = new HashMap<>(source.info);
164165
}
165166

@@ -172,13 +173,13 @@ private Builder(QueryPlanInfo source) {
172173
* @return this
173174
*/
174175
@Nonnull
175-
public <T> Builder put(@Nonnull QueryPlanInfoKey<T> key, @Nonnull T value) {
176+
public <T> Builder put(@Nonnull final QueryPlanInfoKey<T> key, @Nullable final T value) {
176177
infoMap.put(key, value);
177178
return this;
178179
}
179180

180181
@Nullable
181-
public <T> T get(@Nonnull QueryPlanInfoKey<T> key) {
182+
public <T> T get(@Nonnull final QueryPlanInfoKey<T> key) {
182183
return key.narrow(infoMap.get(key));
183184
}
184185

fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/QueryPlanInfoKeys.java

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,20 @@
2020

2121
package com.apple.foundationdb.record.query.plan;
2222

23+
import com.apple.foundationdb.record.query.plan.cascades.debug.StatsMaps;
24+
2325
/**
2426
* Container for {@link QueryPlanInfo.QueryPlanInfoKey} static instances used in the planner.
2527
*/
2628
public class QueryPlanInfoKeys {
27-
public static final QueryPlanInfo.QueryPlanInfoKey<Integer> TOTAL_TASK_COUNT = new QueryPlanInfo.QueryPlanInfoKey<>("totalTaskCount");
28-
public static final QueryPlanInfo.QueryPlanInfoKey<Integer> MAX_TASK_QUEUE_SIZE = new QueryPlanInfo.QueryPlanInfoKey<>("maxTaskQueueSize");
29-
public static final QueryPlanInfo.QueryPlanInfoKey<QueryPlanConstraint> CONSTRAINTS = new QueryPlanInfo.QueryPlanInfoKey<>("constraints");
29+
public static final QueryPlanInfo.QueryPlanInfoKey<Integer> TOTAL_TASK_COUNT =
30+
new QueryPlanInfo.QueryPlanInfoKey<>("totalTaskCount");
31+
public static final QueryPlanInfo.QueryPlanInfoKey<Integer> MAX_TASK_QUEUE_SIZE =
32+
new QueryPlanInfo.QueryPlanInfoKey<>("maxTaskQueueSize");
33+
public static final QueryPlanInfo.QueryPlanInfoKey<QueryPlanConstraint> CONSTRAINTS =
34+
new QueryPlanInfo.QueryPlanInfoKey<>("constraints");
35+
public static final QueryPlanInfo.QueryPlanInfoKey<StatsMaps> STATS_MAPS =
36+
new QueryPlanInfo.QueryPlanInfoKey<>("statsMaps");
3037

3138
private QueryPlanInfoKeys() {
3239
}

fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/CascadesPlanner.java

Lines changed: 35 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -318,6 +318,9 @@ public QueryPlanResult planQuery(@Nonnull final RecordQuery query, @Nonnull Para
318318
.put(QueryPlanInfoKeys.TOTAL_TASK_COUNT, taskCount)
319319
.put(QueryPlanInfoKeys.MAX_TASK_QUEUE_SIZE, maxQueueSize)
320320
.put(QueryPlanInfoKeys.CONSTRAINTS, constraints)
321+
.put(QueryPlanInfoKeys.STATS_MAPS,
322+
Debugger.getDebuggerMaybe().flatMap(Debugger::getStatsMaps)
323+
.orElse(null))
321324
.build();
322325
return new QueryPlanResult(plan, info);
323326
}
@@ -354,7 +357,13 @@ public QueryPlanResult planGraph(@Nonnull Supplier<Reference> referenceSupplier,
354357
evaluationContext);
355358
final var plan = resultOrFail();
356359
final var constraints = QueryPlanConstraint.collectConstraints(plan);
357-
return new QueryPlanResult(plan, QueryPlanInfo.newBuilder().put(QueryPlanInfoKeys.CONSTRAINTS, constraints).build());
360+
return new QueryPlanResult(plan,
361+
QueryPlanInfo.newBuilder()
362+
.put(QueryPlanInfoKeys.CONSTRAINTS, constraints)
363+
.put(QueryPlanInfoKeys.STATS_MAPS,
364+
Debugger.getDebuggerMaybe()
365+
.flatMap(Debugger::getStatsMaps).orElse(null))
366+
.build());
358367
} finally {
359368
Debugger.withDebugger(Debugger::onDone);
360369
}
@@ -391,33 +400,40 @@ private void planPartial(@Nonnull Supplier<Reference> referenceSupplier,
391400
maxQueueSize = 0;
392401
while (!taskStack.isEmpty()) {
393402
try {
394-
Debugger.withDebugger(debugger -> debugger.onEvent(new Debugger.ExecutingTaskEvent(currentRoot, taskStack, Objects.requireNonNull(taskStack.peek()))));
395403
if (isTaskTotalCountExceeded(configuration, taskCount)) {
396404
throw new RecordQueryPlanComplexityException("Maximum number of tasks (" + configuration.getMaxTotalTaskCount() + ") was exceeded");
397405
}
398406
taskCount++;
399407

408+
Debugger.withDebugger(debugger -> debugger.onEvent(
409+
new Debugger.ExecutingTaskEvent(currentRoot, taskStack, Location.BEGIN,
410+
Objects.requireNonNull(taskStack.peek()))));
400411
Task nextTask = taskStack.pop();
401-
if (logger.isTraceEnabled()) {
402-
logger.trace(KeyValueLogMessage.of("executing task", "nextTask", nextTask.toString()));
403-
}
404-
405-
Debugger.withDebugger(debugger -> debugger.onEvent(nextTask.toTaskEvent(Location.BEGIN)));
406412
try {
407-
nextTask.execute();
408-
} finally {
409-
Debugger.withDebugger(debugger -> debugger.onEvent(nextTask.toTaskEvent(Location.END)));
410-
}
413+
if (logger.isTraceEnabled()) {
414+
logger.trace(KeyValueLogMessage.of("executing task", "nextTask", nextTask.toString()));
415+
}
411416

412-
if (logger.isTraceEnabled()) {
413-
logger.trace(KeyValueLogMessage.of("planner state",
414-
"taskStackSize", taskStack.size(),
415-
"memo", new ReferencePrinter(currentRoot)));
416-
}
417+
Debugger.withDebugger(debugger -> debugger.onEvent(nextTask.toTaskEvent(Location.BEGIN)));
418+
try {
419+
nextTask.execute();
420+
} finally {
421+
Debugger.withDebugger(debugger -> debugger.onEvent(nextTask.toTaskEvent(Location.END)));
422+
}
417423

418-
maxQueueSize = Math.max(maxQueueSize, taskStack.size());
419-
if (isTaskQueueSizeExceeded(configuration, taskStack.size())) {
420-
throw new RecordQueryPlanComplexityException("Maximum task queue size (" + configuration.getMaxTaskQueueSize() + ") was exceeded");
424+
if (logger.isTraceEnabled()) {
425+
logger.trace(KeyValueLogMessage.of("planner state",
426+
"taskStackSize", taskStack.size(),
427+
"memo", new ReferencePrinter(currentRoot)));
428+
}
429+
430+
maxQueueSize = Math.max(maxQueueSize, taskStack.size());
431+
if (isTaskQueueSizeExceeded(configuration, taskStack.size())) {
432+
throw new RecordQueryPlanComplexityException("Maximum task queue size (" + configuration.getMaxTaskQueueSize() + ") was exceeded");
433+
}
434+
} finally {
435+
Debugger.withDebugger(debugger -> debugger.onEvent(
436+
new Debugger.ExecutingTaskEvent(currentRoot, taskStack, Location.END, nextTask)));
421437
}
422438
} catch (final RestartException restartException) {
423439
if (logger.isTraceEnabled()) {

fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/debug/Debugger.java

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,12 @@ static Debugger getDebugger() {
9797
return THREAD_LOCAL.get();
9898
}
9999

100+
@Nonnull
101+
static Optional<Debugger> getDebuggerMaybe() {
102+
final var debugger = getDebugger();
103+
return Optional.ofNullable(debugger);
104+
}
105+
100106
/**
101107
* Invoke the {@link Consumer} on the currently set debugger. Do not do anything if there is no debugger set.
102108
* @param action consumer to invoke
@@ -207,6 +213,9 @@ static Optional<Integer> getOrRegisterSingleton(Object singleton) {
207213
@SuppressWarnings("unused") // only used by debugger
208214
String showStats();
209215

216+
@Nonnull
217+
Optional<StatsMaps> getStatsMaps();
218+
210219
/**
211220
* Shorthands to identify a kind of event.
212221
*/
@@ -435,8 +444,9 @@ class ExecutingTaskEvent extends AbstractEventWithState {
435444

436445
public ExecutingTaskEvent(@Nonnull final Reference rootReference,
437446
@Nonnull final Deque<Task> taskStack,
447+
@Nonnull final Location location,
438448
@Nonnull final Task task) {
439-
super(rootReference, taskStack, Location.COUNT);
449+
super(rootReference, taskStack, location);
440450
this.task = task;
441451
}
442452

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
/*
2+
* Stats.java
3+
*
4+
* This source file is part of the FoundationDB open source project
5+
*
6+
* Copyright 2015-2025 Apple Inc. and the FoundationDB project authors
7+
*
8+
* Licensed under the Apache License, Version 2.0 (the "License");
9+
* you may not use this file except in compliance with the License.
10+
* You may obtain a copy of the License at
11+
*
12+
* http://www.apache.org/licenses/LICENSE-2.0
13+
*
14+
* Unless required by applicable law or agreed to in writing, software
15+
* distributed under the License is distributed on an "AS IS" BASIS,
16+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17+
* See the License for the specific language governing permissions and
18+
* limitations under the License.
19+
*/
20+
21+
package com.apple.foundationdb.record.query.plan.cascades.debug;
22+
23+
import com.google.common.collect.ImmutableMap;
24+
25+
import javax.annotation.Nonnull;
26+
import java.util.Map;
27+
28+
public class Stats {
29+
@Nonnull
30+
protected final Map<Debugger.Location, Long> locationCountMap;
31+
32+
protected long totalTimeInNs;
33+
protected long ownTimeInNs;
34+
35+
protected Stats(@Nonnull final Map<Debugger.Location, Long> locationCountMap,
36+
final long totalTimeInNs,
37+
final long ownTimeInNs) {
38+
this.locationCountMap = locationCountMap;
39+
this.totalTimeInNs = totalTimeInNs;
40+
this.ownTimeInNs = ownTimeInNs;
41+
}
42+
43+
@Nonnull
44+
public Map<Debugger.Location, Long> getLocationCountMap() {
45+
return locationCountMap;
46+
}
47+
48+
public long getCount(@Nonnull final Debugger.Location location) {
49+
return locationCountMap.getOrDefault(location, 0L);
50+
}
51+
52+
public long getTotalTimeInNs() {
53+
return totalTimeInNs;
54+
}
55+
56+
public long getOwnTimeInNs() {
57+
return ownTimeInNs;
58+
}
59+
60+
@Nonnull
61+
public Stats toImmutable() {
62+
return new Stats(ImmutableMap.copyOf(locationCountMap), totalTimeInNs, ownTimeInNs);
63+
}
64+
}
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
/*
2+
* StatsMaps.java
3+
*
4+
* This source file is part of the FoundationDB open source project
5+
*
6+
* Copyright 2015-2025 Apple Inc. and the FoundationDB project authors
7+
*
8+
* Licensed under the Apache License, Version 2.0 (the "License");
9+
* you may not use this file except in compliance with the License.
10+
* You may obtain a copy of the License at
11+
*
12+
* http://www.apache.org/licenses/LICENSE-2.0
13+
*
14+
* Unless required by applicable law or agreed to in writing, software
15+
* distributed under the License is distributed on an "AS IS" BASIS,
16+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17+
* See the License for the specific language governing permissions and
18+
* limitations under the License.
19+
*/
20+
21+
package com.apple.foundationdb.record.query.plan.cascades.debug;
22+
23+
import com.apple.foundationdb.record.query.plan.cascades.CascadesRule;
24+
import com.google.common.base.Suppliers;
25+
import com.google.common.collect.ImmutableMap;
26+
27+
import javax.annotation.Nonnull;
28+
import java.util.Map;
29+
import java.util.function.Supplier;
30+
31+
public class StatsMaps {
32+
@Nonnull
33+
private final Map<Class<? extends Debugger.Event>, ? extends Stats> eventClassStatsMap;
34+
@Nonnull
35+
private final Map<Class<? extends CascadesRule<?>>, ? extends Stats> plannerRuleClassStatsMap;
36+
37+
@Nonnull
38+
private final Supplier<Map<Class<? extends Debugger.Event>, Stats>> immutableEventClassStatsMapSupplier;
39+
@Nonnull
40+
private final Supplier<Map<Class<? extends CascadesRule<?>>, Stats>> immutablePlannerRuleClassStatsMapSupplier;
41+
42+
public StatsMaps(@Nonnull final Map<Class<? extends Debugger.Event>, ? extends Stats> eventClassStatsMap,
43+
@Nonnull final Map<Class<? extends CascadesRule<?>>, ? extends Stats> plannerRuleClassStatsMap) {
44+
this.eventClassStatsMap = eventClassStatsMap;
45+
this.plannerRuleClassStatsMap = plannerRuleClassStatsMap;
46+
this.immutableEventClassStatsMapSupplier = Suppliers.memoize(this::computeImmutableEventClassStatsMap);
47+
this.immutablePlannerRuleClassStatsMapSupplier = Suppliers.memoize(this::computeImmutablePlannerRuleClassStatsMap);
48+
}
49+
50+
@Nonnull
51+
public Map<Class<? extends Debugger.Event>, Stats> getEventClassStatsMap() {
52+
return immutableEventClassStatsMapSupplier.get();
53+
}
54+
55+
@Nonnull
56+
public Map<Class<? extends CascadesRule<?>>, Stats> getPlannerRuleClassStatsMap() {
57+
return immutablePlannerRuleClassStatsMapSupplier.get();
58+
}
59+
60+
@Nonnull
61+
private Map<Class<? extends Debugger.Event>, Stats> computeImmutableEventClassStatsMap() {
62+
final var eventClassStatsMapBuilder =
63+
ImmutableMap.<Class<? extends Debugger.Event>, Stats>builder();
64+
for (final var entry : eventClassStatsMap.entrySet()) {
65+
eventClassStatsMapBuilder.put(entry.getKey(), entry.getValue().toImmutable());
66+
}
67+
return eventClassStatsMapBuilder.build();
68+
}
69+
70+
@Nonnull
71+
private Map<Class<? extends CascadesRule<?>>, Stats> computeImmutablePlannerRuleClassStatsMap() {
72+
final var plannerRuleClassStatsMapBuilder =
73+
ImmutableMap.<Class<? extends CascadesRule<?>>, Stats>builder();
74+
for (final var entry : plannerRuleClassStatsMap.entrySet()) {
75+
plannerRuleClassStatsMapBuilder.put(entry.getKey(), entry.getValue().toImmutable());
76+
}
77+
return plannerRuleClassStatsMapBuilder.build();
78+
}
79+
}

0 commit comments

Comments
 (0)