Coverage Report

Created: 2025-04-19 16:54

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/build/source/nativelink-util/src/metrics_utils.rs
Line
Count
Source
1
// Copyright 2024 The NativeLink Authors. All rights reserved.
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
15
use std::mem::forget;
16
use std::sync::atomic::{AtomicU64, Ordering};
17
use std::time::{Instant, SystemTime, UNIX_EPOCH};
18
19
use futures::Future;
20
use nativelink_metric::{
21
    MetricFieldData, MetricKind, MetricPublishKnownKindData, MetricsComponent, group, publish,
22
};
23
24
#[derive(Debug, Default)]
25
pub struct FuncCounterWrapper {
26
    pub successes: AtomicU64,
27
    pub failures: AtomicU64,
28
}
29
30
impl FuncCounterWrapper {
31
    #[inline]
32
1
    pub fn wrap<T, E>(&self, func: impl FnOnce() -> Result<T, E>) -> Result<T, E> {
33
1
        let result = (func)();
34
1
        if result.is_ok() {
  Branch (34:12): [True: 1, False: 0]
  Branch (34:12): [Folded - Ignored]
  Branch (34:12): [Folded - Ignored]
35
1
            self.successes.fetch_add(1, Ordering::Acquire);
36
1
        } else {
37
0
            self.failures.fetch_add(1, Ordering::Acquire);
38
0
        }
39
1
        result
40
1
    }
41
}
42
43
// Derive-macros have no way to tell the collector that the parent
44
// is now a group with the name of the group as the field so we
45
// can attach multiple values on the same group, so we need to
46
// manually implement the `MetricsComponent` trait to do so.
47
impl MetricsComponent for FuncCounterWrapper {
48
0
    fn publish(
49
0
        &self,
50
0
        _kind: MetricKind,
51
0
        field_metadata: MetricFieldData,
52
0
    ) -> Result<MetricPublishKnownKindData, nativelink_metric::Error> {
53
0
        let _enter = group!(field_metadata.name).entered();
54
0
55
0
        publish!(
56
0
            "successes",
57
0
            &self.successes,
58
0
            MetricKind::Counter,
59
0
            format!(
60
0
                "The number of times {} was successful.",
61
                field_metadata.name
62
            )
63
        );
64
0
        publish!(
65
0
            "failures",
66
0
            &self.failures,
67
0
            MetricKind::Counter,
68
0
            format!("The number of times {} failed.", field_metadata.name)
69
        );
70
71
0
        Ok(MetricPublishKnownKindData::Component)
72
0
    }
73
}
74
75
/// This is a utility that will only increment the referenced counter when it is dropped.
76
/// This struct is zero cost and has a runtime cost only when it is dropped.
77
/// This struct is very useful for tracking when futures are dropped.
78
#[derive(Debug)]
79
struct DropCounter<'a> {
80
    counter: &'a AtomicU64,
81
}
82
83
impl<'a> DropCounter<'a> {
84
    #[inline]
85
248
    pub(crate) const fn new(counter: &'a AtomicU64) -> Self {
86
248
        Self { counter }
87
248
    }
88
}
89
90
impl Drop for DropCounter<'_> {
91
    #[inline]
92
4
    fn drop(&mut self) {
93
4
        self.counter.fetch_add(1, Ordering::Acquire);
94
4
    }
95
}
96
97
#[derive(Debug)]
98
pub struct AsyncTimer<'a> {
99
    start: Instant,
100
    drop_counter: DropCounter<'a>,
101
    counter: &'a AsyncCounterWrapper,
102
}
103
104
impl AsyncTimer<'_> {
105
    #[inline]
106
0
    pub fn measure(self) {
107
0
        self.counter
108
0
            .sum_func_duration_ns
109
0
            .fetch_add(self.start.elapsed().as_nanos() as u64, Ordering::Acquire);
110
0
        self.counter.calls.fetch_add(1, Ordering::Acquire);
111
0
        self.counter.successes.fetch_add(1, Ordering::Acquire);
112
0
        // This causes DropCounter's drop to never be called.
113
0
        forget(self.drop_counter);
114
0
    }
