use std::collections::HashMap;
use crate::{Connection, inner_connection::InnerConnection};
#[derive(Debug, Clone)]
pub struct ProfilingInfo {
pub metrics: HashMap<String, String>,
pub children: Vec<ProfilingInfo>,
}
impl ProfilingInfo {
fn from_raw(info: libduckdb_sys::duckdb_profiling_info) -> Option<Self> {
if info.is_null() {
return None;
}
let mut map = unsafe { libduckdb_sys::duckdb_profiling_info_get_metrics(info) };
let map_size = unsafe { libduckdb_sys::duckdb_get_map_size(map) };
let mut metrics = HashMap::<String, String>::with_capacity(map_size as usize);
for i in 0..map_size {
let mut key = unsafe { libduckdb_sys::duckdb_get_map_key(map, i) };
let mut val = unsafe { libduckdb_sys::duckdb_get_map_value(map, i) };
let key_mem = unsafe { libduckdb_sys::duckdb_get_varchar(key) };
let val_mem = unsafe { libduckdb_sys::duckdb_get_varchar(val) };
let key_str = unsafe { std::ffi::CStr::from_ptr(key_mem) }
.to_string_lossy()
.to_string();
let val_str = unsafe { std::ffi::CStr::from_ptr(val_mem) }
.to_string_lossy()
.to_string();
metrics.insert(key_str, val_str);
unsafe { libduckdb_sys::duckdb_free(key_mem as *mut std::ffi::c_void) };
unsafe { libduckdb_sys::duckdb_free(val_mem as *mut std::ffi::c_void) };
unsafe { libduckdb_sys::duckdb_destroy_value(&mut key) };
unsafe { libduckdb_sys::duckdb_destroy_value(&mut val) };
}
unsafe { libduckdb_sys::duckdb_destroy_value(&mut map) };
let child_count = unsafe { libduckdb_sys::duckdb_profiling_info_get_child_count(info) };
let mut children = Vec::with_capacity(child_count as usize);
for i in 0..child_count {
let child_info = unsafe { Self::from_raw(libduckdb_sys::duckdb_profiling_info_get_child(info, i)) };
if let Some(info) = child_info {
children.push(info);
}
}
Some(ProfilingInfo { metrics, children })
}
}
impl InnerConnection {
pub fn get_profiling_info(&self) -> Option<ProfilingInfo> {
let info = unsafe { libduckdb_sys::duckdb_get_profiling_info(self.con) };
ProfilingInfo::from_raw(info)
}
}
impl Connection {
pub fn get_profiling_info(&self) -> Option<ProfilingInfo> {
self.db.borrow().get_profiling_info()
}
}
#[cfg(test)]
mod tests {
use crate::Connection;
#[test]
fn test_profiling_info() {
let conn = Connection::open_in_memory().unwrap();
conn.execute("SELECT 1", []).unwrap();
let info = conn.get_profiling_info();
assert!(
info.is_none(),
"Profiling info should be None when profiling is not enabled"
);
conn.execute("PRAGMA enable_profiling = 'no_output'", []).unwrap();
conn.execute("PRAGMA profiling_mode = 'standard'", []).unwrap();
conn.execute("SELECT 1", []).unwrap();
let info = conn.get_profiling_info();
assert!(info.is_some(), "Metrics should be present when profiling is enabled");
let info = info.unwrap();
assert!(!info.metrics.is_empty(), "Metrics should not be empty");
assert!(
!info.children.is_empty(),
"There should be at least one child for a simple query"
);
assert!(
info.metrics.contains_key("ROWS_RETURNED"),
"Metrics should contain ROWS_RETURNED"
);
assert!(
info.metrics.get("ROWS_RETURNED").unwrap() == "1",
"ROWS_RETURNED should be 1"
);
}
}