icydb_core/db/diagnostics/
mod.rs1mod execution_trace;
7#[cfg(test)]
8mod tests;
9
10use crate::{
11 db::{
12 Db, EntityName,
13 data::{DataKey, StorageKey},
14 index::IndexKey,
15 },
16 error::InternalError,
17 traits::CanisterKind,
18 value::Value,
19};
20use candid::CandidType;
21use serde::{Deserialize, Serialize};
22use std::collections::BTreeMap;
23
24pub(crate) use execution_trace::ExecutionOptimizationCounter;
25pub(crate) use execution_trace::record_execution_optimization_hit_for_tests;
26#[cfg(test)]
27pub(crate) use execution_trace::take_execution_optimization_hits_for_tests;
28pub use execution_trace::{
29 ExecutionAccessPathVariant, ExecutionMetrics, ExecutionOptimization, ExecutionTrace,
30};
31
32#[derive(CandidType, Clone, Debug, Default, Deserialize, Serialize)]
38pub struct StorageReport {
39 pub(crate) storage_data: Vec<DataStoreSnapshot>,
40 pub(crate) storage_index: Vec<IndexStoreSnapshot>,
41 pub(crate) entity_storage: Vec<EntitySnapshot>,
42 pub(crate) corrupted_keys: u64,
43 pub(crate) corrupted_entries: u64,
44}
45
46impl StorageReport {
47 #[must_use]
49 pub const fn new(
50 storage_data: Vec<DataStoreSnapshot>,
51 storage_index: Vec<IndexStoreSnapshot>,
52 entity_storage: Vec<EntitySnapshot>,
53 corrupted_keys: u64,
54 corrupted_entries: u64,
55 ) -> Self {
56 Self {
57 storage_data,
58 storage_index,
59 entity_storage,
60 corrupted_keys,
61 corrupted_entries,
62 }
63 }
64
65 #[must_use]
67 pub const fn storage_data(&self) -> &[DataStoreSnapshot] {
68 self.storage_data.as_slice()
69 }
70
71 #[must_use]
73 pub const fn storage_index(&self) -> &[IndexStoreSnapshot] {
74 self.storage_index.as_slice()
75 }
76
77 #[must_use]
79 pub const fn entity_storage(&self) -> &[EntitySnapshot] {
80 self.entity_storage.as_slice()
81 }
82
83 #[must_use]
85 pub const fn corrupted_keys(&self) -> u64 {
86 self.corrupted_keys
87 }
88
89 #[must_use]
91 pub const fn corrupted_entries(&self) -> u64 {
92 self.corrupted_entries
93 }
94}
95
96#[derive(CandidType, Clone, Debug, Default, Deserialize, Serialize)]
102pub struct DataStoreSnapshot {
103 pub(crate) path: String,
104 pub(crate) entries: u64,
105 pub(crate) memory_bytes: u64,
106}
107
108impl DataStoreSnapshot {
109 #[must_use]
111 pub const fn new(path: String, entries: u64, memory_bytes: u64) -> Self {
112 Self {
113 path,
114 entries,
115 memory_bytes,
116 }
117 }
118
119 #[must_use]
121 pub const fn path(&self) -> &str {
122 self.path.as_str()
123 }
124
125 #[must_use]
127 pub const fn entries(&self) -> u64 {
128 self.entries
129 }
130
131 #[must_use]
133 pub const fn memory_bytes(&self) -> u64 {
134 self.memory_bytes
135 }
136}
137
138#[derive(CandidType, Clone, Debug, Default, Deserialize, Serialize)]
144pub struct IndexStoreSnapshot {
145 pub(crate) path: String,
146 pub(crate) entries: u64,
147 pub(crate) user_entries: u64,
148 pub(crate) system_entries: u64,
149 pub(crate) memory_bytes: u64,
150}
151
152impl IndexStoreSnapshot {
153 #[must_use]
155 pub const fn new(
156 path: String,
157 entries: u64,
158 user_entries: u64,
159 system_entries: u64,
160 memory_bytes: u64,
161 ) -> Self {
162 Self {
163 path,
164 entries,
165 user_entries,
166 system_entries,
167 memory_bytes,
168 }
169 }
170
171 #[must_use]
173 pub const fn path(&self) -> &str {
174 self.path.as_str()
175 }
176
177 #[must_use]
179 pub const fn entries(&self) -> u64 {
180 self.entries
181 }
182
183 #[must_use]
185 pub const fn user_entries(&self) -> u64 {
186 self.user_entries
187 }
188
189 #[must_use]
191 pub const fn system_entries(&self) -> u64 {
192 self.system_entries
193 }
194
195 #[must_use]
197 pub const fn memory_bytes(&self) -> u64 {
198 self.memory_bytes
199 }
200}
201
202#[derive(CandidType, Clone, Debug, Default, Deserialize, Serialize)]
208pub struct EntitySnapshot {
209 pub(crate) store: String,
211
212 pub(crate) path: String,
214
215 pub(crate) entries: u64,
217
218 pub(crate) memory_bytes: u64,
220
221 pub(crate) min_key: Option<Value>,
223
224 pub(crate) max_key: Option<Value>,
226}
227
228impl EntitySnapshot {
229 #[must_use]
231 pub const fn new(
232 store: String,
233 path: String,
234 entries: u64,
235 memory_bytes: u64,
236 min_key: Option<Value>,
237 max_key: Option<Value>,
238 ) -> Self {
239 Self {
240 store,
241 path,
242 entries,
243 memory_bytes,
244 min_key,
245 max_key,
246 }
247 }
248
249 #[must_use]
251 pub const fn store(&self) -> &str {
252 self.store.as_str()
253 }
254
255 #[must_use]
257 pub const fn path(&self) -> &str {
258 self.path.as_str()
259 }
260
261 #[must_use]
263 pub const fn entries(&self) -> u64 {
264 self.entries
265 }
266
267 #[must_use]
269 pub const fn memory_bytes(&self) -> u64 {
270 self.memory_bytes
271 }
272
273 #[must_use]
275 pub const fn min_key(&self) -> Option<&Value> {
276 self.min_key.as_ref()
277 }
278
279 #[must_use]
281 pub const fn max_key(&self) -> Option<&Value> {
282 self.max_key.as_ref()
283 }
284}
285
286#[derive(Default)]
292struct EntityStats {
293 entries: u64,
294 memory_bytes: u64,
295 min_key: Option<StorageKey>,
296 max_key: Option<StorageKey>,
297}
298
299impl EntityStats {
300 fn update(&mut self, dk: &DataKey, value_len: u64) {
302 self.entries = self.entries.saturating_add(1);
303 self.memory_bytes = self
304 .memory_bytes
305 .saturating_add(DataKey::entry_size_bytes(value_len));
306
307 let k = dk.storage_key();
308
309 match &mut self.min_key {
310 Some(min) if k < *min => *min = k,
311 None => self.min_key = Some(k),
312 _ => {}
313 }
314
315 match &mut self.max_key {
316 Some(max) if k > *max => *max = k,
317 None => self.max_key = Some(k),
318 _ => {}
319 }
320 }
321}
322
323pub(crate) fn storage_report<C: CanisterKind>(
328 db: &Db<C>,
329 name_to_path: &[(&'static str, &'static str)],
330) -> Result<StorageReport, InternalError> {
331 db.ensure_recovered_state()?;
332 let name_map: BTreeMap<&'static str, &str> = name_to_path.iter().copied().collect();
334 let mut data = Vec::new();
335 let mut index = Vec::new();
336 let mut entity_storage: Vec<EntitySnapshot> = Vec::new();
337 let mut corrupted_keys = 0u64;
338 let mut corrupted_entries = 0u64;
339
340 db.with_store_registry(|reg| {
341 let mut stores = reg.iter().collect::<Vec<_>>();
343 stores.sort_by_key(|(path, _)| *path);
344
345 for (path, store_handle) in stores {
346 store_handle.with_data(|store| {
348 data.push(DataStoreSnapshot::new(
349 path.to_string(),
350 store.len(),
351 store.memory_bytes(),
352 ));
353
354 let mut by_entity: BTreeMap<EntityName, EntityStats> = BTreeMap::new();
356
357 for entry in store.iter() {
358 let Ok(dk) = DataKey::try_from_raw(entry.key()) else {
359 corrupted_keys = corrupted_keys.saturating_add(1);
360 continue;
361 };
362
363 let value_len = entry.value().len() as u64;
364
365 by_entity
366 .entry(*dk.entity_name())
367 .or_default()
368 .update(&dk, value_len);
369 }
370
371 for (entity_name, stats) in by_entity {
372 let path_name = name_map
373 .get(entity_name.as_str())
374 .copied()
375 .unwrap_or(entity_name.as_str());
376 entity_storage.push(EntitySnapshot::new(
377 path.to_string(),
378 path_name.to_string(),
379 stats.entries,
380 stats.memory_bytes,
381 stats.min_key.map(|key| key.as_value()),
382 stats.max_key.map(|key| key.as_value()),
383 ));
384 }
385 });
386
387 store_handle.with_index(|store| {
389 let mut user_entries = 0u64;
390 let mut system_entries = 0u64;
391
392 for (key, value) in store.entries() {
393 let Ok(decoded_key) = IndexKey::try_from_raw(&key) else {
394 corrupted_entries = corrupted_entries.saturating_add(1);
395 continue;
396 };
397
398 if decoded_key.uses_system_namespace() {
399 system_entries = system_entries.saturating_add(1);
400 } else {
401 user_entries = user_entries.saturating_add(1);
402 }
403
404 if value.validate().is_err() {
405 corrupted_entries = corrupted_entries.saturating_add(1);
406 }
407 }
408
409 index.push(IndexStoreSnapshot::new(
410 path.to_string(),
411 store.len(),
412 user_entries,
413 system_entries,
414 store.memory_bytes(),
415 ));
416 });
417 }
418 });
419
420 entity_storage
423 .sort_by(|left, right| (left.store(), left.path()).cmp(&(right.store(), right.path())));
424
425 Ok(StorageReport::new(
426 data,
427 index,
428 entity_storage,
429 corrupted_keys,
430 corrupted_entries,
431 ))
432}