Skip to content

Commit 2ee4763

Browse files
committed
Add new lightweight debugger implementation
This commit adds a new implmentation of the Cascades Debugger interface, which only keeps track of statstics around debugger events and doesn't support keeping track of symbol tables. This new implementation should be safe to use outside of tests since it should have a minimal impact on the planner's performance.
1 parent a9edd0f commit 2ee4763

File tree

6 files changed

+241
-0
lines changed

6 files changed

+241
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
/*
2+
* LightweightDebugger.java
3+
*
4+
* This source file is part of the FoundationDB open source project
5+
*
6+
* Copyright 2015-2025 Apple Inc. and the FoundationDB project authors
7+
*
8+
* Licensed under the Apache License, Version 2.0 (the "License");
9+
* you may not use this file except in compliance with the License.
10+
* You may obtain a copy of the License at
11+
*
12+
* http://www.apache.org/licenses/LICENSE-2.0
13+
*
14+
* Unless required by applicable law or agreed to in writing, software
15+
* distributed under the License is distributed on an "AS IS" BASIS,
16+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17+
* See the License for the specific language governing permissions and
18+
* limitations under the License.
19+
*/
20+
21+
package com.apple.foundationdb.record.query.plan.cascades.debug;
22+
23+
import com.apple.foundationdb.record.logging.KeyValueLogMessage;
24+
import com.apple.foundationdb.record.query.plan.cascades.PlanContext;
25+
import com.apple.foundationdb.record.query.plan.cascades.Reference;
26+
import org.slf4j.Logger;
27+
import org.slf4j.LoggerFactory;
28+
29+
import javax.annotation.Nonnull;
30+
import javax.annotation.Nullable;
31+
import java.util.Objects;
32+
import java.util.Optional;
33+
import java.util.concurrent.TimeUnit;
34+
35+
/**
36+
* Implementation of a lightweight debugger that only maintains debugging events for calculating {@link Stats}
37+
* (i.e. it implements only the {@link StatsDebugger} interface).
38+
* This debugger is safe for use in production.
39+
*/
40+
public class LightweightDebugger implements StatsDebugger {
41+
private static final Logger logger = LoggerFactory.getLogger(LightweightDebugger.class);
42+
43+
@Nullable
44+
private EventState currentEventState;
45+
@Nullable
46+
private String queryAsString;
47+
@Nullable
48+
private PlanContext planContext;
49+
50+
public LightweightDebugger() {
51+
this.currentEventState = null;
52+
this.planContext = null;
53+
}
54+
55+
@Nonnull
56+
EventState getCurrentEventState() {
57+
return Objects.requireNonNull(currentEventState);
58+
}
59+
60+
@Nullable
61+
@Override
62+
public PlanContext getPlanContext() {
63+
return planContext;
64+
}
65+
66+
@Override
67+
public boolean isSane() {
68+
return true;
69+
}
70+
71+
@Override
72+
public void onInstall() {
73+
// do nothing
74+
}
75+
76+
@Override
77+
public void onSetup() {
78+
reset();
79+
}
80+
81+
@Override
82+
public void onShow(@Nonnull final Reference ref) {
83+
// do nothing
84+
}
85+
86+
@Override
87+
public void onQuery(@Nonnull final String recordQuery, @Nonnull final PlanContext planContext) {
88+
this.queryAsString = recordQuery;
89+
this.planContext = planContext;
90+
91+
logQuery();
92+
}
93+
94+
@Override
95+
public void onEvent(final Debugger.Event event) {
96+
if ((queryAsString == null) || (planContext == null) || (currentEventState == null)) {
97+
return;
98+
}
99+
getCurrentEventState().addCurrentEvent(event);
100+
}
101+
102+
@Override
103+
public void onDone() {
104+
if (currentEventState != null && queryAsString != null) {
105+
final var state = Objects.requireNonNull(currentEventState);
106+
if (logger.isDebugEnabled()) {
107+
logger.debug(KeyValueLogMessage.of("planning done",
108+
"query", Objects.requireNonNull(queryAsString).substring(0, Math.min(queryAsString.length(), 30)),
109+
"duration-in-ms", TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - state.getStartTs()),
110+
"ticks", state.getCurrentTick()));
111+
}
112+
}
113+
reset();
114+
}
115+
116+
@Nonnull
117+
@Override
118+
public Optional<StatsMaps> getStatsMaps() {
119+
if (currentEventState != null) {
120+
return Optional.of(currentEventState.getStatsMaps());
121+
}
122+
return Optional.empty();
123+
}
124+
125+
private void reset() {
126+
this.currentEventState = EventState.initial(false, false, null);
127+
this.planContext = null;
128+
this.queryAsString = null;
129+
}
130+
131+
void logQuery() {
132+
if (logger.isDebugEnabled()) {
133+
logger.debug(KeyValueLogMessage.of("planning started", "query", queryAsString));
134+
}
135+
}
136+
}

yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/command/DebuggerImplementation.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222

2323
import com.apple.foundationdb.record.query.plan.cascades.debug.Debugger;
2424
import com.apple.foundationdb.record.query.plan.cascades.debug.DebuggerWithSymbolTables;
25+
import com.apple.foundationdb.record.query.plan.cascades.debug.LightweightDebugger;
2526
import com.apple.foundationdb.record.query.plan.cascades.debug.PlannerRepl;
2627
import com.apple.foundationdb.relational.yamltests.YamlExecutionContext;
2728
import org.jline.terminal.TerminalBuilder;
@@ -31,6 +32,7 @@
3132
import java.util.function.Function;
3233

