report API

Luna-Flow/mare_mark/report 把 JSONL 事件流转换为 Plot IR 文档,并把该文档渲染为 mmks_1 JSON、独立的 SVG 或自包含的 HTML 页面。这四个函数都是纯函数:它们接收字符串和值,返回字符串。读写文件是 cli 的工作。投影规则在 report 设计 中解释。

源码:src/report/report.mbt。

import {
  "Luna-Flow/mare_mark/ir_model",
  "Luna-Flow/mare_mark/report",
}

解析事件

document_from_jsonl

document_from_jsonl 把 JSONL 事件流解析为 PlotDocument。

pub fn document_from_jsonl(String, target? : String) -> Result[@ir_model.PlotDocument, String]

target(默认为 "unknown")被复制到文档中;事件本身不携带它。空行会被跳过。其他每一行都必须是 JSON 对象,若存在 artifact_version,它必须是字符串 "mmka_1"。各行按其 "type" 解释:

type必需字段效果
observationimplementation(字符串)、dataset_id、elapsed_us(数值)一个图表点(x = dataset_id,y = elapsed_us,series = implementation),除非 valid 为 false 或 batch_sink 不是 "kept"
validation无按 implementation 和 actual_kind(缺失时为 "unclassified")计入能力矩阵;状态为 invalid 或 infrastructure_failure 时会添加一行不匹配记录,并且在存在 case 和 dataset_id 时移除该实现在该用例和数据集上的点
validation_failure无一行反例(最小输入、最小指纹、缩减路径、重放命令)
summary无设置 run_id 和语料计数;以最后一个摘要为准
其他任何类型忽略,包括 calibration

文档包含一个标题为 "Benchmark observations"、单位为 "µs/op"、区间种类为 "raw" 的 Scaling 图表;若没有任何点保留下来,则不含图表。探索性和验证性观测绘制在一起。语料总数是四个摘要计数之和。

错误是带有从 1 开始的行号的消息:无效 JSON、不是对象的行、不受支持或非字符串的产物版本、缺少 implementation、dataset_id 或 elapsed_us 的观测,以及既没有点、也没有不匹配和反例的流。

test "parse a small event stream" {
  let source =
    #|{"type":"observation","case":"add","implementation":"a","dataset_id":0,"elapsed_us":12.5}
    #|{"type":"observation","case":"add","implementation":"b","dataset_id":0,"elapsed_us":10.0}
    #|{"type":"observation","case":"add","implementation":"b","dataset_id":1,"elapsed_us":30.0,"valid":false}
    #|{"type":"summary","run_id":"demo","passed_count":2}
  let document = @report.document_from_jsonl(source, target="native").unwrap()
  inspect(document.run_id, content="demo")
  inspect(document.plots[0].points.length(), content="2")
  inspect(document.differential.corpus.total, content="2")
  let bad = @report.document_from_jsonl("{\"artifact_version\":\"mmka_9\"}")
  inspect(bad is Err("unsupported artifact version 'mmka_9' at line 1"), content="true")
}

渲染

plot_json

plot_json 把文档序列化为 mmks_1 JSON。

pub fn plot_json(@ir_model.PlotDocument) -> String

该对象具有键 schema_version、run_id、target、plots(每项含 kind、title、unit、interval_kind,以及由 x、y、series 组成的 points)和 differential(mismatches、capabilities、counterexamples、corpus)。图表种类以蛇形命名写出(scaling、raw_distribution、block_order、interval、outlier、change_point、heatmap、pareto)。输出是紧凑的,字符串由 JSON 编码器转义。

test "plot JSON" {
  let point = @ir_model.PlotPoint::new("0", 1.0, "a")
  let plot = @ir_model.Plot::new(@ir_model.PlotKind::Scaling, "t", "µs/op", "raw", [point])
  let document = @ir_model.PlotDocument::new("r", "native", [plot])
  let json = @report.plot_json(document)
  inspect(json.has_prefix("{\"schema_version\":\"mmks_1\",\"run_id\":\"r\""), content="true")
  inspect(json.contains("\"points\":[{\"x\":\"0\",\"y\":1,\"series\":\"a\"}]"), content="true")
}

plot_svg

plot_svg 把一张图表渲染为独立的 SVG 元素。

pub fn plot_svg(@ir_model.Plot) -> String

该 SVG 的视框为 960 × 420,带有可访问的 <title> 和 <desc>、内联样式、五条带刻度值的水平网格线、最多约十个 x 标签以及图例。每个点绘制为一个圆,带有提示 series — x: y unit。Scaling、Interval 和 ChangePoint 图表还会按系列连接每个 x 类别上各点的均值。Heatmap 图表为每个系列和 x 类别绘制一个单元格,其不透明度随均值增大。空图表显示 “No observations”。所有文本都经过 XML 转义。

test "plot SVG" {
  let points = [
    @ir_model.PlotPoint::new("64", 2.0, "scalar"),
    @ir_model.PlotPoint::new("128", 4.5, "scalar"),
  ]
  let plot = @ir_model.Plot::new(@ir_model.PlotKind::Scaling, "a < b", "µs/op", "raw", points)
  let svg = @report.plot_svg(plot)
  inspect(svg.has_prefix("<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 960 420\""), content="true")
  inspect(svg.contains("<title id=\"plot-title\">a &lt; b</title>"), content="true")
  inspect(svg.contains("<polyline"), content="true")
}

html

html 把整个文档渲染为一个自包含的 HTML 页面。

pub fn html(@ir_model.PlotDocument) -> String

页面包含一个带有运行 id、目标和模式版本的页头,接着是差分报告(语料结果、不匹配、能力覆盖矩阵、最小反例),然后每张图表一个 figure。CSS 是内联的,图表是内联 SVG,没有脚本也没有外部资源,因此该文件可以原样归档或作为附件。输出只取决于文档。

test "self-contained HTML" {
  let source =
    #|{"type":"observation","case":"add","implementation":"a","dataset_id":0,"elapsed_us":12.5}
    #|{"type":"summary","run_id":"demo"}
  let page = @report.html(@report.document_from_jsonl(source).unwrap())
  inspect(page.has_prefix("<!doctype html>"), content="true")
  inspect(page.contains("<title>mare_mark · demo</title>"), content="true")
  inspect(page.contains("<script"), content="false")
}