///|
fn sorted_values(values : Array[Double]) -> Array[Double] {
let result = values.copy()
result.sort_by(Double::compare)
result
}
///|
pub fn quantile(values : Array[Double], fraction : Double) -> Double {
if values.is_empty() {
return 0.0
}
let sorted = sorted_values(values)
let position = Double::min(Double::max(fraction, 0.0), 1.0) *
Double::from_int(sorted.length() - 1)
let lower = position.to_int()
let upper = Int::min(lower + 1, sorted.length() - 1)
let weight = position - Double::from_int(lower)
sorted[lower] * (1.0 - weight) + sorted[upper] * weight
}
///|
pub fn summarize_values(values : Array[Double]) -> MetricDistribution {
if values.is_empty() {
return {
count: 0,
mean: 0.0,
min: 0.0,
max: 0.0,
median: 0.0,
p25: 0.0,
p75: 0.0,
stddev: 0.0,
}
}
let mut total = 0.0
let mut minimum = values[0]
let mut maximum = values[0]
for value in values {
total += value
minimum = Double::min(minimum, value)
maximum = Double::max(maximum, value)
}
let mean = total / Double::from_int(values.length())
let mut squared = 0.0
for value in values {
let delta = value - mean
squared += delta * delta
}
{
count: values.length(),
mean,
min: minimum,
max: maximum,
median: quantile(values, 0.5),
p25: quantile(values, 0.25),
p75: quantile(values, 0.75),
stddev: Double::sqrt(squared / Double::from_int(values.length())),
}
}
///|
pub fn summarize_report_metric(
report : BenchmarkReport,
name : String,
) -> MetricDistribution {
let values : Array[Double] = []
for query in report.queries {
match query.metrics.get(name) {
Some(value) => values.push(value)
None => ()
}
}
summarize_values(values)
}
///|
pub fn trimmed_mean(values : Array[Double], trim_fraction : Double) -> Double {
if values.is_empty() {
return 0.0
}
let sorted = sorted_values(values)
let trim = Int::max(
0,
Int::min(
sorted.length() / 2,
(Double::from_int(sorted.length()) * Double::max(trim_fraction, 0.0)).to_int(),
),
)
let start = trim
let end = sorted.length() - trim
if start >= end {
return 0.0
}
let mut total = 0.0
for index in start.. Double {
let limit = Int::min(values.length(), weights.length())
let mut total = 0.0
let mut weight_total = 0.0
for index in 0.. Double {
summarize_report_metric(report, metric_name(prefix, cutoff)).mean
}
///|
pub fn metric_wins(
baseline : BenchmarkReport,
candidate : BenchmarkReport,
name : String,
) -> (Int, Int, Int) {
let base : Map[String, Double] = Map([])
let next : Map[String, Double] = Map([])
for query in baseline.queries {
match query.metrics.get(name) {
Some(value) => base[query.query_id] = value
None => ()
}
}
for query in candidate.queries {
match query.metrics.get(name) {
Some(value) => next[query.query_id] = value
None => ()
}
}
let mut wins = 0
let mut losses = 0
let mut ties = 0
let ids : Map[String, Unit] = Map([])
for id, _ in base {
ids[id] = ()
}
for id, _ in next {
ids[id] = ()
}
for id, _ in ids {
let left = base.get_or_default(id, 0.0)
let right = next.get_or_default(id, 0.0)
if right > left + 0.000000001 {
wins += 1
} else if right < left - 0.000000001 {
losses += 1
} else {
ties += 1
}
}
(wins, losses, ties)
}