event 设计
设计目标
事件流是实验的审计记录:报告中的每个数字和每个被重放的失败都必须能从中恢复。event 让这份记录保持只追加、面向行,并让存储方面的选择远离运行器。
数学背景
把一次运行建模为事件序列 ,把接收器建模为折叠 。JSONL 序列化是从事件到行的映射 ,存储的流是用换行符连接的各行 。
这种形状带来两个性质。
- 前缀封闭。 流的每个前缀都是某个更短运行的流。在 个事件后崩溃会留下 的有效记录;独立处理各行的读取器可以使用它。
- 投影与拼接可交换。 报告是逐行构建的,因此两个拼接起来的流的投影可以由各行的投影算出。合并或过滤流只是普通的文本处理。
tee 是两个折叠的积:它把 映射到 ,因此一次运行可以在单趟中同时存储到内存和磁盘。
设计决策
由闭包组成的记录
问题。 接收器持有状态(数组、文件句柄)并且会被组合(tee)。方案。 带方法的 trait;由函数组成的记录。选择。 由五个闭包组成的记录。理由。 闭包可以捕获接收器所需的任何状态,记录可以在测试中内联构建,而 tee 这样的组合子就是返回新记录的普通函数。
每行一个 JSON 对象
问题。 记录必须可流式传输、可追加,并且能被任何工具读取。选择。 采用 JSONL,每一行都带有 type 判别字段和 artifact_version。理由。 每一行都是自描述的,因此读取器可以跳过不认识的类型,grep 也能用,崩溃也永远不会损坏之前的行。每行带版本使来自不同版本的流可以拼接在一起,同时仍能被精确拒绝。
只记录原始值
观测携带的是一个批次的每次迭代时间,从不是均值、中位数或过滤后的值。报告中的每个统计量都从这些原始值重新计算,离群值策略无法改写历史。
种子写为字符串
seed 写为十进制字符串,因为 JSON 数值是双精度浮点数,无法表示每个 64 位整数:大于 的整数会被舍入。
finish 返回位置
finish 返回记录的去向(memory://run/…、jsonl://memory/…、一个文件名),运行器把它存入摘要。调用者无需运行器了解文件,就能知道证据在哪里。
正确性与不变量
- 每次 emit 调用恰好写出一行(
JsonlSink、streaming_jsonl)或恰好追加一个元素(InMemorySink)。 - 每一行都带有
artifact_versionmmka_1和type。 tee保持顺序:对每个事件,左侧接收器都先于右侧接收器看到它。to_jsonl每个事件一行,以\n分隔。- 字符串由 JSON 编码器转义,因此一行中永远不会包含原始换行符。
被否决的方案
- 每次运行一个 JSON 文档。 不可追加;一次崩溃会丢失全部内容。
- CSV。 嵌套的证据(操作数、标志、环境)放不进去。
- 二进制格式。 在评审和 issue 报告中无法阅读。
边界
- 没有文件 IO:把行写入磁盘是调用者的工作(由
cli和应用完成)。 InMemorySink不保存摘要。- JSONL 写入器省略验证状态的原因字符串以及观测的
setup_timing。 - 这里没有读取器;由
report和cli解析事件流。