-
Notifications
You must be signed in to change notification settings - Fork 25
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Metrics exporter to Prometheus with OTLP #1438
Changes from 11 commits
b57aa09
92329b5
397c390
68a42b6
a8235de
3d986b4
dcbbf51
35fbb2e
ec5e5e4
92c3a28
d49a6da
8076f04
5d7c689
76c6652
7e1cb2b
dd2a174
822c659
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
use axum::{routing::get, Extension, Router}; | ||
use hyper::StatusCode; | ||
|
||
use crate::{ | ||
helpers::{routing::RouteId, BodyStream}, | ||
net::{ | ||
http_serde::{self}, | ||
Error, MpcHttpTransport, | ||
}, | ||
}; | ||
|
||
/// Takes details from the HTTP request and creates a `[TransportCommand]::CreateQuery` that is sent | ||
/// to the [`HttpTransport`]. | ||
async fn handler(transport: Extension<MpcHttpTransport>) -> Result<Vec<u8>, Error> { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd encourage to write some tests here as it is often hard to figure out why HTTP server is not responding or responds with errors. There are some examples in this folder that exercise happy path and error path |
||
match transport | ||
.dispatch(RouteId::Metrics, BodyStream::empty()) | ||
.await | ||
{ | ||
Ok(resp) => Ok(resp.into_body()), | ||
Err(err) => Err(Error::application(StatusCode::INTERNAL_SERVER_ERROR, err)), | ||
} | ||
} | ||
|
||
pub fn router(transport: MpcHttpTransport) -> Router { | ||
Router::new() | ||
.route(http_serde::metrics::AXUM_PATH, get(handler)) | ||
.layer(Extension(transport)) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
[package] | ||
name = "ipa-metrics-prometheus" | ||
version = "0.1.0" | ||
edition = "2021" | ||
|
||
[features] | ||
default = [] | ||
|
||
[dependencies] | ||
ipa-metrics = { path = "../ipa-metrics" } | ||
|
||
# Open telemetry crates: opentelemetry-prometheus crate implementation is based on Opentelemetry API and SDK 0.23. (TBC) | ||
opentelemetry = "0.24" | ||
opentelemetry_sdk = { version = "0.24", features = ["metrics", "rt-tokio"] } | ||
opentelemetry-prometheus = { version = "0.17" } | ||
prometheus = "0.13.3" |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
use std::io; | ||
|
||
use ipa_metrics::MetricsStore; | ||
use opentelemetry::{metrics::MeterProvider, KeyValue}; | ||
use opentelemetry_sdk::metrics::SdkMeterProvider; | ||
use prometheus::{self, Encoder, TextEncoder}; | ||
|
||
pub trait PrometheusMetricsExporter { | ||
fn export<W: io::Write>(&mut self, w: &mut W); | ||
} | ||
|
||
impl PrometheusMetricsExporter for MetricsStore { | ||
fn export<W: io::Write>(&mut self, w: &mut W) { | ||
// Setup prometheus registry and open-telemetry exporter | ||
let registry = prometheus::Registry::new(); | ||
|
||
let exporter = opentelemetry_prometheus::exporter() | ||
.with_registry(registry.clone()) | ||
.build() | ||
.unwrap(); | ||
|
||
let meter_provider = SdkMeterProvider::builder().with_reader(exporter).build(); | ||
|
||
// Convert the snapshot to otel struct | ||
// TODO : We need to define a proper scope for the metrics | ||
let meter = meter_provider.meter("ipa-helper"); | ||
|
||
let counters = self.counters(); | ||
counters.for_each(|(counter_name, counter_value)| { | ||
let otlp_counter = meter.u64_counter(counter_name.key).init(); | ||
|
||
let attributes: Vec<KeyValue> = counter_name | ||
.labels() | ||
.map(|l| KeyValue::new(l.name, l.val.to_string())) | ||
.collect(); | ||
|
||
otlp_counter.add(counter_value, &attributes[..]); | ||
}); | ||
|
||
let encoder = TextEncoder::new(); | ||
let metric_families = registry.gather(); | ||
// TODO: Handle error? | ||
encoder.encode(&metric_families, w).unwrap(); | ||
} | ||
} | ||
|
||
#[cfg(test)] | ||
mod test { | ||
|
||
use std::thread; | ||
|
||
use ipa_metrics::{counter, install_new_thread, MetricChannelType}; | ||
|
||
use super::PrometheusMetricsExporter; | ||
|
||
#[test] | ||
fn export_to_prometheus() { | ||
let (producer, controller, _) = install_new_thread(MetricChannelType::Rendezvous).unwrap(); | ||
|
||
thread::spawn(move || { | ||
producer.install(); | ||
counter!("baz", 4); | ||
counter!("bar", 1); | ||
let _ = producer.drop_handle(); | ||
}) | ||
.join() | ||
.unwrap(); | ||
|
||
let mut store = controller.snapshot().unwrap(); | ||
|
||
let mut buff = Vec::new(); | ||
store.export(&mut buff); | ||
|
||
let result = String::from_utf8(buff).unwrap(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It would be good to validate the results somehow. Also consider removing the println. |
||
println!("Export to Prometheus: {result}"); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
mod exporter; | ||
|
||
pub use exporter::PrometheusMetricsExporter; |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -17,4 +17,3 @@ hashbrown = "0.15" | |
rustc-hash = "2.0.0" | ||
# logging | ||
tracing = "0.1" | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: Does this handler belong inside the query module? You can perhaps park it alongside
echo