Skip to content

Commit 68c45b4

Browse files
committed
Add restate-memory crate with EstimatedMemorySize trait
Introduce a new restate-memory crate containing memory management utilities. The crate provides EstimatedMemorySize trait for types that can be significant contributors to memory usage. Key features: - EstimatedMemorySize trait with blanket impls for references (&T, &mut T) - Impls for common types: String, Bytes, BytesMut, [u8], str, Vec<T>, [T] - Impls for smart pointers: Arc<T>, Rc<T>, Box<T>, Cow<T> - Impls for lock guards: MutexGuard, RwLockReadGuard, RwLockWriteGuard, Ref, RefMut - Impl for Option<T> The trait is re-exported through restate-types::memory for convenience. Also implements EstimatedMemorySize for Record, Payloads, Store, and PolyBytes in restate-types.
1 parent d9eb00b commit 68c45b4

File tree

12 files changed

+382
-2
lines changed

12 files changed

+382
-2
lines changed

Cargo.lock

Lines changed: 9 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ restate-invoker-impl = { path = "crates/invoker-impl" }
6262
restate-local-cluster-runner = { path = "crates/local-cluster-runner" }
6363
restate-log-server = { path = "crates/log-server" }
6464
restate-log-server-grpc = { path = "crates/log-server-grpc" }
65+
restate-memory = { path = "crates/memory" }
6566
restate-metadata-providers = { path = "crates/metadata-providers" }
6667
restate-metadata-server = { path = "crates/metadata-server" }
6768
restate-metadata-server-grpc = { path = "crates/metadata-server-grpc" }

crates/core/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ restate-workspace-hack = { workspace = true }
2222

2323
restate-core-derive = { workspace = true, optional = true }
2424
restate-futures-util = { workspace = true }
25+
restate-memory = { workspace = true }
2526
restate-metadata-store = { workspace = true }
2627
restate-time-util = { workspace = true }
2728
restate-types = { workspace = true }

crates/core/src/network/incoming.rs

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ use tokio::sync::{oneshot, watch};
1616
use tracing::Span;
1717
use tracing_opentelemetry::OpenTelemetrySpanExt;
1818

19+
use restate_memory::EstimatedMemorySize;
1920
use restate_types::GenerationalNodeId;
2021
use restate_types::net::codec::{WireDecode, WireEncode};
2122
use restate_types::net::{ProtocolVersion, Service, UnaryMessage, WatchResponse};
@@ -255,6 +256,27 @@ impl<S> Incoming<RawSvcRpc<S>> {
255256
}
256257
}
257258