3334
public enum DebuggerImplementation {
35+
LIGHTWEIGHT(context -> new LightweightDebugger()),
3436
INSANE(context -> DebuggerWithSymbolTables.withSanityChecks()),
3537
SANE(context -> DebuggerWithSymbolTables.withoutSanityChecks()),
3638
REPL(context -> {

yaml-tests/src/test/java/YamlIntegrationTests.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -305,4 +305,9 @@ public void literalExtractionTests(YamlTest.Runner runner) throws Exception {
305305
public void caseSensitivityTest(YamlTest.Runner runner) throws Exception {
306306
runner.runYamsql("case-sensitivity.yamsql");
307307
}
308+
309+
@TestTemplate
310+
public void simpleQueryWithDifferentDebuggersTest(YamlTest.Runner runner) throws Exception {
311+
runner.runYamsql("simple-query-with-different-debuggers.yamsql");
312+
}
308313
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
2+
N
3+
%simple-query-with-different-debuggers%EXPLAIN select * from t1 where id > 1�
4+
����"* ���(0��I8@9SCAN(<,>) | FILTER _.ID GREATER_THAN promote(@c8 AS LONG)�
5+
digraph G {
6+
fontname=courier;
7+
rankdir=BT;
8+
splines=polyline;
9+
1 [ label=<<table border="0" cellborder="1" cellspacing="0" cellpadding="8"><tr><td align="left">Predicate Filter</td></tr><tr><td align="left">WHERE q9bd5c851_f08a_4d97_bc5d_1ae41728b1b7.ID GREATER_THAN promote(@c8 AS LONG)</td></tr></table>> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, )" ];
10+
2 [ label=<<table border="0" cellborder="1" cellspacing="0" cellpadding="8"><tr><td align="left">Scan</td></tr><tr><td align="left">range: &lt;-∞, ∞&gt;</td></tr></table>> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, )" ];
11+
3 [ label=<<table border="0" cellborder="1" cellspacing="0" cellpadding="8"><tr><td align="left">Primary Storage</td></tr><tr><td align="left">record types: [T1]</td></tr></table>> color="black" shape="plain" style="filled" fillcolor="lightblue" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, )" ];
12+
3 -> 2 [ color="gray20" style="solid" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ];
13+
2 -> 1 [ label=<&nbsp;q9bd5c851_f08a_4d97_bc5d_1ae41728b1b7> label="q9bd5c851_f08a_4d97_bc5d_1ae41728b1b7" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ];
14+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
simple-query-with-different-debuggers:
2+
- query: EXPLAIN select * from t1 where id > 1
3+
explain: SCAN(<,>) | FILTER _.ID GREATER_THAN promote(@c8 AS LONG)
4+
task_count: 163
5+
task_total_time_ms: 72
6+
transform_count: 42
7+
transform_time_ms: 61
8+
transform_yield_count: 14
9+
insert_time_ms: 1
10+
insert_new_count: 14
11+
insert_reused_count: 2
12+
- query: EXPLAIN select * from t1 where id > 1
13+
explain: SCAN(<,>) | FILTER _.ID GREATER_THAN promote(@c8 AS LONG)
14+
task_count: 163
15+
task_total_time_ms: 72
16+
transform_count: 42
17+
transform_time_ms: 61
18+
transform_yield_count: 14
19+
insert_time_ms: 1
20+
insert_new_count: 14
21+
insert_reused_count: 2
22+
- query: EXPLAIN select * from t1 where id > 1
23+
explain: SCAN(<,>) | FILTER _.ID GREATER_THAN promote(@c8 AS LONG)
24+
task_count: 163
25+
task_total_time_ms: 72
26+
transform_count: 42
27+
transform_time_ms: 61
28+
transform_yield_count: 14
29+
insert_time_ms: 1
30+
insert_new_count: 14
31+
insert_reused_count: 2
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
#
2+
# simple-query-with-different-debuggers.yamsql
3+
#
4+
# This source file is part of the FoundationDB open source project
5+
#
6+
# Copyright 2021-2025 Apple Inc. and the FoundationDB project authors
7+
#
8+
# Licensed under the Apache License, Version 2.0 (the "License");
9+
# you may not use this file except in compliance with the License.
10+
# You may obtain a copy of the License at
11+
#
12+
# http://www.apache.org/licenses/LICENSE-2.0
13+
#
14+
# Unless required by applicable law or agreed to in writing, software
15+
# distributed under the License is distributed on an "AS IS" BASIS,
16+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17+
# See the License for the specific language governing permissions and
18+
# limitations under the License.
19+
20+
---
21+
schema_template:
22+
create table t1(id bigint, col1 string, primary key(id))
23+
---
24+
setup:
25+
steps:
26+
- query: INSERT INTO T1 VALUES
27+
(1, 'a'),
28+
(2, 'b'),
29+
(3, 'c')
30+
---
31+
test_block:
32+
name: simple-query-with-different-debuggers
33+
preset: single_repetition_ordered
34+
tests:
35+
-
36+
- query: select * from t1 where id > 1
37+
- explain: "SCAN(<,>) | FILTER _.ID GREATER_THAN promote(@c8 AS LONG)"
38+
- debugger: lightweight
39+
- result: [{id: 2, col1: 'b'},
40+
{id: 3, col1: 'c'}]
41+
-
42+
- query: select * from t1 where id > 1
43+
- explain: "SCAN(<,>) | FILTER _.ID GREATER_THAN promote(@c8 AS LONG)"
44+
- debugger: sane
45+
- result: [{id: 2, col1: 'b'},
46+
{id: 3, col1: 'c'}]
47+
-
48+
- query: select * from t1 where id > 1
49+
- explain: "SCAN(<,>) | FILTER _.ID GREATER_THAN promote(@c8 AS LONG)"
50+
- debugger: insane
51+
- result: [{id: 2, col1: 'b'},
52+
{id: 3, col1: 'c'}]
53+
...

0 commit comments

Comments
 (0)