sui_indexer_alt_metrics/
lib.rs

1// Copyright (c) Mysten Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4use std::{net::SocketAddr, time::Instant};
5
6use anyhow::Context;
7use axum::{Extension, Router, http::StatusCode, routing::get};
8use prometheus::{Registry, TextEncoder, core::Collector};
9use prometheus_closure_metric::{ClosureMetric, ValueType};
10use tokio::{net::TcpListener, task::JoinHandle};
11use tokio_util::sync::CancellationToken;
12use tracing::info;
13
14pub mod db;
15
16#[derive(clap::Args, Debug, Clone)]
17pub struct MetricsArgs {
18    /// Address to serve Prometheus metrics from.
19    #[arg(long, default_value_t = Self::default().metrics_address)]
20    pub metrics_address: SocketAddr,
21}
22
23/// A service that exposes prometheus metrics over HTTP on a "/metrics" route on the provided
24/// listen address.
25pub struct MetricsService {
26    addr: SocketAddr,
27    registry: Registry,
28    cancel: CancellationToken,
29}
30
31impl MetricsService {
32    /// Create a new instance of the service, listening on the address provided in `args`, serving
33    /// metrics from the `registry`. The service will shut down if the provided `cancel` token is
34    /// cancelled.
35    ///
36    /// The service will not be run until [Self::run] is called.
37    pub fn new(args: MetricsArgs, registry: Registry, cancel: CancellationToken) -> Self {
38        Self {
39            addr: args.metrics_address,
40            registry,
41            cancel,
42        }
43    }
44
45    /// Add metrics to this registry to serve them from this service.
46    pub fn registry(&self) -> &Registry {
47        &self.registry
48    }
49
50    /// Start the service. The service will run until the cancellation token is triggered.
51    pub async fn run(self) -> anyhow::Result<JoinHandle<()>> {
52        let Self {
53            addr,
54            registry,
55            cancel,
56        } = self;
57
58        let listener = TcpListener::bind(&self.addr)
59            .await
60            .with_context(|| format!("Failed to bind metrics at {addr}"))?;
61
62        let app = Router::new()
63            .route("/metrics", get(metrics))
64            .layer(Extension(registry));
65
66        Ok(tokio::spawn(async move {
67            info!("Starting metrics service on {}", addr);
68            axum::serve(listener, app)
69                .with_graceful_shutdown(async move {
70                    cancel.cancelled().await;
71                    info!("Shutdown received, shutting down metrics service");
72                })
73                .await
74                .unwrap()
75        }))
76    }
77}
78
79impl Default for MetricsArgs {
80    fn default() -> Self {
81        Self {
82            metrics_address: "0.0.0.0:9184".parse().unwrap(),
83        }
84    }
85}
86
87/// A metric that tracks the service uptime.
88pub fn uptime(version: &str) -> anyhow::Result<Box<dyn Collector>> {
89    let init = Instant::now();
90    let opts = prometheus::opts!("uptime", "how long the service has been running in seconds")
91        .variable_label("version");
92
93    let metric = move || init.elapsed().as_secs();
94    let uptime = ClosureMetric::new(opts, ValueType::Counter, metric, &[version])
95        .context("Failed to create uptime metric")?;
96
97    Ok(Box::new(uptime))
98}
99
100/// Route handler for metrics service
101async fn metrics(Extension(registry): Extension<Registry>) -> (StatusCode, String) {
102    match TextEncoder.encode_to_string(&registry.gather()) {
103        Ok(s) => (StatusCode::OK, s),
104        Err(e) => (
105            StatusCode::INTERNAL_SERVER_ERROR,
106            format!("unable to encode metrics: {e}"),
107        ),
108    }
109}