259+
impl EstimatedMemorySize for Incoming<RawRpc> {
260+
#[inline]
261+
fn estimated_memory_size(&self) -> usize {
262+
self.inner.payload.estimated_memory_size()
263+
}
264+
}
265+
266+
impl<S> EstimatedMemorySize for Incoming<RawSvcRpc<S>> {
267+
#[inline]
268+
fn estimated_memory_size(&self) -> usize {
269+
self.inner.payload.estimated_memory_size()
270+
}
271+
}
272+
273+
impl<S> EstimatedMemorySize for Incoming<Rpc<S>> {
274+
#[inline]
275+
fn estimated_memory_size(&self) -> usize {
276+
self.inner.payload.estimated_memory_size()
277+
}
278+
}
279+
258280
impl Incoming<RawRpc> {
259281
pub fn msg_type(&self) -> &str {
260282
&self.inner.msg_type
@@ -336,6 +358,27 @@ impl<S> Incoming<RawSvcUnary<S>> {
336358
}
337359
}
338360

361+
impl EstimatedMemorySize for Incoming<RawUnary> {
362+
#[inline]
363+
fn estimated_memory_size(&self) -> usize {
364+
self.inner.payload.estimated_memory_size()
365+
}
366+
}
367+
368+
impl<S> EstimatedMemorySize for Incoming<RawSvcUnary<S>> {
369+
#[inline]
370+
fn estimated_memory_size(&self) -> usize {
371+
self.inner.payload.estimated_memory_size()
372+
}
373+
}
374+
375+
impl<S> EstimatedMemorySize for Incoming<Unary<S>> {
376+
#[inline]
377+
fn estimated_memory_size(&self) -> usize {
378+
self.inner.payload.estimated_memory_size()
379+
}
380+
}
381+
339382
impl Incoming<RawUnary> {
340383
pub fn msg_type(&self) -> &str {
341384
&self.inner.msg_type
@@ -407,6 +450,27 @@ impl Incoming<RawWatch> {
407450
}
408451
}
409452

453+
impl EstimatedMemorySize for Incoming<RawWatch> {
454+
#[inline]
455+
fn estimated_memory_size(&self) -> usize {
456+
self.inner.payload.estimated_memory_size()
457+
}
458+
}
459+
460+
impl<S> EstimatedMemorySize for Incoming<RawSvcWatch<S>> {
461+
#[inline]
462+
fn estimated_memory_size(&self) -> usize {
463+
self.inner.payload.estimated_memory_size()
464+
}
465+
}
466+
467+
impl<S> EstimatedMemorySize for Incoming<Watch<S>> {
468+
#[inline]
469+
fn estimated_memory_size(&self) -> usize {
470+
self.inner.payload.estimated_memory_size()
471+
}
472+
}
473+
410474
// A polymorphic incoming subscription request bound to a certain service
411475
impl<S> Incoming<RawSvcWatch<S>> {
412476
/// The sort-code is applicable if the sender specifies a target mailbox for this message.

crates/memory/Cargo.toml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
[package]
2+
name = "restate-memory"
3+
version.workspace = true
4+
authors.workspace = true
5+
edition.workspace = true
6+
rust-version.workspace = true
7+
license.workspace = true
8+
publish = false
9+
10+
[features]
11+
default = []
12+
13+
[dependencies]
14+
restate-workspace-hack = { workspace = true }
15+
16+
bytes = { workspace = true }

crates/memory/src/footprint.rs

Lines changed: 239 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,239 @@
1+
// Copyright (c) 2023 - 2026 Restate Software, Inc., Restate GmbH.
2+
// All rights reserved.
3+
//
4+
// Use of this software is governed by the Business Source License
5+
// included in the LICENSE file.
6+
//
7+
// As of the Change Date specified in that file, in accordance with
8+
// the Business Source License, use of this software will be governed
9+
// by the Apache License, Version 2.0.
10+
11+
/// A trait for types that can be significant contributors to memory usage.
12+
pub trait EstimatedMemorySize {
13+
/// Estimated number of bytes to be used by this value in memory.
14+
fn estimated_memory_size(&self) -> usize;
15+
}
16+
17+
impl<T: EstimatedMemorySize + ?Sized> EstimatedMemorySize for &T {
18+
#[inline]
19+
fn estimated_memory_size(&self) -> usize {
20+
T::estimated_memory_size(self)
21+
}
22+
}
23+
24+
impl<T: EstimatedMemorySize + ?Sized> EstimatedMemorySize for &mut T {
25+
#[inline]
26+
fn estimated_memory_size(&self) -> usize {
27+
T::estimated_memory_size(self)
28+
}
29+
}
30+
31+
impl EstimatedMemorySize for () {
32+
#[inline]
33+
fn estimated_memory_size(&self) -> usize {
34+
0
35+
}
36+
}
37+
38+
impl<T: EstimatedMemorySize> EstimatedMemorySize for Option<T> {
39+
#[inline]
40+
fn estimated_memory_size(&self) -> usize {
41+
self.as_ref()
42+
.map_or(0, EstimatedMemorySize::estimated_memory_size)
43+
}
44+
}
45+
46+
impl<T: EstimatedMemorySize> EstimatedMemorySize for Vec<T> {
47+
#[inline]
48+
fn estimated_memory_size(&self) -> usize {
49+
self.iter()
50+
.map(EstimatedMemorySize::estimated_memory_size)
51+
.sum()
52+
}
53+
}
54+
55+
impl<T: EstimatedMemorySize> EstimatedMemorySize for [T] {
56+
#[inline]
57+
fn estimated_memory_size(&self) -> usize {
58+
self.iter()
59+
.map(EstimatedMemorySize::estimated_memory_size)
60+
.sum()
61+
}
62+
}
63+
64+
impl EstimatedMemorySize for String {
65+
#[inline]
66+
fn estimated_memory_size(&self) -> usize {
67+
self.len()
68+
}
69+
}
70+
71+
impl EstimatedMemorySize for bytes::Bytes {
72+
#[inline]
73+
fn estimated_memory_size(&self) -> usize {
74+
self.len()
75+
}
76+
}
77+
78+
impl EstimatedMemorySize for bytes::BytesMut {
79+
#[inline]
80+
fn estimated_memory_size(&self) -> usize {
81+
self.len()
82+
}
83+
}
84+
85+
impl EstimatedMemorySize for [u8] {
86+
#[inline]
87+
fn estimated_memory_size(&self) -> usize {
88+
self.len()
89+
}
90+
}
91+
92+
impl EstimatedMemorySize for str {
93+
#[inline]
94+
fn estimated_memory_size(&self) -> usize {
95+
self.len()
96+
}
97+
}
98+
99+
impl<T: EstimatedMemorySize + ?Sized> EstimatedMemorySize for std::sync::Arc<T> {
100+
#[inline]
101+
fn estimated_memory_size(&self) -> usize {
102+
T::estimated_memory_size(self)
103+
}
104+
}
105+
106+
impl<T: EstimatedMemorySize + ?Sized> EstimatedMemorySize for std::rc::Rc<T> {
107+
#[inline]
108+
fn estimated_memory_size(&self) -> usize {
109+
T::estimated_memory_size(self)
110+
}
111+
}
112+
113+
impl<T: EstimatedMemorySize + ?Sized> EstimatedMemorySize for Box<T> {
114+
#[inline]
115+
fn estimated_memory_size(&self) -> usize {
116+
T::estimated_memory_size(self)
117+
}
118+
}
119+
120+
impl<T: EstimatedMemorySize + ?Sized> EstimatedMemorySize for std::sync::MutexGuard<'_, T> {
121+
#[inline]
122+
fn estimated_memory_size(&self) -> usize {
123+
T::estimated_memory_size(self)
124+
}
125+
}
126+
127+
impl<T: EstimatedMemorySize + ?Sized> EstimatedMemorySize for std::sync::RwLockReadGuard<'_, T> {
128+
#[inline]
129+
fn estimated_memory_size(&self) -> usize {
130+
T::estimated_memory_size(self)
131+
}
132+
}
133+
134+
impl<T: EstimatedMemorySize + ?Sized> EstimatedMemorySize for std::sync::RwLockWriteGuard<'_, T> {
135+
#[inline]
136+
fn estimated_memory_size(&self) -> usize {
137+
T::estimated_memory_size(self)
138+
}
139+
}
140+
141+
impl<T: EstimatedMemorySize> EstimatedMemorySize for std::cell::Ref<'_, T> {
142+
#[inline]
143+
fn estimated_memory_size(&self) -> usize {
144+
T::estimated_memory_size(self)
145+
}
146+
}
147+
148+
impl<T: EstimatedMemorySize> EstimatedMemorySize for std::cell::RefMut<'_, T> {
149+
#[inline]
150+
fn estimated_memory_size(&self) -> usize {
151+
T::estimated_memory_size(self)
152+
}
153+
}
154+
155+
impl<T: EstimatedMemorySize + ToOwned + ?Sized> EstimatedMemorySize for std::borrow::Cow<'_, T> {
156+
#[inline]
157+
fn estimated_memory_size(&self) -> usize {
158+
T::estimated_memory_size(self.as_ref())
159+
}
160+
}
161+
162+
#[cfg(test)]
163+
mod tests {
164+
use super::*;
165+
166+
use bytes::{Bytes, BytesMut};
167+
168+
#[test]
169+
fn test_primitives() {
170+
assert_eq!(().estimated_memory_size(), 0);
171+
172+
let slice: &[u8] = &[1, 2, 3, 4, 5];
173+
assert_eq!(slice.estimated_memory_size(), 5);
174+
}
175+
176+
#[test]
177+
fn test_string_and_bytes() {
178+
assert_eq!("hello".to_string().estimated_memory_size(), 5);
179+
assert_eq!(Bytes::from_static(b"world").estimated_memory_size(), 5);
180+
assert_eq!(BytesMut::from(&b"test"[..]).estimated_memory_size(), 4);
181+
}
182+
183+
#[test]
184+
fn test_option() {
185+
let none: Option<String> = None;
186+
assert_eq!(none.estimated_memory_size(), 0);
187+
188+
let some = Some("hello".to_string());
189+
assert_eq!(some.estimated_memory_size(), 5);
190+
}
191+
192+
#[test]
193+
fn test_vec_and_slice() {
194+
let vec: Vec<String> = vec!["hello".to_string(), "world".to_string()];
195+
assert_eq!(vec.estimated_memory_size(), 10);
196+
197+
let slice: &[String] = &vec;
198+
assert_eq!(slice.estimated_memory_size(), 10);
199+
}
200+
201+
#[test]
202+
#[allow(clippy::needless_borrow, clippy::unnecessary_mut_passed)]
203+
fn test_references() {
204+
// These explicit borrows are intentional - we're testing the
205+
// EstimatedMemorySize implementations for &T and &mut T
206+
let s = "hello".to_string();
207+
assert_eq!((&s).estimated_memory_size(), 5);
208+
assert_eq!((&&s).estimated_memory_size(), 5);
209+
210+
let mut s2 = "world".to_string();
211+
assert_eq!((&mut s2).estimated_memory_size(), 5);
212+
}
213+
214+
#[test]
215+
fn test_smart_pointers() {
216+
use std::borrow::Cow;
217+
use std::rc::Rc;
218+
use std::sync::Arc;
219+
220+
// Box
221+
let boxed = Box::new("hello".to_string());
222+
assert_eq!(boxed.estimated_memory_size(), 5);
223+
224+
// Arc
225+
let arc = Arc::new("world".to_string());
226+
assert_eq!(arc.estimated_memory_size(), 5);
227+
228+
// Rc
229+
let rc = Rc::new("test".to_string());
230+
assert_eq!(rc.estimated_memory_size(), 4);
231+
232+
// Cow
233+
let cow_borrowed: Cow<'_, str> = Cow::Borrowed("borrowed");
234+
assert_eq!(cow_borrowed.estimated_memory_size(), 8);
235+
236+
let cow_owned: Cow<'_, str> = Cow::Owned("owned".to_string());
237+
assert_eq!(cow_owned.estimated_memory_size(), 5);
238+
}
239+
}

0 commit comments

Comments
 (0)