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;