use std::fmt::Write;
use std::sync::Arc;
use grafeo_common::types::{LogicalType, Value};
use grafeo_core::execution::profile::{ProfileStats, SharedProfileStats};
use parking_lot::Mutex;
use super::plan::LogicalOperator;
use crate::database::QueryResult;
#[derive(Debug)]
pub struct ProfileNode {
pub name: String,
pub label: String,
pub stats: SharedProfileStats,
pub children: Vec<ProfileNode>,
}
pub struct ProfileEntry {
pub name: String,
pub label: String,
pub stats: SharedProfileStats,
}
impl ProfileEntry {
pub fn new(name: &str, label: String) -> (Self, SharedProfileStats) {
let stats = Arc::new(Mutex::new(ProfileStats::default()));
let entry = Self {
name: name.to_string(),
label,
stats: Arc::clone(&stats),
};
(entry, stats)
}
}
pub fn build_profile_tree(
logical: &LogicalOperator,
entries: &mut impl Iterator<Item = ProfileEntry>,
) -> ProfileNode {
let children: Vec<ProfileNode> = logical
.children()
.into_iter()
.map(|child| build_profile_tree(child, entries))
.collect();
let entry = entries
.next()
.expect("profile entry count must match logical operator count");
ProfileNode {
name: entry.name,
label: entry.label,
stats: entry.stats,
children,
}
}
pub fn profile_result(root: &ProfileNode, total_time_ms: f64) -> QueryResult {
let mut output = String::new();
format_node(&mut output, root, 0);
let _ = writeln!(output);
let _ = write!(output, "Total time: {total_time_ms:.2}ms");
QueryResult {
columns: vec!["profile".to_string()],
column_types: vec![LogicalType::String],
rows: vec![vec![Value::String(output.into())]],
execution_time_ms: Some(total_time_ms),
rows_scanned: None,
status_message: None,
gql_status: grafeo_common::utils::GqlStatus::SUCCESS,
}
}
fn format_node(out: &mut String, node: &ProfileNode, depth: usize) {
let indent = " ".repeat(depth);
let self_time_ns = self_time_ns(node);
let self_time_ms = self_time_ns as f64 / 1_000_000.0;
let rows_out = node.stats.lock().rows_out;
let _ = writeln!(
out,
"{indent}{name} ({label}) rows={rows} time={time:.2}ms",
name = node.name,
label = node.label,
rows = rows_out,
time = self_time_ms,
);
for child in &node.children {
format_node(out, child, depth + 1);
}
}
fn self_time_ns(node: &ProfileNode) -> u64 {
let own_time = node.stats.lock().time_ns;
let child_time: u64 = node.children.iter().map(|c| c.stats.lock().time_ns).sum();
own_time.saturating_sub(child_time)
}