Skip to main content

reifydb_core/metrics/
heap.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright (c) 2026 ReifyDB
3
4use std::{
5	collections::{BTreeMap, BTreeSet, HashMap, HashSet, VecDeque},
6	mem,
7	ops::Add,
8	sync::Arc,
9};
10
11use reifydb_codec::{key::encoded::EncodedKey, row::bytes::EncodedBytes};
12pub use reifydb_macro::HeapSize;
13use reifydb_value::{
14	byte_size::ByteSize,
15	count::Count,
16	util::hash::Hash128,
17	value::{
18		Value,
19		date::Date,
20		datetime::DateTime,
21		duration::Duration,
22		identity::IdentityId,
23		ordered_f32::OrderedF32,
24		ordered_f64::OrderedF64,
25		percentile::{Centroid, Percentiles},
26		row_number::RowNumber,
27		time::Time,
28		uuid::{Uuid4, Uuid7},
29	},
30};
31
32pub trait HeapSize {
33	fn heap_size(&self) -> usize;
34}
35
36#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
37pub struct StateMemory {
38	pub entries: Count,
39	pub bytes: ByteSize,
40}
41
42impl StateMemory {
43	pub const ZERO: Self = Self {
44		entries: Count::ZERO,
45		bytes: ByteSize::ZERO,
46	};
47
48	pub fn new(entries: Count, bytes: ByteSize) -> Self {
49		Self {
50			entries,
51			bytes,
52		}
53	}
54}
55
56impl Add for StateMemory {
57	type Output = Self;
58
59	fn add(self, rhs: Self) -> Self {
60		Self {
61			entries: self.entries + rhs.entries,
62			bytes: self.bytes + rhs.bytes,
63		}
64	}
65}
66
67#[derive(Clone, Copy, Debug, PartialEq, Eq)]
68pub struct StateCompleteness {
69	pub values_complete: bool,
70	pub membership_complete: bool,
71	pub absences_served: Count,
72	pub false_positives: Count,
73	pub revocations: Count,
74}
75
76impl StateCompleteness {
77	pub const MERGE_IDENTITY: Self = Self {
78		values_complete: true,
79		membership_complete: true,
80		absences_served: Count::ZERO,
81		false_positives: Count::ZERO,
82		revocations: Count::ZERO,
83	};
84
85	pub fn merge(self, rhs: Self) -> Self {
86		Self {
87			values_complete: self.values_complete && rhs.values_complete,
88			membership_complete: self.membership_complete && rhs.membership_complete,
89			absences_served: self.absences_served + rhs.absences_served,
90			false_positives: self.false_positives + rhs.false_positives,
91			revocations: self.revocations + rhs.revocations,
92		}
93	}
94}
95
96#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
97pub struct OperatorSample {
98	pub memory: Option<StateMemory>,
99	pub row_number_cache: Option<StateMemory>,
100}
101
102impl OperatorSample {
103	pub fn with_memory(memory: StateMemory) -> Self {
104		Self {
105			memory: Some(memory),
106			..Self::default()
107		}
108	}
109
110	pub fn with_row_number_cache(mut self, memory: StateMemory) -> Self {
111		self.row_number_cache = Some(memory);
112		self
113	}
114}
115
116macro_rules! zero_heap {
117	($($ty:ty),* $(,)?) => {
118		$(impl HeapSize for $ty {
119			fn heap_size(&self) -> usize {
120				0
121			}
122		})*
123	};
124}
125
126zero_heap!(u8, u16, u32, u64, u128, usize, i8, i16, i32, i64, i128, isize, f32, f64, bool, char, ());
127zero_heap!(
128	OrderedF32, OrderedF64, Date, DateTime, Time, Duration, IdentityId, Uuid4, Uuid7, RowNumber, Hash128, Centroid
129);
130
131const BIGNUM_APPROX_HEAP: usize = 32;
132const BTREE_NODE_FILL_DIVISOR: usize = 2;
133
134impl HeapSize for Percentiles {
135	fn heap_size(&self) -> usize {
136		mem::size_of_val(self.centroids())
137	}
138}
139
140impl HeapSize for String {
141	fn heap_size(&self) -> usize {
142		self.capacity()
143	}
144}
145
146impl HeapSize for EncodedBytes {
147	fn heap_size(&self) -> usize {
148		self.as_slice().len()
149	}
150}
151
152impl HeapSize for EncodedKey {
153	fn heap_size(&self) -> usize {
154		match self {
155			EncodedKey::Inline {
156				..
157			} => 0,
158			EncodedKey::Shared(bytes) => bytes.len() + 2 * mem::size_of::<usize>(),
159		}
160	}
161}
162
163impl<T: HeapSize> HeapSize for Option<T> {
164	fn heap_size(&self) -> usize {
165		self.as_ref().map_or(0, HeapSize::heap_size)
166	}
167}
168
169impl<T: HeapSize> HeapSize for Vec<T> {
170	fn heap_size(&self) -> usize {
171		self.capacity() * mem::size_of::<T>() + self.iter().map(HeapSize::heap_size).sum::<usize>()
172	}
173}
174
175impl<T: HeapSize, const N: usize> HeapSize for [T; N] {
176	fn heap_size(&self) -> usize {
177		self.iter().map(HeapSize::heap_size).sum::<usize>()
178	}
179}
180
181impl<T: HeapSize> HeapSize for VecDeque<T> {
182	fn heap_size(&self) -> usize {
183		self.capacity() * mem::size_of::<T>() + self.iter().map(HeapSize::heap_size).sum::<usize>()
184	}
185}
186
187impl<T: HeapSize> HeapSize for Box<T> {
188	fn heap_size(&self) -> usize {
189		mem::size_of::<T>() + (**self).heap_size()
190	}
191}
192
193impl<T: HeapSize> HeapSize for Arc<T> {
194	fn heap_size(&self) -> usize {
195		mem::size_of::<usize>() * 2 + mem::size_of::<T>() + (**self).heap_size()
196	}
197}
198
199impl HeapSize for Arc<str> {
200	fn heap_size(&self) -> usize {
201		mem::size_of::<usize>() * 2 + self.len()
202	}
203}
204
205impl<T: HeapSize> HeapSize for Arc<[T]> {
206	fn heap_size(&self) -> usize {
207		mem::size_of::<usize>() * 2
208			+ self.len() * mem::size_of::<T>()
209			+ self.iter().map(HeapSize::heap_size).sum::<usize>()
210	}
211}
212
213impl<T: HeapSize> HeapSize for Box<[T]> {
214	fn heap_size(&self) -> usize {
215		self.len() * mem::size_of::<T>() + self.iter().map(HeapSize::heap_size).sum::<usize>()
216	}
217}
218
219impl HeapSize for Box<str> {
220	fn heap_size(&self) -> usize {
221		self.len()
222	}
223}
224
225impl<K: HeapSize, V: HeapSize> HeapSize for BTreeMap<K, V> {
226	fn heap_size(&self) -> usize {
227		self.len() * BTREE_NODE_FILL_DIVISOR * (mem::size_of::<K>() + mem::size_of::<V>())
228			+ self.iter().map(|(k, v)| k.heap_size() + v.heap_size()).sum::<usize>()
229	}
230}
231
232impl<T: HeapSize> HeapSize for BTreeSet<T> {
233	fn heap_size(&self) -> usize {
234		self.len() * BTREE_NODE_FILL_DIVISOR * mem::size_of::<T>()
235			+ self.iter().map(HeapSize::heap_size).sum::<usize>()
236	}
237}
238
239impl<K: HeapSize, V: HeapSize, S> HeapSize for HashMap<K, V, S> {
240	fn heap_size(&self) -> usize {
241		self.capacity() * (mem::size_of::<K>() + mem::size_of::<V>() + 1)
242			+ self.iter().map(|(k, v)| k.heap_size() + v.heap_size()).sum::<usize>()
243	}
244}
245
246impl<T: HeapSize, S> HeapSize for HashSet<T, S> {
247	fn heap_size(&self) -> usize {
248		self.capacity() * (mem::size_of::<T>() + 1) + self.iter().map(HeapSize::heap_size).sum::<usize>()
249	}
250}
251
252impl<A: HeapSize, B: HeapSize> HeapSize for (A, B) {
253	fn heap_size(&self) -> usize {
254		self.0.heap_size() + self.1.heap_size()
255	}
256}
257
258impl<A: HeapSize, B: HeapSize, C: HeapSize> HeapSize for (A, B, C) {
259	fn heap_size(&self) -> usize {
260		self.0.heap_size() + self.1.heap_size() + self.2.heap_size()
261	}
262}
263
264impl HeapSize for Value {
265	fn heap_size(&self) -> usize {
266		match self {
267			Value::Utf8(text) => text.capacity(),
268			Value::Blob(blob) => blob.as_bytes().len(),
269			Value::Int(_) | Value::Uint(_) | Value::Decimal(_) => BIGNUM_APPROX_HEAP,
270			Value::Any(inner) => mem::size_of::<Value>() + inner.heap_size(),
271			Value::List(items) | Value::Tuple(items) => items.heap_size(),
272			Value::Record(fields) => {
273				fields.capacity() * mem::size_of::<(String, Value)>()
274					+ fields.iter()
275						.map(|(name, value)| name.capacity() + value.heap_size())
276						.sum::<usize>()
277			}
278			_ => 0,
279		}
280	}
281}
282
283#[cfg(test)]
284mod tests {
285	use super::HeapSize;
286
287	#[derive(HeapSize)]
288	struct DerivedSample {
289		name: String,
290		values: Vec<u64>,
291		count: u64,
292	}
293
294	#[test]
295	fn derived_heap_size_sums_all_fields() {
296		// The derive must sum every field so a heap-owning field added later is picked up
297		// automatically, and scalar fields must contribute zero.
298		let sample = DerivedSample {
299			name: String::with_capacity(32),
300			values: Vec::with_capacity(4),
301			count: 7,
302		};
303		assert_eq!(sample.count, 7);
304		assert_eq!(sample.heap_size(), 32 + 4 * 8);
305	}
306}