vibesql_executor/
profiling.rs

1//! Performance profiling and debug utilities for understanding bottlenecks
2//!
3//! This module provides profiling infrastructure that integrates with the
4//! structured debug output system. When `VIBESQL_DEBUG_FORMAT=json`, profiling
5//! output is emitted as JSON for machine parsing.
6//!
7//! # Environment Variables
8//!
9//! ## Umbrella Debug Flag
10//!
11//! `VIBESQL_DEBUG` provides unified control over all semantic debug logging:
12//!
13//! - `VIBESQL_DEBUG=1` - Enable all semantic logging (optimizer decisions)
14//! - `VIBESQL_DEBUG=optimizer` - Enable only optimizer-related logging
15//! - `VIBESQL_DEBUG=scan` - Enable only scan path logging
16//! - `VIBESQL_DEBUG=dml` - Enable only DML-related logging
17//!
18//! ## Individual Flags (for fine-grained control)
19//!
20//! These flags work independently or are auto-enabled by `VIBESQL_DEBUG`:
21//!
22//! - `JOIN_REORDER_VERBOSE` - Join reorder decisions and costs
23//! - `SUBQUERY_TRANSFORM_VERBOSE` - Subquery-to-join transformations
24//! - `TABLE_ELIM_VERBOSE` - Table elimination decisions
25//! - `SCAN_PATH_VERBOSE` - Scan path selection (index vs table scan)
26//! - `INDEX_SELECT_DEBUG` - Index selection decisions
27//!
28//! ## Phase Timing Flags
29//!
30//! - `DELETE_PROFILE` - Delete operation phase timing
31//! - `JOIN_PROFILE` - Join execution phase timing
32//! - `RANGE_SCAN_PROFILE` - Range scan phase timing
33
34use std::sync::LazyLock;
35
36use instant::Instant;
37
38use crate::debug_output::{self, Category, DebugEvent};
39
40/// Debug level from VIBESQL_DEBUG environment variable (lazily initialized)
41/// 0 = disabled, 1 = all, 2 = optimizer, 3 = scan, 4 = dml
42static DEBUG_LEVEL: LazyLock<u8> = LazyLock::new(|| {
43    std::env::var("VIBESQL_DEBUG")
44        .ok()
45        .and_then(|v| match v.to_lowercase().as_str() {
46            "1" | "true" | "all" => Some(1),
47            "optimizer" => Some(2),
48            "scan" => Some(3),
49            "dml" => Some(4),
50            _ => v.parse().ok(),
51        })
52        .unwrap_or(0)
53});
54
55/// Profiling enabled flag (lazily initialized from VIBESQL_PROFILE)
56static PROFILING_ENABLED: LazyLock<bool> =
57    LazyLock::new(|| std::env::var("VIBESQL_PROFILE").is_ok());
58
59/// Debug categories enabled via VIBESQL_DEBUG
60#[derive(Clone, Copy, PartialEq, Eq)]
61pub enum DebugCategory {
62    /// All debug logging enabled
63    All,
64    /// Optimizer decisions (join reorder, subquery transform, table elimination)
65    Optimizer,
66    /// Scan path selection (index vs table scan, columnar)
67    Scan,
68    /// DML operations (cost model, delete profiling)
69    Dml,
70    /// Disabled
71    None,
72}
73
74// Thread-local cache for debug checks to avoid repeated env var lookups
75thread_local! {
76    static SCAN_PATH_VERBOSE_ENABLED: std::cell::Cell<Option<bool>> = const { std::cell::Cell::new(None) };
77}
78
79/// Initialize profiling based on environment variable.
80/// Also initializes the debug output format.
81pub fn init() {
82    // Initialize debug output format first
83    debug_output::init();
84
85    // Force lazy initialization of profiling flag
86    if *PROFILING_ENABLED {
87        debug_output::debug_event(Category::Profile, "init", "PROFILE")
88            .text("Profiling enabled")
89            .field_bool("enabled", true)
90            .emit();
91    }
92}
93
94/// Check if profiling is enabled
95pub fn is_enabled() -> bool {
96    *PROFILING_ENABLED
97}
98
99/// Get the current debug category
100pub fn debug_category() -> DebugCategory {
101    match *DEBUG_LEVEL {
102        0 => DebugCategory::None,
103        1 => DebugCategory::All,
104        2 => DebugCategory::Optimizer,
105        3 => DebugCategory::Scan,
106        4 => DebugCategory::Dml,
107        _ => DebugCategory::None,
108    }
109}
110
111/// Check if a specific debug category is enabled
112pub fn is_debug_enabled(category: DebugCategory) -> bool {
113    let current = debug_category();
114    current == DebugCategory::All || current == category
115}
116
117/// Check if scan path debug logging is enabled
118/// Returns true if VIBESQL_DEBUG=1, VIBESQL_DEBUG=scan, or SCAN_PATH_VERBOSE=1
119#[inline]
120pub fn is_scan_debug_enabled() -> bool {
121    // Use thread-local cache to avoid repeated env var lookups
122    SCAN_PATH_VERBOSE_ENABLED.with(|cache| {
123        if let Some(enabled) = cache.get() {
124            return enabled;
125        }
126
127        let enabled =
128            is_debug_enabled(DebugCategory::Scan) || std::env::var("SCAN_PATH_VERBOSE").is_ok();
129
130        cache.set(Some(enabled));
131        enabled
132    })
133}
134
135/// A profiling timer that logs elapsed time when dropped
136pub struct ProfileTimer {
137    label: &'static str,
138    start: Instant,
139    enabled: bool,
140}
141
142impl ProfileTimer {
143    /// Create a new profiling timer
144    pub fn new(label: &'static str) -> Self {
145        let enabled = is_enabled();
146        Self { label, start: Instant::now(), enabled }
147    }
148}
149
150impl Drop for ProfileTimer {
151    fn drop(&mut self) {
152        if self.enabled {
153            let elapsed = self.start.elapsed();
154            DebugEvent::new(Category::Profile, "timer", "PROFILE")
155                .text(format!(
156                    "{} took {:.3}ms ({:.0}µs)",
157                    self.label,
158                    elapsed.as_secs_f64() * 1000.0,
159                    elapsed.as_micros()
160                ))
161                .field_str("label", self.label)
162                .field_duration_ms("duration_ms", elapsed)
163                .field_duration_us("duration_us", elapsed)
164                .emit();
165        }
166    }
167}
168
169/// Macro to create a profiling scope
170#[macro_export]
171macro_rules! profile {
172    ($label:expr) => {
173        let _timer = $crate::profiling::ProfileTimer::new($label);
174    };
175}