canic-host 0.110.53

Host-side App build and desired-state Fleet reconciliation library for Canic workspaces
Documentation
use super::*;
use canic_core::dto::{page::Page, public_status::PublicMetric};

fn snapshot(family: PublicMetricFamily, names: &[&str]) -> PublicMetricsSnapshot {
    PublicMetricsSnapshot {
        family,
        state: PublicSnapshotState::Fresh,
        sampled_at_ns: Some(90),
        stale_after_ns: 500,
        truncated: false,
        metrics: Page {
            total: names.len() as u64,
            entries: names
                .iter()
                .map(|name| PublicMetric {
                    name: (*name).into(),
                    canister_id: None,
                    value: u128::MAX,
                    unit: "cycles".into(),
                    observed_at_ns: 90,
                    kind: PublicMetricKind::Counter {
                        window_id: 7,
                        saturated: true,
                    },
                })
                .collect(),
        },
    }
}

#[test]
fn grants_keep_precision_source_time_and_saturation_without_inventing_consumption() {
    let family = PublicMetricFamily::Operations;
    let mut reply = snapshot(
        family,
        &[
            "cycles_funding.cycles_granted_total",
            "cycles_funding.cycles_granted_to_child",
            "cycles_funding.cycles_requested_total",
            "timer.events.scheduler_started",
        ],
    );
    reply.state = PublicSnapshotState::Stale;
    reply.metrics.entries[1].canister_id = Some(candid::Principal::anonymous());
    let view = samples(reply, family).unwrap();
    assert_eq!(view.state, MetricSampleState::Stale);
    assert_eq!(view.rows.len(), 3);
    assert_eq!(view.rows[0].value, u128::MAX.to_string());
    assert_eq!(view.rows[0].observed_at_ns, 90);
    assert_eq!(
        view.rows[0].measurement,
        MetricKind::Counter {
            window_id: 7,
            saturated: true
        }
    );
    assert_eq!(view.rows[1].canister_id.as_deref(), Some("2vxsx-fae"));
    assert_eq!(view.sampled_at_ns, Some(90));
}

#[test]
fn timer_measurements_keep_source_registration_and_both_phases() {
    let family = PublicMetricFamily::Performance;
    let mut reply = snapshot(
        family,
        &[
            "perf.timer.app.checkpoint.tick.scheduler",
            "perf.timer.app.checkpoint.tick.scheduler.calls",
            "perf.timer.app.checkpoint.tick.work",
            "perf.timer.app.checkpoint.tick.work.calls",
            "perf.endpoint.query.status",
        ],
    );
    for row in &mut reply.metrics.entries {
        row.kind = PublicMetricKind::TimerCounter {
            registration: canic_core::dto::public_status::TimerMetricRegistration {
                canister_version: 3,
                started_at_ns: 10,
                sequence: 7,
            },
            saturated: true,
        };
    }
    let view = samples(reply, family).unwrap();
    assert_eq!(view.rows.len(), 4);
    assert!(view.rows.iter().all(|row| row.measurement
        == MetricKind::TimerCounter {
            registration: TimerRegistrationView {
                canister_version: 3,
                started_at_ns: 10,
                sequence: 7
            },
            saturated: true,
        }));
    let json = serde_json::to_vec(&view).unwrap();
    assert_eq!(
        serde_json::from_slice::<MetricSamplesView>(&json).unwrap(),
        view
    );
}

#[test]
fn pagination_and_source_truncation_survive_filtering() {
    let family = PublicMetricFamily::Operations;
    let mut reply = snapshot(family, &["unselected"]);
    reply.metrics.total = 2;
    let view = samples(reply, family).unwrap();
    assert!(view.truncated);
    assert!(view.rows.is_empty());
    let mut reply = snapshot(family, &[]);
    reply.truncated = true;
    assert!(samples(reply, family).unwrap().truncated);
}

#[test]
fn malformed_pages_reject_before_projection() {
    let family = PublicMetricFamily::Cycles;
    assert_eq!(
        samples(
            snapshot(family, &["balance"]),
            PublicMetricFamily::Performance
        ),
        Err(ObservationFailure::InvalidResponse)
    );
    assert_eq!(
        samples(snapshot(family, &["balance", "balance"]), family),
        Err(ObservationFailure::InvalidResponse)
    );
    let mut reply = snapshot(family, &["balance"]);
    reply.metrics.entries[0].observed_at_ns = 89;
    assert_eq!(
        samples(reply, family),
        Err(ObservationFailure::InvalidResponse)
    );
    let names = vec!["balance"; 257];
    assert_eq!(
        samples(snapshot(family, &names), family),
        Err(ObservationFailure::InvalidResponse)
    );
    let mut reply = snapshot(family, &["balance"]);
    reply.metrics.total = 0;
    assert_eq!(
        samples(reply, family),
        Err(ObservationFailure::InvalidResponse)
    );
}

#[test]
fn disabled_empty_family_remains_disabled() {
    let family = PublicMetricFamily::Cycles;
    let mut reply = snapshot(family, &[]);
    reply.state = PublicSnapshotState::Disabled;
    reply.sampled_at_ns = None;
    let view = samples(reply, family).unwrap();
    assert_eq!(view.state, MetricSampleState::Disabled);
    assert_eq!(view.sampled_at_ns, None);
    assert!(view.rows.is_empty());
}

#[test]
fn application_metrics_preserve_arbitrary_names_units_and_mixed_source_times() {
    let family = PublicMetricFamily::Application;
    let mut reply = snapshot(family, &["app.queue_depth", "app.accounted_bytes"]);
    reply.metrics.entries[0].kind = PublicMetricKind::Gauge;
    reply.metrics.entries[0].unit = "count".into();
    reply.metrics.entries[1].observed_at_ns = 95;
    reply.metrics.entries[1].unit = "bytes".into();
    reply.metrics.total = 3;
    let view = samples(reply, family).unwrap();
    assert_eq!(view.rows[0].name, "app.queue_depth");
    assert_eq!(view.rows[0].unit, "count");
    assert_eq!(view.rows[0].measurement, MetricKind::Gauge);
    assert_eq!(view.rows[1].name, "app.accounted_bytes");
    assert_eq!(view.rows[1].unit, "bytes");
    assert_eq!(view.rows[1].value, u128::MAX.to_string());
    assert_eq!(view.rows[1].observed_at_ns, 95);
    assert_eq!(view.sampled_at_ns, Some(90));
    assert!(view.truncated);
}

#[test]
fn application_metrics_reject_ambiguous_or_unbounded_rows() {
    let family = PublicMetricFamily::Application;
    for name in [String::new(), "x".repeat(129), "line\nbreak".into()] {
        assert_eq!(
            samples(snapshot(family, &[&name]), family),
            Err(ObservationFailure::InvalidResponse)
        );
    }
    let mut reply = snapshot(family, &["app.queue_depth"]);
    reply.sampled_at_ns = None;
    assert_eq!(
        samples(reply, family),
        Err(ObservationFailure::InvalidResponse)
    );
    assert_eq!(
        samples(snapshot(family, &["same", "same"]), family),
        Err(ObservationFailure::InvalidResponse)
    );
}