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}