how_to_use_profiler/
how_to_use_profiler.rs

1use altius_benchtools::profiler;
2use std::{fs, thread};
3
4/// Performs an expensive calculation for benchmarking purposes.
5///
6/// This function simulates a CPU-intensive task by performing repeated
7/// modular exponentiation operations with a large prime modulus.
8///
9/// # Arguments
10///
11/// * `seed` - A seed value that affects the initial state of the calculation
12/// * `repeat` - The number of iterations to perform
13///
14/// # Returns
15///
16/// The final result of the calculation after repeated squaring operations
17fn expensive_calculation(seed: u128, repeat: u128) -> u128 {
18    let modulus: u128 = 18446744073709551617;
19    let mut base: u128 = 12142100291992418551 + seed;
20    for _ in 0..repeat {
21        base = (base * base) % modulus;
22    }
23    base
24}
25
26/// Demonstrates profiling different tasks across multiple threads.
27///
28/// This example shows how to use thread-specific profiling functions to track
29/// the execution of distinct tasks running in separate threads. Each task is
30/// uniquely identified by its name and thread ID.
31///
32/// The pattern used here is appropriate when:
33/// - You need to track multiple distinct tasks across different threads
34/// - Each task needs its own timing and metadata
35/// - Tasks are uniquely identified by both name and thread ID
36///
37/// # Arguments
38///
39/// * `output_path` - Path where the JSON profiling data will be saved
40///
41/// # Notes
42///
43/// * Each task must have a unique name within its thread
44/// * A task must be ended in the same thread where it was started
45/// * Tasks cannot be nested with the same name in the same thread
46///
47/// # Example Output
48///
49/// * See `outputs/profiler-output-0.json` after running the example
50fn different_task_in_different_threads(output_path: &str) {
51    profiler::clear();
52    let thread_num = 3;
53    let sub_task_num = 5;
54    let mut handles = vec![];
55
56    for thread_id in 0..thread_num {
57        let handle = thread::spawn(move || {
58            for task_id in 0..sub_task_num {
59                let seed = thread_id * sub_task_num + task_id;
60                let task_name = format!("task-{}-{}", thread_id, task_id);
61                profiler::start(task_name.as_str());
62                profiler::note_str(
63                    task_name.as_str(),
64                    "extra-descreption",
65                    format!("You can add any extra description here for {}", task_name).as_str(),
66                );
67                expensive_calculation(seed as u128, 10000);
68                profiler::end(task_name.as_str());
69            }
70        });
71        handles.push(handle);
72    }
73
74    for handle in handles {
75        handle.join().unwrap();
76    }
77    profiler::dump_json(output_path);
78}
79
80/// Demonstrates profiling tasks across multiple threads with global task tracking.
81///
82/// This example shows how to use thread-agnostic profiling functions to track
83/// tasks that may be executed across different threads. Each task is identified
84/// by its base name, and an auto-incremented index is appended to distinguish
85/// multiple instances of the same task.
86///
87/// The pattern used here is appropriate when:
88/// - You need to track tasks that may span multiple threads
89/// - You want to reuse the same task name multiple times
90/// - Task identity is more important than which thread executed it
91///
92/// # Arguments
93///
94/// * `output_path` - Path where the JSON profiling data will be saved
95///
96/// # Notes
97///
98/// * Tasks with the same name are automatically given unique identifiers
99/// * A task must be ended before starting another with the same name
100/// * Each task instance is tracked with an auto-incremented ID in the output
101/// * All entries are recorded in the "main" thread regardless of actual thread
102fn global_task_ignore_thread(output_path: &str) {
103    profiler::clear();
104    let thread_num = 3;
105    let sub_task_num = 5;
106    let mut handles = vec![];
107
108    for thread_id in 0..thread_num {
109        let handle = thread::spawn(move || {
110            for task_id in 0..sub_task_num {
111                let seed = thread_id * sub_task_num + task_id;
112                let task_name = format!("task-in-thread-{}", thread_id);
113                profiler::start_multi(task_name.as_str());
114                profiler::note_str_multi(
115                    task_name.as_str(),
116                    "seed",
117                    format!("The seed for this task is {}", seed).as_str(),
118                );
119                expensive_calculation(seed as u128, 10000);
120                profiler::end_multi(task_name.as_str());
121            }
122        });
123        handles.push(handle);
124    }
125
126    for handle in handles {
127        handle.join().unwrap();
128    }
129    profiler::dump_json(output_path);
130}
131
132/// Demonstrates recording profiling data without explicit start/end markers.
133///
134/// This example shows how to use the unchecked profiling functions to record
135/// data without following the normal start/end task flow. This is useful for
136/// recording arbitrary data points or results without timing specific tasks.
137///
138/// The pattern used here is appropriate when:
139/// - You need to record data points without timing concerns
140/// - You want to collect results or metrics from various operations
141/// - You don't need the strict start/end task structure
142///
143/// # Arguments
144///
145/// * `output_path` - Path where the JSON profiling data will be saved
146///
147/// # Notes
148///
149/// * This approach bypasses the normal task flow and safety checks
150/// * Useful for recording results or metrics without timing concerns
151/// * Creates task entries automatically if they don't exist
152/// * All entries are recorded in the "main" thread regardless of actual thread
153///
154/// # Safety
155///
156/// This function uses `note_str_unchecked` which:
157/// - Does not verify if the task exists
158/// - Creates a new task entry if none exists
159/// - Does not enforce the normal start/end task flow
160fn global_record_ignore_start_and_end(output_path: &str) {
161    profiler::clear();
162    let thread_num = 3;
163    let sub_task_num = 5;
164    let mut handles = vec![];
165
166    for thread_id in 0..thread_num {
167        let handle = thread::spawn(move || {
168            for task_id in 0..sub_task_num {
169                let seed = thread_id * sub_task_num + task_id;
170                let result = expensive_calculation(seed as u128, 10000);
171                profiler::note_str_unchecked(
172                    "result-for-tasks",
173                    format!("seed-{}", seed).as_str(),
174                    result.to_string().as_str(),
175                );
176            }
177        });
178        handles.push(handle);
179    }
180
181    for handle in handles {
182        handle.join().unwrap();
183    }
184    profiler::dump_json(output_path);
185}
186
187/// Runs all profiler examples and saves the output to JSON files.
188///
189/// This function demonstrates the three main profiling patterns:
190/// 1. Thread-specific task profiling - Records distinct tasks in different threads
191/// 2. Global task profiling across threads - Records tasks with auto-incremented IDs
192/// 3. Unchecked data recording without start/end markers - Records data points without timing
193///
194/// Each example generates a separate JSON output file in the "outputs" directory.
195fn main() {
196    fs::create_dir_all("outputs").unwrap();
197    different_task_in_different_threads("outputs/profiler-output-0.json");
198    global_task_ignore_thread("outputs/profiler-output-1.json");
199    global_record_ignore_start_and_end("outputs/profiler-output-2.json");
200}