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")]
168#[must_use]
169pub fn format_with_commas(value: usize) -> String {
170 let s = value.to_string();
171 let mut result = String::new();
172 let bytes = s.as_bytes();
173 let len = bytes.len();
174
175 for (i, &b) in bytes.iter().enumerate() {
176 result.push(b as char);
177 let digits_left = len - i - 1;
178 if digits_left > 0 && digits_left.is_multiple_of(3) {
179 result.push(',');
180 }
181 }
182
183 result
184}
185
186#[cfg(feature = "profiling")]
188#[must_use]
189pub fn format_with_commas_f64(value: f64) -> String {
190 let formatted = format!("{:.2}", value.abs()); let mut parts = formatted.splitn(2, '.');
193
194 let int_part = parts.next().unwrap_or("");
195 let frac_part = parts.next(); let mut result = String::new();
199 let bytes = int_part.as_bytes();
200 let len = bytes.len();
201
202 for (i, &b) in bytes.iter().enumerate() {
203 result.push(b as char);
204 let digits_left = len - i - 1;
205 if digits_left > 0 && digits_left % 3 == 0 {
206 result.push(',');
207 }
208 }
209
210 if let Some(frac) = frac_part {
212 result.push('.');
213 result.push_str(frac);
214 }
215
216 if value.is_sign_negative() {
218 result.insert(0, '-');
219 }
220
221 result
222}
223
224#[cfg(all(test, feature = "profiling"))]
225mod tests {
226 use std::time::Duration;
227
228 use crate::profiling::display::{
229 format_with_commas, format_with_commas_f64, print_named_counts, print_named_spans,
230 };
231 use crate::profiling::*;
232
233 #[test]
234 fn increments_named_count_correctly() {
235 increment_named_count("display_incr_test_event");
236 increment_named_count("display_incr_test_event");
237 increment_named_count("display_incr_another_event");
238
239 let data = profiling_data();
240 assert_eq!(data.get_named_count("display_incr_test_event"), Some(2));
241 assert_eq!(data.get_named_count("display_incr_another_event"), Some(1));
242 }
243
244 #[test]
245 fn print_named_counts_outputs_expected_format() {
246 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 increment_named_count("display_event1_print");
252 print_named_counts(); }
254
255 #[test]
257 fn formats_single_digit() {
258 assert_eq!(format_with_commas(7), "7");
259 }
260
261 #[test]
262 fn formats_two_digits() {
263 assert_eq!(format_with_commas(42), "42");
264 }
265
266 #[test]
267 fn formats_three_digits() {
268 assert_eq!(format_with_commas(999), "999");
269 }
270
271 #[test]
272 fn formats_four_digits() {
273 assert_eq!(format_with_commas(1000), "1,000");
274 }
275
276 #[test]
277 fn formats_five_digits() {
278 assert_eq!(format_with_commas(27_171), "27,171");
279 }
280
281 #[test]
282 fn formats_six_digits() {
283 assert_eq!(format_with_commas(123_456), "123,456");
284 }
285
286 #[test]
287 fn formats_seven_digits() {
288 assert_eq!(format_with_commas(1_000_000), "1,000,000");
289 }
290
291 #[test]
292 fn formats_zero() {
293 assert_eq!(format_with_commas(0), "0");
294 }
295
296 #[test]
297 fn formats_large_number() {
298 assert_eq!(format_with_commas(9_876_543_210), "9,876,543,210");
299 }
300
301 #[test]
305 fn formats_small_integer() {
306 assert_eq!(format_with_commas_f64(7.0), "7.00");
307 assert_eq!(format_with_commas_f64(42.0), "42.00");
308 }
309
310 #[test]
311 fn formats_small_decimal() {
312 #![allow(clippy::approx_constant)]
313 assert_eq!(format_with_commas_f64(3.14), "3.14");
314 assert_eq!(format_with_commas_f64(0.99), "0.99");
315 }
316
317 #[test]
318 fn formats_zero_f64() {
319 assert_eq!(format_with_commas_f64(0.0), "0.00");
320 }
321
322 #[test]
323 fn formats_exact_thousand() {
324 assert_eq!(format_with_commas_f64(1000.0), "1,000.00");
325 }
326
327 #[test]
328 fn formats_large_number_f64() {
329 assert_eq!(format_with_commas_f64(1234567.89), "1,234,567.89");
330 assert_eq!(format_with_commas_f64(123456789.0), "123,456,789.00");
331 }
332
333 #[test]
334 fn formats_number_with_rounding_up() {
335 assert_eq!(format_with_commas_f64(999.999), "1,000.00");
336 assert_eq!(format_with_commas_f64(999999.999), "1,000,000.00");
337 }
338
339 #[test]
340 fn formats_number_with_rounding_down() {
341 assert_eq!(format_with_commas_f64(1234.444), "1,234.44");
342 }
343
344 #[test]
345 fn formats_negative_number() {
346 assert_eq!(format_with_commas_f64(-1234567.89), "-1,234,567.89");
347 }
348
349 #[test]
350 fn formats_negative_rounding_edge() {
351 assert_eq!(format_with_commas_f64(-999.995), "-1,000.00");
352 }
353
354 #[test]
357 fn print_named_spans_outputs_expected_format() {
358 {
360 let _init = open_span("display_init_span");
361 std::thread::sleep(Duration::from_millis(10));
362 }
363 {
365 let mut container = profiling_data();
366 container
367 .spans
368 .insert("database_query", (Duration::from_millis(1500), 42));
369 container
370 .spans
371 .insert("api_request", (Duration::from_millis(800), 120));
372 container
373 .spans
374 .insert("data_processing", (Duration::from_secs(5), 15));
375 container
376 .spans
377 .insert("file_io", (Duration::from_millis(350), 78));
378 container
379 .spans
380 .insert("rendering", (Duration::from_secs(2), 30));
381 }
382 print_named_spans();
383 }
384
385 #[test]
386 fn test_print_computed_statistics_integration() {
387 use crate::profiling::{add_computed_statistic, increment_named_count};
388 increment_named_count("display_metric_integration");
390 increment_named_count("display_metric_integration");
391
392 add_computed_statistic::<usize>(
393 "display_metric_count_integration",
394 "Total metrics",
395 Box::new(|data| data.get_named_count("display_metric_integration")),
396 Box::new(|value| {
397 println!("Metric count: {}", value);
398 }),
399 );
400
401 print_computed_statistics();
402 }
403}