Skip to content

Commit a264ccb

Browse files
authored
feat(metrics): add push gateway support for Prometheus metrics (#19243)
1 parent b1dfbc7 commit a264ccb

File tree

6 files changed

+125
-10
lines changed

6 files changed

+125
-10
lines changed

crates/node/builder/src/launch/common.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -610,7 +610,7 @@ where
610610
}
611611
})
612612
.build(),
613-
);
613+
).with_push_gateway(self.node_config().metrics.push_gateway_url.clone(), self.node_config().metrics.push_gateway_interval);
614614

615615
MetricServer::new(config).serve().await?;
616616
}
Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use clap::Parser;
2-
use reth_cli_util::parse_socket_address;
3-
use std::net::SocketAddr;
2+
use reth_cli_util::{parse_duration_from_secs, parse_socket_address};
3+
use std::{net::SocketAddr, time::Duration};
44

55
/// Metrics configuration.
66
#[derive(Debug, Clone, Default, Parser)]
@@ -10,4 +10,26 @@ pub struct MetricArgs {
1010
/// The metrics will be served at the given interface and port.
1111
#[arg(long="metrics", alias = "metrics.prometheus", value_name = "PROMETHEUS", value_parser = parse_socket_address, help_heading = "Metrics")]
1212
pub prometheus: Option<SocketAddr>,
13+
14+
/// URL for pushing Prometheus metrics to a push gateway.
15+
///
16+
/// If set, the node will periodically push metrics to the specified push gateway URL.
17+
#[arg(
18+
long = "metrics.prometheus.push.url",
19+
value_name = "PUSH_GATEWAY_URL",
20+
help_heading = "Metrics"
21+
)]
22+
pub push_gateway_url: Option<String>,
23+
24+
/// Interval in seconds for pushing metrics to push gateway.
25+
///
26+
/// Default: 5 seconds
27+
#[arg(
28+
long = "metrics.prometheus.push.interval",
29+
default_value = "5",
30+
value_parser = parse_duration_from_secs,
31+
value_name = "SECONDS",
32+
help_heading = "Metrics"
33+
)]
34+
pub push_gateway_interval: Duration,
1335
}

crates/node/core/src/node_config.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -234,7 +234,7 @@ impl<ChainSpec> NodeConfig<ChainSpec> {
234234
}
235235

