ixa 2.0.0-beta2

A framework for building agent-based models
Documentation
# 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

```text
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:

```rust
use ixa::profiling::increment_named_count;

increment_named_count("forecasted infection");
increment_named_count("accepted infection attempt");
```

Time an operation:

```rust
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:

```rust
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):

```rust
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

```rust
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_time`
- `execution_statistics`
- `named_counts`
- `named_spans`
- `computed_statistics`

Example:

```rust
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\<T>` (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):

```rust,ignore
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:

```rust
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}")),
);
```