nautilus_blockchain/
reporting.rs

1// -------------------------------------------------------------------------------------------------
2//  Copyright (C) 2015-2025 Nautech Systems Pty Ltd. All rights reserved.
3//  https://nautechsystems.io
4//
5//  Licensed under the GNU Lesser General Public License Version 3.0 (the "License");
6//  You may not use this file except in compliance with the License.
7//  You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html
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
16//! Performance reporting and metrics tracking for blockchain operations.
17
18use std::{fmt::Display, time::Instant};
19
20/// Formats a number with comma separators for better readability.
21/// Works with both integers and floats (floats are rounded to integers).
22/// Example: 1234567 -> "1,234,567", 1234567.8 -> "1,234,568"
23fn format_number<T>(n: T) -> String
24where
25    T: Into<f64>,
26{
27    let num = n.into().round() as u64;
28    let mut result = String::new();
29    let s = num.to_string();
30    let chars: Vec<char> = s.chars().collect();
31
32    for (i, ch) in chars.iter().enumerate() {
33        if i > 0 && (chars.len() - i) % 3 == 0 {
34            result.push(',');
35        }
36        result.push(*ch);
37    }
38
39    result
40}
41
42#[derive(Debug, Clone)]
43pub enum BlockchainSyncReportItems {
44    Blocks,
45    PoolCreatedEvents,
46    PoolEvents,
47}
48
49impl Display for BlockchainSyncReportItems {
50    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
51        write!(f, "{self:?}")
52    }
53}
54
55/// Tracks performance metrics during block synchronization
56#[derive(Debug)]
57pub struct BlockchainSyncReporter {
58    item: BlockchainSyncReportItems,
59    start_time: Instant,
60    last_progress_time: Instant,
61    blocks_processed: u64,
62    blocks_since_last_report: u64,
63    total_blocks: u64,
64    progress_update_interval: u64,
65    next_progress_threshold: u64,
66}
67
68impl BlockchainSyncReporter {
69    /// Creates a new metrics tracker for block synchronization
70    #[must_use]
71    pub fn new(
72        item: BlockchainSyncReportItems,
73        from_block: u64,
74        total_blocks: u64,
75        update_interval: u64,
76    ) -> Self {
77        let now = Instant::now();
78        Self {
79            item,
80            start_time: now,
81            last_progress_time: now,
82            blocks_processed: 0,
83            blocks_since_last_report: 0,
84            total_blocks,
85            progress_update_interval: update_interval,
86            next_progress_threshold: from_block + update_interval,
87        }
88    }
89
90    /// Updates metrics after a database operation
91    pub fn update(&mut self, batch_size: usize) {
92        self.blocks_processed += batch_size as u64;
93        self.blocks_since_last_report += batch_size as u64;
94    }
95
96    /// Checks if progress should be logged based on the current block number
97    #[must_use]
98    pub fn should_log_progress(&self, block_number: u64, current_block: u64) -> bool {
99        let block_threshold_reached =
100            block_number >= self.next_progress_threshold || block_number >= current_block;
101        // Minimum 1 second between logs
102        let time_threshold_reached = self.last_progress_time.elapsed().as_secs_f64() >= 1.0;
103
104        block_threshold_reached && time_threshold_reached
105    }
106
107    /// Logs current progress with detailed metrics
108    pub fn log_progress(&mut self, block_number: u64) {
109        let elapsed = self.start_time.elapsed();
110        let interval_elapsed = self.last_progress_time.elapsed();
111
112        // Calculate rates - avoid division by zero
113        let avg_rate = if elapsed.as_secs_f64() > 0.0 {
114            self.blocks_processed as f64 / elapsed.as_secs_f64()
115        } else {
116            0.0
117        };
118
119        let current_rate = if interval_elapsed.as_secs_f64() > 0.001 {
120            // Minimum 1ms
121            self.blocks_since_last_report as f64 / interval_elapsed.as_secs_f64()
122        } else {
123            0.0
124        };
125
126        let progress_pct =
127            (self.blocks_processed as f64 / self.total_blocks as f64 * 100.0).min(100.0);
128
129        tracing::info!(
130            "Syncing {} progress: {:.1}% | Block: {} | Rate: {} blocks/s | Avg: {} blocks/s",
131            self.item,
132            progress_pct,
133            format_number(block_number as f64),
134            format_number(current_rate),
135            format_number(avg_rate),
136        );
137
138        self.next_progress_threshold = block_number + self.progress_update_interval;
139        self.last_progress_time = Instant::now();
140        self.blocks_since_last_report = 0;
141    }
142
143    /// Logs final statistics summary
144    pub fn log_final_stats(&self) {
145        let total_elapsed = self.start_time.elapsed();
146        let avg_rate = self.blocks_processed as f64 / total_elapsed.as_secs_f64();
147        tracing::info!(
148            "Finished syncing {} | Total: {} blocks in {:.1}s | Avg rate: {} blocks/s",
149            self.item,
150            format_number(self.blocks_processed as f64),
151            total_elapsed.as_secs_f64(),
152            format_number(avg_rate),
153        );
154    }
155}