use std::time::Duration;
use crate::types::{Message, Attachment, Reaction, EditEntry, ImageMetadata, SiteMetadata};
use crate::compact::{CompactMessage, CompactMessageVec, CompactReaction, CompactAttachment, MessageFlags, NpubInterner};
use crate::profile::{Profile, ProfileFlags};
use crate::chat::Chat;
#[derive(Debug, Default, Clone)]
pub struct CacheStats {
pub message_count: usize,
pub chat_count: usize,
pub total_memory_bytes: usize,
pub last_insert_duration: Duration,
pub avg_insert_duration_ns: u64,
pub insert_count: u64,
insert_total_ns: u64,
}
impl CacheStats {
pub fn new() -> Self {
Self::default()
}
pub fn record_insert(&mut self, duration: Duration) {
self.last_insert_duration = duration;
self.insert_count += 1;
self.insert_total_ns += duration.as_nanos() as u64;
self.avg_insert_duration_ns = self.insert_total_ns / self.insert_count;
}
pub fn update_from_chats(&mut self, chats: &[Chat]) {
self.chat_count = chats.len();
self.message_count = chats.iter().map(|c| c.messages.len()).sum();
self.total_memory_bytes = chats.iter().map(|c| c.deep_size()).sum();
}
pub fn log(&self) {
println!(
"[CacheStats] chats={} messages={} memory={} last_insert={:?} avg_insert={}ns inserts={}",
self.chat_count,
self.message_count,
format_bytes(self.total_memory_bytes),
self.last_insert_duration,
self.avg_insert_duration_ns,
self.insert_count,
);
}
pub fn summary(&self) -> String {
format!(
"chats={} msgs={} mem={} avg_insert={}ns",
self.chat_count,
self.message_count,
format_bytes(self.total_memory_bytes),
self.avg_insert_duration_ns,
)
}
pub fn should_log(&self, interval: u64) -> bool {
self.insert_count > 0 && self.insert_count % interval == 0
}
}
fn format_bytes(bytes: usize) -> String {
if bytes >= 1024 * 1024 {
format!("{:.2}MB", bytes as f64 / (1024.0 * 1024.0))
} else if bytes >= 1024 {
format!("{:.2}KB", bytes as f64 / 1024.0)
} else {
format!("{}B", bytes)
}
}
pub trait DeepSize {
fn deep_size(&self) -> usize;
}
impl DeepSize for String {
#[inline]
fn deep_size(&self) -> usize {
std::mem::size_of::<String>() + self.capacity()
}
}
impl DeepSize for u64 {
#[inline]
fn deep_size(&self) -> usize { std::mem::size_of::<u64>() }
}
impl DeepSize for u32 {
#[inline]
fn deep_size(&self) -> usize { std::mem::size_of::<u32>() }
}
impl DeepSize for bool {
#[inline]
fn deep_size(&self) -> usize { std::mem::size_of::<bool>() }
}
impl<T: DeepSize> DeepSize for Vec<T> {
fn deep_size(&self) -> usize {
std::mem::size_of::<Vec<T>>()
+ self.capacity() * std::mem::size_of::<T>()
+ self.iter().map(|item| item.deep_size().saturating_sub(std::mem::size_of::<T>())).sum::<usize>()
}
}
impl<T: DeepSize> DeepSize for Option<T> {
fn deep_size(&self) -> usize {
std::mem::size_of::<Option<T>>()
+ self.as_ref().map(|v| v.deep_size().saturating_sub(std::mem::size_of::<T>())).unwrap_or(0)
}
}
impl DeepSize for ImageMetadata {
fn deep_size(&self) -> usize {
std::mem::size_of::<ImageMetadata>() + self.thumbhash.capacity()
}
}
impl DeepSize for Attachment {
fn deep_size(&self) -> usize {
std::mem::size_of::<Attachment>()
+ self.id.capacity() + self.key.capacity() + self.nonce.capacity()
+ self.extension.capacity() + self.name.capacity()
+ self.url.capacity() + self.path.capacity()
+ self.img_meta.as_ref().map(|m| m.thumbhash.capacity()).unwrap_or(0)
+ self.webxdc_topic.as_ref().map(|s| s.capacity()).unwrap_or(0)
+ self.group_id.as_ref().map(|s| s.capacity()).unwrap_or(0)
+ self.original_hash.as_ref().map(|s| s.capacity()).unwrap_or(0)
+ self.scheme_version.as_ref().map(|s| s.capacity()).unwrap_or(0)
+ self.mls_filename.as_ref().map(|s| s.capacity()).unwrap_or(0)
}
}
impl DeepSize for Reaction {
fn deep_size(&self) -> usize {
std::mem::size_of::<Reaction>()
+ self.id.capacity() + self.reference_id.capacity()
+ self.author_id.capacity() + self.emoji.capacity()
}
}
impl DeepSize for EditEntry {
fn deep_size(&self) -> usize {
std::mem::size_of::<EditEntry>() + self.content.capacity()
}
}
impl DeepSize for SiteMetadata {
fn deep_size(&self) -> usize {
std::mem::size_of::<SiteMetadata>()
+ self.domain.capacity()
+ self.og_title.as_ref().map(|s| s.capacity()).unwrap_or(0)
+ self.og_description.as_ref().map(|s| s.capacity()).unwrap_or(0)
+ self.og_image.as_ref().map(|s| s.capacity()).unwrap_or(0)
+ self.og_url.as_ref().map(|s| s.capacity()).unwrap_or(0)
+ self.og_type.as_ref().map(|s| s.capacity()).unwrap_or(0)
+ self.title.as_ref().map(|s| s.capacity()).unwrap_or(0)
+ self.description.as_ref().map(|s| s.capacity()).unwrap_or(0)
+ self.favicon.as_ref().map(|s| s.capacity()).unwrap_or(0)
}
}
impl DeepSize for Message {
fn deep_size(&self) -> usize {
std::mem::size_of::<Message>()
+ self.id.capacity() + self.content.capacity() + self.replied_to.capacity()
+ self.replied_to_content.as_ref().map(|s| s.capacity()).unwrap_or(0)
+ self.replied_to_npub.as_ref().map(|s| s.capacity()).unwrap_or(0)
+ self.npub.as_ref().map(|s| s.capacity()).unwrap_or(0)
+ self.wrapper_event_id.as_ref().map(|s| s.capacity()).unwrap_or(0)
+ self.preview_metadata.as_ref().map(|m| m.deep_size()).unwrap_or(0)
+ self.attachments.iter().map(|a| a.deep_size()).sum::<usize>()
+ self.reactions.iter().map(|r| r.deep_size()).sum::<usize>()
+ self.edit_history.as_ref().map(|h| h.iter().map(|e| e.deep_size()).sum::<usize>()).unwrap_or(0)
}
}
impl DeepSize for ProfileFlags {
#[inline]
fn deep_size(&self) -> usize { std::mem::size_of::<ProfileFlags>() }
}
impl DeepSize for Profile {
fn deep_size(&self) -> usize {
std::mem::size_of::<Profile>()
+ self.name.len() + self.display_name.len() + self.nickname.len()
+ self.lud06.len() + self.lud16.len()
+ self.banner.len() + self.avatar.len()
+ self.about.len() + self.website.len() + self.nip05.len()
+ self.status_title.len() + self.status_purpose.len() + self.status_url.len()
+ self.avatar_cached.len() + self.banner_cached.len()
}
}
impl DeepSize for MessageFlags {
#[inline]
fn deep_size(&self) -> usize { std::mem::size_of::<MessageFlags>() }
}
impl DeepSize for CompactReaction {
fn deep_size(&self) -> usize {
std::mem::size_of::<CompactReaction>() + self.emoji.len()
}
}
impl DeepSize for CompactAttachment {
fn deep_size(&self) -> usize {
std::mem::size_of::<CompactAttachment>()
+ self.extension.len() + self.url.len() + self.path.len()
+ self.name.capacity()
+ self.img_meta.as_ref().map(|m| m.deep_size()).unwrap_or(0)
+ self.group_id.as_ref().map(|_| 32).unwrap_or(0)
+ self.original_hash.as_ref().map(|_| 32).unwrap_or(0)
+ self.webxdc_topic.as_ref().map(|s| s.len()).unwrap_or(0)
+ self.mls_filename.as_ref().map(|s| s.len()).unwrap_or(0)
+ self.scheme_version.as_ref().map(|s| s.len()).unwrap_or(0)
}
}
impl DeepSize for CompactMessage {
fn deep_size(&self) -> usize {
std::mem::size_of::<CompactMessage>()
+ self.content.len()
+ self.replied_to_content.as_ref().map(|s| s.len()).unwrap_or(0)
+ self.preview_metadata.as_ref().map(|m| m.deep_size()).unwrap_or(0)
+ self.attachments.iter().map(|a| a.deep_size()).sum::<usize>()
+ self.reactions.iter().map(|r| r.deep_size()).sum::<usize>()
+ self.edit_history.as_ref().map(|h| h.iter().map(|e| e.deep_size()).sum::<usize>()).unwrap_or(0)
+ self.addressed_bots.as_ref().map(|b| std::mem::size_of_val(&**b) + b.capacity() * 2).unwrap_or(0)
}
}
impl DeepSize for CompactMessageVec {
fn deep_size(&self) -> usize {
std::mem::size_of::<CompactMessageVec>()
+ std::mem::size_of_val(self.messages())
+ self.iter().map(|m| m.deep_size().saturating_sub(std::mem::size_of::<CompactMessage>())).sum::<usize>()
+ self.len() * std::mem::size_of::<([u8; 32], u32)>()
}
}
impl DeepSize for NpubInterner {
fn deep_size(&self) -> usize {
self.memory_usage()
}
}
impl DeepSize for Chat {
fn deep_size(&self) -> usize {
std::mem::size_of::<Chat>()
+ self.id.capacity()
+ 32 + self.participants.capacity() * std::mem::size_of::<u16>()
+ self.messages.deep_size()
+ self.metadata.custom_fields.iter()
.map(|(k, v)| k.capacity() + v.capacity())
.sum::<usize>()
+ self.typing_participants.capacity() * std::mem::size_of::<(u16, u64)>()
}
}