📡 Pulse(带宽驱动全双工 UDP 探针)
以带宽为规格,以事件为真相 —— Specify bandwidth. Measure truth.
Pulse 将目标带宽换算为整数包率(pps),驱动全双工 UDP;客户端与服务端只记录原始事件到 CSV,由 pulse merge 离线计算延迟、丢包与 Goodput,并生成 HoloWAN Recorder 兼容的 report.txt。运行时可选用类 iperf3 UDP 的周期表输出(见下文「终端周期表」)。

📘 术语说明
| 术语 |
说明 |
| 【pps】 |
packets per second,由目标带宽与 payload 大小换算得到的整数发包速率,用于定时发送。 |
| 【Goodput】 |
有效吞吐量(Mbps),由 merge 按实际收包数与时长统计,与声明带宽可能不同。 |
| 【HoloWAN Recorder】 |
HoloWAN 录制器兼容的文本报告头与周期数据行格式,便于接入既有网络仿真工具链。 |
| 【payload】 |
应用层 UDP 数据字节数;CLI 中的 -size 指 payload,不含 9 字节二进制包头。 |
⚡ 快速开始(约 5 秒)
在两个终端分别启动服务端与客户端(周期统计默认每秒一行,打在标准输出):
# 终端 1:启动服务端
./pulse server -p 5000
# 终端 2:启动客户端(使用默认参数)
./pulse client -s localhost:5000
# 合并结果(自动生成 report.txt)
./pulse merge \
--client results/*/client.csv \
--server results/*/server.csv
默认测试时长 10 秒,上行 1 Mbps / 下行 2 Mbps,结果存于 results/ 目录。若不需要终端周期表,可在 server / client 上加 -i 0 关闭。
📋 核心能力
- ⏱️ 精确带宽控制:将目标带宽(如
1Mbps)转换为整数包率(pps),避免浮点调度误差
- 📦 统一载荷大小:上下行共用
-size(payload 字节数,不含 9 字节二进制头)
- 🕒 时间轴对齐:服务端记录的时间戳已映射到客户端时轴,可直接计算端到端延迟
- 📊 离线分析友好:
merge 子命令自动读取 config.json,按分桶周期输出 HoloWAN 格式报告
- 📟 类 iperf3 UDP 周期表:
client / server 支持 -i / -interval,主时长内按间隔打印 Transfer / Bitrate / Jitter / Lost/Total(详见下文);结构化日志走 stderr,表格走 stdout
- 🔁 同一二进制:
server / client / merge / pcap plan|replay / version
- 📱 跨平台支持:Linux / macOS / Windows / Android(arm64)
- 📦 依赖:除 gopacket(PCAP 解析,读文件走纯 Go
pcapgo、默认 CGO_ENABLED=0)外无其他第三方模块
💻 安装与构建
获取源码
独立仓库:
git clone https://gitcode.com/sangachy/pulse.git
cd pulse
若你在更大 monorepo 内仅使用本工具子路径,请在包含本目录 go.mod 的目录下执行下文命令(与仓库布局无关,以 go.mod 所在目录为准)。
下文所有 go build、./scripts/... 命令均默认在该目录下执行。
要求
构建
国内拉模块可先(与 scripts/build-release.sh 行为一致):
source scripts/go-env-aliyun.sh # 设置 GOPROXY/GOSUMDB,可写入 shell 配置持久化
go build -trimpath -buildvcs=false -o pulse ./cmd/pulse
提示:若构建环境无 .git 目录,建议保留 -buildvcs=false。
若未设置 GOPROXY 时拉取 gopacket 出现 proxy.golang.org 超时,请先执行上一段 source scripts/go-env-aliyun.sh 再 go build / go test。
PCAP 合成回放(简要)
pulse pcap plan:从 PCAP 生成 plans/ 下 JSON 计划;未指定双端时按 最大流自动推断 IP,手动模式须同时给 -client-endpoint / -server-endpoint(可为 IPv4 或 IP:PORT,仅 IP 时该侧任意端口均匹配,见 docs/design-pulse.md)。
pulse pcap replay:按 plan 与墙钟锚点与 pulse server(默认从 plans/ 读同名文件)做双向 UDP 回放。
- 服务端:
-pcap-plan-dir 默认 plans。
多平台发布(含版本注入)
# 使用默认版本 v0.1.0-alpha.2
./scripts/build-release.sh
# 指定版本(需与 git tag 一致)
VERSION=v0.1.0 ./scripts/build-release.sh
Windows 用户:
scripts\build-release.bat
仅构建 Android(arm64)
mkdir -p dist
GOOS=android GOARCH=arm64 CGO_ENABLED=0 go build -trimpath -buildvcs=false \
-ldflags "-s -w -X gitcode.com/sangachy/pulse/internal/version.Version=v0.1.0" \
-o dist/pulse-android-arm64 ./cmd/pulse
设计细节(START 协议、二进制包头、周期表语义)见 docs/design-pulse.md
🧪 使用指南
1. 启动服务端(必须先运行)
./pulse server -p 5000 -o results
PCAP 回放:请先将 plan JSON 放入运行目录下 plans/(或 pulse server -pcap-plan-dir DIR),再执行 pulse pcap replay;详见上文「PCAP 合成回放」与设计文档。
2. 启动客户端
# 使用默认参数(size=512, ul=1Mbps, dl=2Mbps, duration=10s)
./pulse client -s 192.168.1.100:5000 --test-id="video_call_01"
# 自定义参数
./pulse client \
-s 192.168.1.100:5000 \
-size 1024 \
--ul-bandwidth 1Mbps \
--dl-bandwidth 5Mbps \
-duration 30s \
--time-delta=0 \
--test-id=video_call_01 \
-o /data/pulse_runs
若省略 --test-id,系统自动生成唯一 ID(格式:YYYYMMDDTHHmmss_xxxxxx,如 20260306T201103_a3f91c),日志中会打印实际路径。
3. 合并分析(post-processing)
# 自动读取同目录 config.json,输出 report.txt
./pulse merge \
--client results/video_call_01/client.csv \
--server results/video_call_01/server.csv
# 显式指定报告路径
./pulse merge \
--client results/video_call_01/client.csv \
--server results/video_call_01/server.csv \
--o-txt /tmp/report.txt \
--o-json /tmp/summary.json
--o-json 根对象形如(note_cn 为长字符串,此处省略):
{
"schema": "pulse.merge.summary.v2",
"config": {
"test_id": "video_call_01",
"size": 1024,
"duration_sec": 30,
"sample_interval_sec": 0.1,
"server_addr": "192.168.1.100:5000",
"ul_bandwidth": "1Mbps",
"dl_bandwidth": "2Mbps",
"grace_period_sec": 10,
"time_delta_ns": 0,
"ul_interval_ns": 4194304,
"dl_interval_ns": 2097152,
"ul_packet_count": 3579139,
"dl_packet_count": 7158278,
"ul_pps": 238,
"dl_pps": 476,
"ul_target_mbps": 1,
"dl_target_mbps": 2,
"ul_actual_mbps": 0.999,
"dl_actual_mbps": 1.998,
"output_root": "results"
},
"uplink": {
"delay_avg_ms": 12.3,
"delay_paired_sample_count": 1000,
"loss_percent": 0.5,
"goodput_whole_session_mbps": 0.95,
"packets_sent": 1000,
"packets_recv": 995
},
"downlink": {
"delay_avg_ms": 11.0,
"delay_paired_sample_count": 998,
"loss_percent": 0.2,
"goodput_whole_session_mbps": 1.80,
"packets_sent": 1000,
"packets_recv": 998
},
"out_of_bucket": {
"ul_sent_before_bucket_base": 0,
"ul_sent_after_duration_window": 0,
"dl_sent_before_bucket_base": 0,
"dl_sent_after_duration_window": 0
},
"note_cn": "…"
}
✅ merge 会:
- 从
client.csv 同目录加载 config.json
- 按
sample_interval_sec 分桶
- 丢弃超窗样本(并在 stdout 报告 underflow/overflow)
- 输出 HoloWAN 兼容的
report.txt(Start / End Time 由双端 CSV 全部非空 timestamp_ns 的最小、最大值换算为本地墙钟时间;无任何时间戳时回退为执行 merge 的时刻;表头 Test Name:、Duration(sec):、Interval(sec):、Packet Size(byte): 与 config.json 的 test_id / duration_sec / sample_interval_sec / size 一一对应。客户端写入的 config.json 含完整跑测快照(含 grace_period_sec、time_delta_ns、间隔/包数/pps、目标与实际 Mbps、output_root 等);仅当 config.json 提供 server_addr 或上下行带宽字符串时,merge 在 Packet Size(byte): 与 Enable Reordering: 之间追加三行 Server Address:、UL Bandwidth:、DL Bandwidth:(其余派生字段不进 report.txt,见同目录 config.json 与 --o-json 的 config)。仅含上述四键的旧版 config.json 仍可 merge,且无扩展表头)
📟 终端周期表(iperf3 UDP 风格)
pulse client 与 pulse server 均支持在主测试时长内按固定间隔打印人类可读表(默认 1 秒一行),表头对齐 iperf3 UDP 五列:Interval、Transfer、Bitrate、Jitter、Lost/Total Datagrams,行首 [SUM]。
| 端 |
Transfer / Bitrate |
Jitter / Lost/Total |
| Client |
本周期内 UL 发出 + DL 收到 的 payload 字节合计 |
仅针对 DL 入站(到达间隔相对理论节拍;丢包为期望 DL 包数 − 实收) |
| Server |
本周期内 UL 收到 + DL 发出 的 payload 字节合计 |
仅针对 UL 入站 |
- 与
merge 分桶无关:--sample-interval 只写入 config.json;周期表用 -i / -interval(time.ParseDuration),0 关闭。-interval 非空时覆盖 -i。
- 输出:表格 → stdout;
slog 诊断 → stderr(便于重定向与脚本解析)。
- 权威指标:延迟/丢包整场汇总仍以
pulse merge 与 CSV 为准;终端表便于运行时扫一眼。
示例(两终端对跑时各自 stdout 各有一套表;以下为示意):
[ ID] Interval Transfer Bitrate Jitter Lost/Total Datagrams
[SUM] 0.00- 1.00 sec 488 KBytes 4.00 Mbits/sec 0.050 ms 0/488 (0%)
📤 输出文件说明
每轮测试生成独立目录:{output_root}/{test_id}/
客户端生成
| 文件 |
说明 |
client.csv |
本地事件:SENT_UL(发上行)、RECV_DL(收下行) |
config.json |
一次 client 跑测的完整配置快照(供 merge 与归档;含 CLI 与 schedule 派生字段) |
服务端生成
| 文件 |
说明 |
server.csv |
远端事件:RECV_UL(收上行)、SENT_DL(发下行)时间戳已映射到客户端时轴 |
示例:client.csv
event,seq,timestamp_ns,size
SENT_UL,0,1710662904123456789,1024
RECV_DL,0,1710662904194326789,1024
示例:server.csv
event,seq,timestamp_ns,size
RECV_UL,0,1710662904155606789,1024
SENT_DL,0,1710662904190000000,1024
RECV_UL,1,,1024 # 空时间戳 = 丢包
示例:report.txt(HoloWAN 格式)
HoloWAN Recorder File (Pulse)
Test Name: "video_call_01"
Duration(sec): 30
Start Time: 2026-03-20 14:50:32
End Time: 2026-03-20 14:51:02
Interval(sec): 0.1
Packet Size(byte): 1024
Server Address: "192.168.1.100:5000"
UL Bandwidth: "1Mbps"
DL Bandwidth: "2Mbps"
Enable Reordering: True
Contents: Delay1(ms),Loss1(%),Bandwidth1(Mbps),Delay2(ms),Loss2(%),Bandwidth2(Mbps)
Switch: 1,1,1,1,1,1
------------------------------------------------
-0.952341,0.000000,0.999424,1.128756,0.081967,0.998605
...
Delay1/Loss1/Bandwidth1:上行(客户端 → 服务端)
Delay2/Loss2/Bandwidth2:下行(服务端 → 客户端)
🔬 指标计算逻辑(merge 阶段)
| 指标 |
计算方式 |
| Delay1(上行延迟) |
T_server.RECV_UL − T_client.SENT_UL |
| Delay2(下行延迟) |
T_client.RECV_DL − T_server.SENT_DL |
| Loss1(上行丢包率) |
(UL_SENT − UL_RECV) / UL_SENT |
| Loss2(下行丢包率) |
(DL_SENT − DL_RECV) / DL_SENT |
Goodput 整场(终端输出 / --o-json) |
(UL_RECV 或 DL_RECV 总包数 × size × 8) / (duration_sec × 1e6),duration_sec 与 size 来自 client.csv 同目录 config.json |
Goodput 分桶(report.txt 每行 Bandwidth*) |
上行:(该桶内 UL_RECV 数 × size × 8) / (sample_interval_sec × 1e6);下行同理用 DL_RECV;sample_interval_sec 来自 config.json |
所有延迟基于配对成功的包计算算术平均值;Goodput 与目标带宽无关。report.txt 表头 Start/End Time 为秒精度(YYYY-MM-DD HH:MM:SS),与常见 HoloWAN / playback 解析习惯一致。Test Name: 对应 config.json 的 test_id(或与目录名兜底一致),与 Duration(sec):、Interval(sec):、Packet Size(byte): 同为「词首大写」风格;常见 HoloWAN 解析会将 Test Name 规范为 test_name。
⚙️ 命令行参数详解
pulse server
| 参数 |
必选 |
默认值 |
说明 |
-p |
✅ |
— |
监听端口 |
-o |
❌ |
results |
输出根目录 |
-i |
❌ |
1s |
周期表间隔,0 关闭(同 -interval) |
-interval |
❌ |
(空) |
非空时覆盖 -i |
pulse client
| 参数 |
必选 |
默认值 |
说明 |
-s |
✅ |
— |
服务端地址 IP:PORT |
-size |
❌ |
512 |
payload 字节数(不含头部) |
--ul-bandwidth |
❌ |
1Mbps |
上行目标带宽 |
--dl-bandwidth |
❌ |
2Mbps |
下行目标带宽 |
-duration |
❌ |
10s |
测试时长 |
--sample-interval |
❌ |
100ms |
分桶采样间隔(仅写入 config.json,供 merge) |
-i |
❌ |
1s |
周期表间隔,0 关闭(同 -interval) |
-interval |
❌ |
(空) |
非空时覆盖 -i |
--time-delta |
❌ |
0 |
客户端相对服务端的时钟偏移(纳秒) |
--test-id |
❌ |
自动生成 |
测试标识符 |
-o |
❌ |
results |
输出根目录 |
带宽单位:支持 bps、Kbps、Mbps(不区分大小写),简写 k/m 等价于 Kbps/Mbps;纯数字视为 bps。
pulse merge
| 参数 |
必选 |
说明 |
--client |
✅ |
客户端 CSV 路径 |
--server |
✅ |
服务端 CSV 路径 |
--o-txt |
❌ |
报告文本输出路径(默认同目录 report.txt) |
--o-json |
❌ |
汇总 JSON(schema: pulse.merge.summary.v2):config、uplink、downlink、out_of_bucket、note_cn,见上文示例 |
pulse version
输出一行:pulse vX.Y.Z(与 Git tag 和构建注入一致)
📡 协议与内部机制
首包(START 消息)
客户端发送 JSON 首包启动测试:
{
"cmd": "start",
"test_id": "video_call_01",
"dl_size": 1024,
"dl_interval_ns": 1639344,
"ul_interval_ns": 8196721,
"duration_s": 30,
"time_delta_ns": 123456789,
"ul_packet_count": 3660,
"dl_packet_count": 18300
}
后续数据包
采用紧凑二进制格式(含魔数、序列号、方向、payload),详见 docs/design-pulse.md。
🧮 带宽 → 整数包率(pps)算法
给定目标带宽 B(bps)和 payload 大小 S(字节):
| 步骤 |
含义 |
公式(与实现一致,纯文本可读) |
| 1 |
理论包率(浮点) |
pps_theoretical = B / (8 * S) |
| 2 |
实际包率(四舍五入为整数) |
pps = round(pps_theoretical) |
| 3 |
实际可达带宽(bps) |
B_actual = pps * 8 * S |
| 4 |
发包间隔(纳秒) |
interval_ns = 10^9 / pps(Go 中为整数除法,见 internal/schedule) |
⚠️ 若 pps = 0(如带宽过低),客户端将报错退出。
🛠️ 开发与测试
# 运行全部测试
go test ./...
# 仅运行单元测试(跳过集成)
go test -short ./...
- 可选集成数据:
TestDouyinPCAPPlanReplayMerge(根包)读取 testdata/douyin.pcap;未放置该文件时测试会 自动 Skip,不影响 go test ./...。
- Git LFS:
testdata/*.pcap 在 .gitattributes 中配置为 LFS(体积较大)。克隆后若需跑该集成测试,请安装 Git LFS 并在本仓库执行 git lfs install 与 git lfs pull,或将自有抓包放到 testdata/douyin.pcap。
📦 依赖
- 零第三方依赖:仅使用 Go 标准库(
net, flag, encoding/csv, log/slog, time 等)
📜 License
木兰宽松许可证第二版(Mulan Permissive Software License, Version 2)
© 2024–2026 sangachy
详见 LICENSE。
再分发:须向接收方提供许可证全文副本(通常随包保留根目录 LICENSE),并保留源码中的版权与免责声明。各 .go 源文件头部已含 Mulan PSL v2 建议声明(英文)。
Pulse — No symmetry assumptions. No hidden defaults. Just precise, mergeable events.