Skip to content

Commit c145596

Browse files
authored
Add API to dump memory usage (#916)
* add memory usage information hooks * gate memory usage API under `salsa_unstable` feature * use snapshot tests for memory usage API
1 parent 87a730f commit c145596

File tree

14 files changed

+472
-14
lines changed

14 files changed

+472
-14
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: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,11 @@ impl CycleHeads {
210210
}
211211
}
212212
}
213+
214+
#[cfg(feature = "salsa_unstable")]
215+
pub(crate) fn allocation_size(&self) -> usize {
216+
std::mem::size_of_val(self.0.as_slice())
217+
}
213218
}
214219

215220
impl IntoIterator for CycleHeads {

src/database.rs

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

src/function/memo.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -316,6 +316,18 @@ impl<V: Send + Sync + Any> crate::table::memo::Memo for Memo<V> {
316316
fn origin(&self) -> QueryOriginRef<'_> {
317317
self.revisions.origin.as_ref()
318318
}
319+
320+
#[cfg(feature = "salsa_unstable")]
321+
fn memory_usage(&self) -> crate::SlotInfo {
322+
let size_of = std::mem::size_of::<Memo<V>>() + self.revisions.allocation_size();
323+
324+
crate::SlotInfo {
325+
size_of_metadata: size_of - std::mem::size_of::<V>(),
326+
debug_name: std::any::type_name::<V>(),
327+
size_of_fields: std::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
@@ -181,6 +181,13 @@ pub trait Ingredient: Any + std::fmt::Debug + Send + Sync {
181181
let _ = (db, key_index);
182182
(None, InputAccumulatedValues::Empty)
183183
}
184+
185+
/// Returns memory usage information about any instances of the ingredient,
186+
/// if applicable.
187+
#[cfg(feature = "salsa_unstable")]
188+
fn memory_usage(&self, _db: &dyn Database) -> Option<Vec<crate::SlotInfo>> {
189+
None
190+
}
184191
}
185192

186193
impl dyn Ingredient {

src/input.rs

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,18 @@ impl<C: Configuration> Ingredient for IngredientImpl<C> {
241241
fn memo_table_types(&self) -> Arc<MemoTableTypes> {
242242
self.memo_table_types.clone()
243243
}
244+
245+
/// Returns memory usage information about any inputs.
246+
#[cfg(feature = "salsa_unstable")]
247+
fn memory_usage(&self, db: &dyn Database) -> Option<Vec<crate::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,24 @@ 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+
#[cfg(feature = "salsa_unstable")]
306+
unsafe fn memory_usage(&self, memo_table_types: &MemoTableTypes) -> crate::SlotInfo {
307+
// SAFETY: The caller guarantees this is the correct types table.
308+
let memos = unsafe { memo_table_types.attach_memos(&self.memos) };
309+
310+
crate::SlotInfo {
311+
debug_name: C::DEBUG_NAME,
312+
size_of_metadata: std::mem::size_of::<Self>() - std::mem::size_of::<C::Fields>(),
313+
size_of_fields: std::mem::size_of::<C::Fields>(),
314+
memos: memos.memory_usage(),
315+
}
316+
}
287317
}
288318

289319
pub trait HasBuilder {

src/interned.rs

Lines changed: 49 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -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(all(not(feature = "shuttle"), feature = "salsa_unstable"))]
201+
unsafe fn memory_usage(&self, memo_table_types: &MemoTableTypes) -> crate::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+
crate::SlotInfo {
209+
debug_name: C::DEBUG_NAME,
210+
size_of_metadata: std::mem::size_of::<Self>() - std::mem::size_of::<C::Fields<'_>>(),
211+
size_of_fields: std::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(all(not(feature = "shuttle"), feature = "salsa_unstable"))]
858+
fn memory_usage(&self, db: &dyn Database) -> Option<Vec<crate::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: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,9 @@ pub use parallel::{join, par_map};
3939
#[cfg(feature = "macros")]
4040
pub use salsa_macros::{accumulator, db, input, interned, tracked, Supertype, Update};
4141

42+
#[cfg(feature = "salsa_unstable")]
43+
pub use self::database::{IngredientInfo, SlotInfo};
44+
4245
pub use self::accumulator::Accumulator;
4346
pub use self::active_query::Backtrace;
4447
pub use self::cancelled::Cancelled;

src/table.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,7 @@ impl Table {
253253
unsafe { page.memo_types.attach_memos_mut(memos) }
254254
}
255255

256+
#[cfg(feature = "salsa_unstable")]
256257
pub(crate) fn slots_of<T: Slot>(&self) -> impl Iterator<Item = &T> + '_ {
257258
self.pages
258259
.iter()
@@ -392,6 +393,7 @@ impl Page {
392393
PageView(self, PhantomData)
393394
}
394395

396+
#[cfg(feature = "salsa_unstable")]
395397
fn cast_type<T: Slot>(&self) -> Option<PageView<'_, T>> {
396398
if self.slot_type_id == TypeId::of::<T>() {
397399
Some(PageView(self, PhantomData))

0 commit comments

Comments
 (0)