Skip to main content

mina_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)]
65// TODO(openapi): custom Serialize impl outputs Map<ActionKind, ActionStatsForRanges>
66#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema), schema(value_type = Object))]
67pub struct ActionStatsSnapshot(Vec<ActionStatsForRanges>);
68
69impl ActionStatsSnapshot {
70    pub fn add(&mut self, action: &ActionKindWithMeta, prev_action: &ActionKindWithMeta) {
71        if *prev_action.action() == ActionKind::None {
72            return;
73        }
74
75        let kind_i = *prev_action.action() as usize;
76        let duration = action
77            .meta()
78            .time_as_nanos()
79            .saturating_sub(prev_action.meta().time_as_nanos());
80
81        // TODO(binier): add constant len in ActionKind instead and use
82        // that for constant vec length.
83        let len = self.0.len();
84        let need_len = kind_i.checked_add(1).expect("overflow");
85        if len < need_len {
86            self.0.resize(need_len, Default::default());
87        }
88        self.0
89            .get_mut(kind_i)
90            .expect("kind_i out of bounds")
91            .add(duration);
92    }
93}
94
95impl Serialize for ActionStatsSnapshot {
96    fn serialize<S>(&self, s: S) -> Result<S::Ok, S::Error>
97    where
98        S: Serializer,
99    {
100        let mut m = s.serialize_map(Some(self.0.len()))?;
101        self.0
102            .iter()
103            .enumerate()
104            .skip(1) // skip `None` action
105            .map(|(i, v)| (ActionKind::try_from(i as u16).unwrap(), v))
106            .try_for_each(|(k, v)| m.serialize_entry(&k, v))?;
107        m.end()
108    }
109}
110
111impl<'de> Deserialize<'de> for ActionStatsSnapshot {
112    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
113    where
114        D: Deserializer<'de>,
115    {
116        let mut m: BTreeMap<ActionKind, ActionStatsForRanges> =
117            Deserialize::deserialize(deserializer)?;
118        let list = (0..ActionKind::COUNT)
119            .map(|i| {
120                let kind = i.try_into().unwrap();
121                m.remove(&kind).unwrap_or_default()
122            })
123            .collect();
124        Ok(Self(list))
125    }
126}
127
128#[derive(Serialize, Deserialize, Debug, Clone)]
129#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
130pub struct ActionStatsForBlock {
131    pub id: u64,
132    pub time: Timestamp,
133    pub block_level: u32,
134    pub block_hash: StateHash,
135    pub cpu_idle: u64,
136    pub cpu_busy: u64,
137    pub stats: ActionStatsSnapshot,
138}
139
140impl ActionStatsForBlock {
141    fn new_action(&mut self, action: &ActionKindWithMeta, prev_action: &ActionKindWithMeta) {
142        let duration = action
143            .meta()
144            .time_as_nanos()
145            .saturating_sub(prev_action.meta().time_as_nanos());
146        match prev_action.action() {
147            ActionKind::None => {}
148            ActionKind::EventSourceWaitForEvents => {
149                self.cpu_idle = self.cpu_idle.saturating_add(duration)
150            }
151            _ => self.cpu_busy = self.cpu_busy.saturating_add(duration),
152        }
153        self.stats.add(action, prev_action);
154    }
155}
156
157#[derive(Serialize, Deserialize, Debug, Default, Clone)]
158#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
159pub struct ActionStatsForRange {
160    /// Total number of times this action kind was executed.
161    pub total_calls: u64,
162    /// Sum of durations from this action till the next one in nanoseconds.
163    pub total_duration: u64,
164    /// Max duration.
165    pub max_duration: u64,
166}
167
168#[derive(Serialize, Deserialize, Debug, Default, Clone)]
169#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
170pub struct ActionStatsForRanges {
171    pub under_1_us: ActionStatsForRange,
172    pub under_10_us: ActionStatsForRange,
173    pub under_50_us: ActionStatsForRange,
174    pub under_100_us: ActionStatsForRange,
175    pub under_500_us: ActionStatsForRange,
176    pub under_1_ms: ActionStatsForRange,
177    pub under_5_ms: ActionStatsForRange,
178    pub under_50_ms: ActionStatsForRange,
179    pub above_50_ms: ActionStatsForRange,
180}
181
182impl ActionStatsForRanges {
183    pub fn add(&mut self, duration: u64) {
184        let stats = if duration <= 1_000 {
185            &mut self.under_1_us
186        } else if duration <= 10_000 {
187            &mut self.under_10_us
188        } else if duration <= 50_000 {
189            &mut self.under_50_us
190        } else if duration <= 100_000 {
191            &mut self.under_100_us
192        } else if duration <= 500_000 {
193            &mut self.under_500_us
194        } else if duration <= 1_000_000 {
195            &mut self.under_1_ms
196        } else if duration <= 5_000_000 {
197            &mut self.under_5_ms
198        } else if duration <= 50_000_000 {
199            &mut self.under_50_ms
200        } else {
201            &mut self.above_50_ms
202        };
203        stats.total_calls = stats.total_calls.saturating_add(1);
204        stats.total_duration = stats.total_duration.saturating_add(duration);
205        stats.max_duration = std::cmp::max(stats.max_duration, duration);
206    }
207}