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		decimal::Decimal,
25		duration::Duration,
26		identity::IdentityId,
27		ordered_f32::OrderedF32,
28		ordered_f64::OrderedF64,
29		partition::Partition,
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!(OrderedF32, OrderedF64, Date, DateTime, Time, Duration, IdentityId, Uuid4, Uuid7, RowNumber, Hash128);
138zero_heap!(Partition, RowShapeFingerprint, TimerKind, ContentVersion);
139
140const BIGNUM_APPROX_HEAP: usize = 32;
141
142impl HeapSize for Decimal {
143	fn heap_size(&self) -> usize {
144		BIGNUM_APPROX_HEAP
145	}
146}
147
148impl HeapSize for String {
149	fn heap_size(&self) -> usize {
150		self.capacity()
151	}
152}
153
154impl HeapSize for EncodedBytes {
155	fn heap_size(&self) -> usize {
156		self.as_slice().len()
157	}
158}
159
160impl HeapSize for EncodedKey {
161	fn heap_size(&self) -> usize {
162		match self {
163			EncodedKey::Inline {
164				..
165			} => 0,
166			EncodedKey::Shared(bytes) => bytes.len() + 2 * mem::size_of::<usize>(),
167		}
168	}
169}
170
171impl HeapSize for TaggedKey {
172	fn heap_size(&self) -> usize {
173		match self {
174			TaggedKey::SortedViewRow(key) => key.run.len(),
175			TaggedKey::PartitionedSortedViewRow(key) => key.run.len(),
176			TaggedKey::RingBufferMetadata(key) => {
177				key.partition_values.capacity() * mem::size_of::<Value>()
178					+ key.partition_values.iter().map(HeapSize::heap_size).sum::<usize>()
179			}
180			TaggedKey::QueueDeduplication(key) => key.tail.heap_size(),
181			TaggedKey::IndexEntry(key) => key.key.heap_size(),
182			TaggedKey::OperatorState(key) => key.suffix.capacity(),
183			_ => 0,
184		}
185	}
186}
187
188impl HeapSize for EncodedIndexKey {
189	fn heap_size(&self) -> usize {
190		match self {
191			EncodedIndexKey::Inline {
192				..
193			} => 0,
194			EncodedIndexKey::Heap(bytes) => bytes.capacity(),
195		}
196	}
197}
198
199impl<T: HeapSize> HeapSize for Option<T> {
200	fn heap_size(&self) -> usize {
201		self.as_ref().map_or(0, HeapSize::heap_size)
202	}
203}
204
205impl<T: HeapSize> HeapSize for Vec<T> {
206	fn heap_size(&self) -> usize {
207		self.capacity() * mem::size_of::<T>() + self.iter().map(HeapSize::heap_size).sum::<usize>()
208	}
209}
210
211impl<T: HeapSize, const N: usize> HeapSize for [T; N] {
212	fn heap_size(&self) -> usize {
213		self.iter().map(HeapSize::heap_size).sum::<usize>()
214	}
215}
216
217impl<T: HeapSize> HeapSize for VecDeque<T> {
218	fn heap_size(&self) -> usize {
219		self.capacity() * mem::size_of::<T>() + self.iter().map(HeapSize::heap_size).sum::<usize>()
220	}
221}
222
223impl<T: HeapSize> HeapSize for Box<T> {
224	fn heap_size(&self) -> usize {
225		mem::size_of::<T>() + (**self).heap_size()
226	}
227}
228
229impl<T: HeapSize> HeapSize for Arc<T> {
230	fn heap_size(&self) -> usize {
231		mem::size_of::<usize>() * 2 + mem::size_of::<T>() + (**self).heap_size()
232	}
233}
234
235impl HeapSize for Arc<str> {
236	fn heap_size(&self) -> usize {
237		mem::size_of::<usize>() * 2 + self.len()
238	}
239}
240
241impl<T: HeapSize> HeapSize for Arc<[T]> {
242	fn heap_size(&self) -> usize {
243		mem::size_of::<usize>() * 2
244			+ self.len() * mem::size_of::<T>()
245			+ self.iter().map(HeapSize::heap_size).sum::<usize>()
246	}
247}
248
249impl<T: HeapSize> HeapSize for Box<[T]> {
250	fn heap_size(&self) -> usize {
251		self.len() * mem::size_of::<T>() + self.iter().map(HeapSize::heap_size).sum::<usize>()
252	}
253}
254
255impl HeapSize for Box<str> {
256	fn heap_size(&self) -> usize {
257		self.len()
258	}
259}
260
261impl<K: HeapSize, V: HeapSize> HeapSize for BTreeMap<K, V> {
262	fn heap_size(&self) -> usize {
263		self.len() * (mem::size_of::<K>() + mem::size_of::<V>())
264			+ self.iter().map(|(k, v)| k.heap_size() + v.heap_size()).sum::<usize>()
265	}
266}
267
268impl<T: HeapSize> HeapSize for BTreeSet<T> {
269	fn heap_size(&self) -> usize {
270		self.len() * mem::size_of::<T>() + self.iter().map(HeapSize::heap_size).sum::<usize>()
271	}
272}
273
274impl<K: HeapSize, V: HeapSize, S> HeapSize for HashMap<K, V, S> {
275	fn heap_size(&self) -> usize {
276		self.capacity() * (mem::size_of::<K>() + mem::size_of::<V>() + 1)
277			+ self.iter().map(|(k, v)| k.heap_size() + v.heap_size()).sum::<usize>()
278	}
279}
280
281impl<T: HeapSize, S> HeapSize for HashSet<T, S> {
282	fn heap_size(&self) -> usize {
283		self.capacity() * (mem::size_of::<T>() + 1) + self.iter().map(HeapSize::heap_size).sum::<usize>()
284	}
285}
286
287impl<A: HeapSize, B: HeapSize> HeapSize for (A, B) {
288	fn heap_size(&self) -> usize {
289		self.0.heap_size() + self.1.heap_size()
290	}
291}
292
293impl<A: HeapSize, B: HeapSize, C: HeapSize> HeapSize for (A, B, C) {
294	fn heap_size(&self) -> usize {
295		self.0.heap_size() + self.1.heap_size() + self.2.heap_size()
296	}
297}
298
299impl HeapSize for Value {
300	fn heap_size(&self) -> usize {
301		match self {
302			Value::Utf8(text) => text.capacity(),
303			Value::Blob(blob) => blob.as_bytes().len(),
304			Value::Int(_) | Value::Uint(_) | Value::Decimal(_) => BIGNUM_APPROX_HEAP,
305			Value::Any(inner) => mem::size_of::<Value>() + inner.heap_size(),
306			Value::List(items) | Value::Tuple(items) => items.heap_size(),
307			Value::Record(fields) => {
308				fields.capacity() * mem::size_of::<(String, Value)>()
309					+ fields.iter()
310						.map(|(name, value)| name.capacity() + value.heap_size())
311						.sum::<usize>()
312			}
313			_ => 0,
314		}
315	}
316}
317
318#[cfg(test)]
319mod tests {
320	use super::HeapSize;
321
322	#[derive(HeapSize)]
323	struct DerivedSample {
324		name: String,
325		values: Vec<u64>,
326		count: u64,
327	}
328
329	#[test]
330	fn derived_heap_size_sums_all_fields() {
331		// The derive must sum every field so a heap-owning field added later is picked up
332		// automatically, and scalar fields must contribute zero.
333		let sample = DerivedSample {
334			name: String::with_capacity(32),
335			values: Vec::with_capacity(4),
336			count: 7,
337		};
338		assert_eq!(sample.count, 7);
339		assert_eq!(sample.heap_size(), 32 + 4 * 8);
340	}
341}