Skip to main content

cranpose_core/slot/
introspection.rs

1use std::mem;
2
3use super::{
4    GroupRecord, SlotDebugAnchor, SlotDebugEntry, SlotDebugEntryKind, SlotDebugGroup,
5    SlotDebugScope, SlotDebugSnapshot, SlotTable, SlotTableLocalDebugStats,
6    SlotTableMutationDebugStats,
7};
8use crate::{Key, RecomposeScope, ScopeId};
9
10impl SlotTable {
11    fn group_heap_bytes(&self) -> usize {
12        self.groups.capacity() * mem::size_of::<GroupRecord>()
13    }
14
15    /// Heap bytes reserved by the table for groups, payloads, nodes and anchors.
16    pub fn heap_bytes(&self) -> usize {
17        self.group_heap_bytes()
18            + self.payloads.heap_bytes()
19            + self.node_heap_bytes()
20            + self.anchors.heap_bytes()
21            + self.payload_anchors.heap_bytes()
22    }
23
24    /// Returns size and count figures for this table alone, without retained or pending-drop data.
25    pub fn debug_stats(&self) -> SlotTableLocalDebugStats {
26        SlotTableLocalDebugStats {
27            group_count: self.groups.len(),
28            group_capacity: self.groups.capacity(),
29            group_record_size: mem::size_of::<GroupRecord>(),
30            group_heap_bytes: self.group_heap_bytes(),
31            payload_count: self.total_payload_count(),
32            payload_capacity: self.payloads.capacity(),
33            active_payload_anchor_count: self.payload_anchors.active_len(),
34            payload_anchor_slot_count: self.payload_anchors.slot_len(),
35            detached_payload_anchor_count: self.payload_anchors.detached_len(),
36            invalidated_payload_anchor_count: self.payload_anchors.invalidated_len(),
37            free_payload_anchor_count: self.payload_anchors.free_len(),
38            payload_anchor_capacity: self.payload_anchors.capacity(),
39            payload_anchor_heap_bytes: self.payload_anchors.heap_bytes(),
40            node_count: self.total_node_count(),
41            node_capacity: self.node_debug_capacity(),
42            active_anchor_count: self.anchors.active_len(),
43            anchor_slot_count: self.anchors.slot_len(),
44            anchor_sparse_count: self.anchors.sparse_slot_len(),
45            detached_anchor_count: self.anchors.detached_len(),
46            invalidated_anchor_count: self.anchors.invalidated_len(),
47            free_anchor_count: self.anchors.free_len(),
48            anchor_capacity: self.anchors.capacity(),
49            anchor_heap_bytes: self.anchors.heap_bytes(),
50            mutation: SlotTableMutationDebugStats {
51                payload_shift_bytes: self.payloads.shift_bytes(),
52                ..self.diagnostics.mutation()
53            },
54        }
55    }
56
57    /// Returns one `(group index, call site key, scope id, subtree length)` tuple per group.
58    pub fn debug_dump_groups(&self) -> Vec<(usize, Key, Option<ScopeId>, usize)> {
59        self.groups
60            .iter()
61            .enumerate()
62            .map(|(index, group)| {
63                (
64                    index,
65                    group.key.static_key,
66                    group.scope.as_ref().map(RecomposeScope::id),
67                    group.subtree_len as usize,
68                )
69            })
70            .collect()
71    }
72
73    /// Returns a copy of the groups, anchors and scopes of this table.
74    /// The retained subtree counts are zero here; the composer fills them in.
75    pub fn debug_snapshot(&self) -> SlotDebugSnapshot {
76        let active_groups = self
77            .groups
78            .iter()
79            .enumerate()
80            .map(|(index, group)| SlotDebugGroup {
81                index,
82                anchor: group.anchor,
83                parent_anchor: group.parent_anchor,
84                static_key: group.key.static_key,
85                explicit_key: group.key.explicit_key,
86                ordinal: group.key.ordinal,
87                scope_id: group.scope.as_ref().map(RecomposeScope::id),
88                depth: group.depth,
89                subtree_len: group.subtree_len,
90                payload_len: self.group_payload_len_at(index),
91                node_len: self.group_node_len_at(index),
92                subtree_node_count: group.subtree_node_count,
93            })
94            .collect::<Vec<_>>();
95        let mut anchors = self
96            .anchors
97            .active_entries()
98            .map(|(anchor, stored)| SlotDebugAnchor {
99                anchor,
100                group_index: self.resolve_group_index(stored, anchor).unwrap_or(stored),
101            })
102            .collect::<Vec<_>>();
103        anchors.sort_by_key(|entry| entry.group_index);
104
105        let mut scopes = self
106            .groups
107            .iter()
108            .enumerate()
109            .filter_map(|(group_index, group)| {
110                group.scope.as_ref().map(|scope| SlotDebugScope {
111                    scope_id: scope.id(),
112                    anchor: group.anchor,
113                    group_index,
114                })
115            })
116            .collect::<Vec<_>>();
117        scopes.sort_by_key(|entry| entry.scope_id);
118
119        SlotDebugSnapshot {
120            active_payload_count: self.total_payload_count(),
121            active_node_count: self.total_node_count(),
122            active_scope_count: scopes.len(),
123            active_groups,
124            anchors,
125            scopes,
126            retained_subtree_count: 0,
127            retained_group_count: 0,
128            retained_payload_count: 0,
129            retained_node_count: 0,
130            retained_scope_count: 0,
131        }
132    }
133
134    /// Returns one row per group, payload and node in the table, in that order.
135    pub fn debug_dump_slot_entries(&self) -> Vec<SlotDebugEntry> {
136        let mut rows = Vec::new();
137        for (index, group) in self.groups.iter().enumerate() {
138            rows.push(SlotDebugEntry {
139                kind: SlotDebugEntryKind::Group,
140                path: format!("group[{index}]"),
141                line: format!(
142                    "Group(key={:?}, scope={:?}, subtree_len={}, payload_len={}, node_len={})",
143                    group.key,
144                    group.scope.as_ref().map(RecomposeScope::id),
145                    group.subtree_len,
146                    self.group_payload_len_at(index),
147                    self.group_node_len_at(index)
148                ),
149            });
150        }
151        for (group_index, group) in self.groups.iter().enumerate() {
152            for (payload_index, payload) in self.group_payload_records_at(group_index).enumerate() {
153                rows.push(SlotDebugEntry {
154                    kind: SlotDebugEntryKind::Payload,
155                    path: format!("group[{group_index}].payload[{payload_index}]"),
156                    line: format!(
157                        "Payload(owner={:?}, kind={}, type={})",
158                        group.anchor,
159                        payload.kind.label(),
160                        (payload.payload_type.type_name)()
161                    ),
162                });
163            }
164        }
165        for (group_index, _) in self.groups.iter().enumerate() {
166            for (node_index, node) in self.group_node_records_at(group_index).iter().enumerate() {
167                rows.push(SlotDebugEntry {
168                    kind: SlotDebugEntryKind::Node,
169                    path: format!("group[{group_index}].node[{node_index}]"),
170                    line: format!(
171                        "Node(owner={:?}, parent={:?}, id={}, gen={}, lifecycle={:?})",
172                        node.owner, node.parent_id, node.id, node.generation, node.lifecycle
173                    ),
174                });
175            }
176        }
177        rows
178    }
179}