Sign in

    moonflame

    Render folded stack profiles into interactive SVG flame graphs.

    flamegraph
    profiling
    visualization
    svg
    Download zip
    Author
    Version
    0.2.0
    License
    MIT
    Last updated
    6 hours ago
    Downloads
    12

    Dependencies

    #MoonFlame

    用 MoonBit 将折叠栈性能数据转换成可交互的 SVG 火焰图。

    生成的 SVG 是一个自包含文件,可以在浏览器离线打开、归档和分享。 MoonFlame 提供可复用的纯计算库及命令行工具;采样由上游工具完成。

    示例火焰图

    下载 SVG 示例 并用浏览器打开,可体验悬停、缩放与搜索。 GitHub README 的图片引用只显示静态图,不会执行 SVG 内的脚本。

    #环境与快速验证

    当前版本:0.2.0。需要 moonc >= 0.10.14,首次安装后执行 moon update。 生成端需要 MoonBit 工具链;查看 SVG 只需要浏览器。核心库没有第三方依赖; 命令行使用官方 moonbitlang/x@0.4.45 读写文件、设置退出状态。

    # macOS / Linux 安装 MoonBit curl -fsSL https://cli.moonbitlang.com/install/unix.sh | bash

    # Windows 安装 MoonBit irm https://cli.moonbitlang.com/install/powershell.ps1 | iex

    moon version --all moon update moon run cmd/main -- testdata/demo.folded --out flame.svg moon run cmd/main -- hotspots testdata/demo.folded --top 10

    默认 CLI 目标为 native。Windows 干净环境的标准验证配置为 MSVC 开发者终端; CI 显式配置 MSVC。旧版 0.10.14 运行时在 MinGW 下存在 rand_s 声明问题, 不自动修改用户工具链,也不把本机运行时补丁作为干净环境证明。 本机该版本的 native CLI 对含非 ASCII 字符的 Windows 绝对路径有限制; 可在对应目录使用相对路径,或使用 JS CLI。

    核心库检查、构建和测试覆盖 wasm、wasm-gc、JS、native;CLI 流和退出码的 端到端验证覆盖 native、JS、WASI wasm。JS 用法:

    moon run --target js cmd/main -- testdata/demo.folded --out flame.svg

    moonc 0.10.14 的 wasm-gc eprintln 会回退到标准输出,因此该目标不属于 支持流重定向契约的 CLI 目标;命令行使用 native、JS 或 WASI wasm。 这不影响 wasm-gc 环境调用纯计算库。

    #输入、数据与核心功能

    折叠栈每行是一条调用路径,最后一个空格分隔权重;帧间用分号分隔:

    main;compute;fft 900 main;compute 100

    权重必须为有限非负数。空行忽略,零和负零允许但不参与聚合;负数、NaN、 Infinity、无效数值和缺少权重都属于无效行。空帧名以 (root) 显示。 --unit 声明输入权重含义:ns / us / ms / s / count,默认纳秒。 时间自动缩放,计数按原值显示;不要将不明单位的权重直接解释为耗时。

    • 聚合在同父同名的节点间进行,原始名称作为身份;显示归一化不改写输入数据。
    • 默认保留 32 个真实帧,超出部分进入 (deeper),不丢失深部权重。
    • 布局按权重比例切分;有子项的节点以 (self) 补齐自身权重,消除舍入裂缝。 渲染会省略过小矩形,因此可见矩形不承诺覆盖所有子项。
    • 火焰图根在底部,--inverted 改为根在顶部的冰柱图。
    • 同输入产生确定性输出,同名帧同色;普通图采用暖色,差异图红增蓝减白不变。

    冻结样例含义
    testdata/demo.folded本项目演示程序的真实采样:32 条合并栈、最深 13 帧、总权重约 1893 ms
    testdata/stress-recursive.folded上游样例的冻结剖面转换:最深 7254 帧,用于深度限制和不限深回归
    testdata/demo-baseline.folded人工构造的演示基线,不是另一次真实采样

    样例来源、固定提交、生成步骤及适用许可证见 第三方材料说明,SHA-256 见 testdata/SOURCES.json。

    #命令行

    moon run cmd/main -- <input> [options] moon run cmd/main -- hotspots <input> [--top N] moon run cmd/main -- callers <input> <name> moon run cmd/main -- callees <input> <name> moon run cmd/main -- diff <before> <after> [-o out.svg] moon run cmd/main -- delta <before> <after> [--top N] moon run cmd/main -- filter <pattern> <input> [-o out.folded]

    出图参数默认值约束
    -o, --out标准输出指定则写文件
    --width1400有限且大于 20 像素(左右各留白 10)
    --max-depth32非负整数;0 表示不限深
    --unitns必须为已知单位;也接受代码中定义的完整单位别名
    --inverted关闭冰柱方向

    报告命令接受深度和单位;hotspots、delta 另接受 --top,默认 10, 0 表示全部,负数无效。filter 只接受过滤子串、输入和输出参数。 参数写错会报错,不会悄悄退回默认值。

    命令行严格验证所有输入行:只要一行无效,整次失败,不生成部分结果, 也不打开或截断已有输出文件。库解析接口提供逐行诊断,可由调用方自行决定容错策略。 所有诊断和写入提示进入标准错误,标准输出只包含结果。

    退出码含义
    0成功、帮助、版本查询;过滤零匹配也是成功
    1IO、输入数据、计算或数值校验失败
    2参数错误

    过滤零匹配时输出为空;指定输出路径会以空文件替换旧结果,避免下游误用旧数据。 普通渲染和报告在没有正权重样本时失败。

    #报告与差异

    callers 按直接调用者、callees 按直接被调用者分组。查询先做显示名归一化再按 子串匹配,分组身份始终是原始完整名称。同一样本对同一条目最多贡献一次,即使自递归 或互递归也不重复累计;每条占比不超过 100%,不同条目可能重叠,合计不保证为 100%。 限深后的报告会提示 (deeper) 收纳了未展开的部分。

    moon run cmd/main -- callers testdata/demo.folded walk_tree moon run cmd/main -- callees testdata/demo.folded build_strings moon run cmd/main -- filter build_strings testdata/demo.folded -o sub.folded moon run cmd/main -- sub.folded --out sub.svg moon run cmd/main -- diff testdata/demo-baseline.folded testdata/demo.folded -o diff.svg moon run cmd/main -- delta testdata/demo-baseline.folded testdata/demo.folded

    差异图宽度按当前(第二份)剖面,节点颜色表示累计权重变化;(self) 表示 自身权重变化,独立计算,不会把消失子调用的变化错误归到父函数自身。 仅基线存在的帧不显示,可交换输入生成另一方向,或用 delta 查看消失函数。 文本 delta 按函数自身权重变化排序,显示所有只在一侧存在的函数。

    演示基线规则:含 bubble__sort 的行权重乘 3,含 build__strings 的行乘 0.8, 向下取整,其余不变。不要把该构造基线作为真实性能改善证据。

    差异火焰图

    #SVG 交互

    操作行为
    悬停帧名、权重、全图占比和变化量
    单击缩放到该子树;祖先半透明,点击祖先可扩大视图
    Reset Zoom恢复原始几何和显示状态
    Ctrl-F / Search正则搜索;再次点击重置
    Ctrl-I / ic切换是否忽略大小写

    搜索占比相对当前视图,命中区间取并集,排除隐藏帧和仅作上下文的祖先。 无命中显示 0%,无效正则明确提示。窄帧放大后会出现可容纳的标签。 内嵌脚本需直接打开 SVG 或内联嵌入;<img> 引用只显示静态图。

    #作为库使用

    注册表模块为 gywcs101/moonflame;0.2.0 为本次整改准备的版本,正式发布状态以 mooncakes 模块页为准。 发布后可执行 moon add gywcs101/moonflame。

    在调用项目的 moon.pkg 中添加包导入:

    import { "gywcs101/moonflame", }

    在源码中使用以下完整链路。此段由独立消费项目实际编译和执行:

    ///|
    pub fn folded_to_svg(text : String) -> String raise {
    let parsed = @moonflame.parse_folded(text)
    if parsed.skipped_lines > 0 { fail("invalid profile") }
    let tree = @moonflame.aggregate(parsed.samples, max_depth=32)
    let layout = @moonflame.layout(tree, 1380.0, @moonflame.FRAME_HEIGHT)
    @moonflame.render_svg(layout)
    }

    ParseResult.diagnostics 包含一基行号和错误类别。核心入口可能抛出可捕获的 ValidationError(权重、树结构或几何错误),可向上传播或分别捕获。 库允许空树产生空 SVG;CLI 为避免误解会拒绝没有正权重的剖面。

    API参数含义
    parse_folded(text)有效样本及逐行诊断
    aggregate(samples, max_depth=...)非负样本聚合
    layout(tree, width, row_height)内容宽度,不含渲染留白
    render_svg(layout)生成完整 SVG
    top_hotspots(tree, top)按自身权重聚合热点
    find_callers(tree, name) / find_callees(tree, name)调用关系覆盖统计
    diff_against(current, baseline)当前树的累计与自身变化
    compute_delta(before, after, top)函数自身权重差异
    filter_folded(text, pattern)保留匹配栈的原始权重

    完整签名见 pkg.generated.mbti。版本迁移与行为调整见 MIGRATION.md。 Double 权重的整数精度受 IEEE-754 限制,大于 2^53 的整数不保证精确表示。

    #采样自己的程序

    安装可选采样器 mizchi/moon-pprof, 采样端需要它的运行环境;渲染器不依赖采样器。以下为用 0.1.3 验证的 wasm-gc 链路:

    moon build --target wasm-gc moon-pprof profile --wasm-gc _build/wasm-gc/debug/build/cmd/main/main.wasm --iterations 3 --out app.pb.gz moon-pprof pprof2folded app.pb.gz app.folded moon run cmd/main -- app.folded --out flame.svg

    项目内演示负载使用 powershell demo/reproduce.ps1 或 bash demo/reproduce.sh。 工具需位于 PATH。新的采样结果会变化,冻结测试数据不应自动覆盖。 0.1.3 历史实验中,调用密集与循环密集负载的采样密度有明显差异;该观察受采样器、 负载与环境影响,不能据此断言所有短函数都采不到或延长运行永远无益。 其他后端采样能力以所用上游版本的文档为准。

    #开发与复验

    moon check --target all --deny-warn moon build --target all --deny-warn moon test --target all --deny-warn moon fmt --check moon info python scripts/test-cli.py python scripts/test-cli.py --target js python scripts/test-cli.py --target wasm python scripts/test-consumer.py python scripts/verify-fixtures.py npm ci --prefix tests/browser --ignore-scripts node tests/browser/node_modules/playwright/cli.js install chromium node tests/browser/test.cjs moon package python scripts/verify-package.py

    demo/ 是独立模块,在该目录另行运行 moon check --deny-warn 和 moon test --deny-warn。 CI 保留三平台四后端检查,并增加真实进程、浏览器交互、外部消费和分发材料检查。 浏览器测试使用 Node 20+ 和锁定的 Playwright 开发依赖,不进入核心库运行依赖。 可通过 PLAYWRIGHT_CHROMIUM_EXECUTABLE 指定已有 Chromium。

    核心文件按解析、聚合、校验、布局、渲染、报告和命令行分层;cmd/main 是 IO 入口。 许可证说明、复现脚本和测试脚本随仓库提供。整改对照和验证边界见 REMEDIATION.md。

    #参考与许可

    原创部分采用 MIT。外观、折叠栈与交互语义参考 FlameGraph(CDDL-1.0),未复制其源码。 上游样例及相关衍生数据随附 Apache-2.0 完整许可证。 详见 THIRD_PARTY_NOTICES.md。

    ValidationError

    pub(all) suberror ValidationError {
    InvalidWeight(String)
    InvalidTree(String)
    InvalidGeometry(String)
    }

    Invalid profiles and geometry are recoverable errors, including direct library input.

    CallTree

    pub struct CallTree {
    roots : Array[Node]
    total : Double
    } derive(Eq)

    聚合结果。

    CallTree::equal

    fn CallTree::equal(CallTree, CallTree) -> Bool

    显式声明 Eq 的方法可被当作常规方法调用。

    新版编译器不再自动完成这个提升,不声明会报 implicit_impl_as_method。

    CallTree::max_delta

    fn CallTree::max_delta(self : CallTree) -> Double

    树中最大的变化量绝对值,用于把差异配色归一化到同一量级。

    不做归一化的话,只要有一处剧变,其它所有变化在颜色上就全糊成一片。

    CallTree::not_equal

    fn CallTree::not_equal(x : CallTree, y : CallTree) -> Bool

    显式声明 Eq 的方法可被当作常规方法调用。

    新版编译器不再自动完成这个提升,不声明会报 implicit_impl_as_method。

    DeltaEntry

    pub struct DeltaEntry {
    name : String
    before : Double
    after : Double
    change : Double
    ratio : Double
    } derive(Eq)

    一个函数在两份剖面之间的自身耗时变化。

    DeltaEntry::equal

    fn DeltaEntry::equal(DeltaEntry, DeltaEntry) -> Bool

    显式声明 Eq 的方法可被当作常规方法调用。

    新版编译器不再自动完成这个提升,不声明会报 implicit_impl_as_method。

    DeltaEntry::not_equal

    fn DeltaEntry::not_equal(x : DeltaEntry, y : DeltaEntry) -> Bool

    显式声明 Eq 的方法可被当作常规方法调用。

    新版编译器不再自动完成这个提升,不声明会报 implicit_impl_as_method。

    Hotspot

    pub struct Hotspot {
    name : String
    self_time : Double
    ratio : Double
    occurrences : Int
    } derive(Eq)

    一个热点条目:按函数名聚合后的自身耗时。

    Hotspot::equal

    fn Hotspot::equal(Hotspot, Hotspot) -> Bool

    显式声明 Eq 的方法可被当作常规方法调用。

    新版编译器不再自动完成这个提升,不声明会报 implicit_impl_as_method。

    Hotspot::not_equal

    fn Hotspot::not_equal(x : Hotspot, y : Hotspot) -> Bool

    显式声明 Eq 的方法可被当作常规方法调用。

    新版编译器不再自动完成这个提升,不声明会报 implicit_impl_as_method。

    Layout

    pub struct Layout {
    rects : Array[Rect]
    width : Double
    row_height : Double
    max_depth : Int
    total : Double
    max_delta : Double
    inverted : Bool
    } derive(Eq)

    布局结果。渲染器拿到的布局自带完整几何信息,无需再传画布参数。

    Layout::equal

    fn Layout::equal(Layout, Layout) -> Bool

    显式声明 Eq 的方法可被当作常规方法调用。

    新版编译器不再自动完成这个提升,不声明会报 implicit_impl_as_method。

    Layout::height

    fn Layout::height(self : Layout) -> Double

    画布所需高度。

    Layout::not_equal

    fn Layout::not_equal(x : Layout, y : Layout) -> Bool

    显式声明 Eq 的方法可被当作常规方法调用。

    新版编译器不再自动完成这个提升,不声明会报 implicit_impl_as_method。

    Layout::row_count

    fn Layout::row_count(self : Layout) -> Int

    需要绘制的行数(= 最深深度 + 1)。

    Node

    pub struct Node {
    name : String
    value : Double
    children : Array[Node]
    delta : Double?
    self_delta : Double?
    } derive(Eq)

    调用树节点。children 已排序,可直接用于布局。

    Node::child

    fn Node::child(self : Node, name : String) -> Node?

    按名字查找直接子节点。

    Node::equal

    fn Node::equal(Node, Node) -> Bool

    显式声明 Eq 的两个方法可被当作常规方法调用。

    新版编译器(moonc 0.10.14 起)不再自动把 impl Eq 的方法提升为常规方法, 不声明就会报 implicit_impl_as_method 废弃警告。这条声明把该提升写明, 行为与过去一致;配合 CI 的「警告即失败」,也避免未来默认行为变更后突然失效。

    Node::not_equal

    fn Node::not_equal(x : Node, y : Node) -> Bool

    显式声明 Eq 的两个方法可被当作常规方法调用。

    新版编译器(moonc 0.10.14 起)不再自动把 impl Eq 的方法提升为常规方法, 不声明就会报 implicit_impl_as_method 废弃警告。这条声明把该提升写明, 行为与过去一致;配合 CI 的「警告即失败」,也避免未来默认行为变更后突然失效。

    Node::self_ratio

    fn Node::self_ratio(self : Node, total : Double) -> Double

    自身耗时占给定总量的比例,用于文本报告与图上的百分比标注。

    Node::self_time

    fn Node::self_time(self : Node) -> Double

    自身耗时:累计权重减去所有子节点累计权重之和。

    采样栈可能停在中间(例如 main;compute 100 表示这 100 的权重归 compute 自己),此时该节点没有对应子节点,差额即它自身消耗的时间。

    ParseIssue

    pub struct ParseIssue {
    line : Int
    kind : ParseIssueKind
    } derive(Eq)

    ParseIssue::equal

    fn ParseIssue::equal(ParseIssue, ParseIssue) -> Bool

    ParseIssue::not_equal

    fn ParseIssue::not_equal(x : ParseIssue, y : ParseIssue) -> Bool

    ParseIssueKind

    pub(all) enum ParseIssueKind {
    MissingWeight
    InvalidNumber
    NonFiniteWeight
    NegativeWeight
    } derive(Eq)

    ParseIssueKind::equal

    ParseIssueKind::not_equal

    fn ParseIssueKind::not_equal(x : ParseIssueKind, y : ParseIssueKind) -> Bool

    ParseResult

    pub struct ParseResult {
    samples : Array[Sample]
    skipped_lines : Int
    diagnostics : Array[ParseIssue]
    } derive(Eq)

    解析结果。skipped_lines 记录非空但无法解析的行数,便于调用方提示用户。

    ParseResult::equal

    fn ParseResult::equal(ParseResult, ParseResult) -> Bool

    显式声明 Eq 的方法可被当作常规方法调用。

    新版编译器不再自动完成这个提升,不声明会报 implicit_impl_as_method。

    ParseResult::not_equal

    fn ParseResult::not_equal(x : ParseResult, y : ParseResult) -> Bool

    显式声明 Eq 的方法可被当作常规方法调用。

    新版编译器不再自动完成这个提升,不声明会报 implicit_impl_as_method。

    Rect

    pub struct Rect {
    x : Double
    y : Double
    width : Double
    height : Double
    name : String
    value : Double
    depth : Int
    delta : Double?
    } derive(Eq)

    一个待绘制的矩形。

    Rect::equal

    fn Rect::equal(Rect, Rect) -> Bool

    显式声明 Eq 的方法可被当作常规方法调用。

    新版编译器不再自动完成这个提升,不声明会报 implicit_impl_as_method。

    Rect::not_equal

    fn Rect::not_equal(x : Rect, y : Rect) -> Bool

    显式声明 Eq 的方法可被当作常规方法调用。

    新版编译器不再自动完成这个提升,不声明会报 implicit_impl_as_method。

    Relation

    pub struct Relation {
    label : String
    value : Double
    ratio : Double
    } derive(Eq)

    一条调用关系条目。

    Relation::equal

    fn Relation::equal(Relation, Relation) -> Bool

    显式声明 Eq 的方法可被当作常规方法调用。

    新版编译器不再自动完成这个提升,不声明会报 implicit_impl_as_method。

    Relation::not_equal

    fn Relation::not_equal(x : Relation, y : Relation) -> Bool

    显式声明 Eq 的方法可被当作常规方法调用。

    新版编译器不再自动完成这个提升,不声明会报 implicit_impl_as_method。

    Sample

    pub struct Sample {
    frames : Array[String]
    weight : Double
    } derive(Eq)

    一个采样样本:一条调用栈 + 权重。

    frames 自顶向下排列,frames[0] 为根帧。

    Sample::equal

    fn Sample::equal(Sample, Sample) -> Bool

    显式声明 Eq 的方法可被当作常规方法调用。

    新版编译器不再自动完成这个提升,不声明会报 implicit_impl_as_method。

    Sample::not_equal

    fn Sample::not_equal(x : Sample, y : Sample) -> Bool

    显式声明 Eq 的方法可被当作常规方法调用。

    新版编译器不再自动完成这个提升,不声明会报 implicit_impl_as_method。

    WeightUnit

    pub(all) enum WeightUnit {
    Count
    Nanoseconds
    Microseconds
    Milliseconds
    Seconds
    } derive(Eq)

    权重单位。

    注意名字不能叫 Unit:那是 MoonBit 的内置类型,重名会遮蔽它, 导致全仓库所有 -> Unit 的函数都报出「has type Unit, wanted Unit」这类怪错。

    WeightUnit::equal

    fn WeightUnit::equal(WeightUnit, WeightUnit) -> Bool

    显式声明 Eq 的方法可被当作常规方法调用。

    新版编译器不再自动完成这个提升,不声明会报 implicit_impl_as_method。

    WeightUnit::label

    fn WeightUnit::label(self : WeightUnit) -> String

    单位的规范写法,用于帮助信息与报告表头。

    WeightUnit::not_equal

    fn WeightUnit::not_equal(x : WeightUnit, y : WeightUnit) -> Bool

    显式声明 Eq 的方法可被当作常规方法调用。

    新版编译器不再自动完成这个提升,不声明会报 implicit_impl_as_method。

    WeightUnit::parse

    fn WeightUnit::parse(text : String) -> WeightUnit?

    从命令行文本解析单位。无法识别时返回 None。

    BASE_FONT_SIZE

    let BASE_FONT_SIZE : Double

    基础字号。经典实现用 12px。

    CANVAS_XPAD

    let CANVAS_XPAD : Double

    画布左右留白。与经典实现一致,JS 的缩放数学也依赖此值。

    FRAME_HEIGHT

    let FRAME_HEIGHT : Double

    每行高度。经典实现用 16px。

    aggregate

    fn aggregate(samples : Array[Sample], max_depth? : Int) -> CallTree raise ValidationError

    把样本聚合成调用树。

    max_depth 限制每个根节点下保留的帧数:超出的部分会被合并进 (deeper) 合成节点(见 insert_path)。max_depth = 0 或不传表示不限制。

    真实采样数据里递归栈可达数千层(实测最深 7254 层),按每层一行渲染会得到 十几万像素高的图,因此渲染前必须限深——这是 CLI 默认行为。

    compute_delta

    fn compute_delta(before : CallTree, after : CallTree, top : Int) -> Array[DeltaEntry] raise ValidationError

    对比两份剖面的自身耗时,按函数名对齐。

    只出现在其中一侧的函数也会被列出(另一侧记 0),因为「新增的开销」 和「消失的开销」恰恰是差异分析最关心的两件事。

    top <= 0 表示不截断。

    deeper_placeholder

    fn deeper_placeholder() -> String

    深度超限时,用来收纳「更深的帧」的合成节点名。

    default_font_size

    fn default_font_size() -> Double

    默认字号。

    default_min_width

    fn default_min_width() -> Double

    默认最小矩形宽度(像素)。

    diff_against

    fn diff_against(tree : CallTree, baseline : CallTree) -> CallTree raise ValidationError

    把两份剖面叠加,给 tree 的每个节点标注相对 baseline 的变化量。

    宽度仍按 tree(当前剖面)计算,因此只在基线里出现的帧不会显示—— 这与参考实现 difffolded.pl 的取舍一致(可交换两份文件再生成一张, 从另一个方向看消失的帧)。

    fill_for_delta

    fn fill_for_delta(delta : Double, max_delta : Double) -> String

    差异模式的配色:红 = 变多(劣化)、蓝 = 变少(改善)、白 = 基本没变。

    用经典实现的 color_scale 公式:底色是白,随变化幅度向红或蓝加深:

    变化 > 0: rgb(255, s, s) s = 210 * (max - delta) / max 变化 < 0: rgb(s, s, 255) s = 210 * (max + delta) / max 变化 = 0: rgb(255, 255, 255)

    用 max_delta 归一化:不归一化的话,只要有一处剧变, 其它所有变化在颜色上就会全糊成一片,等于没画。

    零变化取纯白而不是灰色——灰色在浅色背景上与文字对比度太低, 名字几乎读不出来;纯白配深色文字则清晰可读。

    fill_of

    fn fill_of(name : String) -> String

    帧名对应的填充色。

    用经典实现的 hot 调色板公式,只是把它的随机分量换成确定性的哈希:

    v = 名称哈希([0,1)) r = 205 + int(50 * v) g = 0 + int(230 * v) b = 0 + int(55 * v)

    三个通道共用同一个 v,因此颜色落成一条深红 → 橙 → 黄的单维渐变—— 这正是经典火焰图那种暖色观感。

    filter_folded

    fn filter_folded(text : String, pattern : String) -> String

    按子串过滤折叠栈:只保留调用链里含该子串的行。

    这是经典的 grep funcA input | flamegraph.pl 用法,但省掉了管道—— 过滤后的结果仍是折叠栈格式,可以直接再喂给出图命令。

    匹配前做名字归一化,否则用户按可读名 build_strings 去筛, 会一条都筛不到(数据里存的是 build__strings)。

    权重原样保留、不做归一化:过滤后的图回答的是「这个子系统内部怎么分配时间」, 而它占全局多少,靠保留原始权重才能在两张图之间对照。

    find_callees

    fn find_callees(tree : CallTree, wanted : String) -> Array[Relation] raise ValidationError

    find_callers

    fn find_callers(tree : CallTree, wanted : String) -> Array[Relation] raise ValidationError

    查询「谁调用了这个函数」。按直接调用者聚合。

    同一个函数会在多条路径下被调用(例如 array::Array::at 既在冒泡排序里 也在插入排序里被调用),因此返回的是一个列表而不是单条路径。

    format_signed_weights

    fn format_signed_weights(value : Double, unit : WeightUnit) -> String

    带符号的变化量,正数带 + 便于识别。

    format_weight

    fn format_weight(value : Double, unit : WeightUnit) -> String

    把权重格式化成人能读的文本。

    时间单位按数量级自动缩放(871808200 纳秒 → 871.8 ms); 计数则原样显示并加千位分隔(1234567 → 1,234,567),不加后缀—— 加了反而会让人误以为它是时间。

    has_matching_frame

    fn has_matching_frame(tree : CallTree, wanted : String) -> Bool raise ValidationError

    Distinguish a matched leaf from a query that matched no frame.

    layout

    fn layout(tree : CallTree, width : Double, row_height : Double, inverted? : Bool) -> Layout raise ValidationError

    把调用树布局成矩形列表(前序遍历顺序:父节点先于子节点)。

    多个根节点时按各自权重水平切分画布。

    inverted 选择纵向方向:

    • 默认(false)火焰图:根在底部,逐层向上生长。底部密、顶部疏, 高耗时路径表现为向上伸出的「火苗」——这是 Brendan Gregg 定义的经典形态。
    • inverted = true 冰柱图(icicle):根在顶部,逐层向下。

    两种方向都只影响 y 坐标,水平切分完全一致。

    normalize_name

    fn normalize_name(name : String) -> String

    把上游未还原的符号名归一化成可读形式。

    顺序很重要:先还原长度前缀,再折叠下划线。 反过来的话,13run__workload 被折成 12 个字符后长度前缀就对不上了。

    parse_folded

    fn parse_folded(text : String) -> ParseResult

    解析整个折叠栈文本。

    空白行被直接忽略;非空但无法解析的行计入 skipped_lines。

    parse_line

    fn parse_line(line : StringView) -> Sample?

    解析一行折叠栈。

    返回 None 表示该行不是有效样本,由调用方决定是忽略还是计入跳过数。

    render_delta

    fn render_delta(entries : Array[DeltaEntry], before_total : Double, after_total : Double, unit : WeightUnit) -> String

    把差异排行渲染成文本。

    与差异火焰图的分工:图看结构(哪条路径变宽了), 这张表看函数(哪个函数的自身耗时变了多少)。

    render_hotspots

    fn render_hotspots(hotspots : Array[Hotspot], total : Double, unit : WeightUnit) -> String

    把热点列表渲染成纯文本报告。

    unit 决定绝对值怎么显示(时间按数量级缩放,计数加千位分隔); 占比不受单位影响。

    render_relations

    fn render_relations(relations : Array[Relation], wanted : String, kind : String, unit : WeightUnit, matched? : Bool) -> String

    把调用关系渲染成文本。

    render_svg

    fn render_svg(layout : Layout, min_width? : Double, font_size? : Double, show_labels? : Bool, unit? : WeightUnit, normalize_names? : Bool) -> String raise ValidationError

    把布局渲染成 SVG 文本。

    画布尺寸 = 布局宽度 + 左右留白,高度 = 布局高度 + 上下留白; 留白用于放置标题、Search / Reset Zoom 按钮与底部信息栏。

    min_width 以下的矩形会被跳过(默认 0.1 像素,与经典实现一致): 布局给出的是精确几何,但 sub-pixel 的矩形画出来也看不见,只会白白撑大文件。

    show_labels 控制是否在矩形内写帧名。窄框里写字会溢出,因此按字号粗略 估算宽度,放不下就不画(交互脚本在缩放后也会重新估算并截断)。

    unit 决定悬停提示里绝对值的显示方式;normalize_names 控制是否把上游 未还原的符号名归一化(只影响显示,解析层始终原样保留)。

    self_placeholder

    fn self_placeholder() -> String

    「自身耗时」合成帧的名字。

    top_hotspots

    fn top_hotspots(tree : CallTree, top : Int) -> Array[Hotspot] raise ValidationError

    取自身耗时最高的若干函数。

    自身耗时为 0 的函数是纯粹的「过路」节点,不构成热点,会被过滤掉 (与 moon-pprof summary 的口径一致)。

    top <= 0 表示不截断,返回全部条目。

    validate_tree

    fn validate_tree(tree : CallTree) -> Unit raise ValidationError

    Validate iteratively so that unlimited-depth profiles do not consume the host stack.

    version

    fn version() -> String

    返回当前版本号,与 moon.mod 中的 version 保持一致。

    ⚠️ 改版本号时有三处必须一起改,否则会出现「库里报 A、命令行报 B」:
    1. moon.mod 的 version
    2. 本函数的返回值
    3. cli/cli.mbt 里传给命令行解析器的 version= moonflame_test.mbt 会断言本函数的返回值,漏改会直接测试失败。