Hew metrics aggregation demo
//! A small metrics aggregation service: sources push samples to a
//! collector actor, a periodic ticker counts snapshots, and asks read
//! the aggregates back out.

type Sample {
    name: string;
    value: f64;
}

actor Collector {
    var counts: HashMap<string, i64> = HashMap::new();
    var sums: HashMap<string, f64> = HashMap::new();
    var snapshots_taken: i64 = 0;

    receive fn observe(name: string, value: f64) {
        let prior_count = match counts.get(name) {
            Some(c) => c,
            None => 0,
        };
        counts.insert(name, prior_count + 1);
        let prior_sum = match sums.get(name) {
            Some(s) => s,
            None => 0.0,
        };
        sums.insert(name, prior_sum + value);
    }

    #[every(40ms)]
    receive fn snapshot() {
        snapshots_taken = snapshots_taken + 1;
    }

    receive fn mean(name: string) -> f64 {
        let c = match counts.get(name) {
            Some(c) => c,
            None => 0,
        };
        if c == 0 {
            return 0.0;
        }
        let s = match sums.get(name) {
            Some(s) => s,
            None => 0.0,
        };
        s / (c as f64)
    }

    receive fn count(name: string) -> i64 {
        match counts.get(name) {
            Some(c) => c,
            None => 0,
        }
    }

    receive fn snapshots() -> i64 {
        snapshots_taken
    }
}

fn percentile_label(p: f64) -> string {
    match p {
        v if v >= 0.99 => "p99",
        v if v >= 0.95 => "p95",
        v if v >= 0.5 => "p50",
        _ => "low",
    }
}

fn main() {
    let collector = spawn Collector();

    // Sends from inside a function closure are not yet implemented
    // (captured pid loses its actor layout in the closure's MIR context);
    // push samples from plain loops instead.
    var i = 0;
    while i < 100 {
        collector.observe("api.latency", 10.0 + ((i % 50) as f64));
        i = i + 1;
    }
    var j = 0;
    while j < 50 {
        collector.observe("db.latency", 3.0 + (j as f64));
        j = j + 1;
    }

    sleep_ms(200);

    match await collector.count("api.latency") {
        Ok(n) => println(f"api.latency samples: {n}"),
        Err(_) => println("ask failed: count"),
    }
    match await collector.mean("api.latency") {
        Ok(m) => println(f"api.latency mean: {m}"),
        Err(_) => println("ask failed: mean"),
    }
    match await collector.mean("db.latency") {
        Ok(m) => println(f"db.latency mean: {m}"),
        Err(_) => println("ask failed: mean"),
    }
    match await collector.snapshots() {
        Ok(s) => {
            if s >= 3 {
                println("periodic snapshots: ok");
            } else {
                println(f"periodic snapshots: only {s}");
            }
        },
        Err(_) => println("ask failed: snapshots"),
    }
    println(f"label: {percentile_label(0.97)}");
}