use crate::EngineError;
use crate::histogram::calculate_bucket_duration;
use journal_index::Seconds;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct QueryTimeRange {
requested_start: u32,
requested_end: u32,
bucket_duration: u32,
aligned_start: u32,
aligned_end: u32,
}
impl QueryTimeRange {
pub fn new(start: u32, end: u32) -> Result<Self, EngineError> {
if start >= end {
return Err(EngineError::InvalidTimeRange { start, end });
}
let duration = end - start;
let bucket_duration = calculate_bucket_duration(duration);
let aligned_start = (start / bucket_duration) * bucket_duration;
let aligned_end = end.div_ceil(bucket_duration) * bucket_duration;
Ok(Self {
requested_start: start,
requested_end: end,
bucket_duration,
aligned_start,
aligned_end,
})
}
pub fn requested_start(&self) -> u32 {
self.requested_start
}
pub fn requested_end(&self) -> u32 {
self.requested_end
}
pub fn bucket_duration(&self) -> u32 {
self.bucket_duration
}
pub fn bucket_duration_seconds(&self) -> Seconds {
Seconds(self.bucket_duration)
}
pub fn aligned_start(&self) -> u32 {
self.aligned_start
}
pub fn aligned_end(&self) -> u32 {
self.aligned_end
}
pub fn aligned_duration(&self) -> u32 {
self.aligned_end - self.aligned_start
}
pub fn requested_duration(&self) -> u32 {
self.requested_end - self.requested_start
}
pub fn buckets(&self) -> impl Iterator<Item = (u32, u32)> + '_ {
let bucket_duration = self.bucket_duration;
let num_buckets = (self.aligned_end - self.aligned_start) / bucket_duration;
(0..num_buckets).map(move |i| {
let start = self.aligned_start + (i * bucket_duration);
let end = start + bucket_duration;
(start, end)
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_invalid_range() {
assert!(QueryTimeRange::new(100, 100).is_err());
assert!(QueryTimeRange::new(100, 50).is_err());
}
#[test]
fn test_alignment() {
let range = QueryTimeRange::new(100, 500).unwrap();
assert!(range.aligned_start() <= range.requested_start());
assert!(range.aligned_end() >= range.requested_end());
assert_eq!(range.aligned_start() % range.bucket_duration(), 0);
assert_eq!(range.aligned_end() % range.bucket_duration(), 0);
}
#[test]
fn test_accessors() {
let range = QueryTimeRange::new(100, 500).unwrap();
assert_eq!(range.requested_start(), 100);
assert_eq!(range.requested_end(), 500);
assert_eq!(range.requested_duration(), 400);
assert!(range.bucket_duration() > 0);
assert_eq!(
range.aligned_duration(),
range.aligned_end() - range.aligned_start()
);
}
#[test]
fn test_buckets_iterator() {
let range = QueryTimeRange::new(0, 1000).unwrap();
let buckets: Vec<(u32, u32)> = range.buckets().collect();
assert!(!buckets.is_empty());
let mut expected_start = range.aligned_start();
for (start, end) in &buckets {
assert_eq!(*start, expected_start);
assert_eq!(end - start, range.bucket_duration());
expected_start = *end;
}
assert_eq!(expected_start, range.aligned_end());
for (start, end) in &buckets {
assert_eq!(end - start, range.bucket_duration());
}
}
}