grafeo_engine/query/
profile.rs1use std::fmt::Write;
8use std::sync::Arc;
9
10use grafeo_common::types::{LogicalType, Value};
11use grafeo_core::execution::profile::{ProfileStats, SharedProfileStats};
12use parking_lot::Mutex;
13
14use super::plan::LogicalOperator;
15use crate::database::QueryResult;
16
17#[derive(Debug)]
19pub struct ProfileNode {
20 pub name: String,
22 pub label: String,
24 pub stats: SharedProfileStats,
26 pub children: Vec<ProfileNode>,
28}
29
30pub struct ProfileEntry {
32 pub name: String,
34 pub label: String,
36 pub stats: SharedProfileStats,
38}
39
40impl ProfileEntry {
41 pub fn new(name: &str, label: String) -> (Self, SharedProfileStats) {
43 let stats = Arc::new(Mutex::new(ProfileStats::default()));
44 let entry = Self {
45 name: name.to_string(),
46 label,
47 stats: Arc::clone(&stats),
48 };
49 (entry, stats)
50 }
51}
52
53pub fn build_profile_tree(
59 logical: &LogicalOperator,
60 entries: &mut impl Iterator<Item = ProfileEntry>,
61) -> ProfileNode {
62 let children: Vec<ProfileNode> = logical
64 .children()
65 .into_iter()
66 .map(|child| build_profile_tree(child, entries))
67 .collect();
68
69 let entry = entries
71 .next()
72 .expect("profile entry count must match logical operator count");
73
74 ProfileNode {
75 name: entry.name,
76 label: entry.label,
77 stats: entry.stats,
78 children,
79 }
80}
81
82pub fn profile_result(root: &ProfileNode, total_time_ms: f64) -> QueryResult {
85 let mut output = String::new();
86 format_node(&mut output, root, 0);
87 let _ = writeln!(output);
88 let _ = write!(output, "Total time: {total_time_ms:.2}ms");
89
90 QueryResult {
91 columns: vec!["profile".to_string()],
92 column_types: vec![LogicalType::String],
93 rows: vec![vec![Value::String(output.into())]],
94 execution_time_ms: Some(total_time_ms),
95 rows_scanned: None,
96 status_message: None,
97 gql_status: grafeo_common::utils::GqlStatus::SUCCESS,
98 }
99}
100
101fn format_node(out: &mut String, node: &ProfileNode, depth: usize) {
103 let indent = " ".repeat(depth);
104
105 let self_time_ns = self_time_ns(node);
107 let self_time_ms = self_time_ns as f64 / 1_000_000.0;
108
109 let rows_out = node.stats.lock().rows_out;
110
111 let _ = writeln!(
112 out,
113 "{indent}{name} ({label}) rows={rows} time={time:.2}ms",
114 name = node.name,
115 label = node.label,
116 rows = rows_out,
117 time = self_time_ms,
118 );
119
120 for child in &node.children {
121 format_node(out, child, depth + 1);
122 }
123}
124
125fn self_time_ns(node: &ProfileNode) -> u64 {
127 let own_time = node.stats.lock().time_ns;
128 let child_time: u64 = node.children.iter().map(|c| c.stats.lock().time_ns).sum();
129 own_time.saturating_sub(child_time)
130}