Skip to main content

oxide_core/heap/
report.rs

1//! The memory report: what every part of a runtime holds, read from the
2//! bitmaps, free lists and capacities without a collection.
3//! [`MemoryReport::reserved_bytes`] equals [`Heap::bytes`], the total the heap
4//! cap compares against.
5
6use std::fmt;
7
8use super::Heap;
9use super::arena::{Arena, Record};
10use super::gc::GcStats;
11
12/// One typed arena's records and storage.
13#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
14pub struct ArenaReport {
15    /// Live records.
16    pub live: usize,
17    /// Slots, live or free.
18    pub capacity: usize,
19    /// Bytes held by live records and their out-of-line payloads.
20    pub used_bytes: usize,
21    /// Bytes reserved: record storage, bitmaps, the free list and payloads.
22    pub reserved_bytes: usize,
23}
24
25impl ArenaReport {
26    fn of<T: Record>(arena: &Arena<T>) -> Self {
27        ArenaReport {
28            live: arena.live_count(),
29            capacity: arena.slot_count(),
30            used_bytes: arena.used_bytes(),
31            reserved_bytes: arena.bytes(),
32        }
33    }
34}
35
36/// The value heap: the ranges of slots that objects' properties and
37/// elements live in.
38#[derive(Clone, Debug, Default, PartialEq, Eq)]
39pub struct ValueHeapReport {
40    /// Slots held by live ranges.
41    pub used_slots: usize,
42    /// Free ranges by size class: `free_by_class[k]` counts free ranges of
43    /// `class_size(k)` slots.
44    pub free_by_class: Vec<usize>,
45    /// Free runs the class lists do not hold, and their slots.
46    pub free_runs: usize,
47    pub free_run_slots: usize,
48    /// Slots allocated, used or free.
49    pub capacity_slots: usize,
50    pub reserved_bytes: usize,
51}
52
53/// What the code units and their side tables hold. The static units (the
54/// code store) are not on the heap: they are not collected and do not count
55/// against the cap. The dynamic units (compiled at run time) are, and
56/// `dynamic_bytes` counts toward [`MemoryReport::reserved_bytes`].
57#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
58pub struct CodeReport {
59    /// Live units, static and dynamic.
60    pub units: usize,
61    pub static_units: usize,
62    pub dynamic_units: usize,
63    /// Bytes the dynamic units hold ([`CodeUnit::bytes`](crate::CodeUnit::bytes)).
64    pub dynamic_bytes: usize,
65    pub instructions: usize,
66    pub instruction_bytes: usize,
67    /// Bytes of the units' source-position tables.
68    pub position_bytes: usize,
69    /// Bytes of source text kept for `Function.prototype.toString`.
70    pub source_bytes: usize,
71    /// Compiled regular-expression programs, and the bytes they hold.
72    pub regexp_programs: usize,
73    pub regexp_bytes: usize,
74}
75
76/// A runtime's memory, part by part.
77#[derive(Clone, Debug, Default, PartialEq)]
78pub struct MemoryReport {
79    pub objects: ArenaReport,
80    pub shapes: ArenaReport,
81    pub strings: ArenaReport,
82    pub symbols: ArenaReport,
83    pub bigints: ArenaReport,
84    pub cells: ArenaReport,
85    pub functions: ArenaReport,
86    pub bytecode: ArenaReport,
87    pub internals: ArenaReport,
88    pub values: ValueHeapReport,
89    /// Bytes of flattened ropes (the strings arena counts a rope as empty).
90    pub rope_bytes: usize,
91    /// Interned strings.
92    pub atoms: usize,
93    /// Atoms that live as long as the heap.
94    pub pinned_atoms: usize,
95    pub code: CodeReport,
96    /// Registers the register file has room for.
97    pub register_capacity: usize,
98    /// Registers made addressable so far: the file never shrinks, so this is
99    /// its high-water mark.
100    pub register_high_water: usize,
101    /// Inline-cache entries, held or free.
102    pub ic_entries: usize,
103    /// Entries no unit holds, reused by the next units published.
104    pub ic_free: usize,
105    pub gc: GcStats,
106    /// Nominal bytes allocated since the last collection, and the count at
107    /// which the next one runs.
108    pub allocated_since_gc: usize,
109    pub threshold: usize,
110    /// The reserved-bytes budget the last collection set.
111    pub budget: usize,
112    /// Bytes of the tables beside the arenas: the ropes' halves, the atom
113    /// table and the inline caches.
114    pub table_bytes: usize,
115    /// The heap cap in bytes; `usize::MAX` for none.
116    pub cap: usize,
117}
118
119impl MemoryReport {
120    /// The arenas by name, in report order.
121    pub fn arenas(&self) -> [(&'static str, &ArenaReport); 9] {
122        [
123            ("objects", &self.objects),
124            ("shapes", &self.shapes),
125            ("strings", &self.strings),
126            ("symbols", &self.symbols),
127            ("bigints", &self.bigints),
128            ("cells", &self.cells),
129            ("functions", &self.functions),
130            ("bytecode", &self.bytecode),
131            ("internals", &self.internals),
132        ]
133    }
134
135    /// Bytes reserved on the heap: what the cap compares against.
136    pub fn reserved_bytes(&self) -> usize {
137        self.arenas()
138            .iter()
139            .map(|(_, arena)| arena.reserved_bytes)
140            .sum::<usize>()
141            + self.values.reserved_bytes
142            + self.rope_bytes
143            + self.code.dynamic_bytes
144            + self.code.regexp_bytes
145            + self.table_bytes
146    }
147}
148
149impl Heap {
150    pub(crate) fn report(&self) -> MemoryReport {
151        let mut free_by_class = Vec::new();
152        for range in self.values.free_blocks() {
153            let class = super::values::class_of(range.len) as usize;
154            if free_by_class.len() <= class {
155                free_by_class.resize(class + 1, 0);
156            }
157            free_by_class[class] += 1;
158        }
159        MemoryReport {
160            objects: ArenaReport::of(&self.objects),
161            shapes: ArenaReport::of(&self.shapes),
162            strings: ArenaReport::of(&self.strings),
163            symbols: ArenaReport::of(&self.symbols),
164            bigints: ArenaReport::of(&self.bigints),
165            cells: ArenaReport::of(&self.cells),
166            functions: ArenaReport::of(&self.payloads.functions),
167            bytecode: ArenaReport::of(&self.payloads.bytecode),
168            internals: ArenaReport::of(&self.payloads.internals),
169            values: ValueHeapReport {
170                used_slots: self.values.used_slots(),
171                free_by_class,
172                free_runs: self.values.free_runs().count(),
173                free_run_slots: self
174                    .values
175                    .free_runs()
176                    .map(|(start, end)| (end - start) as usize)
177                    .sum(),
178                capacity_slots: self.values.used_slots() + self.values.free_slots(),
179                reserved_bytes: self.values.bytes(),
180            },
181            rope_bytes: self.rope_bytes.get(),
182            atoms: self.atoms.len(),
183            pinned_atoms: self.pinned_atoms.len(),
184            code: CodeReport {
185                dynamic_units: self.code.len(),
186                dynamic_bytes: self.code.live_bytes(),
187                regexp_programs: self.regexps.len(),
188                regexp_bytes: self.regexps.bytes(),
189                ..CodeReport::default()
190            },
191            register_capacity: self.vm.registers.capacity(),
192            register_high_water: self.vm.registers.len(),
193            ic_entries: self.ics.len(),
194            ic_free: self.ics.free_len(),
195            gc: self.gc.stats,
196            allocated_since_gc: self.gc.allocated,
197            threshold: self.gc.threshold(),
198            budget: self.gc.budget(),
199            table_bytes: self.table_bytes(),
200            cap: self.gc.limits.max_bytes,
201        }
202    }
203}
204
205fn kib(bytes: usize) -> String {
206    format!("{:.1} KiB", bytes as f64 / 1024.0)
207}
208
209impl fmt::Display for MemoryReport {
210    /// `qjs --memory-report`'s table.
211    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
212        writeln!(
213            f,
214            "{:<12}{:>10}{:>10}{:>14}{:>14}",
215            "memory", "live", "capacity", "used", "reserved"
216        )?;
217        for (name, arena) in self.arenas() {
218            writeln!(
219                f,
220                "{name:<12}{:>10}{:>10}{:>14}{:>14}",
221                arena.live,
222                arena.capacity,
223                kib(arena.used_bytes),
224                kib(arena.reserved_bytes)
225            )?;
226        }
227        let free: usize = self.values.capacity_slots - self.values.used_slots;
228        writeln!(
229            f,
230            "{:<12}{:>10}{:>10}{:>14}{:>14}",
231            "values",
232            self.values.used_slots,
233            self.values.capacity_slots,
234            kib(self.values.used_slots * 8),
235            kib(self.values.reserved_bytes)
236        )?;
237        writeln!(
238            f,
239            "  free value slots: {free} in {:?} by class and {} runs of {} slots",
240            self.values.free_by_class, self.values.free_runs, self.values.free_run_slots
241        )?;
242        writeln!(
243            f,
244            "ropes: {}; atoms: {} ({} pinned); tables {}",
245            kib(self.rope_bytes),
246            self.atoms,
247            self.pinned_atoms,
248            kib(self.table_bytes)
249        )?;
250        writeln!(
251            f,
252            "heap reserved: {}; threshold {}; budget {}; cap {}",
253            kib(self.reserved_bytes()),
254            kib(self.threshold),
255            kib(self.budget),
256            if self.cap == usize::MAX {
257                "none".to_owned()
258            } else {
259                kib(self.cap)
260            }
261        )?;
262        writeln!(
263            f,
264            "code: {} units ({} static, {} dynamic holding {}), {} instructions ({}), \
265             positions {}, source {}, {} regexp programs ({})",
266            self.code.units,
267            self.code.static_units,
268            self.code.dynamic_units,
269            kib(self.code.dynamic_bytes),
270            self.code.instructions,
271            kib(self.code.instruction_bytes),
272            kib(self.code.position_bytes),
273            kib(self.code.source_bytes),
274            self.code.regexp_programs,
275            kib(self.code.regexp_bytes)
276        )?;
277        writeln!(
278            f,
279            "registers: {} high-water, {} capacity; inline caches: {} entries, {} free",
280            self.register_high_water, self.register_capacity, self.ic_entries, self.ic_free
281        )?;
282        writeln!(
283            f,
284            "gc: {} collections, {} records and {} freed, live {}",
285            self.gc.collections,
286            self.gc.freed_records,
287            kib(self.gc.freed_bytes as usize),
288            kib(self.gc.live_bytes)
289        )
290    }
291}