Skip to main content

pedant_core/observe/
probe.rs

1//! Proof-only observation of the production loaders and the one-pass extractor.
2//!
3//! The probe records what the ordinary loaders already do; it never supplies a
4//! second implementation. Observation state lives on the installing thread and
5//! is released when the last handle drops, so one test cannot read another's
6//! counts.
7
8use std::cell::{Cell, RefCell};
9use std::rc::{Rc, Weak};
10
11use super::event::Observation;
12
13/// Everything one installed probe observed.
14#[derive(Default)]
15struct ProbeState {
16    manifest_reads: RefCell<Vec<Box<str>>>,
17    source_reads: RefCell<Vec<Box<str>>>,
18    parses: RefCell<Vec<Box<str>>>,
19    site_visits: RefCell<Vec<Box<str>>>,
20    import_walks: RefCell<Vec<Box<str>>>,
21    capability_projections: RefCell<Vec<Box<str>>>,
22    semantic_file_setups: RefCell<Vec<Box<str>>>,
23    semantic_queries: RefCell<Vec<Box<str>>>,
24    promotions: RefCell<Vec<Box<str>>>,
25    project_loads: Cell<u32>,
26    semantic_workspace_loads: Cell<u32>,
27    dependency_chain_extensions: Cell<u64>,
28    dependency_chain_history_copies: Cell<u64>,
29}
30
31thread_local! {
32    /// The observation state of the probe installed on this thread, if any.
33    /// A weak reference means dropping the last handle releases the state
34    /// without a teardown call.
35    static INSTALLED: RefCell<Weak<ProbeState>> = const { RefCell::new(Weak::new()) };
36}
37
38/// A cloneable handle to this thread's observation state.
39#[derive(Clone)]
40pub struct ResolutionProbe {
41    state: Rc<ProbeState>,
42}
43
44impl ResolutionProbe {
45    /// Install a probe on the current thread, replacing any earlier one.
46    pub fn install() -> Self {
47        let state = Rc::new(ProbeState::default());
48        set_installed(Rc::downgrade(&state));
49        Self { state }
50    }
51
52    /// Every `Cargo.toml` the production loaders read, in order.
53    pub fn manifest_reads(&self) -> Box<[Box<str>]> {
54        snapshot_of(&self.state.manifest_reads)
55    }
56
57    /// Every Rust source the production loaders read, in order.
58    pub fn source_reads(&self) -> Box<[Box<str>]> {
59        snapshot_of(&self.state.source_reads)
60    }
61
62    /// Every Rust source production parsed, in order.
63    ///
64    /// One route reaches `syn`, so a source appears once per parse whichever
65    /// caller asked for it — the snapshot reader or the lint pipeline.
66    pub fn parses(&self) -> Box<[Box<str>]> {
67        snapshot_of(&self.state.parses)
68    }
69
70    /// Every source the one-pass site visitor walked, in order.
71    pub fn site_visits(&self) -> Box<[Box<str>]> {
72        snapshot_of(&self.state.site_visits)
73    }
74
75    /// Every `use` item whose tree the extractor walked, in order.
76    pub fn import_walks(&self) -> Box<[Box<str>]> {
77        snapshot_of(&self.state.import_walks)
78    }
79
80    /// Every stored `FileIr` the production detector projected, in order. A
81    /// consumer that reparsed instead of reusing the stored IR names its source
82    /// in [`Self::parses`] a second time rather than here, because every route
83    /// to a fresh `FileIr` parses first and every parse is recorded there.
84    pub fn capability_projections(&self) -> Box<[Box<str>]> {
85        snapshot_of(&self.state.capability_projections)
86    }
87
88    /// How many project indexes the production loader built.
89    pub fn project_loads(&self) -> u32 {
90        self.state.project_loads.get()
91    }
92
93    /// How many dependency-selection edges extended an ancestry chain.
94    pub fn dependency_chain_extensions(&self) -> u64 {
95        self.state.dependency_chain_extensions.get()
96    }
97
98    /// How many existing ancestry entries dependency selection copied while
99    /// extending chains.
100    pub fn dependency_chain_history_copies(&self) -> u64 {
101        self.state.dependency_chain_history_copies.get()
102    }
103
104    /// How many rust-analyzer workspaces were loaded.
105    pub fn semantic_workspace_loads(&self) -> u32 {
106        self.state.semantic_workspace_loads.get()
107    }
108
109    /// Every snapshot source the semantic database set up, in order. A source
110    /// whose analysis is already cached appears once, not again.
111    pub fn semantic_file_setups(&self) -> Box<[Box<str>]> {
112        snapshot_of(&self.state.semantic_file_setups)
113    }
114
115    /// Every snapshot source queried for its definition targets, in order.
116    pub fn semantic_queries(&self) -> Box<[Box<str>]> {
117        snapshot_of(&self.state.semantic_queries)
118    }
119
120    /// Every source whose reference took its candidates from a semantic edge.
121    pub fn promotions(&self) -> Box<[Box<str>]> {
122        snapshot_of(&self.state.promotions)
123    }
124}
125
126fn snapshot_of(entries: &RefCell<Vec<Box<str>>>) -> Box<[Box<str>]> {
127    entries.borrow().iter().cloned().collect()
128}
129
130/// Point this thread's slot at `state`, while the thread still holds its
131/// locals.
132///
133/// A thread whose locals are already destroyed observes nothing, which is what
134/// an uninstalled probe does, so a refused access is a no-op rather than a
135/// panic.
136fn set_installed(state: Weak<ProbeState>) {
137    let replaced = INSTALLED.try_with(|installed| installed.replace(state));
138    drop(replaced);
139}
140
141/// The observation state installed on this thread, while the thread still
142/// holds its locals.
143fn installed() -> Option<Rc<ProbeState>> {
144    INSTALLED
145        .try_with(|installed| installed.borrow().upgrade())
146        .ok()
147        .flatten()
148}
149
150/// Record one production event against the installed probe, if any.
151pub(super) fn record(event: &Observation<'_>) {
152    installed().iter().for_each(|state| apply(state, event));
153}
154
155fn apply(state: &Rc<ProbeState>, event: &Observation<'_>) {
156    match event {
157        Observation::ProjectLoad => {
158            state
159                .project_loads
160                .set(state.project_loads.get().saturating_add(1));
161        }
162        #[cfg(feature = "semantic")]
163        Observation::SemanticWorkspaceLoad => {
164            state
165                .semantic_workspace_loads
166                .set(state.semantic_workspace_loads.get().saturating_add(1));
167        }
168        Observation::ManifestRead(path) => push(&state.manifest_reads, path),
169        Observation::SourceRead(path) => push(&state.source_reads, path),
170        Observation::SourceParse(path) => push(&state.parses, path),
171        Observation::SiteVisit(path) => push(&state.site_visits, path),
172        Observation::ImportWalk(path) => push(&state.import_walks, path),
173        Observation::CapabilityProjection(path) => push(&state.capability_projections, path),
174        Observation::DependencyChainExtension {
175            history_entries_copied,
176        } => record_dependency_chain_extension(state, *history_entries_copied),
177        #[cfg(feature = "semantic")]
178        Observation::SemanticFileSetup(path) => push(&state.semantic_file_setups, path),
179        #[cfg(feature = "semantic")]
180        Observation::SemanticQuery(path) => push(&state.semantic_queries, path),
181        #[cfg(feature = "semantic")]
182        Observation::Promotion(path) => push(&state.promotions, path),
183    }
184}
185
186fn record_dependency_chain_extension(state: &ProbeState, history_entries_copied: usize) {
187    let copied = u64::try_from(history_entries_copied).unwrap_or(u64::MAX);
188    state
189        .dependency_chain_extensions
190        .set(state.dependency_chain_extensions.get().saturating_add(1));
191    state.dependency_chain_history_copies.set(
192        state
193            .dependency_chain_history_copies
194            .get()
195            .saturating_add(copied),
196    );
197}
198
199fn push(entries: &RefCell<Vec<Box<str>>>, path: &str) {
200    entries.borrow_mut().push(Box::from(path));
201}