Skip to main content

nautilus_blockchain/
reporting.rs

1// -------------------------------------------------------------------------------------------------
2//  Copyright (C) 2015-2026 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).is_multiple_of(3) {
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    PoolProfilerBootstrap,
48}
49
50impl Display for BlockchainSyncReportItems {
51    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
52        write!(f, "{self:?}")
53    }
54}
55
56/// Tracks performance metrics during block synchronization
57#[derive(Debug)]
58pub struct BlockchainSyncReporter {
59    item: BlockchainSyncReportItems,
60    start_time: Instant,
61    last_progress_time: Instant,
62    from_block: u64,
63    blocks_processed: u64,
64    blocks_since_last_report: u64,
65    total_blocks: u64,
66    progress_update_interval: u64,
67    next_progress_threshold: u64,
68}
69
70impl BlockchainSyncReporter {
71    /// Creates a new metrics tracker for block synchronization
72    #[must_use]
73    pub fn new(
74        item: BlockchainSyncReportItems,
75        from_block: u64,
76        total_blocks: u64,
77        update_interval: u64,
78    ) -> Self {
79        let now = Instant::now();
80        Self {
81            item,
82            start_time: now,
83            last_progress_time: now,
84            from_block,
85            blocks_processed: 0,
86            blocks_since_last_report: 0,
87            total_blocks,
88            progress_update_interval: update_interval,
89            next_progress_threshold: from_block + update_interval,
90        }
91    }
92
93    /// Updates metrics after a database operation
94    pub fn update(&mut self, batch_size: usize) {
95        self.blocks_processed += batch_size as u64;
96        self.blocks_since_last_report += batch_size as u64;
97    }
98
99    /// Checks if progress should be logged based on the current block number
100    #[must_use]
101    pub fn should_log_progress(&self, block_number: u64, current_block: u64) -> bool {
102        let block_threshold_reached =
103            block_number >= self.next_progress_threshold || block_number >= current_block;
104        // Minimum 1 second between logs
105        let time_threshold_reached = self.last_progress_time.elapsed().as_secs_f64() >= 1.0;
106
107        block_threshold_reached && time_threshold_reached
108    }
109
110    /// Logs current progress with detailed metrics
111    pub fn log_progress(&mut self, block_number: u64) {
112        let elapsed = self.start_time.elapsed();
113        let interval_elapsed = self.last_progress_time.elapsed();
114
115        // Calculate rates - avoid division by zero
116        let avg_rate = if elapsed.as_secs_f64() > 0.0 {
117            self.blocks_processed as f64 / elapsed.as_secs_f64()
118        } else {
119            0.0
120        };
121
122        let current_rate = if interval_elapsed.as_secs_f64() > 0.001 {
123            // Minimum 1ms
124            self.blocks_since_last_report as f64 / interval_elapsed.as_secs_f64()
125        } else {
126            0.0
127        };
128
129        // Calculate progress based on actual block position relative to the sync range
130        let blocks_completed = block_number.saturating_sub(self.from_block);
131        let progress_pct = (blocks_completed as f64 / self.total_blocks as f64 * 100.0).min(100.0);
132
133        log::info!(
134            "Syncing {} progress: {:.1}% | Block: {} | Rate: {} blocks/s | Avg: {} blocks/s",
135            self.item,
136            progress_pct,
137            format_number(block_number as f64),
138            format_number(current_rate),
139            format_number(avg_rate),
140        );
141
142        self.next_progress_threshold = block_number + self.progress_update_interval;
143        self.last_progress_time = Instant::now();
144        self.blocks_since_last_report = 0;
145    }
146
147    /// Logs final statistics summary
148    pub fn log_final_stats(&self) {
149        let total_elapsed = self.start_time.elapsed();
150        let avg_rate = self.blocks_processed as f64 / total_elapsed.as_secs_f64();
151        log::info!(
152            "Finished syncing {} | Total: {} blocks in {:.1}s | Avg rate: {} blocks/s",
153            self.item,
154            format_number(self.blocks_processed as f64),
155            total_elapsed.as_secs_f64(),
156            format_number(avg_rate),
157        );
158    }
159}