ixa/profiling/
reporting.rs1use std::io::Result as IoResult;
2use std::path::Path;
3
4use super::file::write_profiling_data_to_file;
5use super::print_profiling_data;
6#[cfg(feature = "profiling")]
7use super::print_query_timings;
8use crate::execution_stats::{print_execution_statistics as print_statistics, ExecutionStatistics};
9use crate::{error, Context, ContextReportExt};
10
11pub trait ProfilingContextExt: ContextReportExt {
13 fn print_execution_statistics(&mut self, include_profiling_data: bool) {
18 print_execution_statistics(self, include_profiling_data);
19 }
20
21 fn write_profiling_data(&mut self) {
28 #[cfg(feature = "profiling")]
29 write_profiling_output(self, |path, statistics| {
30 write_profiling_data_to_file(path, statistics, &[])
31 });
32 #[cfg(not(feature = "profiling"))]
33 write_profiling_output(self, |path, statistics| {
34 write_profiling_data_to_file(path, statistics)
35 });
36 }
37}
38
39impl ProfilingContextExt for Context {
40 fn print_execution_statistics(&mut self, include_profiling_data: bool) {
41 print_execution_statistics(self, include_profiling_data);
42 #[cfg(feature = "profiling")]
43 {
44 if include_profiling_data {
45 print_query_timings(&self.query_profiling_snapshot());
46 }
47 }
48 }
49
50 fn write_profiling_data(&mut self) {
51 #[cfg(feature = "profiling")]
52 {
53 let query_timings = self.query_profiling_snapshot();
54 write_profiling_output(self, |path, statistics| {
55 write_profiling_data_to_file(path, statistics, &query_timings)
56 });
57 }
58 #[cfg(not(feature = "profiling"))]
59 write_profiling_output(self, |path, statistics| {
60 write_profiling_data_to_file(path, statistics)
61 });
62 }
63}
64
65fn print_execution_statistics<C: ContextReportExt>(context: &mut C, include_profiling_data: bool) {
66 let stats = context.get_execution_statistics();
67 print_statistics(&stats);
68
69 if include_profiling_data {
70 print_profiling_data();
71 }
72}
73
74fn write_profiling_output<C, F>(context: &mut C, write: F)
75where
76 C: ContextReportExt,
77 F: FnOnce(&Path, ExecutionStatistics) -> IoResult<()>,
78{
79 let (mut prefix, directory, overwrite) = {
80 let report_options = context.report_options();
81 (
82 report_options.file_prefix.clone(),
83 report_options.output_dir.clone(),
84 report_options.overwrite,
85 )
86 };
87
88 let execution_statistics = context.get_execution_statistics();
89 prefix.push_str("profiling.json");
92 let profiling_data_path = directory.join(prefix);
93 let profiling_data_path = Path::new(&profiling_data_path);
94
95 if !overwrite && profiling_data_path.exists() {
96 error!(
97 "profiling output file already exists: {}",
98 profiling_data_path.display()
99 );
100 return;
101 }
102
103 write(profiling_data_path, execution_statistics)
104 .expect("could not write profiling data to file");
105}
106
107#[cfg(all(test, feature = "profiling"))]
108mod profiling_tests {
109 use std::fs;
110 use std::time::Duration;
111
112 use tempfile::tempdir;
113
114 use super::ProfilingContextExt;
115 use crate::context::Context;
116 use crate::profiling::{add_computed_statistic, increment_named_count, open_span};
117 use crate::report::ContextReportExt as _; #[test]
120 fn print_execution_statistics_without_profiling_data() {
121 let mut context = Context::new();
122 context.print_execution_statistics(false);
123 }
124
125 #[test]
126 fn print_execution_statistics_with_profiling_data() {
127 increment_named_count("reporting_print_event");
129 increment_named_count("reporting_print_event");
130 {
131 let _span = open_span("reporting_print_span");
132 std::thread::sleep(Duration::from_millis(5));
133 }
134 add_computed_statistic::<usize>(
135 "reporting_print_stat",
136 "Count of reporting_print_event",
137 Box::new(|data| data.get_named_count("reporting_print_event")),
138 Box::new(|_v| {}),
139 );
140
141 let mut context = Context::new();
142 context.print_execution_statistics(true);
143 }
144
145 #[test]
146 fn write_profiling_data_creates_json() {
147 let temp_dir = tempdir().unwrap();
148 let out_dir = temp_dir.path().to_path_buf();
149
150 increment_named_count("reporting_write_event");
152 {
153 let _span = open_span("reporting_write_span");
154 std::thread::sleep(Duration::from_millis(3));
155 }
156
157 let mut context = Context::new();
158 let config = context.report_options();
159 config
160 .file_prefix("test_")
161 .directory(out_dir.clone())
162 .overwrite(true);
163
164 context.write_profiling_data();
165
166 let file_path = out_dir.join("test_profiling.json");
167 assert!(file_path.exists(), "JSON file should be created");
168
169 let content = fs::read_to_string(&file_path).expect("Failed to read JSON");
170 let json: serde_json::Value = serde_json::from_str(&content).expect("Invalid JSON");
171 assert!(json["execution_statistics"].is_object());
172 assert!(json["named_counts"].is_array());
173 assert!(json["named_spans"].is_array());
174 assert!(json["computed_statistics"].is_object());
175 }
176
177 #[test]
178 fn write_profiling_data_respects_overwrite_false() {
179 let temp_dir = tempdir().unwrap();
180 let out_dir = temp_dir.path().to_path_buf();
181
182 let file_path = out_dir.join("prefix_profiling.json");
184 fs::write(&file_path, "PREEXISTING").unwrap();
185
186 let mut context = Context::new();
187 let config = context.report_options();
188 config
189 .file_prefix("prefix_")
190 .directory(out_dir.clone())
191 .overwrite(false);
192
193 context.write_profiling_data();
195
196 let after = fs::read_to_string(&file_path).unwrap();
197 assert_eq!(
198 after, "PREEXISTING",
199 "File should remain unchanged when overwrite=false"
200 );
201 }
202
203 #[test]
204 fn write_profiling_data_overwrites_when_true() {
205 let temp_dir = tempdir().unwrap();
206 let out_dir = temp_dir.path().to_path_buf();
207
208 let file_path = out_dir
209 .join("ow_")
210 .join("..") .canonicalize()
212 .unwrap_or(out_dir.clone())
213 .join("ow_profiling.json");
214
215 let _ = fs::create_dir_all(file_path.parent().unwrap());
217 fs::write(&file_path, "OLD").unwrap();
218
219 let mut context = Context::new();
220 let config = context.report_options();
221 config
222 .file_prefix("ow_")
223 .directory(file_path.parent().unwrap().to_path_buf())
224 .overwrite(true);
225
226 context.write_profiling_data();
227
228 let content = fs::read_to_string(&file_path).unwrap();
229 assert!(
230 content.starts_with("{"),
231 "File should contain JSON after overwrite"
232 );
233 assert_ne!(
234 content, "OLD",
235 "File content should be updated when overwrite=true"
236 );
237 }
238}
239
240#[cfg(test)]
241mod tests {
242 use super::ProfilingContextExt;
243 use crate::context::Context;
244
245 #[test]
246 fn context_implements_profiling_extension() {
247 let mut context = Context::new();
248 context.print_execution_statistics(false);
249 context.write_profiling_data();
250 }
251}