use super::funejson::Value; use super::math_round_div::round_div; use super::monitor_series_window::{samples_from_value, series_window, MetricSample}; const MAX_BUCKETS: i64 = 10000; /// One row per bucket from `from` to `to`, empty buckets included so a chart /// shows gaps as gaps. Buckets are aligned to `from`; the last is clipped to `to`. /// /// # Panics /// Panics on a bucket width under 1, more than 10000 buckets, `from` after /// `to`, or misordered samples. pub fn rollup(samples: &[MetricSample], from: i64, to: i64, bucket_seconds: i64) -> Vec { if bucket_seconds < 1 { panic!("bucketSeconds must be at least 1, received {}", bucket_seconds); } let inside = series_window(samples, from, to); let n = (to - from + bucket_seconds - 1) / bucket_seconds; if n > MAX_BUCKETS { panic!("too many buckets: {}, at most {}", n, MAX_BUCKETS); } let mut out = Vec::new(); let mut j = 0usize; for i in 0..n { let start = from + i * bucket_seconds; let end = (start + bucket_seconds).min(to); let mut count = 0i64; let mut sum = 0i64; let mut min: Option = None; let mut max: Option = None; let mut last: Option = None; while j < inside.len() && inside[j].at < end { let v = inside[j].value; count += 1; sum += v; min = Some(min.map_or(v, |m| m.min(v))); max = Some(max.map_or(v, |m| m.max(v))); last = Some(v); j += 1; } let avg = if count == 0 { None } else { Some(round_div(sum, count, "half-up")) }; out.push(Bucket { start, end, count, min, max, avg, sum, last }); } out } fn opt(v: Option) -> Value { match v { Some(i) => Value::Int(i), None => Value::Null, } } pub fn bucket_to_value(b: &Bucket) -> Value { Value::obj(vec![ ("start", Value::Int(b.start)), ("end", Value::Int(b.end)), ("count", Value::Int(b.count)), ("min", opt(b.min)), ("max", opt(b.max)), ("avg", opt(b.avg)), ("sum", Value::Int(b.sum)), ("last", opt(b.last)), ]) } fn whole(v: &Value, message: &str) -> i64 { match v { Value::Int(i) => *i, _ => panic!("{}", message), } } pub fn fune_vector(args: &[Value]) -> Value { let samples = samples_from_value(&args[0]); let from = whole(&args[1], "from and to must be whole seconds"); let to = whole(&args[2], "from and to must be whole seconds"); let bucket = whole(&args[3], "bucketSeconds must be a whole number of seconds"); Value::Arr(rollup(&samples, from, to, bucket).iter().map(bucket_to_value).collect()) }