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