Skip to content

Commit 4a3497a

Browse files
committed
add memory usage information hooks
1 parent 87a730f commit 4a3497a

File tree

13 files changed

+458
-20
lines changed

13 files changed

+458
-20
lines changed

src/accumulator/accumulated_map.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use std::ops;
22

3-
use rustc_hash::FxHashMap;
3+
use rustc_hash::FxBuildHasher;
44

55
use crate::accumulator::accumulated::Accumulated;
66
use crate::accumulator::{Accumulator, AnyAccumulated};
@@ -9,7 +9,7 @@ use crate::IngredientIndex;
99

1010
#[derive(Default)]
1111
pub struct AccumulatedMap {
12-
map: FxHashMap<IngredientIndex, Box<dyn AnyAccumulated>>,
12+
map: hashbrown::HashMap<IngredientIndex, Box<dyn AnyAccumulated>, FxBuildHasher>,
1313
}
1414

1515
impl std::fmt::Debug for AccumulatedMap {
@@ -50,6 +50,10 @@ impl AccumulatedMap {
5050
pub fn clear(&mut self) {
5151
self.map.clear()
5252
}
53+
54+
pub fn allocation_size(&self) -> usize {
55+
self.map.allocation_size()
56+
}
5357
}
5458

5559
/// Tracks whether any input read during a query's execution has any accumulated values.

src/cycle.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,8 @@
4949
//! cycle head may then iterate, which may result in a new set of iterations on the inner cycle,
5050
//! for each iteration of the outer cycle.
5151
52+
use std::mem;
53+
5254
use thin_vec::{thin_vec, ThinVec};
5355

5456
use crate::key::DatabaseKeyIndex;
@@ -210,6 +212,10 @@ impl CycleHeads {
210212
}
211213
}
212214
}
215+
216+
pub(crate) fn allocation_size(&self) -> usize {
217+
mem::size_of_val(self.0.as_slice())
218+
}
213219
}
214220

215221
impl IntoIterator for CycleHeads {

src/database.rs

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
use std::any::Any;
22
use std::borrow::Cow;
33

4+
use hashbrown::HashMap;
5+
46
use crate::zalsa::{IngredientIndex, ZalsaDatabase};
57
use crate::{Durability, Revision};
68

@@ -132,4 +134,99 @@ impl dyn Database {
132134
let views = self.zalsa().views();
133135
views.downcaster_for().downcast(self)
134136
}
137+
138+
/// Returns information about any Salsa structs.
139+
pub fn structs_info(&self) -> Vec<IngredientInfo> {
140+
self.zalsa()
141+
.ingredients()
142+
.filter_map(|ingredient| {
143+
let mut size_of_fields = 0;
144+
let mut size_of_metadata = 0;
145+
let mut instances = 0;
146+
147+
for slot in ingredient.memory_usage(self)? {
148+
instances += 1;
149+
size_of_fields += slot.size_of_fields;
150+
size_of_metadata += slot.size_of_metadata;
151+
}
152+
153+
Some(IngredientInfo {
154+
count: instances,
155+
size_of_fields,
156+
size_of_metadata,
157+
debug_name: ingredient.debug_name(),
158+
})
159+
})
160+
.collect()
161+
}
162+
163+
/// Returns information about any memoized Salsa queries.
164+
///
165+
/// The returned map holds memory usage information for memoized values of a given query, keyed
166+
/// by its `(input, output)` type names.
167+
pub fn queries_info(&self) -> HashMap<(&'static str, &'static str), IngredientInfo> {
168+
let mut queries = HashMap::new();
169+
170+
for input_ingredient in self.zalsa().ingredients() {
171+
let Some(input_info) = input_ingredient.memory_usage(self) else {
172+
continue;
173+
};
174+
175+
for input in input_info {
176+
for output in input.memos {
177+
let info = queries
178+
.entry((input.debug_name, output.debug_name))
179+
.or_insert(IngredientInfo {
180+
debug_name: output.debug_name,
181+
..Default::default()
182+
});
183+
184+
info.count += 1;
185+
info.size_of_fields += output.size_of_fields;
186+
info.size_of_metadata += output.size_of_metadata;
187+
}
188+
}
189+
}
190+
191+
queries
192+
}
193+
}
194+
195+
/// Information about instances of a particular Salsa ingredient.
196+
#[derive(Default, Debug, PartialEq, Eq, PartialOrd, Ord)]
197+
pub struct IngredientInfo {
198+
debug_name: &'static str,
199+
count: usize,
200+
size_of_metadata: usize,
201+
size_of_fields: usize,
202+
}
203+
204+
impl IngredientInfo {
205+
/// Returns the debug name of the ingredient.
206+
pub fn debug_name(&self) -> &'static str {
207+
self.debug_name
208+
}
209+
210+
/// Returns the total size of the fields of any instances of this ingredient, in bytes.
211+
pub fn size_of_fields(&self) -> usize {
212+
self.size_of_fields
213+
}
214+
215+
/// Returns the total size of Salsa metadata of any instances of this ingredient, in bytes.
216+
pub fn size_of_metadata(&self) -> usize {
217+
self.size_of_metadata
218+
}
219+
220+
/// Returns the number of instances of this ingredient.
221+
pub fn count(&self) -> usize {
222+
self.count
223+
}
224+
}
225+
226+
/// Memory usage information about a particular instance of struct, input or output.
227+
pub struct SlotInfo {
228+
pub(crate) debug_name: &'static str,
229+
pub(crate) size_of_metadata: usize,
230+
pub(crate) size_of_fields: usize,
231+
pub(crate) memos: Vec<SlotInfo>,
135232
}

