开发初衷:我之前在调一个自己的 M:N 调度器时,遇到过一个非常恶心的问题。高并发下系统吞吐量会突然掉到零,但 ASAN 和 TSAN 都是绿的,因为它根本不是传统意义上的内存破坏,而是一次典型的
lost wakeup。协程逻辑上已经永远等不回来了,可常规工具又很难把这种“状态机断裂”直接抓出来。coroTracer 就是为这种问题写的。
coroTracer 是一个 进程外(out-of-process) 的协程采集器。
它专门面向 M:N 协程调度器,目标很明确:
- 抓协程状态切换
- 降低对目标进程的干扰
- 输出可复用的原始轨迹
- 让后续分析、落库、离线处理都建立在可靠的底层采集之上
它现在的定位不是 APM,也不是在线分析平台。
当前仓库专注于两件事:
- 把协程状态安全地采集成 JSONL
- 把已有 JSONL 导出成 SQLite / MySQL / PostgreSQL / CSV
另外,采集协议的核心安全性已经在 Lean 4 里建模并证明,相关文件见:
项目现状说明:到目前为止,这个项目已经是闭环可用的。采集、落盘、导出这一整套链路已经能实际工作。真要说当前还比较明显的不足,主要就是采集容量仍然是有限协程数量,而不是动态扩容。除此之外,项目本身已经可以使用。后续更新会继续有,但节奏大概率会明显放缓,可能会比之前慢很多。 这次更新主要集中在数据格式转换和导出链路上,没有去碰核心采集代码。Codex 确实大幅提高了这次迭代的效率,也让这轮更新能够更快上线。
+-----------------------+ +-----------------------+
| Target Application | | Go Tracer Engine |
| (C++, Rust, Zig...) | | |
| | [ Lock-Free SHM ] | |
| +-----------------+ | +-----------------+ | +-----------------+ |
| | cTP SDK Probe |=======> | StationData [N] | <=======| Harvester Loop | |
| +-----------------+ | Write +-----------------+ Read | +-----------------+ |
| | ^ | |
| [ Socket ] |---(Wakeup)---UDS---(Listen)---| [ File I/O ] |
+-----------------------+ +-----------------------+
|
v
+------------------+
| trace_output |
| .jsonl |
+------------------+
|
v
+----------------------------------+
| SQLite / MySQL / PostgreSQL / CSV |
+----------------------------------+
Go 引擎负责:
- 创建共享内存
- 创建 Unix Domain Socket
- 拉起目标程序
- 持续扫描共享内存里的协程事件
- 以 JSONL 形式落盘
输出的 JSONL 每行大致长这样:
{"probe_id":123,"tid":456,"addr":"0x0000000000000000","seq":2,"is_active":true,"ts":123456789}字段含义对应源码里的 TraceRecord:
probe_id:协程探针唯一标识tid:真实线程 IDaddr:挂起点地址或相关协程地址seq:槽位序列号is_active:当前是否处于活跃态ts:时间戳
仓库现在内置了 export/ 目录,支持把 已有 JSONL 转成:
- SQLite 数据库
- MySQL 数据库
- PostgreSQL 数据库
- DataFrame 友好的 CSV 文件
注意这里是 已有 JSONL 的二次导出,不是“边采集边转数据库”。
当前提供了一个 C++20 头文件版 SDK:
同时也提供了一个不依赖框架、基于 Rust poll 模型的 SDK:
它们的职责是:
- 连接共享内存
- 连接 UDS
- 在协程挂起 / 恢复时写入状态
- 遵守 cTP 的内存契约
这套设计的核心思想很简单:
把执行平面和观测平面彻底拆开。
目标进程只负责把状态写进共享内存。
Go 采集器在进程外做异步收割,不把复杂逻辑塞回目标进程里。
底层协议文档见:
核心点有三个:
GlobalHeader和StationData都强制按固定大小布局Epoch强制对齐到 64 字节 cache line- 读写双方通过
seq实现无锁一致性协议
写侧并不是“直接把字段一把写进去”,而是遵守一个很明确的顺序:
- 先把
seq改成奇数,表示“正在写” - 再写 payload
- 最后把
seq改成偶数,表示“写完了”
这对应 SDK/c++/coroTracer.h 里的 PromiseMixin::write_trace。
Go 读侧也不是“看见数据就信”,而是走三步:
- 先读一次
seq - 如果
seq是偶数且比本地lastSeen新,才去拷 payload - 拷完以后再读一次
seq - 只有两次
seq一样,才真正写 JSONL
这部分在:
为了避免 Go 引擎在低流量时一直空转:
- Go 空闲时会把
TracerSleeping设成1 - C++ 写入完成后如果发现引擎在睡眠,就发一个 1 字节 UDS 唤醒信号
这样高吞吐时避免系统调用风暴,低吞吐时又不会纯忙等。
go build -o coroTracer main.go./coroTracer -n 256 -cmd "./your_target_app" -out trace.jsonl这条命令做的事情是:
- 预分配 256 个 station
- 拉起
./your_target_app - 把轨迹写到
trace.jsonl
这里有一个重要约束:
-cmd模式只负责采集 JSONL- 不会在同一轮运行里继续导出数据库
也就是说,采集和导出是 两个独立阶段。
目标程序会自动从环境变量继承 IPC 配置。
最小接入大概是这样:
#include "coroTracer.h"
int main() {
corotracer::InitTracer();
// ... 启动你的调度器
}如果是 coroutine promise,可以继承 PromiseMixin:
struct promise_type : public corotracer::PromiseMixin {
// 你的业务逻辑
};SDK 会在内部记录 await_suspend / await_resume 对应的状态切换。
导出模式只处理 已经存在的 JSONL 文件。
不能和 -cmd 同时使用。
也就是说,下面这种是允许的:
./coroTracer -export sqlite -in trace.jsonl但下面这种是不允许的:
./coroTracer -cmd "./your_target_app" -export sqlite./coroTracer -export sqlite -in trace.jsonl -sqlite-out trace.sqlite说明:
- 默认会把输出文件名推导成
<input>.sqlite - 运行时依赖本机
sqlite3命令
./coroTracer -export csv -in trace.jsonl -csv-out trace.csv这个 CSV 可以直接喂给:
- pandas
- polars
- DuckDB
- R
./coroTracer \
-export mysql \
-in trace.jsonl \
-db-host 127.0.0.1 \
-db-port 3306 \
-db-user root \
-db-password your_password \
-db-name coro_tracer \
-db-table coro_trace_events如果你用 Unix Socket,也可以传:
./coroTracer \
-export mysql \
-in trace.jsonl \
-db-user root \
-db-password your_password \
-mysql-socket /tmp/mysql.sock说明:
- 运行时依赖本机
mysql命令 - 会自动建库、建表并插入数据
./coroTracer \
-export postgresql \
-in trace.jsonl \
-db-host 127.0.0.1 \
-db-port 5432 \
-db-user postgres \
-db-password your_password \
-db-name coro_tracer \
-db-table coro_trace_events \
-pg-sslmode disable说明:
- 运行时依赖本机
psql命令 - 会自动检查目标数据库,不存在时尝试创建
- 默认用
postgres作为 maintenance database,可以通过-pg-maintenance-db改掉
当前支持的导出相关参数包括:
-export-in-sqlite-out-csv-out-db-cli-db-host-db-port-db-user-db-password-db-name-db-table-mysql-socket-pg-maintenance-db-pg-sslmode
其中:
-db-password就是直接传用户自己的数据库密码-db-cli用来覆盖默认 CLI 名称- MySQL 默认是
mysql - PostgreSQL 默认是
psql
- MySQL 默认是
更完整的参数说明见:
这个项目里比较重要的一点是:
采集协议不是“拍脑袋觉得没问题”,而是已经做了形式化建模。
你可以按这个顺序看:
证明覆盖的核心内容是:
- Go 不会把半写入的脏数据提交进日志
- 在写侧短暂不干扰的窗口里,Go 一定能成功完成一次采集提交
它和源码的对应关系主要在:
为了避免误解,这里把项目边界写清楚。
它现在提供的是:
- 底层采集
- JSONL 落盘
- 导出到数据库 / CSV
它不再包含以前那种“内置报告生成器 / 页面分析器”的路线。
虽然协议本身是语言无关的,但当前仓库里正式给出的 SDK 包括:
- C++20 coroutine 接入
- Rust
Future::poll接入
Zig、C 理论上也都能做,因为底层依赖的是:
mmap- 固定 ABI 布局
- 原子读写契约
如果你要用导出功能,当前实现会依赖本机命令行工具:
- SQLite:
sqlite3 - MySQL:
mysql - PostgreSQL:
psql
这是为了保持项目本身的 Go 依赖尽量轻,不额外引入数据库 driver。
当前比较关键的目录和文件:
- main.go:程序入口,负责区分采集模式和导出模式
- engine/engine.go:共享内存、UDS、热循环采集
- structure/station.go:核心读取协议
- structure/jsonl.go:JSONL 落盘
- export/:SQLite / MySQL / PostgreSQL / CSV 导出
- SDK/c++/coroTracer.h:C++20 SDK
- SDK/rust/:Rust poll 模型 SDK
- docs/cTP.md:内存协议文档
- proof/proof.lean:Lean 4 证明
- proof.md:中文证明详解
- proof_en.md:英文证明详解
测试套件完全自动化,一条命令覆盖所有层:
bash tests/run_tests.sh| 阶段 | 内容 |
|---|---|
| 1 | Go 单元测试 — go test -race ./...,覆盖全部包 |
| 2 | Rust SDK 单元测试 — cargo test(SDK/rust/) |
| 3 | 编译 Go tracer 二进制 |
| 4 | 编译 Rust 集成测试 tracee |
| 5 | Rust tracee 单元测试 — cargo test(tests/rust_tracee/) |
| 6 | 集成运行 — Go 引擎 + Rust tracee 跑 12 个异步场景 |
| 7 | JSONL 不变量校验(SeqLock 偶数 seq、addr 格式、两种事件类型、纳秒时钟) |
| 8 | CSV 导出回路验证 |
| 9 | SQLite 导出回路验证 (sqlite3 不在 PATH 时自动跳过) |
structure/—GlobalHeader/Epoch/StationData尺寸与字段偏移、SeqLockHarvest全路径(空站、单写、不重复读、奇数 seq 跳过、撕裂读丢弃、环形缓冲回绕)engine/—TracerEngine初始化、shm 文件大小、有/无数据时的doScan、分配计数截断export/—StreamJSONL(所有边界情况)、CSV 导出、SQLite 导出、schema SQL 生成、所有 escape / quote 辅助函数main—deriveOutputPath、resolveExportInput
SDK/rust/(3 个)— 协议布局编译期断言、TracedFuturepoll 语义、Send约束tests/rust_tracee/(14 个)—PollTrace生命周期(new、pending/resume 循环、幂等 mark-dead、drop)、TracedFuture输出保留与 Pending 语义、Send约束、多线程并发 future
12 个异步场景在 Go 引擎下端到端运行:
- 单次 sleep
- 20 个并发任务
- 同一 future 多次挂起
- Oneshot channel 生产者/消费者
- mpsc channel(N 个生产者、1 个消费者)
- Barrier 汇合点
yield_now挂起- 混合 active/suspend 事件
- 压力测试 — 100 个并发任务
- 嵌套 future 链
PollTrace低级 APITracedFuture完成前被 drop
| 依赖 | 用途 |
|---|---|
| Go 工具链 | Go 编译 + 单元测试 |
| Rust / cargo | Rust SDK 测试 + tracee 编译 |
sqlite3 二进制 |
阶段 9(SQLite 导出)— 可选 |
所有日志和生成文件都在 tests/output/:
tests/output/
trace.jsonl # 原始采集事件
trace.csv # CSV 导出结果
trace.sqlite # SQLite 导出结果(有 sqlite3 时)
go_unit_tests.log
rust_sdk_tests.log
rust_tracee_tests.log
integration_run.log
