Skip to main content

nichlink_run_method/runtime/trace/
call_trace.rs

1//! The call-trace collector and the protocol surface it records.
2//! 调用追踪收集器及其记录的协议表面。
3
4use std::collections::BTreeMap;
5
6use crate::registry_core::declaration::SourceLocation;
7use crate::runtime::trace::{CallSite, TraceMode};
8use crate::runtime::{CallEdge, EvidenceKind, LogicalCallEdge};
9
10pub use super::edges::{DataEdge, DataHop};
11pub use super::locals::{LocalId, LocalKind, LocalValue, Observation};
12
13/// Runtime collection policy helpers. The enum and its parser live in the
14/// kernel; these free functions carry the build-profile and environment
15/// bindings that make them a run_method concern.
16/// 运行时追踪收集策略的辅助函数。枚举与解析器在 kernel;
17/// 以下自由函数承载让它们属于 run_method 面的构建配置与环境绑定。
18/// The policy used by an application runtime when no debug mode is chosen.
19/// 未显式选择调试模式时,应用运行时采用的策略。
20pub const fn application_default_trace_mode() -> TraceMode {
21    if cfg!(debug_assertions) {
22        TraceMode::ErrorsOnly
23    } else {
24        TraceMode::Off
25    }
26}
27
28/// Reads the optional process-level override once when a trace is created.
29/// 创建追踪器时读取一次可选的进程级覆盖配置。
30pub fn trace_mode_from_env() -> TraceMode {
31    std::env::var("NICH_LINK_TRACE")
32        .ok()
33        .and_then(|value| TraceMode::parse(&value))
34        .unwrap_or_else(application_default_trace_mode)
35}
36
37/// The runtime evidence collector shared by frames, locals, and value edges.
38/// 运行期证据收集器,由调用帧、局部值与值边共用。
39#[derive(Clone, Debug)]
40pub struct CallTrace {
41    pub(super) mode: TraceMode,
42    pub(super) frames: Vec<FrameRecord>,
43    pub(super) frame_index: BTreeMap<u64, usize>,
44    pub(super) current: Vec<u64>,
45    pub(super) locals: Vec<LocalValue>,
46    pub(super) local_index: BTreeMap<u64, usize>,
47    pub(super) local_name_index: BTreeMap<String, Vec<u64>>,
48    pub(super) local_function_index: BTreeMap<&'static str, Vec<u64>>,
49    pub(super) edges: Vec<DataEdge>,
50    pub(super) outgoing_index: BTreeMap<u64, Vec<usize>>,
51    pub(super) incoming_index: BTreeMap<u64, Vec<usize>>,
52    pub(super) next_local_id: u64,
53    pub(super) next_frame_id: u64,
54    pub(super) error_scope_depth: usize,
55}
56
57#[derive(Clone, Copy, Debug)]
58pub(super) struct TraceMark {
59    frames: usize,
60    locals: usize,
61    edges: usize,
62}
63
64impl Default for CallTrace {
65    fn default() -> Self {
66        Self::runtime()
67    }
68}
69
70#[derive(Clone, Debug)]
71pub(super) struct FrameRecord {
72    pub(super) call: CallSite,
73    pub(super) parent: Option<u64>,
74}
75
76/// A borrowed frame and its parent link, without materializing a path.
77/// 借用的调用帧及其父链接,不生成路径数组。
78#[derive(Clone, Copy, Debug)]
79pub struct FrameView<'a> {
80    /// The recorded call site.
81    /// 记录下来的调用点。
82    pub call: &'a CallSite,
83    /// The enclosing frame id, `None` for a root frame.
84    /// 外层调用帧 id;根帧为 `None`。
85    pub parent: Option<u64>,
86}
87
88impl CallTrace {
89    /// Creates a collector using the application default policy.
90    /// 使用应用默认策略创建收集器。
91    ///
92    /// Debug builds default to `ErrorsOnly`; release builds default to `Off`.
93    /// 调试构建默认使用 `ErrorsOnly`,发布构建默认使用 `Off`。
94    pub fn new() -> Self {
95        Self::runtime()
96    }
97
98    /// Creates a collector using the application default policy.
99    /// 使用应用默认策略创建收集器。
100    pub fn runtime() -> Self {
101        Self::with_mode(trace_mode_from_env())
102    }
103
104    /// Creates a disabled collector with no-op recording methods.
105    /// 创建关闭收集器,所有记录方法都是 no-op。
106    pub fn disabled() -> Self {
107        Self::with_mode(TraceMode::Off)
108    }
109
110    /// Creates an errors-only collector.
111    /// 创建只保留失败证据的收集器。
112    pub fn errors_only() -> Self {
113        Self::with_mode(TraceMode::ErrorsOnly)
114    }
115
116    /// Creates a full collector.
117    /// 创建完整收集器。
118    pub fn full() -> Self {
119        Self::with_mode(TraceMode::Full)
120    }
121
122    /// Create an empty collector with an explicit collection policy.
123    /// 以显式收集策略创建一个不含证据的收集器。
124    pub fn with_mode(mode: TraceMode) -> Self {
125        Self {
126            mode,
127            frames: Vec::new(),
128            frame_index: BTreeMap::new(),
129            current: Vec::new(),
130            locals: Vec::new(),
131            local_index: BTreeMap::new(),
132            local_name_index: BTreeMap::new(),
133            local_function_index: BTreeMap::new(),
134            edges: Vec::new(),
135            outgoing_index: BTreeMap::new(),
136            incoming_index: BTreeMap::new(),
137            next_local_id: 0,
138            next_frame_id: 0,
139            error_scope_depth: 0,
140        }
141    }
142
143    /// The collection policy currently in force.
144    /// 当前生效的收集策略。
145    pub const fn mode(&self) -> TraceMode {
146        self.mode
147    }
148
149    /// Changes the policy and clears evidence that no longer matches it.
150    /// 修改策略,并清理不再符合新策略的证据。
151    pub fn set_mode(&mut self, mode: TraceMode) {
152        self.mode = mode;
153        self.clear();
154    }
155
156    /// Whether the current policy records anything, i.e. `mode` is not `Off`.
157    /// 当前策略是否记录任何内容,即 `mode` 不为 `Off`。
158    pub const fn is_collecting(&self) -> bool {
159        !matches!(self.mode, TraceMode::Off)
160    }
161
162    /// Removes all collected evidence without changing the policy.
163    /// 清除全部已收集证据,但不修改策略。
164    pub fn clear(&mut self) {
165        self.frames.clear();
166        self.frame_index.clear();
167        self.current.clear();
168        self.locals.clear();
169        self.local_index.clear();
170        self.local_name_index.clear();
171        self.local_function_index.clear();
172        self.edges.clear();
173        self.outgoing_index.clear();
174        self.incoming_index.clear();
175        // The id counters deliberately keep their values: clearing evidence must
176        // not make an already-handed-out id describe new evidence.
177        // id 计数器刻意保留取值:清除证据不应让已经发放出去的 id 描述新的证据。
178        //
179        // `error_scope_depth` counts the enclosing `with_result` scopes, which
180        // clearing evidence does not close. Zeroing it here made the matching
181        // decrement underflow, so a mode reset inside a scope panicked in debug
182        // and, in release, wrapped to `usize::MAX` — after which every later
183        // scope looked nested and the ErrorsOnly rollback silently stopped
184        // happening.
185        // `error_scope_depth` 记录着外层 `with_result` 作用域的数量,清除证据并不会
186        // 关闭它们。在这里清零会让配对的减法下溢:debug 下 panic,release 下回绕成
187        // `usize::MAX`,此后每个作用域都被当成嵌套,ErrorsOnly 的回滚静默失效。
188    }
189
190    pub(super) fn mark(&self) -> TraceMark {
191        TraceMark {
192            frames: self.frames.len(),
193            locals: self.locals.len(),
194            edges: self.edges.len(),
195        }
196    }
197
198    /// Discard the evidence a scope collected, without handing its ids out again.
199    /// 丢弃某个作用域收集的证据,但不把它的 id 再发一次。
200    ///
201    /// Rewinding the counters made an id that escaped the scope (`with_result`
202    /// returns one on success, then rolls the scope back) point at whatever
203    /// evidence took that number next — a silent alias. Ids are minted once per
204    /// trace and are never reused, so an escaped id simply resolves to nothing.
205    /// 回绕计数器会让逃出作用域的 id(`with_result` 成功时会返回它,随后回滚该作用域)
206    /// 指向下一个占用该编号的证据——一种静默别名。id 在一条 trace 里只发放一次、永不
207    /// 复用,因此逃出的 id 只会解析不到任何东西。
208    pub(super) fn rollback(&mut self, mark: TraceMark) {
209        self.frames.truncate(mark.frames);
210        self.locals.truncate(mark.locals);
211        self.edges.truncate(mark.edges);
212        self.rebuild_indexes();
213    }
214
215    pub(super) fn rebuild_indexes(&mut self) {
216        self.frame_index.clear();
217        for (index, frame) in self.frames.iter().enumerate() {
218            self.frame_index.insert(frame.call.frame_id, index);
219        }
220        self.local_index.clear();
221        self.local_name_index.clear();
222        self.local_function_index.clear();
223        for (index, local) in self.locals.iter().enumerate() {
224            self.local_index.insert(local.id, index);
225            self.local_name_index
226                .entry(local.name.clone())
227                .or_default()
228                .push(local.id);
229            if let Some(frame_id) = local.frame_id
230                && let Some(function) = self.frame(frame_id).map(|frame| frame.call.function)
231            {
232                self.local_function_index
233                    .entry(function)
234                    .or_default()
235                    .push(local.id);
236            }
237        }
238        self.outgoing_index.clear();
239        self.incoming_index.clear();
240        for (index, edge) in self.edges.iter().enumerate() {
241            self.outgoing_index
242                .entry(edge.from)
243                .or_default()
244                .push(index);
245            self.incoming_index.entry(edge.to).or_default().push(index);
246        }
247    }
248
249    /// Runs a fallible operation and commits evidence only when it fails.
250    /// 执行一个可失败操作,只有失败时才提交追踪证据。
251    ///
252    /// Nested scopes share the outer transaction. This means a successful
253    /// inner call remains available when its parent later returns `Err`.
254    /// 嵌套作用域共享外层事务,因此内部成功但父调用最终失败时,内部证据仍会保留。
255    pub fn with_result<T, E>(
256        &mut self,
257        operation: impl FnOnce(&mut Self) -> Result<T, E>,
258    ) -> Result<T, E> {
259        if !matches!(self.mode, TraceMode::ErrorsOnly) {
260            return operation(self);
261        }
262        let outer = self.error_scope_depth == 0;
263        let mark = outer.then(|| self.mark());
264        self.error_scope_depth += 1;
265        let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| operation(self)));
266        // Saturating rather than plain subtraction: the depth is bookkeeping for
267        // this pairing, and an inconsistent value must not turn a trace into a
268        // panic or a wrapped counter.
269        // 用饱和减法而不是直接相减:深度只是这次配对的记账,取值异常不该让 trace
270        // panic 或把计数器回绕。
271        self.error_scope_depth = self.error_scope_depth.saturating_sub(1);
272        match result {
273            Ok(Ok(value)) => {
274                if let Some(mark) = mark {
275                    self.rollback(mark);
276                }
277                Ok(value)
278            }
279            Ok(Err(error)) => Err(error),
280            Err(payload) => std::panic::resume_unwind(payload),
281        }
282    }
283
284    pub(super) fn callsite_source(
285        &self,
286        file: &'static str,
287        line: u32,
288        column: u32,
289    ) -> SourceLocation {
290        SourceLocation {
291            file,
292            line,
293            column,
294            function: self
295                .current
296                .last()
297                .and_then(|id| self.frame(*id))
298                .map(|frame| frame.call.function)
299                .unwrap_or("<runtime>"),
300        }
301    }
302
303    /// Distinct caller/callee frame pairs, in first-seen order.
304    /// 去重后的调用者/被调用者帧对,按首次出现顺序排列。
305    pub fn call_edges(&self) -> Vec<CallEdge> {
306        let mut result = Vec::new();
307        let mut seen = std::collections::BTreeSet::new();
308        for frame in &self.frames {
309            let Some(parent) = frame.parent.and_then(|id| self.frame(id)) else {
310                continue;
311            };
312            let edge = CallEdge {
313                caller: parent.call.clone(),
314                callee: frame.call.clone(),
315            };
316            if seen.insert((edge.caller.frame_id, edge.callee.frame_id)) {
317                result.push(edge);
318            }
319        }
320        result
321    }
322
323    /// Distinct caller/callee function pairs, all labeled live evidence.
324    /// 去重后的调用者/被调用者函数对,证据类型均标为 live。
325    ///
326    /// Frame identity is dropped on purpose: two calls to the same functions
327    /// from different frames collapse into one logical edge.
328    /// 这里刻意丢掉帧身份:不同帧中对同一对函数的两次调用会合并成一条逻辑边。
329    pub fn logical_call_edges(&self) -> Vec<LogicalCallEdge> {
330        let mut seen = std::collections::BTreeSet::new();
331        self.call_edges()
332            .into_iter()
333            .filter_map(|edge| {
334                let key = (
335                    edge.caller.node,
336                    edge.caller.function,
337                    edge.callee.node,
338                    edge.callee.function,
339                );
340                seen.insert(key).then_some(LogicalCallEdge {
341                    caller: edge.caller,
342                    callee: edge.callee,
343                    evidence: EvidenceKind::Live,
344                })
345            })
346            .collect()
347    }
348}