236236
/// Set the metrics address for the node
237-
pub const fn with_metrics(mut self, metrics: MetricArgs) -> Self {
237+
pub fn with_metrics(mut self, metrics: MetricArgs) -> Self {
238238
self.metrics = metrics;
239239
self
240240
}

crates/node/metrics/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ tokio.workspace = true
2121
jsonrpsee-server.workspace = true
2222
http.workspace = true
2323
tower.workspace = true
24+
reqwest.workspace = true
2425

2526
tracing.workspace = true
2627
eyre.workspace = true

crates/node/metrics/src/server.rs

Lines changed: 86 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,10 @@ use eyre::WrapErr;
88
use http::{header::CONTENT_TYPE, HeaderValue, Response};
99
use metrics::describe_gauge;
1010
use metrics_process::Collector;
11+
use reqwest::Client;
1112
use reth_metrics::metrics::Unit;
1213
use reth_tasks::TaskExecutor;
13-
use std::{convert::Infallible, net::SocketAddr, sync::Arc};
14+
use std::{convert::Infallible, net::SocketAddr, sync::Arc, time::Duration};
1415

1516
/// Configuration for the [`MetricServer`]
1617
#[derive(Debug)]
@@ -20,6 +21,8 @@ pub struct MetricServerConfig {
2021
chain_spec_info: ChainSpecInfo,
2122
task_executor: TaskExecutor,
2223
hooks: Hooks,
24+
push_gateway_url: Option<String>,
25+
push_gateway_interval: Duration,
2326
}
2427

2528
impl MetricServerConfig {
@@ -31,7 +34,22 @@ impl MetricServerConfig {
3134
task_executor: TaskExecutor,
3235
hooks: Hooks,
3336
) -> Self {
34-
Self { listen_addr, hooks, task_executor, version_info, chain_spec_info }
37+
Self {
38+
listen_addr,
39+
hooks,
40+
task_executor,
41+
version_info,
42+
chain_spec_info,
43+
push_gateway_url: None,
44+
push_gateway_interval: Duration::from_secs(5),
45+
}
46+
}
47+
48+
/// Set the gateway URL and interval for pushing metrics
49+
pub fn with_push_gateway(mut self, url: Option<String>, interval: Duration) -> Self {
50+
self.push_gateway_url = url;
51+
self.push_gateway_interval = interval;
52+
self
3553
}
3654
}
3755

@@ -49,18 +67,35 @@ impl MetricServer {
4967

5068
/// Spawns the metrics server
5169
pub async fn serve(&self) -> eyre::Result<()> {
52-
let MetricServerConfig { listen_addr, hooks, task_executor, version_info, chain_spec_info } =
53-
&self.config;
70+
let MetricServerConfig {
71+
listen_addr,
72+
hooks,
73+
task_executor,
74+
version_info,
75+
chain_spec_info,
76+
push_gateway_url,
77+
push_gateway_interval,
78+
} = &self.config;
5479

55-
let hooks = hooks.clone();
80+
let hooks_for_endpoint = hooks.clone();
5681
self.start_endpoint(
5782
*listen_addr,
58-
Arc::new(move || hooks.iter().for_each(|hook| hook())),
83+
Arc::new(move || hooks_for_endpoint.iter().for_each(|hook| hook())),
5984
task_executor.clone(),
6085
)
6186
.await
6287
.wrap_err_with(|| format!("Could not start Prometheus endpoint at {listen_addr}"))?;
6388

89+
// Start push-gateway task if configured
90+
if let Some(url) = push_gateway_url {
91+
self.start_push_gateway_task(
92+
url.clone(),
93+
*push_gateway_interval,
94+
hooks.clone(),
95+
task_executor.clone(),
96+
)?;
97+
}
98+
6499
// Describe metrics after recorder installation
65100
describe_db_metrics();
66101
describe_static_file_metrics();
@@ -128,6 +163,51 @@ impl MetricServer {
128163

129164
Ok(())
130165
}
166+
167+
/// Starts a background task to push metrics to a metrics gateway
168+
fn start_push_gateway_task(
169+
&self,
170+
url: String,
171+
interval: Duration,
172+
hooks: Hooks,
173+
task_executor: TaskExecutor,
174+
) -> eyre::Result<()> {
175+
let client = Client::builder()
176+
.build()
177+
.wrap_err("Could not create HTTP client to push metrics to gateway")?;
178+
task_executor.spawn_with_graceful_shutdown_signal(move |mut signal| {
179+
Box::pin(async move {
180+
tracing::info!(url = %url, interval = ?interval, "Starting task to push metrics to gateway");
181+
let handle = install_prometheus_recorder();
182+
loop {
183+
tokio::select! {
184+
_ = &mut signal => {
185+
tracing::info!("Shutting down task to push metrics to gateway");
186+
break;
187+
}
188+
_ = tokio::time::sleep(interval) => {
189+
hooks.iter().for_each(|hook| hook());
190+
let metrics = handle.handle().render();
191+
match client.put(&url).header("Content-Type", "text/plain").body(metrics).send().await {
192+
Ok(response) => {
193+
if !response.status().is_success() {
194+
tracing::warn!(
195+
status = %response.status(),
196+
"Failed to push metrics to gateway"
197+
);
198+
}
199+
}
200+
Err(err) => {
201+
tracing::warn!(%err, "Failed to push metrics to gateway");
202+
}
203+
}
204+
}
205+
}
206+
}
207+
})
208+
});
209+
Ok(())
210+
}
131211
}
132212

133213
fn describe_db_metrics() {

docs/vocs/docs/pages/cli/reth/node.mdx

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,18 @@ Metrics:
4444
4545
The metrics will be served at the given interface and port.
4646
47+
--metrics.prometheus.push.url <PUSH_GATEWAY_URL>
48+
URL for pushing Prometheus metrics to a push gateway.
49+
50+
If set, the node will periodically push metrics to the specified push gateway URL.
51+
52+
--metrics.prometheus.push.interval <SECONDS>
53+
Interval in seconds for pushing metrics to push gateway.
54+
55+
Default: 5 seconds
56+
57+
[default: 5]
58+
4759
Datadir:
4860
--datadir <DATA_DIR>
4961
The path to the data dir for all reth files and subdirectories.

0 commit comments

Comments
 (0)