Skip to main content

common_cache/
metrics.rs

1// Copyright 2023 Greptime Team
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use std::sync::{Arc, Mutex, Weak};
16use std::time::Instant;
17
18use prometheus::core::{Collector, Desc};
19use prometheus::proto::{Gauge, MetricFamily};
20use prometheus::{Histogram, IntCounter, IntGauge, IntGaugeVec, Opts};
21
22/// Caller-bound metrics for native cache loading. No collectors are registered here.
23/// Requests count only `try_get_with` calls, including its cache hits and callers sharing
24/// another request's load. Plain `get` calls are not counted. Loads count
25/// only loader futures actually polled by the backend; their difference is not a waiter count.
26#[derive(Clone)]
27pub struct CacheMetrics {
28    pub requests: OperationMetrics,
29    pub loads: OperationMetrics,
30}
31
32/// Metrics for one operation, with labels bound once when constructing a cache.
33/// Completion counters are mutually exclusive. Dropping an unfinished future is cancellation.
34#[derive(Clone)]
35pub struct OperationMetrics {
36    pub started: IntCounter,
37    pub succeeded: IntCounter,
38    pub failed: IntCounter,
39    pub cancelled: IntCounter,
40    pub active: IntGauge,
41    /// Wall-clock lifetime, including suspended time, for every outcome.
42    pub elapsed: Histogram,
43}
44
45impl OperationMetrics {
46    /// Starts tracking an operation and its elapsed time.
47    pub(crate) fn start(&self) -> OperationGuard<'_> {
48        self.started.inc();
49        self.active.inc();
50        OperationGuard {
51            metrics: self,
52            started: Instant::now(),
53            completed: false,
54        }
55    }
56}
57
58/// Tracks an operation, recording cancellation if dropped before completion.
59pub(crate) struct OperationGuard<'a> {
60    metrics: &'a OperationMetrics,
61    started: Instant,
62    completed: bool,
63}
64
65impl OperationGuard<'_> {
66    /// Records success or failure and completes the operation.
67    pub(crate) fn finish<T, E>(mut self, result: &Result<T, E>) {
68        if result.is_ok() {
69            self.metrics.succeeded.inc();
70        } else {
71            self.metrics.failed.inc();
72        }
73        self.completed = true;
74    }
75}
76
77impl Drop for OperationGuard<'_> {
78    fn drop(&mut self) {
79        if !self.completed {
80            self.metrics.cancelled.inc();
81        }
82        self.metrics.active.dec();
83        self.metrics
84            .elapsed
85            .observe(self.started.elapsed().as_secs_f64());
86    }
87}
88
89/// Cache byte metrics combining manually maintained gauges and sampled cache weights.
90/// Sampling does not force backend maintenance or include pending loader values.
91#[derive(Clone)]
92pub struct CacheSizeMetrics {
93    gauges: IntGaugeVec,
94    sources: Arc<Mutex<Vec<CacheSizeSource>>>,
95}
96
97/// A byte metric with labels bound for one or more cache instances.
98#[derive(Clone)]
99pub struct CacheSizeMetric {
100    metrics: CacheSizeMetrics,
101    gauge: IntGauge,
102}
103
104/// Keeps a cache instance registered for size sampling while any owner remains.
105#[derive(Clone)]
106pub struct CacheSizeRegistration {
107    _reader: Arc<dyn Fn() -> u64 + Send + Sync>,
108}
109
110struct CacheSizeSource {
111    gauge: IntGauge,
112    read: Weak<dyn Fn() -> u64 + Send + Sync>,
113}
114
115impl CacheSizeMetrics {
116    /// Creates an unregistered collector. Register a clone with the caller's registry.
117    pub fn new(opts: Opts, labels: &[&str]) -> prometheus::Result<Self> {
118        Ok(Self {
119            gauges: IntGaugeVec::new(opts, labels)?,
120            sources: Arc::new(Mutex::new(Vec::new())),
121        })
122    }
123
124    /// Returns the backing gauges for callers retaining manual accounting.
125    pub fn gauges(&self) -> &IntGaugeVec {
126        &self.gauges
127    }
128
129    /// Returns a manually maintained gauge for existing non-cache accounting.
130    pub fn with_label_values(&self, labels: &[&str]) -> IntGauge {
131        self.gauges.with_label_values(labels)
132    }
133
134    /// Binds labels for sampled cache weights. Instances sharing labels are summed.
135    pub fn bind(&self, labels: &[&str]) -> CacheSizeMetric {
136        CacheSizeMetric {
137            metrics: self.clone(),
138            gauge: self.gauges.with_label_values(labels),
139        }
140    }
141}
142
143impl CacheSizeMetric {
144    /// Registers a cache-level weight reader without retaining it in the collector.
145    /// Keep the returned registration alongside the cache, including in its clones.
146    pub fn register(
147        &self,
148        read: impl Fn() -> u64 + Send + Sync + 'static,
149    ) -> CacheSizeRegistration {
150        let read: Arc<dyn Fn() -> u64 + Send + Sync> = Arc::new(read);
151        self.metrics
152            .sources
153            .lock()
154            .unwrap_or_else(|e| e.into_inner())
155            .push(CacheSizeSource {
156                gauge: self.gauge.clone(),
157                read: Arc::downgrade(&read),
158            });
159        CacheSizeRegistration { _reader: read }
160    }
161}
162
163impl Collector for CacheSizeMetrics {
164    fn desc(&self) -> Vec<&Desc> {
165        self.gauges.desc()
166    }
167
168    fn collect(&self) -> Vec<MetricFamily> {
169        let mut families = self.gauges.collect();
170        let mut sources = self.sources.lock().unwrap_or_else(|e| e.into_inner());
171        sources.retain(|source| {
172            let Some(read) = source.read.upgrade() else {
173                return false;
174            };
175            let labels = source.gauge.collect();
176            for family in &mut families {
177                for metric in family.mut_metric() {
178                    if metric.get_label() == labels[0].get_metric()[0].get_label() {
179                        let bytes = metric
180                            .get_gauge()
181                            .as_ref()
182                            .map_or(0.0, |gauge| gauge.value())
183                            + read() as f64;
184                        let mut gauge = Gauge::default();
185                        gauge.set_value(bytes);
186                        metric.set_gauge(gauge);
187                    }
188                }
189            }
190            true
191        });
192        families
193    }
194}