Watch
0
0
Fork
You've already forked hyperhive
0

push ci run metrics to victoriametrics, with a grafana board

A swarm-ci-metrics host unit on a 5-minute timer lists every forge
repo's Actions runs as the new read-only ci-metrics account and pushes
each finished run's duration and queue wait over OTLP, stamped at the
run's stop time. The first pass backfills from run 1. swarm-controller
mints the account's token into bao and keeps a ci-metrics team with
read on the Actions unit in every organisation.

Closes #5376
This commit is contained in:
atlas 2026-10-16 18:23:59 +02:00 • committed by mara
commit b072fbf4d6
36 changed files with 2849 additions and 56 deletions

View file

@ -0,0 +1,64 @@
//! The CI metrics poller agreement: where its forge token and its OIDC client
//! secret live in the store, and what each object holds.
//!
//! swarm-controller mints the forge token; the poller reads it, and its client
//! secret, under its own certificate. Its read stanza in `swarm-bao.nix` names
//! exactly these two paths.
use serde::{Deserialize, Serialize};
use crate::{
Error,
path::{Kind, principal_prefix},
};
/// The poller's name segment under [`Kind::Service`].
const SERVICE: &str = "ci-metrics";
/// The path holding the token of the poller's read-only forge account, as a
/// [`crate::forge::Credential`].
///
/// # Errors
/// Never in practice: the name segment is a constant. The `Result` is
/// [`principal_prefix`]'s.
pub fn forge_token_path() -> Result<String, Error> {
let prefix = principal_prefix(Kind::Service, SERVICE)?;
Ok(format!("{prefix}/forge/token"))
}
/// The path holding the secret of the poller's OIDC client `client_id`, where
/// `swarm-secret-publisher.nix` puts every swarm service's.
///
/// # Errors
/// [`Error::PathSegment`] when `client_id` is not a single path segment.
pub fn client_secret_path(client_id: &str) -> Result<String, Error> {
let prefix = principal_prefix(Kind::Service, client_id)?;
Ok(format!("{prefix}/oidc/client"))
}
/// What [`client_secret_path`] holds.
///
/// No `Debug`: `value` authenticates the poller at the collector.
#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ClientSecret {
/// The secret, under the field name the publisher writes.
pub value: String,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn the_paths_are_the_ones_the_store_grants() {
// Spelled out: `swarm-bao.nix` grants each literal.
assert_eq!(
forge_token_path().expect("a constant segment is legal"),
"swarm/services/ci-metrics/forge/token"
);
assert_eq!(
client_secret_path("swarm-ci-metrics").expect("a plain id is legal"),
"swarm/services/swarm-ci-metrics/oidc/client"
);
}
}

View file

@ -6,7 +6,8 @@
//! deployment's environment into a logged-in client ([`client`]), and, per kind
//! of secret, the path it lives at together with the fields it holds
//! ([`matrix`], [`queue`], [`mtls`], [`forge`], [`github`], [`grafana`],
//! [`acp`], [`ci`], [`review_classifier`], [`builder`], [`claude_login`]).
//! [`acp`], [`ci`], [`review_classifier`], [`builder`], [`ci_metrics`],
//! [`claude_login`]).
//! Each of those is a thing the controller and a hive must say identically, so
//! it is said once here.
//!
@ -27,6 +28,7 @@
pub mod acp;
pub mod builder;
pub mod ci;
pub mod ci_metrics;
pub mod claude_login;
pub mod client;
pub mod forge;