sui_indexer_alt_metrics/
lib.rs

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