Skip to main content

cranpose_core/slot/
debug.rs

1use crate::{AnchorId, Key, ScopeId};
2
3/// Size and count figures for one slot table, from `SlotTable::debug_stats`.
4/// Counts are in items and sizes are in bytes.
5#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
6pub struct SlotTableLocalDebugStats {
7    /// Groups stored in the table.
8    pub group_count: usize,
9    /// Groups the group vector holds before it reallocates.
10    pub group_capacity: usize,
11    /// Size of one group record in bytes.
12    pub group_record_size: usize,
13    /// Heap bytes reserved for group records.
14    pub group_heap_bytes: usize,
15    /// Payload slots stored across all groups.
16    pub payload_count: usize,
17    /// Payload slots the payload store holds before it reallocates.
18    pub payload_capacity: usize,
19    /// Payload anchors that point at a live payload.
20    pub active_payload_anchor_count: usize,
21    /// Entries in the payload anchor registry in any state.
22    pub payload_anchor_slot_count: usize,
23    /// Payload anchors whose payload is detached from the table.
24    pub detached_payload_anchor_count: usize,
25    /// Payload anchor entries that no longer resolve to a payload.
26    pub invalidated_payload_anchor_count: usize,
27    /// Payload anchor ids free for reuse.
28    pub free_payload_anchor_count: usize,
29    /// Payload anchor entries the registry holds before it reallocates.
30    pub payload_anchor_capacity: usize,
31    /// Heap bytes reserved by the payload anchor registry.
32    pub payload_anchor_heap_bytes: usize,
33    /// Nodes stored across all groups.
34    pub node_count: usize,
35    /// Nodes the node storage holds before it reallocates.
36    pub node_capacity: usize,
37    /// Group anchors that point at a live group.
38    pub active_anchor_count: usize,
39    /// Entries in the group anchor registry in any state.
40    pub anchor_slot_count: usize,
41    /// Anchor entries kept in the sparse map instead of the dense vector.
42    pub anchor_sparse_count: usize,
43    /// Group anchors whose group is detached from the table.
44    pub detached_anchor_count: usize,
45    /// Group anchor entries that no longer resolve to a group.
46    pub invalidated_anchor_count: usize,
47    /// Group anchor ids free for reuse.
48    pub free_anchor_count: usize,
49    /// Anchor entries the registry holds before it reallocates.
50    pub anchor_capacity: usize,
51    /// Heap bytes reserved by the group anchor registry.
52    pub anchor_heap_bytes: usize,
53    /// Counters for subtree moves and index refreshes.
54    #[cfg(any(test, feature = "leak-debug"))]
55    pub mutation: SlotTableMutationDebugStats,
56}
57
58#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
59pub(crate) struct SlotLifecycleDebugStats {
60    pub pending_drop_count: usize,
61    pub pending_drop_capacity: usize,
62}
63
64/// Counters for subtrees the composer keeps for hidden content, from the retention state.
65/// Counts are in items and sizes are in bytes.
66#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
67pub struct SlotRetentionDebugStats {
68    /// Subtrees currently retained.
69    pub retained_subtree_count: usize,
70    /// Groups inside retained subtrees.
71    pub retained_group_count: usize,
72    /// Payload slots inside retained subtrees.
73    pub retained_payload_count: usize,
74    /// Nodes inside retained subtrees.
75    pub retained_node_count: usize,
76    /// Recompose scopes inside retained subtrees.
77    pub retained_scope_count: usize,
78    /// Group anchors held by retained subtrees.
79    pub retained_anchor_count: usize,
80    /// Heap bytes held by retained subtrees.
81    pub retained_heap_bytes: usize,
82    /// Subtrees dropped from retention since the composition started.
83    pub retained_evictions_total: usize,
84}
85
86/// Combined size and count figures for a slot table: the table's own figures, its pending
87/// drops and its retained subtrees.
88/// Counts are in items and sizes are in bytes.
89#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
90pub struct SlotTableDebugStats {
91    /// Groups stored in the table.
92    pub group_count: usize,
93    /// Groups the group vector holds before it reallocates.
94    pub group_capacity: usize,
95    /// Size of one group record in bytes.
96    pub group_record_size: usize,
97    /// Heap bytes reserved for group records.
98    pub group_heap_bytes: usize,
99    /// Payload slots stored across all groups.
100    pub payload_count: usize,
101    /// Payload slots the payload store holds before it reallocates.
102    pub payload_capacity: usize,
103    /// Payload anchors that point at a live payload.
104    pub active_payload_anchor_count: usize,
105    /// Entries in the payload anchor registry in any state.
106    pub payload_anchor_slot_count: usize,
107    /// Payload anchors whose payload is detached from the table.
108    pub detached_payload_anchor_count: usize,
109    /// Payload anchor entries that no longer resolve to a payload.
110    pub invalidated_payload_anchor_count: usize,
111    /// Payload anchor ids free for reuse.
112    pub free_payload_anchor_count: usize,
113    /// Payload anchor entries the registry holds before it reallocates.
114    pub payload_anchor_capacity: usize,
115    /// Heap bytes reserved by the payload anchor registry.
116    pub payload_anchor_heap_bytes: usize,
117    /// Nodes stored across all groups.
118    pub node_count: usize,
119    /// Nodes the node storage holds before it reallocates.
120    pub node_capacity: usize,
121    /// Removed values waiting for their deferred drop.
122    pub pending_drop_count: usize,
123    /// Values the pending drop queue holds before it reallocates.
124    pub pending_drop_capacity: usize,
125    /// Group anchors that point at a live group.
126    pub active_anchor_count: usize,
127    /// Entries in the group anchor registry in any state.
128    pub anchor_slot_count: usize,
129    /// Anchor entries kept in the sparse map instead of the dense vector.
130    pub anchor_sparse_count: usize,
131    /// Group anchors whose group is detached from the table.
132    pub detached_anchor_count: usize,
133    /// Group anchor entries that no longer resolve to a group.
134    pub invalidated_anchor_count: usize,
135    /// Group anchor ids free for reuse.
136    pub free_anchor_count: usize,
137    /// Anchor entries the registry holds before it reallocates.
138    pub anchor_capacity: usize,
139    /// Heap bytes reserved by the group anchor registry.
140    pub anchor_heap_bytes: usize,
141    /// Subtrees currently retained.
142    pub retained_subtree_count: usize,
143    /// Groups inside retained subtrees.
144    pub retained_group_count: usize,
145    /// Payload slots inside retained subtrees.
146    pub retained_payload_count: usize,
147    /// Nodes inside retained subtrees.
148    pub retained_node_count: usize,
149    /// Recompose scopes inside retained subtrees.
150    pub retained_scope_count: usize,
151    /// Group anchors held by retained subtrees.
152    pub retained_anchor_count: usize,
153    /// Heap bytes held by retained subtrees.
154    pub retained_heap_bytes: usize,
155    /// Subtrees dropped from retention since the composition started.
156    pub retained_evictions_total: usize,
157    /// Counters for subtree moves and index refreshes.
158    #[cfg(any(test, feature = "leak-debug"))]
159    pub mutation: SlotTableMutationDebugStats,
160}
161
162impl SlotTableDebugStats {
163    pub(crate) fn from_parts(
164        local: SlotTableLocalDebugStats,
165        lifecycle: SlotLifecycleDebugStats,
166        retention: SlotRetentionDebugStats,
167    ) -> Self {
168        Self {
169            group_count: local.group_count,
170            group_capacity: local.group_capacity,
171            group_record_size: local.group_record_size,
172            group_heap_bytes: local.group_heap_bytes,
173            payload_count: local.payload_count,
174            payload_capacity: local.payload_capacity,
175            active_payload_anchor_count: local.active_payload_anchor_count,
176            payload_anchor_slot_count: local.payload_anchor_slot_count,
177            detached_payload_anchor_count: local.detached_payload_anchor_count,
178            invalidated_payload_anchor_count: local.invalidated_payload_anchor_count,
179            free_payload_anchor_count: local.free_payload_anchor_count,
180            payload_anchor_capacity: local.payload_anchor_capacity,
181            payload_anchor_heap_bytes: local.payload_anchor_heap_bytes,
182            node_count: local.node_count,
183            node_capacity: local.node_capacity,
184            pending_drop_count: lifecycle.pending_drop_count,
185            pending_drop_capacity: lifecycle.pending_drop_capacity,
186            active_anchor_count: local.active_anchor_count,
187            anchor_slot_count: local.anchor_slot_count,
188            anchor_sparse_count: local.anchor_sparse_count,
189            detached_anchor_count: local.detached_anchor_count,
190            invalidated_anchor_count: local.invalidated_anchor_count,
191            free_anchor_count: local.free_anchor_count,
192            anchor_capacity: local.anchor_capacity,
193            anchor_heap_bytes: local.anchor_heap_bytes,
194            retained_subtree_count: retention.retained_subtree_count,
195            retained_group_count: retention.retained_group_count,
196            retained_payload_count: retention.retained_payload_count,
197            retained_node_count: retention.retained_node_count,
198            retained_scope_count: retention.retained_scope_count,
199            retained_anchor_count: retention.retained_anchor_count,
200            retained_heap_bytes: retention.retained_heap_bytes,
201            retained_evictions_total: retention.retained_evictions_total,
202            #[cfg(any(test, feature = "leak-debug"))]
203            mutation: local.mutation,
204        }
205    }
206}
207
208/// Counters for work the slot table does when groups move, from the table's debug stats.
209/// `*_count` fields count operations or items summed over all operations; `*_max_span` fields
210/// hold the largest single operation.
211#[cfg(any(test, feature = "leak-debug"))]
212#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
213pub struct SlotTableMutationDebugStats {
214    /// Subtree moves done.
215    pub subtree_move_count: usize,
216    /// Groups moved, summed over all subtree moves.
217    pub moved_group_count: usize,
218    /// Most groups moved by one subtree move.
219    pub moved_group_max_span: usize,
220    /// Payload slots moved, summed over all subtree moves.
221    pub moved_payload_count: usize,
222    /// Most payload slots moved by one subtree move.
223    pub moved_payload_max_span: usize,
224    /// Nodes moved, summed over all subtree moves.
225    pub moved_node_count: usize,
226    /// Most nodes moved by one subtree move.
227    pub moved_node_max_span: usize,
228    /// Times the payload locations of a range of groups were rewritten.
229    pub payload_location_range_refresh_count: usize,
230    /// Groups covered, summed over all range refreshes of payload locations.
231    pub payload_location_range_refresh_group_count: usize,
232    /// Most groups covered by one range refresh of payload locations.
233    pub payload_location_range_refresh_group_max_span: usize,
234    /// Payload slots rewritten, summed over all range refreshes of payload locations.
235    pub payload_location_range_refresh_payload_count: usize,
236    /// Most payload slots rewritten by one range refresh of payload locations.
237    pub payload_location_range_refresh_payload_max_span: usize,
238    /// Times the payload locations of a single group were rewritten.
239    pub payload_location_refresh_count: usize,
240    /// Payload slots rewritten, summed over all single-group refreshes of payload locations.
241    pub payload_location_refresh_payload_count: usize,
242    /// Most payload slots rewritten by one single-group refresh of payload locations.
243    pub payload_location_refresh_max_span: usize,
244    /// Times the stored group indexes behind the group anchors were rewritten.
245    pub group_index_refresh_count: usize,
246    /// Groups covered, summed over all group index refreshes.
247    pub group_index_refresh_group_count: usize,
248    /// Most groups covered by one group index refresh.
249    pub group_index_refresh_max_span: usize,
250    /// Times the cached payload and node segment ranges were updated.
251    pub segment_range_update_count: usize,
252    /// Groups covered, summed over all segment range updates.
253    pub segment_range_update_group_count: usize,
254    /// Most groups covered by one segment range update.
255    pub segment_range_update_max_span: usize,
256    /// Bytes the payload storage moved to insert, remove, detach, restore or
257    /// reorder payloads.
258    pub payload_shift_bytes: usize,
259}
260
261// The slot table's mutation counters, or without leak-debug a stand-in whose
262// records compile out.
263#[cfg(any(test, feature = "leak-debug"))]
264pub(crate) type SlotTableDiagnostics = SlotTableMutationDebugStats;
265
266#[cfg(not(any(test, feature = "leak-debug")))]
267#[derive(Default)]
268pub(crate) struct SlotTableDiagnostics;
269
270#[cfg(not(any(test, feature = "leak-debug")))]
271impl SlotTableDiagnostics {
272    pub(crate) fn record_subtree_move(&mut self, _groups: usize, _payloads: usize, _nodes: usize) {}
273
274    pub(crate) fn record_payload_location_range_refresh(
275        &mut self,
276        _groups: usize,
277        _payloads: usize,
278    ) {
279    }
280
281    pub(crate) fn record_group_index_refresh(&mut self, _groups: usize) {}
282
283    pub(crate) fn record_payload_location_refresh(&mut self, _payloads: usize) {}
284
285    pub(crate) fn record_segment_range_update(&mut self, _groups: usize) {}
286}
287
288#[cfg(any(test, feature = "leak-debug"))]
289impl SlotTableMutationDebugStats {
290    pub(crate) fn record_subtree_move(
291        &mut self,
292        group_span: usize,
293        payload_span: usize,
294        node_span: usize,
295    ) {
296        self.subtree_move_count = self.subtree_move_count.saturating_add(1);
297        self.moved_group_count = self.moved_group_count.saturating_add(group_span);
298        self.moved_group_max_span = self.moved_group_max_span.max(group_span);
299        self.moved_payload_count = self.moved_payload_count.saturating_add(payload_span);
300        self.moved_payload_max_span = self.moved_payload_max_span.max(payload_span);
301        self.moved_node_count = self.moved_node_count.saturating_add(node_span);
302        self.moved_node_max_span = self.moved_node_max_span.max(node_span);
303    }
304
305    pub(crate) fn record_payload_location_range_refresh(
306        &mut self,
307        group_span: usize,
308        payload_span: usize,
309    ) {
310        self.payload_location_range_refresh_count =
311            self.payload_location_range_refresh_count.saturating_add(1);
312        self.payload_location_range_refresh_group_count = self
313            .payload_location_range_refresh_group_count
314            .saturating_add(group_span);
315        self.payload_location_range_refresh_group_max_span = self
316            .payload_location_range_refresh_group_max_span
317            .max(group_span);
318        self.payload_location_range_refresh_payload_count = self
319            .payload_location_range_refresh_payload_count
320            .saturating_add(payload_span);
321        self.payload_location_range_refresh_payload_max_span = self
322            .payload_location_range_refresh_payload_max_span
323            .max(payload_span);
324    }
325
326    pub(crate) fn record_group_index_refresh(&mut self, group_span: usize) {
327        self.group_index_refresh_count = self.group_index_refresh_count.saturating_add(1);
328        self.group_index_refresh_group_count = self
329            .group_index_refresh_group_count
330            .saturating_add(group_span);
331        self.group_index_refresh_max_span = self.group_index_refresh_max_span.max(group_span);
332    }
333
334    pub(crate) fn record_payload_location_refresh(&mut self, payload_span: usize) {
335        self.payload_location_refresh_count = self.payload_location_refresh_count.saturating_add(1);
336        self.payload_location_refresh_payload_count = self
337            .payload_location_refresh_payload_count
338            .saturating_add(payload_span);
339        self.payload_location_refresh_max_span =
340            self.payload_location_refresh_max_span.max(payload_span);
341    }
342
343    pub(crate) fn record_segment_range_update(&mut self, group_span: usize) {
344        self.segment_range_update_count = self.segment_range_update_count.saturating_add(1);
345        self.segment_range_update_group_count = self
346            .segment_range_update_group_count
347            .saturating_add(group_span);
348        self.segment_range_update_max_span = self.segment_range_update_max_span.max(group_span);
349    }
350}
351
352/// The kind of row in the slot table dump returned by `SlotTable::debug_dump_slot_entries`.
353#[derive(Debug, Clone, Copy, PartialEq, Eq)]
354pub enum SlotDebugEntryKind {
355    /// A group row.
356    Group,
357    /// A payload row, such as a remembered value.
358    Payload,
359    /// A node row.
360    Node,
361}
362
363/// One row in the slot table dump returned by `SlotTable::debug_dump_slot_entries`.
364#[derive(Debug, Clone, PartialEq, Eq)]
365pub struct SlotDebugEntry {
366    /// What kind of item the row describes.
367    pub kind: SlotDebugEntryKind,
368    /// Location of the item in the table, such as `group[2].payload[0]`.
369    pub path: String,
370    /// Text description of the item for logs.
371    pub line: String,
372}
373
374/// A copy of the groups, anchors and scopes of a slot table, for tests and diagnostics.
375#[derive(Debug, Clone, PartialEq, Eq, Default)]
376pub struct SlotDebugSnapshot {
377    /// Every group in the table, in table order.
378    pub active_groups: Vec<SlotDebugGroup>,
379    /// Every live group anchor with the index of the group it points at, sorted by that index.
380    pub anchors: Vec<SlotDebugAnchor>,
381    /// Every recompose scope with its group, sorted by scope id.
382    pub scopes: Vec<SlotDebugScope>,
383    /// Payload slots in the table.
384    pub active_payload_count: usize,
385    /// Nodes in the table.
386    pub active_node_count: usize,
387    /// Groups that own a recompose scope.
388    pub active_scope_count: usize,
389    /// Subtrees retained for hidden content.
390    pub retained_subtree_count: usize,
391    /// Groups inside retained subtrees.
392    pub retained_group_count: usize,
393    /// Payload slots inside retained subtrees.
394    pub retained_payload_count: usize,
395    /// Nodes inside retained subtrees.
396    pub retained_node_count: usize,
397    /// Recompose scopes inside retained subtrees.
398    pub retained_scope_count: usize,
399}
400
401/// One group of a slot table as seen in a [`SlotDebugSnapshot`].
402#[derive(Debug, Clone, Copy, PartialEq, Eq)]
403pub struct SlotDebugGroup {
404    /// Position of the group in the table's group order.
405    pub index: usize,
406    /// Anchor that identifies this group.
407    pub anchor: AnchorId,
408    /// Anchor of the parent group.
409    pub parent_anchor: AnchorId,
410    /// Key of the call site that produced the group.
411    pub static_key: Key,
412    /// Key the caller gave the group, if any.
413    pub explicit_key: Option<Key>,
414    /// Position of the group among earlier sibling groups with the same call site key. A
415    /// keyed group has ordinal 0.
416    pub ordinal: u32,
417    /// Id of the recompose scope the group owns, if any.
418    pub scope_id: Option<ScopeId>,
419    /// Nesting depth of the group; top level groups have the lowest depth.
420    pub depth: u32,
421    /// Number of groups in the group's subtree, including the group itself.
422    pub subtree_len: u32,
423    /// Payload slots the group owns directly.
424    pub payload_len: usize,
425    /// Nodes the group owns directly.
426    pub node_len: usize,
427    /// Nodes in the group's subtree.
428    pub subtree_node_count: u32,
429}
430
431/// A live group anchor and the group it points at, in a [`SlotDebugSnapshot`].
432#[derive(Debug, Clone, Copy, PartialEq, Eq)]
433pub struct SlotDebugAnchor {
434    /// The anchor.
435    pub anchor: AnchorId,
436    /// Index of the group the anchor points at.
437    pub group_index: usize,
438}
439
440/// A recompose scope and the group that owns it, in a [`SlotDebugSnapshot`].
441#[derive(Debug, Clone, Copy, PartialEq, Eq)]
442pub struct SlotDebugScope {
443    /// Id of the recompose scope.
444    pub scope_id: ScopeId,
445    /// Anchor of the group that owns the scope.
446    pub anchor: AnchorId,
447    /// Index of the group that owns the scope.
448    pub group_index: usize,
449}