Skip to content

Commit a65dde9

Browse files
authored
HTTP exporter can listen on unix domain socket (UDS) as well as TCP (#498)
1 parent a699dd6 commit a65dde9

File tree

5 files changed

+270
-50
lines changed

5 files changed

+270
-50
lines changed

metrics-exporter-prometheus/Cargo.toml

Lines changed: 32 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,24 +20,47 @@ keywords = ["metrics", "telemetry", "prometheus"]
2020
default = ["http-listener", "push-gateway"]
2121
async-runtime = ["tokio", "hyper-util/tokio"]
2222
http-listener = ["async-runtime", "ipnet", "tracing", "_hyper-server"]
23+
uds-listener = ["http-listener"]
2324
push-gateway = ["async-runtime", "tracing", "_hyper-client"]
2425
_hyper-server = ["http-body-util", "hyper/server", "hyper-util/server-auto"]
25-
_hyper-client = ["http-body-util", "hyper/client", "hyper-util/client", "hyper-util/http1", "hyper-util/client-legacy", "hyper-rustls"]
26+
_hyper-client = [
27+
"http-body-util",
28+
"hyper/client",
29+
"hyper-util/client",
30+
"hyper-util/http1",
31+
"hyper-util/client-legacy",
32+
"hyper-rustls",
33+
]
2634

2735
[dependencies]
2836
metrics = { version = "^0.23", path = "../metrics" }
29-
metrics-util = { version = "^0.17", path = "../metrics-util", default-features = false, features = ["recency", "registry", "summary"] }
37+
metrics-util = { version = "^0.17", path = "../metrics-util", default-features = false, features = [
38+
"recency",
39+
"registry",
40+
"summary",
41+
] }
3042
thiserror = { version = "1", default-features = false }
3143
quanta = { version = "0.12", default-features = false }
3244
indexmap = { version = "2.1", default-features = false, features = ["std"] }
3345
base64 = { version = "0.22.0", default-features = false, features = ["std"] }
3446

3547
# Optional
36-
hyper = { version = "1.1", features = [ "server", "client" ], optional = true }
37-
hyper-util = { version="0.1.3", features = [ "tokio", "service", "client", "client-legacy", "http1" ], optional = true }
48+
hyper = { version = "1.1", features = ["server", "client"], optional = true }
49+
hyper-util = { version = "0.1.3", features = [
50+
"tokio",
51+
"service",
52+
"client",
53+
"client-legacy",
54+
"http1",
55+
], optional = true }
3856
http-body-util = { version = "0.1.0", optional = true }
3957
ipnet = { version = "2", optional = true }
40-
tokio = { version = "1", features = ["rt", "net", "time", "rt-multi-thread"], optional = true }
58+
tokio = { version = "1", features = [
59+
"rt",
60+
"net",
61+
"time",
62+
"rt-multi-thread",
63+
], optional = true }
4164
tracing = { version = "0.1.26", optional = true }
4265
hyper-rustls = { version = "0.27.2", optional = true }
4366

@@ -55,6 +78,10 @@ required-features = ["push-gateway"]
5578
name = "prometheus_server"
5679
required-features = ["http-listener"]
5780

81+
[[example]]
82+
name = "prometheus_uds_server"
83+
required-features = ["uds-listener"]
84+
5885
[package.metadata.docs.rs]
5986
all-features = true
6087
rustdoc-args = ["--cfg", "docsrs"]
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
use std::thread;
2+
use std::time::Duration;
3+
4+
use metrics::{counter, describe_counter, describe_histogram, gauge, histogram};
5+
use metrics_exporter_prometheus::PrometheusBuilder;
6+
use metrics_util::MetricKindMask;
7+
8+
use quanta::Clock;
9+
use rand::{thread_rng, Rng};
10+
11+
fn main() {
12+
tracing_subscriber::fmt::init();
13+
14+
let builder = PrometheusBuilder::new().with_http_uds_listener("/tmp/metrics.sock");
15+
builder
16+
.idle_timeout(
17+
MetricKindMask::COUNTER | MetricKindMask::HISTOGRAM,
18+
Some(Duration::from_secs(10)),
19+
)
20+
.install()
21+
.expect("failed to install Prometheus recorder");
22+
23+
// We register these metrics, which gives us a chance to specify a description for them. The
24+
// Prometheus exporter records this description and adds it as HELP text when the endpoint is
25+
// scraped.
26+
//
27+
// Registering metrics ahead of using them is not required, but is the only way to specify the
28+
// description of a metric.
29+
describe_counter!("tcp_server_loops", "The iterations of the TCP server event loop so far.");
30+
describe_histogram!(
31+
"tcp_server_loop_delta_secs",
32+
"The time taken for iterations of the TCP server event loop."
33+
);
34+
35+
let clock = Clock::new();
36+
let mut last = None;
37+
38+
counter!("idle_metric").increment(1);
39+
gauge!("testing").set(42.0);
40+
41+
// Loop over and over, pretending to do some work.
42+
loop {
43+
counter!("tcp_server_loops", "system" => "foo").increment(1);
44+
45+
if let Some(t) = last {
46+
let delta: Duration = clock.now() - t;
47+
histogram!("tcp_server_loop_delta_secs", "system" => "foo").record(delta);
48+
}
49+
50+
let increment_gauge = thread_rng().gen_bool(0.75);
51+
let gauge = gauge!("lucky_iterations");
52+
if increment_gauge {
53+
gauge.increment(1.0);
54+
} else {
55+
gauge.decrement(1.0);
56+
}
57+
58+
last = Some(clock.now());
59+
60+
thread::sleep(Duration::from_millis(750));
61+
}
62+
}

metrics-exporter-prometheus/src/exporter/builder.rs

Lines changed: 41 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,10 @@ impl PrometheusBuilder {
5656

5757
#[cfg(feature = "http-listener")]
5858
let exporter_config = ExporterConfig::HttpListener {
59-
listen_address: SocketAddr::new(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)), 9000),
59+
destination: super::ListenDestination::Tcp(SocketAddr::new(
60+
IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)),
61+
9000,
62+
)),
6063
};
6164
#[cfg(not(feature = "http-listener"))]
6265
let exporter_config = ExporterConfig::Unconfigured;
@@ -93,7 +96,9 @@ impl PrometheusBuilder {
9396
#[cfg_attr(docsrs, doc(cfg(feature = "http-listener")))]
9497
#[must_use]
9598
pub fn with_http_listener(mut self, addr: impl Into<SocketAddr>) -> Self {
96-
self.exporter_config = ExporterConfig::HttpListener { listen_address: addr.into() };
99+
self.exporter_config = ExporterConfig::HttpListener {
100+
destination: super::ListenDestination::Tcp(addr.into()),
101+
};
97102
self
98103
}
99104

@@ -133,6 +138,27 @@ impl PrometheusBuilder {
133138
Ok(self)
134139
}
135140

141+
/// Configures the exporter to expose an HTTP listener that functions as a [scrape endpoint],
142+
/// listening on a Unix Domain socket at the given path
143+
///
144+
/// The HTTP listener that is spawned will respond to GET requests on any request path.
145+
///
146+
/// Running in HTTP listener mode is mutually exclusive with the push gateway i.e. enabling the
147+
/// HTTP listener will disable the push gateway, and vise versa.
148+
///
149+
/// Defaults to disabled.
150+
///
151+
/// [scrape endpoint]: https://prometheus.io/docs/instrumenting/exposition_formats/#text-based-format
152+
#[cfg(feature = "uds-listener")]
153+
#[cfg_attr(docsrs, doc(cfg(feature = "uds-listener")))]
154+
#[must_use]
155+
pub fn with_http_uds_listener(mut self, addr: impl Into<std::path::PathBuf>) -> Self {
156+
self.exporter_config = ExporterConfig::HttpListener {
157+
destination: super::ListenDestination::Uds(addr.into()),
158+
};
159+
self
160+
}
161+
136162
/// Adds an IP address or subnet to the allowlist for the scrape endpoint.
137163
///
138164
/// If a client makes a request to the scrape endpoint and their IP is not present in the
@@ -443,13 +469,19 @@ impl PrometheusBuilder {
443469
ExporterConfig::Unconfigured => Err(BuildError::MissingExporterConfiguration)?,
444470

445471
#[cfg(feature = "http-listener")]
446-
ExporterConfig::HttpListener { listen_address } => {
447-
super::http_listener::new_http_listener(
448-
handle,
449-
listen_address,
450-
allowed_addresses,
451-
)?
452-
}
472+
ExporterConfig::HttpListener { destination } => match destination {
473+
super::ListenDestination::Tcp(listen_address) => {
474+
super::http_listener::new_http_listener(
475+
handle,
476+
listen_address,
477+
allowed_addresses,
478+
)?
479+
}
480+
#[cfg(feature = "uds-listener")]
481+
super::ListenDestination::Uds(listen_path) => {
482+
super::http_listener::new_http_uds_listener(handle, listen_path)?
483+
}
484+
},
453485

454486
#[cfg(feature = "push-gateway")]
455487
ExporterConfig::PushGateway { endpoint, interval, username, password } => {

0 commit comments

Comments
 (0)