use std::sync::LazyLock;
use instant::Instant;
use crate::debug_output::{self, Category, DebugEvent};
static DEBUG_LEVEL: LazyLock<u8> = LazyLock::new(|| {
std::env::var("VIBESQL_DEBUG")
.ok()
.and_then(|v| match v.to_lowercase().as_str() {
"1" | "true" | "all" => Some(1),
"optimizer" => Some(2),
"scan" => Some(3),
"dml" => Some(4),
_ => v.parse().ok(),
})
.unwrap_or(0)
});
static PROFILING_ENABLED: LazyLock<bool> =
LazyLock::new(|| std::env::var("VIBESQL_PROFILE").is_ok());
#[derive(Clone, Copy, PartialEq, Eq)]
pub enum DebugCategory {
All,
Optimizer,
Scan,
Dml,
None,
}
thread_local! {
static SCAN_PATH_VERBOSE_ENABLED: std::cell::Cell<Option<bool>> = const { std::cell::Cell::new(None) };
}
pub fn init() {
debug_output::init();
if *PROFILING_ENABLED {
debug_output::debug_event(Category::Profile, "init", "PROFILE")
.text("Profiling enabled")
.field_bool("enabled", true)
.emit();
}
}
pub fn is_enabled() -> bool {
*PROFILING_ENABLED
}
pub fn debug_category() -> DebugCategory {
match *DEBUG_LEVEL {
0 => DebugCategory::None,
1 => DebugCategory::All,
2 => DebugCategory::Optimizer,
3 => DebugCategory::Scan,
4 => DebugCategory::Dml,
_ => DebugCategory::None,
}
}
pub fn is_debug_enabled(category: DebugCategory) -> bool {
let current = debug_category();
current == DebugCategory::All || current == category
}
#[inline]
pub fn is_scan_debug_enabled() -> bool {
SCAN_PATH_VERBOSE_ENABLED.with(|cache| {
if let Some(enabled) = cache.get() {
return enabled;
}
let enabled =
is_debug_enabled(DebugCategory::Scan) || std::env::var("SCAN_PATH_VERBOSE").is_ok();
cache.set(Some(enabled));
enabled
})
}
pub struct ProfileTimer {
label: &'static str,
start: Instant,
enabled: bool,
}
impl ProfileTimer {
pub fn new(label: &'static str) -> Self {
let enabled = is_enabled();
Self { label, start: Instant::now(), enabled }
}
}
impl Drop for ProfileTimer {
fn drop(&mut self) {
if self.enabled {
let elapsed = self.start.elapsed();
DebugEvent::new(Category::Profile, "timer", "PROFILE")
.text(format!(
"{} took {:.3}ms ({:.0}µs)",
self.label,
elapsed.as_secs_f64() * 1000.0,
elapsed.as_micros()
))
.field_str("label", self.label)
.field_duration_ms("duration_ms", elapsed)
.field_duration_us("duration_us", elapsed)
.emit();
}
}
}
#[macro_export]
macro_rules! profile {
($label:expr) => {
let _timer = $crate::profiling::ProfileTimer::new($label);
};
}