icydb_core/obs/snapshot/
mod.rs1use crate::{
2 db::{Db, store::DataKey},
3 traits::CanisterKind,
4};
5use candid::CandidType;
6use serde::{Deserialize, Serialize};
7use std::collections::BTreeMap;
8
9#[derive(CandidType, Clone, Debug, Default, Deserialize, Serialize)]
15pub struct StorageReport {
16 pub storage_data: Vec<DataStoreSnapshot>,
17 pub storage_index: Vec<IndexStoreSnapshot>,
18 pub entity_storage: Vec<EntitySnapshot>,
19}
20
21#[derive(CandidType, Clone, Debug, Default, Deserialize, Serialize)]
27pub struct DataStoreSnapshot {
28 pub path: String,
29 pub entries: u64,
30 pub memory_bytes: u64,
31}
32
33#[derive(CandidType, Clone, Debug, Default, Deserialize, Serialize)]
39pub struct IndexStoreSnapshot {
40 pub path: String,
41 pub entries: u64,
42 pub memory_bytes: u64,
43}
44
45#[derive(CandidType, Clone, Debug, Default, Deserialize, Serialize)]
51pub struct EntitySnapshot {
52 pub store: String,
54 pub path: String,
56 pub entries: u64,
58 pub memory_bytes: u64,
60 pub min_key: Option<DataKey>,
62 pub max_key: Option<DataKey>,
64}
65
66#[derive(Default)]
72struct EntityStats {
73 entries: u64,
74 memory_bytes: u64,
75 min_key: Option<DataKey>,
76 max_key: Option<DataKey>,
77}
78
79impl EntityStats {
80 fn update(&mut self, dk: &DataKey, value_len: u64) {
81 self.entries = self.entries.saturating_add(1);
82 self.memory_bytes = self
83 .memory_bytes
84 .saturating_add(DataKey::entry_size_bytes(value_len));
85
86 match &mut self.min_key {
87 Some(min) if dk < min => *min = dk.clone(),
88 None => self.min_key = Some(dk.clone()),
89 _ => {}
90 }
91
92 match &mut self.max_key {
93 Some(max) if dk > max => *max = dk.clone(),
94 None => self.max_key = Some(dk.clone()),
95 _ => {}
96 }
97 }
98}
99
100#[must_use]
102pub fn storage_report<C: CanisterKind>(
103 db: &Db<C>,
104 id_to_path: &[(u64, &'static str)],
105) -> StorageReport {
106 let id_map: BTreeMap<u64, &str> = id_to_path.iter().copied().collect();
108 let mut data = Vec::new();
109 let mut index = Vec::new();
110 let mut entity_storage: Vec<EntitySnapshot> = Vec::new();
111
112 db.with_data(|reg| {
113 reg.for_each(|path, store| {
114 data.push(DataStoreSnapshot {
115 path: path.to_string(),
116 entries: store.len(),
117 memory_bytes: store.memory_bytes(),
118 });
119
120 let mut by_entity: BTreeMap<u64, EntityStats> = BTreeMap::new();
122
123 for entry in store.iter() {
124 let dk = entry.key();
125 let value_len = entry.value().len() as u64;
126 by_entity
127 .entry(dk.entity_id())
128 .or_default()
129 .update(dk, value_len);
130 }
131
132 for (entity_id, stats) in by_entity {
133 let path_name = id_map.get(&entity_id).copied().unwrap_or("");
134 entity_storage.push(EntitySnapshot {
135 store: path.to_string(),
136 path: path_name.to_string(),
137 entries: stats.entries,
138 memory_bytes: stats.memory_bytes,
139 min_key: stats.min_key,
140 max_key: stats.max_key,
141 });
142 }
143 });
144 });
145
146 db.with_index(|reg| {
147 reg.for_each(|path, store| {
148 index.push(IndexStoreSnapshot {
149 path: path.to_string(),
150 entries: store.len(),
151 memory_bytes: store.memory_bytes(),
152 });
153 });
154 });
155
156 StorageReport {
157 storage_data: data,
158 storage_index: index,
159 entity_storage,
160 }
161}