node/stats/
stats_actions.rs1use 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 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 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) .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 pub total_calls: u64,
158 pub total_duration: u64,
160 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}