icydb_core/db/diagnostics/
mod.rs1#[cfg(test)]
2mod tests;
3
4use crate::{
5 db::{
6 Db, EntityName,
7 data::{DataKey, StorageKey},
8 index::IndexKey,
9 },
10 error::InternalError,
11 traits::CanisterKind,
12 value::Value,
13};
14use candid::CandidType;
15use serde::{Deserialize, Serialize};
16use std::collections::BTreeMap;
17
18#[derive(CandidType, Clone, Debug, Default, Deserialize, Serialize)]
24pub struct StorageReport {
25 pub storage_data: Vec<DataStoreSnapshot>,
26 pub storage_index: Vec<IndexStoreSnapshot>,
27 pub entity_storage: Vec<EntitySnapshot>,
28 pub corrupted_keys: u64,
29 pub corrupted_entries: u64,
30}
31
32#[derive(CandidType, Clone, Debug, Default, Deserialize, Serialize)]
38pub struct DataStoreSnapshot {
39 pub path: String,
40 pub entries: u64,
41 pub memory_bytes: u64,
42}
43
44#[derive(CandidType, Clone, Debug, Default, Deserialize, Serialize)]
50pub struct IndexStoreSnapshot {
51 pub path: String,
52 pub entries: u64,
53 pub user_entries: u64,
54 pub system_entries: u64,
55 pub memory_bytes: u64,
56}
57
58#[derive(CandidType, Clone, Debug, Default, Deserialize, Serialize)]
64pub struct EntitySnapshot {
65 pub store: String,
67
68 pub path: String,
70
71 pub entries: u64,
73
74 pub memory_bytes: u64,
76
77 pub min_key: Option<Value>,
79
80 pub max_key: Option<Value>,
82}
83
84#[derive(Default)]
90struct EntityStats {
91 entries: u64,
92 memory_bytes: u64,
93 min_key: Option<StorageKey>,
94 max_key: Option<StorageKey>,
95}
96
97impl EntityStats {
98 fn update(&mut self, dk: &DataKey, value_len: u64) {
99 self.entries = self.entries.saturating_add(1);
100 self.memory_bytes = self
101 .memory_bytes
102 .saturating_add(DataKey::entry_size_bytes(value_len));
103
104 let k = dk.storage_key();
105
106 match &mut self.min_key {
107 Some(min) if k < *min => *min = k,
108 None => self.min_key = Some(k),
109 _ => {}
110 }
111
112 match &mut self.max_key {
113 Some(max) if k > *max => *max = k,
114 None => self.max_key = Some(k),
115 _ => {}
116 }
117 }
118}
119
120pub(crate) fn storage_report<C: CanisterKind>(
122 db: &Db<C>,
123 name_to_path: &[(&'static str, &'static str)],
124) -> Result<StorageReport, InternalError> {
125 db.ensure_recovered_state()?;
126 let name_map: BTreeMap<&'static str, &str> = name_to_path.iter().copied().collect();
128 let mut data = Vec::new();
129 let mut index = Vec::new();
130 let mut entity_storage: Vec<EntitySnapshot> = Vec::new();
131 let mut corrupted_keys = 0u64;
132 let mut corrupted_entries = 0u64;
133
134 db.with_store_registry(|reg| {
135 let mut stores = reg.iter().collect::<Vec<_>>();
137 stores.sort_by_key(|(path, _)| *path);
138
139 for (path, store_handle) in stores {
140 store_handle.with_data(|store| {
142 data.push(DataStoreSnapshot {
143 path: path.to_string(),
144 entries: store.len(),
145 memory_bytes: store.memory_bytes(),
146 });
147
148 let mut by_entity: BTreeMap<EntityName, EntityStats> = BTreeMap::new();
150
151 for entry in store.iter() {
152 let Ok(dk) = DataKey::try_from_raw(entry.key()) else {
153 corrupted_keys = corrupted_keys.saturating_add(1);
154 continue;
155 };
156
157 let value_len = entry.value().len() as u64;
158
159 by_entity
160 .entry(*dk.entity_name())
161 .or_default()
162 .update(&dk, value_len);
163 }
164
165 for (entity_name, stats) in by_entity {
166 let path_name = name_map
167 .get(entity_name.as_str())
168 .copied()
169 .unwrap_or(entity_name.as_str());
170 entity_storage.push(EntitySnapshot {
171 store: path.to_string(),
172 path: path_name.to_string(),
173 entries: stats.entries,
174 memory_bytes: stats.memory_bytes,
175 min_key: stats.min_key.map(|key| key.as_value()),
176 max_key: stats.max_key.map(|key| key.as_value()),
177 });
178 }
179 });
180
181 store_handle.with_index(|store| {
183 let mut user_entries = 0u64;
184 let mut system_entries = 0u64;
185
186 for (key, value) in store.entries() {
187 let Ok(decoded_key) = IndexKey::try_from_raw(&key) else {
188 corrupted_entries = corrupted_entries.saturating_add(1);
189 continue;
190 };
191
192 if decoded_key.uses_system_namespace() {
193 system_entries = system_entries.saturating_add(1);
194 } else {
195 user_entries = user_entries.saturating_add(1);
196 }
197
198 if value.validate().is_err() {
199 corrupted_entries = corrupted_entries.saturating_add(1);
200 }
201 }
202
203 index.push(IndexStoreSnapshot {
204 path: path.to_string(),
205 entries: store.len(),
206 user_entries,
207 system_entries,
208 memory_bytes: store.memory_bytes(),
209 });
210 });
211 }
212 });
213
214 entity_storage.sort_by(|left, right| {
217 (left.store.as_str(), left.path.as_str()).cmp(&(right.store.as_str(), right.path.as_str()))
218 });
219
220 Ok(StorageReport {
221 storage_data: data,
222 storage_index: index,
223 entity_storage,
224 corrupted_keys,
225 corrupted_entries,
226 })
227}