Skip to content

Latest commit

 

History

History
517 lines (360 loc) · 14.6 KB

File metadata and controls

517 lines (360 loc) · 14.6 KB

coroTracer:跨语言、零拷贝的协程观测工具

Go Engine SDK C++ Arch License

UDSWakeupMechanics.gif

开发初衷:我之前在调一个自己的 M:N 调度器时,遇到过一个非常恶心的问题。高并发下系统吞吐量会突然掉到零,但 ASAN 和 TSAN 都是绿的,因为它根本不是传统意义上的内存破坏,而是一次典型的 lost wakeup。协程逻辑上已经永远等不回来了,可常规工具又很难把这种“状态机断裂”直接抓出来。coroTracer 就是为这种问题写的。

coroTracer 是一个 进程外(out-of-process) 的协程采集器。
它专门面向 M:N 协程调度器,目标很明确:

  • 抓协程状态切换
  • 降低对目标进程的干扰
  • 输出可复用的原始轨迹
  • 让后续分析、落库、离线处理都建立在可靠的底层采集之上

它现在的定位不是 APM,也不是在线分析平台。
当前仓库专注于两件事:

  1. 把协程状态安全地采集成 JSONL
  2. 把已有 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 |
                                                          +----------------------------------+

当前能力

1. 采集模式

Go 引擎负责:

  • 创建共享内存
  • 创建 Unix Domain Socket
  • 拉起目标程序
  • 持续扫描共享内存里的协程事件
  • 以 JSONL 形式落盘

输出的 JSONL 每行大致长这样:

{"probe_id":123,"tid":456,"addr":"0x0000000000000000","seq":2,"is_active":true,"ts":123456789}

字段含义对应源码里的 TraceRecord

  • probe_id:协程探针唯一标识
  • tid:真实线程 ID
  • addr:挂起点地址或相关协程地址
  • seq:槽位序列号
  • is_active:当前是否处于活跃态
  • ts:时间戳

2. 导出模式

仓库现在内置了 export/ 目录,支持把 已有 JSONL 转成:

  • SQLite 数据库
  • MySQL 数据库
  • PostgreSQL 数据库
  • DataFrame 友好的 CSV 文件

注意这里是 已有 JSONL 的二次导出,不是“边采集边转数据库”。

3. SDK

当前提供了一个 C++20 头文件版 SDK:

同时也提供了一个不依赖框架、基于 Rust poll 模型的 SDK:

它们的职责是:

  • 连接共享内存
  • 连接 UDS
  • 在协程挂起 / 恢复时写入状态
  • 遵守 cTP 的内存契约

核心机制

这套设计的核心思想很简单:

把执行平面和观测平面彻底拆开。

目标进程只负责把状态写进共享内存。
Go 采集器在进程外做异步收割,不把复杂逻辑塞回目标进程里。

1. 共享内存协议(cTP)

底层协议文档见:

核心点有三个:

  1. GlobalHeaderStationData 都强制按固定大小布局
  2. Epoch 强制对齐到 64 字节 cache line
  3. 读写双方通过 seq 实现无锁一致性协议

2. C++ 写侧协议

写侧并不是“直接把字段一把写进去”,而是遵守一个很明确的顺序:

  1. 先把 seq 改成奇数,表示“正在写”
  2. 再写 payload
  3. 最后把 seq 改成偶数,表示“写完了”

这对应 SDK/c++/coroTracer.h 里的 PromiseMixin::write_trace

3. Go 读侧协议

Go 读侧也不是“看见数据就信”,而是走三步:

  1. 先读一次 seq
  2. 如果 seq 是偶数且比本地 lastSeen 新,才去拷 payload
  3. 拷完以后再读一次 seq
  4. 只有两次 seq 一样,才真正写 JSONL

这部分在:

4. UDS 智能唤醒

为了避免 Go 引擎在低流量时一直空转:

  • Go 空闲时会把 TracerSleeping 设成 1
  • C++ 写入完成后如果发现引擎在睡眠,就发一个 1 字节 UDS 唤醒信号

这样高吞吐时避免系统调用风暴,低吞吐时又不会纯忙等。


快速开始

1. 编译

go build -o coroTracer main.go

2. 采集一个目标程序

./coroTracer -n 256 -cmd "./your_target_app" -out trace.jsonl

这条命令做的事情是:

  • 预分配 256 个 station
  • 拉起 ./your_target_app
  • 把轨迹写到 trace.jsonl

这里有一个重要约束:

  • -cmd 模式只负责采集 JSONL
  • 不会在同一轮运行里继续导出数据库

也就是说,采集和导出是 两个独立阶段

3. 接入 C++ SDK

目标程序会自动从环境变量继承 IPC 配置。

最小接入大概是这样:

#include "coroTracer.h"

int main() {
    corotracer::InitTracer();
    // ... 启动你的调度器
}

如果是 coroutine promise,可以继承 PromiseMixin

