Functional Weave
Code in Python

monitor.rollup@1.0.0

impl/rust.rs

2,742 bytes · the Rust implementation · view raw

Imports name this capability’s declared dependencies, which fune builds next to it in your project; each one links to its page.

use super::funejson::Value;  ← the fune runtime: the JSON value the test vectors use; fune build keeps it only where a signature takes one
use super::math_round_div::round_div;  ← from math.round-div ^1.0.0 · built alongside by fune
use super::monitor_series_window::{samples_from_value, series_window, MetricSample};  ← from monitor.series-window ^1.0.0 · built alongside by fune

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<Bucket> {
    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<i64> = None;
        let mut max: Option<i64> = None;
        let mut last: Option<i64> = 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<i64>) -> 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())
}