Skip to main content

rust_rocksdb/
statistics.rs

1use crate::ffi;
2use libc::c_int;
3
4#[derive(Debug, Clone)]
5pub struct NameParseError;
6impl core::fmt::Display for NameParseError {
7    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
8        write!(f, "unrecognized name")
9    }
10}
11
12impl std::error::Error for NameParseError {}
13
14// Helper macro to generate iterable nums that translate into static strings mapped from the cpp
15// land.
16macro_rules! iterable_named_enum {
17    (
18    $(#[$m:meta])*
19    $type_vis:vis enum $typename:ident {
20        $(
21            $(#[$variant_meta:meta])*
22            $variant:ident($variant_str:literal) $(= $value:expr)?,
23        )+
24    }
25    ) => {
26        // Main Type
27        #[allow(clippy::all)]
28        $(#[$m])*
29        $type_vis enum $typename {
30            $(
31            $(#[$variant_meta])*
32            $variant$( = $value)?,
33            )+
34        }
35
36        impl $typename {
37            #[doc = "The corresponding rocksdb string identifier for this variant"]
38            pub const fn name(&self) -> &'static str {
39                match self {
40                    $(
41                        $typename::$variant => $variant_str,
42                    )+
43                }
44            }
45            pub fn iter() -> ::core::slice::Iter<'static, $typename> {
46                static VARIANTS: &'static [$typename] = &[
47                    $(
48                        $typename::$variant,
49                    )+
50                ];
51                VARIANTS.iter()
52            }
53        }
54
55
56        #[automatically_derived]
57        impl ::core::str::FromStr for $typename {
58            type Err = NameParseError;
59            fn from_str(s: &str) -> Result<Self, Self::Err> {
60                match s {
61                    $(
62                        $variant_str => Ok($typename::$variant),
63                    )+
64                    _ => Err(NameParseError),
65                }
66            }
67        }
68
69        #[automatically_derived]
70        impl ::core::fmt::Display for $typename {
71            fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
72                self.name().fmt(f)
73            }
74        }
75    };
76}
77
78/// How much statistics detail to collect, trading overhead for visibility.
79///
80/// The levels are ordered, and each one adds to the one before it. The
81/// discriminants come from the C API rather than being written out here, because
82/// the setter passes the value straight through and a mismatch would silently
83/// select a different level instead of failing.
84///
85/// RocksDB also defines `kExceptTickers`, which has the same value as
86/// `kDisableAll` and so cannot be a separate variant here. [`DisableAll`] is that
87/// value.
88///
89/// [`DisableAll`]: StatsLevel::DisableAll
90#[derive(Debug, Copy, Clone, PartialEq, Eq)]
91#[repr(u32)]
92// MSVC types an anonymous C enum as signed int where clang picks unsigned, so
93// these constants are i32 on Windows and u32 everywhere else. The cast is needed
94// on Windows and redundant on the platforms clippy runs on.
95#[allow(clippy::unnecessary_cast)]
96pub enum StatsLevel {
97    /// Collect nothing. Also what RocksDB reports when no statistics object has
98    /// been installed, so this does not distinguish "turned off" from "never
99    /// turned on".
100    DisableAll = ffi::rocksdb_statistics_level_disable_all as u32,
101    /// Skip histograms and timers.
102    ExceptHistogramOrTimers = ffi::rocksdb_statistics_level_except_histogram_or_timers as u32,
103    /// Collect histograms, skip timers.
104    ExceptTimers = ffi::rocksdb_statistics_level_except_timers as u32,
105    /// Collect everything except time spent inside a mutex lock and time spent on
106    /// compression.
107    ExceptDetailedTimers = ffi::rocksdb_statistics_level_except_detailed_timers as u32,
108    /// Collect everything except the counters that need the time from inside the
109    /// mutex lock.
110    ExceptTimeForMutex = ffi::rocksdb_statistics_level_except_time_for_mutex as u32,
111    /// Collect everything, including how long mutex operations take. Where reading
112    /// the clock is expensive this can limit scalability across threads,
113    /// especially for writes.
114    All = ffi::rocksdb_statistics_level_all as u32,
115}
116
117impl StatsLevel {
118    /// Decodes a raw `rocksdb::StatsLevel`.
119    ///
120    /// `None` for a value this crate has no variant for, which RocksDB's own
121    /// clamping in `rocksdb_options_set_statistics_level` should prevent.
122    pub(crate) fn try_from_raw(raw: c_int) -> Option<Self> {
123        match raw {
124            n if n == Self::DisableAll as c_int => Some(Self::DisableAll),
125            n if n == Self::ExceptHistogramOrTimers as c_int => Some(Self::ExceptHistogramOrTimers),
126            n if n == Self::ExceptTimers as c_int => Some(Self::ExceptTimers),
127            n if n == Self::ExceptDetailedTimers as c_int => Some(Self::ExceptDetailedTimers),
128            n if n == Self::ExceptTimeForMutex as c_int => Some(Self::ExceptTimeForMutex),
129            n if n == Self::All as c_int => Some(Self::All),
130            _ => None,
131        }
132    }
133}
134
135include!("statistics_enum_ticker.rs");
136include!("statistics_enum_histogram.rs");
137
138pub struct HistogramData {
139    pub(crate) inner: *mut ffi::rocksdb_statistics_histogram_data_t,
140}
141
142impl HistogramData {
143    pub fn new() -> HistogramData {
144        HistogramData::default()
145    }
146    pub fn median(&self) -> f64 {
147        unsafe { ffi::rocksdb_statistics_histogram_data_get_median(self.inner) }
148    }
149    pub fn average(&self) -> f64 {
150        unsafe { ffi::rocksdb_statistics_histogram_data_get_average(self.inner) }
151    }
152    pub fn p95(&self) -> f64 {
153        unsafe { ffi::rocksdb_statistics_histogram_data_get_p95(self.inner) }
154    }
155    pub fn p99(&self) -> f64 {
156        unsafe { ffi::rocksdb_statistics_histogram_data_get_p99(self.inner) }
157    }
158    pub fn max(&self) -> f64 {
159        unsafe { ffi::rocksdb_statistics_histogram_data_get_max(self.inner) }
160    }
161    pub fn min(&self) -> f64 {
162        unsafe { ffi::rocksdb_statistics_histogram_data_get_min(self.inner) }
163    }
164    pub fn sum(&self) -> u64 {
165        unsafe { ffi::rocksdb_statistics_histogram_data_get_sum(self.inner) }
166    }
167    pub fn count(&self) -> u64 {
168        unsafe { ffi::rocksdb_statistics_histogram_data_get_count(self.inner) }
169    }
170    pub fn std_dev(&self) -> f64 {
171        unsafe { ffi::rocksdb_statistics_histogram_data_get_std_dev(self.inner) }
172    }
173}
174
175impl Default for HistogramData {
176    fn default() -> Self {
177        let histogram_data_inner = unsafe { ffi::rocksdb_statistics_histogram_data_create() };
178        assert!(
179            !histogram_data_inner.is_null(),
180            "Could not create RocksDB histogram data"
181        );
182
183        Self {
184            inner: histogram_data_inner,
185        }
186    }
187}
188
189impl Drop for HistogramData {
190    fn drop(&mut self) {
191        unsafe {
192            ffi::rocksdb_statistics_histogram_data_destroy(self.inner);
193        }
194    }
195}
196
197#[test]
198fn sanity_checks() {
199    let want = "rocksdb.async.read.bytes";
200    assert_eq!(want, Histogram::AsyncReadBytes.name());
201
202    let want = "rocksdb.block.cache.index.miss";
203    assert_eq!(want, Ticker::BlockCacheIndexMiss.to_string());
204
205    // assert enum lengths
206    assert_eq!(Ticker::iter().count(), 263 /* TICKER_ENUM_MAX */);
207    assert_eq!(Histogram::iter().count(), 80 /* HISTOGRAM_ENUM_MAX */);
208}