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
12fn mib(input: u64) -> f64 { f64::from(u32::try_from(input / 1024).unwrap_or(0)) / 1024.0 }
13
14/// Multi-line cache-entry stats blob. Live variant forces a fresh
15/// collection per call; the "fast" variant returns a cached snapshot and is
16/// unsuitable for admin diagnostics since it never refreshes within a
17/// session. The string surface is what `property_value_cf` returns; the
18/// structured map form is C++-only.
19const ENTRY_STATS_PROPERTY: &str = "rocksdb.block-cache-entry-stats";
20
21/// Per-CF byte count of the block cache the CF is using. Returns the same
22/// value for every participant of a shared pool.
23const 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}