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		column_view::{ColumnView, ViewData},
23		container::{
24			bool_array, decimal_array::DecimalView, digest_array, fixed_array, primitive, varlen_array,
25		},
26		date::Date,
27		datetime::DateTime,
28		decimal::Decimal,
29		dictionary::DictionaryEntryId,
30		digest::Digest,
31		duration::Duration,
32		identity::IdentityId,
33		ordered_f32::OrderedF32,
34		ordered_f64::OrderedF64,
35		partition::Partition,
36		row_number::RowNumber,
37		time::Time,
38		uuid::{Uuid4, Uuid7},
39	},
40};
41
42use crate::{
43	key::any::TaggedKey,
44	state::{join::ContentVersion, timer::TimerKind},
45	value::index::encoded::EncodedIndexKey,
46};
47
48pub trait HeapSize {
49	fn heap_size(&self) -> usize;
50}
51
52#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
53pub struct StateMemory {
54	pub entries: Count,
55	pub bytes: ByteSize,
56}
57
58impl StateMemory {
59	pub const ZERO: Self = Self {
60		entries: Count::ZERO,
61		bytes: ByteSize::ZERO,
62	};
63
64	pub fn new(entries: Count, bytes: ByteSize) -> Self {
65		Self {
66			entries,
67			bytes,
68		}
69	}
70}
71
72impl Add for StateMemory {
73	type Output = Self;
74
75	fn add(self, rhs: Self) -> Self {
76		Self {
77			entries: self.entries + rhs.entries,
78			bytes: self.bytes + rhs.bytes,
79		}
80	}
81}
82
83#[derive(Clone, Copy, Debug, PartialEq, Eq)]
84pub struct StateCompleteness {
85	pub values_complete: bool,
86	pub membership_complete: bool,
87	pub absences_served: Count,
88	pub false_positives: Count,
89	pub revocations: Count,
90}
91
92impl StateCompleteness {
93	pub fn merge(self, rhs: Self) -> Self {
94		Self {
95			values_complete: self.values_complete && rhs.values_complete,
96			membership_complete: self.membership_complete && rhs.membership_complete,
97			absences_served: self.absences_served + rhs.absences_served,
98			false_positives: self.false_positives + rhs.false_positives,
99			revocations: self.revocations + rhs.revocations,
100		}
101	}
102}
103
104#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
105pub struct OperatorSample {
106	pub memory: Option<StateMemory>,
107	pub row_number_cache: Option<StateMemory>,
108}
109
110impl OperatorSample {
111	pub fn with_memory(memory: StateMemory) -> Self {
112		Self {
113			memory: Some(memory),
114			..Self::default()
115		}
116	}
117
118	pub fn with_row_number_cache(mut self, memory: StateMemory) -> Self {
119		self.row_number_cache = Some(memory);
120		self
121	}
122}
123
124macro_rules! zero_heap {
125	($($ty:ty),* $(,)?) => {
126		$(impl HeapSize for $ty {
127			fn heap_size(&self) -> usize {
128				0
129			}
130		})*
131	};
132}
133
134zero_heap!(u8, u16, u32, u64, u128, usize, i8, i16, i32, i64, i128, isize, f32, f64, bool, char, ());
135zero_heap!(OrderedF32, OrderedF64, Date, DateTime, Time, Duration, IdentityId, Uuid4, Uuid7, RowNumber, Hash128);
136zero_heap!(Partition, RowShapeFingerprint, TimerKind, ContentVersion, DictionaryEntryId);
137
138const BIGNUM_APPROX_HEAP: usize = 32;
139
140impl HeapSize for Decimal {
141	fn heap_size(&self) -> usize {
142		BIGNUM_APPROX_HEAP
143	}
144}
145
146impl HeapSize for String {
147	fn heap_size(&self) -> usize {
148		self.capacity()
149	}
150}
151
152impl HeapSize for EncodedBytes {
153	fn heap_size(&self) -> usize {
154		self.as_slice().len()
155	}
156}
157
158impl HeapSize for EncodedKey {
159	fn heap_size(&self) -> usize {
160		match self {
161			EncodedKey::Inline {
162				..
163			} => 0,
164			EncodedKey::Shared(bytes) => bytes.len() + 2 * mem::size_of::<usize>(),
165		}
166	}
167}
168
169impl HeapSize for TaggedKey {
170	fn heap_size(&self) -> usize {
171		match self {
172			TaggedKey::SortedViewRow(key) => key.run.len(),
173			TaggedKey::PartitionedSortedViewRow(key) => key.run.len(),
174			TaggedKey::RingBufferMetadata(key) => key.encoded_partition.capacity(),
175			TaggedKey::QueueDeduplication(key) => key.tail.heap_size(),
176			TaggedKey::IndexEntry(key) => key.key.heap_size(),
177			TaggedKey::OperatorState(key) => key.suffix.capacity(),
178			_ => 0,
179		}
180	}
181}
182
183impl HeapSize for EncodedIndexKey {
184	fn heap_size(&self) -> usize {
185		match self {
186			EncodedIndexKey::Inline {
187				..
188			} => 0,
189			EncodedIndexKey::Heap(bytes) => bytes.capacity(),
190		}
191	}
192}
193
194impl<T: HeapSize> HeapSize for Option<T> {
195	fn heap_size(&self) -> usize {
196		self.as_ref().map_or(0, HeapSize::heap_size)
197	}
198}
199
200impl<T: HeapSize> HeapSize for Vec<T> {
201	fn heap_size(&self) -> usize {
202		self.capacity() * mem::size_of::<T>() + self.iter().map(HeapSize::heap_size).sum::<usize>()
203	}
204}
205
206impl<T: HeapSize, const N: usize> HeapSize for [T; N] {
207	fn heap_size(&self) -> usize {
208		self.iter().map(HeapSize::heap_size).sum::<usize>()
209	}
210}
211
212impl<T: HeapSize> HeapSize for VecDeque<T> {
213	fn heap_size(&self) -> usize {
214		self.capacity() * mem::size_of::<T>() + self.iter().map(HeapSize::heap_size).sum::<usize>()
215	}
216}
217
218impl HeapSize for Digest {
219	fn heap_size(&self) -> usize {
220		self.bucket_count() * (mem::size_of::<i32>() + mem::size_of::<u64>())
221	}
222}
223
224impl<T: HeapSize> HeapSize for Box<T> {
225	fn heap_size(&self) -> usize {
226		mem::size_of::<T>() + (**self).heap_size()
227	}
228}
229
230impl<T: HeapSize> HeapSize for Arc<T> {
231	fn heap_size(&self) -> usize {
232		mem::size_of::<usize>() * 2 + mem::size_of::<T>() + (**self).heap_size()
233	}
234}
235
236impl HeapSize for Arc<str> {
237	fn heap_size(&self) -> usize {
238		mem::size_of::<usize>() * 2 + self.len()
239	}
240}
241
242impl<T: HeapSize> HeapSize for Arc<[T]> {
243	fn heap_size(&self) -> usize {
244		mem::size_of::<usize>() * 2
245			+ self.len() * mem::size_of::<T>()
246			+ self.iter().map(HeapSize::heap_size).sum::<usize>()
247	}
248}
249
250impl<T: HeapSize> HeapSize for Box<[T]> {
251	fn heap_size(&self) -> usize {
252		self.len() * mem::size_of::<T>() + self.iter().map(HeapSize::heap_size).sum::<usize>()
253	}
254}
255
256impl HeapSize for Box<str> {
257	fn heap_size(&self) -> usize {
258		self.len()
259	}
260}
261
262impl<K: HeapSize, V: HeapSize> HeapSize for BTreeMap<K, V> {
263	fn heap_size(&self) -> usize {
264		self.len() * (mem::size_of::<K>() + mem::size_of::<V>())
265			+ self.iter().map(|(k, v)| k.heap_size() + v.heap_size()).sum::<usize>()
266	}
267}
268
269impl<T: HeapSize> HeapSize for BTreeSet<T> {
270	fn heap_size(&self) -> usize {
271		self.len() * mem::size_of::<T>() + self.iter().map(HeapSize::heap_size).sum::<usize>()
272	}
273}
274
275impl<K: HeapSize, V: HeapSize, S> HeapSize for HashMap<K, V, S> {
276	fn heap_size(&self) -> usize {
277		self.capacity() * (mem::size_of::<K>() + mem::size_of::<V>() + 1)
278			+ self.iter().map(|(k, v)| k.heap_size() + v.heap_size()).sum::<usize>()
279	}
280}
281
282impl<T: HeapSize, S> HeapSize for HashSet<T, S> {
283	fn heap_size(&self) -> usize {
284		self.capacity() * (mem::size_of::<T>() + 1) + self.iter().map(HeapSize::heap_size).sum::<usize>()
285	}
286}
287
288impl<A: HeapSize, B: HeapSize> HeapSize for (A, B) {
289	fn heap_size(&self) -> usize {
290		self.0.heap_size() + self.1.heap_size()
291	}
292}
293
294impl<A: HeapSize, B: HeapSize, C: HeapSize> HeapSize for (A, B, C) {
295	fn heap_size(&self) -> usize {
296		self.0.heap_size() + self.1.heap_size() + self.2.heap_size()
297	}
298}
299
300impl HeapSize for Value {
301	fn heap_size(&self) -> usize {
302		match self {
303			Value::Utf8(text) => text.capacity(),
304			Value::Blob(blob) => blob.as_bytes().len(),
305			Value::Decimal(_) => BIGNUM_APPROX_HEAP,
306			Value::Any(inner) => mem::size_of::<Value>() + inner.heap_size(),
307			Value::Digest(digest) => digest.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
320impl HeapSize for ColumnView<'_> {
321	fn heap_size(&self) -> usize {
322		let nulls = self.logical_nulls().map_or(0, |nulls| nulls.len().div_ceil(8));
323		nulls + match &self.data {
324			ViewData::Digest {
325				container,
326				..
327			} => {
328				varlen_array::heap_size(*container)
329					+ digest_array::iter(container)
330						.flatten()
331						.map(|digest| mem::size_of::<Digest>() + digest.heap_size())
332						.sum::<usize>()
333			}
334			ViewData::Bool(a) => bool_array::heap_size(a),
335			ViewData::Decimal(DecimalView::Decimal128(d)) => primitive::heap_size(*d),
336			ViewData::Decimal(DecimalView::Decimal256(d)) => primitive::heap_size(*d),
337			ViewData::None {
338				..
339			} => 0,
340			ViewData::Float4(a) => primitive::heap_size(*a),
341			ViewData::Float8(a) => primitive::heap_size(*a),
342			ViewData::Int1(a) => primitive::heap_size(*a),
343			ViewData::Int2(a) => primitive::heap_size(*a),
344			ViewData::Int4(a) => primitive::heap_size(*a),
345			ViewData::Int8(a) => primitive::heap_size(*a),
346			ViewData::Uint1(a) => primitive::heap_size(*a),
347			ViewData::Uint2(a) => primitive::heap_size(*a),
348			ViewData::Uint4(a) => primitive::heap_size(*a),
349			ViewData::Uint8(a) => primitive::heap_size(*a),
350			ViewData::Date(t) => primitive::heap_size(*t),
351			ViewData::DateTime(t) => primitive::heap_size(*t),
352			ViewData::Time(t) => primitive::heap_size(*t),
353			ViewData::Duration(t) => primitive::heap_size(*t),
354			ViewData::IdentityId(u)
355			| ViewData::Uuid4(u)
356			| ViewData::Uuid7(u)
357			| ViewData::Int16(u)
358			| ViewData::Uint16(u)
359			| ViewData::DictionaryId {
360				container: u,
361				..
362			} => fixed_array::heap_size(u),
363			ViewData::Utf8 {
364				container: v,
365				..
366			} => varlen_array::heap_size(*v),
367			ViewData::Blob {
368				container: v,
369				..
370			}
371			| ViewData::Any {
372				container: v,
373				..
374			} => varlen_array::heap_size(*v),
375		}
376	}
377}
378
379#[cfg(test)]
380mod tests {
381	use super::HeapSize;
382
383	#[derive(HeapSize)]
384	struct DerivedSample {
385		name: String,
386		values: Vec<u64>,
387		count: u64,
388	}
389
390	#[test]
391	fn derived_heap_size_sums_all_fields() {
392		// The derive must sum every field so a heap-owning field added later is picked up
393		// automatically, and scalar fields must contribute zero.
394		let sample = DerivedSample {
395			name: String::with_capacity(32),
396			values: Vec::with_capacity(4),
397			count: 7,
398		};
399		assert_eq!(sample.count, 7);
400		assert_eq!(sample.heap_size(), 32 + 4 * 8);
401	}
402}