///|
/// 折叠栈(folded stack)格式的解析。
///
/// 输入格式为每行一条样本:
///
/// <帧1>;<帧2>;<帧3>;<...> <权重>
///
/// 约定与实现要点:
///
/// - 帧之间用分号分隔,**最后一个空格**之后是权重,因此帧名内部允许含空格;
/// - 权重按浮点解析:上游 moon-pprof 输出的是**纳秒时间**,不是整数采样计数;
/// - 空帧名统一兜底为 `(root)`——真实数据里出现过 `:ackermann;...` 这类以空帧
/// 开头的栈;
/// - 空白行直接忽略,非空但无法解析的行计入 `skipped_lines`。
///|
/// 一个采样样本:一条调用栈 + 权重。
///
/// `frames` 自顶向下排列,`frames[0]` 为根帧。
pub struct Sample {
frames : Array[String]
weight : Double
} derive(Eq)
///|
/// 显式声明 `Eq` 的方法可被当作常规方法调用。
///
/// 新版编译器不再自动完成这个提升,不声明会报 `implicit_impl_as_method`。
pub extend Sample with Eq::{not_equal, equal}
///|
/// 解析结果。`skipped_lines` 记录非空但无法解析的行数,便于调用方提示用户。
pub struct ParseResult {
samples : Array[Sample]
skipped_lines : Int
diagnostics : Array[ParseIssue]
} derive(Eq)
///|
pub(all) enum ParseIssueKind {
MissingWeight
InvalidNumber
NonFiniteWeight
NegativeWeight
} derive(Eq)
///|
pub extend ParseIssueKind with Eq::{not_equal, equal}
///|
pub struct ParseIssue {
line : Int
kind : ParseIssueKind
} derive(Eq)
///|
pub extend ParseIssue with Eq::{not_equal, equal}
///|
/// 显式声明 `Eq` 的方法可被当作常规方法调用。
///
/// 新版编译器不再自动完成这个提升,不声明会报 `implicit_impl_as_method`。
pub extend ParseResult with Eq::{not_equal, equal}
///|
/// 解析权重。无法解析、负数或非有限值(NaN / Inf)时返回 `None`——
/// 这类值会让后续布局的比例计算整体失效,必须在入口拦掉。
fn parse_weight(view : StringView) -> Double? {
let parsed : Result[Double, Error] = Ok(@string.parse_double(view)) catch {
error => Err(error)
}
match parsed {
Ok(value) =>
if value.is_nan() || value.is_inf() || value < 0.0 {
None
} else {
Some(value)
}
Err(_) => None
}
}
///|
/// 解析一行折叠栈。
///
/// 返回 `None` 表示该行不是有效样本,由调用方决定是忽略还是计入跳过数。
pub fn parse_line(line : StringView) -> Sample? {
let trimmed = line.trim()
if trimmed.is_empty() {
return None
}
// 最后一个空格分隔「栈」与「权重」
guard trimmed.rev_find(" ") is Some(sep) else { return None }
let stack_part = trimmed.view(end_offset=sep)
let weight_part = trimmed.view(start_offset=sep + 1)
guard parse_weight(weight_part) is Some(weight) else { return None }
let frames : Array[String] = []
for raw in stack_part.split(";") {
let name = raw.trim()
frames.push(if name.is_empty() { "(root)" } else { name.to_owned() })
}
guard frames.length() > 0 else { return None }
Some({ frames, weight, })
}
///|
/// 解析整个折叠栈文本。
///
/// 空白行被直接忽略;非空但无法解析的行计入 `skipped_lines`。
pub fn parse_folded(text : String) -> ParseResult {
let samples : Array[Sample] = []
let mut skipped = 0
let diagnostics : Array[ParseIssue] = []
let mut line_number = 0
for line in text.split("\n") {
line_number = line_number + 1
match parse_line(line) {
Some(sample) => samples.push(sample)
None =>
if !line.is_blank() {
skipped = skipped + 1
let trimmed = line.trim()
let kind = match trimmed.rev_find(" ") {
None => MissingWeight
Some(sep) => {
let result : Result[Double, Error] = Ok(
@string.parse_double(trimmed.view(start_offset=sep + 1)),
) catch {
error => Err(error)
}
match result {
Err(_) => InvalidNumber
Ok(value) =>
if !finite(value) {
NonFiniteWeight
} else {
NegativeWeight
}
}
}
}
diagnostics.push({ line: line_number, kind, })
}
}
}
{ samples, skipped_lines: skipped, diagnostics, }
}