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