Skip to content

Commit

Permalink
HTTP exporter can listen on unix domain socket (UDS) as well as TCP (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
scottopell authored Jul 11, 2024
1 parent a699dd6 commit a65dde9
Show file tree
Hide file tree
Showing 5 changed files with 270 additions and 50 deletions.
37 changes: 32 additions & 5 deletions metrics-exporter-prometheus/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,24 +20,47 @@ keywords = ["metrics", "telemetry", "prometheus"]
default = ["http-listener", "push-gateway"]
async-runtime = ["tokio", "hyper-util/tokio"]
http-listener = ["async-runtime", "ipnet", "tracing", "_hyper-server"]
uds-listener = ["http-listener"]
push-gateway = ["async-runtime", "tracing", "_hyper-client"]
_hyper-server = ["http-body-util", "hyper/server", "hyper-util/server-auto"]
_hyper-client = ["http-body-util", "hyper/client", "hyper-util/client", "hyper-util/http1", "hyper-util/client-legacy", "hyper-rustls"]
_hyper-client = [
"http-body-util",
"hyper/client",
"hyper-util/client",
"hyper-util/http1",
"hyper-util/client-legacy",
"hyper-rustls",
]

[dependencies]
metrics = { version = "^0.23", path = "../metrics" }
metrics-util = { version = "^0.17", path = "../metrics-util", default-features = false, features = ["recency", "registry", "summary"] }
metrics-util = { version = "^0.17", path = "../metrics-util", default-features = false, features = [
"recency",
"registry",
"summary",
] }
thiserror = { version = "1", default-features = false }
quanta = { version = "0.12", default-features = false }
indexmap = { version = "2.1", default-features = false, features = ["std"] }
base64 = { version = "0.22.0", default-features = false, features = ["std"] }

# Optional
hyper = { version = "1.1", features = [ "server", "client" ], optional = true }
hyper-util = { version="0.1.3", features = [ "tokio", "service", "client", "client-legacy", "http1" ], optional = true }
hyper = { version = "1.1", features = ["server", "client"], optional = true }
hyper-util = { version = "0.1.3", features = [
"tokio",
"service",
"client",
"client-legacy",
"http1",
], optional = true }
http-body-util = { version = "0.1.0", optional = true }
ipnet = { version = "2", optional = true }
tokio = { version = "1", features = ["rt", "net", "time", "rt-multi-thread"], optional = true }
tokio = { version = "1", features = [
"rt",
"net",
"time",
"rt-multi-thread",
], optional = true }
tracing = { version = "0.1.26", optional = true }
hyper-rustls = { version = "0.27.2", optional = true }

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

[[example]]
name = "prometheus_uds_server"
required-features = ["uds-listener"]

[package.metadata.docs.rs]
all-features = true
rustdoc-args = ["--cfg", "docsrs"]
62 changes: 62 additions & 0 deletions metrics-exporter-prometheus/examples/prometheus_uds_server.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
use std::thread;
use std::time::Duration;

use metrics::{counter, describe_counter, describe_histogram, gauge, histogram};
use metrics_exporter_prometheus::PrometheusBuilder;
use metrics_util::MetricKindMask;

use quanta::Clock;
use rand::{thread_rng, Rng};

fn main() {
tracing_subscriber::fmt::init();

let builder = PrometheusBuilder::new().with_http_uds_listener("/tmp/metrics.sock");
builder
.idle_timeout(
MetricKindMask::COUNTER | MetricKindMask::HISTOGRAM,
Some(Duration::from_secs(10)),
)
.install()
.expect("failed to install Prometheus recorder");

// We register these metrics, which gives us a chance to specify a description for them. The
// Prometheus exporter records this description and adds it as HELP text when the endpoint is
// scraped.
//
// Registering metrics ahead of using them is not required, but is the only way to specify the
// description of a metric.
describe_counter!("tcp_server_loops", "The iterations of the TCP server event loop so far.");
describe_histogram!(
"tcp_server_loop_delta_secs",
"The time taken for iterations of the TCP server event loop."
);

let clock = Clock::new();
let mut last = None;

counter!("idle_metric").increment(1);
gauge!("testing").set(42.0);

// Loop over and over, pretending to do some work.
loop {
counter!("tcp_server_loops", "system" => "foo").increment(1);

if let Some(t) = last {
let delta: Duration = clock.now() - t;
histogram!("tcp_server_loop_delta_secs", "system" => "foo").record(delta);
}

let increment_gauge = thread_rng().gen_bool(0.75);
let gauge = gauge!("lucky_iterations");
if increment_gauge {
gauge.increment(1.0);
} else {
gauge.decrement(1.0);
}

last = Some(clock.now());

thread::sleep(Duration::from_millis(750));
}
}
50 changes: 41 additions & 9 deletions metrics-exporter-prometheus/src/exporter/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,10 @@ impl PrometheusBuilder {

#[cfg(feature = "http-listener")]
let exporter_config = ExporterConfig::HttpListener {
listen_address: SocketAddr::new(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)), 9000),
destination: super::ListenDestination::Tcp(SocketAddr::new(
IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)),
9000,
)),
};
#[cfg(not(feature = "http-listener"))]
let exporter_config = ExporterConfig::Unconfigured;
Expand Down Expand Up @@ -93,7 +96,9 @@ impl PrometheusBuilder {
#[cfg_attr(docsrs, doc(cfg(feature = "http-listener")))]
#[must_use]
pub fn with_http_listener(mut self, addr: impl Into<SocketAddr>) -> Self {
self.exporter_config = ExporterConfig::HttpListener { listen_address: addr.into() };
self.exporter_config = ExporterConfig::HttpListener {
destination: super::ListenDestination::Tcp(addr.into()),
};
self
}

Expand Down Expand Up @@ -133,6 +138,27 @@ impl PrometheusBuilder {
Ok(self)
}

/// Configures the exporter to expose an HTTP listener that functions as a [scrape endpoint],
/// listening on a Unix Domain socket at the given path
///
/// The HTTP listener that is spawned will respond to GET requests on any request path.
///
/// Running in HTTP listener mode is mutually exclusive with the push gateway i.e. enabling the
/// HTTP listener will disable the push gateway, and vise versa.
///
/// Defaults to disabled.
///
/// [scrape endpoint]: https://prometheus.io/docs/instrumenting/exposition_formats/#text-based-format
#[cfg(feature = "uds-listener")]
#[cfg_attr(docsrs, doc(cfg(feature = "uds-listener")))]
#[must_use]
pub fn with_http_uds_listener(mut self, addr: impl Into<std::path::PathBuf>) -> Self {
self.exporter_config = ExporterConfig::HttpListener {
destination: super::ListenDestination::Uds(addr.into()),
};
self
}

/// Adds an IP address or subnet to the allowlist for the scrape endpoint.
///
/// If a client makes a request to the scrape endpoint and their IP is not present in the
Expand Down Expand Up @@ -443,13 +469,19 @@ impl PrometheusBuilder {
ExporterConfig::Unconfigured => Err(BuildError::MissingExporterConfiguration)?,

#[cfg(feature = "http-listener")]
ExporterConfig::HttpListener { listen_address } => {
super::http_listener::new_http_listener(
handle,
listen_address,
allowed_addresses,
)?
}
ExporterConfig::HttpListener { destination } => match destination {
super::ListenDestination::Tcp(listen_address) => {
super::http_listener::new_http_listener(
handle,
listen_address,
allowed_addresses,
)?
}
#[cfg(feature = "uds-listener")]
super::ListenDestination::Uds(listen_path) => {
super::http_listener::new_http_uds_listener(handle, listen_path)?
}
},

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

0 comments on commit a65dde9

Please sign in to comment.