tuwunel_database/engine/
memory_usage.rs1use std::{ffi::CStr, fmt::Write};
2
3use rocksdb::perf::get_memory_usage_stats;
4use tuwunel_core::{Result, implement};
5
6use super::{
7 Engine,
8 context::{ColCache, SHARED_POOL},
9};
10use crate::or_else;
11
12fn mib(input: u64) -> f64 { f64::from(u32::try_from(input / 1024).unwrap_or(0)) / 1024.0 }
13
14const ENTRY_STATS_PROPERTY: &str = "rocksdb.block-cache-entry-stats";
20
21const CACHE_CAPACITY_PROPERTY: &CStr = c"rocksdb.block-cache-capacity";
24
25#[implement(Engine)]
26pub fn memory_usage(&self) -> Result<String> {
27 let mut res = String::new();
28 let row_cache = self.ctx.row_cache.lock()?;
29 let row_usage = u64::try_from(row_cache.get_usage())?;
30 let row_capacity = u64::try_from(self.ctx.row_cache_capacity)?;
31 let stats =
32 get_memory_usage_stats(Some(&[&self.db]), Some(&[&*row_cache])).or_else(or_else)?;
33
34 writeln!(res, "- Memory buffers: {:.2} MiB", mib(stats.mem_table_total))?;
35 writeln!(res, "- Pending write: {:.2} MiB", mib(stats.mem_table_unflushed))?;
36 writeln!(res, "- Table readers: {:.2} MiB", mib(stats.mem_table_readers_total))?;
37 writeln!(
38 res,
39 "- Row cache: {:.2} / {:.2} MiB ({:.1}%)",
40 mib(row_usage),
41 mib(row_capacity),
42 utilization_percent(row_usage, row_capacity),
43 )?;
44
45 drop(row_cache);
46
47 let pools = self.ctx.col_cache.lock()?;
48 if pools.is_empty() {
49 return Ok(res);
50 }
51
52 writeln!(res, "- Block cache pools:")?;
53 for (name, pool) in &*pools {
54 self.write_pool(&mut res, name, pool)?;
55 }
56
57 Ok(res)
58}
59
60#[implement(Engine)]
61fn write_pool(&self, out: &mut String, name: &str, pool: &ColCache) -> Result {
62 let label = if name == SHARED_POOL { "Shared" } else { name };
63 let pinned = u64::try_from(pool.cache.get_pinned_usage())?;
64 let usage = u64::try_from(pool.cache.get_usage())?;
65 let capacity = pool
66 .participants
67 .first()
68 .copied()
69 .map(|cf_name| self.cf(cf_name))
70 .and_then(|cf| {
71 self.property_integer(&cf, CACHE_CAPACITY_PROPERTY)
72 .ok()
73 })
74 .unwrap_or(0);
75
76 writeln!(out, " - {label}")?;
77 writeln!(
78 out,
79 " - Usage: {:.2} / {:.2} MiB ({:.1}%)",
80 mib(usage),
81 mib(capacity),
82 utilization_percent(usage, capacity),
83 )?;
84 writeln!(out, " - Pinned: {:.2} MiB", mib(pinned))?;
85
86 let multi_cf = pool.participants.len() != 1 || pool.participants[0] != name;
87 if multi_cf {
88 writeln!(out, " - Participants: {}", pool.participants.join(", "))?;
89 }
90
91 if let Some(repr) = pool.participants.first() {
92 self.write_entry_stats(out, repr)?;
93 }
94
95 Ok(())
96}
97
98#[implement(Engine)]
99fn write_entry_stats(&self, out: &mut String, cf_name: &str) -> Result {
100 let cf = self.cf(cf_name);
101 let raw = self.property(&cf, ENTRY_STATS_PROPERTY)?;
102 let Some(by_role) = raw
103 .lines()
104 .find(|line| line.starts_with("Block cache entry stats"))
105 else {
106 return Ok(());
107 };
108
109 let Some((_, roles)) = by_role.split_once(": ") else {
110 return Ok(());
111 };
112
113 writeln!(out, " - Entries:")?;
114 for role in roles.trim_end_matches(')').split(") ") {
115 let pretty = role.replacen('(', ": ", 1).replace(',', " ");
116 writeln!(out, " - {pretty}")?;
117 }
118
119 Ok(())
120}
121
122#[expect(clippy::as_conversions, clippy::cast_precision_loss)]
123fn utilization_percent(usage: u64, capacity: u64) -> f64 {
124 if capacity == 0 {
125 return 0.0;
126 }
127
128 (usage as f64 / capacity as f64) * 100.0
129}