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 storage_data: Vec<DataStoreSnapshot>,
36 pub storage_index: Vec<IndexStoreSnapshot>,
37 pub entity_storage: Vec<EntitySnapshot>,
38 pub corrupted_keys: u64,
39 pub corrupted_entries: u64,
40}
41
42#[derive(CandidType, Clone, Debug, Default, Deserialize, Serialize)]
48pub struct DataStoreSnapshot {
49 pub path: String,
50 pub entries: u64,
51 pub memory_bytes: u64,
52}
53
54#[derive(CandidType, Clone, Debug, Default, Deserialize, Serialize)]
60pub struct IndexStoreSnapshot {
61 pub path: String,
62 pub entries: u64,
63 pub user_entries: u64,
64 pub system_entries: u64,
65 pub memory_bytes: u64,
66}
67
68#[derive(CandidType, Clone, Debug, Default, Deserialize, Serialize)]
74pub struct EntitySnapshot {
75 pub store: String,
77
78 pub path: String,
80
81 pub entries: u64,
83
84 pub memory_bytes: u64,
86
87 pub min_key: Option<Value>,
89
90 pub max_key: Option<Value>,
92}
93
94#[derive(Default)]
100struct EntityStats {
101 entries: u64,
102 memory_bytes: u64,
103 min_key: Option<StorageKey>,
104 max_key: Option<StorageKey>,
105}
106
107impl EntityStats {
108 fn update(&mut self, dk: &DataKey, value_len: u64) {
110 self.entries = self.entries.saturating_add(1);
111 self.memory_bytes = self
112 .memory_bytes
113 .saturating_add(DataKey::entry_size_bytes(value_len));
114
115 let k = dk.storage_key();
116
117 match &mut self.min_key {
118 Some(min) if k < *min => *min = k,
119 None => self.min_key = Some(k),
120 _ => {}
121 }
122
123 match &mut self.max_key {
124 Some(max) if k > *max => *max = k,
125 None => self.max_key = Some(k),
126 _ => {}
127 }
128 }
129}
130
131pub(crate) fn storage_report<C: CanisterKind>(
136 db: &Db<C>,
137 name_to_path: &[(&'static str, &'static str)],
138) -> Result<StorageReport, InternalError> {
139 db.ensure_recovered_state()?;
140 let name_map: BTreeMap<&'static str, &str> = name_to_path.iter().copied().collect();
142 let mut data = Vec::new();
143 let mut index = Vec::new();
144 let mut entity_storage: Vec<EntitySnapshot> = Vec::new();
145 let mut corrupted_keys = 0u64;
146 let mut corrupted_entries = 0u64;
147
148 db.with_store_registry(|reg| {
149 let mut stores = reg.iter().collect::<Vec<_>>();
151 stores.sort_by_key(|(path, _)| *path);
152
153 for (path, store_handle) in stores {
154 store_handle.with_data(|store| {
156 data.push(DataStoreSnapshot {
157 path: path.to_string(),
158 entries: store.len(),
159 memory_bytes: store.memory_bytes(),
160 });
161
162 let mut by_entity: BTreeMap<EntityName, EntityStats> = BTreeMap::new();
164
165 for entry in store.iter() {
166 let Ok(dk) = DataKey::try_from_raw(entry.key()) else {
167 corrupted_keys = corrupted_keys.saturating_add(1);
168 continue;
169 };
170
171 let value_len = entry.value().len() as u64;
172
173 by_entity
174 .entry(*dk.entity_name())
175 .or_default()
176 .update(&dk, value_len);
177 }
178
179 for (entity_name, stats) in by_entity {
180 let path_name = name_map
181 .get(entity_name.as_str())
182 .copied()
183 .unwrap_or(entity_name.as_str());
184 entity_storage.push(EntitySnapshot {
185 store: path.to_string(),
186 path: path_name.to_string(),
187 entries: stats.entries,
188 memory_bytes: stats.memory_bytes,
189 min_key: stats.min_key.map(|key| key.as_value()),
190 max_key: stats.max_key.map(|key| key.as_value()),
191 });
192 }
193 });
194
195 store_handle.with_index(|store| {
197 let mut user_entries = 0u64;
198 let mut system_entries = 0u64;
199
200 for (key, value) in store.entries() {
201 let Ok(decoded_key) = IndexKey::try_from_raw(&key) else {
202 corrupted_entries = corrupted_entries.saturating_add(1);
203 continue;
204 };
205
206 if decoded_key.uses_system_namespace() {
207 system_entries = system_entries.saturating_add(1);
208 } else {
209 user_entries = user_entries.saturating_add(1);
210 }
211
212 if value.validate().is_err() {
213 corrupted_entries = corrupted_entries.saturating_add(1);
214 }
215 }
216
217 index.push(IndexStoreSnapshot {
218 path: path.to_string(),
219 entries: store.len(),
220 user_entries,
221 system_entries,
222 memory_bytes: store.memory_bytes(),
223 });
224 });
225 }
226 });
227
228 entity_storage.sort_by(|left, right| {
231 (left.store.as_str(), left.path.as_str()).cmp(&(right.store.as_str(), right.path.as_str()))
232 });
233
234 Ok(StorageReport {
235 storage_data: data,
236 storage_index: index,
237 entity_storage,
238 corrupted_keys,
239 corrupted_entries,
240 })
241}