use std::sync::OnceLock;
use prometheus::{IntCounterVec, register_int_counter_vec};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum AttestationOutcome {
Signed,
RebuildFailed,
ExtendSignFailed,
MarkerAppendFailed,
UnattestedPartialBatch,
}
impl AttestationOutcome {
const fn label(self) -> &'static str {
match self {
Self::Signed => "signed",
Self::RebuildFailed => "rebuild_failed",
Self::ExtendSignFailed => "extend_sign_failed",
Self::MarkerAppendFailed => "marker_append_failed",
Self::UnattestedPartialBatch => "unattested_partial_batch",
}
}
}
pub(crate) fn force() {
for outcome in [
AttestationOutcome::Signed,
AttestationOutcome::RebuildFailed,
AttestationOutcome::ExtendSignFailed,
AttestationOutcome::MarkerAppendFailed,
AttestationOutcome::UnattestedPartialBatch,
] {
attestations().with_label_values(&[outcome.label()]);
}
}
fn attestations() -> &'static IntCounterVec {
static V: OnceLock<IntCounterVec> = OnceLock::new();
V.get_or_init(|| {
register_int_counter_vec!(
"polychrome_eventlog_attestation_total",
"Signed-root outcomes for committed journal batches, by outcome. \
A batch that does not reach `signed` is durable and unattested, \
and a partition holding no marker verifies trivially.",
&["outcome"]
)
.expect("the attestation counter registers once into the default registry")
})
}
pub(crate) fn record_attestation(outcome: AttestationOutcome) {
attestations().with_label_values(&[outcome.label()]).inc();
}
#[cfg(test)]
pub(crate) fn attestation_count(outcome: AttestationOutcome) -> u64 {
attestations().with_label_values(&[outcome.label()]).get()
}
#[cfg(test)]
mod tests {
#![allow(clippy::pedantic, clippy::nursery, missing_docs)]
use super::*;
#[test]
fn every_outcome_has_a_distinct_label() {
let labels = [
AttestationOutcome::Signed.label(),
AttestationOutcome::RebuildFailed.label(),
AttestationOutcome::ExtendSignFailed.label(),
AttestationOutcome::MarkerAppendFailed.label(),
AttestationOutcome::UnattestedPartialBatch.label(),
];
let mut seen = labels.to_vec();
seen.sort_unstable();
seen.dedup();
assert_eq!(seen.len(), labels.len(), "two outcomes share a label");
}
#[test]
fn every_outcome_is_in_the_scrape_before_anything_happens() {
force();
let mut buf = Vec::new();
prometheus::Encoder::encode(
&prometheus::TextEncoder::new(),
&prometheus::default_registry().gather(),
&mut buf,
)
.expect("encode the default registry");
let text = String::from_utf8(buf).expect("the exposition format is utf-8");
assert!(
text.contains("polychrome_eventlog_attestation_total"),
"the counter is absent from the scrape:\n{text}"
);
for outcome in [
"signed",
"rebuild_failed",
"extend_sign_failed",
"marker_append_failed",
"unattested_partial_batch",
] {
assert!(
text.contains(&format!("outcome=\"{outcome}\"")),
"outcome={outcome} has no series, so a query for it reads as \
no-data rather than zero:\n{text}"
);
}
}
#[test]
fn recording_an_outcome_advances_its_own_counter_only() {
let before_signed = attestations().with_label_values(&["signed"]).get();
let before_failed = attestations()
.with_label_values(&["extend_sign_failed"])
.get();
record_attestation(AttestationOutcome::Signed);
assert_eq!(
attestations().with_label_values(&["signed"]).get(),
before_signed + 1,
"the signed outcome counts"
);
assert_eq!(
attestations()
.with_label_values(&["extend_sign_failed"])
.get(),
before_failed,
"counting one outcome must not move another"
);
}
}