1use std::time::Duration;
9
10use crate::types::{Message, Attachment, Reaction, EditEntry, ImageMetadata, SiteMetadata};
11use crate::compact::{CompactMessage, CompactMessageVec, CompactReaction, CompactAttachment, MessageFlags, NpubInterner};
12use crate::profile::{Profile, ProfileFlags};
13use crate::chat::Chat;
14
15#[derive(Debug, Default, Clone)]
17pub struct CacheStats {
18 pub message_count: usize,
20 pub chat_count: usize,
22 pub total_memory_bytes: usize,
24 pub last_insert_duration: Duration,
26 pub avg_insert_duration_ns: u64,
28 pub insert_count: u64,
30 insert_total_ns: u64,
32}
33
34impl CacheStats {
35 pub fn new() -> Self {
36 Self::default()
37 }
38
39 pub fn record_insert(&mut self, duration: Duration) {
41 self.last_insert_duration = duration;
42 self.insert_count += 1;
43 self.insert_total_ns += duration.as_nanos() as u64;
44 self.avg_insert_duration_ns = self.insert_total_ns / self.insert_count;
45 }
46
47 pub fn update_from_chats(&mut self, chats: &[Chat]) {
49 self.chat_count = chats.len();
50 self.message_count = chats.iter().map(|c| c.messages.len()).sum();
51 self.total_memory_bytes = chats.iter().map(|c| c.deep_size()).sum();
52 }
53
54 pub fn log(&self) {
56 println!(
57 "[CacheStats] chats={} messages={} memory={} last_insert={:?} avg_insert={}ns inserts={}",
58 self.chat_count,
59 self.message_count,
60 format_bytes(self.total_memory_bytes),
61 self.last_insert_duration,
62 self.avg_insert_duration_ns,
63 self.insert_count,
64 );
65 }
66
67 pub fn summary(&self) -> String {
69 format!(
70 "chats={} msgs={} mem={} avg_insert={}ns",
71 self.chat_count,
72 self.message_count,
73 format_bytes(self.total_memory_bytes),
74 self.avg_insert_duration_ns,
75 )
76 }
77
78 pub fn should_log(&self, interval: u64) -> bool {
80 self.insert_count > 0 && self.insert_count % interval == 0
81 }
82}
83
84fn format_bytes(bytes: usize) -> String {
86 if bytes >= 1024 * 1024 {
87 format!("{:.2}MB", bytes as f64 / (1024.0 * 1024.0))
88 } else if bytes >= 1024 {
89 format!("{:.2}KB", bytes as f64 / 1024.0)
90 } else {
91 format!("{}B", bytes)
92 }
93}
94
95pub trait DeepSize {
97 fn deep_size(&self) -> usize;
98}
99
100impl DeepSize for String {
103 #[inline]
104 fn deep_size(&self) -> usize {
105 std::mem::size_of::<String>() + self.capacity()
106 }
107}
108
109impl DeepSize for u64 {
110 #[inline]
111 fn deep_size(&self) -> usize { std::mem::size_of::<u64>() }
112}
113
114impl DeepSize for u32 {
115 #[inline]
116 fn deep_size(&self) -> usize { std::mem::size_of::<u32>() }
117}
118
119impl DeepSize for bool {
120 #[inline]
121 fn deep_size(&self) -> usize { std::mem::size_of::<bool>() }
122}
123
124impl<T: DeepSize> DeepSize for Vec<T> {
127 fn deep_size(&self) -> usize {
128 std::mem::size_of::<Vec<T>>()
129 + self.capacity() * std::mem::size_of::<T>()
130 + self.iter().map(|item| item.deep_size().saturating_sub(std::mem::size_of::<T>())).sum::<usize>()
131 }
132}
133
134impl<T: DeepSize> DeepSize for Option<T> {
135 fn deep_size(&self) -> usize {
136 std::mem::size_of::<Option<T>>()
137 + self.as_ref().map(|v| v.deep_size().saturating_sub(std::mem::size_of::<T>())).unwrap_or(0)
138 }
139}
140
141impl DeepSize for ImageMetadata {
144 fn deep_size(&self) -> usize {
145 std::mem::size_of::<ImageMetadata>() + self.thumbhash.capacity()
146 }
147}
148
149impl DeepSize for Attachment {
150 fn deep_size(&self) -> usize {
151 std::mem::size_of::<Attachment>()
152 + self.id.capacity() + self.key.capacity() + self.nonce.capacity()
153 + self.extension.capacity() + self.name.capacity()
154 + self.url.capacity() + self.path.capacity()
155 + self.img_meta.as_ref().map(|m| m.thumbhash.capacity()).unwrap_or(0)
156 + self.webxdc_topic.as_ref().map(|s| s.capacity()).unwrap_or(0)
157 + self.group_id.as_ref().map(|s| s.capacity()).unwrap_or(0)
158 + self.original_hash.as_ref().map(|s| s.capacity()).unwrap_or(0)
159 + self.scheme_version.as_ref().map(|s| s.capacity()).unwrap_or(0)
160 + self.mls_filename.as_ref().map(|s| s.capacity()).unwrap_or(0)
161 }
162}
163
164impl DeepSize for Reaction {
165 fn deep_size(&self) -> usize {
166 std::mem::size_of::<Reaction>()
167 + self.id.capacity() + self.reference_id.capacity()
168 + self.author_id.capacity() + self.emoji.capacity()
169 }
170}
171
172impl DeepSize for EditEntry {
173 fn deep_size(&self) -> usize {
174 std::mem::size_of::<EditEntry>() + self.content.capacity()
175 }
176}
177
178impl DeepSize for SiteMetadata {
179 fn deep_size(&self) -> usize {
180 std::mem::size_of::<SiteMetadata>()
181 + self.domain.capacity()
182 + self.og_title.as_ref().map(|s| s.capacity()).unwrap_or(0)
183 + self.og_description.as_ref().map(|s| s.capacity()).unwrap_or(0)
184 + self.og_image.as_ref().map(|s| s.capacity()).unwrap_or(0)
185 + self.og_url.as_ref().map(|s| s.capacity()).unwrap_or(0)
186 + self.og_type.as_ref().map(|s| s.capacity()).unwrap_or(0)
187 + self.title.as_ref().map(|s| s.capacity()).unwrap_or(0)
188 + self.description.as_ref().map(|s| s.capacity()).unwrap_or(0)
189 + self.favicon.as_ref().map(|s| s.capacity()).unwrap_or(0)
190 }
191}
192
193impl DeepSize for Message {
194 fn deep_size(&self) -> usize {
195 std::mem::size_of::<Message>()
196 + self.id.capacity() + self.content.capacity() + self.replied_to.capacity()
197 + self.replied_to_content.as_ref().map(|s| s.capacity()).unwrap_or(0)
198 + self.replied_to_npub.as_ref().map(|s| s.capacity()).unwrap_or(0)
199 + self.npub.as_ref().map(|s| s.capacity()).unwrap_or(0)
200 + self.wrapper_event_id.as_ref().map(|s| s.capacity()).unwrap_or(0)
201 + self.preview_metadata.as_ref().map(|m| m.deep_size()).unwrap_or(0)
202 + self.attachments.iter().map(|a| a.deep_size()).sum::<usize>()
203 + self.reactions.iter().map(|r| r.deep_size()).sum::<usize>()
204 + self.edit_history.as_ref().map(|h| h.iter().map(|e| e.deep_size()).sum::<usize>()).unwrap_or(0)
205 }
206}
207
208impl DeepSize for ProfileFlags {
209 #[inline]
210 fn deep_size(&self) -> usize { std::mem::size_of::<ProfileFlags>() }
211}
212
213impl DeepSize for Profile {
214 fn deep_size(&self) -> usize {
215 std::mem::size_of::<Profile>()
216 + self.name.len() + self.display_name.len() + self.nickname.len()
217 + self.lud06.len() + self.lud16.len()
218 + self.banner.len() + self.avatar.len()
219 + self.about.len() + self.website.len() + self.nip05.len()
220 + self.status_title.len() + self.status_purpose.len() + self.status_url.len()
221 + self.avatar_cached.len() + self.banner_cached.len()
222 }
223}
224
225impl DeepSize for MessageFlags {
226 #[inline]
227 fn deep_size(&self) -> usize { std::mem::size_of::<MessageFlags>() }
228}
229
230impl DeepSize for CompactReaction {
231 fn deep_size(&self) -> usize {
232 std::mem::size_of::<CompactReaction>() + self.emoji.len()
233 }
234}
235
236impl DeepSize for CompactAttachment {
237 fn deep_size(&self) -> usize {
238 std::mem::size_of::<CompactAttachment>()
239 + self.extension.len() + self.url.len() + self.path.len()
240 + self.name.capacity()
241 + self.img_meta.as_ref().map(|m| m.deep_size()).unwrap_or(0)
242 + self.group_id.as_ref().map(|_| 32).unwrap_or(0)
243 + self.original_hash.as_ref().map(|_| 32).unwrap_or(0)
244 + self.webxdc_topic.as_ref().map(|s| s.len()).unwrap_or(0)
245 + self.mls_filename.as_ref().map(|s| s.len()).unwrap_or(0)
246 + self.scheme_version.as_ref().map(|s| s.len()).unwrap_or(0)
247 }
248}
249
250impl DeepSize for CompactMessage {
251 fn deep_size(&self) -> usize {
252 std::mem::size_of::<CompactMessage>()
253 + self.content.len()
254 + self.replied_to_content.as_ref().map(|s| s.len()).unwrap_or(0)
255 + self.preview_metadata.as_ref().map(|m| m.deep_size()).unwrap_or(0)
256 + self.attachments.iter().map(|a| a.deep_size()).sum::<usize>()
257 + self.reactions.iter().map(|r| r.deep_size()).sum::<usize>()
258 + self.edit_history.as_ref().map(|h| h.iter().map(|e| e.deep_size()).sum::<usize>()).unwrap_or(0)
259 + self.addressed_bots.as_ref().map(|b| std::mem::size_of_val(&**b) + b.capacity() * 2).unwrap_or(0)
260 }
261}
262
263impl DeepSize for CompactMessageVec {
264 fn deep_size(&self) -> usize {
265 std::mem::size_of::<CompactMessageVec>()
266 + std::mem::size_of_val(self.messages())
267 + self.iter().map(|m| m.deep_size().saturating_sub(std::mem::size_of::<CompactMessage>())).sum::<usize>()
268 + self.len() * std::mem::size_of::<([u8; 32], u32)>()
269 }
270}
271
272impl DeepSize for NpubInterner {
273 fn deep_size(&self) -> usize {
274 self.memory_usage()
275 }
276}
277
278impl DeepSize for Chat {
279 fn deep_size(&self) -> usize {
280 std::mem::size_of::<Chat>()
281 + self.id.capacity()
282 + 32 + self.participants.capacity() * std::mem::size_of::<u16>()
284 + self.messages.deep_size()
285 + self.metadata.custom_fields.iter()
286 .map(|(k, v)| k.capacity() + v.capacity())
287 .sum::<usize>()
288 + self.typing_participants.capacity() * std::mem::size_of::<(u16, u64)>()
289 }
290}