Skip to content

Commit 30cbca9

Browse files
authored
Merge pull request #844 from hatoo/first_byte
Add first byte stats to JSON output
2 parents 8546b62 + 036082e commit 30cbca9

File tree

3 files changed

+120
-2
lines changed

3 files changed

+120
-2
lines changed

schema.json

Lines changed: 76 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,57 @@
107107
"p99.99"
108108
]
109109
},
110+
"firstByteHistogram": {
111+
"description": "The histogram of first byte time in seconds. The key is the first byte time in seconds and the value is the number of requests",
112+
"type": "object",
113+
"additionalProperties": {
114+
"type": "integer"
115+
}
116+
},
117+
"firstBytePercentiles": {
118+
"description": "The first byte percentiles in seconds",
119+
"type": "object",
120+
"properties": {
121+
"p10": {
122+
"type": "number"
123+
},
124+
"p25": {
125+
"type": "number"
126+
},
127+
"p50": {
128+
"type": "number"
129+
},
130+
"p75": {
131+
"type": "number"
132+
},
133+
"p90": {
134+
"type": "number"
135+
},
136+
"p95": {
137+
"type": "number"
138+
},
139+
"p99": {
140+
"type": "number"
141+
},
142+
"p99.9": {
143+
"type": "number"
144+
},
145+
"p99.99": {
146+
"type": "number"
147+
}
148+
},
149+
"required": [
150+
"p10",
151+
"p25",
152+
"p50",
153+
"p75",
154+
"p90",
155+
"p95",
156+
"p99",
157+
"p99.9",
158+
"p99.99"
159+
]
160+
},
110161
"responseTimeHistogramSuccessful": {
111162
"description": "Only present if `--stats-success-breakdown` argument is passed. The histogram of response time in seconds for successful requests. The key is the response time in seconds and the value is the number of requests",
112163
"type": "object",
@@ -323,11 +374,32 @@
323374
"fastest",
324375
"slowest"
325376
]
377+
},
378+
"firstByte": {
379+
"description": "The time to first byte in seconds",
380+
"type": "object",
381+
"properties": {
382+
"average": {
383+
"type": "number"
384+
},
385+
"fastest": {
386+
"type": "number"
387+
},
388+
"slowest": {
389+
"type": "number"
390+
}
391+
},
392+
"required": [
393+
"average",
394+
"fastest",
395+
"slowest"
396+
]
326397
}
327398
},
328399
"required": [
329400
"DNSDialup",
330-
"DNSLookup"
401+
"DNSLookup",
402+
"firstByte"
331403
]
332404
},
333405
"statusCodeDistribution": {
@@ -354,9 +426,11 @@
354426
"summary",
355427
"responseTimeHistogram",
356428
"latencyPercentiles",
429+
"firstByteHistogram",
430+
"firstBytePercentiles",
357431
"rps",
358432
"details",
359433
"statusCodeDistribution",
360434
"errorDistribution"
361435
]
362-
}
436+
}

src/printer.rs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,8 @@ fn print_json<W: Write>(
180180
dns_dialup: Triple,
181181
#[serde(rename = "DNSLookup")]
182182
dns_lookup: Triple,
183+
#[serde(rename = "firstByte")]
184+
first_byte: Triple,
183185
}
184186

185187
#[derive(Serialize)]
@@ -198,6 +200,10 @@ fn print_json<W: Write>(
198200
response_time_histogram: BTreeMap<String, usize>,
199201
#[serde(rename = "latencyPercentiles")]
200202
latency_percentiles: BTreeMap<String, f64>,
203+
#[serde(rename = "firstByteHistogram")]
204+
first_byte_histogram: BTreeMap<String, usize>,
205+
#[serde(rename = "firstBytePercentiles")]
206+
first_byte_percentiles: BTreeMap<String, f64>,
201207
#[serde(
202208
rename = "responseTimeHistogramSuccessful",
203209
skip_serializing_if = "Option::is_none"
@@ -255,6 +261,20 @@ fn print_json<W: Write>(
255261
.map(|(p, v)| (format!("p{p}"), v))
256262
.collect();
257263

264+
let first_byte_statistics = res.first_byte_all_statistics();
265+
266+
let first_byte_histogram = first_byte_statistics
267+
.histogram
268+
.into_iter()
269+
.map(|(k, v)| (k.to_string(), v))
270+
.collect();
271+
272+
let first_byte_percentiles = first_byte_statistics
273+
.percentiles
274+
.into_iter()
275+
.map(|(p, v)| (format!("p{p}"), v))
276+
.collect();
277+
258278
let mut response_time_histogram_successful: Option<BTreeMap<String, usize>> = None;
259279
let mut latency_percentiles_successful: Option<BTreeMap<String, f64>> = None;
260280
let mut response_time_histogram_not_successful: Option<BTreeMap<String, usize>> = None;
@@ -345,6 +365,7 @@ fn print_json<W: Write>(
345365

346366
let dns_dialup_stat = res.dns_dialup_stat();
347367
let dns_lookup_stat = res.dns_lookup_stat();
368+
let first_byte_stat = res.first_byte_stat();
348369

349370
let details = Details {
350371
dns_dialup: Triple {
@@ -357,6 +378,11 @@ fn print_json<W: Write>(
357378
fastest: dns_lookup_stat.min(),
358379
slowest: dns_lookup_stat.max(),
359380
},
381+
first_byte: Triple {
382+
average: first_byte_stat.mean(),
383+
fastest: first_byte_stat.min(),
384+
slowest: first_byte_stat.max(),
385+
},
360386
};
361387

362388
serde_json::to_writer_pretty(
@@ -365,6 +391,8 @@ fn print_json<W: Write>(
365391
summary,
366392
response_time_histogram,
367393
latency_percentiles,
394+
first_byte_histogram,
395+
first_byte_percentiles,
368396
response_time_histogram_successful,
369397
latency_percentiles_successful,
370398
response_time_histogram_not_successful,

src/result_data.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,13 @@ impl ResultData {
134134
.collect()
135135
}
136136

137+
pub fn first_byte_stat(&self) -> MinMaxMean {
138+
self.success
139+
.iter()
140+
.filter_map(|r| r.first_byte.map(|fb| (fb - r.start).as_secs_f64()))
141+
.collect()
142+
}
143+
137144
pub fn total_data(&self) -> usize {
138145
self.success.iter().map(|r| r.len_bytes).sum()
139146
}
@@ -155,6 +162,15 @@ impl ResultData {
155162
Statistics::new(&mut data)
156163
}
157164

165+
pub fn first_byte_all_statistics(&self) -> Statistics {
166+
let mut data = self
167+
.success
168+
.iter()
169+
.filter_map(|r| r.first_byte.map(|fb| (fb - r.start).as_secs_f64()))
170+
.collect::<Vec<_>>();
171+
Statistics::new(&mut data)
172+
}
173+
158174
pub fn duration_successful_statistics(&self) -> Statistics {
159175
let mut data = self
160176
.success

0 commit comments

Comments
 (0)