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