From 12b5817799845aa3f75a147fb9015c23f349d9c4 Mon Sep 17 00:00:00 2001
From: Hazem Zaghloul
Date: Tue, 29 Jul 2025 18:34:11 +0100
Subject: [PATCH 1/9] Introduce new timers for the different phases of the
Cascades planner
This commit introduces new StoreTimer.Events that record how long each phase (rewriting, planning)
of the Cascades planner take. To achieve this, the Relational layer passes the instance of StoreTimer
within the current transaction to the Cascades planner upon creation, which it makes use of by recording
the time a phase took using a new Cascades task, `FinalizePlannerPhase`, which simply calls record on the
StoreTimer instance (if it is not `null`) with the total time the planner phase took. The
`FinalizePlannerPhase` task is schedueled to be executed by the `InitiatePlannerPhase` task, ensuring that
it is the last task that is run for that phase.
In addition, the logs for queries (which happen if debug logging is enabled or if a query is too slow) is enhanced
to include the durations for the two planner phases.
---
.../query/plan/cascades/CascadesPlanner.java | 46 +++++++++++++++++
.../query/plan/cascades/PlannerPhase.java | 18 +++++--
.../plan/cascades/PlannerTimerEvents.java | 49 +++++++++++++++++++
.../query/plan/cascades/debug/Debugger.java | 40 ++++++++++++++-
.../src/main/proto/planner_debugger.proto | 5 ++
.../benchmark/BenchmarkRecordStore.java | 2 +-
.../api/metrics/MetricCollector.java | 14 ++++++
.../metric/RecordLayerMetricCollector.java | 12 ++---
.../recordlayer/query/PlanGenerator.java | 7 ++-
.../util/RelationalLoggingUtil.java | 15 +++++-
.../recordlayer/QueryLoggingTest.java | 11 +++++
11 files changed, 203 insertions(+), 16 deletions(-)
create mode 100644 fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/PlannerTimerEvents.java
diff --git a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/CascadesPlanner.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/CascadesPlanner.java
index fb9f7b51a0..1100205c9f 100644
--- a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/CascadesPlanner.java
+++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/CascadesPlanner.java
@@ -26,6 +26,7 @@
import com.apple.foundationdb.record.RecordStoreState;
import com.apple.foundationdb.record.logging.KeyValueLogMessage;
import com.apple.foundationdb.record.logging.LogMessageKeys;
+import com.apple.foundationdb.record.provider.common.StoreTimer;
import com.apple.foundationdb.record.query.IndexQueryabilityFilter;
import com.apple.foundationdb.record.query.ParameterRelationshipGraph;
import com.apple.foundationdb.record.query.RecordQuery;
@@ -55,6 +56,7 @@
import org.slf4j.LoggerFactory;
import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
import java.util.ArrayDeque;
import java.util.Collection;
import java.util.Deque;
@@ -212,6 +214,8 @@ public class CascadesPlanner implements QueryPlanner {
private final RecordMetaData metaData;
@Nonnull
private final RecordStoreState recordStoreState;
+ @Nullable
+ private final StoreTimer storeTimer;
@Nonnull
private Reference currentRoot;
@Nonnull
@@ -228,9 +232,14 @@ public class CascadesPlanner implements QueryPlanner {
private int maxQueueSize;
public CascadesPlanner(@Nonnull RecordMetaData metaData, @Nonnull RecordStoreState recordStoreState) {
+ this(metaData, recordStoreState, null);
+ }
+
+ public CascadesPlanner(@Nonnull RecordMetaData metaData, @Nonnull RecordStoreState recordStoreState, @Nullable StoreTimer storeTimer) {
this.configuration = RecordQueryPlannerConfiguration.builder().build();
this.metaData = metaData;
this.recordStoreState = recordStoreState;
+ this.storeTimer = storeTimer;
// Placeholders until we get a query.
this.currentRoot = Reference.empty();
this.planContext = PlanContext.emptyContext();
@@ -524,6 +533,41 @@ public interface Task {
Debugger.Event toTaskEvent(Location location);
}
+ private class FinalizePlannerPhase implements Task {
+ @Nonnull
+ private final PlannerPhase plannerPhase;
+ private final long phaseStartTime;
+
+ public FinalizePlannerPhase(@Nonnull final PlannerPhase plannerPhase, long phaseStartTime) {
+ this.plannerPhase = plannerPhase;
+ this.phaseStartTime = phaseStartTime;
+ }
+
+ @Override
+ @Nonnull
+ public PlannerPhase getPlannerPhase() {
+ return plannerPhase;
+ }
+
+ @Override
+ public void execute() {
+ if (storeTimer != null) {
+ storeTimer.record(plannerPhase.getPhaseCompletionTimerEvent(), System.nanoTime() - phaseStartTime);
+ }
+ }
+
+ @Override
+ @Nonnull
+ public Debugger.Event toTaskEvent(final Location location) {
+ return new Debugger.FinalizePlannerPhaseEvent(plannerPhase, currentRoot, taskStack, location);
+ }
+
+ @Override
+ public String toString() {
+ return "FinalizePlannerPhase(" + plannerPhase.name() + ")";
+ }
+ }
+
/**
* Globally initiate a new planner phase.
* Simplified push/execute overview:
@@ -552,10 +596,12 @@ public PlannerPhase getPlannerPhase() {
@Override
public void execute() {
+ var phaseStartTime = System.nanoTime();
if (plannerPhase.hasNextPhase()) {
// if there is another phase push it first so it gets executed at the very end
taskStack.push(new InitiatePlannerPhase(plannerPhase.getNextPhase()));
}
+ taskStack.push(new FinalizePlannerPhase(plannerPhase, phaseStartTime));
taskStack.push(new OptimizeGroup(plannerPhase, currentRoot));
taskStack.push(new ExploreGroup(plannerPhase, currentRoot));
}
diff --git a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/PlannerPhase.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/PlannerPhase.java
index 0389239812..7c5e366851 100644
--- a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/PlannerPhase.java
+++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/PlannerPhase.java
@@ -35,8 +35,8 @@
*/
public enum PlannerPhase {
// note that the phase are declared in a counterintuitive inverse way since a phase has to specify the next phase
- PLANNING(PlanningRuleSet.getDefault(), PlannerStage.PLANNED, PlanningCostModel::new),
- REWRITING(RewritingRuleSet.getDefault(), PlannerStage.CANONICAL, RewritingCostModel::new, PLANNING);
+ PLANNING(PlanningRuleSet.getDefault(), PlannerStage.PLANNED, PlanningCostModel::new, PlannerTimerEvents.PLANNING_PHASE_COMPLETE),
+ REWRITING(RewritingRuleSet.getDefault(), PlannerStage.CANONICAL, RewritingCostModel::new, PlannerTimerEvents.REWRITING_PHASE_COMPLETE, PLANNING);
@Nonnull
private final CascadesRuleSet ruleSet;
@@ -46,21 +46,26 @@ public enum PlannerPhase {
private final Function costModelCreator;
@Nullable
private final PlannerPhase nextPhase;
+ @Nonnull
+ private final PlannerTimerEvents phaseCompletionTimerEvent;
PlannerPhase(@Nonnull final CascadesRuleSet ruleSet,
@Nonnull final PlannerStage targetStage,
- @Nonnull final Function costModelCreator) {
- this(ruleSet, targetStage, costModelCreator, null);
+ @Nonnull final Function costModelCreator,
+ @Nonnull final PlannerTimerEvents phaseCompletionTimerEvent) {
+ this(ruleSet, targetStage, costModelCreator, phaseCompletionTimerEvent, null);
}
PlannerPhase(@Nonnull final CascadesRuleSet ruleSet,
@Nonnull final PlannerStage targetStage,
@Nonnull final Function costModelCreator,
+ @Nonnull final PlannerTimerEvents phaseCompletionTimerEvent,
@Nullable final PlannerPhase nextPhase) {
this.ruleSet = ruleSet;
this.targetStage = targetStage;
this.costModelCreator = costModelCreator;
this.nextPhase = nextPhase;
+ this.phaseCompletionTimerEvent = phaseCompletionTimerEvent;
}
@Nonnull
@@ -87,6 +92,11 @@ public boolean hasNextPhase() {
return nextPhase != null;
}
+ @Nonnull
+ public PlannerTimerEvents getPhaseCompletionTimerEvent() {
+ return phaseCompletionTimerEvent;
+ }
+
@Nonnull
public PPlannerPhase toProto() {
switch (this) {
diff --git a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/PlannerTimerEvents.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/PlannerTimerEvents.java
new file mode 100644
index 0000000000..8e2dbf3f37
--- /dev/null
+++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/PlannerTimerEvents.java
@@ -0,0 +1,49 @@
+/*
+ * PlannerTimerEvents.java
+ *
+ * This source file is part of the FoundationDB open source project
+ *
+ * Copyright 2015-2025 Apple Inc. and the FoundationDB project authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.apple.foundationdb.record.query.plan.cascades;
+
+import com.apple.foundationdb.record.provider.common.StoreTimer;
+
+import javax.annotation.Nonnull;
+
+public enum PlannerTimerEvents implements StoreTimer.Event {
+ REWRITING_PHASE_COMPLETE("complete the rewriting phase of the planner"),
+ PLANNING_PHASE_COMPLETE("complete the planning phase of the planner");
+
+ private final String title;
+ private final String logKey;
+
+ PlannerTimerEvents(String title) {
+ this.title = title;
+ this.logKey = StoreTimer.Event.super.logKey();
+ }
+
+ @Override
+ public String title() {
+ return title;
+ }
+
+ @Override
+ @Nonnull
+ public String logKey() {
+ return this.logKey;
+ }
+}
diff --git a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/debug/Debugger.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/debug/Debugger.java
index 5ec77a5df2..9cf417f3e1 100644
--- a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/debug/Debugger.java
+++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/debug/Debugger.java
@@ -36,6 +36,7 @@
import com.apple.foundationdb.record.query.plan.cascades.debug.eventprotos.PExecutingTaskEvent;
import com.apple.foundationdb.record.query.plan.cascades.debug.eventprotos.PExploreExpressionEvent;
import com.apple.foundationdb.record.query.plan.cascades.debug.eventprotos.PExploreGroupEvent;
+import com.apple.foundationdb.record.query.plan.cascades.debug.eventprotos.PFinalizePlannerPhaseEvent;
import com.apple.foundationdb.record.query.plan.cascades.debug.eventprotos.PInitiatePlannerPhaseEvent;
import com.apple.foundationdb.record.query.plan.cascades.debug.eventprotos.PInsertIntoMemoEvent;
import com.apple.foundationdb.record.query.plan.cascades.debug.eventprotos.PMatchPartition;
@@ -260,7 +261,8 @@ enum Shorthand {
TRANSFORM,
INSERT_INTO_MEMO,
TRANSLATE_CORRELATIONS,
- INITPHASE
+ INITPHASE,
+ FINALIZEPHASE
}
/**
@@ -529,6 +531,42 @@ public PEvent.Builder toEventBuilder() {
}
}
+ class FinalizePlannerPhaseEvent extends AbstractEventWithState {
+ public FinalizePlannerPhaseEvent(@Nonnull final PlannerPhase plannerPhase,
+ @Nonnull final Reference rootReference,
+ @Nonnull final Deque taskStack,
+ @Nonnull final Location location) {
+ super(plannerPhase, rootReference, taskStack, location);
+ }
+
+ @Override
+ @Nonnull
+ public String getDescription() {
+ return "finalizing planner phase " + getPlannerPhase().name();
+ }
+
+ @Override
+ @Nonnull
+ public Shorthand getShorthand() {
+ return Shorthand.FINALIZEPHASE;
+ }
+
+ @Nonnull
+ @Override
+ public PFinalizePlannerPhaseEvent toProto() {
+ return PFinalizePlannerPhaseEvent.newBuilder()
+ .setSuper(toAbstractEventWithStateProto())
+ .build();
+ }
+
+ @Nonnull
+ @Override
+ public PEvent.Builder toEventBuilder() {
+ return PEvent.newBuilder()
+ .setFinalizePlannerPhaseEvent(toProto());
+ }
+ }
+
/**
* Events of this class are generated every time the planner executes a task.
*/
diff --git a/fdb-record-layer-core/src/main/proto/planner_debugger.proto b/fdb-record-layer-core/src/main/proto/planner_debugger.proto
index 223221f338..4d6fc8a234 100644
--- a/fdb-record-layer-core/src/main/proto/planner_debugger.proto
+++ b/fdb-record-layer-core/src/main/proto/planner_debugger.proto
@@ -41,6 +41,7 @@ message PEvent {
PInsertIntoMemoEvent insert_into_memo_event = 11;
PTranslateCorrelationsEvent translate_correlations_event = 12;
PInitiatePlannerPhaseEvent initiate_planner_phase_event = 13;
+ PFinalizePlannerPhaseEvent finalize_planner_phase_event = 14;
}
}
@@ -105,6 +106,10 @@ message PInitiatePlannerPhaseEvent {
optional PAbstractEventWithState super = 1;
}
+message PFinalizePlannerPhaseEvent {
+ optional PAbstractEventWithState super = 1;
+}
+
message PInsertIntoMemoEvent {
optional string location = 1;
optional PRegisteredRelationalExpression expression = 2;
diff --git a/fdb-record-layer-jmh/src/jmh/java/com/apple/foundationdb/record/benchmark/BenchmarkRecordStore.java b/fdb-record-layer-jmh/src/jmh/java/com/apple/foundationdb/record/benchmark/BenchmarkRecordStore.java
index b201deb4c3..6c7e4af89e 100644
--- a/fdb-record-layer-jmh/src/jmh/java/com/apple/foundationdb/record/benchmark/BenchmarkRecordStore.java
+++ b/fdb-record-layer-jmh/src/jmh/java/com/apple/foundationdb/record/benchmark/BenchmarkRecordStore.java
@@ -168,7 +168,7 @@ public QueryPlanner planner(@Nonnull BenchmarkTimer timer, boolean cascades) {
final RecordMetaData recordMetaData = recordStoreBuilder.getMetaDataProvider().getRecordMetaData();
final RecordStoreState recordStoreState = new RecordStoreState(null, null);
if (cascades) {
- return new CascadesPlanner(recordMetaData, recordStoreState);
+ return new CascadesPlanner(recordMetaData, recordStoreState, timer.getTimer());
} else {
return new RecordQueryPlanner(recordMetaData, recordStoreState, timer.getTimer());
}
diff --git a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/api/metrics/MetricCollector.java b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/api/metrics/MetricCollector.java
index 26bf94d5dc..42ce4ae991 100644
--- a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/api/metrics/MetricCollector.java
+++ b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/api/metrics/MetricCollector.java
@@ -20,12 +20,15 @@
package com.apple.foundationdb.relational.api.metrics;
+import com.apple.foundationdb.record.provider.common.StoreTimer;
import com.apple.foundationdb.relational.api.exceptions.ErrorCode;
import com.apple.foundationdb.relational.api.exceptions.RelationalException;
import com.apple.foundationdb.relational.util.Supplier;
import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
import java.util.Locale;
+import java.util.Optional;
/**
* MetricCollector provides a utility API to register events and counts while performing operations in the Relational
@@ -96,4 +99,15 @@ default boolean hasCounter(@Nonnull RelationalMetric.RelationalCount count) {
* */
default void flush() {
}
+
+ /**
+ * Return an instance of {@link com.apple.foundationdb.record.provider.common.StoreTimer} that corresponds to
+ * this metric collector, if possible.
+ *
+ * @return an instance of {@link com.apple.foundationdb.record.provider.common.StoreTimer} or an empty {@link Optional}
+ */
+ @Nullable
+ default StoreTimer getRecordLayerStoreTimer() {
+ return null;
+ }
}
diff --git a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/metric/RecordLayerMetricCollector.java b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/metric/RecordLayerMetricCollector.java
index 051ee46ef2..8877181deb 100644
--- a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/metric/RecordLayerMetricCollector.java
+++ b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/metric/RecordLayerMetricCollector.java
@@ -31,8 +31,6 @@
import com.apple.foundationdb.relational.util.Assert;
import com.apple.foundationdb.relational.util.Supplier;
-import com.google.common.annotations.VisibleForTesting;
-
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.Objects;
@@ -98,12 +96,12 @@ public boolean hasCounter(@Nonnull RelationalMetric.RelationalCount count) {
@Nullable
private StoreTimer.Counter getCounter(@Nonnull RelationalMetric.RelationalCount count) {
- return getUnderlyingStoreTimer().getCounter(count);
+ return Objects.requireNonNull(getRecordLayerStoreTimer()).getCounter(count);
}
- @Nonnull
- @VisibleForTesting
- public StoreTimer getUnderlyingStoreTimer() {
- return Objects.requireNonNull(context.getTimer());
+ @Override
+ @Nullable
+ public StoreTimer getRecordLayerStoreTimer() {
+ return context.getTimer();
}
}
diff --git a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/PlanGenerator.java b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/PlanGenerator.java
index 141b2d4053..187dd07dfb 100644
--- a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/PlanGenerator.java
+++ b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/PlanGenerator.java
@@ -126,7 +126,7 @@ public Plan> getPlan(@Nonnull final String query) throws RelationalException {
exception = e;
throw e;
} finally {
- RelationalLoggingUtil.publishPlanGenerationLogs(logger, message, plan, exception, totalTimeMicros(), options);
+ RelationalLoggingUtil.publishPlanGenerationLogs(logger, message, plan, exception, planContext.getMetricsCollector().getRecordLayerStoreTimer(), totalTimeMicros(), options);
}
return plan;
}
@@ -390,7 +390,10 @@ private static boolean shouldNotCache(@Nonnull final Set plan,
- @Nullable RelationalException e, long totalTime, Options options) {
+ @Nullable RelationalException e, @Nullable StoreTimer storeTimer,
+ long totalTime, Options options) {
final boolean logQuery = options.getOption(Options.Name.LOG_QUERY);
final boolean isSlow = totalTime > (long) options.getOption(Options.Name.LOG_SLOW_QUERY_THRESHOLD_MICROS);
+
+ if (storeTimer != null) {
+ Optional.ofNullable(storeTimer.getCounter(PlannerTimerEvents.REWRITING_PHASE_COMPLETE)).ifPresent(
+ (c) -> message.addKeyAndValue("plannerRewritingPhaseTimeMicros", TimeUnit.NANOSECONDS.toMicros(c.getTimeNanos())));
+ Optional.ofNullable(storeTimer.getCounter(PlannerTimerEvents.PLANNING_PHASE_COMPLETE)).ifPresent(
+ (c) -> message.addKeyAndValue("plannerPlanningPhaseTimeMicros", TimeUnit.NANOSECONDS.toMicros(c.getTimeNanos())));
+ }
+
message.addKeyAndValue("totalPlanTimeMicros", totalTime);
if (plan != null) {
if (plan instanceof QueryPlan.PhysicalQueryPlan) {
diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/QueryLoggingTest.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/QueryLoggingTest.java
index 00f82ff89d..d73a23f74c 100644
--- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/QueryLoggingTest.java
+++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/QueryLoggingTest.java
@@ -359,6 +359,17 @@ void testLogPlanHash() throws Exception {
Assertions.assertThat(logAppender.getLastLogEventMessage()).contains("planHash=\"-1179946538\"");
}
+ @Test
+ void testLogQueryLogsPlanningDurations() throws Exception {
+ try (final RelationalResultSet resultSet = statement.executeQuery("SELECT * FROM RESTAURANT where rest_no = 34 OPTIONS (LOG QUERY)")) {
+ resultSet.next();
+ }
+ Assertions.assertThat(logAppender.getLastLogEventMessage()).containsPattern("plannerRewritingPhaseTimeMicros=\"\\d+\"");
+ Assertions.assertThat(logAppender.getLastLogEventMessage()).containsPattern("plannerPlanningPhaseTimeMicros=\"\\d+\"");
+ Assertions.assertThat(logAppender.getLastLogEventMessage()).containsPattern("totalPlanTimeMicros=\"\\d+\"");
+
+ }
+
@Test
void testLogException() {
Assert.assertThrows(Exception.class, () -> statement.executeQuery("SELECT * FROM REST where rest_no = 0 OPTIONS (LOG QUERY)"));
From 260e2b122b77731db97456a4b17b547a7cbcd54f Mon Sep 17 00:00:00 2001
From: Hazem Zaghloul
Date: Thu, 7 Aug 2025 15:28:29 +0100
Subject: [PATCH 2/9] Revert "Introduce new timers for the different phases of
the Cascades planner"
This reverts commit 7e8cc8e68ab977deb73b5fe8a87aa835bd599a20.
---
.../query/plan/cascades/CascadesPlanner.java | 46 -----------------
.../query/plan/cascades/PlannerPhase.java | 18 ++-----
.../plan/cascades/PlannerTimerEvents.java | 49 -------------------
.../query/plan/cascades/debug/Debugger.java | 40 +--------------
.../src/main/proto/planner_debugger.proto | 5 --
.../benchmark/BenchmarkRecordStore.java | 2 +-
.../api/metrics/MetricCollector.java | 14 ------
.../metric/RecordLayerMetricCollector.java | 12 +++--
.../recordlayer/query/PlanGenerator.java | 7 +--
.../util/RelationalLoggingUtil.java | 15 +-----
.../recordlayer/QueryLoggingTest.java | 11 -----
11 files changed, 16 insertions(+), 203 deletions(-)
delete mode 100644 fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/PlannerTimerEvents.java
diff --git a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/CascadesPlanner.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/CascadesPlanner.java
index 1100205c9f..fb9f7b51a0 100644
--- a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/CascadesPlanner.java
+++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/CascadesPlanner.java
@@ -26,7 +26,6 @@
import com.apple.foundationdb.record.RecordStoreState;
import com.apple.foundationdb.record.logging.KeyValueLogMessage;
import com.apple.foundationdb.record.logging.LogMessageKeys;
-import com.apple.foundationdb.record.provider.common.StoreTimer;
import com.apple.foundationdb.record.query.IndexQueryabilityFilter;
import com.apple.foundationdb.record.query.ParameterRelationshipGraph;
import com.apple.foundationdb.record.query.RecordQuery;
@@ -56,7 +55,6 @@
import org.slf4j.LoggerFactory;
import javax.annotation.Nonnull;
-import javax.annotation.Nullable;
import java.util.ArrayDeque;
import java.util.Collection;
import java.util.Deque;
@@ -214,8 +212,6 @@ public class CascadesPlanner implements QueryPlanner {
private final RecordMetaData metaData;
@Nonnull
private final RecordStoreState recordStoreState;
- @Nullable
- private final StoreTimer storeTimer;
@Nonnull
private Reference currentRoot;
@Nonnull
@@ -232,14 +228,9 @@ public class CascadesPlanner implements QueryPlanner {
private int maxQueueSize;
public CascadesPlanner(@Nonnull RecordMetaData metaData, @Nonnull RecordStoreState recordStoreState) {
- this(metaData, recordStoreState, null);
- }
-
- public CascadesPlanner(@Nonnull RecordMetaData metaData, @Nonnull RecordStoreState recordStoreState, @Nullable StoreTimer storeTimer) {
this.configuration = RecordQueryPlannerConfiguration.builder().build();
this.metaData = metaData;
this.recordStoreState = recordStoreState;
- this.storeTimer = storeTimer;
// Placeholders until we get a query.
this.currentRoot = Reference.empty();
this.planContext = PlanContext.emptyContext();
@@ -533,41 +524,6 @@ public interface Task {
Debugger.Event toTaskEvent(Location location);
}
- private class FinalizePlannerPhase implements Task {
- @Nonnull
- private final PlannerPhase plannerPhase;
- private final long phaseStartTime;
-
- public FinalizePlannerPhase(@Nonnull final PlannerPhase plannerPhase, long phaseStartTime) {
- this.plannerPhase = plannerPhase;
- this.phaseStartTime = phaseStartTime;
- }
-
- @Override
- @Nonnull
- public PlannerPhase getPlannerPhase() {
- return plannerPhase;
- }
-
- @Override
- public void execute() {
- if (storeTimer != null) {
- storeTimer.record(plannerPhase.getPhaseCompletionTimerEvent(), System.nanoTime() - phaseStartTime);
- }
- }
-
- @Override
- @Nonnull
- public Debugger.Event toTaskEvent(final Location location) {
- return new Debugger.FinalizePlannerPhaseEvent(plannerPhase, currentRoot, taskStack, location);
- }
-
- @Override
- public String toString() {
- return "FinalizePlannerPhase(" + plannerPhase.name() + ")";
- }
- }
-
/**
* Globally initiate a new planner phase.
* Simplified push/execute overview:
@@ -596,12 +552,10 @@ public PlannerPhase getPlannerPhase() {
@Override
public void execute() {
- var phaseStartTime = System.nanoTime();
if (plannerPhase.hasNextPhase()) {
// if there is another phase push it first so it gets executed at the very end
taskStack.push(new InitiatePlannerPhase(plannerPhase.getNextPhase()));
}
- taskStack.push(new FinalizePlannerPhase(plannerPhase, phaseStartTime));
taskStack.push(new OptimizeGroup(plannerPhase, currentRoot));
taskStack.push(new ExploreGroup(plannerPhase, currentRoot));
}
diff --git a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/PlannerPhase.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/PlannerPhase.java
index 7c5e366851..0389239812 100644
--- a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/PlannerPhase.java
+++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/PlannerPhase.java
@@ -35,8 +35,8 @@
*/
public enum PlannerPhase {
// note that the phase are declared in a counterintuitive inverse way since a phase has to specify the next phase
- PLANNING(PlanningRuleSet.getDefault(), PlannerStage.PLANNED, PlanningCostModel::new, PlannerTimerEvents.PLANNING_PHASE_COMPLETE),
- REWRITING(RewritingRuleSet.getDefault(), PlannerStage.CANONICAL, RewritingCostModel::new, PlannerTimerEvents.REWRITING_PHASE_COMPLETE, PLANNING);
+ PLANNING(PlanningRuleSet.getDefault(), PlannerStage.PLANNED, PlanningCostModel::new),
+ REWRITING(RewritingRuleSet.getDefault(), PlannerStage.CANONICAL, RewritingCostModel::new, PLANNING);
@Nonnull
private final CascadesRuleSet ruleSet;
@@ -46,26 +46,21 @@ public enum PlannerPhase {
private final Function costModelCreator;
@Nullable
private final PlannerPhase nextPhase;
- @Nonnull
- private final PlannerTimerEvents phaseCompletionTimerEvent;
PlannerPhase(@Nonnull final CascadesRuleSet ruleSet,
@Nonnull final PlannerStage targetStage,
- @Nonnull final Function costModelCreator,
- @Nonnull final PlannerTimerEvents phaseCompletionTimerEvent) {
- this(ruleSet, targetStage, costModelCreator, phaseCompletionTimerEvent, null);
+ @Nonnull final Function costModelCreator) {
+ this(ruleSet, targetStage, costModelCreator, null);
}
PlannerPhase(@Nonnull final CascadesRuleSet ruleSet,
@Nonnull final PlannerStage targetStage,
@Nonnull final Function costModelCreator,
- @Nonnull final PlannerTimerEvents phaseCompletionTimerEvent,
@Nullable final PlannerPhase nextPhase) {
this.ruleSet = ruleSet;
this.targetStage = targetStage;
this.costModelCreator = costModelCreator;
this.nextPhase = nextPhase;
- this.phaseCompletionTimerEvent = phaseCompletionTimerEvent;
}
@Nonnull
@@ -92,11 +87,6 @@ public boolean hasNextPhase() {
return nextPhase != null;
}
- @Nonnull
- public PlannerTimerEvents getPhaseCompletionTimerEvent() {
- return phaseCompletionTimerEvent;
- }
-
@Nonnull
public PPlannerPhase toProto() {
switch (this) {
diff --git a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/PlannerTimerEvents.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/PlannerTimerEvents.java
deleted file mode 100644
index 8e2dbf3f37..0000000000
--- a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/PlannerTimerEvents.java
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * PlannerTimerEvents.java
- *
- * This source file is part of the FoundationDB open source project
- *
- * Copyright 2015-2025 Apple Inc. and the FoundationDB project authors
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.apple.foundationdb.record.query.plan.cascades;
-
-import com.apple.foundationdb.record.provider.common.StoreTimer;
-
-import javax.annotation.Nonnull;
-
-public enum PlannerTimerEvents implements StoreTimer.Event {
- REWRITING_PHASE_COMPLETE("complete the rewriting phase of the planner"),
- PLANNING_PHASE_COMPLETE("complete the planning phase of the planner");
-
- private final String title;
- private final String logKey;
-
- PlannerTimerEvents(String title) {
- this.title = title;
- this.logKey = StoreTimer.Event.super.logKey();
- }
-
- @Override
- public String title() {
- return title;
- }
-
- @Override
- @Nonnull
- public String logKey() {
- return this.logKey;
- }
-}
diff --git a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/debug/Debugger.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/debug/Debugger.java
index 9cf417f3e1..5ec77a5df2 100644
--- a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/debug/Debugger.java
+++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/debug/Debugger.java
@@ -36,7 +36,6 @@
import com.apple.foundationdb.record.query.plan.cascades.debug.eventprotos.PExecutingTaskEvent;
import com.apple.foundationdb.record.query.plan.cascades.debug.eventprotos.PExploreExpressionEvent;
import com.apple.foundationdb.record.query.plan.cascades.debug.eventprotos.PExploreGroupEvent;
-import com.apple.foundationdb.record.query.plan.cascades.debug.eventprotos.PFinalizePlannerPhaseEvent;
import com.apple.foundationdb.record.query.plan.cascades.debug.eventprotos.PInitiatePlannerPhaseEvent;
import com.apple.foundationdb.record.query.plan.cascades.debug.eventprotos.PInsertIntoMemoEvent;
import com.apple.foundationdb.record.query.plan.cascades.debug.eventprotos.PMatchPartition;
@@ -261,8 +260,7 @@ enum Shorthand {
TRANSFORM,
INSERT_INTO_MEMO,
TRANSLATE_CORRELATIONS,
- INITPHASE,
- FINALIZEPHASE
+ INITPHASE
}
/**
@@ -531,42 +529,6 @@ public PEvent.Builder toEventBuilder() {
}
}
- class FinalizePlannerPhaseEvent extends AbstractEventWithState {
- public FinalizePlannerPhaseEvent(@Nonnull final PlannerPhase plannerPhase,
- @Nonnull final Reference rootReference,
- @Nonnull final Deque taskStack,
- @Nonnull final Location location) {
- super(plannerPhase, rootReference, taskStack, location);
- }
-
- @Override
- @Nonnull
- public String getDescription() {
- return "finalizing planner phase " + getPlannerPhase().name();
- }
-
- @Override
- @Nonnull
- public Shorthand getShorthand() {
- return Shorthand.FINALIZEPHASE;
- }
-
- @Nonnull
- @Override
- public PFinalizePlannerPhaseEvent toProto() {
- return PFinalizePlannerPhaseEvent.newBuilder()
- .setSuper(toAbstractEventWithStateProto())
- .build();
- }
-
- @Nonnull
- @Override
- public PEvent.Builder toEventBuilder() {
- return PEvent.newBuilder()
- .setFinalizePlannerPhaseEvent(toProto());
- }
- }
-
/**
* Events of this class are generated every time the planner executes a task.
*/
diff --git a/fdb-record-layer-core/src/main/proto/planner_debugger.proto b/fdb-record-layer-core/src/main/proto/planner_debugger.proto
index 4d6fc8a234..223221f338 100644
--- a/fdb-record-layer-core/src/main/proto/planner_debugger.proto
+++ b/fdb-record-layer-core/src/main/proto/planner_debugger.proto
@@ -41,7 +41,6 @@ message PEvent {
PInsertIntoMemoEvent insert_into_memo_event = 11;
PTranslateCorrelationsEvent translate_correlations_event = 12;
PInitiatePlannerPhaseEvent initiate_planner_phase_event = 13;
- PFinalizePlannerPhaseEvent finalize_planner_phase_event = 14;
}
}
@@ -106,10 +105,6 @@ message PInitiatePlannerPhaseEvent {
optional PAbstractEventWithState super = 1;
}
-message PFinalizePlannerPhaseEvent {
- optional PAbstractEventWithState super = 1;
-}
-
message PInsertIntoMemoEvent {
optional string location = 1;
optional PRegisteredRelationalExpression expression = 2;
diff --git a/fdb-record-layer-jmh/src/jmh/java/com/apple/foundationdb/record/benchmark/BenchmarkRecordStore.java b/fdb-record-layer-jmh/src/jmh/java/com/apple/foundationdb/record/benchmark/BenchmarkRecordStore.java
index 6c7e4af89e..b201deb4c3 100644
--- a/fdb-record-layer-jmh/src/jmh/java/com/apple/foundationdb/record/benchmark/BenchmarkRecordStore.java
+++ b/fdb-record-layer-jmh/src/jmh/java/com/apple/foundationdb/record/benchmark/BenchmarkRecordStore.java
@@ -168,7 +168,7 @@ public QueryPlanner planner(@Nonnull BenchmarkTimer timer, boolean cascades) {
final RecordMetaData recordMetaData = recordStoreBuilder.getMetaDataProvider().getRecordMetaData();
final RecordStoreState recordStoreState = new RecordStoreState(null, null);
if (cascades) {
- return new CascadesPlanner(recordMetaData, recordStoreState, timer.getTimer());
+ return new CascadesPlanner(recordMetaData, recordStoreState);
} else {
return new RecordQueryPlanner(recordMetaData, recordStoreState, timer.getTimer());
}
diff --git a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/api/metrics/MetricCollector.java b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/api/metrics/MetricCollector.java
index 42ce4ae991..26bf94d5dc 100644
--- a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/api/metrics/MetricCollector.java
+++ b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/api/metrics/MetricCollector.java
@@ -20,15 +20,12 @@
package com.apple.foundationdb.relational.api.metrics;
-import com.apple.foundationdb.record.provider.common.StoreTimer;
import com.apple.foundationdb.relational.api.exceptions.ErrorCode;
import com.apple.foundationdb.relational.api.exceptions.RelationalException;
import com.apple.foundationdb.relational.util.Supplier;
import javax.annotation.Nonnull;
-import javax.annotation.Nullable;
import java.util.Locale;
-import java.util.Optional;
/**
* MetricCollector provides a utility API to register events and counts while performing operations in the Relational
@@ -99,15 +96,4 @@ default boolean hasCounter(@Nonnull RelationalMetric.RelationalCount count) {
* */
default void flush() {
}
-
- /**
- * Return an instance of {@link com.apple.foundationdb.record.provider.common.StoreTimer} that corresponds to
- * this metric collector, if possible.
- *
- * @return an instance of {@link com.apple.foundationdb.record.provider.common.StoreTimer} or an empty {@link Optional}
- */
- @Nullable
- default StoreTimer getRecordLayerStoreTimer() {
- return null;
- }
}
diff --git a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/metric/RecordLayerMetricCollector.java b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/metric/RecordLayerMetricCollector.java
index 8877181deb..051ee46ef2 100644
--- a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/metric/RecordLayerMetricCollector.java
+++ b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/metric/RecordLayerMetricCollector.java
@@ -31,6 +31,8 @@
import com.apple.foundationdb.relational.util.Assert;
import com.apple.foundationdb.relational.util.Supplier;
+import com.google.common.annotations.VisibleForTesting;
+
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.Objects;
@@ -96,12 +98,12 @@ public boolean hasCounter(@Nonnull RelationalMetric.RelationalCount count) {
@Nullable
private StoreTimer.Counter getCounter(@Nonnull RelationalMetric.RelationalCount count) {
- return Objects.requireNonNull(getRecordLayerStoreTimer()).getCounter(count);
+ return getUnderlyingStoreTimer().getCounter(count);
}
- @Override
- @Nullable
- public StoreTimer getRecordLayerStoreTimer() {
- return context.getTimer();
+ @Nonnull
+ @VisibleForTesting
+ public StoreTimer getUnderlyingStoreTimer() {
+ return Objects.requireNonNull(context.getTimer());
}
}
diff --git a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/PlanGenerator.java b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/PlanGenerator.java
index 187dd07dfb..141b2d4053 100644
--- a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/PlanGenerator.java
+++ b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/PlanGenerator.java
@@ -126,7 +126,7 @@ public Plan> getPlan(@Nonnull final String query) throws RelationalException {
exception = e;
throw e;
} finally {
- RelationalLoggingUtil.publishPlanGenerationLogs(logger, message, plan, exception, planContext.getMetricsCollector().getRecordLayerStoreTimer(), totalTimeMicros(), options);
+ RelationalLoggingUtil.publishPlanGenerationLogs(logger, message, plan, exception, totalTimeMicros(), options);
}
return plan;
}
@@ -390,10 +390,7 @@ private static boolean shouldNotCache(@Nonnull final Set plan,
- @Nullable RelationalException e, @Nullable StoreTimer storeTimer,
- long totalTime, Options options) {
+ @Nullable RelationalException e, long totalTime, Options options) {
final boolean logQuery = options.getOption(Options.Name.LOG_QUERY);
final boolean isSlow = totalTime > (long) options.getOption(Options.Name.LOG_SLOW_QUERY_THRESHOLD_MICROS);
-
- if (storeTimer != null) {
- Optional.ofNullable(storeTimer.getCounter(PlannerTimerEvents.REWRITING_PHASE_COMPLETE)).ifPresent(
- (c) -> message.addKeyAndValue("plannerRewritingPhaseTimeMicros", TimeUnit.NANOSECONDS.toMicros(c.getTimeNanos())));
- Optional.ofNullable(storeTimer.getCounter(PlannerTimerEvents.PLANNING_PHASE_COMPLETE)).ifPresent(
- (c) -> message.addKeyAndValue("plannerPlanningPhaseTimeMicros", TimeUnit.NANOSECONDS.toMicros(c.getTimeNanos())));
- }
-
message.addKeyAndValue("totalPlanTimeMicros", totalTime);
if (plan != null) {
if (plan instanceof QueryPlan.PhysicalQueryPlan) {
diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/QueryLoggingTest.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/QueryLoggingTest.java
index d73a23f74c..00f82ff89d 100644
--- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/QueryLoggingTest.java
+++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/QueryLoggingTest.java
@@ -359,17 +359,6 @@ void testLogPlanHash() throws Exception {
Assertions.assertThat(logAppender.getLastLogEventMessage()).contains("planHash=\"-1179946538\"");
}
- @Test
- void testLogQueryLogsPlanningDurations() throws Exception {
- try (final RelationalResultSet resultSet = statement.executeQuery("SELECT * FROM RESTAURANT where rest_no = 34 OPTIONS (LOG QUERY)")) {
- resultSet.next();
- }
- Assertions.assertThat(logAppender.getLastLogEventMessage()).containsPattern("plannerRewritingPhaseTimeMicros=\"\\d+\"");
- Assertions.assertThat(logAppender.getLastLogEventMessage()).containsPattern("plannerPlanningPhaseTimeMicros=\"\\d+\"");
- Assertions.assertThat(logAppender.getLastLogEventMessage()).containsPattern("totalPlanTimeMicros=\"\\d+\"");
-
- }
-
@Test
void testLogException() {
Assert.assertThrows(Exception.class, () -> statement.executeQuery("SELECT * FROM REST where rest_no = 0 OPTIONS (LOG QUERY)"));
From 2582ff810487c91af9c1be06ff48746a8098eb99 Mon Sep 17 00:00:00 2001
From: Hazem Zaghloul
Date: Thu, 7 Aug 2025 17:26:17 +0100
Subject: [PATCH 3/9] Split the Cascades Debugger interface around stats and
symbols
This commit splits the com.apple.foundationdb.record.query.plan.cascades.debug.Debugger interface into two
separate interfaces (StatsDebugger and SymbolDebugger) which both extend the base Debugger interface, in
order to ensure that operations related to recording Debugger events (with the purpose of calculating stats
based on the profiling of these events) make use of the StatsDebugger interface, and operations related to
keeping a registry of all Symbols used within the planner for purposes of associating them with friendly names
make use of the SymbolDebugger interface.
In addition to splitting the Debugger interface, the State class is also split into two classes, one that keeps
of the Debugger events and stats around them (EventState) and one that keeps track of the current Symbols seen
during planning (SymbolTables).
---
.../query/plan/cascades/CascadesPlanner.java | 27 +--
.../query/plan/cascades/CascadesRuleCall.java | 21 +--
.../plan/cascades/CorrelationIdentifier.java | 7 +-
.../PrimaryAccessExpansionVisitor.java | 4 +-
.../query/plan/cascades/Quantifier.java | 4 +-
.../record/query/plan/cascades/Reference.java | 18 +-
.../query/plan/cascades/References.java | 5 +-
.../cascades/ValueIndexExpansionVisitor.java | 4 +-
.../WindowedIndexExpansionVisitor.java | 4 +-
.../query/plan/cascades/debug/Debugger.java | 66 +------
.../debug/DebuggerWithSymbolTables.java | 73 ++++----
.../debug/{State.java => EventState.java} | 152 ++--------------
.../plan/cascades/debug/StatsDebugger.java | 51 ++++++
.../plan/cascades/debug/SymbolDebugger.java | 95 ++++++++++
.../plan/cascades/debug/SymbolTables.java | 169 ++++++++++++++++++
.../cascades/explain/PlannerGraphVisitor.java | 6 +-
.../rules/ImplementNestedLoopJoinRule.java | 3 +-
.../query/plan/cascades/debug/Commands.java | 18 +-
.../plan/cascades/debug/PlannerRepl.java | 69 +++----
19 files changed, 475 insertions(+), 321 deletions(-)
rename fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/debug/{State.java => EventState.java} (71%)
create mode 100644 fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/debug/StatsDebugger.java
create mode 100644 fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/debug/SymbolDebugger.java
create mode 100644 fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/debug/SymbolTables.java
diff --git a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/CascadesPlanner.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/CascadesPlanner.java
index fb9f7b51a0..9ba4796fa2 100644
--- a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/CascadesPlanner.java
+++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/CascadesPlanner.java
@@ -41,6 +41,7 @@
import com.apple.foundationdb.record.query.plan.cascades.debug.Debugger;
import com.apple.foundationdb.record.query.plan.cascades.debug.Debugger.Location;
import com.apple.foundationdb.record.query.plan.cascades.debug.RestartException;
+import com.apple.foundationdb.record.query.plan.cascades.debug.StatsDebugger;
import com.apple.foundationdb.record.query.plan.cascades.explain.ExplainPlanVisitor;
import com.apple.foundationdb.record.query.plan.cascades.explain.PlannerGraphVisitor;
import com.apple.foundationdb.record.query.plan.cascades.expressions.RelationalExpression;
@@ -331,7 +332,7 @@ public QueryPlanResult planQuery(@Nonnull final RecordQuery query,
.put(QueryPlanInfoKeys.MAX_TASK_QUEUE_SIZE, maxQueueSize)
.put(QueryPlanInfoKeys.CONSTRAINTS, constraints)
.put(QueryPlanInfoKeys.STATS_MAPS,
- Debugger.getDebuggerMaybe().flatMap(Debugger::getStatsMaps)
+ StatsDebugger.getDebuggerMaybe().flatMap(StatsDebugger::getStatsMaps)
.orElse(null))
.build();
return new QueryPlanResult(plan, info);
@@ -375,8 +376,8 @@ public QueryPlanResult planGraph(@Nonnull final Supplier referenceSup
QueryPlanInfo.newBuilder()
.put(QueryPlanInfoKeys.CONSTRAINTS, constraints)
.put(QueryPlanInfoKeys.STATS_MAPS,
- Debugger.getDebuggerMaybe()
- .flatMap(Debugger::getStatsMaps).orElse(null))
+ StatsDebugger.getDebuggerMaybe().flatMap(StatsDebugger::getStatsMaps)
+ .orElse(null))
.build());
} finally {
Debugger.withDebugger(Debugger::onDone);
@@ -425,7 +426,7 @@ private void planPartial(@Nonnull final Supplier referenceSupplier,
}
taskCount++;
- Debugger.withDebugger(debugger -> debugger.onEvent(
+ StatsDebugger.withDebugger(debugger -> debugger.onEvent(
new Debugger.ExecutingTaskEvent(currentRoot, taskStack, Location.BEGIN,
Objects.requireNonNull(taskStack.peek()))));
Task nextTask = taskStack.pop();
@@ -434,7 +435,7 @@ private void planPartial(@Nonnull final Supplier referenceSupplier,
logger.trace(KeyValueLogMessage.of("executing task", "nextTask", nextTask.toString()));
}
- Debugger.withDebugger(debugger -> debugger.onEvent(nextTask.toTaskEvent(Location.BEGIN)));
+ StatsDebugger.withDebugger(debugger -> debugger.onEvent(nextTask.toTaskEvent(Location.BEGIN)));
try {
nextTask.execute();
Debugger.sanityCheck(() -> {
@@ -454,7 +455,7 @@ private void planPartial(@Nonnull final Supplier referenceSupplier,
});
} finally {
- Debugger.withDebugger(debugger -> debugger.onEvent(nextTask.toTaskEvent(Location.END)));
+ StatsDebugger.withDebugger(debugger -> debugger.onEvent(nextTask.toTaskEvent(Location.END)));
}
if (logger.isTraceEnabled()) {
@@ -469,7 +470,7 @@ private void planPartial(@Nonnull final Supplier referenceSupplier,
.addLogInfo(LogMessageKeys.TASK_QUEUE_SIZE, taskStack.size());
}
} finally {
- Debugger.withDebugger(debugger -> debugger.onEvent(
+ StatsDebugger.withDebugger(debugger -> debugger.onEvent(
new Debugger.ExecutingTaskEvent(currentRoot, taskStack, Location.END, nextTask)));
}
} catch (final RestartException restartException) {
@@ -1006,14 +1007,14 @@ public void execute() {
}
// we notify the debugger (if installed) that the transform task is succeeding and
// about begin and end of the rule call event
- Debugger.withDebugger(debugger -> debugger.onEvent(toTaskEvent(Location.MATCH_PRE)));
- Debugger.withDebugger(debugger ->
+ StatsDebugger.withDebugger(debugger -> debugger.onEvent(toTaskEvent(Location.MATCH_PRE)));
+ StatsDebugger.withDebugger(debugger ->
debugger.onEvent(new Debugger.TransformRuleCallEvent(plannerPhase, currentRoot,
taskStack, Location.BEGIN, group, getBindable(), rule, ruleCall)));
try {
executeRuleCall(ruleCall);
} finally {
- Debugger.withDebugger(debugger ->
+ StatsDebugger.withDebugger(debugger ->
debugger.onEvent(new Debugger.TransformRuleCallEvent(plannerPhase, currentRoot,
taskStack, Location.END, group, getBindable(), rule, ruleCall)));
}
@@ -1033,21 +1034,21 @@ protected void executeRuleCall(@Nonnull CascadesRuleCall ruleCall) {
// Handle produced artifacts (through yield...() calls)
//
for (final PartialMatch newPartialMatch : ruleCall.getNewPartialMatches()) {
- Debugger.withDebugger(debugger ->
+ StatsDebugger.withDebugger(debugger ->
debugger.onEvent(new Debugger.TransformRuleCallEvent(plannerPhase, currentRoot, taskStack,
Location.YIELD, group, getBindable(), rule, ruleCall)));
taskStack.push(new AdjustMatch(getPlannerPhase(), getGroup(), getExpression(), newPartialMatch));
}
for (final RelationalExpression newExpression : ruleCall.getNewFinalExpressions()) {
- Debugger.withDebugger(debugger ->
+ StatsDebugger.withDebugger(debugger ->
debugger.onEvent(new Debugger.TransformRuleCallEvent(plannerPhase, currentRoot, taskStack,
Location.YIELD, group, getBindable(), rule, ruleCall)));
exploreExpressionAndOptimizeInputs(plannerPhase, getGroup(), newExpression, true);
}
for (final RelationalExpression newExpression : ruleCall.getNewExploratoryExpressions()) {
- Debugger.withDebugger(debugger ->
+ StatsDebugger.withDebugger(debugger ->
debugger.onEvent(new Debugger.TransformRuleCallEvent(plannerPhase, currentRoot, taskStack,
Location.YIELD, group, getBindable(), rule, ruleCall)));
exploreExpression(plannerPhase, group, newExpression, true);
diff --git a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/CascadesRuleCall.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/CascadesRuleCall.java
index d79081a5cb..ced46a3a10 100644
--- a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/CascadesRuleCall.java
+++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/CascadesRuleCall.java
@@ -27,6 +27,7 @@
import com.apple.foundationdb.record.query.plan.cascades.Quantifiers.AliasResolver;
import com.apple.foundationdb.record.query.plan.cascades.debug.Debugger;
import com.apple.foundationdb.record.query.plan.cascades.debug.Debugger.InsertIntoMemoEvent;
+import com.apple.foundationdb.record.query.plan.cascades.debug.StatsDebugger;
import com.apple.foundationdb.record.query.plan.cascades.expressions.RelationalExpression;
import com.apple.foundationdb.record.query.plan.cascades.matching.structure.PlannerBindings;
import com.apple.foundationdb.record.query.plan.plans.RecordQueryPlan;
@@ -185,7 +186,7 @@ public void pushConstraint(@Nonnull final Reference reference,
@Override
public void emitEvent(@Nonnull final Debugger.Location location) {
Verify.verify(location != Debugger.Location.BEGIN && location != Debugger.Location.END);
- Debugger.withDebugger(debugger ->
+ StatsDebugger.withDebugger(debugger ->
debugger.onEvent(
new Debugger.TransformRuleCallEvent(plannerPhase, root, taskStack, location, root,
bindings.get(rule.getMatcher()), rule, this)));
@@ -295,7 +296,7 @@ public EvaluationContext getEvaluationContext() {
@Nonnull
private Reference addNewReference(@Nonnull final Reference newRef) {
for (RelationalExpression expression : newRef.getAllMemberExpressions()) {
- Debugger.withDebugger(debugger -> debugger.onEvent(InsertIntoMemoEvent.newExp(expression)));
+ StatsDebugger.withDebugger(debugger -> debugger.onEvent(InsertIntoMemoEvent.newExp(expression)));
traversal.addExpression(newRef, expression);
}
newReferences.add(newRef);
@@ -352,7 +353,7 @@ public Reference memoizeExploratoryExpressions(@Nonnull final Collection exten
// least one variation) or it will be a new reference, but that reference must be missing at least
// one child from the first variation and therefore cannot be reused
//
- Debugger.withDebugger(debugger -> debugger.onEvent(InsertIntoMemoEvent.begin()));
+ StatsDebugger.withDebugger(debugger -> debugger.onEvent(InsertIntoMemoEvent.begin()));
try {
Preconditions.checkArgument(expressions.stream().noneMatch(expression -> expression instanceof RecordQueryPlan));
@@ -412,7 +413,7 @@ public Reference memoizeExploratoryExpressions(@Nonnull final Collection exten
if (!existingRefs.isEmpty()) {
Reference existingReference = existingRefs.get(0);
expressions.forEach(expr ->
- Debugger.withDebugger(debugger ->
+ StatsDebugger.withDebugger(debugger ->
debugger.onEvent(InsertIntoMemoEvent.reusedExpWithReferences(expr, existingRefs))));
Verify.verify(existingReference != this.root);
return existingReference;
@@ -421,7 +422,7 @@ public Reference memoizeExploratoryExpressions(@Nonnull final Collection exten
// If we didn't find one, create a new reference and add it to the memo
return addNewReference(Reference.ofExploratoryExpressions(plannerPhase.getTargetPlannerStage(), expressions));
} finally {
- Debugger.withDebugger(debugger -> debugger.onEvent(InsertIntoMemoEvent.end()));
+ StatsDebugger.withDebugger(debugger -> debugger.onEvent(InsertIntoMemoEvent.end()));
}
}
@@ -436,7 +437,7 @@ private boolean isEligibleForReuse(@Nonnull Set requiredC
@Nonnull
private Reference memoizeLeafExpressions(@Nonnull final Collection extends RelationalExpression> expressions) {
- Debugger.withDebugger(debugger -> debugger.onEvent(InsertIntoMemoEvent.begin()));
+ StatsDebugger.withDebugger(debugger -> debugger.onEvent(InsertIntoMemoEvent.begin()));
try {
Preconditions.checkArgument(expressions.stream()
.allMatch(expression -> !(expression instanceof RecordQueryPlan) && expression.getQuantifiers().isEmpty()));
@@ -450,7 +451,7 @@ private Reference memoizeLeafExpressions(@Nonnull final Collection extends Rel
}
if (leafRef.containsAllInMemo(expressions, AliasMap.emptyMap(), false)) {
for (RelationalExpression expression : expressions) {
- Debugger.withDebugger(debugger -> debugger.onEvent(InsertIntoMemoEvent.reusedExp(expression)));
+ StatsDebugger.withDebugger(debugger -> debugger.onEvent(InsertIntoMemoEvent.reusedExp(expression)));
}
return leafRef;
}
@@ -458,7 +459,7 @@ private Reference memoizeLeafExpressions(@Nonnull final Collection extends Rel
return addNewReference(Reference.ofExploratoryExpressions(plannerPhase.getTargetPlannerStage(), expressions));
} finally {
- Debugger.withDebugger(debugger -> debugger.onEvent(InsertIntoMemoEvent.end()));
+ StatsDebugger.withDebugger(debugger -> debugger.onEvent(InsertIntoMemoEvent.end()));
}
}
@@ -534,14 +535,14 @@ private Reference memoizeExpressionsExactly(@Nonnull final Collection extends
@Nonnull BiFunction, Set extends RelationalExpression>, Reference> referenceCreator) {
final var allExpressions =
Iterables.concat(exploratoryExpressions, finalExpressions);
- Debugger.withDebugger(debugger -> allExpressions.forEach(
+ StatsDebugger.withDebugger(debugger -> allExpressions.forEach(
expression -> debugger.onEvent(InsertIntoMemoEvent.begin())));
try {
final var exploratoryExpressionSet = new LinkedIdentitySet<>(exploratoryExpressions);
final var finalExpressionSet = new LinkedIdentitySet<>(finalExpressions);
return addNewReference(referenceCreator.apply(exploratoryExpressionSet, finalExpressionSet));
} finally {
- Debugger.withDebugger(debugger -> allExpressions.forEach(
+ StatsDebugger.withDebugger(debugger -> allExpressions.forEach(
expression -> debugger.onEvent(InsertIntoMemoEvent.end())));
}
}
diff --git a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/CorrelationIdentifier.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/CorrelationIdentifier.java
index 72dc45bcb8..498fd7d426 100644
--- a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/CorrelationIdentifier.java
+++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/CorrelationIdentifier.java
@@ -22,6 +22,7 @@
import com.apple.foundationdb.annotation.API;
import com.apple.foundationdb.record.query.plan.cascades.debug.Debugger;
+import com.apple.foundationdb.record.query.plan.cascades.debug.SymbolDebugger;
import com.apple.foundationdb.record.util.ProtoUtils;
import com.google.common.collect.ImmutableSet;
@@ -88,10 +89,10 @@ public static CorrelationIdentifier uniqueID(@Nonnull final Class> clazz) {
@Nonnull
public static CorrelationIdentifier uniqueID(@Nonnull final Class> clazz, @Nonnull final String prefix) {
final CorrelationIdentifier id =
- Debugger.getIndexOptional(clazz)
+ SymbolDebugger.getIndexOptional(clazz)
.map(i -> CorrelationIdentifier.of(prefix + i))
.orElseGet(() -> new CorrelationIdentifier(ProtoUtils.uniqueName(prefix)));
- Debugger.updateIndex(clazz, i -> i + 1);
+ SymbolDebugger.updateIndex(clazz, i -> i + 1);
return id;
}
@@ -106,7 +107,7 @@ public static CorrelationIdentifier uniqueID(@Nonnull final Class> clazz, @Non
*/
@Nonnull
public static CorrelationIdentifier uniqueSingletonID(@Nonnull final UUID singleton, @Nonnull final String prefix) {
- return Debugger.getOrRegisterSingleton(singleton)
+ return SymbolDebugger.getOrRegisterSingleton(singleton)
.map(index -> new CorrelationIdentifier(prefix + index))
.orElseGet(() -> new CorrelationIdentifier(singleton.toString()));
}
diff --git a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/PrimaryAccessExpansionVisitor.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/PrimaryAccessExpansionVisitor.java
index cd1bfe193e..82c820ed15 100644
--- a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/PrimaryAccessExpansionVisitor.java
+++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/PrimaryAccessExpansionVisitor.java
@@ -23,7 +23,7 @@
import com.apple.foundationdb.annotation.SpotBugsSuppressWarnings;
import com.apple.foundationdb.record.metadata.RecordType;
import com.apple.foundationdb.record.metadata.expressions.KeyExpression;
-import com.apple.foundationdb.record.query.plan.cascades.debug.Debugger;
+import com.apple.foundationdb.record.query.plan.cascades.debug.SymbolDebugger;
import com.apple.foundationdb.record.query.plan.cascades.expressions.MatchableSortExpression;
import com.apple.foundationdb.record.query.plan.cascades.predicates.PredicateWithValueAndRanges;
import com.google.common.collect.ImmutableList;
@@ -58,7 +58,7 @@ public PrimaryScanMatchCandidate expand(@Nonnull final Supplier 0);
+ SymbolDebugger.updateIndex(PredicateWithValueAndRanges.class, old -> 0);
final var baseQuantifier = baseQuantifierSupplier.get();
diff --git a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/Quantifier.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/Quantifier.java
index d40fdee0bd..4e259f891b 100644
--- a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/Quantifier.java
+++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/Quantifier.java
@@ -25,7 +25,7 @@
import com.apple.foundationdb.record.PlanSerializable;
import com.apple.foundationdb.record.PlanSerializationContext;
import com.apple.foundationdb.record.planprotos.PPhysicalQuantifier;
-import com.apple.foundationdb.record.query.plan.cascades.debug.Debugger;
+import com.apple.foundationdb.record.query.plan.cascades.debug.SymbolDebugger;
import com.apple.foundationdb.record.query.plan.cascades.expressions.RelationalExpression;
import com.apple.foundationdb.record.query.plan.cascades.typing.Type;
import com.apple.foundationdb.record.query.plan.cascades.typing.Type.Record.Field;
@@ -625,7 +625,7 @@ protected Quantifier(@Nonnull final CorrelationIdentifier alias) {
this.flowedColumnsSupplier = Suppliers.memoize(this::computeFlowedColumns);
this.flowedValuesSupplier = Suppliers.memoize(this::computeFlowedValues);
// Call debugger hook for this new quantifier.
- Debugger.registerQuantifier(this);
+ SymbolDebugger.registerQuantifier(this);
}
@Nonnull
diff --git a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/Reference.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/Reference.java
index d8ede8046a..b897e2fbfa 100644
--- a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/Reference.java
+++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/Reference.java
@@ -25,6 +25,8 @@
import com.apple.foundationdb.record.query.plan.HeuristicPlanner;
import com.apple.foundationdb.record.query.plan.cascades.debug.Debugger;
import com.apple.foundationdb.record.query.plan.cascades.debug.Debugger.InsertIntoMemoEvent;
+import com.apple.foundationdb.record.query.plan.cascades.debug.StatsDebugger;
+import com.apple.foundationdb.record.query.plan.cascades.debug.SymbolDebugger;
import com.apple.foundationdb.record.query.plan.cascades.explain.PlannerGraphVisitor;
import com.apple.foundationdb.record.query.plan.cascades.expressions.RelationalExpression;
import com.apple.foundationdb.record.query.plan.cascades.expressions.RelationalExpressionWithChildren;
@@ -179,7 +181,7 @@ private Reference(@Nonnull final PlannerStage plannerStage,
finalExpressions.forEach(finalExpression -> propertiesMap.add(finalExpression));
this.allMembersView = exploratoryMembers.concatExpressions(finalMembers);
// Call debugger hook for this new reference.
- Debugger.registerReference(this);
+ SymbolDebugger.registerReference(this);
}
@Nonnull
@@ -325,10 +327,10 @@ public boolean insertFinalExpression(@Nonnull final RelationalExpression newValu
private boolean insert(@Nonnull final RelationalExpression newExpression,
final boolean isFinal,
@Nullable final Map, ?> precomputedPropertiesMap) {
- Debugger.withDebugger(debugger -> debugger.onEvent(InsertIntoMemoEvent.begin()));
+ StatsDebugger.withDebugger(debugger -> debugger.onEvent(InsertIntoMemoEvent.begin()));
try {
final boolean containsInMemo = containsInMemo(newExpression, isFinal);
- Debugger.withDebugger(debugger -> {
+ StatsDebugger.withDebugger(debugger -> {
if (containsInMemo) {
debugger.onEvent(InsertIntoMemoEvent.reusedExpWithReferences(newExpression, ImmutableList.of(this)));
} else {
@@ -341,7 +343,7 @@ private boolean insert(@Nonnull final RelationalExpression newExpression,
}
return false;
} finally {
- Debugger.withDebugger(debugger -> debugger.onEvent(InsertIntoMemoEvent.end()));
+ StatsDebugger.withDebugger(debugger -> debugger.onEvent(InsertIntoMemoEvent.end()));
}
}
@@ -359,7 +361,7 @@ private void insertUnchecked(@Nonnull final RelationalExpression newExpression,
final boolean isFinal,
@Nullable final Map, ?> precomputedPropertiesMap) {
// Call debugger hook to potentially register this new expression.
- Debugger.registerExpression(newExpression);
+ SymbolDebugger.registerExpression(newExpression);
Debugger.sanityCheck(() -> Verify.verify(getTotalMembersSize() == 0 ||
getResultType().equals(newExpression.getResultType())));
@@ -627,7 +629,7 @@ public U acceptVisitor(@Nonnull SimpleExpressionVisitor simpleExpressionV
@Override
public String toString() {
- return Debugger.mapDebugger(debugger -> debugger.nameForObject(this) + "[" +
+ return SymbolDebugger.mapDebugger(debugger -> debugger.nameForObject(this) + "[" +
getAllMemberExpressions().stream()
.map(debugger::nameForObject)
.collect(Collectors.joining(",")) + "]")
@@ -856,8 +858,8 @@ public static Reference of(@Nonnull final PlannerStage plannerStage,
@Nonnull final Collection extends RelationalExpression> exploratoryExpressions,
@Nonnull final Collection extends RelationalExpression> finalExpressions) {
// Call debugger hook to potentially register this new expression.
- exploratoryExpressions.forEach(Debugger::registerExpression);
- finalExpressions.forEach(Debugger::registerExpression);
+ exploratoryExpressions.forEach(SymbolDebugger::registerExpression);
+ finalExpressions.forEach(SymbolDebugger::registerExpression);
return new Reference(plannerStage, new LinkedIdentitySet<>(exploratoryExpressions),
new LinkedIdentitySet<>(finalExpressions));
diff --git a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/References.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/References.java
index d2ed48195e..df5f632d42 100644
--- a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/References.java
+++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/References.java
@@ -23,6 +23,7 @@
import com.apple.foundationdb.record.RecordCoreException;
import com.apple.foundationdb.record.query.combinatorics.TopologicalSort;
import com.apple.foundationdb.record.query.plan.cascades.debug.Debugger;
+import com.apple.foundationdb.record.query.plan.cascades.debug.StatsDebugger;
import com.apple.foundationdb.record.query.plan.cascades.expressions.RelationalExpression;
import com.apple.foundationdb.record.query.plan.cascades.expressions.RelationalExpressionWithChildren;
import com.apple.foundationdb.record.query.plan.cascades.values.translation.TranslationMap;
@@ -187,14 +188,14 @@ private static void rebaseGraph(@Nonnull final Memoizer memoizer, @Nonnull final
} else {
translatedExpression = expression.translateCorrelations(translationMap, shouldSimplifyValues,
translatedQuantifiers);
- Debugger.withDebugger(debugger -> debugger.onEvent(
+ StatsDebugger.withDebugger(debugger -> debugger.onEvent(
new Debugger.TranslateCorrelationsEvent(translatedExpression, Debugger.Location.COUNT)));
allMembersSame = false;
}
} else {
translatedExpression = expression.translateCorrelations(translationMap, shouldSimplifyValues,
translatedQuantifiers);
- Debugger.withDebugger(debugger -> debugger.onEvent(
+ StatsDebugger.withDebugger(debugger -> debugger.onEvent(
new Debugger.TranslateCorrelationsEvent(translatedExpression, Debugger.Location.COUNT)));
allMembersSame = false;
}
diff --git a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/ValueIndexExpansionVisitor.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/ValueIndexExpansionVisitor.java
index 48361388c5..0d1ff32998 100644
--- a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/ValueIndexExpansionVisitor.java
+++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/ValueIndexExpansionVisitor.java
@@ -27,7 +27,7 @@
import com.apple.foundationdb.record.metadata.expressions.GroupingKeyExpression;
import com.apple.foundationdb.record.metadata.expressions.KeyExpression;
import com.apple.foundationdb.record.metadata.expressions.KeyWithValueExpression;
-import com.apple.foundationdb.record.query.plan.cascades.debug.Debugger;
+import com.apple.foundationdb.record.query.plan.cascades.debug.SymbolDebugger;
import com.apple.foundationdb.record.query.plan.cascades.expressions.MatchableSortExpression;
import com.apple.foundationdb.record.query.plan.cascades.predicates.Placeholder;
import com.apple.foundationdb.record.query.plan.cascades.predicates.PredicateWithValueAndRanges;
@@ -86,7 +86,7 @@ public ValueIndexExpansionVisitor(@Nonnull Index index, @Nonnull Collection baseQuantifierSupplier,
@Nullable final KeyExpression primaryKey,
final boolean isReverse) {
- Debugger.updateIndex(PredicateWithValueAndRanges.class, old -> 0);
+ SymbolDebugger.updateIndex(PredicateWithValueAndRanges.class, old -> 0);
final var baseQuantifier = baseQuantifierSupplier.get();
final var allExpansionsBuilder = ImmutableList.builder();
diff --git a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/WindowedIndexExpansionVisitor.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/WindowedIndexExpansionVisitor.java
index 6c3ab093ca..9fe928f324 100644
--- a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/WindowedIndexExpansionVisitor.java
+++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/WindowedIndexExpansionVisitor.java
@@ -26,7 +26,7 @@
import com.apple.foundationdb.record.metadata.expressions.GroupingKeyExpression;
import com.apple.foundationdb.record.metadata.expressions.KeyExpression;
import com.apple.foundationdb.record.query.expressions.Comparisons;
-import com.apple.foundationdb.record.query.plan.cascades.debug.Debugger;
+import com.apple.foundationdb.record.query.plan.cascades.debug.SymbolDebugger;
import com.apple.foundationdb.record.query.plan.cascades.expressions.MatchableSortExpression;
import com.apple.foundationdb.record.query.plan.cascades.expressions.SelectExpression;
import com.apple.foundationdb.record.query.plan.cascades.predicates.Placeholder;
@@ -98,7 +98,7 @@ public MatchCandidate expand(@Nonnull final Supplier baseQua
var rootExpression = index.getRootExpression();
Verify.verify(rootExpression instanceof GroupingKeyExpression);
- Debugger.updateIndex(PredicateWithValueAndRanges.class, old -> 0);
+ SymbolDebugger.updateIndex(PredicateWithValueAndRanges.class, old -> 0);
final var allExpansionsBuilder = ImmutableList.builder();
final var baseQuantifier = baseQuantifierSupplier.get();
diff --git a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/debug/Debugger.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/debug/Debugger.java
index 5ec77a5df2..45f43881fc 100644
--- a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/debug/Debugger.java
+++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/debug/Debugger.java
@@ -27,7 +27,6 @@
import com.apple.foundationdb.record.query.plan.cascades.PartialMatch;
import com.apple.foundationdb.record.query.plan.cascades.PlanContext;
import com.apple.foundationdb.record.query.plan.cascades.PlannerPhase;
-import com.apple.foundationdb.record.query.plan.cascades.Quantifier;
import com.apple.foundationdb.record.query.plan.cascades.Reference;
import com.apple.foundationdb.record.query.plan.cascades.debug.eventprotos.PAbstractEventWithState;
import com.apple.foundationdb.record.query.plan.cascades.debug.eventprotos.PAdjustMatchEvent;
@@ -50,7 +49,6 @@
import com.apple.foundationdb.record.query.plan.cascades.expressions.RelationalExpression;
import com.google.common.base.Verify;
import com.google.common.collect.ImmutableList;
-import com.google.errorprone.annotations.CanIgnoreReturnValue;
import com.google.protobuf.Message;
import javax.annotation.Nonnull;
@@ -61,7 +59,6 @@
import java.util.Optional;
import java.util.function.Consumer;
import java.util.function.Function;
-import java.util.function.IntUnaryOperator;
/**
*
@@ -162,11 +159,7 @@ static void sanityCheck(@Nonnull final Runnable runnable) {
*/
@Nonnull
static Optional mapDebugger(@Nonnull final Function function) {
- final Debugger debugger = getDebugger();
- if (debugger != null) {
- return Optional.ofNullable(function.apply(debugger));
- }
- return Optional.empty();
+ return getDebuggerMaybe().map(function);
}
static void install() {
@@ -181,56 +174,13 @@ static void show(@Nonnull final Reference ref) {
withDebugger(debugger -> debugger.onShow(ref));
}
- static Optional getIndexOptional(Class> clazz) {
- return mapDebugger(debugger -> debugger.onGetIndex(clazz));
- }
-
- @Nonnull
- @CanIgnoreReturnValue
- static Optional updateIndex(Class> clazz, IntUnaryOperator updateFn) {
- return mapDebugger(debugger -> debugger.onUpdateIndex(clazz, updateFn));
- }
-
- static void registerExpression(RelationalExpression expression) {
- withDebugger(debugger -> debugger.onRegisterExpression(expression));
- }
-
- static void registerReference(Reference reference) {
- withDebugger(debugger -> debugger.onRegisterReference(reference));
- }
-
- static void registerQuantifier(Quantifier quantifier) {
- withDebugger(debugger -> debugger.onRegisterQuantifier(quantifier));
- }
-
- static Optional getOrRegisterSingleton(Object singleton) {
- return mapDebugger(debugger -> debugger.onGetOrRegisterSingleton(singleton));
- }
-
- @Nullable
- String nameForObject(@Nonnull Object object);
-
@Nullable
PlanContext getPlanContext();
boolean isSane();
- void onEvent(Event event);
-
void onDone();
- int onGetIndex(@Nonnull Class> clazz);
-
- int onUpdateIndex(@Nonnull Class> clazz, @Nonnull IntUnaryOperator updateFn);
-
- void onRegisterExpression(@Nonnull RelationalExpression expression);
-
- void onRegisterReference(@Nonnull Reference reference);
-
- void onRegisterQuantifier(@Nonnull Quantifier quantifier);
-
- int onGetOrRegisterSingleton(@Nonnull Object singleton);
-
void onInstall();
void onSetup();
@@ -239,12 +189,6 @@ static Optional getOrRegisterSingleton(Object singleton) {
void onQuery(String queryAsString, PlanContext planContext);
- @SuppressWarnings("unused") // only used by debugger
- String showStats();
-
- @Nonnull
- Optional getStatsMaps();
-
/**
* Shorthands to identify a kind of event.
*/
@@ -325,7 +269,7 @@ default PEvent toEventProto() {
@Nonnull
static PRegisteredRelationalExpression toExpressionProto(@Nonnull final RelationalExpression expression) {
return PRegisteredRelationalExpression.newBuilder()
- .setName(Debugger.mapDebugger(debugger -> debugger.nameForObject(expression)).orElseThrow())
+ .setName(SymbolDebugger.mapDebugger(debugger -> debugger.nameForObject(expression)).orElseThrow())
.setSemanticHashCode(expression.semanticHashCode())
.build();
}
@@ -333,7 +277,7 @@ static PRegisteredRelationalExpression toExpressionProto(@Nonnull final Relation
@Nonnull
static PRegisteredReference toReferenceProto(@Nonnull final Reference reference) {
final var builder = PRegisteredReference.newBuilder()
- .setName(Debugger.mapDebugger(debugger -> debugger.nameForObject(reference)).orElseThrow());
+ .setName(SymbolDebugger.mapDebugger(debugger -> debugger.nameForObject(reference)).orElseThrow());
for (final var member : reference.getAllMemberExpressions()) {
builder.addExpressions(toExpressionProto(member));
}
@@ -1024,13 +968,13 @@ private InsertIntoMemoEvent(@Nonnull final Location location,
@Nullable final RelationalExpression expression,
@Nonnull final Collection reusedExpressionReferences) {
if (expression != null) {
- Debugger.registerExpression(expression);
+ SymbolDebugger.registerExpression(expression);
}
this.expression = expression;
this.location = location;
this.reusedExpressionReferences = ImmutableList.copyOf(reusedExpressionReferences);
// Call debugger hook to potentially register this new reference.
- this.reusedExpressionReferences.forEach(Debugger::registerReference);
+ this.reusedExpressionReferences.forEach(SymbolDebugger::registerReference);
}
@Override
diff --git a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/debug/DebuggerWithSymbolTables.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/debug/DebuggerWithSymbolTables.java
index a8f2bb8264..fd143746b0 100644
--- a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/debug/DebuggerWithSymbolTables.java
+++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/debug/DebuggerWithSymbolTables.java
@@ -64,13 +64,15 @@
* while debugging.
*/
@SuppressWarnings("PMD.SystemPrintln")
-public class DebuggerWithSymbolTables implements Debugger {
+public class DebuggerWithSymbolTables implements SymbolDebugger, StatsDebugger {
private static final Logger logger = LoggerFactory.getLogger(DebuggerWithSymbolTables.class);
private final boolean isSane;
private final boolean isRecordEvents;
private final Iterable prerecordedEventProtoIterable;
- private final Deque stateStack;
+ private final Deque eventStateStack;
+ private final Deque symbolTablesStack;
+
@Nullable
private String queryAsString;
@Nullable
@@ -84,14 +86,20 @@ private DebuggerWithSymbolTables(final boolean isSane, final boolean isRecordEve
this.isRecordEvents = isRecordEvents;
this.prerecordedEventProtoIterable = prerecordedEventsFileName == null
? null : eventProtosFromFile(prerecordedEventsFileName);
- this.stateStack = new ArrayDeque<>();
+ this.eventStateStack = new ArrayDeque<>();
+ this.symbolTablesStack = new ArrayDeque<>();
this.planContext = null;
this.singletonToIndexMap = Maps.newHashMap();
}
@Nonnull
- State getCurrentState() {
- return Objects.requireNonNull(stateStack.peek());
+ EventState getCurrentEventState() {
+ return Objects.requireNonNull(eventStateStack.peek());
+ }
+
+ @Nonnull
+ SymbolTables getCurrentSymbolState() {
+ return Objects.requireNonNull(symbolTablesStack.peek());
}
@Nullable
@@ -111,27 +119,27 @@ public boolean isSane() {
@Override
public int onGetIndex(@Nonnull final Class> clazz) {
- return getCurrentState().getIndex(clazz);
+ return getCurrentSymbolState().getIndex(clazz);
}
@Override
public int onUpdateIndex(@Nonnull final Class> clazz, @Nonnull final IntUnaryOperator updateFn) {
- return getCurrentState().updateIndex(clazz, updateFn);
+ return getCurrentSymbolState().updateIndex(clazz, updateFn);
}
@Override
public void onRegisterExpression(@Nonnull final RelationalExpression expression) {
- getCurrentState().registerExpression(expression);
+ getCurrentSymbolState().registerExpression(expression);
}
@Override
public void onRegisterReference(@Nonnull final Reference reference) {
- getCurrentState().registerReference(reference);
+ getCurrentSymbolState().registerReference(reference);
}
@Override
public void onRegisterQuantifier(@Nonnull final Quantifier quantifier) {
- getCurrentState().registerQuantifier(quantifier);
+ getCurrentSymbolState().registerQuantifier(quantifier);
}
@Override
@@ -157,7 +165,8 @@ public void onShow(@Nonnull final Reference ref) {
@Override
public void onQuery(@Nonnull final String recordQuery, @Nonnull final PlanContext planContext) {
- this.stateStack.push(State.copyOf(getCurrentState()));
+ this.eventStateStack.push(EventState.copyOf(getCurrentEventState()));
+ this.symbolTablesStack.push(SymbolTables.copyOf(getCurrentSymbolState()));
this.queryAsString = recordQuery;
this.planContext = planContext;
@@ -165,17 +174,20 @@ public void onQuery(@Nonnull final String recordQuery, @Nonnull final PlanContex
}
void restartState() {
- stateStack.pop();
- stateStack.push(State.copyOf(getCurrentState()));
+ eventStateStack.pop();
+ eventStateStack.push(EventState.copyOf(getCurrentEventState()));
+
+ symbolTablesStack.pop();
+ symbolTablesStack.push(SymbolTables.copyOf(getCurrentSymbolState()));
}
@Override
@SuppressWarnings("PMD.GuardLogStatement") // false positive
public void onEvent(final Event event) {
- if ((queryAsString == null) || (planContext == null) || stateStack.isEmpty()) {
+ if ((queryAsString == null) || (planContext == null) || eventStateStack.isEmpty()) {
return;
}
- getCurrentState().addCurrentEvent(event);
+ getCurrentEventState().addCurrentEvent(event);
if (logger.isTraceEnabled()) {
if (event.getLocation() == Location.END && event instanceof TransformRuleCallEvent) {
final TransformRuleCallEvent transformRuleCallEvent = (TransformRuleCallEvent)event;
@@ -237,7 +249,7 @@ boolean isValidEntityName(@Nonnull final String identifier) {
@Nullable
@Override
public String nameForObject(@Nonnull final Object object) {
- final State state = getCurrentState();
+ final SymbolTables state = getCurrentSymbolState();
if (object instanceof RelationalExpression) {
@Nullable final Integer id = state.getInvertedExpressionsCache().getIfPresent(object);
return (id == null) ? null : "exp" + id;
@@ -254,8 +266,8 @@ public String nameForObject(@Nonnull final Object object) {
@Override
public void onDone() {
- if (!stateStack.isEmpty() && queryAsString != null) {
- final var state = Objects.requireNonNull(stateStack.peek());
+ if (!eventStateStack.isEmpty() && queryAsString != null) {
+ final var state = Objects.requireNonNull(eventStateStack.peek());
if (logger.isInfoEnabled()) {
logger.info(KeyValueLogMessage.of("planning done",
"query", Objects.requireNonNull(queryAsString).substring(0, Math.min(queryAsString.length(), 30)),
@@ -323,28 +335,23 @@ protected PEvent computeNext() {
};
}
- @Override
- public String showStats() {
- State currentState = stateStack.peek();
- if (currentState != null) {
- return currentState.showStats();
- }
- return "no stats";
- }
-
@Nonnull
@Override
public Optional getStatsMaps() {
- State currentState = stateStack.peek();
- if (currentState != null) {
- return Optional.of(currentState.getStatsMaps());
+ EventState currentEventState = eventStateStack.peek();
+ if (currentEventState != null) {
+ return Optional.of(currentEventState.getStatsMaps());
}
return Optional.empty();
}
private void reset() {
- this.stateStack.clear();
- this.stateStack.push(State.initial(isRecordEvents, isRecordEvents, prerecordedEventProtoIterable));
+ this.eventStateStack.clear();
+ this.eventStateStack.push(EventState.initial(isRecordEvents, isRecordEvents, prerecordedEventProtoIterable));
+
+ this.symbolTablesStack.clear();
+ this.symbolTablesStack.push(new SymbolTables());
+
this.planContext = null;
this.queryAsString = null;
}
@@ -399,7 +406,7 @@ public static DebuggerWithSymbolTables withPrerecordedEvents(@Nonnull final Stri
public static void printForEachExpression(@Nonnull final Reference root) {
forEachExpression(root, expression -> {
System.out.println("expression: " +
- Debugger.mapDebugger(debugger -> debugger.nameForObject(expression)).orElseThrow() + "; " +
+ SymbolDebugger.mapDebugger(debugger -> debugger.nameForObject(expression)).orElseThrow() + "; " +
"hashCodeWithoutChildren: " + expression.hashCodeWithoutChildren() + "explain: " + expression);
});
}
diff --git a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/debug/State.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/debug/EventState.java
similarity index 71%
rename from fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/debug/State.java
rename to fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/debug/EventState.java
index a39eb38661..67af117caa 100644
--- a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/debug/State.java
+++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/debug/EventState.java
@@ -23,18 +23,12 @@
import com.apple.foundationdb.record.RecordCoreException;
import com.apple.foundationdb.record.logging.KeyValueLogMessage;
import com.apple.foundationdb.record.query.plan.cascades.CascadesRule;
-import com.apple.foundationdb.record.query.plan.cascades.Quantifier;
-import com.apple.foundationdb.record.query.plan.cascades.Reference;
import com.apple.foundationdb.record.query.plan.cascades.debug.eventprotos.PEvent;
-import com.apple.foundationdb.record.query.plan.cascades.expressions.RelationalExpression;
import com.apple.foundationdb.record.util.pair.Pair;
import com.google.common.base.Verify;
-import com.google.common.cache.Cache;
-import com.google.common.cache.CacheBuilder;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
-import com.google.errorprone.annotations.CanIgnoreReturnValue;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -48,22 +42,11 @@
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
-import java.util.function.IntUnaryOperator;
@SuppressWarnings("PMD.SystemPrintln")
-public class State {
+public class EventState {
@Nonnull
- private static final Logger logger = LoggerFactory.getLogger(State.class);
-
- @Nonnull
- private final Map, Integer> classToIndexMap;
- @Nonnull
- private final Cache expressionCache;
- @Nonnull private final Cache invertedExpressionsCache;
- @Nonnull private final Cache referenceCache;
- @Nonnull private final Cache invertedReferenceCache;
- @Nonnull private final Cache quantifierCache;
- @Nonnull private final Cache invertedQuantifierCache;
+ private static final Logger logger = LoggerFactory.getLogger(EventState.class);
@Nullable private final List events;
@Nullable private final List eventProtos;
@@ -76,35 +59,16 @@ public class State {
@Nonnull private final Deque, EventDurations>> eventProfilingStack;
- private int currentTick;
- private final long startTs;
+ protected int currentTick;
+ protected final long startTs;
- public static State initial(final boolean isRecordEvents, final boolean isRecordEventProtos,
- @Nullable Iterable prerecordedEventProtoIterable) {
- return new State(isRecordEvents, isRecordEventProtos, prerecordedEventProtoIterable);
+ public static EventState initial(final boolean isRecordEvents, final boolean isRecordEventProtos,
+ @Nullable Iterable prerecordedEventProtoIterable) {
+ return new EventState(isRecordEvents, isRecordEventProtos, prerecordedEventProtoIterable);
}
- public static State copyOf(final State source) {
- final Cache copyExpressionCache = CacheBuilder.newBuilder().weakValues().build();
- source.getExpressionCache().asMap().forEach(copyExpressionCache::put);
- final Cache copyInvertedExpressionsCache = CacheBuilder.newBuilder().weakKeys().build();
- source.getInvertedExpressionsCache().asMap().forEach(copyInvertedExpressionsCache::put);
- final Cache copyReferenceCache = CacheBuilder.newBuilder().weakValues().build();
- source.getReferenceCache().asMap().forEach(copyReferenceCache::put);
- final Cache copyInvertedReferenceCache = CacheBuilder.newBuilder().weakKeys().build();
- source.getInvertedReferenceCache().asMap().forEach(copyInvertedReferenceCache::put);
- final Cache copyQuantifierCache = CacheBuilder.newBuilder().weakValues().build();
- source.getQuantifierCache().asMap().forEach(copyQuantifierCache::put);
- final Cache copyInvertedQuantifierCache = CacheBuilder.newBuilder().weakKeys().build();
- source.getInvertedQuantifierCache().asMap().forEach(copyInvertedQuantifierCache::put);
-
- return new State(source.getClassToIndexMap(),
- copyExpressionCache,
- copyInvertedExpressionsCache,
- copyReferenceCache,
- copyInvertedReferenceCache,
- copyQuantifierCache,
- copyInvertedQuantifierCache,
+ public static EventState copyOf(final EventState source) {
+ return new EventState(
source.events == null ? null : Lists.newArrayList(source.events),
source.eventProtos == null ? null : Lists.newArrayList(source.eventProtos),
source.prerecordedEventProtoIterable,
@@ -115,15 +79,9 @@ public static State copyOf(final State source) {
source.getStartTs());
}
- private State(final boolean isRecordEvents, final boolean isRecordEventProtos,
- @Nullable final Iterable prerecordedEventProtoIterable) {
- this(Maps.newHashMap(),
- CacheBuilder.newBuilder().weakValues().build(),
- CacheBuilder.newBuilder().weakKeys().build(),
- CacheBuilder.newBuilder().weakValues().build(),
- CacheBuilder.newBuilder().weakKeys().build(),
- CacheBuilder.newBuilder().weakValues().build(),
- CacheBuilder.newBuilder().weakKeys().build(),
+ protected EventState(final boolean isRecordEvents, final boolean isRecordEventProtos,
+ @Nullable final Iterable prerecordedEventProtoIterable) {
+ this(
isRecordEventProtos ? Lists.newArrayList() : null,
isRecordEvents ? Lists.newArrayList() : null,
prerecordedEventProtoIterable,
@@ -134,13 +92,7 @@ private State(final boolean isRecordEvents, final boolean isRecordEventProtos,
System.nanoTime());
}
- private State(@Nonnull final Map, Integer> classToIndexMap,
- @Nonnull final Cache expressionCache,
- @Nonnull final Cache invertedExpressionsCache,
- @Nonnull final Cache referenceCache,
- @Nonnull final Cache invertedReferenceCache,
- @Nonnull final Cache quantifierCache,
- @Nonnull final Cache invertedQuantifierCache,
+ protected EventState(
@Nullable final List events,
@Nullable final List eventProtos,
@Nullable final Iterable prerecordedEventProtoIterable,
@@ -149,13 +101,7 @@ private State(@Nonnull final Map, Integer> classToIndexMap,
@Nonnull final Deque, EventDurations>> eventProfilingStack,
final int currentTick,
final long startTs) {
- this.classToIndexMap = Maps.newHashMap(classToIndexMap);
- this.expressionCache = expressionCache;
- this.invertedExpressionsCache = invertedExpressionsCache;
- this.referenceCache = referenceCache;
- this.invertedReferenceCache = invertedReferenceCache;
- this.quantifierCache = quantifierCache;
- this.invertedQuantifierCache = invertedQuantifierCache;
+
this.events = events;
this.eventProtos = eventProtos;
this.prerecordedEventProtoIterable = prerecordedEventProtoIterable;
@@ -168,41 +114,6 @@ private State(@Nonnull final Map, Integer> classToIndexMap,
this.startTs = startTs;
}
- @Nonnull
- private Map, Integer> getClassToIndexMap() {
- return classToIndexMap;
- }
-
- @Nonnull
- public Cache getExpressionCache() {
- return expressionCache;
- }
-
- @Nonnull
- public Cache getInvertedExpressionsCache() {
- return invertedExpressionsCache;
- }
-
- @Nonnull
- public Cache getReferenceCache() {
- return referenceCache;
- }
-
- @Nonnull
- public Cache getInvertedReferenceCache() {
- return invertedReferenceCache;
- }
-
- @Nonnull
- public Cache getQuantifierCache() {
- return quantifierCache;
- }
-
- @Nonnull
- public Cache getInvertedQuantifierCache() {
- return invertedQuantifierCache;
- }
-
@Nullable
public List getEvents() {
return events;
@@ -226,41 +137,6 @@ public long getStartTs() {
return startTs;
}
- public int getIndex(final Class> clazz) {
- return classToIndexMap.getOrDefault(clazz, 0);
- }
-
- @CanIgnoreReturnValue
- public int updateIndex(final Class> clazz, IntUnaryOperator computeFn) {
- return classToIndexMap.compute(clazz, (c, value) -> value == null ? computeFn.applyAsInt(0) : computeFn.applyAsInt(value));
- }
-
- public void registerExpression(final RelationalExpression expression) {
- if (invertedExpressionsCache.getIfPresent(expression) == null) {
- final int index = getIndex(RelationalExpression.class);
- expressionCache.put(index, expression);
- invertedExpressionsCache.put(expression, index);
- updateIndex(RelationalExpression.class, i -> i + 1);
- }
- }
-
- public void registerReference(final Reference reference) {
- if (invertedReferenceCache.getIfPresent(reference) == null) {
- final int index = getIndex(Reference.class);
- referenceCache.put(index, reference);
- invertedReferenceCache.put(reference, index);
- updateIndex(Reference.class, i -> i + 1);
- }
- }
-
- public void registerQuantifier(final Quantifier quantifier) {
- if (invertedQuantifierCache.getIfPresent(quantifier) == null) {
- final int index = getIndex(Quantifier.class);
- quantifierCache.put(index, quantifier);
- invertedQuantifierCache.put(quantifier, index);
- updateIndex(Quantifier.class, i -> i + 1);
- }
- }
@SuppressWarnings("unchecked")
public void addCurrentEvent(@Nonnull final Debugger.Event event) {
diff --git a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/debug/StatsDebugger.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/debug/StatsDebugger.java
new file mode 100644
index 0000000000..87a8adf397
--- /dev/null
+++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/debug/StatsDebugger.java
@@ -0,0 +1,51 @@
+/*
+ * StatsDebugger.java
+ *
+ * This source file is part of the FoundationDB open source project
+ *
+ * Copyright 2015-2025 Apple Inc. and the FoundationDB project authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.apple.foundationdb.record.query.plan.cascades.debug;
+
+import javax.annotation.Nonnull;
+import java.util.Optional;
+import java.util.function.Consumer;
+import java.util.function.Function;
+
+public interface StatsDebugger extends Debugger {
+ @Nonnull
+ static Optional getDebuggerMaybe() {
+ final var debugger = Debugger.getDebugger();
+ if (debugger instanceof StatsDebugger) {
+ return Optional.of((StatsDebugger)debugger);
+ }
+ return Optional.empty();
+ }
+
+ static void withDebugger(@Nonnull final Consumer action) {
+ getDebuggerMaybe().ifPresent(action);
+ }
+
+ @Nonnull
+ static Optional mapDebugger(@Nonnull final Function function) {
+ return getDebuggerMaybe().map(function);
+ }
+
+ @Nonnull
+ Optional getStatsMaps();
+
+ void onEvent(Debugger.Event event);
+}
diff --git a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/debug/SymbolDebugger.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/debug/SymbolDebugger.java
new file mode 100644
index 0000000000..fcad8bceb6
--- /dev/null
+++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/debug/SymbolDebugger.java
@@ -0,0 +1,95 @@
+/*
+ * SymbolDebugger.java
+ *
+ * This source file is part of the FoundationDB open source project
+ *
+ * Copyright 2015-2025 Apple Inc. and the FoundationDB project authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.apple.foundationdb.record.query.plan.cascades.debug;
+
+import com.apple.foundationdb.record.query.plan.cascades.Quantifier;
+import com.apple.foundationdb.record.query.plan.cascades.Reference;
+import com.apple.foundationdb.record.query.plan.cascades.expressions.RelationalExpression;
+import com.google.errorprone.annotations.CanIgnoreReturnValue;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import java.util.Optional;
+import java.util.function.Consumer;
+import java.util.function.Function;
+import java.util.function.IntUnaryOperator;
+
+public interface SymbolDebugger extends Debugger {
+ @Nonnull
+ static Optional getDebuggerMaybe() {
+ final var debugger = Debugger.getDebugger();
+ if (debugger instanceof SymbolDebugger) {
+ return Optional.of((SymbolDebugger)debugger);
+ }
+ return Optional.empty();
+ }
+
+ static void withDebugger(@Nonnull final Consumer action) {
+ getDebuggerMaybe().ifPresent(action);
+ }
+
+
+ @Nonnull
+ static Optional mapDebugger(@Nonnull final Function function) {
+ return getDebuggerMaybe().map(function);
+ }
+
+ static Optional getIndexOptional(Class> clazz) {
+ return mapDebugger(debugger -> debugger.onGetIndex(clazz));
+ }
+
+ @Nonnull
+ @CanIgnoreReturnValue
+ static Optional updateIndex(Class> clazz, IntUnaryOperator updateFn) {
+ return mapDebugger(debugger -> debugger.onUpdateIndex(clazz, updateFn));
+ }
+
+ static void registerExpression(RelationalExpression expression) {
+ withDebugger(debugger -> debugger.onRegisterExpression(expression));
+ }
+
+ static void registerReference(Reference reference) {
+ withDebugger(debugger -> debugger.onRegisterReference(reference));
+ }
+
+ static void registerQuantifier(Quantifier quantifier) {
+ withDebugger(debugger -> debugger.onRegisterQuantifier(quantifier));
+ }
+
+ static Optional getOrRegisterSingleton(Object singleton) {
+ return mapDebugger(debugger -> debugger.onGetOrRegisterSingleton(singleton));
+ }
+
+ int onGetIndex(@Nonnull Class> clazz);
+
+ int onUpdateIndex(@Nonnull Class> clazz, @Nonnull IntUnaryOperator updateFn);
+
+ void onRegisterExpression(@Nonnull RelationalExpression expression);
+
+ void onRegisterReference(@Nonnull Reference reference);
+
+ void onRegisterQuantifier(@Nonnull Quantifier quantifier);
+
+ int onGetOrRegisterSingleton(@Nonnull Object singleton);
+
+ @Nullable
+ String nameForObject(@Nonnull Object object);
+}
diff --git a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/debug/SymbolTables.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/debug/SymbolTables.java
new file mode 100644
index 0000000000..60e4e2d850
--- /dev/null
+++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/debug/SymbolTables.java
@@ -0,0 +1,169 @@
+/*
+ * SymbolState.java
+ *
+ * This source file is part of the FoundationDB open source project
+ *
+ * Copyright 2015-2025 Apple Inc. and the FoundationDB project authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.apple.foundationdb.record.query.plan.cascades.debug;
+
+import com.apple.foundationdb.record.query.plan.cascades.Quantifier;
+import com.apple.foundationdb.record.query.plan.cascades.Reference;
+import com.apple.foundationdb.record.query.plan.cascades.expressions.RelationalExpression;
+import com.google.common.cache.Cache;
+import com.google.common.cache.CacheBuilder;
+import com.google.common.collect.Maps;
+import com.google.errorprone.annotations.CanIgnoreReturnValue;
+
+
+
+import javax.annotation.Nonnull;
+import java.util.Map;
+import java.util.function.IntUnaryOperator;
+
+public class SymbolTables {
+ @Nonnull
+ private final Map, Integer> classToIndexMap;
+ @Nonnull
+ private final Cache expressionCache;
+ @Nonnull private final Cache invertedExpressionsCache;
+ @Nonnull private final Cache referenceCache;
+ @Nonnull private final Cache invertedReferenceCache;
+ @Nonnull private final Cache quantifierCache;
+ @Nonnull private final Cache invertedQuantifierCache;
+
+ public static SymbolTables copyOf(final SymbolTables source) {
+ final Cache copyExpressionCache = CacheBuilder.newBuilder().weakValues().build();
+ source.getExpressionCache().asMap().forEach(copyExpressionCache::put);
+ final Cache copyInvertedExpressionsCache = CacheBuilder.newBuilder().weakKeys().build();
+ source.getInvertedExpressionsCache().asMap().forEach(copyInvertedExpressionsCache::put);
+ final Cache copyReferenceCache = CacheBuilder.newBuilder().weakValues().build();
+ source.getReferenceCache().asMap().forEach(copyReferenceCache::put);
+ final Cache copyInvertedReferenceCache = CacheBuilder.newBuilder().weakKeys().build();
+ source.getInvertedReferenceCache().asMap().forEach(copyInvertedReferenceCache::put);
+ final Cache copyQuantifierCache = CacheBuilder.newBuilder().weakValues().build();
+ source.getQuantifierCache().asMap().forEach(copyQuantifierCache::put);
+ final Cache copyInvertedQuantifierCache = CacheBuilder.newBuilder().weakKeys().build();
+ source.getInvertedQuantifierCache().asMap().forEach(copyInvertedQuantifierCache::put);
+
+ return new SymbolTables(source.getClassToIndexMap(),
+ copyExpressionCache,
+ copyInvertedExpressionsCache,
+ copyReferenceCache,
+ copyInvertedReferenceCache,
+ copyQuantifierCache,
+ copyInvertedQuantifierCache);
+ }
+
+ public SymbolTables() {
+ classToIndexMap = Maps.newHashMap();
+ expressionCache = CacheBuilder.newBuilder().weakValues().build();
+ invertedExpressionsCache = CacheBuilder.newBuilder().weakKeys().build();
+ referenceCache = CacheBuilder.newBuilder().weakValues().build();
+ invertedReferenceCache = CacheBuilder.newBuilder().weakKeys().build();
+ quantifierCache = CacheBuilder.newBuilder().weakValues().build();
+ invertedQuantifierCache = CacheBuilder.newBuilder().weakKeys().build();
+ }
+
+ private SymbolTables(@Nonnull final Map, Integer> classToIndexMap,
+ @Nonnull final Cache expressionCache,
+ @Nonnull final Cache invertedExpressionsCache,
+ @Nonnull final Cache referenceCache,
+ @Nonnull final Cache invertedReferenceCache,
+ @Nonnull final Cache quantifierCache,
+ @Nonnull final Cache invertedQuantifierCache) {
+ this.classToIndexMap = Maps.newHashMap(classToIndexMap);
+ this.expressionCache = expressionCache;
+ this.invertedExpressionsCache = invertedExpressionsCache;
+ this.referenceCache = referenceCache;
+ this.invertedReferenceCache = invertedReferenceCache;
+ this.quantifierCache = quantifierCache;
+ this.invertedQuantifierCache = invertedQuantifierCache;
+ }
+
+ @Nonnull
+ private Map, Integer> getClassToIndexMap() {
+ return classToIndexMap;
+ }
+
+ @Nonnull
+ public Cache getExpressionCache() {
+ return expressionCache;
+ }
+
+ @Nonnull
+ public Cache getInvertedExpressionsCache() {
+ return invertedExpressionsCache;
+ }
+
+ @Nonnull
+ public Cache getReferenceCache() {
+ return referenceCache;
+ }
+
+ @Nonnull
+ public Cache getInvertedReferenceCache() {
+ return invertedReferenceCache;
+ }
+
+ @Nonnull
+ public Cache getQuantifierCache() {
+ return quantifierCache;
+ }
+
+ @Nonnull
+ public Cache getInvertedQuantifierCache() {
+ return invertedQuantifierCache;
+ }
+
+
+ public int getIndex(final Class> clazz) {
+ return classToIndexMap.getOrDefault(clazz, 0);
+ }
+
+ @CanIgnoreReturnValue
+ public int updateIndex(final Class> clazz, IntUnaryOperator computeFn) {
+ return classToIndexMap.compute(clazz, (c, value) -> value == null ? computeFn.applyAsInt(0) : computeFn.applyAsInt(value));
+ }
+
+ public void registerExpression(final RelationalExpression expression) {
+ if (invertedExpressionsCache.getIfPresent(expression) == null) {
+ final int index = getIndex(RelationalExpression.class);
+ expressionCache.put(index, expression);
+ invertedExpressionsCache.put(expression, index);
+ updateIndex(RelationalExpression.class, i -> i + 1);
+ }
+ }
+
+ public void registerReference(final Reference reference) {
+ if (invertedReferenceCache.getIfPresent(reference) == null) {
+ final int index = getIndex(Reference.class);
+ referenceCache.put(index, reference);
+ invertedReferenceCache.put(reference, index);
+ updateIndex(Reference.class, i -> i + 1);
+ }
+ }
+
+ public void registerQuantifier(final Quantifier quantifier) {
+ if (invertedQuantifierCache.getIfPresent(quantifier) == null) {
+ final int index = getIndex(Quantifier.class);
+ quantifierCache.put(index, quantifier);
+ invertedQuantifierCache.put(quantifier, index);
+ updateIndex(Quantifier.class, i -> i + 1);
+ }
+ }
+
+}
diff --git a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/explain/PlannerGraphVisitor.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/explain/PlannerGraphVisitor.java
index 721fb3e424..df71082516 100644
--- a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/explain/PlannerGraphVisitor.java
+++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/explain/PlannerGraphVisitor.java
@@ -26,7 +26,7 @@
import com.apple.foundationdb.record.query.plan.cascades.SimpleExpressionVisitor;
import com.apple.foundationdb.record.query.plan.cascades.Traversal;
import com.apple.foundationdb.record.query.plan.cascades.debug.BrowserHelper;
-import com.apple.foundationdb.record.query.plan.cascades.debug.Debugger;
+import com.apple.foundationdb.record.query.plan.cascades.debug.SymbolDebugger;
import com.apple.foundationdb.record.query.plan.cascades.explain.GraphExporter.Cluster;
import com.apple.foundationdb.record.query.plan.cascades.explain.GraphExporter.ComponentIdProvider;
import com.apple.foundationdb.record.query.plan.cascades.explain.PlannerGraph.Edge;
@@ -323,7 +323,7 @@ private static Collection> clustersForGroups(final Network {
- final String label = Debugger.mapDebugger(debugger -> debugger.nameForObject(entry.getKey().getIdentity()))
+ final String label = SymbolDebugger.mapDebugger(debugger -> debugger.nameForObject(entry.getKey().getIdentity()))
.orElse("group");
return new GroupCluster(label, entry.getValue());
})
@@ -509,7 +509,7 @@ public PlannerGraph evaluateAtRef(@Nonnull final Reference ref, @Nonnull List {
final Node root = childGraph.getRoot();
final Optional debugNameOptional =
- Debugger.mapDebugger(debugger -> {
+ SymbolDebugger.mapDebugger(debugger -> {
if (root instanceof PlannerGraph.WithExpression) {
final PlannerGraph.WithExpression withExpression = (PlannerGraph.WithExpression)root;
@Nullable final RelationalExpression expression = withExpression.getExpression();
diff --git a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/rules/ImplementNestedLoopJoinRule.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/rules/ImplementNestedLoopJoinRule.java
index 7d1e65a68e..053dcb621f 100644
--- a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/rules/ImplementNestedLoopJoinRule.java
+++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/rules/ImplementNestedLoopJoinRule.java
@@ -29,6 +29,7 @@
import com.apple.foundationdb.record.query.plan.cascades.Reference;
import com.apple.foundationdb.record.query.plan.cascades.RequestedOrderingConstraint;
import com.apple.foundationdb.record.query.plan.cascades.debug.Debugger;
+import com.apple.foundationdb.record.query.plan.cascades.debug.SymbolDebugger;
import com.apple.foundationdb.record.query.plan.cascades.expressions.SelectExpression;
import com.apple.foundationdb.record.query.plan.cascades.matching.structure.BindingMatcher;
import com.apple.foundationdb.record.query.plan.cascades.predicates.QueryPredicate;
@@ -111,7 +112,7 @@ public void onMatch(@Nonnull final ImplementationCascadesRuleCall call) {
final var outerPartition = bindings.get(outerPlanPartitionsMatcher);
final var innerPartition = bindings.get(innerPlanPartitionsMatcher);
- final var joinName = Debugger.mapDebugger(debugger -> debugger.nameForObject(call.getRoot()) + "[" + debugger.nameForObject(selectExpression) + "]: " + outerQuantifier.getAlias() + " ⨝ " + innerQuantifier.getAlias()).orElse("not in debug mode");
+ final var joinName = SymbolDebugger.mapDebugger(debugger -> debugger.nameForObject(call.getRoot()) + "[" + debugger.nameForObject(selectExpression) + "]: " + outerQuantifier.getAlias() + " ⨝ " + innerQuantifier.getAlias()).orElse("not in debug mode");
Debugger.withDebugger(debugger -> logger.debug(KeyValueLogMessage.of("attempting join", "joinedTables", joinName, "requestedOrderings", requestedOrderings)));
final var fullCorrelationOrder =
diff --git a/fdb-record-layer-debugger/src/main/java/com/apple/foundationdb/record/query/plan/cascades/debug/Commands.java b/fdb-record-layer-debugger/src/main/java/com/apple/foundationdb/record/query/plan/cascades/debug/Commands.java
index 128eb8f900..5abe3864da 100644
--- a/fdb-record-layer-debugger/src/main/java/com/apple/foundationdb/record/query/plan/cascades/debug/Commands.java
+++ b/fdb-record-layer-debugger/src/main/java/com/apple/foundationdb/record/query/plan/cascades/debug/Commands.java
@@ -309,13 +309,13 @@ public static class CurrentCommand implements Command {
public boolean executeCommand(@Nonnull final PlannerRepl plannerRepl,
@Nonnull final Event event,
@Nonnull final ParsedLine parsedLine) {
- final State state = plannerRepl.getCurrentState();
- final List events = state.getEvents();
+ final EventState eventState = plannerRepl.getCurrentState();
+ final List events = eventState.getEvents();
if (events == null) {
plannerRepl.printlnError("Configuration mandates to not record events. Please turn on event recording and restart.");
return false;
}
- final Event e = events.get(state.getCurrentTick());
+ final Event e = events.get(eventState.getCurrentTick());
plannerRepl.withProcessors(e, processor -> processor.onDetail(plannerRepl, e));
return false;
}
@@ -341,15 +341,15 @@ public static class EventsCommand implements Command {
public boolean executeCommand(@Nonnull final PlannerRepl plannerRepl,
@Nonnull final Event event,
@Nonnull final ParsedLine parsedLine) {
- final State state = plannerRepl.getCurrentState();
- final List events = state.getEvents();
+ final EventState eventState = plannerRepl.getCurrentState();
+ final List events = eventState.getEvents();
if (events == null) {
plannerRepl.printlnError("Configuration mandates to not record events. Please turn on event recording and restart.");
return false;
}
for (int tick = 0; tick < events.size(); tick++) {
final Event e = events.get(tick);
- if (state.getCurrentTick() == tick) {
+ if (eventState.getCurrentTick() == tick) {
plannerRepl.printHighlighted("==> ");
} else {
plannerRepl.print(" ");
@@ -382,7 +382,7 @@ public static class ExpsCommand implements Command {
public boolean executeCommand(@Nonnull final PlannerRepl plannerRepl,
@Nonnull final Event event,
@Nonnull final ParsedLine parsedLine) {
- final State state = plannerRepl.getCurrentState();
+ final SymbolTables state = plannerRepl.getCurrentSymbolState();
final Cache expressionCache = state.getExpressionCache();
final List ids = Lists.newArrayList(expressionCache.asMap().keySet());
Collections.sort(ids);
@@ -452,7 +452,7 @@ public static class RefsCommand implements Command {
public boolean executeCommand(@Nonnull final PlannerRepl plannerRepl,
@Nonnull final Event event,
@Nonnull final ParsedLine parsedLine) {
- final State state = plannerRepl.getCurrentState();
+ final SymbolTables state = plannerRepl.getCurrentSymbolState();
final Cache referenceCache = state.getReferenceCache();
final List ids = Lists.newArrayList(referenceCache.asMap().keySet());
Collections.sort(ids);
@@ -724,7 +724,7 @@ public static class QunsCommand implements Command {
public boolean executeCommand(@Nonnull final PlannerRepl plannerRepl,
@Nonnull final Event event,
@Nonnull final ParsedLine parsedLine) {
- final State state = plannerRepl.getCurrentState();
+ final SymbolTables state = plannerRepl.getCurrentSymbolState();
final List ids = Lists.newArrayList(state.getQuantifierCache().asMap().keySet());
Collections.sort(ids);
for (Integer id : ids) {
diff --git a/fdb-record-layer-debugger/src/main/java/com/apple/foundationdb/record/query/plan/cascades/debug/PlannerRepl.java b/fdb-record-layer-debugger/src/main/java/com/apple/foundationdb/record/query/plan/cascades/debug/PlannerRepl.java
index 2d52da7453..6df35a0fb7 100644
--- a/fdb-record-layer-debugger/src/main/java/com/apple/foundationdb/record/query/plan/cascades/debug/PlannerRepl.java
+++ b/fdb-record-layer-debugger/src/main/java/com/apple/foundationdb/record/query/plan/cascades/debug/PlannerRepl.java
@@ -69,7 +69,7 @@
/**
* Implementation of a debugger as a repl.
*/
-public class PlannerRepl implements Debugger {
+public class PlannerRepl implements StatsDebugger, SymbolDebugger {
private static final Logger logger = LoggerFactory.getLogger(PlannerRepl.class);
private static final String banner =
@@ -92,7 +92,8 @@ public class PlannerRepl implements Debugger {
processorsMap = loadProcessors();
}
- private final Deque stateStack;
+ private final Deque eventStateStack;
+ private final Deque symbolTablesStack;
private final BiMap breakPoints;
private int currentBreakPointIndex;
@@ -117,7 +118,8 @@ public PlannerRepl(@Nonnull final Terminal terminal) {
}
public PlannerRepl(@Nonnull final Terminal terminal, boolean exitOnQuit) {
- this.stateStack = new ArrayDeque<>();
+ this.eventStateStack = new ArrayDeque<>();
+ this.symbolTablesStack = new ArrayDeque<>();
this.breakPoints = HashBiMap.create();
this.currentBreakPointIndex = 0;
this.currentInternalBreakPointIndex = -1;
@@ -133,10 +135,15 @@ boolean shouldExitOnQuit() {
}
@Nonnull
- State getCurrentState() {
- return Objects.requireNonNull(stateStack.peek());
+ SymbolTables getCurrentSymbolState() {
+ return Objects.requireNonNull(symbolTablesStack.peek());
}
+ EventState getCurrentState() {
+ return Objects.requireNonNull(eventStateStack.peek());
+ }
+
+
@Nullable
@Override
public PlanContext getPlanContext() {
@@ -151,27 +158,27 @@ public boolean isSane() {
@Override
public int onGetIndex(@Nonnull final Class> clazz) {
- return getCurrentState().getIndex(clazz);
+ return getCurrentSymbolState().getIndex(clazz);
}
@Override
public int onUpdateIndex(@Nonnull final Class> clazz, @Nonnull final IntUnaryOperator updateFn) {
- return getCurrentState().updateIndex(clazz, updateFn);
+ return getCurrentSymbolState().updateIndex(clazz, updateFn);
}
@Override
public void onRegisterExpression(@Nonnull final RelationalExpression expression) {
- getCurrentState().registerExpression(expression);
+ getCurrentSymbolState().registerExpression(expression);
}
@Override
public void onRegisterReference(@Nonnull final Reference reference) {
- getCurrentState().registerReference(reference);
+ getCurrentSymbolState().registerReference(reference);
}
@Override
public void onRegisterQuantifier(@Nonnull final Quantifier quantifier) {
- getCurrentState().registerQuantifier(quantifier);
+ getCurrentSymbolState().registerQuantifier(quantifier);
}
@Override
@@ -203,7 +210,9 @@ public void onShow(@Nonnull final Reference ref) {
@Override
public void onQuery(@Nonnull final String queryAsString, @Nonnull final PlanContext planContext) {
- this.stateStack.push(State.copyOf(getCurrentState()));
+ this.eventStateStack.push(EventState.copyOf(getCurrentState()));
+ this.symbolTablesStack.push(SymbolTables.copyOf(getCurrentSymbolState()));
+
this.queryAsString = queryAsString;
this.planContext = planContext;
@@ -214,8 +223,11 @@ public void onQuery(@Nonnull final String queryAsString, @Nonnull final PlanCont
}
void restartState() {
- stateStack.pop();
- stateStack.push(State.copyOf(getCurrentState()));
+ eventStateStack.pop();
+ eventStateStack.push(EventState.copyOf(getCurrentState()));
+
+ symbolTablesStack.pop();
+ symbolTablesStack.push(SymbolTables.copyOf(getCurrentSymbolState()));
}
void addBreakPoint(final BreakPoint breakPoint) {
@@ -255,9 +267,9 @@ public void onEvent(final Event event) {
Objects.requireNonNull(queryAsString);
Objects.requireNonNull(planContext);
- final State state = getCurrentState();
+ final EventState eventState = getCurrentState();
- state.addCurrentEvent(event);
+ eventState.addCurrentEvent(event);
final Set satisfiedBreakPoints = computeSatisfiedBreakPoints(event);
satisfiedBreakPoints.forEach(breakPoint -> breakPoint.onBreak(this));
@@ -265,7 +277,7 @@ public void onEvent(final Event event) {
final boolean stop = !satisfiedBreakPoints.isEmpty();
if (stop) {
printKeyValue("paused in", Thread.currentThread().getName() + " at ");
- printlnKeyValue("tick", String.valueOf(state.getCurrentTick()));
+ printlnKeyValue("tick", String.valueOf(eventState.getCurrentTick()));
withProcessors(event, processor -> processor.onCallback(this, event));
println();
@@ -332,7 +344,7 @@ boolean processIdentifiers(final String potentialIdentifier,
final Consumer expressionConsumer,
final Consumer referenceConsumer,
final Consumer quantifierConsumer) {
- final State state = getCurrentState();
+ final SymbolTables state = getCurrentSymbolState();
final String upperCasePotentialIdentifier = potentialIdentifier.toUpperCase(Locale.ROOT);
if (upperCasePotentialIdentifier.startsWith("EXP")) {
@Nullable final RelationalExpression expression = lookupInCache(state.getExpressionCache(), upperCasePotentialIdentifier, "EXP");
@@ -399,7 +411,7 @@ boolean isValidEntityName(@Nonnull final String identifier) {
@Nullable
@Override
public String nameForObject(@Nonnull final Object object) {
- final State state = getCurrentState();
+ final SymbolTables state = getCurrentSymbolState();
if (object instanceof RelationalExpression) {
@Nullable final Integer id = state.getInvertedExpressionsCache().getIfPresent(object);
return (id == null) ? null : "exp" + id;
@@ -453,28 +465,21 @@ public void onDone() {
reset();
}
- @Override
- public String showStats() {
- State currentState = stateStack.peek();
- if (currentState != null) {
- return currentState.showStats();
- }
- return "no stats";
- }
-
@Nonnull
@Override
public Optional getStatsMaps() {
- State currentState = stateStack.peek();
- if (currentState != null) {
- return Optional.of(currentState.getStatsMaps());
+ EventState currentEventState = eventStateStack.peek();
+ if (currentEventState != null) {
+ return Optional.of(currentEventState.getStatsMaps());
}
return Optional.empty();
}
private void reset() {
- this.stateStack.clear();
- this.stateStack.push(State.initial(true, true, null));
+ this.eventStateStack.clear();
+ this.eventStateStack.push(EventState.initial(true, true, null));
+ this.symbolTablesStack.clear();
+ this.symbolTablesStack.push(new SymbolTables());
this.breakPoints.clear();
this.currentBreakPointIndex = 0;
this.currentInternalBreakPointIndex = -1;
From bdf2e166bf05b9141edbcf72811afd3df9cd4fbd Mon Sep 17 00:00:00 2001
From: Hazem Zaghloul
Date: Thu, 7 Aug 2025 17:36:41 +0100
Subject: [PATCH 4/9] Extract showing Debugger stats in the browser to a
separate class
---
.../query/plan/cascades/debug/EventState.java | 82 ------------
.../plan/cascades/debug/StatsViewer.java | 121 ++++++++++++++++++
2 files changed, 121 insertions(+), 82 deletions(-)
create mode 100644 fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/debug/StatsViewer.java
diff --git a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/debug/EventState.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/debug/EventState.java
index 67af117caa..94cd18e6f9 100644
--- a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/debug/EventState.java
+++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/debug/EventState.java
@@ -252,88 +252,6 @@ StatsMaps getStatsMaps() {
return new StatsMaps(eventClassStatsMap, plannerRuleClassStatsMap);
}
- public String showStats() {
- StringBuilder tableBuilder = new StringBuilder();
- tableBuilder.append("");
- tableHeader(tableBuilder, "Event");
- final ImmutableMap eventStatsMap =
- eventClassStatsMap.entrySet()
- .stream()
- .map(entry -> Pair.of(entry.getKey().getSimpleName(), entry.getValue()))
- .sorted(Map.Entry.comparingByKey())
- .collect(ImmutableMap.toImmutableMap(Pair::getKey, Pair::getValue));
- tableBody(tableBuilder, eventStatsMap);
- tableBuilder.append("
");
-
- final String eventProfilingString = tableBuilder.toString();
-
- tableBuilder = new StringBuilder();
- tableBuilder.append("");
- tableHeader(tableBuilder, "Planner Rule");
- final ImmutableMap plannerRuleStatsMap =
- plannerRuleClassStatsMap.entrySet()
- .stream()
- .map(entry -> Pair.of(entry.getKey().getSimpleName(), entry.getValue()))
- .sorted(Map.Entry.comparingByKey())
- .collect(ImmutableMap.toImmutableMap(Pair::getKey, Pair::getValue));
- tableBody(tableBuilder, plannerRuleStatsMap);
- tableBuilder.append("
");
-
- final String plannerRuleProfilingString = tableBuilder.toString();
-
- return BrowserHelper.browse("/showProfilingReport.html",
- ImmutableMap.of("$EVENT_PROFILING", eventProfilingString,
- "$PLANNER_RULE_PROFILING", plannerRuleProfilingString));
- }
-
- private void tableHeader(@Nonnull final StringBuilder stringBuilder, @Nonnull final String category) {
- stringBuilder.append("");
- stringBuilder.append("");
- stringBuilder.append("").append(category).append(" | ");
- stringBuilder.append("Location | ");
- stringBuilder.append("Count | ");
- stringBuilder.append("Total Time (micros) | ");
- stringBuilder.append("Average Time (micros) | ");
- stringBuilder.append("Total Own Time (micros) | ");
- stringBuilder.append("Average Own Time (micros) | ");
- stringBuilder.append("
");
- stringBuilder.append("");
- }
-
- private void tableBody(@Nonnull final StringBuilder stringBuilder, @Nonnull final Map statsMap) {
- stringBuilder.append("");
- for (final Map.Entry entry : statsMap.entrySet()) {
- final MutableStats mutableStats = entry.getValue();
- for (final var locationEntry : mutableStats.getLocationCountMap().entrySet()) {
- stringBuilder.append("");
- stringBuilder.append("").append(entry.getKey()).append(" | ");
- if (locationEntry.getKey() == Debugger.Location.BEGIN) {
- stringBuilder.append(" | ");
- } else {
- stringBuilder.append("").append(locationEntry.getKey().name()).append(" | ");
- }
- stringBuilder.append("").append(locationEntry.getValue()).append(" | ");
- if (locationEntry.getKey() == Debugger.Location.BEGIN) {
- stringBuilder.append("").append(formatNsInMicros(mutableStats.getTotalTimeInNs())).append(" | ");
- stringBuilder.append("").append(formatNsInMicros(mutableStats.getTotalTimeInNs() / mutableStats.getCount(Debugger.Location.BEGIN))).append(" | ");
- stringBuilder.append("").append(formatNsInMicros(mutableStats.getOwnTimeInNs())).append(" | ");
- stringBuilder.append("").append(formatNsInMicros(mutableStats.getOwnTimeInNs() / mutableStats.getCount(Debugger.Location.BEGIN))).append(" | ");
- } else {
- stringBuilder.append(" | ");
- stringBuilder.append(" | ");
- }
- stringBuilder.append("
");
- }
- }
- stringBuilder.append("");
- }
-
- @Nonnull
- private String formatNsInMicros(final long ns) {
- final long micros = TimeUnit.NANOSECONDS.toMicros(ns);
- return String.format(Locale.ROOT, "%,d", micros);
- }
-
private static class MutableStats extends Stats {
public MutableStats() {
super(Maps.newLinkedHashMap(), 0L, 0L);
diff --git a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/debug/StatsViewer.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/debug/StatsViewer.java
new file mode 100644
index 0000000000..3c6ad206fe
--- /dev/null
+++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/debug/StatsViewer.java
@@ -0,0 +1,121 @@
+/*
+ * StatsViewer.java
+ *
+ * This source file is part of the FoundationDB open source project
+ *
+ * Copyright 2015-2025 Apple Inc. and the FoundationDB project authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.apple.foundationdb.record.query.plan.cascades.debug;
+
+import com.apple.foundationdb.record.util.pair.Pair;
+import com.google.common.collect.ImmutableMap;
+
+import javax.annotation.Nonnull;
+import java.util.Locale;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+
+/** Only used by debuggers to display stats.*/
+@SuppressWarnings("unused")
+public class StatsViewer {
+ public static String showStats(@Nonnull final StatsDebugger statsDebugger) {
+ final var statsMaps = statsDebugger.getStatsMaps();
+
+ if (statsMaps.isEmpty()) {
+ return "no stats available";
+ }
+
+ StringBuilder tableBuilder = new StringBuilder();
+ tableBuilder.append("");
+ tableHeader(tableBuilder, "Event");
+ final ImmutableMap eventStatsMap =
+ statsMaps.get().getEventClassStatsMap().entrySet()
+ .stream()
+ .map(entry -> Pair.of(entry.getKey().getSimpleName(), entry.getValue()))
+ .sorted(Map.Entry.comparingByKey())
+ .collect(ImmutableMap.toImmutableMap(Pair::getKey, Pair::getValue));
+ tableBody(tableBuilder, eventStatsMap);
+ tableBuilder.append("
");
+
+ final String eventProfilingString = tableBuilder.toString();
+
+ tableBuilder = new StringBuilder();
+ tableBuilder.append("");
+ tableHeader(tableBuilder, "Planner Rule");
+ final ImmutableMap plannerRuleStatsMap =
+ statsMaps.get().getPlannerRuleClassStatsMap().entrySet()
+ .stream()
+ .map(entry -> Pair.of(entry.getKey().getSimpleName(), entry.getValue()))
+ .sorted(Map.Entry.comparingByKey())
+ .collect(ImmutableMap.toImmutableMap(Pair::getKey, Pair::getValue));
+ tableBody(tableBuilder, plannerRuleStatsMap);
+ tableBuilder.append("
");
+
+ final String plannerRuleProfilingString = tableBuilder.toString();
+
+ return BrowserHelper.browse("/showProfilingReport.html",
+ ImmutableMap.of("$EVENT_PROFILING", eventProfilingString,
+ "$PLANNER_RULE_PROFILING", plannerRuleProfilingString));
+ }
+
+ private static void tableHeader(@Nonnull final StringBuilder stringBuilder, @Nonnull final String category) {
+ stringBuilder.append("");
+ stringBuilder.append("");
+ stringBuilder.append("").append(category).append(" | ");
+ stringBuilder.append("Location | ");
+ stringBuilder.append("Count | ");
+ stringBuilder.append("Total Time (micros) | ");
+ stringBuilder.append("Average Time (micros) | ");
+ stringBuilder.append("Total Own Time (micros) | ");
+ stringBuilder.append("Average Own Time (micros) | ");
+ stringBuilder.append("
");
+ stringBuilder.append("");
+ }
+
+ private static void tableBody(@Nonnull final StringBuilder stringBuilder, @Nonnull final Map statsMap) {
+ stringBuilder.append("");
+ for (final Map.Entry entry : statsMap.entrySet()) {
+ final Stats stats = entry.getValue();
+ for (final var locationEntry : stats.getLocationCountMap().entrySet()) {
+ stringBuilder.append("");
+ stringBuilder.append("").append(entry.getKey()).append(" | ");
+ if (locationEntry.getKey() == Debugger.Location.BEGIN) {
+ stringBuilder.append(" | ");
+ } else {
+ stringBuilder.append("").append(locationEntry.getKey().name()).append(" | ");
+ }
+ stringBuilder.append("").append(locationEntry.getValue()).append(" | ");
+ if (locationEntry.getKey() == Debugger.Location.BEGIN) {
+ stringBuilder.append("").append(formatNsInMicros(stats.getTotalTimeInNs())).append(" | ");
+ stringBuilder.append("").append(formatNsInMicros(stats.getTotalTimeInNs() / stats.getCount(Debugger.Location.BEGIN))).append(" | ");
+ stringBuilder.append("").append(formatNsInMicros(stats.getOwnTimeInNs())).append(" | ");
+ stringBuilder.append("").append(formatNsInMicros(stats.getOwnTimeInNs() / stats.getCount(Debugger.Location.BEGIN))).append(" | ");
+ } else {
+ stringBuilder.append(" | ");
+ stringBuilder.append(" | ");
+ }
+ stringBuilder.append("
");
+ }
+ }
+ stringBuilder.append("");
+ }
+
+ @Nonnull
+ private static String formatNsInMicros(final long ns) {
+ final long micros = TimeUnit.NANOSECONDS.toMicros(ns);
+ return String.format(Locale.ROOT, "%,d", micros);
+ }
+}
From cf9848b04f555dd5ec9366d31fed3d19f80aa291 Mon Sep 17 00:00:00 2001
From: Hazem Zaghloul
Date: Thu, 7 Aug 2025 17:40:57 +0100
Subject: [PATCH 5/9] Group Debugger events by PlannerPhase in stats
This commit modifies the EventState class and the StatsMaps that it produces
to group Debugger events by the planner phase it was emitted during to enable
the ability of getting stats for each planner phase separately.
Given that some Debugger events (e.g. InsertIntoMemoEvent) do not store the
planner phase they were emitted in (as it not visible to the code that emits
them), these events are stored separately and can be retrieved from the
StatsMaps (in addition to the ability of retrieving all events, regardless
of the planner phase that they were emitted in).
---
.../query/plan/cascades/debug/EventState.java | 72 +++++++++++-------
.../query/plan/cascades/debug/Stats.java | 12 +++
.../query/plan/cascades/debug/StatsMaps.java | 75 +++++++++++++++++--
.../plan/cascades/debug/StatsViewer.java | 38 +++++++---
4 files changed, 153 insertions(+), 44 deletions(-)
diff --git a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/debug/EventState.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/debug/EventState.java
index 94cd18e6f9..d75ccf8730 100644
--- a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/debug/EventState.java
+++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/debug/EventState.java
@@ -23,10 +23,10 @@
import com.apple.foundationdb.record.RecordCoreException;
import com.apple.foundationdb.record.logging.KeyValueLogMessage;
import com.apple.foundationdb.record.query.plan.cascades.CascadesRule;
+import com.apple.foundationdb.record.query.plan.cascades.PlannerPhase;
import com.apple.foundationdb.record.query.plan.cascades.debug.eventprotos.PEvent;
import com.apple.foundationdb.record.util.pair.Pair;
import com.google.common.base.Verify;
-import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import org.slf4j.Logger;
@@ -35,29 +35,30 @@
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.ArrayDeque;
+import java.util.Collections;
import java.util.Deque;
import java.util.Iterator;
+import java.util.LinkedHashMap;
import java.util.List;
-import java.util.Locale;
import java.util.Map;
import java.util.Objects;
-import java.util.concurrent.TimeUnit;
@SuppressWarnings("PMD.SystemPrintln")
public class EventState {
@Nonnull
private static final Logger logger = LoggerFactory.getLogger(EventState.class);
- @Nullable private final List events;
- @Nullable private final List eventProtos;
- @Nullable private final Iterable prerecordedEventProtoIterable;
- @Nullable private Iterator prerecordedEventProtoIterator;
+ @Nullable protected final List events;
+ @Nullable protected final List eventProtos;
+ @Nullable protected final Iterable prerecordedEventProtoIterable;
+ @Nullable protected Iterator prerecordedEventProtoIterator;
- @Nonnull private final Map, MutableStats> eventClassStatsMap;
+ @Nonnull protected final Map, MutableStats> eventWithoutStateClassStatsMap;
+ @Nonnull protected final Map, MutableStats>> eventWithStateClassStatsMapByPlannerPhase;
- @Nonnull private final Map>, MutableStats> plannerRuleClassStatsMap;
+ @Nonnull protected final Map>, MutableStats> plannerRuleClassStatsMap;
- @Nonnull private final Deque, EventDurations>> eventProfilingStack;
+ @Nonnull protected final Deque, EventDurations>> eventProfilingStack;
protected int currentTick;
protected final long startTs;
@@ -72,7 +73,8 @@ public static EventState copyOf(final EventState source) {
source.events == null ? null : Lists.newArrayList(source.events),
source.eventProtos == null ? null : Lists.newArrayList(source.eventProtos),
source.prerecordedEventProtoIterable,
- Maps.newLinkedHashMap(source.eventClassStatsMap),
+ Maps.newLinkedHashMap(source.eventWithoutStateClassStatsMap),
+ Maps.newEnumMap(source.eventWithStateClassStatsMapByPlannerPhase),
Maps.newLinkedHashMap(source.plannerRuleClassStatsMap),
new ArrayDeque<>(source.eventProfilingStack),
source.getCurrentTick(),
@@ -86,6 +88,7 @@ protected EventState(final boolean isRecordEvents, final boolean isRecordEventPr
isRecordEvents ? Lists.newArrayList() : null,
prerecordedEventProtoIterable,
Maps.newLinkedHashMap(),
+ Maps.newEnumMap(PlannerPhase.class),
Maps.newLinkedHashMap(),
new ArrayDeque<>(),
-1,
@@ -93,21 +96,23 @@ protected EventState(final boolean isRecordEvents, final boolean isRecordEventPr
}
protected EventState(
- @Nullable final List events,
- @Nullable final List eventProtos,
- @Nullable final Iterable prerecordedEventProtoIterable,
- @Nonnull final Map, MutableStats> eventClassStatsMap,
- @Nonnull final Map>, MutableStats> plannerRuleClassStatsMap,
- @Nonnull final Deque, EventDurations>> eventProfilingStack,
- final int currentTick,
- final long startTs) {
+ @Nullable final List events,
+ @Nullable final List eventProtos,
+ @Nullable final Iterable prerecordedEventProtoIterable,
+ @Nonnull final Map, MutableStats> eventWithoutStateClassStatsMap,
+ @Nonnull final Map, MutableStats>> eventWithStateClassStatsMapByPlannerPhase,
+ @Nonnull final Map>, MutableStats> plannerRuleClassStatsMap,
+ @Nonnull final Deque, EventDurations>> eventProfilingStack,
+ final int currentTick,
+ final long startTs) {
this.events = events;
this.eventProtos = eventProtos;
this.prerecordedEventProtoIterable = prerecordedEventProtoIterable;
this.prerecordedEventProtoIterator = prerecordedEventProtoIterable == null
? null : prerecordedEventProtoIterable.iterator();
- this.eventClassStatsMap = eventClassStatsMap;
+ this.eventWithoutStateClassStatsMap = eventWithoutStateClassStatsMap;
+ this.eventWithStateClassStatsMapByPlannerPhase = eventWithStateClassStatsMapByPlannerPhase;
this.plannerRuleClassStatsMap = plannerRuleClassStatsMap;
this.eventProfilingStack = eventProfilingStack;
this.currentTick = currentTick;
@@ -179,7 +184,7 @@ public void addCurrentEvent(@Nonnull final Debugger.Event event) {
final long totalTime = currentTsInNs - eventDurations.getStartTsInNs();
final long ownTime = totalTime - eventDurations.getAdjustmentForOwnTimeInNs();
- final MutableStats forEventClass = getEventStatsForEventClass(currentEventClass);
+ final MutableStats forEventClass = getEventStatsForEvent(event);
forEventClass.increaseTotalTimeInNs(totalTime);
forEventClass.increaseOwnTimeInNs(ownTime);
if (event instanceof Debugger.TransformRuleCallEvent) {
@@ -229,8 +234,9 @@ private void verifyCurrentEventProto(final PEvent currentEventProto) {
@SuppressWarnings("unchecked")
private void updateCounts(@Nonnull final Debugger.Event event) {
- final MutableStats forEventClass = getEventStatsForEventClass(event.getClass());
+ final MutableStats forEventClass = getEventStatsForEvent(event);
forEventClass.increaseCount(event.getLocation(), 1L);
+
if (event instanceof Debugger.EventWithRule) {
final CascadesRule> rule = ((Debugger.EventWithRule)event).getRule();
final Class extends CascadesRule>> ruleClass = (Class extends CascadesRule>>)rule.getClass();
@@ -239,17 +245,31 @@ private void updateCounts(@Nonnull final Debugger.Event event) {
}
}
- private MutableStats getEventStatsForEventClass(@Nonnull Class extends Debugger.Event> eventClass) {
- return eventClassStatsMap.compute(eventClass, (eC, mutableStats) -> mutableStats != null ? mutableStats : new MutableStats());
+ private MutableStats getEventStatsForEvent(@Nonnull Debugger.Event event) {
+ return (event instanceof Debugger.EventWithState) ?
+ getEventStatsForEventWithStateClassByPlannerPhase((Debugger.EventWithState)event) :
+ getEventStatsForEventWithoutStateClass(event.getClass());
+ }
+
+ private MutableStats getEventStatsForEventWithoutStateClass(@Nonnull Class extends Debugger.Event> eventClass) {
+ return eventWithoutStateClassStatsMap.compute(eventClass, (eC, mutableStats) -> mutableStats != null ? mutableStats : new MutableStats());
+ }
+
+ private MutableStats getEventStatsForEventWithStateClassByPlannerPhase(@Nonnull Debugger.EventWithState event) {
+ return eventWithStateClassStatsMapByPlannerPhase.computeIfAbsent(event.getPlannerPhase(), pP -> new LinkedHashMap<>())
+ .computeIfAbsent(event.getClass(), (eC) -> new MutableStats());
}
private MutableStats getEventStatsForPlannerRuleClass(@Nonnull Class extends CascadesRule>> plannerRuleClass) {
- return plannerRuleClassStatsMap.compute(plannerRuleClass, (eC, mutableStats) -> mutableStats != null ? mutableStats : new MutableStats());
+ return plannerRuleClassStatsMap.computeIfAbsent(plannerRuleClass, (eC) -> new MutableStats());
}
@Nonnull
StatsMaps getStatsMaps() {
- return new StatsMaps(eventClassStatsMap, plannerRuleClassStatsMap);
+ return new StatsMaps(
+ Collections.unmodifiableMap(eventWithoutStateClassStatsMap),
+ Collections.unmodifiableMap(eventWithStateClassStatsMapByPlannerPhase),
+ Collections.unmodifiableMap(plannerRuleClassStatsMap));
}
private static class MutableStats extends Stats {
diff --git a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/debug/Stats.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/debug/Stats.java
index 43e7f8a44d..62cd044502 100644
--- a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/debug/Stats.java
+++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/debug/Stats.java
@@ -24,6 +24,8 @@
import javax.annotation.Nonnull;
import java.util.Map;
+import java.util.Set;
+import java.util.stream.Stream;
public class Stats {
@Nonnull
@@ -61,4 +63,14 @@ public long getOwnTimeInNs() {
public Stats toImmutable() {
return new Stats(ImmutableMap.copyOf(locationCountMap), totalTimeInNs, ownTimeInNs);
}
+
+ public static Stats merge(final Stats first, final Stats second) {
+ return new Stats(
+ Stream.of(first.getLocationCountMap(), second.getLocationCountMap())
+ .map(Map::entrySet)
+ .flatMap(Set::stream)
+ .collect(ImmutableMap.toImmutableMap(Map.Entry::getKey, Map.Entry::getValue, Long::sum)),
+ first.getTotalTimeInNs() + second.getTotalTimeInNs(),
+ first.getOwnTimeInNs() + second.getOwnTimeInNs());
+ }
}
diff --git a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/debug/StatsMaps.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/debug/StatsMaps.java
index 3ccae3ad15..84d112b6d2 100644
--- a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/debug/StatsMaps.java
+++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/debug/StatsMaps.java
@@ -21,29 +21,45 @@
package com.apple.foundationdb.record.query.plan.cascades.debug;
import com.apple.foundationdb.record.query.plan.cascades.CascadesRule;
+import com.apple.foundationdb.record.query.plan.cascades.PlannerPhase;
import com.google.common.base.Suppliers;
import com.google.common.collect.ImmutableMap;
import javax.annotation.Nonnull;
+import java.util.LinkedHashMap;
import java.util.Map;
+import java.util.Optional;
import java.util.function.Supplier;
public class StatsMaps {
@Nonnull
- private final Map, ? extends Stats> eventClassStatsMap;
+ private final Map, ? extends Stats> eventWithoutStateClassStatsMap;
+ @Nonnull
+ private final Map, ? extends Stats>> eventWithStateClassStatsByPlannerPhaseMap;
@Nonnull
private final Map>, ? extends Stats> plannerRuleClassStatsMap;
@Nonnull
private final Supplier
- * Debugging functionality should only be enabled in test cases, never in deployments.
*
* In order to enable debugging capabilities, clients should use {@link #setDebugger} which sets a debugger to be used
* for the current thread. Once set, the planner starts interacting with the debugger in order to communicate important
* state changes, like begin of planning, end of planner, etc.
*
+ * Certain debugger implementations should only be enabled in test cases, never in deployments.
*
* Clients using the debugger should never hold on/manage/use an instance of a debugger directly. Instead, clients
* should use {@link #withDebugger} and {@link #mapDebugger} to invoke methods on the currently installed debugger.
diff --git a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/debug/DebuggerWithSymbolTables.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/debug/DebuggerWithSymbolTables.java
index fd143746b0..9aa067ba80 100644
--- a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/debug/DebuggerWithSymbolTables.java
+++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/debug/DebuggerWithSymbolTables.java
@@ -60,8 +60,11 @@
import static com.apple.foundationdb.record.query.plan.cascades.properties.ReferencesAndDependenciesProperty.referencesAndDependencies;
/**
+ *
* Implementation of a debugger that maintains symbol tables for easier human consumption e.g. in test cases and/or
* while debugging.
+ *
+ * Instances of this debugger should only be enabled in test cases, never in deployments.
*/
@SuppressWarnings("PMD.SystemPrintln")
public class DebuggerWithSymbolTables implements SymbolDebugger, StatsDebugger {
diff --git a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/debug/EventState.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/debug/EventState.java
index d75ccf8730..8f4d75b3c4 100644
--- a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/debug/EventState.java
+++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/debug/EventState.java
@@ -43,6 +43,10 @@
import java.util.Map;
import java.util.Objects;
+/**
+ * A class used by different implementations of {@link StatsDebugger} to record {@link Debugger.Event} and produce
+ * stats (number of events, total time taken by events of a specific type, etc.) for the recorded events.
+ */
@SuppressWarnings("PMD.SystemPrintln")
public class EventState {
@Nonnull
diff --git a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/debug/LightweightDebugger.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/debug/LightweightDebugger.java
index 2d44518bd3..ba299a8833 100644
--- a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/debug/LightweightDebugger.java
+++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/debug/LightweightDebugger.java
@@ -35,7 +35,7 @@
/**
* Implementation of a lightweight debugger that only maintains debugging events for calculating {@link Stats}
* (i.e. it implements only the {@link StatsDebugger} interface).
- * This debugger is safe for use in production.
+ * This debugger is safe for use in deployments.
*/
public class LightweightDebugger implements StatsDebugger {
private static final Logger logger = LoggerFactory.getLogger(LightweightDebugger.class);
diff --git a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/debug/StatsDebugger.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/debug/StatsDebugger.java
index 87a8adf397..96a9199bdf 100644
--- a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/debug/StatsDebugger.java
+++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/debug/StatsDebugger.java
@@ -25,6 +25,16 @@
import java.util.function.Consumer;
import java.util.function.Function;
+/**
+ *
+ * This interface extends the {@link Debugger} interface and provides functionality related to
+ * recording instances of {@link Debugger.Event} and producing statistics based on these events
+ * in the form of {@link StatsMaps}.
+ *
+ * Similar to the {@link Debugger} interface, the main mean of communication with the debugger
+ * for operations related to recording events is the set of statics defined within this interface.
+ *
+ */
public interface StatsDebugger extends Debugger {
@Nonnull
static Optional getDebuggerMaybe() {
diff --git a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/debug/StatsViewer.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/debug/StatsViewer.java
index 120f98590f..b3d3d8a68b 100644
--- a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/debug/StatsViewer.java
+++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/debug/StatsViewer.java
@@ -30,7 +30,19 @@
import java.util.Optional;
import java.util.concurrent.TimeUnit;
-/** Only used by debuggers to display stats.*/
+/**
+ * A class that can be used to view {@link Stats} for all {@link Debugger.Event} collected by {@link StatsDebugger}
+ * during planning in a browser.
+ *
+ * To view the stats, evaluate the following expression in a debugging session at a breakpoint before the Cascades planner
+ * returns the plan:
+ *
+ * StatsViewer.showStats(StatsDebugger.getDebuggerMaybe().get())
+ *
+ *
+ * The stats will be displayed in a new window of the system browser.
+ *
+ */
@SuppressWarnings("unused")
public class StatsViewer {
public static String showStats(@Nonnull final StatsDebugger statsDebugger) {
diff --git a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/debug/SymbolDebugger.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/debug/SymbolDebugger.java
index fcad8bceb6..7392412e82 100644
--- a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/debug/SymbolDebugger.java
+++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/debug/SymbolDebugger.java
@@ -32,6 +32,16 @@
import java.util.function.Function;
import java.util.function.IntUnaryOperator;
+/**
+ *
+ * This interface extends the {@link Debugger} interface and provides functionality related to
+ * registering different symbols (i.e. objects) during planning and retrieving debugging-friendly
+ * names for them.
+ *
+ * Similar to the {@link Debugger} interface, the main mean of communication with the debugger
+ * for operations related to recording events is the set of statics defined within this interface.
+ *
+ */
public interface SymbolDebugger extends Debugger {
@Nonnull
static Optional getDebuggerMaybe() {
diff --git a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/debug/SymbolTables.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/debug/SymbolTables.java
index 60e4e2d850..cdc8c63cac 100644
--- a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/debug/SymbolTables.java
+++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/debug/SymbolTables.java
@@ -34,6 +34,11 @@
import java.util.Map;
import java.util.function.IntUnaryOperator;
+/**
+ * A class used by different implementations of {@link SymbolDebugger} to map different types of objects
+ * during planning ({@link RelationalExpression}, {@link Reference}, {@link Quantifier}) to unique indices, which
+ * is used to generate debugging-friendly names for these objects.
+ */
public class SymbolTables {
@Nonnull
private final Map, Integer> classToIndexMap;
From b53e0424ea51d7552b6710594566422947d7799d Mon Sep 17 00:00:00 2001
From: Hazem Zaghloul
Date: Fri, 8 Aug 2025 18:43:19 +0100
Subject: [PATCH 9/9] Fix bug in StatsMaps for events without a state
---
.../record/query/plan/cascades/debug/LightweightDebugger.java | 2 ++
.../record/query/plan/cascades/debug/StatsMaps.java | 2 +-
2 files changed, 3 insertions(+), 1 deletion(-)
diff --git a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/debug/LightweightDebugger.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/debug/LightweightDebugger.java
index ba299a8833..b387ae1e49 100644
--- a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/debug/LightweightDebugger.java
+++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/debug/LightweightDebugger.java
@@ -85,6 +85,8 @@ public void onShow(@Nonnull final Reference ref) {
@Override
public void onQuery(@Nonnull final String recordQuery, @Nonnull final PlanContext planContext) {
+ reset();
+
this.queryAsString = recordQuery;
this.planContext = planContext;
diff --git a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/debug/StatsMaps.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/debug/StatsMaps.java
index 84d112b6d2..a8a86727d8 100644
--- a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/debug/StatsMaps.java
+++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/debug/StatsMaps.java
@@ -75,7 +75,7 @@ public Map>, Stats> getPlannerRuleClassStatsMap(
@Nonnull
public Map, Stats> getEventWithoutStateClassStatsMap() {
- return immutableEventClassStatsMapSupplier.get();
+ return immutableEventWithoutStateClassStatsMapSupplier.get();
}
@Nonnull