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 sui_futures::service::Service;
11use tokio::{net::TcpListener, sync::oneshot};
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}
29
30impl MetricsService {
31    /// Create a new instance of the service, listening on the address provided in `args`, serving
32    /// metrics from the `registry`.
33    ///
34    /// The service will not be run until [Self::run] is called.
35    pub fn new(args: MetricsArgs, registry: Registry) -> Self {
36        Self {
37            addr: args.metrics_address,
38            registry,
39        }
40    }
41
42    /// Add metrics to this registry to serve them from this service.
43    pub fn registry(&self) -> &Registry {
44        &self.registry
45    }
46
47    /// Start the service. The service will run until the cancellation token is triggered.
48    pub async fn run(self) -> anyhow::Result<Service> {
49        let Self { addr, registry } = self;
50
51        let listener = TcpListener::bind(&self.addr)
52            .await
53            .with_context(|| format!("Failed to bind metrics at {addr}"))?;
54
55        let app = Router::new()
56            .route("/metrics", get(metrics))
57            .layer(Extension(registry));
58
59        let (stx, srx) = oneshot::channel::<()>();
60        Ok(Service::new()
61            .with_shutdown_signal(async move {
62                let _ = stx.send(());
63            })
64            .spawn(async move {
65                info!("Starting metrics service on {addr}");
66                Ok(axum::serve(listener, app)
67                    .with_graceful_shutdown(async move {
68                        let _ = srx.await;
69                        info!("Shutdown received, shutting down metrics service");
70                    })
71                    .await?)
72            }))
73    }
74}
75
76impl Default for MetricsArgs {
77    fn default() -> Self {
78        Self {
79            metrics_address: "0.0.0.0:9184".parse().unwrap(),
80        }
81    }
82}
83
84/// A metric that tracks the service uptime.
85pub fn uptime(version: &str) -> anyhow::Result<Box<dyn Collector>> {
86    let init = Instant::now();
87    let opts = prometheus::opts!("uptime", "how long the service has been running in seconds")
88        .variable_label("version");
89
90    let metric = move || init.elapsed().as_secs();
91    let uptime = ClosureMetric::new(opts, ValueType::Counter, metric, &[version])
92        .context("Failed to create uptime metric")?;
93
94    Ok(Box::new(uptime))
95}
96
97/// Route handler for metrics service
98async fn metrics(Extension(registry): Extension<Registry>) -> (StatusCode, String) {
99    match TextEncoder.encode_to_string(&registry.gather()) {
100        Ok(s) => (StatusCode::OK, s),
101        Err(e) => (
102            StatusCode::INTERNAL_SERVER_ERROR,
103            format!("unable to encode metrics: {e}"),
104        ),
105    }
106}