Skip to main content

lmn_core/output/
report.rs

1use std::collections::BTreeMap;
2
3use serde::Serialize;
4
5use crate::threshold::ThresholdReport;
6
7// ── RunReport ─────────────────────────────────────────────────────────────────
8
9/// Versioned, serialization-ready report of a completed load test run.
10///
11/// This is the canonical contract between the Rust engine and all downstream
12/// consumers (CLI JSON output, NestJS SaaS platform, CI pipelines, cloud mode).
13/// The top-level `version` field allows consumers to gate on the schema version.
14#[derive(Serialize, Debug)]
15pub struct RunReport {
16    /// Schema version. Currently `2`. Increment on any breaking schema change.
17    pub version: u32,
18    pub run: RunMeta,
19    pub requests: RequestSummary,
20    pub latency: LatencyStats,
21    /// HTTP status code counts keyed by string code (e.g. `"200"`, `"404"`).
22    /// The special key `"error"` covers connection errors with no HTTP response.
23    pub status_codes: BTreeMap<String, u64>,
24    /// Present only when `--response-template` / `--response-alias` was used.
25    /// `null` when no response template was configured.
26    pub response_stats: Option<ResponseStatsReport>,
27    /// Present only when `mode == "curve"`. `null` in fixed mode.
28    pub curve_stages: Option<Vec<StageReport>>,
29    /// Present only when scenario/step-attributed request telemetry is available.
30    pub scenarios: Option<Vec<ScenarioReport>>,
31    /// Present when thresholds were evaluated after the run. `null` otherwise.
32    pub thresholds: Option<ThresholdReport>,
33}
34
35// ── RunMeta ───────────────────────────────────────────────────────────────────
36
37/// Top-level metadata about the run's execution mode and timings.
38#[derive(Serialize, Debug)]
39pub struct RunMeta {
40    /// Execution mode: `"fixed"` or `"curve"`.
41    pub mode: String,
42    /// Total wall-clock elapsed time for the run in milliseconds.
43    pub elapsed_ms: f64,
44    /// Total curve duration in milliseconds. `null` in fixed mode.
45    pub curve_duration_ms: Option<f64>,
46    /// Time spent pre-generating template bodies in milliseconds. `null` when no
47    /// request template was used.
48    pub template_generation_ms: Option<f64>,
49}
50
51// ── RequestSummary ────────────────────────────────────────────────────────────
52
53/// Aggregated request counts and derived throughput / error rate metrics.
54#[derive(Serialize, Debug)]
55pub struct RequestSummary {
56    pub total: usize,
57    pub ok: usize,
58    pub failed: usize,
59    /// Number of requests skipped (step not executed due to capture dependency
60    /// failure or iteration abort). `0` in non-scenario runs.
61    #[serde(skip_serializing_if = "is_zero_usize")]
62    pub skipped: usize,
63    /// Fraction of failed requests: `failed / total`. `0.0` when `total == 0`.
64    pub error_rate: f64,
65    /// Requests per second: `total / elapsed_seconds`. `0.0` when elapsed is zero.
66    pub throughput_rps: f64,
67}
68
69fn is_zero_usize(v: &usize) -> bool {
70    *v == 0
71}
72
73// ── LatencyStats ──────────────────────────────────────────────────────────────
74
75/// Snapshot of latency percentiles and summary statistics in milliseconds.
76///
77/// All values are `f64` milliseconds. Fields are named with the `_ms` suffix
78/// to make the unit self-documenting for downstream consumers.
79#[derive(Serialize, Debug)]
80pub struct LatencyStats {
81    pub min_ms: f64,
82    pub p10_ms: f64,
83    pub p25_ms: f64,
84    pub p50_ms: f64,
85    pub p75_ms: f64,
86    pub p90_ms: f64,
87    pub p95_ms: f64,
88    pub p99_ms: f64,
89    pub max_ms: f64,
90    pub avg_ms: f64,
91}
92
93// ── ResponseStatsReport ───────────────────────────────────────────────────────
94
95/// Summary of response body field analysis from a response template.
96///
97/// This is derived from `ResponseStats` in the response template domain.
98/// `HashMaps` are promoted to `BTreeMap`s for stable JSON key ordering.
99#[derive(Serialize, Debug)]
100pub struct ResponseStatsReport {
101    /// Number of responses that were parsed and contributed to field statistics.
102    pub responses_parsed: u64,
103    /// Distribution of string-valued field extractions. Outer key is the field path,
104    /// inner key is the extracted value, value is the count.
105    pub string_fields: BTreeMap<String, BTreeMap<String, u64>>,
106    /// Summary statistics for float-valued field extractions.
107    pub float_fields: BTreeMap<String, FloatFieldSummary>,
108    /// Count of responses where a tracked field could not be extracted.
109    pub mismatch_counts: BTreeMap<String, u64>,
110}
111
112/// Summary statistics for a float response field.
113#[derive(Serialize, Debug)]
114pub struct FloatFieldSummary {
115    pub min: f64,
116    pub avg: f64,
117    pub p50: f64,
118    pub p95: f64,
119    pub p99: f64,
120    pub max: f64,
121}
122
123// ── StageReport ───────────────────────────────────────────────────────────────
124
125/// Per-stage metrics for a curve-mode run. Stage indices are 0-based.
126#[derive(Serialize, Debug)]
127pub struct StageReport {
128    /// 0-based stage index in the load curve.
129    pub index: usize,
130    /// Configured stage duration in milliseconds.
131    pub duration_ms: f64,
132    /// Configured target VU count for this stage.
133    pub target_vus: u32,
134    /// Ramp type: `"linear"` or `"step"`.
135    pub ramp: String,
136    pub requests: usize,
137    pub ok: usize,
138    pub failed: usize,
139    /// Fraction of failed requests within this stage.
140    pub error_rate: f64,
141    /// Requests per second within this stage's duration window.
142    pub throughput_rps: f64,
143    pub latency: LatencyStats,
144}
145
146// ── ScenarioReport ────────────────────────────────────────────────────────────
147
148/// Per-scenario request metrics.
149#[derive(Serialize, Debug)]
150pub struct ScenarioReport {
151    pub name: String,
152    pub requests: RequestSummary,
153    pub latency: LatencyStats,
154    pub status_codes: BTreeMap<String, u64>,
155    pub steps: Vec<ScenarioStepReport>,
156}
157
158/// Per-step request metrics nested under one scenario.
159#[derive(Serialize, Debug)]
160pub struct ScenarioStepReport {
161    pub name: String,
162    pub requests: RequestSummary,
163    pub latency: LatencyStats,
164    pub status_codes: BTreeMap<String, u64>,
165}