mito2/sst/index/
statistics.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::time::{Duration, Instant};
16
17use crate::metrics::{INDEX_CREATE_BYTES_TOTAL, INDEX_CREATE_ELAPSED, INDEX_CREATE_ROWS_TOTAL};
18
19pub(crate) type ByteCount = u64;
20pub(crate) type RowCount = usize;
21
22/// Stage of the index creation process.
23enum Stage {
24    Update,
25    Finish,
26    Cleanup,
27}
28
29/// Statistics for index creation. Flush metrics when dropped.
30pub(crate) struct Statistics {
31    /// Index type.
32    index_type: &'static str,
33    /// Accumulated elapsed time for the index update stage.
34    update_elapsed: Duration,
35    /// Accumulated elapsed time for the index finish stage.
36    finish_elapsed: Duration,
37    /// Accumulated elapsed time for the cleanup stage.
38    cleanup_eplased: Duration,
39    /// Number of rows in the index.
40    row_count: RowCount,
41    /// Number of bytes in the index.
42    byte_count: ByteCount,
43}
44
45impl Statistics {
46    pub fn new(index_type: &'static str) -> Self {
47        Self {
48            index_type,
49            update_elapsed: Duration::default(),
50            finish_elapsed: Duration::default(),
51            cleanup_eplased: Duration::default(),
52            row_count: 0,
53            byte_count: 0,
54        }
55    }
56
57    /// Starts timing the update stage, returning a `TimerGuard` to automatically record duration.
58    #[must_use]
59    pub fn record_update(&mut self) -> TimerGuard<'_> {
60        TimerGuard::new(self, Stage::Update)
61    }
62
63    /// Starts timing the finish stage, returning a `TimerGuard` to automatically record duration.
64    #[must_use]
65    pub fn record_finish(&mut self) -> TimerGuard<'_> {
66        TimerGuard::new(self, Stage::Finish)
67    }
68
69    /// Starts timing the cleanup stage, returning a `TimerGuard` to automatically record duration.
70    #[must_use]
71    pub fn record_cleanup(&mut self) -> TimerGuard<'_> {
72        TimerGuard::new(self, Stage::Cleanup)
73    }
74
75    /// Returns row count.
76    pub fn row_count(&self) -> RowCount {
77        self.row_count
78    }
79
80    /// Returns byte count.
81    pub fn byte_count(&self) -> ByteCount {
82        self.byte_count
83    }
84}
85
86impl Drop for Statistics {
87    fn drop(&mut self) {
88        INDEX_CREATE_ELAPSED
89            .with_label_values(&["update", self.index_type])
90            .observe(self.update_elapsed.as_secs_f64());
91        INDEX_CREATE_ELAPSED
92            .with_label_values(&["finish", self.index_type])
93            .observe(self.finish_elapsed.as_secs_f64());
94        INDEX_CREATE_ELAPSED
95            .with_label_values(&["cleanup", self.index_type])
96            .observe(self.cleanup_eplased.as_secs_f64());
97        INDEX_CREATE_ELAPSED
98            .with_label_values(&["total", self.index_type])
99            .observe(
100                (self.update_elapsed + self.finish_elapsed + self.cleanup_eplased).as_secs_f64(),
101            );
102
103        INDEX_CREATE_ROWS_TOTAL
104            .with_label_values(&[self.index_type])
105            .inc_by(self.row_count as _);
106        INDEX_CREATE_BYTES_TOTAL
107            .with_label_values(&[self.index_type])
108            .inc_by(self.byte_count as _);
109    }
110}
111
112/// `TimerGuard` is a RAII struct that ensures elapsed time
113/// is recorded when it goes out of scope.
114pub(crate) struct TimerGuard<'a> {
115    stats: &'a mut Statistics,
116    stage: Stage,
117    timer: Instant,
118}
119
120impl<'a> TimerGuard<'a> {
121    /// Creates a new `TimerGuard`,
122    fn new(stats: &'a mut Statistics, stage: Stage) -> Self {
123        Self {
124            stats,
125            stage,
126            timer: Instant::now(),
127        }
128    }
129
130    /// Increases the row count of the index creation statistics.
131    pub fn inc_row_count(&mut self, n: usize) {
132        self.stats.row_count += n;
133    }
134
135    /// Increases the byte count of the index creation statistics.
136    pub fn inc_byte_count(&mut self, n: u64) {
137        self.stats.byte_count += n;
138    }
139}
140
141impl Drop for TimerGuard<'_> {
142    fn drop(&mut self) {
143        match self.stage {
144            Stage::Update => {
145                self.stats.update_elapsed += self.timer.elapsed();
146            }
147            Stage::Finish => {
148                self.stats.finish_elapsed += self.timer.elapsed();
149            }
150            Stage::Cleanup => {
151                self.stats.cleanup_eplased += self.timer.elapsed();
152            }
153        }
154    }
155}
156
157#[cfg(test)]
158mod tests {
159    use super::*;
160
161    #[test]
162    fn test_statistics_basic() {
163        let mut stats = Statistics::new("test");
164        {
165            let mut guard = stats.record_update();
166            guard.inc_byte_count(100);
167            guard.inc_row_count(10);
168
169            let now = Instant::now();
170            while now.elapsed().is_zero() {
171                // busy loop
172            }
173        }
174        {
175            let _guard = stats.record_finish();
176            let now = Instant::now();
177            while now.elapsed().is_zero() {
178                // busy loop
179            }
180        }
181        {
182            let _guard = stats.record_cleanup();
183            let now = Instant::now();
184            while now.elapsed().is_zero() {
185                // busy loop
186            }
187        }
188        assert_eq!(stats.row_count(), 10);
189        assert_eq!(stats.byte_count(), 100);
190        assert!(stats.update_elapsed > Duration::default());
191        assert!(stats.finish_elapsed > Duration::default());
192        assert!(stats.cleanup_eplased > Duration::default());
193    }
194}