metrics-rs / metrics

A metrics ecosystem for Rust.
MIT License
1.13k stars 158 forks source link

OpenTelemetry exporter? #275

Open ericsampson opened 2 years ago

ericsampson commented 2 years ago

Hi! Has anyone discussed/worked on an OTel Metrics compliant exporter?

Cheers! Eric

tobz commented 2 years ago

There's been limited discussion a while back about it: https://github.com/metrics-rs/metrics/issues/19.

It's not an item I'd personally spend time, but I'm definitely open to any PRs adding support for it. 👍🏻

ericsampson commented 2 years ago

ok thanks! the OpenTelemetry metrics spec has stabilized by now so it should be doable.

Not sure if and when i’ll have time, but i just wanted to make sure not to be duplicating work with someone. cheers!

AdriaanPrinsloo commented 1 year ago

Hi, I'm interested in this functionality as well. Does anybody know if this has gotten any traction?

ericsampson commented 1 year ago

@AdriaanPrinsloo if you have time to work on it, we could perhaps work together.

ewoolsey commented 9 months ago

So, I gave this a quick shot. I'll post below, but long story short it seems impossible with the current opentelemetry api.

  1. Not all metrics trait methods for counter, guage, etc. are possible to implement with opentelemetry, so you would have to ignore calls like increment on the otel Guage.
  2. It's impossible in otel to describe a guage after it's been innitialized, something that's required in metrics.

Here's my prototype.

use std::{
    collections::BTreeMap,
    sync::{Arc, Mutex},
};

use metrics::{CounterFn, GaugeFn, Key, KeyName, Metadata, Recorder, SharedString, Unit};
use opentelemetry::{
    metrics::{Meter, MeterProvider as _},
    KeyValue,
};
use opentelemetry_sdk::metrics::SdkMeterProvider;

pub struct OtelRecorder {
    meter_provider: SdkMeterProvider,
    meters: Mutex<BTreeMap<String, Meter>>,
    counters: Mutex<BTreeMap<String, Arc<OtelCounter>>>,
}

struct OtelCounter {
    counter: opentelemetry::metrics::Counter<u64>,
    attributes: Vec<KeyValue>,
}

impl CounterFn for OtelCounter {
    fn increment(&self, value: u64) {
        self.counter.add(value, &self.attributes);
    }

    fn absolute(&self, _value: u64) {
        panic!("Absolute not supported");
    }
}

struct OtelGuage {
    guage: opentelemetry::metrics::Gauge<f64>,
    attributes: Vec<KeyValue>,
}

impl GaugeFn for OtelGuage {
    fn increment(&self, _value: f64) {
        panic!("increment not supported");
    }

    fn decrement(&self, _value: f64) {
        panic!("decrement not supported");
    }

    fn set(&self, value: f64) {
        self.guage.record(value, &self.attributes);
    }
}

impl OtelRecorder {
    fn get_meter(&self, metadata: &Metadata<'_>) -> Meter {
        // First get the module path
        let module_path = metadata.target().to_string();
        // Check to see if the meter already exists
        let mut meters = self.meters.lock().unwrap();
        match meters.get(&module_path) {
            Some(meter) => meter.clone(),
            None => {
                // Create a new meter
                let meter = self.meter_provider.meter(module_path.clone());
                meters.insert(module_path.to_string(), meter.clone());
                meter
            }
        }
    }
}

impl Recorder for OtelRecorder {
    fn describe_counter(&self, key_name: KeyName, unit: Option<Unit>, description: SharedString) {
        todo!()
    }

    fn describe_gauge(&self, key_name: KeyName, unit: Option<Unit>, description: SharedString) {
        todo!()
    }

    fn describe_histogram(&self, key_name: KeyName, unit: Option<Unit>, description: SharedString) {
        todo!()
    }

    fn register_counter(&self, key: &Key, metadata: &Metadata<'_>) -> metrics::Counter {
        // let observer
        let meter = self.get_meter(metadata);
        let counter = meter.u64_counter(key.name().to_string()).init();
        let otel_counter = Arc::new(OtelCounter {
            counter,
            attributes: key
                .labels()
                .map(|label| KeyValue::new(label.key().to_string(), label.value().to_string()))
                .collect(),
        });
        self.counters
            .lock()
            .unwrap()
            .insert(key.name().to_string(), otel_counter.clone());
        metrics::Counter::from_arc(otel_counter)
    }

    fn register_gauge(&self, key: &Key, _metadata: &Metadata<'_>) -> metrics::Gauge {
        todo!()
    }

    fn register_histogram(&self, key: &Key, _metadata: &Metadata<'_>) -> metrics::Histogram {
        todo!()
    }
}