1#![warn(missing_docs)]
2use actix_web::http::StatusCode;
8use actix_web::web::Data;
9use actix_web::{get, App, HttpResponse, HttpServer};
10use prometheus::{Registry as SubstrateRegistry, TextEncoder};
11use prometheus_client::encoding::text::encode;
12use prometheus_client::registry::Registry as PrometheusClientRegistry;
13use std::error::Error;
14use std::future::Future;
15use std::io::ErrorKind;
16use std::net::SocketAddr;
17use tracing::{error, info, warn};
18
19pub enum RegistryAdapter {
22 PrometheusClient(PrometheusClientRegistry),
24 Substrate(SubstrateRegistry),
26 Both(PrometheusClientRegistry, SubstrateRegistry),
28}
29
30#[get("/metrics")]
31async fn metrics(registry: Data<RegistryAdapter>) -> Result<HttpResponse, Box<dyn Error>> {
32 let mut encoded_metrics = String::new();
33
34 match &**registry {
35 RegistryAdapter::PrometheusClient(libp2p_registry) => {
36 encode(&mut encoded_metrics, libp2p_registry)?;
37 }
38 RegistryAdapter::Substrate(substrate_registry) => {
39 TextEncoder::new().encode_utf8(&substrate_registry.gather(), &mut encoded_metrics)?;
40 }
41 RegistryAdapter::Both(libp2p_registry, substrate_registry) => {
42 TextEncoder::new().encode_utf8(&substrate_registry.gather(), &mut encoded_metrics)?;
44 encode(&mut encoded_metrics, libp2p_registry)?;
46 }
47 }
48
49 let resp = HttpResponse::build(StatusCode::OK)
50 .content_type("application/openmetrics-text; version=1.0.0; charset=utf-8")
51 .body(encoded_metrics);
52
53 Ok(resp)
54}
55
56pub fn start_prometheus_metrics_server(
58 mut endpoints: Vec<SocketAddr>,
59 registry: RegistryAdapter,
60) -> std::io::Result<impl Future<Output = std::io::Result<()>>> {
61 let data = Data::new(registry);
62
63 let app_factory = move || App::new().app_data(data.clone()).service(metrics);
64 let result = HttpServer::new(app_factory.clone())
65 .workers(2)
66 .bind(endpoints.as_slice());
67
68 let server = match result {
69 Ok(server) => server,
70 Err(error) => {
71 if error.kind() != ErrorKind::AddrInUse {
72 error!(?error, "Failed to start metrics server.");
73
74 return Err(error);
75 }
76
77 warn!(
79 ?error,
80 "Failed to start metrics server. Falling back to the random port...",
81 );
82
83 endpoints.iter_mut().for_each(|endpoint| {
84 endpoint.set_port(0);
85 });
86
87 let result = HttpServer::new(app_factory)
88 .workers(2)
89 .bind(endpoints.as_slice());
90
91 match result {
92 Ok(server) => server,
93 Err(error) => {
94 error!(?error, "Failed to start metrics server on the random port.");
95
96 return Err(error);
97 }
98 }
99 }
100 };
101
102 info!(endpoints = ?server.addrs(), "Metrics server started.",);
103
104 Ok(server.run())
105}