mina_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)]
65#[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 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) .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 pub total_calls: u64,
162 pub total_duration: u64,
164 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}