const SUB_BITS: u32 = 6;
const SUB_BUCKETS: u64 = 1 << SUB_BITS;
const EXACT: u64 = SUB_BUCKETS << 1;
fn width_bits(us: u64) -> u32 {
if us < EXACT {
0
} else {
63 - us.leading_zeros() - SUB_BITS
}
}
pub(crate) fn bucket_floor(us: u64) -> u64 {
let bits = width_bits(us);
(us >> bits) << bits
}
pub(crate) fn bucket_end(us: u64) -> u64 {
bucket_floor(us).saturating_add(1 << width_bits(us))
}
pub(crate) fn bucket_value(us: u64) -> u64 {
bucket_floor(us) + ((1 << width_bits(us)) >> 1)
}
pub(crate) fn bucket_index(us: u64) -> usize {
let bits = width_bits(us);
if bits == 0 {
us as usize
} else {
(EXACT + (bits as u64 - 1) * SUB_BUCKETS + ((us >> bits) - SUB_BUCKETS)) as usize
}
}
pub(crate) fn bucket_at(index: usize) -> u64 {
let index = index as u64;
if index < EXACT {
index
} else {
let bits = (index - EXACT) / SUB_BUCKETS + 1;
((index - EXACT) % SUB_BUCKETS + SUB_BUCKETS) << bits
}
}
pub(crate) fn percentile(
buckets: impl IntoIterator<Item = (u64, usize)>,
total: usize,
minimum: u64,
maximum: u64,
percent: f32,
) -> u64 {
let rank = (total as f64 * super::percent_as_written(percent)).round() as usize;
match total {
0 => 0,
_ if rank >= total => maximum,
_ if rank <= 1 => minimum,
_ => super::response_time_percentile(buckets, total, minimum, maximum, percent),
}
}
const PLACED_FROM: usize = 32;
#[derive(Debug, Clone, Default)]
pub struct TimeBuckets {
list: Vec<(u64, usize)>,
places: Vec<u32>,
}
impl PartialEq for TimeBuckets {
fn eq(&self, other: &Self) -> bool {
self.list == other.list
}
}
impl Eq for TimeBuckets {}
impl TimeBuckets {
pub fn new() -> Self {
TimeBuckets::default()
}
pub(crate) fn add(&mut self, bucket: u64, count: usize) {
if !self.places.is_empty() {
return self.add_placed(bucket, count);
}
if let Some(last) = self.list.last_mut() {
if last.0 == bucket {
last.1 += count;
return;
}
if last.0 < bucket {
self.list.push((bucket, count));
return self.place_if_long();
}
}
match self.list.binary_search_by_key(&bucket, |&(key, _)| key) {
Ok(at) => self.list[at].1 += count,
Err(at) => {
self.list.insert(at, (bucket, count));
self.place_if_long();
}
}
}
fn add_placed(&mut self, bucket: u64, count: usize) {
let index = bucket_index(bucket);
if index >= self.places.len() {
self.places.resize(index + 1, 0);
}
match self.places[index] {
0 => {
let at = self
.list
.binary_search_by_key(&bucket, |&(key, _)| key)
.unwrap_err();
self.list.insert(at, (bucket, count));
self.place_from(at);
}
place => self.list[place as usize - 1].1 += count,
}
}
fn place_if_long(&mut self) {
if self.list.len() >= PLACED_FROM {
self.place_from(0);
}
}
fn place_from(&mut self, at: usize) {
if let Some(&(last, _)) = self.list.last() {
let size = bucket_index(last) + 1;
if self.places.len() < size {
self.places.resize(size, 0);
}
}
for (place, &(bucket, _)) in self.list.iter().enumerate().skip(at) {
self.places[bucket_index(bucket)] = place as u32 + 1;
}
}
pub(crate) fn push(&mut self, bucket: u64, count: usize) {
if self.list.capacity() == 0 {
self.list.reserve(32);
}
self.list.push((bucket, count));
}
pub(crate) fn merge(&mut self, other: &TimeBuckets, mut also: impl FnMut(u64, usize)) {
for &(bucket, count) in &other.list {
also(bucket, count);
self.add(bucket, count);
}
}
pub fn insert(&mut self, bucket: u64, count: usize) {
match self.list.binary_search_by_key(&bucket, |&(key, _)| key) {
Ok(at) => self.list[at].1 = count,
Err(_) => self.add(bucket, count),
}
}
pub fn get(&self, bucket: &u64) -> Option<&usize> {
self.list
.binary_search_by_key(bucket, |&(key, _)| key)
.ok()
.map(|at| &self.list[at].1)
}
pub fn len(&self) -> usize {
self.list.len()
}
pub fn is_empty(&self) -> bool {
self.list.is_empty()
}
pub fn iter(&self) -> impl Iterator<Item = (&u64, &usize)> + '_ {
self.list.iter().map(|(bucket, count)| (bucket, count))
}
}
impl<'a> IntoIterator for &'a TimeBuckets {
type Item = (&'a u64, &'a usize);
type IntoIter = std::iter::Map<
std::slice::Iter<'a, (u64, usize)>,
fn(&'a (u64, usize)) -> (&'a u64, &'a usize),
>;
fn into_iter(self) -> Self::IntoIter {
self.list.iter().map(|(bucket, count)| (bucket, count))
}
}
impl std::ops::Index<&u64> for TimeBuckets {
type Output = usize;
fn index(&self, bucket: &u64) -> &usize {
self.get(bucket).expect("no such bucket")
}
}
impl std::iter::FromIterator<(u64, usize)> for TimeBuckets {
fn from_iter<I: IntoIterator<Item = (u64, usize)>>(buckets: I) -> Self {
let mut times = TimeBuckets::new();
for (bucket, count) in buckets {
times.add(bucket, count);
}
times
}
}
impl serde::Serialize for TimeBuckets {
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
if self.list.windows(2).all(|pair| pair[0].0 < pair[1].0) {
serializer.collect_map(self.iter())
} else {
let combined: TimeBuckets = self.list.iter().copied().collect();
serializer.collect_map(combined.iter())
}
}
}
impl<'de> serde::Deserialize<'de> for TimeBuckets {
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
let map = std::collections::BTreeMap::<u64, usize>::deserialize(deserializer)?;
Ok(map.into_iter().collect())
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn exact_below_128_microseconds() {
for us in 0..EXACT {
assert_eq!(bucket_floor(us), us);
assert_eq!(bucket_value(us), us);
assert_eq!(bucket_index(us), us as usize);
assert_eq!(bucket_end(us), us + 1);
}
assert_eq!(bucket_floor(128), 128);
assert_eq!(bucket_end(128), 130);
assert_eq!(bucket_value(129), 129);
assert_eq!(bucket_index(128), 128);
assert_eq!(bucket_index(256), 192);
}
#[test]
fn bucket_at_inverts_bucket_index() {
for index in 0..2_000 {
let floor = bucket_at(index);
assert_eq!(bucket_index(floor), index);
assert_eq!(bucket_floor(floor), floor);
}
}
#[test]
fn time_buckets_add_and_merge_as_a_map() {
let mut rng: u64 = 7;
let mut next = move || {
rng = rng
.wrapping_mul(6364136223846793005)
.wrapping_add(1442695040888963407);
bucket_floor((rng >> 33) % 5_000_000)
};
let mut reference = std::collections::BTreeMap::new();
let mut merged = TimeBuckets::new();
for size in [0, 1, 3, 40, 400, 2_000, 5] {
let mut batch = TimeBuckets::new();
for _ in 0..size {
let bucket = next();
batch.add(bucket, 1);
*reference.entry(bucket).or_insert(0) += 1;
}
let mut seen = 0;
merged.merge(&batch, |_, count| seen += count);
assert_eq!(seen, size);
let flat: Vec<(u64, usize)> = merged.iter().map(|(&b, &c)| (b, c)).collect();
let expected: Vec<(u64, usize)> = reference.iter().map(|(&b, &c)| (b, c)).collect();
assert_eq!(flat, expected);
}
let mut pushed = TimeBuckets::new();
for &(bucket, count) in merged.list.iter().rev() {
pushed.push(bucket, count / 2);
pushed.push(bucket, count - count / 2);
}
let mut from_pushed = TimeBuckets::new();
from_pushed.merge(&pushed, |_, _| {});
assert_eq!(from_pushed, merged);
let json = serde_json::to_string(&merged).unwrap();
assert_eq!(serde_json::from_str::<TimeBuckets>(&json).unwrap(), merged);
assert_eq!(json, serde_json::to_string(&reference).unwrap());
}
#[test]
fn a_pushed_batch_serializes_each_bucket_once() {
let mut batch = TimeBuckets::new();
batch.push(2_048, 1);
batch.push(128, 2);
batch.push(2_048, 3);
let json = serde_json::to_string(&batch).unwrap();
assert_eq!(json, r#"{"128":2,"2048":4}"#);
let back: TimeBuckets = serde_json::from_str(&json).unwrap();
assert_eq!(back.get(&128), Some(&2));
assert_eq!(back.get(&2_048), Some(&4));
}
#[test]
fn bucket_count_to_the_default_timeout() {
assert_eq!(bucket_index(60_000_000) - bucket_index(1) + 1, 1_330);
assert_eq!(bucket_end(u64::MAX), u64::MAX);
}
}