use crate::{Bitmap, IndexError, Microseconds, Result, Seconds};
use journal_common::compat::is_multiple_of;
use serde::{Deserialize, Serialize};
use std::num::NonZeroU32;
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
#[cfg_attr(feature = "allocative", derive(allocative::Allocative))]
pub struct Bucket {
pub start_time: Seconds,
pub count: u32,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "allocative", derive(allocative::Allocative))]
pub struct Histogram {
pub bucket_duration: NonZeroU32,
pub buckets: Vec<Bucket>,
}
impl Histogram {
pub fn from_timestamp_offset_pairs(
bucket_duration: Seconds,
timestamp_offset_pairs: &[(Microseconds, std::num::NonZeroU64)],
) -> Result<Histogram> {
if bucket_duration.0 == 0 {
return Err(IndexError::ZeroBucketDuration);
}
if timestamp_offset_pairs.is_empty() {
return Err(IndexError::EmptyHistogramInput);
}
debug_assert!(timestamp_offset_pairs.is_sorted());
let mut buckets = Vec::new();
let mut current_bucket = None;
for (offset_index, &(timestamp, _offset)) in timestamp_offset_pairs.iter().enumerate() {
let bucket =
Seconds((timestamp.to_seconds().0 / bucket_duration.0) * bucket_duration.0);
match current_bucket {
None => {
debug_assert_eq!(offset_index, 0);
current_bucket = Some(bucket);
}
Some(prev_bucket) if bucket.0 > prev_bucket.0 => {
buckets.push(Bucket {
start_time: prev_bucket,
count: offset_index as u32 - 1,
});
current_bucket = Some(bucket);
}
_ => {} }
}
if let Some(last_bucket) = current_bucket {
buckets.push(Bucket {
start_time: last_bucket,
count: timestamp_offset_pairs.len() as u32 - 1,
});
}
let bucket_duration = NonZeroU32::new(bucket_duration.0).expect("non-zero bucket duration");
Ok(Histogram {
bucket_duration,
buckets,
})
}
pub fn start_time(&self) -> Seconds {
let first_bucket = self.buckets.first().expect("histogram to have buckets");
first_bucket.start_time
}
pub fn end_time(&self) -> Seconds {
let last_bucket = self.buckets.last().expect("histogram to have buckets");
Seconds(last_bucket.start_time.0 + self.bucket_duration.get())
}
pub fn time_range(&self) -> (Seconds, Seconds) {
(self.start_time(), self.end_time())
}
pub fn num_buckets(&self) -> usize {
self.buckets.len()
}
pub fn total_entries(&self) -> usize {
let last_bucket = self.buckets.last().expect("histogram to have buckets");
last_bucket.count as usize + 1
}
pub fn is_empty(&self) -> bool {
self.buckets.is_empty()
}
pub fn count_entries_in_time_range(
&self,
bitmap: &Bitmap,
start_time: Seconds,
end_time: Seconds,
) -> Option<usize> {
if start_time >= end_time {
return None;
}
if !is_multiple_of(start_time.0, self.bucket_duration.get())
|| !is_multiple_of(end_time.0, self.bucket_duration.get())
{
return None;
}
if self.buckets.is_empty() || bitmap.is_empty() {
return Some(0);
}
let start_bucket_idx = self.buckets.partition_point(|b| b.start_time < start_time);
if start_bucket_idx >= self.buckets.len() {
return Some(0);
}
let end_bucket_idx = self
.buckets
.partition_point(|b| b.start_time < end_time)
.saturating_sub(1);
if start_bucket_idx > end_bucket_idx {
return Some(0);
}
let start_running_count = if start_bucket_idx == 0 {
0
} else {
self.buckets[start_bucket_idx - 1].count + 1
};
let end_running_count = self.buckets[end_bucket_idx].count;
let count = bitmap.range_cardinality(start_running_count..(end_running_count + 1));
Some(count as usize)
}
#[deprecated(since = "0.1.0", note = "Use count_entries_in_time_range() instead")]
pub fn count_bitmap_entries_in_range(
&self,
bitmap: &Bitmap,
start_time: Seconds,
end_time: Seconds,
) -> Option<usize> {
self.count_entries_in_time_range(bitmap, start_time, end_time)
}
}
#[cfg(test)]
mod tests {
use super::*;
fn create_test_histogram() -> Histogram {
let pairs: Vec<(Microseconds, std::num::NonZeroU64)> = (0..20)
.map(|i| {
let bucket_index = i / 5;
let offset_in_bucket = i % 5;
let timestamp_secs = bucket_index * 60 + offset_in_bucket * 10;
(
Microseconds(timestamp_secs * 1_000_000),
std::num::NonZeroU64::new(i as u64 + 1).unwrap(),
)
})
.collect();
Histogram::from_timestamp_offset_pairs(Seconds(60), &pairs).unwrap()
}
#[test]
fn test_from_timestamp_offset_pairs_single_entry() {
let pairs = vec![(
Seconds(1).to_microseconds(),
std::num::NonZeroU64::new(1).unwrap(),
)];
let histogram = Histogram::from_timestamp_offset_pairs(Seconds(60), &pairs).unwrap();
assert_eq!(histogram.bucket_duration.get(), 60);
assert_eq!(histogram.num_buckets(), 1);
assert_eq!(histogram.buckets[0].start_time, Seconds(0));
assert_eq!(histogram.buckets[0].count, 0);
assert_eq!(histogram.total_entries(), 1);
}
#[test]
fn test_from_timestamp_offset_pairs_all_in_one_bucket() {
let pairs: Vec<_> = (0..5)
.map(|i| {
(
Seconds(i * 10).to_microseconds(),
std::num::NonZeroU64::new(i as u64 + 1).unwrap(),
)
})
.collect();
let histogram = Histogram::from_timestamp_offset_pairs(Seconds(60), &pairs).unwrap();
assert_eq!(histogram.num_buckets(), 1);
assert_eq!(histogram.buckets[0].start_time, Seconds(0));
assert_eq!(histogram.buckets[0].count, 4);
assert_eq!(histogram.total_entries(), 5);
}
#[test]
fn test_from_timestamp_offset_pairs_exact_boundaries() {
let pairs = vec![
(Microseconds(0), std::num::NonZeroU64::new(1).unwrap()),
(
Seconds(60).to_microseconds(),
std::num::NonZeroU64::new(2).unwrap(),
),
(
Seconds(120).to_microseconds(),
std::num::NonZeroU64::new(3).unwrap(),
),
(
Seconds(180).to_microseconds(),
std::num::NonZeroU64::new(4).unwrap(),
),
];
let histogram = Histogram::from_timestamp_offset_pairs(Seconds(60), &pairs).unwrap();
assert_eq!(histogram.num_buckets(), 4);
assert_eq!(histogram.buckets[0].start_time, Seconds(0));
assert_eq!(histogram.buckets[0].count, 0);
assert_eq!(histogram.buckets[1].start_time, Seconds(60));
assert_eq!(histogram.buckets[1].count, 1);
assert_eq!(histogram.buckets[2].start_time, Seconds(120));
assert_eq!(histogram.buckets[2].count, 2);
assert_eq!(histogram.buckets[3].start_time, Seconds(180));
assert_eq!(histogram.buckets[3].count, 3);
assert_eq!(histogram.total_entries(), 4);
}
#[test]
fn test_from_timestamp_offset_pairs_multiple_buckets() {
let pairs = vec![
(
Seconds(10).to_microseconds(),
std::num::NonZeroU64::new(1).unwrap(),
),
(
Seconds(20).to_microseconds(),
std::num::NonZeroU64::new(2).unwrap(),
),
(
Seconds(70).to_microseconds(),
std::num::NonZeroU64::new(3).unwrap(),
),
(
Seconds(80).to_microseconds(),
std::num::NonZeroU64::new(4).unwrap(),
),
(
Seconds(90).to_microseconds(),
std::num::NonZeroU64::new(5).unwrap(),
),
];
let histogram = Histogram::from_timestamp_offset_pairs(Seconds(60), &pairs).unwrap();
assert_eq!(histogram.num_buckets(), 2);
assert_eq!(histogram.buckets[0].start_time, Seconds(0));
assert_eq!(histogram.buckets[0].count, 1); assert_eq!(histogram.buckets[1].start_time, Seconds(60));
assert_eq!(histogram.buckets[1].count, 4); assert_eq!(histogram.total_entries(), 5);
}
#[test]
fn test_from_timestamp_offset_pairs_sparse_buckets() {
let pairs = vec![
(Microseconds(0), std::num::NonZeroU64::new(1).unwrap()),
(
Seconds(180).to_microseconds(),
std::num::NonZeroU64::new(2).unwrap(),
), ];
let histogram = Histogram::from_timestamp_offset_pairs(Seconds(60), &pairs).unwrap();
assert_eq!(histogram.num_buckets(), 2);
assert_eq!(histogram.buckets[0].start_time, Seconds(0));
assert_eq!(histogram.buckets[0].count, 0);
assert_eq!(histogram.buckets[1].start_time, Seconds(180));
assert_eq!(histogram.buckets[1].count, 1);
assert_eq!(histogram.total_entries(), 2);
}
#[test]
fn test_from_timestamp_offset_pairs_large_bucket_duration() {
let pairs = vec![
(Microseconds(0), std::num::NonZeroU64::new(1).unwrap()),
(
Seconds(500).to_microseconds(),
std::num::NonZeroU64::new(2).unwrap(),
),
(
Seconds(1000).to_microseconds(),
std::num::NonZeroU64::new(3).unwrap(),
),
];
let histogram = Histogram::from_timestamp_offset_pairs(Seconds(600), &pairs).unwrap();
assert_eq!(histogram.bucket_duration.get(), 600);
assert_eq!(histogram.num_buckets(), 2);
assert_eq!(histogram.buckets[0].start_time, Seconds(0));
assert_eq!(histogram.buckets[0].count, 1); assert_eq!(histogram.buckets[1].start_time, Seconds(600));
assert_eq!(histogram.buckets[1].count, 2); assert_eq!(histogram.total_entries(), 3);
}
#[test]
fn test_from_timestamp_offset_pairs_zero_bucket_duration() {
let pairs = vec![(Microseconds(0), std::num::NonZeroU64::new(1).unwrap())];
let result = Histogram::from_timestamp_offset_pairs(Seconds(0), &pairs);
assert!(matches!(result, Err(IndexError::ZeroBucketDuration)));
}
#[test]
fn test_from_empty_timestamp_offset_pairs() {
let pairs = Vec::new();
let result = Histogram::from_timestamp_offset_pairs(Seconds(1), &pairs);
assert!(matches!(result, Err(IndexError::EmptyHistogramInput)));
}
#[test]
fn test_count_entries_in_time_range_full_bucket() {
let histogram = create_test_histogram();
let bitmap = Bitmap::from_sorted_iter([5, 6, 7, 8, 9]).unwrap();
let count = histogram.count_entries_in_time_range(&bitmap, Seconds(60), Seconds(120));
assert_eq!(count, Some(5));
}
#[test]
fn test_count_entries_in_time_range_partial_match() {
let histogram = create_test_histogram();
let bitmap = Bitmap::from_sorted_iter([7, 8, 9, 10, 11]).unwrap();
let count = histogram.count_entries_in_time_range(&bitmap, Seconds(60), Seconds(120));
assert_eq!(count, Some(3));
}
#[test]
fn test_count_entries_in_time_range_multiple_buckets() {
let histogram = create_test_histogram();
let bitmap = Bitmap::from_sorted_iter([5, 6, 10, 11, 15, 16]).unwrap();
let count = histogram.count_entries_in_time_range(&bitmap, Seconds(60), Seconds(180));
assert_eq!(count, Some(4)); }
#[test]
fn test_count_entries_in_time_range_no_matches() {
let histogram = create_test_histogram();
let bitmap = Bitmap::from_sorted_iter([0, 1, 2]).unwrap();
let count = histogram.count_entries_in_time_range(&bitmap, Seconds(120), Seconds(180));
assert_eq!(count, Some(0));
}
#[test]
fn test_count_entries_in_time_range_empty_bitmap() {
let histogram = create_test_histogram();
let bitmap = Bitmap::new();
let count = histogram.count_entries_in_time_range(&bitmap, Seconds(0), Seconds(60));
assert_eq!(count, Some(0));
}
#[test]
fn test_count_entries_in_time_range_unaligned_start() {
let histogram = create_test_histogram();
let bitmap = Bitmap::from_sorted_iter([5, 6, 7]).unwrap();
let count = histogram.count_entries_in_time_range(&bitmap, Seconds(30), Seconds(120));
assert_eq!(count, None);
}
#[test]
fn test_count_entries_in_time_range_unaligned_end() {
let histogram = create_test_histogram();
let bitmap = Bitmap::from_sorted_iter([5, 6, 7]).unwrap();
let count = histogram.count_entries_in_time_range(&bitmap, Seconds(60), Seconds(100));
assert_eq!(count, None);
}
#[test]
fn test_count_entries_in_time_range_invalid_range() {
let histogram = create_test_histogram();
let bitmap = Bitmap::from_sorted_iter([5, 6, 7]).unwrap();
let count = histogram.count_entries_in_time_range(&bitmap, Seconds(120), Seconds(60));
assert_eq!(count, None);
let count = histogram.count_entries_in_time_range(&bitmap, Seconds(60), Seconds(60));
assert_eq!(count, None);
}
#[test]
fn test_count_entries_in_time_range_outside_histogram() {
let histogram = create_test_histogram();
let bitmap = Bitmap::from_sorted_iter([5, 6, 7]).unwrap();
let count = histogram.count_entries_in_time_range(&bitmap, Seconds(0), Seconds(60));
assert!(count.is_some());
let count = histogram.count_entries_in_time_range(&bitmap, Seconds(240), Seconds(300));
assert_eq!(count, Some(0));
}
#[test]
fn test_count_entries_in_time_range_first_bucket() {
let histogram = create_test_histogram();
let bitmap = Bitmap::from_sorted_iter([0, 1, 2, 3, 4]).unwrap();
let count = histogram.count_entries_in_time_range(&bitmap, Seconds(0), Seconds(60));
assert_eq!(count, Some(5));
}
#[test]
fn test_count_entries_in_time_range_last_bucket() {
let histogram = create_test_histogram();
let bitmap = Bitmap::from_sorted_iter([15, 16, 17, 18, 19]).unwrap();
let count = histogram.count_entries_in_time_range(&bitmap, Seconds(180), Seconds(240));
assert_eq!(count, Some(5));
}
#[test]
fn test_count_entries_in_time_range_all_buckets() {
let histogram = create_test_histogram();
let bitmap = Bitmap::from_sorted_iter([0, 5, 10, 15]).unwrap();
let count = histogram.count_entries_in_time_range(&bitmap, Seconds(0), Seconds(240));
assert_eq!(count, Some(4));
}
#[test]
fn test_histogram_properties() {
let histogram = create_test_histogram();
assert_eq!(histogram.start_time(), Seconds(0));
assert_eq!(histogram.end_time(), Seconds(240));
assert_eq!(histogram.time_range(), (Seconds(0), Seconds(240)));
assert_eq!(histogram.num_buckets(), 4);
assert!(!histogram.is_empty());
assert_eq!(histogram.total_entries(), 20);
}
#[test]
fn test_bitmap_with_indices_beyond_histogram_range() {
let histogram = create_test_histogram();
let bitmap = Bitmap::from_sorted_iter([5, 6, 7, 25, 30, 100]).unwrap();
let count = histogram.count_entries_in_time_range(&bitmap, Seconds(60), Seconds(120));
assert_eq!(count, Some(3));
}
#[test]
fn test_bitmap_all_indices_outside_queried_range() {
let histogram = create_test_histogram();
let bitmap = Bitmap::from_sorted_iter([0, 1, 2, 3, 4]).unwrap();
let count = histogram.count_entries_in_time_range(&bitmap, Seconds(120), Seconds(180));
assert_eq!(count, Some(0));
}
#[test]
fn test_bitmap_with_sparse_scattered_indices() {
let histogram = create_test_histogram();
let bitmap = Bitmap::from_sorted_iter([1, 7, 11, 18]).unwrap();
let count = histogram.count_entries_in_time_range(&bitmap, Seconds(60), Seconds(180));
assert_eq!(count, Some(2));
}
#[test]
fn test_bitmap_at_range_boundaries() {
let histogram = create_test_histogram();
let bitmap = Bitmap::from_sorted_iter([4, 5, 9, 10]).unwrap();
let count = histogram.count_entries_in_time_range(&bitmap, Seconds(60), Seconds(120));
assert_eq!(count, Some(2));
}
#[test]
fn test_bitmap_with_only_out_of_range_indices() {
let histogram = create_test_histogram();
let bitmap = Bitmap::from_sorted_iter([25, 30, 50, 100]).unwrap();
let count = histogram.count_entries_in_time_range(&bitmap, Seconds(0), Seconds(60));
assert_eq!(count, Some(0));
}
#[test]
fn test_bitmap_single_index_in_range() {
let histogram = create_test_histogram();
let bitmap = Bitmap::from_sorted_iter([0, 1, 2, 7, 15, 16, 17]).unwrap();
let count = histogram.count_entries_in_time_range(&bitmap, Seconds(60), Seconds(120));
assert_eq!(count, Some(1));
}
}