duckdb/profiling.rs
1//! Query profiling support for DuckDB.
2//!
3//! This module provides access to DuckDB's query profiling infrastructure,
4//! allowing you to inspect per-operator metrics for executed queries.
5//!
6//! # Usage
7//!
8//! Profiling must be explicitly enabled on a connection before metrics are
9//! collected. Use the `enable_profiling` and `profiling_mode` PRAGMAs to
10//! configure it:
11//!
12//! ```rust,no_run
13//! # use duckdb::Connection;
14//! let conn = Connection::open_in_memory().unwrap();
15//!
16//! // Prevents DuckDB from printing profiling output to stdout after each query,
17//! // which is the default behavior when profiling is enabled.
18//! conn.execute("PRAGMA enable_profiling = 'no_output'", []).unwrap();
19//!
20//! // Enables standard profiling mode, which collects a comprehensive set of metrics for each operator in the query plan.
21//! // There is also `detailed` mode for even more fine-grained metrics. See DuckDB's documentation for more details.
22//! conn.execute("PRAGMA profiling_mode = 'standard'", []).unwrap();
23//!
24//! // Now we can execute a query and retrieve its profiling info.
25//! conn.execute("SELECT 42", []).unwrap();
26//!
27//! let info = conn.get_profiling_info().expect("profiling should be enabled");
28//! println!("rows returned: {}", info.metrics["ROWS_RETURNED"]);
29//! ```
30//!
31//! # Structure
32//!
33//! [`ProfilingInfo`] is a tree that mirrors DuckDB's query plan. The root node
34//! (`QUERY_ROOT`) holds metrics for the entire query (e.g. total `LATENCY` and
35//! `ROWS_RETURNED`). Each child node represents a plan operator and carries its
36//! own metrics such as `OPERATOR_NAME` and `OPERATOR_CARDINALITY`.
37
38use std::collections::HashMap;
39
40use crate::{Connection, inner_connection::InnerConnection};
41
42/// [`ProfilingInfo`] is a recursive type containing metrics for each node in DuckDB's query plan.
43/// There are two types of nodes: the "QUERY_ROOT" and "OPERATOR" nodes.
44/// The "QUERY_ROOT" refers exclusively to the top-level node; its metrics are measured over the entire query.
45/// The "OPERATOR" nodes refer to the individual operators in the query plan.
46#[derive(Debug, Clone)]
47pub struct ProfilingInfo {
48 /// The metrics for the node, represented as a map from metric name to metric value.
49 /// The actual format and units of the metric varies between metric kinds. For example,
50 /// - "ROWS_RETURNED" is an integer, formatted as a string
51 /// - "LATENCY" is a double-floating point number measuring the total query time in seconds, formatted as a string
52 /// - "OPERATOR_NAME" is a string
53 /// - "EXTRA_INFO" is a map of its own encoded as a string
54 pub metrics: HashMap<String, String>,
55 /// The children of the node and their respective metrics.
56 pub children: Vec<ProfilingInfo>,
57}
58
59impl ProfilingInfo {
60 /// # Safety
61 /// `info` must be a valid (or NULL) pointer obtained from [`libduckdb_sys::duckdb_get_profiling_info`].
62 /// Metric key/value string conversions are treated as fallible. NULL varchar results are skipped.
63 fn from_raw(info: libduckdb_sys::duckdb_profiling_info) -> Option<Self> {
64 if info.is_null() {
65 return None;
66 }
67
68 // Extract metrics
69 let mut map = unsafe { libduckdb_sys::duckdb_profiling_info_get_metrics(info) };
70 let map_size = unsafe { libduckdb_sys::duckdb_get_map_size(map) };
71
72 let mut metrics = HashMap::<String, String>::with_capacity(map_size as usize);
73
74 for i in 0..map_size {
75 let mut key = unsafe { libduckdb_sys::duckdb_get_map_key(map, i) };
76 let mut val = unsafe { libduckdb_sys::duckdb_get_map_value(map, i) };
77
78 if let (Some(key_str), Some(val_str)) = (Self::value_to_string(key), Self::value_to_string(val)) {
79 metrics.insert(key_str, val_str);
80 }
81
82 unsafe { libduckdb_sys::duckdb_destroy_value(&mut key) };
83 unsafe { libduckdb_sys::duckdb_destroy_value(&mut val) };
84 }
85
86 unsafe { libduckdb_sys::duckdb_destroy_value(&mut map) };
87
88 // Extract children
89 let child_count = unsafe { libduckdb_sys::duckdb_profiling_info_get_child_count(info) };
90 let mut children = Vec::with_capacity(child_count as usize);
91 for i in 0..child_count {
92 let child_info = unsafe { Self::from_raw(libduckdb_sys::duckdb_profiling_info_get_child(info, i)) };
93 if let Some(info) = child_info {
94 children.push(info);
95 }
96 }
97
98 Some(ProfilingInfo { metrics, children })
99 }
100
101 fn value_to_string(value: libduckdb_sys::duckdb_value) -> Option<String> {
102 let ptr = unsafe { libduckdb_sys::duckdb_get_varchar(value) };
103 unsafe { libduckdb_sys::DuckDbString::from_nullable_ptr(ptr) }
104 .map(|varchar| varchar.to_string_lossy().to_string())
105 }
106}
107
108impl InnerConnection {
109 /// Retrieves the [`ProfilingInfo`] for the last executed query, if profiling is enabled.
110 pub fn get_profiling_info(&self) -> Option<ProfilingInfo> {
111 let info = unsafe { libduckdb_sys::duckdb_get_profiling_info(self.con) };
112 ProfilingInfo::from_raw(info)
113 }
114}
115
116impl Connection {
117 /// Retrieves the [`ProfilingInfo`] for the last executed query, if profiling is enabled.
118 pub fn get_profiling_info(&self) -> Option<ProfilingInfo> {
119 self.db.borrow().get_profiling_info()
120 }
121}
122
123#[cfg(test)]
124mod tests {
125 use crate::Connection;
126
127 #[test]
128 fn test_profiling_info() {
129 let conn = Connection::open_in_memory().unwrap();
130
131 conn.execute("SELECT 1", []).unwrap();
132 let info = conn.get_profiling_info();
133
134 assert!(
135 info.is_none(),
136 "Profiling info should be None when profiling is not enabled"
137 );
138
139 conn.execute("PRAGMA enable_profiling = 'no_output'", []).unwrap();
140 conn.execute("PRAGMA profiling_mode = 'standard'", []).unwrap();
141 conn.execute("SELECT 1", []).unwrap();
142 let info = conn.get_profiling_info();
143
144 assert!(info.is_some(), "Metrics should be present when profiling is enabled");
145 let info = info.unwrap();
146
147 assert!(!info.metrics.is_empty(), "Metrics should not be empty");
148 assert!(
149 !info.children.is_empty(),
150 "There should be at least one child for a simple query"
151 );
152
153 assert!(
154 info.metrics.contains_key("ROWS_RETURNED"),
155 "Metrics should contain ROWS_RETURNED"
156 );
157 assert!(
158 info.metrics.get("ROWS_RETURNED").unwrap() == "1",
159 "ROWS_RETURNED should be 1"
160 );
161 }
162}