icydb_core/db/diagnostics/
mod.rs1#[cfg(test)]
7mod tests;
8
9use crate::{
10 db::{
11 Db, EntityName,
12 data::{DataKey, StorageKey},
13 index::IndexKey,
14 },
15 error::InternalError,
16 traits::CanisterKind,
17 value::Value,
18};
19use candid::CandidType;
20use serde::{Deserialize, Serialize};
21use std::collections::BTreeMap;
22
23#[derive(CandidType, Clone, Debug, Default, Deserialize, Serialize)]
29pub struct StorageReport {
30 pub storage_data: Vec<DataStoreSnapshot>,
31 pub storage_index: Vec<IndexStoreSnapshot>,
32 pub entity_storage: Vec<EntitySnapshot>,
33 pub corrupted_keys: u64,
34 pub corrupted_entries: u64,
35}
36
37#[derive(CandidType, Clone, Debug, Default, Deserialize, Serialize)]
43pub struct DataStoreSnapshot {
44 pub path: String,
45 pub entries: u64,
46 pub memory_bytes: u64,
47}
48
49#[derive(CandidType, Clone, Debug, Default, Deserialize, Serialize)]
55pub struct IndexStoreSnapshot {
56 pub path: String,
57 pub entries: u64,
58 pub user_entries: u64,
59 pub system_entries: u64,
60 pub memory_bytes: u64,
61}
62
63#[derive(CandidType, Clone, Debug, Default, Deserialize, Serialize)]
69pub struct EntitySnapshot {
70 pub store: String,
72
73 pub path: String,
75
76 pub entries: u64,
78
79 pub memory_bytes: u64,
81
82 pub min_key: Option<Value>,
84
85 pub max_key: Option<Value>,
87}
88
89#[derive(Default)]
95struct EntityStats {
96 entries: u64,
97 memory_bytes: u64,
98 min_key: Option<StorageKey>,
99 max_key: Option<StorageKey>,
100}
101
102impl EntityStats {
103 fn update(&mut self, dk: &DataKey, value_len: u64) {
105 self.entries = self.entries.saturating_add(1);
106 self.memory_bytes = self
107 .memory_bytes
108 .saturating_add(DataKey::entry_size_bytes(value_len));
109
110 let k = dk.storage_key();
111
112 match &mut self.min_key {
113 Some(min) if k < *min => *min = k,
114 None => self.min_key = Some(k),
115 _ => {}
116 }
117
118 match &mut self.max_key {
119 Some(max) if k > *max => *max = k,
120 None => self.max_key = Some(k),
121 _ => {}
122 }
123 }
124}
125
126pub(crate) fn storage_report<C: CanisterKind>(
131 db: &Db<C>,
132 name_to_path: &[(&'static str, &'static str)],
133) -> Result<StorageReport, InternalError> {
134 db.ensure_recovered_state()?;
135 let name_map: BTreeMap<&'static str, &str> = name_to_path.iter().copied().collect();
137 let mut data = Vec::new();
138 let mut index = Vec::new();
139 let mut entity_storage: Vec<EntitySnapshot> = Vec::new();
140 let mut corrupted_keys = 0u64;
141 let mut corrupted_entries = 0u64;
142
143 db.with_store_registry(|reg| {
144 let mut stores = reg.iter().collect::<Vec<_>>();
146 stores.sort_by_key(|(path, _)| *path);
147
148 for (path, store_handle) in stores {
149 store_handle.with_data(|store| {
151 data.push(DataStoreSnapshot {
152 path: path.to_string(),
153 entries: store.len(),
154 memory_bytes: store.memory_bytes(),
155 });
156
157 let mut by_entity: BTreeMap<EntityName, EntityStats> = BTreeMap::new();
159
160 for entry in store.iter() {
161 let Ok(dk) = DataKey::try_from_raw(entry.key()) else {
162 corrupted_keys = corrupted_keys.saturating_add(1);
163 continue;
164 };
165
166 let value_len = entry.value().len() as u64;
167
168 by_entity
169 .entry(*dk.entity_name())
170 .or_default()
171 .update(&dk, value_len);
172 }
173
174 for (entity_name, stats) in by_entity {
175 let path_name = name_map
176 .get(entity_name.as_str())
177 .copied()
178 .unwrap_or(entity_name.as_str());
179 entity_storage.push(EntitySnapshot {
180 store: path.to_string(),
181 path: path_name.to_string(),
182 entries: stats.entries,
183 memory_bytes: stats.memory_bytes,
184 min_key: stats.min_key.map(|key| key.as_value()),
185 max_key: stats.max_key.map(|key| key.as_value()),
186 });
187 }
188 });
189
190 store_handle.with_index(|store| {
192 let mut user_entries = 0u64;
193 let mut system_entries = 0u64;
194
195 for (key, value) in store.entries() {
196 let Ok(decoded_key) = IndexKey::try_from_raw(&key) else {
197 corrupted_entries = corrupted_entries.saturating_add(1);
198 continue;
199 };
200
201 if decoded_key.uses_system_namespace() {
202 system_entries = system_entries.saturating_add(1);
203 } else {
204 user_entries = user_entries.saturating_add(1);
205 }
206
207 if value.validate().is_err() {
208 corrupted_entries = corrupted_entries.saturating_add(1);
209 }
210 }
211
212 index.push(IndexStoreSnapshot {
213 path: path.to_string(),
214 entries: store.len(),
215 user_entries,
216 system_entries,
217 memory_bytes: store.memory_bytes(),
218 });
219 });
220 }
221 });
222
223 entity_storage.sort_by(|left, right| {
226 (left.store.as_str(), left.path.as_str()).cmp(&(right.store.as_str(), right.path.as_str()))
227 });
228
229 Ok(StorageReport {
230 storage_data: data,
231 storage_index: index,
232 entity_storage,
233 corrupted_keys,
234 corrupted_entries,
235 })
236}