diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index cb8ecec4..3d51fc3b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -165,7 +165,7 @@ jobs: cargo clippy --all-targets --features tokio-console -- -D warnings cargo clippy --all-targets --features deadlock -- -D warnings cargo clippy --all-targets --features tracing -- -D warnings - cargo clippy --all-targets --features prometheus,opentelemetry -- -D warnings + cargo clippy --all-targets --features prometheus,opentelemetry_0_26,opentelemetry_0_27 -- -D warnings cargo clippy --all-targets -- -D warnings - if: steps.cache.outputs.cache-hit != 'true' uses: taiki-e/install-action@cargo-llvm-cov @@ -182,7 +182,7 @@ jobs: RUST_BACKTRACE: 1 CI: true run: | - cargo llvm-cov --no-report nextest --features "strict_assertions,sanity,prometheus,opentelemetry" + cargo llvm-cov --no-report nextest --features "strict_assertions,sanity,prometheus,opentelemetry_0_26,opentelemetry_0_27" - name: Run examples with coverage env: RUST_BACKTRACE: 1 diff --git a/Cargo.toml b/Cargo.toml index e42e2a47..62838d1d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -47,7 +47,8 @@ tokio = { package = "madsim-tokio", version = "0.2", features = [ "fs", ] } prometheus = "0.13" -opentelemetry = "0.27" +opentelemetry_0_27 = { package = "opentelemetry", version = "0.27" } +opentelemetry_0_26 = { package = "opentelemetry", version = "0.26" } # foyer components foyer-common = { version = "0.13.0-dev", path = "foyer-common" } diff --git a/Makefile b/Makefile index 40c3a225..67714e85 100644 --- a/Makefile +++ b/Makefile @@ -10,7 +10,7 @@ check: ./scripts/minimize-dashboards.sh cargo sort -w cargo fmt --all - cargo clippy --all-targets --features prometheus,opentelemetry + cargo clippy --all-targets --features prometheus,opentelemetry_0_26,opentelemetry_0_27 check-all: shellcheck ./scripts/* @@ -21,11 +21,11 @@ check-all: cargo clippy --all-targets --features tokio-console cargo clippy --all-targets --features sanity cargo clippy --all-targets --features tracing - cargo clippy --all-targets --features prometheus,opentelemetry + cargo clippy --all-targets --features prometheus,opentelemetry_0_26,opentelemetry_0_27 cargo clippy --all-targets test: - RUST_BACKTRACE=1 cargo nextest run --all --features "strict_assertions,sanity,prometheus,opentelemetry" + RUST_BACKTRACE=1 cargo nextest run --all --features "strict_assertions,sanity,prometheus,opentelemetry_0_26,opentelemetry_0_27" RUST_BACKTRACE=1 cargo test --doc test-ignored: diff --git a/foyer-common/Cargo.toml b/foyer-common/Cargo.toml index 4c6a7e38..6c6b3dfa 100644 --- a/foyer-common/Cargo.toml +++ b/foyer-common/Cargo.toml @@ -19,7 +19,8 @@ fastrace = { workspace = true } futures = "0.3" hashbrown = { workspace = true } itertools = { workspace = true } -opentelemetry = { workspace = true, optional = true } +opentelemetry_0_26 = { workspace = true, optional = true } +opentelemetry_0_27 = { workspace = true, optional = true } parking_lot = { workspace = true } pin-project = "1" prometheus = { workspace = true, optional = true } @@ -34,7 +35,9 @@ rand = "0.8.5" strict_assertions = [] tracing = ["fastrace/enable"] prometheus = ["dep:prometheus"] -opentelemetry = ["dep:opentelemetry"] +opentelemetry = ["opentelemetry_0_27"] +opentelemetry_0_27 = ["dep:opentelemetry_0_27"] +opentelemetry_0_26 = ["dep:opentelemetry_0_26"] [lints] workspace = true diff --git a/foyer-common/src/metrics/model.rs b/foyer-common/src/metrics/model.rs index 3db3f536..233f03a6 100644 --- a/foyer-common/src/metrics/model.rs +++ b/foyer-common/src/metrics/model.rs @@ -366,11 +366,23 @@ mod tests { case(&PrometheusMetricsRegistry::new(prometheus::Registry::new())); } - #[cfg(feature = "opentelemetry")] + #[cfg(feature = "opentelemetry_0_27")] #[test] - fn test_metrics_opentelemetry() { - use crate::metrics::registry::opentelemetry::OpenTelemetryMetricsRegistry; + fn test_metrics_opentelemetry_0_27() { + use crate::metrics::registry::opentelemetry_0_27::OpenTelemetryMetricsRegistry; - case(&OpenTelemetryMetricsRegistry::new(opentelemetry::global::meter("test"))); + case(&OpenTelemetryMetricsRegistry::new(opentelemetry_0_27::global::meter( + "test", + ))); + } + + #[cfg(feature = "opentelemetry_0_26")] + #[test] + fn test_metrics_opentelemetry_0_26() { + use crate::metrics::registry::opentelemetry_0_26::OpenTelemetryMetricsRegistry; + + case(&OpenTelemetryMetricsRegistry::new(opentelemetry_0_26::global::meter( + "test", + ))); } } diff --git a/foyer-common/src/metrics/registry/mod.rs b/foyer-common/src/metrics/registry/mod.rs index 840f7fde..a88fecc5 100644 --- a/foyer-common/src/metrics/registry/mod.rs +++ b/foyer-common/src/metrics/registry/mod.rs @@ -14,9 +14,18 @@ /// Some phantom metrics components that do nothing. pub mod noop; -/// OpenTelemetry metrics components. -#[cfg(feature = "opentelemetry")] -pub mod opentelemetry; + /// Prometheus metrics components. #[cfg(feature = "prometheus")] pub mod prometheus; + +#[cfg(feature = "opentelemetry")] +pub use opentelemetry_0_27 as opentelemetry; + +/// OpenTelemetry metrics components. +#[cfg(feature = "opentelemetry_0_27")] +pub mod opentelemetry_0_27; + +/// OpenTelemetry metrics components. +#[cfg(feature = "opentelemetry_0_26")] +pub mod opentelemetry_0_26; diff --git a/foyer-common/src/metrics/registry/opentelemetry_0_26.rs b/foyer-common/src/metrics/registry/opentelemetry_0_26.rs new file mode 100644 index 00000000..e4374c16 --- /dev/null +++ b/foyer-common/src/metrics/registry/opentelemetry_0_26.rs @@ -0,0 +1,209 @@ +// Copyright 2024 foyer Project Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use std::sync::atomic::{AtomicU64, Ordering}; + +use itertools::Itertools; +use opentelemetry::{ + metrics::{Counter as OtCounter, Gauge as OtGauge, Histogram as OtHistogram, Meter}, + KeyValue, +}; +use opentelemetry_0_26 as opentelemetry; + +use crate::metrics::{CounterOps, CounterVecOps, GaugeOps, GaugeVecOps, HistogramOps, HistogramVecOps, RegistryOps}; + +/// OpenTelemetry counter metric. +#[derive(Debug)] +pub struct Counter { + counter: OtCounter, + labels: Vec, +} + +impl CounterOps for Counter { + fn increase(&self, val: u64) { + self.counter.add(val, &self.labels); + } +} + +/// OpenTelemetry gauge metric. +#[derive(Debug)] +pub struct Gauge { + val: AtomicU64, + gauge: OtGauge, + labels: Vec, +} + +impl GaugeOps for Gauge { + fn increase(&self, val: u64) { + let v = self.val.fetch_add(val, Ordering::Relaxed) + val; + self.gauge.record(v, &self.labels); + } + + fn decrease(&self, val: u64) { + let v = self.val.fetch_sub(val, Ordering::Relaxed) - val; + self.gauge.record(v, &self.labels); + } + + fn absolute(&self, val: u64) { + self.val.store(val, Ordering::Relaxed); + self.gauge.record(val, &self.labels); + } +} + +/// OpenTelemetry histogram metric. +#[derive(Debug)] +pub struct Histogram { + histogram: OtHistogram, + labels: Vec, +} + +impl HistogramOps for Histogram { + fn record(&self, val: f64) { + self.histogram.record(val, &self.labels); + } +} + +/// OpenTelemetry metric vector. +#[derive(Debug)] +pub struct MetricVec { + meter: Meter, + name: &'static str, + desc: &'static str, + label_names: &'static [&'static str], +} + +impl CounterVecOps for MetricVec { + fn counter(&self, labels: &[&str]) -> impl CounterOps { + let counter = self.meter.u64_counter(self.name).with_description(self.desc).init(); + let labels = self + .label_names + .iter() + .zip_eq(labels.iter()) + .map(|(name, label)| KeyValue::new(name.to_string(), label.to_string())) + .collect(); + Counter { counter, labels } + } +} + +impl GaugeVecOps for MetricVec { + fn gauge(&self, labels: &[&str]) -> impl GaugeOps { + let gauge = self.meter.u64_gauge(self.name).with_description(self.desc).init(); + let labels = self + .label_names + .iter() + .zip_eq(labels.iter()) + .map(|(name, label)| KeyValue::new(name.to_string(), label.to_string())) + .collect(); + let val = AtomicU64::new(0); + Gauge { val, gauge, labels } + } +} + +impl HistogramVecOps for MetricVec { + fn histogram(&self, labels: &[&str]) -> impl HistogramOps { + let histogram = self.meter.f64_histogram(self.name).with_description(self.desc).init(); + let labels = self + .label_names + .iter() + .zip_eq(labels.iter()) + .map(|(name, label)| KeyValue::new(name.to_string(), label.to_string())) + .collect(); + Histogram { histogram, labels } + } +} + +/// OpenTelemetry metrics registry. +#[derive(Debug)] +pub struct OpenTelemetryMetricsRegistry { + meter: Meter, +} + +impl OpenTelemetryMetricsRegistry { + /// Create an OpenTelemetry metrics registry. + pub fn new(meter: Meter) -> Self { + Self { meter } + } +} + +impl RegistryOps for OpenTelemetryMetricsRegistry { + fn register_counter_vec( + &self, + name: &'static str, + desc: &'static str, + label_names: &'static [&'static str], + ) -> impl CounterVecOps { + let meter = self.meter.clone(); + MetricVec { + meter, + name, + desc, + label_names, + } + } + + fn register_gauge_vec( + &self, + name: &'static str, + desc: &'static str, + label_names: &'static [&'static str], + ) -> impl GaugeVecOps { + let meter = self.meter.clone(); + MetricVec { + meter, + name, + desc, + label_names, + } + } + + fn register_histogram_vec( + &self, + name: &'static str, + desc: &'static str, + label_names: &'static [&'static str], + ) -> impl HistogramVecOps { + let meter = self.meter.clone(); + MetricVec { + meter, + name, + desc, + label_names, + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test() { + let meter = opentelemetry::global::meter("test"); + let ot = OpenTelemetryMetricsRegistry::new(meter); + + let cv = ot.register_counter_vec("test_counter_1", "test counter 1", &["label1", "label2"]); + let c = cv.counter(&["l1", "l2"]); + c.increase(42); + + let gv = ot.register_gauge_vec("test_gauge_1", "test gauge 1", &["label1", "label2"]); + let g = gv.gauge(&["l1", "l2"]); + g.increase(514); + g.decrease(114); + g.absolute(114514); + + let hv = ot.register_histogram_vec("test_histogram_1", "test histogram 1", &["label1", "label2"]); + let h = hv.histogram(&["l1", "l2"]); + h.record(114.514); + } +} diff --git a/foyer-common/src/metrics/registry/opentelemetry.rs b/foyer-common/src/metrics/registry/opentelemetry_0_27.rs similarity index 99% rename from foyer-common/src/metrics/registry/opentelemetry.rs rename to foyer-common/src/metrics/registry/opentelemetry_0_27.rs index 349d0802..99ae98c1 100644 --- a/foyer-common/src/metrics/registry/opentelemetry.rs +++ b/foyer-common/src/metrics/registry/opentelemetry_0_27.rs @@ -19,6 +19,7 @@ use opentelemetry::{ metrics::{Counter as OtCounter, Gauge as OtGauge, Histogram as OtHistogram, Meter}, KeyValue, }; +use opentelemetry_0_27 as opentelemetry; use crate::metrics::{CounterOps, CounterVecOps, GaugeOps, GaugeVecOps, HistogramOps, HistogramVecOps, RegistryOps}; diff --git a/foyer/Cargo.toml b/foyer/Cargo.toml index f08108a3..cb831682 100644 --- a/foyer/Cargo.toml +++ b/foyer/Cargo.toml @@ -47,6 +47,8 @@ tracing = [ ] prometheus = ["foyer-common/prometheus"] opentelemetry = ["foyer-common/opentelemetry"] +opentelemetry_0_27 = ["foyer-common/opentelemetry_0_27"] +opentelemetry_0_26 = ["foyer-common/opentelemetry_0_26"] [lints] workspace = true