node/stats/
stats_actions.rs

1use std::collections::{BTreeMap, VecDeque};
2
3use mina_p2p_messages::v2::StateHash;
4use redux::Timestamp;
5use serde::{ser::SerializeMap, Deserialize, Deserializer, Serialize, Serializer};
6
7use crate::ActionKind;
8
9use super::ActionKindWithMeta;
10
11#[derive(Default, Clone)]
12pub struct ActionStats {
13    /// Stats since the start of the node, indexed by `ActionKind`.
14    pub(super) since_start: ActionStatsSnapshot,
15    pub(super) per_block: VecDeque<ActionStatsForBlock>,
16}
17
18impl ActionStats {
19    pub fn new_best_tip(&mut self, time: Timestamp, level: u32, hash: StateHash) {
20        while self.per_block.len() >= 20000 {
21            self.per_block.pop_back();
22        }
23        let id = self
24            .per_block
25            .back()
26            .map_or(0, |v| v.id.checked_add(1).expect("overflow"));
27        self.per_block.push_back(ActionStatsForBlock {
28            id,
29            time,
30            block_level: level,
31            block_hash: hash,
32            cpu_idle: 0,
33            cpu_busy: 0,
34            stats: Default::default(),
35        });
36    }
37
38    pub fn add(&mut self, action: &ActionKindWithMeta, prev_action: &ActionKindWithMeta) {
39        self.since_start.add(action, prev_action);
40        if let Some(stats) = self.per_block.back_mut() {
41            stats.new_action(action, prev_action);
42        }
43    }
44
45    pub fn collect_stats_for_block_with_id(&self, id: Option<u64>) -> Option<ActionStatsForBlock> {
46        let blocks = &self.per_block;
47        let last = blocks.back()?;
48        let id = match id {
49            Some(id) => {
50                if id == last.id {
51                    return Some(last.clone());
52                }
53                id
54            }
55            None => return Some(last.clone()),
56        };
57        let i = id
58            .checked_add(blocks.len() as u64)?
59            .checked_sub(last.id.checked_add(1)?)?;
60        blocks.get(i as usize).cloned()
61    }
62}
63
64#[derive(Debug, Default, Clone)]
65pub struct ActionStatsSnapshot(Vec<ActionStatsForRanges>);
66
67impl ActionStatsSnapshot {
68    pub fn add(&mut self, action: &ActionKindWithMeta, prev_action: &ActionKindWithMeta) {
69        if *prev_action.action() == ActionKind::None {
70            return;
71        }
72
73        let kind_i = *prev_action.action() as usize;
74        let duration = action
75            .meta()
76            .time_as_nanos()
77            .saturating_sub(prev_action.meta().time_as_nanos());
78
79        // TODO(binier): add constant len in ActionKind instead and use
80        // that for constant vec length.
81        let len = self.0.len();
82        let need_len = kind_i.checked_add(1).expect("overflow");
83        if len < need_len {
84            self.0.resize(need_len, Default::default());
85        }
86        self.0
87            .get_mut(kind_i)
88            .expect("kind_i out of bounds")
89            .add(duration);
90    }
91}
92
93impl Serialize for ActionStatsSnapshot {
94    fn serialize<S>(&self, s: S) -> Result<S::Ok, S::Error>
95    where
96        S: Serializer,
97    {
98        let mut m = s.serialize_map(Some(self.0.len()))?;
99        self.0
100            .iter()
101            .enumerate()
102            .skip(1) // skip `None` action
103            .map(|(i, v)| (ActionKind::try_from(i as u16).unwrap(), v))
104            .try_for_each(|(k, v)| m.serialize_entry(&k, v))?;
105        m.end()
106    }
107}
108
109impl<'de> Deserialize<'de> for ActionStatsSnapshot {
110    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
111    where
112        D: Deserializer<'de>,
113    {
114        let mut m: BTreeMap<ActionKind, ActionStatsForRanges> =
115            Deserialize::deserialize(deserializer)?;
116        let list = (0..ActionKind::COUNT)
117            .map(|i| {
118                let kind = i.try_into().unwrap();
119                m.remove(&kind).unwrap_or_default()
120            })
121            .collect();
122        Ok(Self(list))
123    }
124}
125
126#[derive(Serialize, Deserialize, Debug, Clone)]
127pub struct ActionStatsForBlock {
128    pub id: u64,
129    pub time: Timestamp,
130    pub block_level: u32,
131    pub block_hash: StateHash,
132    pub cpu_idle: u64,
133    pub cpu_busy: u64,
134    pub stats: ActionStatsSnapshot,
135}
136
137impl ActionStatsForBlock {
138    fn new_action(&mut self, action: &ActionKindWithMeta, prev_action: &ActionKindWithMeta) {
139        let duration = action
140            .meta()
141            .time_as_nanos()
142            .saturating_sub(prev_action.meta().time_as_nanos());
143        match prev_action.action() {
144            ActionKind::None => {}
145            ActionKind::EventSourceWaitForEvents => {
146                self.cpu_idle = self.cpu_idle.saturating_add(duration)
147            }
148            _ => self.cpu_busy = self.cpu_busy.saturating_add(duration),
149        }
150        self.stats.add(action, prev_action);
151    }
152}
153
154#[derive(Serialize, Deserialize, Debug, Default, Clone)]
155pub struct ActionStatsForRange {
156    /// Total number of times this action kind was executed.
157    pub total_calls: u64,
158    /// Sum of durations from this action till the next one in nanoseconds.
159    pub total_duration: u64,
160    /// Max duration.
161    pub max_duration: u64,
162}
163
164#[derive(Serialize, Deserialize, Debug, Default, Clone)]
165pub struct ActionStatsForRanges {
166    pub under_1_us: ActionStatsForRange,
167    pub under_10_us: ActionStatsForRange,
168    pub under_50_us: ActionStatsForRange,
169    pub under_100_us: ActionStatsForRange,
170    pub under_500_us: ActionStatsForRange,
171    pub under_1_ms: ActionStatsForRange,
172    pub under_5_ms: ActionStatsForRange,
173    pub under_50_ms: ActionStatsForRange,
174    pub above_50_ms: ActionStatsForRange,
175}
176
177impl ActionStatsForRanges {
178    pub fn add(&mut self, duration: u64) {
179        let stats = if duration <= 1_000 {
180            &mut self.under_1_us
181        } else if duration <= 10_000 {
182            &mut self.under_10_us
183        } else if duration <= 50_000 {
184            &mut self.under_50_us
185        } else if duration <= 100_000 {
186            &mut self.under_100_us
187        } else if duration <= 500_000 {
188            &mut self.under_500_us
189        } else if duration <= 1_000_000 {
190            &mut self.under_1_ms
191        } else if duration <= 5_000_000 {
192            &mut self.under_5_ms
193        } else if duration <= 50_000_000 {
194            &mut self.under_50_ms
195        } else {
196            &mut self.above_50_ms
197        };
198        stats.total_calls = stats.total_calls.saturating_add(1);
199        stats.total_duration = stats.total_duration.saturating_add(duration);
200        stats.max_duration = std::cmp::max(stats.max_duration, duration);
201    }
202}