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