Skip to main content

tuwunel_database/engine/
memory_usage.rs

1use 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
12/// Per-CF byte count of the block cache the CF is using. Returns the same
13/// value for every participant of a shared pool.
14const CACHE_CAPACITY_PROPERTY: &CStr = c"rocksdb.block-cache-capacity";
15
16fn mib(input: u64) -> f64 { f64::from(u32::try_from(input / 1024).unwrap_or(0)) / 1024.0 }
17
18/// Formats the database engine's current memory usage.
19///
20/// The report covers memtables, pending writes, table readers, and the row
21/// cache. When column-cache pools exist, it also includes their usage,
22/// capacity, pinned memory, and participating column-family counts.
23#[implement(Engine)]
24pub fn memory_usage(&self) -> Result<String> {
25	let mut res = String::new();
26	let row_cache = self.ctx.row_cache.lock()?;
27	let row_usage = u64::try_from(row_cache.get_usage())?;
28	let row_capacity = u64::try_from(self.ctx.row_cache_capacity)?;
29	let stats =
30		get_memory_usage_stats(Some(&[&self.db]), Some(&[&*row_cache])).or_else(or_else)?;
31
32	writeln!(res, "- Memory buffers: {:.2} MiB", mib(stats.mem_table_total))?;
33	writeln!(res, "- Pending write:  {:.2} MiB", mib(stats.mem_table_unflushed))?;
34	writeln!(res, "- Table readers:  {:.2} MiB", mib(stats.mem_table_readers_total))?;
35	writeln!(
36		res,
37		"- Row cache:      {:.2} / {:.2} MiB ({:.1}%)",
38		mib(row_usage),
39		mib(row_capacity),
40		utilization_percent(row_usage, row_capacity),
41	)?;
42
43	drop(row_cache);
44
45	let pools = self.ctx.col_cache.lock()?;
46	if pools.is_empty() {
47		return Ok(res);
48	}
49
50	writeln!(res, "\n```")?;
51	writeln!(
52		res,
53		"{:<34}  {:>11}  {:>14}  {:>8}  {:>12}  {:>3}",
54		"POOL", "USAGE (MiB)", "CAPACITY (MiB)", "UTIL (%)", "PINNED (MiB)", "CFS",
55	)?;
56
57	for (name, pool) in &*pools {
58		self.write_pool(&mut res, name, pool)?;
59	}
60	writeln!(res, "```")?;
61
62	Ok(res)
63}
64
65#[implement(Engine)]
66fn write_pool(&self, out: &mut String, name: &str, pool: &ColCache) -> Result {
67	let label = if name == SHARED_POOL { "Shared" } else { name };
68	let pinned = u64::try_from(pool.cache.get_pinned_usage())?;
69	let usage = u64::try_from(pool.cache.get_usage())?;
70	let capacity = pool
71		.participants
72		.first()
73		.copied()
74		.map(|cf_name| self.cf(cf_name))
75		.and_then(|cf| {
76			self.property_integer(&cf, CACHE_CAPACITY_PROPERTY)
77				.ok()
78		})
79		.unwrap_or(0);
80
81	writeln!(
82		out,
83		"{label:<34}  {:>11.2}  {:>14.2}  {:>8.1}  {:>12.2}  {:>3}",
84		mib(usage),
85		mib(capacity),
86		utilization_percent(usage, capacity),
87		mib(pinned),
88		pool.participants.len(),
89	)?;
90
91	Ok(())
92}
93
94#[expect(clippy::as_conversions, clippy::cast_precision_loss)]
95fn utilization_percent(usage: u64, capacity: u64) -> f64 {
96	if capacity == 0 {
97		return 0.0;
98	}
99
100	(usage as f64 / capacity as f64) * 100.0
101}