115
}
116
117
/// Tracks the number of calls, successes, failures, and drops of an async function.
118
/// call `.wrap(future)` to wrap a future and stats about the future are automatically
119
/// tracked and can be published to a `CollectorState`.
120
#[derive(Debug, Default)]
121
pub struct AsyncCounterWrapper {
122
    pub calls: AtomicU64,
123
    pub successes: AtomicU64,
124
    pub failures: AtomicU64,
125
    pub drops: AtomicU64,
126
    // Time spent in nanoseconds in the future.
127
    // 64 bit address space gives ~584 years of nanoseconds.
128
    pub sum_func_duration_ns: AtomicU64,
129
}
130
131
// Derive-macros have no way to tell the collector that the parent
132
// is now a group with the name of the group as the field so we
133
// can attach multiple values on the same group, so we need to
134
// manually implement the `MetricsComponent` trait to do so.
135
impl MetricsComponent for AsyncCounterWrapper {
136
0
    fn publish(
137
0
        &self,
138
0
        _kind: MetricKind,
139
0
        field_metadata: MetricFieldData,
140
0
    ) -> Result<MetricPublishKnownKindData, nativelink_metric::Error> {
141
0
        let _enter = group!(field_metadata.name).entered();
142
0
143
0
        publish!(
144
0
            "calls",
145
0
            &self.calls,
146
0
            MetricKind::Counter,
147
0
            format!("The number of times {} was called.", field_metadata.name)
148
        );
149
0
        publish!(
150
0
            "successes",
151
0
            &self.successes,
152
0
            MetricKind::Counter,
153
0
            format!(
154
0
                "The number of times {} was successful.",
155
                field_metadata.name
156
            )
157
        );
158
0
        publish!(
159
0
            "failures",
160
0
            &self.failures,
161
0
            MetricKind::Counter,
162
0
            format!("The number of times {} failed.", field_metadata.name)
163
        );
164
0
        publish!(
165
0
            "drops",
166
0
            &self.drops,
167
0
            MetricKind::Counter,
168
0
            format!("The number of times {} was dropped.", field_metadata.name)
169
        );
170
0
        publish!(
171
0
            "sum_func_duration_ns",
172
0
            &self.sum_func_duration_ns,
173
0
            MetricKind::Counter,
174
0
            format!(
175
0
                "The sum of the time spent in nanoseconds in {}.",
176
                field_metadata.name
177
            )
178
        );
179
180
0
        Ok(MetricPublishKnownKindData::Component)
181
0
    }
182
}
183
184
impl AsyncCounterWrapper {
185
    #[inline]
186
0
    pub fn wrap_fn<'a, T: 'a, E>(
187
0
        &'a self,
188
0
        func: impl FnOnce() -> Result<T, E> + 'a,
189
0
    ) -> Result<T, E> {
190
0
        self.calls.fetch_add(1, Ordering::Acquire);
191
0
        let result = (func)();
192
0
        if result.is_ok() {
  Branch (192:12): [Folded - Ignored]
  Branch (192:12): [Folded - Ignored]
193
0
            self.successes.fetch_add(1, Ordering::Acquire);
194
0
        } else {
195
0
            self.failures.fetch_add(1, Ordering::Acquire);
196
0
        }
197
0
        result
198
0
    }
199
200
    #[inline]
201
233
    pub async fn wrap<'a, T, E, F: Future<Output = Result<T, E>> + 'a>(
202
233
        &'a self,
203
233
        future: F,
204
233
    ) -> Result<T, E> {
205
233
        let result = self.wrap_no_capture_result(future).await;
206
233
        if result.is_ok() {
  Branch (206:12): [True: 0, False: 0]
  Branch (206:12): [Folded - Ignored]
  Branch (206:12): [True: 3, False: 1]
  Branch (206:12): [Folded - Ignored]
  Branch (206:12): [True: 6, False: 0]
  Branch (206:12): [True: 30, False: 0]
  Branch (206:12): [True: 15, False: 1]
  Branch (206:12): [True: 15, False: 0]
  Branch (206:12): [True: 16, False: 0]
  Branch (206:12): [True: 16, False: 0]
  Branch (206:12): [True: 11, False: 0]
  Branch (206:12): [True: 11, False: 0]
  Branch (206:12): [True: 15, False: 0]
  Branch (206:12): [True: 15, False: 0]
  Branch (206:12): [True: 15, False: 0]
  Branch (206:12): [True: 13, False: 0]
  Branch (206:12): [True: 11, False: 0]
  Branch (206:12): [True: 11, False: 0]
  Branch (206:12): [True: 26, False: 1]
  Branch (206:12): [True: 1, False: 0]
207
230
            self.successes.fetch_add(1, Ordering::Acquire);
208
230
        } else {
209
3
            self.failures.fetch_add(1, Ordering::Acquire);
210
3
        }
211
233
        result
212
233
    }
213
214
    #[inline]
215
235
    pub async fn wrap_no_capture_result<'a, T, F: Future<Output = T> + 'a>(
216
235
        &'a self,
217
235
        future: F,
218
235
    ) -> T {
219
235
        self.calls.fetch_add(1, Ordering::Acquire);
220
235
        let drop_counter = DropCounter::new(&self.drops);
221
235
        let instant = Instant::now();
222
235
        let result = future.await;
223
        // By default `drop_counter` will increment the drop counter when it goes out of scope.
224
        // This will ensure we don't increment the counter if we make it here with a zero cost.
225
235
        forget(drop_counter);
226
235
        self.sum_func_duration_ns
227
235
            .fetch_add(instant.elapsed().as_nanos() as u64, Ordering::Acquire);
228
235
        result
229
235
    }
230
231
    #[inline]
232
13
    pub fn begin_timer(&self) -> AsyncTimer<'_> {
233
13
        AsyncTimer {
234
13
            start: Instant::now(),
235
13
            drop_counter: DropCounter::new(&self.drops),
236
13
            counter: self,
237
13
        }
238
13
    }
