1#[cfg(feature = "profiling")]
2use humantime::format_duration;
3
4#[cfg(feature = "profiling")]
5use super::{profiling_data, ProfilingData, NAMED_COUNTS_HEADERS, NAMED_SPANS_HEADERS};
6
7#[cfg(feature = "profiling")]
9pub fn print_profiling_data() {
10 print_named_spans();
11 print_named_counts();
12 print_computed_statistics();
13}
14
15#[cfg(not(feature = "profiling"))]
16pub fn print_profiling_data() {}
17
18#[cfg(feature = "profiling")]
20pub fn print_named_counts() {
21 let container = profiling_data();
22 if container.counts.is_empty() {
23 return;
25 }
26 let rows = container.get_named_counts_table();
27
28 let mut formatted_rows = vec![
29 NAMED_COUNTS_HEADERS
31 .iter()
32 .map(|s| (*s).to_string())
33 .collect(),
34 ];
35
36 formatted_rows.extend(rows.into_iter().map(|(label, count, rate)| {
37 vec![
38 label,
39 format_with_commas(count),
40 format_with_commas_f64(rate),
41 ]
42 }));
43
44 println!();
45 print_formatted_table(&formatted_rows);
46}
47
48#[cfg(not(feature = "profiling"))]
49pub fn print_named_counts() {}
50
51#[cfg(feature = "profiling")]
53pub fn print_named_spans() {
54 let rows = profiling_data().get_named_spans_table();
55 if rows.is_empty() {
56 return;
58 }
59
60 let mut formatted_rows = vec![
61 NAMED_SPANS_HEADERS
63 .iter()
64 .map(|s| (*s).to_string())
65 .collect(),
66 ];
67
68 formatted_rows.extend(
69 rows.into_iter()
70 .map(|(label, count, duration, percent_runtime)| {
71 vec![
72 label,
73 format_with_commas(count),
74 format_duration(duration).to_string(),
75 format!("{:.2}%", percent_runtime),
76 ]
77 }),
78 );
79
80 println!();
81 print_formatted_table(&formatted_rows);
82}
83
84#[cfg(not(feature = "profiling"))]
85pub fn print_named_spans() {}
86
87#[cfg(feature = "profiling")]
89pub fn print_computed_statistics() {
90 let mut container = profiling_data();
91
92 let stat_count = container.computed_statistics.len();
94 if stat_count == 0 {
95 return;
96 }
97 for idx in 0..stat_count {
98 let mut statistic = container.computed_statistics[idx].take().unwrap();
100 statistic.value = statistic.functions.compute(&container);
101 container.computed_statistics[idx] = Some(statistic);
103 }
104
105 println!();
106
107 for statistic in &container.computed_statistics {
108 let statistic = statistic.as_ref().unwrap();
109 if statistic.value.is_none() {
110 continue;
111 }
112 statistic.functions.print(statistic.value.unwrap());
113 }
114}
115#[cfg(not(feature = "profiling"))]
116pub fn print_computed_statistics() {}
117
118#[cfg(feature = "profiling")]
122pub fn print_formatted_table(rows: &[Vec<String>]) {
123 if rows.len() < 2 {
124 return;
125 }
126
127 let num_cols = rows[0].len();
128 let mut col_widths = vec![0; num_cols];
129
130 for row in rows {
132 for (i, cell) in row.iter().enumerate() {
133 col_widths[i] = col_widths[i].max(cell.len());
134 }
135 }
136
137 let header = &rows[0];
139 for (i, cell) in header.iter().enumerate() {
140 if i == 0 {
141 print!("{:<width$} ", cell, width = col_widths[i] + 1);
142 } else {
143 print!("{:>width$} ", cell, width = col_widths[i] + 1);
144 }
145 }
146 println!();
147
148 let total_width: usize = col_widths.iter().map(|w| *w + 1).sum::<usize>() + 2;
150 println!("{}", "-".repeat(total_width));
151
152 for row in &rows[1..] {
154 for (i, cell) in row.iter().enumerate() {
156 if i == 0 {
157 print!("{:<width$} ", cell, width = col_widths[i] + 1);
158 } else {
159 print!("{:>width$} ", cell, width = col_widths[i] + 1);
160 }
161 }
162 println!();
163 }
164}
165
166#[cfg(feature = "profiling")]
168pub fn format_with_commas(value: usize) -> String {
169 let s = value.to_string();
170 let mut result = String::new();
171 let bytes = s.as_bytes();
172 let len = bytes.len();
173
174 for (i, &b) in bytes.iter().enumerate() {
175 result.push(b as char);
176 let digits_left = len - i - 1;
177 if digits_left > 0 && digits_left.is_multiple_of(3) {
178 result.push(',');
179 }
180 }
181
182 result
183}
184
185#[cfg(feature = "profiling")]
187pub fn format_with_commas_f64(value: f64) -> String {
188 let formatted = format!("{:.2}", value.abs()); let mut parts = formatted.splitn(2, '.');
191
192 let int_part = parts.next().unwrap_or("");
193 let frac_part = parts.next(); let mut result = String::new();
197 let bytes = int_part.as_bytes();
198 let len = bytes.len();
199
200 for (i, &b) in bytes.iter().enumerate() {
201 result.push(b as char);
202 let digits_left = len - i - 1;
203 if digits_left > 0 && digits_left % 3 == 0 {
204 result.push(',');
205 }
206 }
207
208 if let Some(frac) = frac_part {
210 result.push('.');
211 result.push_str(frac);
212 }
213
214 if value.is_sign_negative() {
216 result.insert(0, '-');
217 }
218
219 result
220}
221
222#[cfg(all(test, feature = "profiling"))]
223mod tests {
224 #![allow(clippy::unreadable_literal)]
225 use std::time::Duration;
226
227 use crate::profiling::display::{
228 format_with_commas, format_with_commas_f64, print_named_counts, print_named_spans,
229 };
230 use crate::profiling::*;
231
232 #[test]
233 fn increments_named_count_correctly() {
234 increment_named_count("display_incr_test_event");
235 increment_named_count("display_incr_test_event");
236 increment_named_count("display_incr_another_event");
237
238 let data = profiling_data();
239 assert_eq!(data.get_named_count("display_incr_test_event"), Some(2));
240 assert_eq!(data.get_named_count("display_incr_another_event"), Some(1));
241 }
242
243 #[test]
244 fn print_named_counts_outputs_expected_format() {
245 increment_named_count("display_event1_print");
247 increment_named_count("display_event1_print");
248 increment_named_count("display_event1_print");
249 increment_named_count("display_event1_print");
250 increment_named_count("display_event1_print");
251 print_named_counts(); }
253
254 #[test]
256 fn formats_single_digit() {
257 assert_eq!(format_with_commas(7), "7");
258 }
259
260 #[test]
261 fn formats_two_digits() {
262 assert_eq!(format_with_commas(42), "42");
263 }
264
265 #[test]
266 fn formats_three_digits() {
267 assert_eq!(format_with_commas(999), "999");
268 }
269
270 #[test]
271 fn formats_four_digits() {
272 assert_eq!(format_with_commas(1000), "1,000");
273 }
274
275 #[test]
276 fn formats_five_digits() {
277 assert_eq!(format_with_commas(27_171), "27,171");
278 }
279
280 #[test]
281 fn formats_six_digits() {
282 assert_eq!(format_with_commas(123_456), "123,456");
283 }
284
285 #[test]
286 fn formats_seven_digits() {
287 assert_eq!(format_with_commas(1_000_000), "1,000,000");
288 }
289
290 #[test]
291 fn formats_zero() {
292 assert_eq!(format_with_commas(0), "0");
293 }
294
295 #[test]
296 fn formats_large_number() {
297 assert_eq!(format_with_commas(9_876_543_210), "9,876,543,210");
298 }
299
300 #[test]
304 fn formats_small_integer() {
305 assert_eq!(format_with_commas_f64(7.0), "7.00");
306 assert_eq!(format_with_commas_f64(42.0), "42.00");
307 }
308
309 #[test]
310 fn formats_small_decimal() {
311 #![allow(clippy::approx_constant)]
312 assert_eq!(format_with_commas_f64(3.14), "3.14");
313 assert_eq!(format_with_commas_f64(0.99), "0.99");
314 }
315
316 #[test]
317 fn formats_zero_f64() {
318 assert_eq!(format_with_commas_f64(0.0), "0.00");
319 }
320
321 #[test]
322 fn formats_exact_thousand() {
323 assert_eq!(format_with_commas_f64(1000.0), "1,000.00");
324 }
325
326 #[test]
327 fn formats_large_number_f64() {
328 assert_eq!(format_with_commas_f64(1234567.89), "1,234,567.89");
329 assert_eq!(format_with_commas_f64(123456789.0), "123,456,789.00");
330 }
331
332 #[test]
333 fn formats_number_with_rounding_up() {
334 assert_eq!(format_with_commas_f64(999.999), "1,000.00");
335 assert_eq!(format_with_commas_f64(999999.999), "1,000,000.00");
336 }
337
338 #[test]
339 fn formats_number_with_rounding_down() {
340 assert_eq!(format_with_commas_f64(1234.444), "1,234.44");
341 }
342
343 #[test]
344 fn formats_negative_number() {
345 assert_eq!(format_with_commas_f64(-1234567.89), "-1,234,567.89");
346 }
347
348 #[test]
349 fn formats_negative_rounding_edge() {
350 assert_eq!(format_with_commas_f64(-999.995), "-1,000.00");
351 }
352
353 #[test]
356 fn print_named_spans_outputs_expected_format() {
357 {
359 let _init = open_span("display_init_span");
360 std::thread::sleep(Duration::from_millis(10));
361 }
362 {
364 let mut container = profiling_data();
365 container
366 .spans
367 .insert("database_query", (Duration::from_millis(1500), 42));
368 container
369 .spans
370 .insert("api_request", (Duration::from_millis(800), 120));
371 container
372 .spans
373 .insert("data_processing", (Duration::from_secs(5), 15));
374 container
375 .spans
376 .insert("file_io", (Duration::from_millis(350), 78));
377 container
378 .spans
379 .insert("rendering", (Duration::from_secs(2), 30));
380 }
381 print_named_spans();
382 }
383
384 #[test]
385 fn test_print_computed_statistics_integration() {
386 use crate::profiling::{add_computed_statistic, increment_named_count};
387 increment_named_count("display_metric_integration");
389 increment_named_count("display_metric_integration");
390
391 add_computed_statistic::<usize>(
392 "display_metric_count_integration",
393 "Total metrics",
394 Box::new(|data| data.get_named_count("display_metric_integration")),
395 Box::new(|value| {
396 println!("Metric count: {}", value);
397 }),
398 );
399
400 print_computed_statistics();
401 }
402}