1use std::sync::atomic::{AtomicU64, Ordering};
2use std::time::Duration;
3
4#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
5#[non_exhaustive]
6pub struct ReaderStatistics {
11 cursors_created: u64,
12 segment_parses: u64,
13 segment_handle_opens: u64,
14 segment_handle_reopens: u64,
15 table_checksum_bytes: u64,
16 table_checksum_nanos: u64,
17 chunk_cache_hits: u64,
18 chunk_cache_misses: u64,
19 chunk_cache_coalesced: u64,
20 table_page_cache_hits: u64,
21 table_page_cache_misses: u64,
22 segment_read_bytes: u64,
23 segment_read_nanos: u64,
24 segment_pool_wait_nanos: u64,
25 encoded_bytes_read: u64,
26 decoded_bytes: u64,
27 decompression_nanos: u64,
28}
29
30impl ReaderStatistics {
31 pub fn cursors_created(&self) -> u64 {
33 self.cursors_created
34 }
35
36 pub fn segment_parses(&self) -> u64 {
38 self.segment_parses
39 }
40
41 pub fn segment_handle_opens(&self) -> u64 {
43 self.segment_handle_opens
44 }
45
46 pub fn segment_handle_reopens(&self) -> u64 {
48 self.segment_handle_reopens
49 }
50
51 pub fn table_checksum_bytes(&self) -> u64 {
53 self.table_checksum_bytes
54 }
55
56 pub fn table_checksum_nanos(&self) -> u64 {
58 self.table_checksum_nanos
59 }
60
61 pub fn chunk_cache_hits(&self) -> u64 {
63 self.chunk_cache_hits
64 }
65
66 pub fn chunk_cache_misses(&self) -> u64 {
68 self.chunk_cache_misses
69 }
70
71 pub fn chunk_cache_coalesced(&self) -> u64 {
73 self.chunk_cache_coalesced
74 }
75
76 pub fn table_page_cache_hits(&self) -> u64 {
78 self.table_page_cache_hits
79 }
80
81 pub fn table_page_cache_misses(&self) -> u64 {
83 self.table_page_cache_misses
84 }
85
86 pub fn segment_read_bytes(&self) -> u64 {
88 self.segment_read_bytes
89 }
90
91 pub fn segment_read_nanos(&self) -> u64 {
93 self.segment_read_nanos
94 }
95
96 pub fn segment_pool_wait_nanos(&self) -> u64 {
98 self.segment_pool_wait_nanos
99 }
100
101 pub fn encoded_bytes_read(&self) -> u64 {
103 self.encoded_bytes_read
104 }
105
106 pub fn decoded_bytes(&self) -> u64 {
108 self.decoded_bytes
109 }
110
111 pub fn decompression_nanos(&self) -> u64 {
113 self.decompression_nanos
114 }
115
116 #[must_use]
118 pub fn saturating_delta(self, earlier: Self) -> Self {
119 Self {
120 cursors_created: self.cursors_created.saturating_sub(earlier.cursors_created),
121 segment_parses: self.segment_parses.saturating_sub(earlier.segment_parses),
122 segment_handle_opens: self
123 .segment_handle_opens
124 .saturating_sub(earlier.segment_handle_opens),
125 segment_handle_reopens: self
126 .segment_handle_reopens
127 .saturating_sub(earlier.segment_handle_reopens),
128 table_checksum_bytes: self
129 .table_checksum_bytes
130 .saturating_sub(earlier.table_checksum_bytes),
131 table_checksum_nanos: self
132 .table_checksum_nanos
133 .saturating_sub(earlier.table_checksum_nanos),
134 chunk_cache_hits: self
135 .chunk_cache_hits
136 .saturating_sub(earlier.chunk_cache_hits),
137 chunk_cache_misses: self
138 .chunk_cache_misses
139 .saturating_sub(earlier.chunk_cache_misses),
140 chunk_cache_coalesced: self
141 .chunk_cache_coalesced
142 .saturating_sub(earlier.chunk_cache_coalesced),
143 table_page_cache_hits: self
144 .table_page_cache_hits
145 .saturating_sub(earlier.table_page_cache_hits),
146 table_page_cache_misses: self
147 .table_page_cache_misses
148 .saturating_sub(earlier.table_page_cache_misses),
149 segment_read_bytes: self
150 .segment_read_bytes
151 .saturating_sub(earlier.segment_read_bytes),
152 segment_read_nanos: self
153 .segment_read_nanos
154 .saturating_sub(earlier.segment_read_nanos),
155 segment_pool_wait_nanos: self
156 .segment_pool_wait_nanos
157 .saturating_sub(earlier.segment_pool_wait_nanos),
158 encoded_bytes_read: self
159 .encoded_bytes_read
160 .saturating_sub(earlier.encoded_bytes_read),
161 decoded_bytes: self.decoded_bytes.saturating_sub(earlier.decoded_bytes),
162 decompression_nanos: self
163 .decompression_nanos
164 .saturating_sub(earlier.decompression_nanos),
165 }
166 }
167}
168
169#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
170#[non_exhaustive]
171pub struct ReaderCacheInfo {
173 chunk_cache_capacity: u64,
174 table_entry_cache_capacity: u64,
175 table_entry_cache_current: u64,
176 table_entry_cache_peak: u64,
177}
178
179impl ReaderCacheInfo {
180 pub(crate) fn new(
181 chunk_cache_capacity_bytes: u64,
182 table_entry_cache_capacity_bytes: usize,
183 table_entry_cache_current_bytes: usize,
184 table_entry_cache_peak_bytes: usize,
185 ) -> Self {
186 Self {
187 chunk_cache_capacity: chunk_cache_capacity_bytes,
188 table_entry_cache_capacity: usize_to_u64(table_entry_cache_capacity_bytes),
189 table_entry_cache_current: usize_to_u64(table_entry_cache_current_bytes),
190 table_entry_cache_peak: usize_to_u64(table_entry_cache_peak_bytes),
191 }
192 }
193
194 pub fn chunk_cache_capacity_bytes(&self) -> u64 {
196 self.chunk_cache_capacity
197 }
198
199 pub fn table_entry_cache_capacity_bytes(&self) -> u64 {
201 self.table_entry_cache_capacity
202 }
203
204 pub fn table_entry_cache_current_bytes(&self) -> u64 {
206 self.table_entry_cache_current
207 }
208
209 pub fn table_entry_cache_peak_bytes(&self) -> u64 {
211 self.table_entry_cache_peak
212 }
213}
214
215#[derive(Debug, Default)]
216pub(crate) struct ReaderStatisticsCollector {
217 enabled: bool,
218 cursors_created: AtomicU64,
219 segment_parses: AtomicU64,
220 segment_handle_opens: AtomicU64,
221 segment_handle_reopens: AtomicU64,
222 table_checksum_bytes: AtomicU64,
223 table_checksum_nanos: AtomicU64,
224 chunk_cache_hits: AtomicU64,
225 chunk_cache_misses: AtomicU64,
226 chunk_cache_coalesced: AtomicU64,
227 table_page_cache_hits: AtomicU64,
228 table_page_cache_misses: AtomicU64,
229 segment_read_bytes: AtomicU64,
230 segment_read_nanos: AtomicU64,
231 segment_pool_wait_nanos: AtomicU64,
232 encoded_bytes_read: AtomicU64,
233 decoded_bytes: AtomicU64,
234 decompression_nanos: AtomicU64,
235}
236
237impl ReaderStatisticsCollector {
238 pub(crate) fn new(enabled: bool) -> Self {
239 Self {
240 enabled,
241 ..Self::default()
242 }
243 }
244
245 pub(crate) fn enabled(&self) -> bool {
246 self.enabled
247 }
248
249 pub(crate) fn snapshot(&self) -> Option<ReaderStatistics> {
250 self.enabled.then(|| ReaderStatistics {
251 cursors_created: self.cursors_created.load(Ordering::Relaxed),
252 segment_parses: self.segment_parses.load(Ordering::Relaxed),
253 segment_handle_opens: self.segment_handle_opens.load(Ordering::Relaxed),
254 segment_handle_reopens: self.segment_handle_reopens.load(Ordering::Relaxed),
255 table_checksum_bytes: self.table_checksum_bytes.load(Ordering::Relaxed),
256 table_checksum_nanos: self.table_checksum_nanos.load(Ordering::Relaxed),
257 chunk_cache_hits: self.chunk_cache_hits.load(Ordering::Relaxed),
258 chunk_cache_misses: self.chunk_cache_misses.load(Ordering::Relaxed),
259 chunk_cache_coalesced: self.chunk_cache_coalesced.load(Ordering::Relaxed),
260 table_page_cache_hits: self.table_page_cache_hits.load(Ordering::Relaxed),
261 table_page_cache_misses: self.table_page_cache_misses.load(Ordering::Relaxed),
262 segment_read_bytes: self.segment_read_bytes.load(Ordering::Relaxed),
263 segment_read_nanos: self.segment_read_nanos.load(Ordering::Relaxed),
264 segment_pool_wait_nanos: self.segment_pool_wait_nanos.load(Ordering::Relaxed),
265 encoded_bytes_read: self.encoded_bytes_read.load(Ordering::Relaxed),
266 decoded_bytes: self.decoded_bytes.load(Ordering::Relaxed),
267 decompression_nanos: self.decompression_nanos.load(Ordering::Relaxed),
268 })
269 }
270
271 pub(crate) fn record_cursor_created(&self) {
272 self.add(&self.cursors_created, 1);
273 }
274
275 pub(crate) fn record_segment_parse(&self) {
276 self.add(&self.segment_parses, 1);
277 }
278
279 pub(crate) fn record_segment_handle_open(&self, count: usize) {
280 self.add(&self.segment_handle_opens, usize_to_u64(count));
281 }
282
283 pub(crate) fn record_segment_handle_reopen(&self) {
284 self.add(&self.segment_handle_reopens, 1);
285 }
286
287 pub(crate) fn record_table_checksum(&self, bytes: u64, elapsed: Duration) {
288 self.add(&self.table_checksum_bytes, bytes);
289 self.add(&self.table_checksum_nanos, duration_nanos(elapsed));
290 }
291
292 pub(crate) fn record_chunk_cache_access(&self, hit: bool) {
293 if hit {
294 self.add(&self.chunk_cache_hits, 1);
295 } else {
296 self.add(&self.chunk_cache_misses, 1);
297 }
298 }
299
300 pub(crate) fn record_chunk_cache_coalesced(&self) {
301 self.add(&self.chunk_cache_coalesced, 1);
302 }
303
304 pub(crate) fn record_segment_read(&self, bytes: usize, elapsed: Duration) {
305 self.add(&self.segment_read_bytes, usize_to_u64(bytes));
306 self.add(&self.segment_read_nanos, duration_nanos(elapsed));
307 }
308
309 pub(crate) fn record_segment_pool_wait(&self, elapsed: Duration) {
310 self.add(&self.segment_pool_wait_nanos, duration_nanos(elapsed));
311 }
312
313 pub(crate) fn record_table_page_cache_access(&self, hit: bool) {
314 if hit {
315 self.add(&self.table_page_cache_hits, 1);
316 } else {
317 self.add(&self.table_page_cache_misses, 1);
318 }
319 }
320
321 pub(crate) fn record_encoded_bytes_read(&self, bytes: u64) {
322 self.add(&self.encoded_bytes_read, bytes);
323 }
324
325 pub(crate) fn record_decoded_bytes(&self, bytes: usize) {
326 self.add(&self.decoded_bytes, usize_to_u64(bytes));
327 }
328
329 pub(crate) fn record_decompression(&self, elapsed: Duration) {
330 self.add(&self.decompression_nanos, duration_nanos(elapsed));
331 }
332
333 fn add(&self, counter: &AtomicU64, value: u64) {
334 if !self.enabled || value == 0 {
335 return;
336 }
337 let _ = counter.try_update(Ordering::Relaxed, Ordering::Relaxed, |current| {
338 Some(current.saturating_add(value))
339 });
340 }
341}
342
343fn duration_nanos(duration: Duration) -> u64 {
344 u64::try_from(duration.as_nanos()).unwrap_or(u64::MAX)
345}
346
347fn usize_to_u64(value: usize) -> u64 {
348 u64::try_from(value).unwrap_or(u64::MAX)
349}
350
351#[cfg(test)]
352mod tests {
353 use super::*;
354
355 #[test]
356 fn counter_addition_saturates() {
357 let statistics = ReaderStatisticsCollector::new(true);
358 let counter = AtomicU64::new(u64::MAX - 1);
359 statistics.add(&counter, 2);
360 statistics.add(&counter, 1);
361 assert_eq!(counter.load(Ordering::Relaxed), u64::MAX);
362 }
363
364 #[test]
365 fn disabled_and_zero_additions_preserve_counter() {
366 let counter = AtomicU64::new(42);
367 ReaderStatisticsCollector::new(false).add(&counter, 5);
368 ReaderStatisticsCollector::new(true).add(&counter, 0);
369 assert_eq!(counter.load(Ordering::Relaxed), 42);
370 }
371
372 #[test]
373 fn concurrent_additions_are_not_lost() {
374 let statistics = ReaderStatisticsCollector::new(true);
375 let counter = AtomicU64::new(0);
376 std::thread::scope(|scope| {
377 for _ in 0..8 {
378 scope.spawn(|| {
379 for _ in 0..1_000 {
380 statistics.add(&counter, 1);
381 }
382 });
383 }
384 });
385 assert_eq!(counter.load(Ordering::Relaxed), 8_000);
386 }
387}