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
24#[cfg(test)]
25pub(crate) use execution_trace::ExecutionOptimizationCounter;
26pub use execution_trace::{
27 ExecutionAccessPathVariant, ExecutionMetrics, ExecutionOptimization, ExecutionTrace,
28};
29
30#[derive(CandidType, Clone, Debug, Default, Deserialize, Serialize)]
36pub struct StorageReport {
37 pub(crate) storage_data: Vec<DataStoreSnapshot>,
38 pub(crate) storage_index: Vec<IndexStoreSnapshot>,
39 pub(crate) entity_storage: Vec<EntitySnapshot>,
40 pub(crate) corrupted_keys: u64,
41 pub(crate) corrupted_entries: u64,
42}
43
44impl StorageReport {
45 #[must_use]
47 pub const fn new(
48 storage_data: Vec<DataStoreSnapshot>,
49 storage_index: Vec<IndexStoreSnapshot>,
50 entity_storage: Vec<EntitySnapshot>,
51 corrupted_keys: u64,
52 corrupted_entries: u64,
53 ) -> Self {
54 Self {
55 storage_data,
56 storage_index,
57 entity_storage,
58 corrupted_keys,
59 corrupted_entries,
60 }
61 }
62
63 #[must_use]
65 pub const fn storage_data(&self) -> &[DataStoreSnapshot] {
66 self.storage_data.as_slice()
67 }
68
69 #[must_use]
71 pub const fn storage_index(&self) -> &[IndexStoreSnapshot] {
72 self.storage_index.as_slice()
73 }
74
75 #[must_use]
77 pub const fn entity_storage(&self) -> &[EntitySnapshot] {
78 self.entity_storage.as_slice()
79 }
80
81 #[must_use]
83 pub const fn corrupted_keys(&self) -> u64 {
84 self.corrupted_keys
85 }
86
87 #[must_use]
89 pub const fn corrupted_entries(&self) -> u64 {
90 self.corrupted_entries
91 }
92}
93
94#[derive(CandidType, Clone, Debug, Default, Deserialize, Serialize)]
100pub struct DataStoreSnapshot {
101 pub(crate) path: String,
102 pub(crate) entries: u64,
103 pub(crate) memory_bytes: u64,
104}
105
106impl DataStoreSnapshot {
107 #[must_use]
109 pub const fn new(path: String, entries: u64, memory_bytes: u64) -> Self {
110 Self {
111 path,
112 entries,
113 memory_bytes,
114 }
115 }
116
117 #[must_use]
119 pub const fn path(&self) -> &str {
120 self.path.as_str()
121 }
122
123 #[must_use]
125 pub const fn entries(&self) -> u64 {
126 self.entries
127 }
128
129 #[must_use]
131 pub const fn memory_bytes(&self) -> u64 {
132 self.memory_bytes
133 }
134}
135
136#[derive(CandidType, Clone, Debug, Default, Deserialize, Serialize)]
142pub struct IndexStoreSnapshot {
143 pub(crate) path: String,
144 pub(crate) entries: u64,
145 pub(crate) user_entries: u64,
146 pub(crate) system_entries: u64,
147 pub(crate) memory_bytes: u64,
148}
149
150impl IndexStoreSnapshot {
151 #[must_use]
153 pub const fn new(
154 path: String,
155 entries: u64,
156 user_entries: u64,
157 system_entries: u64,
158 memory_bytes: u64,
159 ) -> Self {
160 Self {
161 path,
162 entries,
163 user_entries,
164 system_entries,
165 memory_bytes,
166 }
167 }
168
169 #[must_use]
171 pub const fn path(&self) -> &str {
172 self.path.as_str()
173 }
174
175 #[must_use]
177 pub const fn entries(&self) -> u64 {
178 self.entries
179 }
180
181 #[must_use]
183 pub const fn user_entries(&self) -> u64 {
184 self.user_entries
185 }
186
187 #[must_use]
189 pub const fn system_entries(&self) -> u64 {
190 self.system_entries
191 }
192
193 #[must_use]
195 pub const fn memory_bytes(&self) -> u64 {
196 self.memory_bytes
197 }
198}
199
200#[derive(CandidType, Clone, Debug, Default, Deserialize, Serialize)]
206pub struct EntitySnapshot {
207 pub(crate) store: String,
209
210 pub(crate) path: String,
212
213 pub(crate) entries: u64,
215
216 pub(crate) memory_bytes: u64,
218
219 pub(crate) min_key: Option<Value>,
221
222 pub(crate) max_key: Option<Value>,
224}
225
226impl EntitySnapshot {
227 #[must_use]
229 pub const fn new(
230 store: String,
231 path: String,
232 entries: u64,
233 memory_bytes: u64,
234 min_key: Option<Value>,
235 max_key: Option<Value>,
236 ) -> Self {
237 Self {
238 store,
239 path,
240 entries,
241 memory_bytes,
242 min_key,
243 max_key,
244 }
245 }
246
247 #[must_use]
249 pub const fn store(&self) -> &str {
250 self.store.as_str()
251 }
252
253 #[must_use]
255 pub const fn path(&self) -> &str {
256 self.path.as_str()
257 }
258
259 #[must_use]
261 pub const fn entries(&self) -> u64 {
262 self.entries
263 }
264
265 #[must_use]
267 pub const fn memory_bytes(&self) -> u64 {
268 self.memory_bytes
269 }
270
271 #[must_use]
273 pub const fn min_key(&self) -> Option<&Value> {
274 self.min_key.as_ref()
275 }
276
277 #[must_use]
279 pub const fn max_key(&self) -> Option<&Value> {
280 self.max_key.as_ref()
281 }
282}
283
284#[derive(Default)]
290struct EntityStats {
291 entries: u64,
292 memory_bytes: u64,
293 min_key: Option<StorageKey>,
294 max_key: Option<StorageKey>,
295}
296
297impl EntityStats {
298 fn update(&mut self, dk: &DataKey, value_len: u64) {
300 self.entries = self.entries.saturating_add(1);
301 self.memory_bytes = self
302 .memory_bytes
303 .saturating_add(DataKey::entry_size_bytes(value_len));
304
305 let k = dk.storage_key();
306
307 match &mut self.min_key {
308 Some(min) if k < *min => *min = k,
309 None => self.min_key = Some(k),
310 _ => {}
311 }
312
313 match &mut self.max_key {
314 Some(max) if k > *max => *max = k,
315 None => self.max_key = Some(k),
316 _ => {}
317 }
318 }
319}
320
321pub(crate) fn storage_report<C: CanisterKind>(
326 db: &Db<C>,
327 name_to_path: &[(&'static str, &'static str)],
328) -> Result<StorageReport, InternalError> {
329 db.ensure_recovered_state()?;
330 let name_map: BTreeMap<&'static str, &str> = name_to_path.iter().copied().collect();
332 let mut data = Vec::new();
333 let mut index = Vec::new();
334 let mut entity_storage: Vec<EntitySnapshot> = Vec::new();
335 let mut corrupted_keys = 0u64;
336 let mut corrupted_entries = 0u64;
337
338 db.with_store_registry(|reg| {
339 let mut stores = reg.iter().collect::<Vec<_>>();
341 stores.sort_by_key(|(path, _)| *path);
342
343 for (path, store_handle) in stores {
344 store_handle.with_data(|store| {
346 data.push(DataStoreSnapshot::new(
347 path.to_string(),
348 store.len(),
349 store.memory_bytes(),
350 ));
351
352 let mut by_entity: BTreeMap<EntityName, EntityStats> = BTreeMap::new();
354
355 for entry in store.iter() {
356 let Ok(dk) = DataKey::try_from_raw(entry.key()) else {
357 corrupted_keys = corrupted_keys.saturating_add(1);
358 continue;
359 };
360
361 let value_len = entry.value().len() as u64;
362
363 by_entity
364 .entry(*dk.entity_name())
365 .or_default()
366 .update(&dk, value_len);
367 }
368
369 for (entity_name, stats) in by_entity {
370 let path_name = name_map
371 .get(entity_name.as_str())
372 .copied()
373 .unwrap_or(entity_name.as_str());
374 entity_storage.push(EntitySnapshot::new(
375 path.to_string(),
376 path_name.to_string(),
377 stats.entries,
378 stats.memory_bytes,
379 stats.min_key.map(|key| key.as_value()),
380 stats.max_key.map(|key| key.as_value()),
381 ));
382 }
383 });
384
385 store_handle.with_index(|store| {
387 let mut user_entries = 0u64;
388 let mut system_entries = 0u64;
389
390 for (key, value) in store.entries() {
391 let Ok(decoded_key) = IndexKey::try_from_raw(&key) else {
392 corrupted_entries = corrupted_entries.saturating_add(1);
393 continue;
394 };
395
396 if decoded_key.uses_system_namespace() {
397 system_entries = system_entries.saturating_add(1);
398 } else {
399 user_entries = user_entries.saturating_add(1);
400 }
401
402 if value.validate().is_err() {
403 corrupted_entries = corrupted_entries.saturating_add(1);
404 }
405 }
406
407 index.push(IndexStoreSnapshot::new(
408 path.to_string(),
409 store.len(),
410 user_entries,
411 system_entries,
412 store.memory_bytes(),
413 ));
414 });
415 }
416 });
417
418 entity_storage
421 .sort_by(|left, right| (left.store(), left.path()).cmp(&(right.store(), right.path())));
422
423 Ok(StorageReport::new(
424 data,
425 index,
426 entity_storage,
427 corrupted_keys,
428 corrupted_entries,
429 ))
430}