pub(crate) const BOUNDARIES_MS: [u64; 8] = [50, 100, 250, 500, 1000, 2500, 5000, 10_000];
pub(crate) const BUCKET_COUNT: usize = BOUNDARIES_MS.len() + 1;
pub(crate) fn bucket_index(duration_ms: f64) -> usize {
BOUNDARIES_MS
.iter()
.position(|&boundary| duration_ms <= boundary as f64)
.unwrap_or(BOUNDARIES_MS.len())
}
pub(crate) fn bucket_label(index: usize) -> String {
BOUNDARIES_MS
.get(index)
.map(|boundary| boundary.to_string())
.unwrap_or_else(|| "inf".to_string())
}
pub(crate) fn histogram_json(counts: &[u64; BUCKET_COUNT]) -> String {
let fields: Vec<String> = counts
.iter()
.enumerate()
.filter(|(_, &count)| count > 0)
.map(|(index, count)| format!("\"{}\":{}", bucket_label(index), count))
.collect();
format!("{{{}}}", fields.join(","))
}
#[cfg(test)]
mod tests {
use super::*;
fn bucket_for(duration_ms: f64) -> String {
bucket_label(bucket_index(duration_ms))
}
#[test]
fn returns_the_smallest_boundary_a_duration_fits_under_as_a_string() {
assert_eq!(bucket_for(10.0), "50");
assert_eq!(bucket_for(50.0), "50");
assert_eq!(bucket_for(50.5), "100");
assert_eq!(bucket_for(4999.0), "5000");
}
#[test]
fn returns_inf_for_anything_larger_than_the_largest_boundary() {
assert_eq!(bucket_for(10_001.0), "inf");
assert_eq!(bucket_for(1_000_000.0), "inf");
}
#[test]
fn puts_a_duration_exactly_on_a_boundary_into_that_boundarys_own_bucket() {
for boundary in BOUNDARIES_MS {
assert_eq!(bucket_for(boundary as f64), boundary.to_string());
}
}
#[test]
fn boundaries_match_the_servers_histogram_percentile() {
assert_eq!(BOUNDARIES_MS, [50, 100, 250, 500, 1000, 2500, 5000, 10_000]);
}
#[test]
fn json_lists_only_the_buckets_that_were_hit_in_ascending_order() {
let mut counts = [0u64; BUCKET_COUNT];
counts[bucket_index(700.0)] += 1;
counts[bucket_index(10.0)] += 2;
counts[bucket_index(12_000.0)] += 1;
assert_eq!(histogram_json(&counts), "{\"50\":2,\"1000\":1,\"inf\":1}");
}
}