Skip to main content

open_chess_server/
metrics.rs

1//! Prometheus metrics for the chess server.
2
3use prometheus::{IntCounter, IntCounterVec, IntGaugeVec, Opts, Registry};
4
5#[derive(Clone)]
6pub struct Metrics {
7    pub registry: Registry,
8    pub moves: IntCounter,
9    pub games_started: IntCounter,
10    pub games_finished: IntCounterVec,
11    pub challenges: IntCounterVec,
12    pub streams: IntGaugeVec,
13    pub analysed: IntCounterVec,
14}
15
16impl Metrics {
17    pub fn new() -> Metrics {
18        let registry = Registry::new_custom(Some("chess".into()), None).expect("valid prefix");
19        let moves = IntCounter::new("moves_total", "Moves played").expect("valid metric");
20        let games_started = IntCounter::new("games_started_total", "Games started").expect("valid metric");
21        let games_finished =
22            IntCounterVec::new(Opts::new("games_finished_total", "Games finished, by status"), &["status"])
23                .expect("valid metric");
24        let challenges =
25            IntCounterVec::new(Opts::new("challenges_total", "Challenges, by outcome"), &["outcome"])
26                .expect("valid metric");
27        let streams =
28            IntGaugeVec::new(Opts::new("streams_open", "Open NDJSON streams on this replica"), &["kind"])
29                .expect("valid metric");
30        let analysed = IntCounterVec::new(
31            Opts::new("fairplay_sides_analysed_total", "Players' games screened"),
32            &["flagged"],
33        )
34        .expect("valid metric");
35        for c in [
36            Box::new(moves.clone()) as Box<dyn prometheus::core::Collector>,
37            Box::new(games_started.clone()),
38            Box::new(games_finished.clone()),
39            Box::new(challenges.clone()),
40            Box::new(streams.clone()),
41            Box::new(analysed.clone()),
42        ] {
43            registry.register(c).expect("unique metric");
44        }
45        Metrics { registry, moves, games_started, games_finished, challenges, streams, analysed }
46    }
47
48    pub fn render(&self) -> String {
49        use prometheus::Encoder;
50        let mut buf = Vec::new();
51        let _ = prometheus::TextEncoder::new().encode(&self.registry.gather(), &mut buf);
52        String::from_utf8(buf).unwrap_or_default()
53    }
54}
55
56impl Default for Metrics {
57    fn default() -> Self {
58        Metrics::new()
59    }
60}
61
62/// Counts an open stream for as long as it's alive.
63pub struct StreamGuard(prometheus::IntGauge);
64
65impl StreamGuard {
66    pub fn new(m: &Metrics, kind: &str) -> StreamGuard {
67        let g = m.streams.with_label_values(&[kind]);
68        g.inc();
69        StreamGuard(g)
70    }
71}
72
73impl Drop for StreamGuard {
74    fn drop(&mut self) {
75        self.0.dec();
76    }
77}