239
}
240
241
/// Tracks a number.
242
#[derive(Debug, Default)]
243
pub struct Counter(AtomicU64);
244
245
impl Counter {
246
    #[inline]
247
0
    pub fn inc(&self) {
248
0
        self.add(1);
249
0
    }
250
251
    #[inline]
252
6.00k
    pub fn add(&self, value: u64) {
253
6.00k
        self.0.fetch_add(value, Ordering::Acquire);
254
6.00k
    }
255
256
    #[inline]
257
0
    pub fn sub(&self, value: u64) {
258
0
        self.0.fetch_sub(value, Ordering::Acquire);
259
0
    }
260
}
261
262
impl MetricsComponent for Counter {
263
0
    fn publish(
264
0
        &self,
265
0
        kind: MetricKind,
266
0
        field_metadata: MetricFieldData,
267
0
    ) -> Result<MetricPublishKnownKindData, nativelink_metric::Error> {
268
0
        self.0.publish(kind, field_metadata)
269
0
    }
270
}
271
272
/// Tracks an counter through time and the last time the counter was changed.
273
#[derive(Debug, Default)]
274
pub struct CounterWithTime {
275
    pub counter: AtomicU64,
276
    pub last_time: AtomicU64,
277
}
278
279
impl CounterWithTime {
280
    #[inline]
281
117
    pub fn inc(&self) {
282
117
        self.counter.fetch_add(1, Ordering::Acquire);
283
117
        self.last_time.store(
284
117
            SystemTime::now()
285
117
                .duration_since(UNIX_EPOCH)
286
117
                .unwrap()
287
117
                .as_secs(),
288
117
            Ordering::Release,
289
117
        );
290
117
    }
291
}
292
293
// Derive-macros have no way to tell the collector that the parent
294
// is now a group with the name of the group as the field so we
295
// can attach multiple values on the same group, so we need to
296
// manually implement the `MetricsComponent` trait to do so.
297
impl MetricsComponent for CounterWithTime {
298
0
    fn publish(
299
0
        &self,
300
0
        _kind: MetricKind,
301
0
        field_metadata: MetricFieldData,
302
0
    ) -> Result<MetricPublishKnownKindData, nativelink_metric::Error> {
303
0
        let _enter = group!(field_metadata.name).entered();
304
0
305
0
        publish!(
306
0
            "counter",
307
0
            &self.counter,
308
0
            MetricKind::Counter,
309
0
            format!("Current count of {}.", field_metadata.name)
310
        );
311
0
        publish!(
312
0
            "last_time",
313
0
            &self.last_time,
314
0
            MetricKind::Counter,
315
0
            format!("Last timestamp {} was published.", field_metadata.name)
316
        );
317
318
0
        Ok(MetricPublishKnownKindData::Component)
319
0
    }
320
}