altius_benchtools/
profiler.rs

1//! A high-performance profiler for tracing RPC server execution with multi-threaded support.
2//! 
3//! # Overview
4//! 
5//! The profiler module provides a comprehensive solution for tracing and profiling RPC server execution,
6//! with support for multi-threaded environments, detailed event tracking, and flexible output formats.
7//! 
8//! # Features
9//! 
10//! - Task timing with precise start/end markers
11//! - Thread-safe profiling in concurrent environments
12//! - Rich event annotation system
13//! - Multiple output formats (JSON, ZIP)
14//! - Special handling for transaction and commit events
15//! - Global singleton instance with thread-safe access
16//! 
17//! # Examples
18//! 
19//! ```rust
20//! use altius_benchtools::profiler;
21//! 
22//! // Start timing a task
23//! profiler::start("my_task");
24//! 
25//! // Add notes to the current task
26//! profiler::note_str("my_task", "operation", "database_query");
27//! profiler::note_str("my_task", "query_type", "SELECT");
28//! 
29//! // Record timing points
30//! profiler::note_time("my_task", "query_start");
31//! 
32//! // End timing the task
33//! profiler::end("my_task");
34//! 
35//! // Export results
36//! profiler::dump_json("profile_output.json");
37//! ```
38//! 
39//! # Multi-threaded Usage
40//! 
41//! The profiler is thread-safe and can be used across multiple threads:
42//! 
43//! ```rust
44//! use std::thread;
45//! use altius_benchtools::profiler;
46//! 
47//! let handle = thread::spawn(|| {
48//!     profiler::start("worker_task");
49//!     profiler::note_str("worker_task", "thread_info", "worker_1");
50//!     // ... perform work ...
51//!     profiler::end("worker_task");
52//! });
53//! ```
54//! 
55//! # Output Format
56//! 
57//! The profiler generates a structured JSON output containing:
58//! 
59//! - Task timing information (start time, duration)
60//! - Thread identification
61//! - Custom annotations and notes
62//! - Special event types (transactions, commits)
63//! 
64//! Example output structure:
65//! ```json
66//! {
67//!   "title": {},
68//!   "details": [
69//!     [
70//!       {
71//!         "type": "transaction",
72//!         "tx": "task_name",
73//!         "runtime": 1234567,
74//!         "start": 1000000,
75//!         "end": 2234567,
76//!         "status": "success",
77//!         "detail": {
78//!           "operation": "database_query",
79//!           "query_type": "SELECT"
80//!         }
81//!       }
82//!     ]
83//!   ]
84//! }
85//! ```
86//! 
87//! # Note on Thread Safety
88//! 
89//! The profiler uses a global singleton instance protected by a mutex to ensure thread safety.
90//! All operations are atomic and can be safely performed from any thread.
91
92use once_cell::sync::Lazy;
93use serde_json::{json, Map, Value};
94use std::{
95    collections::HashMap,
96    fs::File,
97    io::{BufWriter, Write},
98    sync::Mutex,
99    thread::current,
100    time::Instant,
101};
102use zip::{write::FileOptions, CompressionMethod, ZipWriter};
103
104/// Global profiler instance initialized lazily
105static PROFILER: Lazy<Mutex<Profiler>> = Lazy::new(|| {
106    Mutex::new(Profiler {
107        genesis: Instant::now(),
108        thread_tasks: HashMap::new(),
109        global_tasks: HashMap::new(),
110    })
111});
112
113#[derive(Debug)]
114struct Profiler {
115    genesis: Instant,
116    thread_tasks: HashMap<
117        String, // thread id
118        HashMap<
119            String, // task name
120            Vec<(
121                u128,               // start time
122                Option<u128>,       // end time (optional)
123                Map<String, Value>, // other description
124            )>,
125        >,
126    >,
127    global_tasks: HashMap<
128        String,       // task name
129        (u128, bool), // occurrence count & is ended
130    >,
131}
132
133impl Profiler {
134    /// Returns a reference to the global profiler instance
135    fn global() -> &'static Mutex<Profiler> {
136        &PROFILER
137    }
138
139    /// Gets the current thread's name as a string
140    fn get_current_thread_name() -> String {
141        let thread_id = current().id();
142        format!("{:?}", thread_id)
143    }
144
145    /// Inserts a new thread into the profiler if it doesn't exist
146    /// Returns true if the thread was newly inserted, false if it already existed
147    fn insert_thread(&mut self, thread: &str) -> bool {
148        let non_exist = !self.thread_tasks.contains_key(thread);
149        if non_exist {
150            self.thread_tasks.insert(thread.to_string(), HashMap::new());
151        }
152        non_exist
153    }
154
155    /// Inserts a new task for a specific thread if it doesn't exist
156    /// Returns true if the task was newly inserted, false if it already existed
157    fn insert_thread_task(&mut self, task: &str, thread: &str) -> bool {
158        self.insert_thread(thread);
159        let thread_tasks = self.thread_tasks.get_mut(thread).unwrap();
160        let non_exist = !thread_tasks.contains_key(task);
161        if non_exist {
162            thread_tasks.insert(task.to_string(), vec![]);
163        }
164        non_exist
165    }
166
167    /// Inserts a new task for the current thread if it doesn't exist
168    /// Returns true if the task was newly inserted, false if it already existed
169    fn insert_current_thread_task(&mut self, task: &str) -> bool {
170        self.insert_thread_task(task, &Profiler::get_current_thread_name())
171    }
172
173    /// Gets a reference to the events vector for a specific task and thread
174    /// Panics if either the thread or task don't exist
175    fn must_get(&self, task: &str, thread: &str) -> &Vec<(u128, Option<u128>, Map<String, Value>)> {
176        self.thread_tasks.get(thread).unwrap().get(task).unwrap()
177    }
178
179    /// Gets a reference to the events vector for a specific task in the current thread
180    /// Panics if either the thread or task don't exist
181    fn must_get_current(&self, task: &str) -> &Vec<(u128, Option<u128>, Map<String, Value>)> {
182        self.thread_tasks
183            .get(&Profiler::get_current_thread_name())
184            .unwrap()
185            .get(task)
186            .unwrap()
187    }
188
189    /// Gets a mutable reference to the events vector for a specific task and thread
190    /// Panics if either the thread or task don't exist
191    fn must_get_mut(
192        &mut self,
193        task: &str,
194        thread: &str,
195    ) -> &mut Vec<(u128, Option<u128>, Map<String, Value>)> {
196        self.thread_tasks
197            .get_mut(thread)
198            .unwrap()
199            .get_mut(task)
200            .unwrap()
201    }
202
203    /// Gets a mutable reference to the events vector for a specific task in the current thread
204    /// Panics if either the thread or task don't exist
205    fn must_get_mut_current(
206        &mut self,
207        task: &str,
208    ) -> &mut Vec<(u128, Option<u128>, Map<String, Value>)> {
209        self.thread_tasks
210            .get_mut(&Profiler::get_current_thread_name())
211            .unwrap()
212            .get_mut(task)
213            .unwrap()
214    }
215
216    /// Clears all profiling data from the profiler
217    fn clear(&mut self) {
218        self.thread_tasks.clear();
219    }
220}
221
222/// Returns the genesis time when the profiler was initialized.
223/// 
224/// This timestamp serves as the reference point for all timing measurements
225/// in the profiler. All durations are calculated relative to this time.
226/// 
227/// # Returns
228/// 
229/// * `Instant` - The initialization timestamp of the profiler
230pub fn get_genesis() -> Instant {
231    let profiler = Profiler::global().lock().unwrap();
232    profiler.genesis
233}
234
235/// Starts timing a new task in the current thread.
236/// 
237/// This function begins tracking a new task's execution time. Each task must be ended
238/// with a corresponding call to [`end()`]. Multiple tasks can be tracked simultaneously,
239/// but nested tasks of the same name are not supported.
240/// 
241/// # Arguments
242/// 
243/// * `task` - A string identifier for the task to be timed
244/// 
245/// # Panics
246/// 
247/// * Panics if the last event for this task name is not ended (i.e., if you try to start
248///   a task that was already started but not ended)
249/// 
250/// # Examples
251/// 
252/// ```rust
253/// use altius_benchtools::profiler;
254/// 
255/// profiler::start("database_query");
256/// // ... perform database operation ...
257/// profiler::end("database_query");
258/// ```
259pub fn start(task: &str) {
260    let mut profiler = Profiler::global().lock().unwrap();
261    let genesis = profiler.genesis;
262    match profiler.insert_current_thread_task(task) {
263        false => assert!(
264            profiler.must_get_current(task).last().unwrap().1.is_some(),
265            "the last event must be end"
266        ),
267        true => (),
268    };
269    profiler.must_get_mut_current(task).push((
270        Instant::now().duration_since(genesis).as_nanos(),
271        None,
272        Map::new(),
273    ));
274}
275
276/// Starts timing a new task that may be called multiple times with the same name.
277/// 
278/// This function is specifically designed for tasks that need to be executed multiple times
279/// with the same base name. It automatically appends an index to the task name to differentiate
280/// between multiple instances.
281/// 
282/// # Arguments
283/// 
284/// * `base_task` - The base name for the task. The actual task name will be `{base_task}-[{index}]`
285/// 
286/// # Panics
287/// 
288/// * Panics if the last event for this task name is not ended
289/// 
290/// # Thread Safety
291/// 
292/// * This function only works in the main thread
293/// 
294/// # Examples
295/// 
296/// ```rust
297/// use altius_benchtools::profiler;
298/// 
299/// // First instance
300/// profiler::start_multi("batch_process");
301/// // ... process batch 1 ...
302/// profiler::end_multi("batch_process");
303/// 
304/// // Second instance
305/// profiler::start_multi("batch_process");
306/// // ... process batch 2 ...
307/// profiler::end_multi("batch_process");
308/// ```
309pub fn start_multi(base_task: &str) {
310    let mut profiler = Profiler::global().lock().unwrap();
311    let genesis = profiler.genesis;
312    let count = match profiler.global_tasks.get_mut(base_task) {
313        None => {
314            profiler
315                .global_tasks
316                .insert(base_task.to_string(), (1, false));
317            0
318        }
319        Some((count, is_ended)) => {
320            assert!(*is_ended, "the last event must be end");
321            *count += 1;
322            *is_ended = false;
323            *count - 1
324        }
325    };
326    let task = &format!("{}-[{}]", base_task, count);
327    match profiler.insert_thread_task(task, "main") {
328        false => assert!(
329            profiler.must_get(task, "main").last().unwrap().1.is_some(),
330            "the last event must be end"
331        ),
332        true => (),
333    };
334    profiler.must_get_mut(task, "main").push((
335        Instant::now().duration_since(genesis).as_nanos(),
336        None,
337        Map::new(),
338    ));
339}
340
341/// Ends timing for a task in the current thread.
342/// 
343/// This function stops tracking a task's execution time and records its duration.
344/// It must be called after a corresponding [`start()`] call for the same task.
345/// 
346/// # Arguments
347/// 
348/// * `task` - The string identifier of the task to end
349/// 
350/// # Panics
351/// 
352/// * Panics if the last event for this task was not started (i.e., if you try to end
353///   a task that wasn't started)
354/// 
355/// # Examples
356/// 
357/// ```rust
358/// use altius_benchtools::profiler;
359/// 
360/// profiler::start("api_request");
361/// // ... perform API request ...
362/// profiler::end("api_request");
363/// ```
364pub fn end(task: &str) {
365    let mut profiler = Profiler::global().lock().unwrap();
366    assert!(
367        profiler.must_get_current(task).last().unwrap().1.is_none(),
368        "the last event must be start"
369    );
370    profiler.must_get_mut_current(task).last_mut().unwrap().1 =
371        Some(Instant::now().duration_since(profiler.genesis).as_nanos());
372}
373
374/// Ends timing for a task that was called multiple times.
375/// 
376/// This function ends timing for a task started with [`start_multi()`]. It must be called
377/// after a corresponding start_multi call with the same base task name.
378/// 
379/// # Arguments
380/// 
381/// * `base_task` - The base name of the task to end (same as used in start_multi)
382/// 
383/// # Panics
384/// 
385/// * Panics if the last event for this task was not started
386/// * Panics if called from a non-main thread
387/// 
388/// # Thread Safety
389/// 
390/// * This function only works in the main thread
391/// 
392/// # Examples
393/// 
394/// ```rust
395/// use altius_benchtools::profiler;
396/// 
397/// // First batch
398/// profiler::start_multi("batch_process");
399/// // ... process batch 1 ...
400/// profiler::end_multi("batch_process"); // Ends "batch_process-[0]"
401/// 
402/// // Second batch
403/// profiler::start_multi("batch_process");
404/// // ... process batch 2 ...
405/// profiler::end_multi("batch_process"); // Ends "batch_process-[1]"
406/// ```
407pub fn end_multi(base_task: &str) {
408    let mut profiler = Profiler::global().lock().unwrap();
409    let (count, is_ended) = profiler.global_tasks.get_mut(base_task).unwrap();
410    assert!(!*is_ended, "the last event must not be end");
411    *is_ended = true;
412    let task = &format!("{}-[{}]", base_task, *count - 1);
413    assert!(
414        profiler.must_get(task, "main").last().unwrap().1.is_none(),
415        "the last event must be start"
416    );
417    profiler.must_get_mut(task, "main").last_mut().unwrap().1 =
418        Some(Instant::now().duration_since(profiler.genesis).as_nanos());
419}
420
421/// Adds a key-value note to the last event of a task.
422/// 
423/// This function allows you to annotate a task with additional metadata in the form
424/// of key-value pairs. The value can be any valid JSON value.
425/// 
426/// # Arguments
427/// 
428/// * `task` - The string identifier of the task to annotate
429/// * `key` - The key for the metadata entry
430/// * `value` - The value to associate with the key (any valid JSON value)
431/// 
432/// # Panics
433/// 
434/// * Panics if the last event was not started (i.e., if you try to add a note to
435///   a task that wasn't started or was already ended)
436/// 
437/// # Examples
438/// 
439/// ```rust
440/// use altius_benchtools::profiler;
441/// use serde_json::json;
442/// 
443/// profiler::start("http_request");
444/// profiler::note("http_request", "method", json!("POST"));
445/// profiler::note("http_request", "headers", json!({
446///     "Content-Type": "application/json",
447///     "Authorization": "Bearer token"
448/// }));
449/// // ... perform request ...
450/// profiler::end("http_request");
451/// ```
452pub fn note(task: &str, key: &str, value: Value) {
453    let mut profiler = Profiler::global().lock().unwrap();
454    assert!(
455        profiler.must_get_current(task).last().unwrap().1.is_none(),
456        "the last event must be start"
457    );
458    profiler
459        .must_get_mut_current(task)
460        .last_mut()
461        .unwrap()
462        .2
463        .insert(key.to_string(), value);
464}
465
466/// Adds a string key-value note to the last event of a task.
467/// 
468/// This is a convenience wrapper around [`note()`] that automatically converts
469/// the string value to a JSON string value.
470/// 
471/// # Arguments
472/// 
473/// * `task` - The string identifier of the task to annotate
474/// * `key` - The key for the metadata entry
475/// * `value` - The string value to associate with the key
476/// 
477/// # Panics
478/// 
479/// * Panics if the last event was not started
480/// 
481/// # Examples
482/// 
483/// ```rust
484/// use altius_benchtools::profiler;
485/// 
486/// profiler::start("request");
487/// profiler::note_str("request", "endpoint", "/api/v1/users");
488/// profiler::note_str("request", "method", "GET");
489/// // ... perform request ...
490/// profiler::end("request");
491/// ```
492pub fn note_str(task: &str, key: &str, value: &str) {
493    note(task, key, Value::String(value.to_string()));
494}
495
496/// Adds multiple key-value notes to the last event of a task.
497/// 
498/// This function allows you to add multiple annotations at once by providing
499/// a map of key-value pairs.
500/// 
501/// # Arguments
502/// 
503/// * `task` - The string identifier of the task to annotate
504/// * `description` - A mutable map containing the key-value pairs to add
505/// 
506/// # Panics
507/// 
508/// * Panics if the last event was not started
509/// 
510/// # Examples
511/// 
512/// ```rust
513/// use altius_benchtools::profiler;
514/// use serde_json::{Map, Value, json};
515/// 
516/// profiler::start("query");
517/// let mut desc = Map::new();
518/// desc.insert("table".to_string(), json!("users"));
519/// desc.insert("type".to_string(), json!("SELECT"));
520/// desc.insert("filter".to_string(), json!({"age": ">= 18"}));
521/// profiler::notes("query", &mut desc);
522/// // ... perform query ...
523/// profiler::end("query");
524/// ```
525pub fn notes(task: &str, description: &mut Map<String, Value>) {
526    let mut profiler = Profiler::global().lock().unwrap();
527    profiler
528        .must_get_mut_current(task)
529        .last_mut()
530        .unwrap()
531        .2
532        .append(description);
533}
534
535/// Adds the current time as a value for a key in the last event of a task.
536/// 
537/// This function records the current timestamp relative to the profiler's genesis time
538/// and adds it as a note to the task.
539/// 
540/// # Arguments
541/// 
542/// * `task` - The string identifier of the task to annotate
543/// * `key` - The key under which to store the timestamp
544/// 
545/// # Panics
546/// 
547/// * Panics if the last event was not started
548/// 
549/// # Examples
550/// 
551/// ```rust
552/// use altius_benchtools::profiler;
553/// 
554/// profiler::start("long_operation");
555/// // ... initial setup ...
556/// profiler::note_time("long_operation", "setup_complete");
557/// // ... main work ...
558/// profiler::note_time("long_operation", "work_complete");
559/// // ... cleanup ...
560/// profiler::end("long_operation");
561/// ```
562pub fn note_time(task: &str, key: &str) {
563    let mut profiler = Profiler::global().lock().unwrap();
564    let genesis = profiler.genesis;
565    assert!(
566        profiler.must_get_current(task).last().unwrap().1.is_none(),
567        "the last event must be start"
568    );
569    profiler
570        .must_get_mut_current(task)
571        .last_mut()
572        .unwrap()
573        .2
574        .insert(
575            key.to_string(),
576            (Instant::now().duration_since(genesis).as_nanos() as u64).into(),
577        );
578}
579
580/// Adds a string key-value note to the last event of a task that was called multiple times.
581/// 
582/// This function adds a string note to a task created with [`start_multi()`]. It must be used
583/// with the base task name (without the index suffix).
584/// 
585/// # Arguments
586/// 
587/// * `base_task` - The base name of the task (same as used in start_multi)
588/// * `key` - The key for the metadata entry
589/// * `value` - The string value to associate with the key
590/// 
591/// # Panics
592/// 
593/// * Panics if the last event was not started
594/// * Panics if called from a non-main thread
595/// 
596/// # Thread Safety
597/// 
598/// * This function only works in the main thread
599/// 
600/// # Examples
601/// 
602/// ```rust
603/// use altius_benchtools::profiler;
604/// 
605/// profiler::start_multi("batch_job");
606/// profiler::note_str_multi("batch_job", "status", "processing");
607/// // ... process batch ...
608/// profiler::note_str_multi("batch_job", "status", "completed");
609/// profiler::end_multi("batch_job");
610/// ```
611pub fn note_str_multi(base_task: &str, key: &str, value: &str) {
612    let mut profiler = Profiler::global().lock().unwrap();
613    let (count, is_ended) = profiler.global_tasks.get_mut(base_task).unwrap();
614    assert!(!*is_ended, "the last event must not be end");
615    let task = &format!("{}-[{}]", base_task, *count - 1);
616    assert!(
617        profiler.must_get(task, "main").last().unwrap().1.is_none(),
618        "the last event must be start"
619    );
620    profiler
621        .must_get_mut(task, "main")
622        .last_mut()
623        .unwrap()
624        .2
625        .insert(key.to_string(), Value::String(value.to_string()));
626}
627
628/// Adds a string key-value note to a task without the usual safety checks.
629/// 
630/// This is an unchecked version of [`note_str()`] that will create a new task entry
631/// if one doesn't exist. Use with caution as it bypasses the normal start/end task flow.
632/// 
633/// # Arguments
634/// 
635/// * `task` - The string identifier of the task to annotate
636/// * `key` - The key for the metadata entry
637/// * `value` - The string value to associate with the key
638/// 
639/// # Safety
640/// 
641/// This function is marked as unchecked because it:
642/// - Does not verify if the task exists
643/// - Creates a new task entry if none exists
644/// - Does not enforce the normal start/end task flow
645/// 
646/// # Examples
647/// 
648/// ```rust
649/// use altius_benchtools::profiler;
650/// 
651/// // Note: This bypasses normal task flow - use with caution
652/// profiler::note_str_unchecked("background_task", "status", "running");
653/// ```
654pub fn note_str_unchecked(task: &str, key: &str, value: &str) {
655    let mut profiler = Profiler::global().lock().unwrap();
656    let genesis = profiler.genesis;
657    match profiler.insert_thread_task(task, "main") {
658        true => profiler.must_get_mut(task, "main").push((
659            Instant::now().duration_since(genesis).as_nanos(),
660            None,
661            Map::new(),
662        )),
663        false => (),
664    };
665    profiler
666        .must_get_mut(task, "main")
667        .last_mut()
668        .unwrap()
669        .2
670        .insert(key.to_string(), Value::String(value.to_string()));
671}
672
673/// Clears all profiling data from the profiler.
674/// 
675/// This function removes all recorded tasks, events, and their associated metadata
676/// from the profiler. The genesis time is preserved.
677/// 
678/// # Examples
679/// 
680/// ```rust
681/// use altius_benchtools::profiler;
682/// 
683/// // After some profiling...
684/// profiler::clear(); // Reset profiler state
685/// ```
686pub fn clear() {
687    let mut profiler = Profiler::global().lock().unwrap();
688    profiler.clear();
689}
690
691/// Dumps the profiler data as a JSON string.
692/// 
693/// This function exports all profiling data in a structured JSON format. The output
694/// includes timing information, thread identification, custom annotations, and special
695/// event types (transactions, commits).
696/// 
697/// # Returns
698/// 
699/// * `String` - A pretty-printed JSON string containing all profiling data
700/// 
701/// # Examples
702/// 
703/// ```rust
704/// use altius_benchtools::profiler;
705/// 
706/// // ... perform profiling ...
707/// 
708/// let json_data = profiler::dump();
709/// println!("Profile data: {}", json_data);
710/// ```
711/// 
712/// # Output Format
713/// 
714/// The output JSON has the following structure:
715/// ```json
716/// {
717///   "title": {},
718///   "details": [
719///     [
720///       {
721///         "type": "transaction",
722///         "tx": "task_name",
723///         "runtime": 1234567,
724///         "start": 1000000,
725///         "end": 2234567,
726///         "status": "success",
727///         "detail": { ... }
728///       }
729///     ]
730///   ]
731/// }
732/// ```
733pub fn dump() -> String {
734    let profiler = Profiler::global().lock().unwrap();
735    let now = Instant::now().duration_since(profiler.genesis).as_nanos();
736
737    let mut output_frontend = Value::Array(vec![]);
738
739    for (_thread_name, thread_events) in &profiler.thread_tasks {
740        let mut detail = vec![];
741        for (name, thread_tasks) in thread_events {
742            for event in thread_tasks {
743                let (start, end_opt, description) = event;
744                let duration = end_opt.unwrap_or(now) - start;
745
746                match description.get("type") {
747                    Some(Value::String(type_str)) => match type_str.as_str() {
748                        "transaction" => detail.push(json!({
749                            "type": "transaction",
750                            "tx": name,
751                            "runtime": duration,
752                            "start": start,
753                            "end": end_opt,
754                            // "status": description.get("status").unwrap().as_str().unwrap_or("unknown"),
755                            "status": match description.get("status") {
756                                Some(value) => value.as_str().unwrap_or("unknown"),
757                                None => "unknown",
758                            },
759                            "detail": description,
760                        })),
761                        "commit" => detail.push(json!({
762                            "type": "commit",
763                            "tx": match description.get("tx") {
764                                Some(value) => value.as_str().unwrap_or("unknown"),
765                                None => "unknown",
766                            },
767                            "runtime": duration,
768                            "start": start,
769                            "end": end_opt,
770                            "detail": description,
771                        })),
772                        other_type => detail.push(json!({
773                            "type": other_type,
774                            "name": name,
775                            "runtime": duration,
776                            "start": start,
777                            "end": end_opt,
778                            "detail": description,
779                        })),
780                    },
781                    _ => detail.push(json!({
782                        "type": "other",
783                        "name": name,
784                        "runtime": duration,
785                        "start": start,
786                        "end": end_opt,
787                        "detail": description,
788                    })),
789                }
790            }
791        }
792        output_frontend
793            .as_array_mut()
794            .unwrap()
795            .push(Value::Array(detail));
796    }
797
798    serde_json::to_string_pretty(&output_frontend).unwrap()
799}
800
801/// Dumps the profiler data to a JSON file at the specified path.
802/// 
803/// This function writes all profiling data to a file in a pretty-printed JSON format.
804/// It's a convenience wrapper around [`dump()`] that handles file I/O.
805/// 
806/// # Arguments
807/// 
808/// * `output_path` - The path where the JSON file should be written
809/// 
810/// # Panics
811/// 
812/// * Panics if the file cannot be created or written to
813/// 
814/// # Examples
815/// 
816/// ```rust
817/// use altius_benchtools::profiler;
818/// 
819/// // After some profiling...
820/// profiler::dump_json("profile_results.json");
821/// ```
822pub fn dump_json(output_path: &str) {
823    let result_json = dump();
824    let mut file = File::create(output_path).unwrap();
825    file.write_all(result_json.as_bytes()).unwrap();
826}
827
828/// Dumps the profiler data to a ZIP file containing a JSON file.
829/// 
830/// This function exports all profiling data to a compressed ZIP file containing
831/// a JSON file. The ZIP file will contain a single JSON file with the same base name.
832/// This is useful for storing large profiling datasets efficiently.
833/// 
834/// # Arguments
835/// 
836/// * `output_name` - The base name for the output files (without extension)
837/// 
838/// # Panics
839/// 
840/// * Panics if the ZIP file cannot be created or written to
841/// 
842/// # Examples
843/// 
844/// ```rust
845/// use altius_benchtools::profiler;
846/// 
847/// // After some profiling...
848/// profiler::dump_zip("profile_results");
849/// // Creates profile_results.zip containing profile_results.json
850/// ```
851pub fn dump_zip(output_name: &str) {
852    let result_json = dump();
853    let file = File::create(output_name.to_string() + ".zip").unwrap();
854    let mut zip = ZipWriter::new(BufWriter::new(file));
855    let options = FileOptions::<()>::default().compression_method(CompressionMethod::Deflated);
856    zip.start_file(output_name.to_string() + ".json", options)
857        .unwrap();
858    zip.write_all(result_json.as_bytes()).unwrap();
859    zip.finish().unwrap();
860}
861
862/// Prints the current state of the profiler for debugging purposes.
863/// 
864/// This function prints a debug representation of the entire profiler state
865/// to stdout. This is primarily intended for development and debugging use.
866/// 
867/// # Examples
868/// 
869/// ```rust
870/// use altius_benchtools::profiler;
871/// 
872/// // After some profiling...
873/// profiler::debug_print(); // Prints internal profiler state
874/// ```
875pub fn debug_print() {
876    println!("Profiler: {:?}", PROFILER);
877}