Profiling Module
Ixa includes a lightweight, feature-gated profiling module you can use to:
- Count named events (and compute event rates)
- Time named operations ("spans")
- Print results to the console
- Write results to a JSON file along with execution statistics
The API lives under ixa::profiling and is behind the profiling Cargo feature
(enabled by default). If you disable the feature, the API becomes a no-op so you
can leave profiling calls in your code.
Example console output
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%
Basic usage
Count an event:
use ixa::profiling::increment_named_count;
increment_named_count("forecasted infection");
increment_named_count("accepted infection attempt");
Time an operation:
use ixa::profiling::{close_span, open_span};
let span = open_span("forecast loop");
// operation code here (algorithm, function call, etc.)
close_span(span); // optional; dropping the span also closes it
Spans also auto-close at end of scope (RAII), which is useful for early returns:
use ixa::profiling::open_span;
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 (after the simulation completes):
use ixa::profiling::print_profiling_data;
print_profiling_data();
This prints spans, counts, and any computed statistics. You can also call
print_named_spans(), print_named_counts(), and print_computed_statistics()
individually.
Minimal example
use ixa::prelude::*;
use ixa::profiling::*;
fn main() {
let mut context = Context::new();
context.add_plan(0.0, |context| {
increment_named_count("my_model:event");
{
let _span = open_span("my_model:expensive_step");
// ... do work ...
} // span auto-closes on drop
context.shutdown();
});
context.execute();
// Console output (spans, counts, computed statistics).
print_profiling_data();
// Writes JSON to: <output_dir>/<file_prefix>profiling.json
// using the same report options configuration as CSV reports.
context.write_profiling_data();
}
See examples/profiling in the repository for a more complete example,
including configuring report_options() to control the output directory, file
prefix, and overwrite behavior.
Writing JSON output
ProfilingContextExt::write_profiling_data() writes a pretty JSON file to:
<output_dir>/<file_prefix>profiling.json
using the same report_options() configuration as CSV reports (directory, file
prefix, overwrite). The JSON includes:
date_timeexecution_statisticsnamed_countsnamed_spanscomputed_statistics
Example:
use std::path::PathBuf;
use ixa::prelude::*;
use ixa::profiling::ProfilingContextExt;
fn main() {
let mut context = Context::new();
context
.report_options()
.directory(PathBuf::from("./output"))
.file_prefix("run_")
.overwrite(true);
// ... run the simulation ...
context.execute();
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 tracks how much runtime is covered by
some span.
Computed statistics
You can register custom, derived metrics over collected ProfilingData using
add_computed_statistic(label, description, computer, printer). The "computer"
returns an Option (for conditionally defined statistics), and the "printer"
prints the computed value.
Computed statistics are printed by print_computed_statistics() and included in
the JSON under computed_statistics (label, description, value).
The supported computed value types are usize, i64, and f64.
API (simplified):
pub type CustomStatisticComputer<T> = Box<dyn (Fn(&ProfilingData) -> Option<T>) + Send + Sync>;
pub type CustomStatisticPrinter<T> = Box<dyn Fn(T) + Send + Sync>;
pub fn add_computed_statistic<T: ComputableType>(
label: &'static str,
description: &'static str,
computer: CustomStatisticComputer<T>,
printer: CustomStatisticPrinter<T>,
);
Example:
use ixa::profiling::{add_computed_statistic, increment_named_count};
increment_named_count("my_model:event");
increment_named_count("my_model:event");
add_computed_statistic::<usize>(
"my_model:event_count",
"Total example events",
Box::new(|data| data.counts.get("my_model:event").copied()),
Box::new(|value| println!("Computed my_model:event_count = {value}")),
);