1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
//! This module provides a lightweight, feature-gated profiling interface for simulations.
//! It tracks event counts and measures elapsed time for named operations ("spans"), and can
//! export the results to the console and to a JSON file together with execution statistics. It supports:
//!
//! - Event counting – track how often named events occur during a run.
//! - Rate calculation – compute rates (events per second) since the first count.
//! - Span timing – measure time intervals with automatic closing on drop.
//! - Coverage – report how much of total runtime is covered by any span via a special
//! "Total Measured" span.
//! - Computed statistics – define custom, derived metrics over collected data.
//! - A default computed statistic, infection forecasting efficiency.
//!
//! Feature flag: all functionality is gated behind the `profiling` feature (enabled by default).
//! When the feature is disabled, the public API remains available but becomes a no-op and gets
//! optimized away by the compiler, so you can leave profiling calls in your code at zero cost.
//!
//! ## Example console output
//! ```ignore
//! Span Label Count Duration % runtime
//! ----------------------------------------------------------------------
//! load_synth_population 1 950us 792ns 0.36%
//! infection_attempt 1035 6ms 33us 91ns 2.28%
//! sample_setting 1035 3ms 66us 52ns 1.16%
//! get_contact 1035 1ms 135us 202ns 0.43%
//! schedule_next_forecasted_infection 1286 22ms 329us 102ns 8.44%
//! Total Measured 1385 23ms 897us 146ns 9.03%
//!
//! Event Label Count Rate (per sec)
//! -----------------------------------------------------
//! property progression 36 136.05
//! recovery 27 102.04
//! accepted infection attempt 1,035 3,911.50
//! forecasted infection 1,286 4,860.09
//!
//! Infection Forecasting Efficiency: 80.48%
//! ```
//!
//! ## API functions
//! - `increment_named_count`
//! - `open_span`
//! - `close_span`
//! - `print_profiling_data`
//! - `print_named_counts`
//! - `print_named_spans`
//! - `print_computed_statistics`
//! - `add_computed_statistic`
//!
//! All of the above functions are no-ops without the `profiling` feature.
//!
//! ## Basic usage
//!
//! Count an event:
//! ```rust,ignore
//! increment_named_count("forecasted infection");
//! increment_named_count("accepted infection attempt");
//! ```
//!
//! Time an operation:
//! ```rust,ignore
//! let span = open_span("forecast loop");
//! // operation code here (algorithm, function call, etc.)
//! close_span(span); // optional; dropping the span also closes it
//! ```
//!
//! You can also rely on RAII to auto-close a span at the end of scope:
//! ```rust,ignore
//! fn complicated_function() {
//! let _span = open_span("complicated function");
//! // Complicated control flow here, maybe with lots of `return` points.
//! } // `_span` goes out of scope, automatically closed.
//! ```
//!
//! Printing results to the console:
//! ```rust,ignore
//! // Call after the simulation completes
//! print_profiling_data();
//! ```
//! Prints spans, counts, and any computed statistics via the functions
//! `print_named_spans()`, `print_named_counts()`, `print_computed_statistics()`,
//! which you can use individually if you prefer.
//!
//! Writing results to JSON together with execution statistics:
//! ```rust,ignore
//! use ixa::Context; // your simulation context
//! use crate::profiling::ProfilingContextExt;
//!
//! fn finalize(mut context: Context) {
//! // Ensure Params::profiling_data_path is set, and report options specify
//! // output_dir/file_prefix/overwrite. This writes a pretty JSON file with:
//! // date_time, execution_statistics, named_counts, named_spans, computed_statistics
//! context.write_profiling_data();
//! }
//! ```
//!
//! Special names and coverage
//! - Spans may overlap or nest. The sum of all individual span durations will not
//! generally equal total runtime. A special span named `"Total Measured"` is open
//! if and only if any other span is open. It tells you how much of the total running
//! time is covered by some span.
//!
//! ## Computed statistics
//!
//! You can register custom computed statistics that derive values from the current
//! `ProfilingData`. Use `add_computed_statistic(label, description, computer, printer)`
//! to add one. The relevant API is:
//!
//! ```rust, ignore
//! // Not exactly as implemented for technical reasons.
//! pub fn add_computed_statistic(
//! // The label used in the profiling JSON file
//! label: &'static str,
//! /// Description of the statistic. Used in the JSON report.
//! description: &'static str,
//! /// A function that takes a reference to the `ProfilingData` and computes a value
//! computer: CustomStatisticComputer,
//! /// A function that prints the computed value to the console.
//! printer: CustomStatisticPrinter,
//! );
//!
//! pub type CustomStatisticComputer<T> =
//! Box<dyn (Fn(&ProfilingData) -> Option<T>) + Send + Sync>;
//! pub type CustomStatisticPrinter<T> = Box<dyn (Fn(T)) + Send + Sync>;
//!
//! // The "computer" gets an immutable reference to all counts and spans and to the start time.
//! pub fn add_computed_statistic<T: ComputableType>(
//! label: &'static str,
//! description: &'static str,
//! computer: CustomStatisticComputer<T>,
//! printer: CustomStatisticPrinter<T>,
//! )
//! ```
//!
//! The "computer" returns an option for cases when a statistic is only conditionally
//! defined. The "printer" takes the computed value and prints it to the console.
//!
//! Computed statistics are printed by `print_computed_statistics()` and included in the
//! JSON report under `computed_statistics` (with label, description, and value).
//!
//!
//! Example of using `"forecasted infection"` and `"accepted infection attempt"`.
//! ```rust,ignore
//! context.add_plan(next_time, move |context| {
//! increment_named_count("forecasted infection");
//! if evaluate_forecast(context, person, forecasted_total_infectiousness) {
//! if let Some(setting_id) = context.get_setting_for_contact(person) {
//! if let Some(next_contact) = infection_attempt(context, person, setting_id) {
//! increment_named_count("accepted infection attempt");
//! context.infect_person(next_contact, Some(person), None, None);
//! }
//! }
//! }
//! schedule_next_forecasted_infection(context, person);
//! });
//! ```
use Path;
use Instant;
pub use *;
pub use *;
pub use *;
use write_profiling_data_to_file;
pub use *;
use crate::;
/// Publicly expose access to profiling data only for testing.
// "Magic" constants used in this module
/// The distinguished total measured time label.
const TOTAL_MEASURED: &str = "Total Measured";
const NAMED_SPANS_HEADERS: & = &;
const NAMED_COUNTS_HEADERS: & = &;