///|
pub(all) struct NumericStats {
count : Int
min : Int
max : Int
total : Int
average : Int
} derive(Eq, Debug)
///|
pub fn entry_time_stats(entries : Array[HarEntry]) -> NumericStats {
let values : Array[Int] = []
for i = 0; i < entries.length(); i = i + 1 {
values.push(entries[i].time)
}
numeric_stats(values)
}
///|
pub fn entry_size_stats(entries : Array[HarEntry]) -> NumericStats {
let values : Array[Int] = []
for i = 0; i < entries.length(); i = i + 1 {
values.push(entry_bytes(entries[i]))
}
numeric_stats(values)
}
///|
pub fn wait_time_stats(entries : Array[HarEntry]) -> NumericStats {
let values : Array[Int] = []
for i = 0; i < entries.length(); i = i + 1 {
values.push(phase_value(entries[i].timings.wait))
}
numeric_stats(values)
}
///|
pub fn receive_time_stats(entries : Array[HarEntry]) -> NumericStats {
let values : Array[Int] = []
for i = 0; i < entries.length(); i = i + 1 {
values.push(phase_value(entries[i].timings.receive))
}
numeric_stats(values)
}
///|
pub fn dns_time_stats(entries : Array[HarEntry]) -> NumericStats {
let values : Array[Int] = []
for i = 0; i < entries.length(); i = i + 1 {
values.push(phase_value(entries[i].timings.dns))
}
numeric_stats(values)
}
///|
pub fn connect_time_stats(entries : Array[HarEntry]) -> NumericStats {
let values : Array[Int] = []
for i = 0; i < entries.length(); i = i + 1 {
values.push(phase_value(entries[i].timings.connect))
}
numeric_stats(values)
}
///|
pub fn ssl_time_stats(entries : Array[HarEntry]) -> NumericStats {
let values : Array[Int] = []
for i = 0; i < entries.length(); i = i + 1 {
values.push(phase_value(entries[i].timings.ssl))
}
numeric_stats(values)
}
///|
fn phase_value(value : Int) -> Int {
if value < 0 {
0
} else {
value
}
}
///|
pub fn numeric_stats(values : Array[Int]) -> NumericStats {
if values.is_empty() {
return NumericStats::{ count: 0, min: 0, max: 0, total: 0, average: 0 }
}
let mut min = values[0]
let mut max = values[0]
let mut total = 0
for i = 0; i < values.length(); i = i + 1 {
if values[i] < min {
min = values[i]
}
if values[i] > max {
max = values[i]
}
total = total + values[i]
}
NumericStats::{
count: values.length(),
min,
max,
total,
average: total / values.length(),
}
}
///|
pub fn percentile_nearest_rank(values : Array[Int], percentile : Int) -> Int {
if values.is_empty() {
return 0
}
let sorted = copy_ints(values)
sort_ints(sorted)
let p = if percentile < 0 {
0
} else if percentile > 100 {
100
} else {
percentile
}
let rank = (p * sorted.length() + 99) / 100
let index = if rank <= 0 { 0 } else { rank - 1 }
sorted[index]
}
///|
pub fn p50_time(entries : Array[HarEntry]) -> Int {
percentile_entry_time(entries, 50)
}
///|
pub fn p75_time(entries : Array[HarEntry]) -> Int {
percentile_entry_time(entries, 75)
}
///|
pub fn p90_time(entries : Array[HarEntry]) -> Int {
percentile_entry_time(entries, 90)
}
///|
pub fn p95_time(entries : Array[HarEntry]) -> Int {
percentile_entry_time(entries, 95)
}
///|
pub fn p99_time(entries : Array[HarEntry]) -> Int {
percentile_entry_time(entries, 99)
}
///|
fn percentile_entry_time(entries : Array[HarEntry], percentile : Int) -> Int {
let values : Array[Int] = []
for i = 0; i < entries.length(); i = i + 1 {
values.push(entries[i].time)
}
percentile_nearest_rank(values, percentile)
}
///|
fn copy_ints(values : Array[Int]) -> Array[Int] {
let out : Array[Int] = []
for i = 0; i < values.length(); i = i + 1 {
out.push(values[i])
}
out
}
///|
fn sort_ints(values : Array[Int]) -> Unit {
for i = 0; i < values.length(); i = i + 1 {
for j = i + 1; j < values.length(); j = j + 1 {
if values[j] < values[i] {
let tmp = values[i]
values[i] = values[j]
values[j] = tmp
}
}
}
}
///|
pub fn render_numeric_stats(name : String, stats : NumericStats) -> String {
name +
": count=" +
stats.count.to_string() +
" min=" +
stats.min.to_string() +
" max=" +
stats.max.to_string() +
" avg=" +
stats.average.to_string() +
" total=" +
stats.total.to_string()
}
///|
pub fn timing_breakdown(entry : HarEntry) -> String {
"blocked=" +
phase_value(entry.timings.blocked).to_string() +
" dns=" +
phase_value(entry.timings.dns).to_string() +
" connect=" +
phase_value(entry.timings.connect).to_string() +
" ssl=" +
phase_value(entry.timings.ssl).to_string() +
" send=" +
phase_value(entry.timings.send).to_string() +
" wait=" +
phase_value(entry.timings.wait).to_string() +
" receive=" +
phase_value(entry.timings.receive).to_string()
}
///|
pub fn request_overhead_bytes(entry : HarEntry) -> Int {
known_positive(entry.request.headers_size) +
known_positive(entry.request.body_size)
}
///|
pub fn response_overhead_bytes(entry : HarEntry) -> Int {
known_positive(entry.response.headers_size) +
known_positive(entry.response.body_size)
}
///|
pub fn payload_bytes(entry : HarEntry) -> Int {
known_positive(entry.response.content.size)
}
///|
fn known_positive(value : Int) -> Int {
if value > 0 {
value
} else {
0
}
}