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