Skip to content

Commit f78e34e

Browse files
committed
Add pipeline trace logging (enable_pipeline_trace setting)
Adds two debugging capabilities to the pipeline execution system: 1. Static pipeline graph: after query initialization, prints all pipelines and their dependency relationships to stderr. 2. Runtime timing trace: records wall-clock start/end times for each pipeline and outputs a Chrome Trace JSON report to stderr on query completion. The JSON is loadable in https://ui.perfetto.dev/ or chrome://tracing for an interactive Gantt visualization. Usage: SET enable_pipeline_trace = true; SELECT ...; -- pipeline graph + Chrome trace JSON go to stderr New files: src/include/duckdb/parallel/pipeline_tracer.hpp src/parallel/pipeline_tracer.cpp Key changes: - Pipeline: add pipeline_id, start_time_ns, end_time_ns, MarkStart/MarkEnd - PipelineEvent: call MarkStart in Schedule(), MarkEnd in FinishEvent() - PipelineFinishEvent: call MarkEnd in FinishEvent() (last-call wins, so finalization time is captured for base pipelines) - Executor: assign IDs, save traced_pipelines ref, print graph at init and Chrome trace JSON before pipelines.clear() - Settings: add enable_pipeline_trace (local bool, default false) https://claude.ai/code/session_01UV8WQWq1RyBsyTAxsbu4yG
1 parent 9f396ea commit f78e34e

13 files changed

Lines changed: 218 additions & 1 deletion

File tree

src/common/settings.json

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -514,6 +514,14 @@
514514
"default_scope": "global",
515515
"default_value": "false"
516516
},
517+
{
518+
"name": "enable_pipeline_trace",
519+
"description": "Enables pipeline trace logging: prints the pipeline graph and outputs a Chrome Trace JSON timing report to stderr on query completion",
520+
"type": "BOOLEAN",
521+
"scope": "local",
522+
"struct": "EnablePipelineTraceSetting",
523+
"custom_implementation": true
524+
},
517525
{
518526
"name": "enable_profiling",
519527
"description": "Enables profiling, and sets the output format (JSON, QUERY_TREE, QUERY_TREE_OPTIMIZER)",
@@ -1119,4 +1127,4 @@
11191127
"scope": "global",
11201128
"default_value": "4096"
11211129
}
1122-
]
1130+
]

