Skip to main content

nichlink_run_method/runtime/trace/artifact/
artifact.rs

1//! The versioned `nichlink.trace` artifact: a recorded `CallTrace` as text.
2//! 带版本的 `nichlink.trace` artifact:以文本保存的已记录 `CallTrace`。
3//!
4//! The document is line-oriented `key=value`, exactly like `graft.plan`: unknown
5//! keys and other versions are refused rather than guessed. Record fields are
6//! tab-separated and backslash-escaped, because a local value and an edge label
7//! are arbitrary text. The module is a child of `trace` so it can read the
8//! collector's `pub(in trace)` fields without widening any public field. The
9//! parsing and file halves live in sibling files only to keep every file under
10//! the repository's size ratchet.
11//! 本文档与 `graft.plan` 一样是按行的 `key=value`:未知键与其他版本一律拒绝,而不是猜。
12//! 记录字段以制表符分隔并做反斜杠转义,因为局部值与边标签是任意文本。本模块是 `trace` 的
13//! 子模块,因此能读取收集器的 `pub(in trace)` 字段而不放宽任何公开字段。解析与文件两半放在
14//! 同级文件里,只是为了把每个文件保持在仓库尺寸棘轮之下。
15
16use std::fmt;
17
18use crate::registry_core::declaration::SourceLocation;
19use crate::registry_core::identity::{NodeId, root_node_id};
20use crate::registry_core::lexicon::DEFAULT_NAMESPACE;
21
22use super::call_trace::FrameRecord;
23use super::{CallSite, CallTrace, DataEdge, LocalValue, TraceMode};
24
25#[path = "io.rs"]
26mod io;
27#[path = "parse.rs"]
28mod parse;
29
30pub use self::io::*;
31
32/// The only `nichlink.trace` layout this build understands.
33/// 本版本唯一能读懂的 `nichlink.trace` 版式。
34pub const TRACE_ARTIFACT_VERSION: u32 = 1;
35
36/// One recorded frame, flattened for text.
37/// 一个已记录调用帧的文本平铺形式。
38///
39/// `function` and `source` are interned while a document is parsed, so repeated
40/// frames share one string per distinct name instead of leaking one per record.
41/// `function` 与 `source` 在解析文档时被驻留,因此重复帧对每个不同名字只共享一个字符串,
42/// 而不是每条记录泄漏一个。
43#[derive(Clone, Debug, PartialEq, Eq)]
44pub struct TraceFrame {
45    /// The trace-local frame id.
46    /// 追踪内的帧 id。
47    pub frame_id: u64,
48    /// The enclosing frame id, `None` for a root frame.
49    /// 外层调用帧 id;根帧为 `None`。
50    pub parent: Option<u64>,
51    /// The registry node the frame belongs to.
52    /// 该帧所属的注册机节点。
53    pub node: NodeId,
54    /// The logical function active in the frame.
55    /// 该帧中处于活动状态的逻辑函数名。
56    pub function: &'static str,
57    /// The callsite that entered the frame, when one is known.
58    /// 进入该帧的调用点(若可知)。
59    pub source: Option<SourceLocation>,
60}
61
62/// A recorded `CallTrace` as the versioned `nichlink.trace` document.
63/// 已记录 `CallTrace` 的带版本 `nichlink.trace` 文档。
64#[derive(Clone, Debug, PartialEq, Eq)]
65pub struct TraceArtifact {
66    /// Layout version; must equal `TRACE_ARTIFACT_VERSION`.
67    /// 版式版本;必须等于 `TRACE_ARTIFACT_VERSION`。
68    pub version: u32,
69    /// Host package namespace the artifact was recorded under.
70    /// 记录该 artifact 时宿主包的命名空间。
71    pub namespace: String,
72    /// Registry root identity the recorded nodes were minted under.
73    /// 铸造这些已记录节点时所用的注册机根身份。
74    pub root: NodeId,
75    /// The collection policy in force while recording.
76    /// 记录期间生效的收集策略。
77    pub mode: TraceMode,
78    /// Recorded frames, in recording order.
79    /// 已记录的调用帧,按记录顺序。
80    pub frames: Vec<TraceFrame>,
81    /// Recorded locals, in recording order.
82    /// 已记录的局部值,按记录顺序。
83    pub locals: Vec<LocalValue>,
84    /// Recorded value edges, in recording order.
85    /// 已记录的值边,按记录顺序。
86    pub edges: Vec<DataEdge>,
87}
88
89/// Why a `nichlink.trace` document was refused.
90/// `nichlink.trace` 文档被拒绝的原因。
91#[derive(Clone, Debug, PartialEq, Eq)]
92pub enum TraceArtifactError {
93    /// Document declares a layout this build cannot read.
94    /// 文档声明的版式是本构建读不懂的。
95    UnsupportedVersion(u32),
96    /// Document omitted a required scalar key.
97    /// 文档缺少一个必需的标量键。
98    MissingKey(&'static str),
99    /// Document carried a key this format does not define.
100    /// 文档带有本格式未定义的键。
101    UnknownKey(String),
102    /// Document had a line, field count, or escape the format cannot read.
103    /// 文档中存在本格式读不懂的行、字段数或转义。
104    Malformed {
105        /// 1-based line the failure was found on.
106        /// 发现失败的行号,从 1 起。
107        line: usize,
108        /// What was wrong with that line.
109        /// 该行错在哪里。
110        message: String,
111    },
112    /// Two frames carry the same id.
113    /// 两个调用帧使用了同一个 id。
114    DuplicateFrame(u64),
115    /// Two locals carry the same id.
116    /// 两个局部值使用了同一个 id。
117    DuplicateLocal(u64),
118    /// A frame names a parent frame the document does not contain.
119    /// 某个调用帧指名的父帧不在文档中。
120    MissingParent(u64),
121    /// An edge endpoint has no local in the document.
122    /// 某条边的端点没有对应的局部值。
123    DanglingEdge {
124        /// The local id the edge leaves.
125        /// 该边离开的局部值 id。
126        from: u64,
127        /// The local id the edge enters.
128        /// 该边进入的局部值 id。
129        to: u64,
130    },
131}
132
133impl fmt::Display for TraceArtifactError {
134    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
135        match self {
136            Self::UnsupportedVersion(version) => write!(
137                formatter,
138                "trace artifact version `{version}` is not supported (expected {TRACE_ARTIFACT_VERSION})"
139            ),
140            Self::MissingKey(key) => write!(formatter, "trace artifact is missing `{key}`"),
141            Self::UnknownKey(key) => write!(formatter, "trace artifact has an unknown key `{key}`"),
142            Self::Malformed { line, message } => {
143                write!(formatter, "trace artifact line {line}: {message}")
144            }
145            Self::DuplicateFrame(id) => write!(formatter, "trace artifact repeats frame id `{id}`"),
146            Self::DuplicateLocal(id) => write!(formatter, "trace artifact repeats local id `{id}`"),
147            Self::MissingParent(id) => {
148                write!(
149                    formatter,
150                    "trace artifact frame parent `{id}` is not recorded"
151                )
152            }
153            Self::DanglingEdge { from, to } => {
154                write!(
155                    formatter,
156                    "trace artifact edge `{from} -> {to}` has no local"
157                )
158            }
159        }
160    }
161}
162
163impl std::error::Error for TraceArtifactError {}
164
165impl TraceArtifact {
166    /// Flatten a recorded trace into the artifact document.
167    /// 把已记录的追踪平铺成 artifact 文档。
168    ///
169    /// The namespace and root anchors default to `DEFAULT_NAMESPACE`; the file
170    /// writer re-stamps them from the process environment, because only the host
171    /// that owns the package knows its name.
172    /// namespace 与 root 锚点默认取 `DEFAULT_NAMESPACE`;文件写入方会用进程环境重新盖戳,
173    /// 因为只有拥有该包的宿主才知道它的名字。
174    pub fn from_trace(trace: &CallTrace) -> Self {
175        Self {
176            version: TRACE_ARTIFACT_VERSION,
177            namespace: DEFAULT_NAMESPACE.to_owned(),
178            root: root_node_id(DEFAULT_NAMESPACE),
179            mode: trace.mode,
180            frames: trace
181                .frames
182                .iter()
183                .map(|frame| TraceFrame {
184                    frame_id: frame.call.frame_id,
185                    parent: frame.parent,
186                    node: frame.call.node,
187                    function: frame.call.function,
188                    source: frame.call.source,
189                })
190                .collect(),
191            locals: trace.locals.clone(),
192            edges: trace.edges.clone(),
193        }
194    }
195
196    /// Render the canonical document, one `key=value` line per record.
197    /// 渲染规范文档,每条记录一行 `key=value`。
198    pub fn render(&self) -> String {
199        let mut output = format!(
200            "version={}\nnamespace={}\nroot={}\nmode={}\n",
201            self.version,
202            escape(&self.namespace),
203            self.root,
204            mode_spelling(self.mode),
205        );
206        for frame in &self.frames {
207            let parent = frame
208                .parent
209                .map_or_else(|| "-".to_owned(), |id| id.to_string());
210            let (file, line, column) = source_fields(frame.source);
211            output.push_str(&format!(
212                "frame={}\t{}\t{}\t{}\t{}\t{}\t{}\n",
213                frame.frame_id,
214                parent,
215                frame.node,
216                escape(frame.function),
217                file,
218                line,
219                column
220            ));
221        }
222        for local in &self.locals {
223            let frame = local
224                .frame_id
225                .map_or_else(|| "-".to_owned(), |id| id.to_string());
226            output.push_str(&format!(
227                "local={}\t{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}\n",
228                local.id,
229                frame,
230                local.kind.label(),
231                local.observation.label(),
232                escape(&local.name),
233                escape(&local.type_name),
234                escape(&local.value),
235                escape(local.source.file),
236                local.source.line,
237                local.source.column
238            ));
239        }
240        for edge in &self.edges {
241            let (file, line, column) = source_fields(edge.source);
242            output.push_str(&format!(
243                "edge={}\t{}\t{}\t{}\t{}\t{}\n",
244                edge.from,
245                edge.to,
246                escape(&edge.label),
247                file,
248                line,
249                column
250            ));
251        }
252        output
253    }
254
255    /// Rebuild the trace these flat arenas describe, indexes included.
256    /// 重建这些平铺 arena 所描述的追踪,含全部索引。
257    ///
258    /// Every reference is checked first: duplicate ids, a frame whose parent is
259    /// absent, and an edge whose endpoint has no local are refused rather than
260    /// silently producing a trace that resolves to nothing.
261    /// 先检查每一条引用:重复 id、父帧缺失的帧、端点没有局部值的边都会被拒绝,而不是静默产出
262    /// 一条什么都解析不到的追踪。
263    pub fn into_trace(self) -> Result<CallTrace, TraceArtifactError> {
264        let mut frame_ids = std::collections::BTreeSet::new();
265        for frame in &self.frames {
266            if !frame_ids.insert(frame.frame_id) {
267                return Err(TraceArtifactError::DuplicateFrame(frame.frame_id));
268            }
269        }
270        for frame in &self.frames {
271            if let Some(parent) = frame.parent
272                && !frame_ids.contains(&parent)
273            {
274                return Err(TraceArtifactError::MissingParent(parent));
275            }
276        }
277        let mut local_ids = std::collections::BTreeSet::new();
278        for local in &self.locals {
279            if !local_ids.insert(local.id) {
280                return Err(TraceArtifactError::DuplicateLocal(local.id));
281            }
282        }
283        for edge in &self.edges {
284            if !local_ids.contains(&edge.from) || !local_ids.contains(&edge.to) {
285                return Err(TraceArtifactError::DanglingEdge {
286                    from: edge.from,
287                    to: edge.to,
288                });
289            }
290        }
291        let next_frame_id = self
292            .frames
293            .iter()
294            .map(|frame| frame.frame_id)
295            .max()
296            .map_or(0, |id| id.wrapping_add(1));
297        let next_local_id = self
298            .locals
299            .iter()
300            .map(|local| local.id)
301            .max()
302            .map_or(0, |id| id.wrapping_add(1));
303        let frames: Vec<FrameRecord> = self
304            .frames
305            .iter()
306            .map(|frame| FrameRecord {
307                call: CallSite {
308                    node: frame.node,
309                    function: frame.function,
310                    frame_id: frame.frame_id,
311                    source: frame.source,
312                },
313                parent: frame.parent,
314            })
315            .collect();
316        let mut trace = CallTrace::with_mode(self.mode);
317        trace.frames = frames;
318        trace.locals = self.locals;
319        trace.edges = self.edges;
320        trace.next_frame_id = next_frame_id;
321        trace.next_local_id = next_local_id;
322        trace.rebuild_indexes();
323        Ok(trace)
324    }
325}
326
327/// The canonical spelling of a collection policy in the document.
328/// 收集策略在文档中的规范拼写。
329fn mode_spelling(mode: TraceMode) -> &'static str {
330    match mode {
331        TraceMode::Off => "off",
332        TraceMode::ErrorsOnly => "errors-only",
333        TraceMode::Full => "full",
334    }
335}
336
337/// The `(file, line, column)` fields of an optional source location.
338/// 可选源码位置的 `(file, line, column)` 字段。
339fn source_fields(source: Option<SourceLocation>) -> (String, String, String) {
340    match source {
341        Some(source) => (
342            escape(source.file),
343            source.line.to_string(),
344            source.column.to_string(),
345        ),
346        None => ("-".to_owned(), "-".to_owned(), "-".to_owned()),
347    }
348}
349
350/// Escape a value so it stays on one tab-separated record.
351/// 转义一个取值,使它留在同一条制表符分隔的记录里。
352fn escape(value: &str) -> String {
353    let mut output = String::with_capacity(value.len());
354    for character in value.chars() {
355        match character {
356            '\\' => output.push_str("\\\\"),
357            '\t' => output.push_str("\\t"),
358            '\n' => output.push_str("\\n"),
359            '\r' => output.push_str("\\r"),
360            other => output.push(other),
361        }
362    }
363    output
364}
365
366#[cfg(test)]
367#[path = "artifact_tests.rs"]
368mod artifact_tests;