Skip to main content

hashtree_cli/
diagnostics.rs

1use nostr::Filter;
2
3#[derive(Debug, Clone, Copy, Default)]
4pub struct ProcessMemorySnapshot {
5    pub vm_rss_kb: Option<u64>,
6    pub rss_anon_kb: Option<u64>,
7    pub anonymous_kb: Option<u64>,
8    pub threads: Option<u64>,
9}
10
11#[cfg(target_os = "linux")]
12pub fn process_memory_snapshot() -> ProcessMemorySnapshot {
13    let mut snapshot = ProcessMemorySnapshot::default();
14
15    if let Ok(status) = std::fs::read_to_string("/proc/self/status") {
16        for line in status.lines() {
17            if let Some(value) = line.strip_prefix("VmRSS:") {
18                snapshot.vm_rss_kb = parse_status_kb(value);
19            } else if let Some(value) = line.strip_prefix("RssAnon:") {
20                snapshot.rss_anon_kb = parse_status_kb(value);
21            } else if let Some(value) = line.strip_prefix("Threads:") {
22                snapshot.threads = value.trim().parse::<u64>().ok();
23            }
24        }
25    }
26
27    if let Ok(rollup) = std::fs::read_to_string("/proc/self/smaps_rollup") {
28        for line in rollup.lines() {
29            if let Some(value) = line.strip_prefix("Anonymous:") {
30                snapshot.anonymous_kb = parse_status_kb(value);
31                break;
32            }
33        }
34    }
35
36    snapshot
37}
38
39#[cfg(not(target_os = "linux"))]
40pub fn process_memory_snapshot() -> ProcessMemorySnapshot {
41    ProcessMemorySnapshot::default()
42}
43
44pub fn nostr_filter_summary(filter: &Filter) -> String {
45    let generic_tag_values = filter
46        .generic_tags
47        .values()
48        .map(|values| values.len())
49        .sum::<usize>();
50    format!(
51        "ids={} authors={} kinds={} tags={} tag_values={} search={} since={} until={} limit={}",
52        filter.ids.as_ref().map(|ids| ids.len()).unwrap_or(0),
53        filter
54            .authors
55            .as_ref()
56            .map(|authors| authors.len())
57            .unwrap_or(0),
58        filter.kinds.as_ref().map(|kinds| kinds.len()).unwrap_or(0),
59        filter.generic_tags.len(),
60        generic_tag_values,
61        filter.search.is_some(),
62        filter.since.is_some(),
63        filter.until.is_some(),
64        filter
65            .limit
66            .map(|limit| limit.to_string())
67            .unwrap_or_else(|| "none".to_string()),
68    )
69}
70
71pub fn nostr_filters_summary(filters: &[Filter]) -> String {
72    const MAX_FILTERS_IN_LOG: usize = 4;
73    let mut summaries = filters
74        .iter()
75        .take(MAX_FILTERS_IN_LOG)
76        .map(nostr_filter_summary)
77        .collect::<Vec<_>>();
78    if filters.len() > MAX_FILTERS_IN_LOG {
79        summaries.push(format!(
80            "...+{} filters",
81            filters.len() - MAX_FILTERS_IN_LOG
82        ));
83    }
84    summaries.join("; ")
85}
86
87#[cfg(all(target_os = "linux", target_env = "gnu"))]
88pub fn trim_process_allocations() {
89    // SAFETY: malloc_trim asks glibc to return free heap pages to the OS.
90    unsafe {
91        libc::malloc_trim(0);
92    }
93}
94
95#[cfg(not(all(target_os = "linux", target_env = "gnu")))]
96pub fn trim_process_allocations() {}
97
98#[cfg(target_os = "linux")]
99fn parse_status_kb(value: &str) -> Option<u64> {
100    value.split_whitespace().next()?.parse::<u64>().ok()
101}