src/function/memo.rs

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
use std::any::Any;
22
use std::fmt::{Debug, Formatter};
3-
use std::mem::transmute;
3+
use std::mem::{self, transmute};
44
use std::ptr::NonNull;
55

66
use crate::cycle::{empty_cycle_heads, CycleHead, CycleHeads, IterationCount, ProvisionalStatus};
7+
use crate::database::SlotInfo;
78
use crate::function::{Configuration, IngredientImpl};
89
use crate::hash::FxHashSet;
910
use crate::ingredient::{Ingredient, WaitForResult};
@@ -316,6 +317,17 @@ impl<V: Send + Sync + Any> crate::table::memo::Memo for Memo<V> {
316317
fn origin(&self) -> QueryOriginRef<'_> {
317318
self.revisions.origin.as_ref()
318319
}
320+
321+
fn memory_usage(&self) -> SlotInfo {
322+
let size_of = mem::size_of::<Memo<V>>() + self.revisions.allocation_size();
323+
324+
SlotInfo {
325+
size_of_metadata: size_of - mem::size_of::<V>(),
326+
debug_name: std::any::type_name::<V>(),
327+
size_of_fields: mem::size_of::<V>(),
328+
memos: Vec::new(),
329+
}
330+
}
319331
}
320332

321333
pub(super) enum TryClaimHeadsResult<'me> {

src/ingredient.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ use crate::accumulator::accumulated_map::{AccumulatedMap, InputAccumulatedValues
55
use crate::cycle::{
66
empty_cycle_heads, CycleHeads, CycleRecoveryStrategy, IterationCount, ProvisionalStatus,
77
};
8+
use crate::database::SlotInfo;
89
use crate::function::VerifyResult;
910
use crate::plumbing::IngredientIndices;
1011
use crate::runtime::Running;
@@ -181,6 +182,12 @@ pub trait Ingredient: Any + std::fmt::Debug + Send + Sync {
181182
let _ = (db, key_index);
182183
(None, InputAccumulatedValues::Empty)
183184
}
185+
186+
/// Returns memory usage information about any instances of the ingredient,
187+
/// if applicable.
188+
fn memory_usage(&self, _db: &dyn Database) -> Option<Vec<SlotInfo>> {
189+
None
190+
}
184191
}
185192

