node/stats/
mod.rs

1//! # Statistics Module
2//!
3//! This module collects and manages runtime statistics for the node. It tracks
4//! three main categories of statistics that can be queried via RPC endpoints.
5//!
6//! ## Components
7//!
8//! | Sub-module        | Purpose                                              |
9//! |-------------------|------------------------------------------------------|
10//! | [`actions`]       | Tracks action dispatch frequency and timing          |
11//! | [`sync`]          | Tracks blockchain synchronization progress           |
12//! | [`block_producer`]| Tracks block production attempts and outcomes        |
13//!
14//! ## Architecture
15//!
16//! The [`Stats`] struct is held by the [`Service`](crate::Service) trait
17//! implementation and is accessed via `store.service.stats()` in effects.
18//! Statistics are updated during effect execution, not in reducers, since
19//! they are side-effects that don't affect the core state machine.
20//!
21//! ## Usage
22//!
23//! Statistics are collected in effects throughout the codebase:
24//!
25//! - **Action stats**: Updated in [`effects()`](crate::effects()) for every
26//!   dispatched action
27//! - **Sync stats**: Updated in [`transition_frontier`](crate::transition_frontier)
28//!   effects during synchronization
29//! - **Block producer stats**: Updated in
30//!   [`block_producer_effectful`](crate::block_producer_effectful) during block
31//!   production
32//!
33//! ## RPC Endpoints
34//!
35//! Statistics are exposed via RPC for monitoring and debugging. These endpoints
36//! are consumed by the frontend dashboard to display node health and performance:
37//!
38//! - `ActionStatsGet` - Returns action dispatch statistics
39//! - `SyncStatsGet` - Returns synchronization statistics
40//! - `BlockProducerStatsGet` - Returns block production statistics
41
42mod stats_actions;
43pub mod actions {
44    pub use super::stats_actions::*;
45}
46use actions::{ActionStats, ActionStatsForBlock, ActionStatsSnapshot};
47
48mod stats_sync;
49pub mod sync {
50    pub use super::stats_sync::*;
51}
52use sync::{SyncStats, SyncStatsSnapshot, SyncingLedger};
53
54mod stats_block_producer;
55pub mod block_producer {
56    pub use super::stats_block_producer::*;
57}
58use block_producer::BlockProducerStats;
59
60use mina_core::block::{AppliedBlock, ArcBlockWithHash};
61use redux::{ActionMeta, ActionWithMeta, Timestamp};
62
63use crate::{
64    transition_frontier::sync::{
65        ledger::{staged::PeerStagedLedgerPartsFetchError, SyncLedgerTargetKind},
66        TransitionFrontierSyncBlockState,
67    },
68    ActionKind,
69};
70
71pub type ActionKindWithMeta = ActionWithMeta<ActionKind>;
72
73pub struct Stats {
74    last_action: ActionKindWithMeta,
75    action_stats: ActionStats,
76    sync_stats: SyncStats,
77    block_producer_stats: BlockProducerStats,
78}
79
80impl Stats {
81    pub fn new() -> Self {
82        Self {
83            last_action: ActionMeta::ZERO.with_action(ActionKind::None),
84            action_stats: Default::default(),
85            sync_stats: Default::default(),
86            block_producer_stats: Default::default(),
87        }
88    }
89
90    pub fn block_producer(&mut self) -> &mut BlockProducerStats {
91        &mut self.block_producer_stats
92    }
93
94    pub fn new_sync_target(
95        &mut self,
96        time: Timestamp,
97        best_tip: &ArcBlockWithHash,
98        root_block: &ArcBlockWithHash,
99    ) -> &mut Self {
100        self.sync_stats.new_target(time, best_tip, root_block);
101        self
102    }
103
104    pub fn syncing_ledger(
105        &mut self,
106        kind: SyncLedgerTargetKind,
107        update: SyncingLedger,
108    ) -> &mut Self {
109        self.sync_stats.ledger(kind, update);
110        self
111    }
112
113    pub fn syncing_blocks_init(
114        &mut self,
115        states: &[TransitionFrontierSyncBlockState],
116    ) -> &mut Self {
117        self.sync_stats.blocks_init(states);
118        self
119    }
120
121    pub fn syncing_block_update(&mut self, state: &TransitionFrontierSyncBlockState) -> &mut Self {
122        self.sync_stats.block_update(state);
123        self
124    }
125
126    pub fn new_best_chain(&mut self, time: Timestamp, chain: &[AppliedBlock]) -> &mut Self {
127        let best_tip = chain.last().unwrap().block_with_hash();
128        self.action_stats
129            .new_best_tip(time, best_tip.height(), best_tip.hash().clone());
130        self.sync_stats.synced(time);
131        self.block_producer_stats.new_best_chain(time, chain);
132        self
133    }
134
135    pub fn new_action(&mut self, kind: ActionKind, meta: ActionMeta) -> &mut Self {
136        let action = meta.with_action(kind);
137        self.action_stats.add(&action, &self.last_action);
138        self.last_action = action;
139        self
140    }
141
142    pub fn collect_action_stats_since_start(&self) -> ActionStatsSnapshot {
143        self.action_stats.since_start.clone()
144    }
145
146    pub fn collect_action_stats_for_block_with_id(
147        &self,
148        id: Option<u64>,
149    ) -> Option<ActionStatsForBlock> {
150        self.action_stats.collect_stats_for_block_with_id(id)
151    }
152
153    pub fn collect_sync_stats(&self, limit: Option<usize>) -> Vec<SyncStatsSnapshot> {
154        self.sync_stats.collect_stats(limit)
155    }
156
157    pub fn get_sync_time(&self) -> Option<Timestamp> {
158        self.sync_stats
159            .collect_stats(Some(1))
160            .first()
161            .and_then(|stats| stats.synced)
162    }
163
164    pub fn staging_ledger_fetch_failure(
165        &mut self,
166        error: &PeerStagedLedgerPartsFetchError,
167        time: Timestamp,
168    ) {
169        self.sync_stats
170            .staging_ledger_fetch_failure(format!("{error:?}"), time)
171    }
172}
173
174impl Default for Stats {
175    fn default() -> Self {
176        Self::new()
177    }
178}