src/include/duckdb/execution/executor.hpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,10 @@ class Executor {
155155
vector<shared_ptr<Pipeline>> pipelines;
156156
//! The root pipelines of the query
157157
vector<shared_ptr<Pipeline>> root_pipelines;
158+
//! Pipelines saved for trace output (persists after pipelines.clear())
159+
vector<shared_ptr<Pipeline>> traced_pipelines;
160+
//! Steady-clock nanoseconds at query initialization (base for Chrome trace timestamps)
161+
int64_t pipeline_trace_start_ns = 0;
158162
//! The recursive CTE's in this query plan
159163
vector<reference<PhysicalOperator>> recursive_ctes;
160164
//! The pipeline executor for the root pipeline

src/include/duckdb/main/client_config.hpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,10 @@ struct ClientConfig {
101101
//! If HTTP logging is enabled or not.
102102
bool enable_http_logging = true;
103103

104+
//! If pipeline trace logging is enabled (SET enable_pipeline_trace = true).
105+
//! Prints the pipeline dependency graph and Chrome Trace JSON timing to stderr.
106+
bool enable_pipeline_trace = false;
107+
104108
//! **DEPRECATED** The file to save query HTTP logging information to, instead of printing it to the console
105109
//! (empty = output to the DuckDB logger)
106110
string http_logging_output;

src/include/duckdb/main/settings.hpp

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -780,6 +780,18 @@ struct EnableObjectCacheSetting {
780780
static constexpr idx_t SettingIndex = 42;
781781
};
782782

783+
struct EnablePipelineTraceSetting {
784+
using RETURN_TYPE = bool;
785+
static constexpr const char *Name = "enable_pipeline_trace";
786+
static constexpr const char *Description =
787+
"Enables pipeline trace logging: prints the pipeline graph and outputs a Chrome Trace JSON "
788+
"timing report to stderr on query completion";
789+
static constexpr const char *InputType = "BOOLEAN";
790+
static void SetLocal(ClientContext &context, const Value &parameter);
791+
static void ResetLocal(ClientContext &context);
792+
static Value GetSetting(const ClientContext &context);
793+
};
794+
783795
struct EnableProfilingSetting {
784796
using RETURN_TYPE = string;
785797
static constexpr const char *Name = "enable_profiling";

src/include/duckdb/parallel/pipeline.hpp

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ class Pipeline : public enable_shared_from_this<Pipeline> {
7676
friend class PipelineFinishEvent;
7777
friend class PipelineBuildState;
7878
friend class MetaPipeline;
79+
friend class PipelineTracer;
7980

8081
public:
8182
explicit Pipeline(Executor &execution_context);
@@ -100,6 +101,21 @@ class Pipeline : public enable_shared_from_this<Pipeline> {
100101
void Print() const;
101102
void PrintDependencies() const;
102103

104+
//! Record the wall-clock start time of this pipeline's execution.
105+
void MarkStart();
106+
//! Record the wall-clock end time (last-call wins, so PipelineFinishEvent overwrites PipelineEvent).
107+
void MarkEnd();
108+
109+
idx_t GetPipelineId() const {
110+
return pipeline_id;
111+
}
112+
int64_t GetStartTimeNs() const {
113+
return start_time_ns;
114+
}
115+
int64_t GetEndTimeNs() const {
116+
return end_time_ns;
117+
}
118+
103119
//! Returns query progress
104120
bool GetProgress(ProgressData &progress_data);
105121

@@ -132,6 +148,12 @@ class Pipeline : public enable_shared_from_this<Pipeline> {
132148
atomic<bool> initialized;
133149
//! The source of this pipeline
134150
optional_ptr<PhysicalOperator> source;
151+
152+
//! Pipeline ID assigned by PipelineTracer::AssignIds (0-based)
153+
idx_t pipeline_id = 0;
154+
//! Wall-clock start/end times in steady_clock nanoseconds since epoch (-1 = unset)
155+
int64_t start_time_ns = -1;
156+
int64_t end_time_ns = -1;
135157
//! The chain of intermediate operators
136158
vector<reference<PhysicalOperator>> operators;
137159
//! The sink (i.e. destination) for data; this is e.g. a hash table to-be-built
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
//===----------------------------------------------------------------------===//
2+
// DuckDB
3+
//
4+
// duckdb/parallel/pipeline_tracer.hpp
5+
//
6+
//
7+
//===----------------------------------------------------------------------===//
8+
9+
#pragma once
10+
11+
#include "duckdb/common/common.hpp"
12+
13+
namespace duckdb {
14+
15+
class Pipeline;
16+
17+
//! PipelineTracer collects and outputs pipeline structure and execution timing.
18+
//! Enabled by SET enable_pipeline_trace = true.
19+
//! Outputs:
20+
//! 1. A pipeline dependency graph to stderr after initialization.
21+
//! 2. A Chrome Trace JSON timing report to stderr after query completion
22+
//! (loadable in https://ui.perfetto.dev/ or chrome://tracing).
23+
class PipelineTracer {
24+
public:
25+
//! Assign sequential IDs (0, 1, 2, ...) to all pipelines.
26+
//! Must be called before PrintGraph or PrintChromeTrace.
27+
static void AssignIds(vector<shared_ptr<Pipeline>> &pipelines);
28+
29+
//! Print the static pipeline structure and dependency graph to stderr.
30+
static void PrintGraph(const vector<shared_ptr<Pipeline>> &pipelines);
31+
32+
//! Print Chrome Trace JSON to stderr.
33+
//! query_start_ns: steady_clock nanoseconds since epoch at query start.
34+
static void PrintChromeTrace(const vector<shared_ptr<Pipeline>> &pipelines, int64_t query_start_ns);
35+
36+
private:
37+
//! Build a short human-readable description: "TableScan→HashJoinBuild→..."
38+
static string Describe(const Pipeline &pipeline);
39+
};
40+
41+
} // namespace duckdb

src/main/settings/custom_settings.cpp

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1205,6 +1205,22 @@ Value EnableHTTPLoggingSetting::GetSetting(const ClientContext &context) {
12051205
return Value::BOOLEAN(config.enable_http_logging);
12061206
}
12071207

1208+
//===----------------------------------------------------------------------===//
1209+
// Enable Pipeline Trace
1210+
//===----------------------------------------------------------------------===//
1211+
1212+
void EnablePipelineTraceSetting::SetLocal(ClientContext &context, const Value &input) {
1213+
ClientConfig::GetConfig(context).enable_pipeline_trace = input.GetValue<bool>();
1214+
}
1215+
1216+
void EnablePipelineTraceSetting::ResetLocal(ClientContext &context) {
1217+
ClientConfig::GetConfig(context).enable_pipeline_trace = ClientConfig().enable_pipeline_trace;
1218+
}
1219+
1220+
Value EnablePipelineTraceSetting::GetSetting(const ClientContext &context) {
1221+
return Value::BOOLEAN(ClientConfig::GetConfig(context).enable_pipeline_trace);
1222+
}
1223+
12081224
//===----------------------------------------------------------------------===//
12091225
// Enable Mbedtls
12101226
//===----------------------------------------------------------------------===//

src/parallel/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ add_library_unity(
1010
interrupt.cpp
1111
pipeline.cpp
1212
pipeline_complete_event.cpp
13+
pipeline_tracer.cpp
1314
pipeline_event.cpp
1415
pipeline_executor.cpp
1516
pipeline_finish_event.cpp

src/parallel/executor.cpp

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#include "duckdb/execution/executor.hpp"
22

3+
#include "duckdb/common/chrono.hpp"
34
#include "duckdb/execution/execution_context.hpp"
45
#include "duckdb/execution/operator/helper/physical_result_collector.hpp"
56
#include "duckdb/execution/operator/scan/physical_table_scan.hpp"
@@ -16,8 +17,10 @@
1617
#include "duckdb/parallel/pipeline_finish_event.hpp"
1718
#include "duckdb/parallel/pipeline_initialize_event.hpp"
1819
#include "duckdb/parallel/pipeline_prepare_finish_event.hpp"
20+
#include "duckdb/parallel/pipeline_tracer.hpp"
1921
#include "duckdb/parallel/task_scheduler.hpp"
2022
#include "duckdb/parallel/thread_context.hpp"
23+
#include "duckdb/main/client_config.hpp"
2124

2225
#include <algorithm>
2326
#include <chrono>
@@ -424,6 +427,16 @@ void Executor::InitializeInternal(PhysicalOperator &plan) {
424427
// finally, verify and schedule
425428
VerifyPipelines();
426429
ScheduleEvents(to_schedule);
430+
431+
// pipeline trace: assign IDs, record query start time, and print the static graph
432+
auto &trace_config = ClientConfig::GetConfig(context);
433+
if (trace_config.enable_pipeline_trace) {
434+
PipelineTracer::AssignIds(pipelines);
435+
pipeline_trace_start_ns = static_cast<int64_t>(
436+
duration_cast<nanoseconds>(steady_clock::now().time_since_epoch()).count());
437+
PipelineTracer::PrintGraph(pipelines);
438+
traced_pipelines = pipelines;
439+
}
427440
}
428441
}
429442

@@ -622,6 +635,11 @@ PendingExecutionResult Executor::ExecuteTask(bool dry_run) {
622635
D_ASSERT(!task);
623636

624637
lock_guard<mutex> elock(executor_lock);
638+
// emit Chrome Trace JSON before clearing pipelines (timing data lives in Pipeline objects)
639+
if (!traced_pipelines.empty()) {
640+
PipelineTracer::PrintChromeTrace(traced_pipelines, pipeline_trace_start_ns);
641+
traced_pipelines.clear();
642+
}
625643
pipelines.clear();
626644
NextExecutor();
627645
if (HasError()) { // LCOV_EXCL_START
@@ -647,6 +665,8 @@ void Executor::Reset() {
647665
events.clear();
648666
to_be_rescheduled_tasks.clear();
649667
execution_result = PendingExecutionResult::RESULT_NOT_READY;
668+
traced_pipelines.clear();
669+
pipeline_trace_start_ns = 0;
650670
}
651671

652672
shared_ptr<Pipeline> Executor::CreateChildPipeline(Pipeline &current, PhysicalOperator &op) {

src/parallel/pipeline.cpp

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#include "duckdb/parallel/pipeline.hpp"
22

33
#include "duckdb/common/algorithm.hpp"
4+
#include "duckdb/common/chrono.hpp"
45
#include "duckdb/common/printer.hpp"
56
#include "duckdb/common/tree_renderer/text_tree_renderer.hpp"
67
#include "duckdb/execution/executor.hpp"
@@ -73,6 +74,16 @@ ClientContext &Pipeline::GetClientContext() {
7374
return executor.context;
7475
}
7576

77+
void Pipeline::MarkStart() {
78+
start_time_ns = static_cast<int64_t>(
79+
duration_cast<nanoseconds>(steady_clock::now().time_since_epoch()).count());
80+
}
81+
82+
void Pipeline::MarkEnd() {
83+
end_time_ns = static_cast<int64_t>(
84+
duration_cast<nanoseconds>(steady_clock::now().time_since_epoch()).count());
85+
}
86+
7687
bool Pipeline::GetProgress(ProgressData &progress) {
7788
D_ASSERT(source);
7889
idx_t source_cardinality = MinValue<idx_t>(source->estimated_cardinality, 1ULL << 48ULL);

0 commit comments

Comments
 (0)