1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under both the MIT license found in the
 * LICENSE-MIT file in the root directory of this source tree and the Apache
 * License, Version 2.0 found in the LICENSE-APACHE file in the root directory
 * of this source tree.
 */

use auto_impl::auto_impl;
use fbinit::FacebookInit;

pub type BoxSingletonCounter = Box<dyn SingletonCounter + Send + Sync>;
pub type BoxCounter = Box<dyn Counter + Send + Sync>;
pub type BoxTimeseries = Box<dyn Timeseries + Send + Sync>;
pub type BoxHistogram = Box<dyn Histogram + Send + Sync>;
pub type BoxLocalCounter = Box<dyn Counter>;
pub type BoxLocalTimeseries = Box<dyn Timeseries>;
pub type BoxLocalHistogram = Box<dyn Histogram>;

/// SingletonCounter is a non-aggregated, global counter. Use this if you don't want any aggregation,
/// and just want to expose a value through stats.
#[auto_impl(Box)]
pub trait SingletonCounter {
    /// Sets the value of the counter
    fn set_value(&self, fb: FacebookInit, value: i64);

    /// Increment the value of the counter
    fn increment_value(&self, fb: FacebookInit, value: i64);

    /// Gets the current value of the counter
    fn get_value(&self, fb: FacebookInit) -> Option<i64>;
}

/// Counter is the simplest type of aggregated stat, it behaves as a single number that can be
/// incremented.
#[auto_impl(Box)]
pub trait Counter {
    /// Increments the counter by the given amount.
    fn increment_value(&self, value: i64);
}

/// Timeseries is a type of stat that can aggregate data send to it into
/// predefined intervals of time. Example aggregations are average, sum or rate.
#[auto_impl(Box)]
pub trait Timeseries {
    /// Adds value to the timeseries. It is being aggregated based on ExportType
    fn add_value(&self, value: i64);

    /// You might want to call this method when you have a very hot counter to avoid some
    /// congestions on it.
    /// Value is the sum of values of the samples and nsamples is the number of samples.
    /// Please notice that difference in the value semantic compared to
    /// `Histogram::add_repeated_value`.
    fn add_value_aggregated(&self, value: i64, nsamples: u32);
}

/// Histogram is a type of stat that can aggregate data send to it into
/// predefined buckets. Example aggregations are average, sum or P50 (percentile).
/// The aggregation should also happen on an interval basis, since its rarely
/// useful to see aggregated all-time stats of a service running for many days.
#[auto_impl(Box)]
pub trait Histogram {
    /// Adds value to the histogram. It is being aggregated based on ExportType
    fn add_value(&self, value: i64);

    /// You might want to call this method when you have a very hot counter to avoid some
    /// congestions on it. The default implementation simply calls add_value O(nsamples) times.
    /// If you have a performance-sensitive use case, check whether your Stats type has an O(1)
    /// implementation.
    /// Value is the value of a single samples and nsamples is the number of samples.
    /// Please notice that difference in the value semantic compared to
    /// `Timeseries::add_value_aggregated`.
    fn add_repeated_value(&self, value: i64, nsamples: u32) {
        for _ in 0..nsamples {
            self.add_value(value);
        }
    }

    /// Flush any buffered data so that it is observable externally. Should only
    /// be used for testing.
    fn flush(&self) {}
}

mod localkey_impls {
    use std::thread::LocalKey;

    use super::*;

    pub trait CounterStatic {
        fn increment_value(&'static self, value: i64);
    }

    impl<T: Counter> CounterStatic for LocalKey<T> {
        fn increment_value(&'static self, value: i64) {
            self.with(|s| T::increment_value(s, value));
        }
    }

    pub trait TimeseriesStatic {
        fn add_value(&'static self, value: i64);
        fn add_value_aggregated(&'static self, value: i64, nsamples: u32);
    }

    impl<T: Timeseries> TimeseriesStatic for LocalKey<T> {
        fn add_value(&'static self, value: i64) {
            self.with(|s| s.add_value(value));
        }

        fn add_value_aggregated(&'static self, value: i64, nsamples: u32) {
            self.with(|s| s.add_value_aggregated(value, nsamples));
        }
    }

    pub trait HistogramStatic {
        fn add_value(&'static self, value: i64);
        fn add_repeated_value(&'static self, value: i64, nsamples: u32);
    }

    impl<T: Histogram> HistogramStatic for LocalKey<T> {
        fn add_value(&'static self, value: i64) {
            self.with(|s| s.add_value(value));
        }

        fn add_repeated_value(&'static self, value: i64, nsamples: u32) {
            self.with(|s| s.add_repeated_value(value, nsamples));
        }
    }
}
pub use localkey_impls::*;

#[cfg(test)]
mod test {
    use super::*;

    #[test]
    fn test_add_repeated_value() {
        // Arrange
        struct DummyHistogram {
            n_added: std::sync::atomic::AtomicU32,
        }

        impl Histogram for DummyHistogram {
            fn add_value(&self, _value: i64) {
                self.n_added
                    .fetch_add(1, std::sync::atomic::Ordering::AcqRel);
            }
        }
        let dummy_histogram = DummyHistogram {
            n_added: std::sync::atomic::AtomicU32::new(0),
        };
        let n_to_add: u32 = 3;
        // Act
        dummy_histogram.add_repeated_value(0, n_to_add);
        // Assert
        assert_eq!(dummy_histogram.n_added.into_inner(), n_to_add);
    }
}