struct promise_type : public corotracer::PromiseMixin {
    // 你的业务逻辑
};

SDK 会在内部记录 await_suspend / await_resume 对应的状态切换。


导出 JSONL

导出模式只处理 已经存在的 JSONL 文件
不能和 -cmd 同时使用。

也就是说,下面这种是允许的:

./coroTracer -export sqlite -in trace.jsonl

但下面这种是不允许的:

./coroTracer -cmd "./your_target_app" -export sqlite

1. 导出 SQLite

./coroTracer -export sqlite -in trace.jsonl -sqlite-out trace.sqlite

说明:

  • 默认会把输出文件名推导成 <input>.sqlite
  • 运行时依赖本机 sqlite3 命令

2. 导出 CSV(DataFrame 友好格式)

./coroTracer -export csv -in trace.jsonl -csv-out trace.csv

这个 CSV 可以直接喂给:

  • pandas
  • polars
  • DuckDB
  • R

3. 导出 MySQL

./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 命令
  • 会自动建库、建表并插入数据

4. 导出 PostgreSQL

./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 改掉

5. 常用导出参数

当前支持的导出相关参数包括:

  • -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

更完整的参数说明见:


Lean 4 证明

这个项目里比较重要的一点是:
采集协议不是“拍脑袋觉得没问题”,而是已经做了形式化建模。

你可以按这个顺序看:

  1. proof/proof.lean
  2. proof.md
  3. proof_en.md

证明覆盖的核心内容是:

  • Go 不会把半写入的脏数据提交进日志
  • 在写侧短暂不干扰的窗口里,Go 一定能成功完成一次采集提交

它和源码的对应关系主要在:


当前边界

为了避免误解,这里把项目边界写清楚。

1. 这个仓库当前不是分析平台

它现在提供的是:

  • 底层采集
  • JSONL 落盘
  • 导出到数据库 / CSV

它不再包含以前那种“内置报告生成器 / 页面分析器”的路线。

2. 这个仓库当前重点是 C++20 / Rust SDK

虽然协议本身是语言无关的,但当前仓库里正式给出的 SDK 包括:

  • C++20 coroutine 接入
  • Rust Future::poll 接入

Zig、C 理论上也都能做,因为底层依赖的是:

  • mmap
  • 固定 ABI 布局
  • 原子读写契约

3. 运行时外部依赖

如果你要用导出功能,当前实现会依赖本机命令行工具:

  • SQLite:sqlite3
  • MySQL:mysql
  • PostgreSQL:psql

这是为了保持项目本身的 Go 依赖尽量轻,不额外引入数据库 driver。


仓库结构

当前比较关键的目录和文件:


测试

测试套件完全自动化,一条命令覆盖所有层:

bash tests/run_tests.sh

脚本执行内容

阶段 内容
1 Go 单元测试 — go test -race ./...,覆盖全部包
2 Rust SDK 单元测试 — cargo testSDK/rust/
3 编译 Go tracer 二进制
4 编译 Rust 集成测试 tracee
5 Rust tracee 单元测试 — cargo testtests/rust_tracee/
6 集成运行 — Go 引擎 + Rust tracee 跑 12 个异步场景
7 JSONL 不变量校验(SeqLock 偶数 seq、addr 格式、两种事件类型、纳秒时钟)
8 CSV 导出回路验证
9 SQLite 导出回路验证 sqlite3 不在 PATH 时自动跳过)

Go 单元测试覆盖范围

  • structure/GlobalHeader / Epoch / StationData 尺寸与字段偏移、SeqLock Harvest 全路径(空站、单写、不重复读、奇数 seq 跳过、撕裂读丢弃、环形缓冲回绕)
  • engine/TracerEngine 初始化、shm 文件大小、有/无数据时的 doScan、分配计数截断
  • export/StreamJSONL(所有边界情况)、CSV 导出、SQLite 导出、schema SQL 生成、所有 escape / quote 辅助函数
  • mainderiveOutputPathresolveExportInput

Rust 单元测试覆盖范围

  • SDK/rust/(3 个)— 协议布局编译期断言、TracedFuture poll 语义、Send 约束
  • tests/rust_tracee/(14 个)— PollTrace 生命周期(new、pending/resume 循环、幂等 mark-dead、drop)、TracedFuture 输出保留与 Pending 语义、Send 约束、多线程并发 future

集成测试场景(Rust tracee)

12 个异步场景在 Go 引擎下端到端运行:

  1. 单次 sleep
  2. 20 个并发任务
  3. 同一 future 多次挂起
  4. Oneshot channel 生产者/消费者
  5. mpsc channel(N 个生产者、1 个消费者)
  6. Barrier 汇合点
  7. yield_now 挂起
  8. 混合 active/suspend 事件
  9. 压力测试 — 100 个并发任务
  10. 嵌套 future 链
  11. PollTrace 低级 API
  12. TracedFuture 完成前被 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

联系方式

lixia.chat@outlook.com