// SPDX-License-Identifier: Apache-2.0 // SPDX-FileCopyrightText: Copyright The LanceDB Authors //! Python-facing wrappers over [`lancedb::metrics_otel`]. //! //! The aggregation, catalog, and histogram bucketing all live in the LanceDB //! core crate; this module only converts the core snapshot types into PyO3 //! classes and exposes the three entry points to Python, where //! `lancedb/otel.py` bridges them into the user's OpenTelemetry `MeterProvider`. use std::collections::HashMap; use lancedb::metrics_otel::{MetricPoint, MetricValue}; use pyo3::prelude::*; /// One metric data point exposed to Python. For counters and gauges only /// `value` is set; for histograms `buckets` (cumulative `le` counts), `count`, /// and `sum` are set. #[pyclass(name = "MetricPoint", get_all)] pub struct PyMetricPoint { name: String, kind: String, attributes: HashMap, value: Option, buckets: Option>, count: Option, sum: Option, } impl From for PyMetricPoint { fn from(point: MetricPoint) -> Self { let kind = point.kind.as_str().to_string(); let (value, buckets, count, sum) = match point.value { MetricValue::Scalar(v) => (Some(v), None, None, None), MetricValue::Histogram { buckets, count, sum, } => (None, Some(buckets), Some(count), Some(sum)), }; Self { name: point.name, kind, attributes: point.attributes, value, buckets, count, sum, } } } /// A described metric, used by the Python layer to create instruments up front. #[pyclass(name = "MetricDescription", get_all)] pub struct PyMetricDescription { name: String, kind: String, unit: Option, description: String, } /// Install the LanceDB metrics recorder as the process-global `metrics` recorder. /// /// Returns `True` if the recorder is installed (now or previously). Returns /// `False` if a *different* recorder is already installed — `metrics` allows /// only one global recorder per process, so LanceDB cannot coexist with another. #[pyfunction] pub fn register_lancedb_metrics_recorder() -> bool { lancedb::metrics_otel::register_metrics_recorder() } /// The catalog of described LanceDB metrics. Empty until the recorder is installed. #[pyfunction] pub fn lancedb_metrics_catalog() -> Vec { lancedb::metrics_otel::metrics_catalog() .into_iter() .map(|desc| PyMetricDescription { name: desc.name, kind: desc.kind.as_str().to_string(), unit: desc.unit, description: desc.description, }) .collect() } /// A point-in-time snapshot of every recorded metric. Empty until the recorder /// is installed. /// /// The read is lock-free but not O(1): it walks every registered series and /// allocates owned copies of their names and labels. The GIL is released across /// that work so a periodic collection doesn't stall other Python threads. #[pyfunction] pub fn snapshot_lancedb_metrics(py: Python<'_>) -> Vec { let points = py.detach(lancedb::metrics_otel::snapshot_metrics); points.into_iter().map(PyMetricPoint::from).collect() }