186193
impl dyn Ingredient {

src/input.rs

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use std::any::{Any, TypeId};
2-
use std::fmt;
32
use std::ops::IndexMut;
3+
use std::{fmt, mem};
44

55
pub mod input_field;
66
pub mod setter;
@@ -9,6 +9,7 @@ pub mod singleton;
99
use input_field::FieldIngredientImpl;
1010

1111
use crate::cycle::CycleHeads;
12+
use crate::database::SlotInfo;
1213
use crate::function::VerifyResult;
1314
use crate::id::{AsId, FromId, FromIdWithDb};
1415
use crate::ingredient::Ingredient;
@@ -241,6 +242,17 @@ impl<C: Configuration> Ingredient for IngredientImpl<C> {
241242
fn memo_table_types(&self) -> Arc<MemoTableTypes> {
242243
self.memo_table_types.clone()
243244
}
245+
246+
/// Returns memory usage information about any inputs.
247+
fn memory_usage(&self, db: &dyn Database) -> Option<Vec<SlotInfo>> {
248+
let memory_usage = self
249+
.entries(db)
250+
// SAFETY: The memo table belongs to a value that we allocated, so it
251+
// has the correct type.
252+
.map(|value| unsafe { value.memory_usage(&self.memo_table_types) })
253+
.collect();
254+
Some(memory_usage)
255+
}
244256
}
245257

246258
impl<C: Configuration> std::fmt::Debug for IngredientImpl<C> {
@@ -284,6 +296,23 @@ where
284296
pub fn fields(&self) -> &C::Fields {
285297
&self.fields
286298
}
299+
300+
/// Returns memory usage information about the input.
301+
///
302+
/// # Safety
303+
///
304+
/// The `MemoTable` must belong to a `Value` of the correct type.
305+
unsafe fn memory_usage(&self, memo_table_types: &MemoTableTypes) -> SlotInfo {
306+
// SAFETY: The caller guarantees this is the correct types table.
307+
let memos = unsafe { memo_table_types.attach_memos(&self.memos) };
308+
309+
SlotInfo {
310+
debug_name: C::DEBUG_NAME,
311+
size_of_metadata: mem::size_of::<Self>() - mem::size_of::<C::Fields>(),
312+
size_of_fields: mem::size_of::<C::Fields>(),
313+
memos: memos.memory_usage(),
314+
}
315+
}
287316
}
288317

289318
pub trait HasBuilder {

src/interned.rs

Lines changed: 51 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
use std::any::TypeId;
22
use std::cell::{Cell, UnsafeCell};
3-
use std::fmt;
43
use std::hash::{BuildHasher, Hash, Hasher};
54
use std::marker::PhantomData;
65
use std::num::NonZeroUsize;
76
use std::path::{Path, PathBuf};
7+
use std::{fmt, mem};
88

99
use crossbeam_utils::CachePadded;
1010
use intrusive_collections::{intrusive_adapter, LinkedList, LinkedListLink, UnsafeRef};
@@ -21,7 +21,7 @@ use crate::sync::{Arc, Mutex, OnceLock};
2121
use crate::table::memo::{MemoTable, MemoTableTypes, MemoTableWithTypesMut};
2222
use crate::table::Slot;
2323
use crate::zalsa::{IngredientIndex, Zalsa};
24-
use crate::{Database, DatabaseKeyIndex, Event, EventKind, Id, Revision};
24+
use crate::{Database, DatabaseKeyIndex, Event, EventKind, Id, Revision, SlotInfo};
2525

2626
/// Trait that defines the key properties of an interned struct.
2727
///
@@ -190,6 +190,28 @@ where
190190
// ensures that they are not reused while being accessed.
191191
unsafe { &*self.fields.get() }
192192
}
193+
194+
/// Returns memory usage information about the interned value.
195+
///
196+
/// # Safety
197+
///
198+
/// The `MemoTable` must belong to a `Value` of the correct type. Additionally, the
199+
/// lock must be held for the shard containing the value.
200+
#[cfg(not(feature = "shuttle"))]
201+
unsafe fn memory_usage(&self, memo_table_types: &MemoTableTypes) -> SlotInfo {
202+
// SAFETY: The caller guarantees we hold the lock for the shard containing the value, so we
203+
// have at-least read-only access to the value's memos.
204+
let memos = unsafe { &*self.memos.get() };
205+
// SAFETY: The caller guarantees this is the correct types table.
206+
let memos = unsafe { memo_table_types.attach_memos(memos) };
207+
208+
SlotInfo {
209+
debug_name: C::DEBUG_NAME,
210+
size_of_metadata: mem::size_of::<Self>() - mem::size_of::<C::Fields<'_>>(),
211+
size_of_fields: mem::size_of::<C::Fields<'_>>(),
212+
memos: memos.memory_usage(),
213+
}
214+
}
193215
}
194216

195217
impl<C: Configuration> Default for JarImpl<C> {
@@ -680,7 +702,7 @@ where
680702
//
681703
// # Safety
682704
//
683-
// The lock must be held.
705+
// The lock must be held for the shard containing the value.
684706
unsafe fn value_hash<'db>(&'db self, id: Id, zalsa: &'db Zalsa) -> u64 {
685707
// This closure is only called if the table is resized. So while it's expensive
686708
// to lookup all values, it will only happen rarely.
@@ -694,7 +716,7 @@ where
694716
//
695717
// # Safety
696718
//
697-
// The lock must be held.
719+
// The lock must be held for the shard containing the value.
698720
unsafe fn value_eq<'db, Key>(
699721
id: Id,
700722
key: &Key,
@@ -830,6 +852,31 @@ where
830852
fn memo_table_types(&self) -> Arc<MemoTableTypes> {
831853
self.memo_table_types.clone()
832854
}
855+
856+
/// Returns memory usage information about any interned values.
857+
#[cfg(not(feature = "shuttle"))]
858+
fn memory_usage(&self, db: &dyn Database) -> Option<Vec<SlotInfo>> {
859+
use parking_lot::lock_api::RawMutex;
860+
861+
for shard in self.shards.iter() {
862+
// SAFETY: We do not hold any active mutex guards.
863+
unsafe { shard.raw().lock() };
864+
}
865+
866+
let memory_usage = self
867+
.entries(db)
868+
// SAFETY: The memo table belongs to a value that we allocated, so it
869+
// has the correct type. Additionally, we are holding the locks for all shards.
870+
.map(|value| unsafe { value.memory_usage(&self.memo_table_types) })
871+
.collect();
872+
873+
for shard in self.shards.iter() {
874+
// SAFETY: We acquired the locks for all shards.
875+
unsafe { shard.raw().unlock() };
876+
}
877+
878+
Some(memory_usage)
879+
}
833880
}
834881

835882
impl<C> std::fmt::Debug for IngredientImpl<C>

src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ pub use self::accumulator::Accumulator;
4343
pub use self::active_query::Backtrace;
4444
pub use self::cancelled::Cancelled;
4545
pub use self::cycle::CycleRecoveryAction;
46-
pub use self::database::{AsDynDatabase, Database};
46+
pub use self::database::{AsDynDatabase, Database, SlotInfo};
4747
pub use self::database_impl::DatabaseImpl;
4848
pub use self::durability::Durability;
4949
pub use self::event::{Event, EventKind};

0 commit comments

Comments
 (0)