mirror of
https://github.com/EasyTier/EasyTier.git
synced 2026-08-07 13:09:46 +00:00
427 lines
19 KiB
Markdown
427 lines
19 KiB
Markdown
# hotpath CPU 热点分析与发包链路优化
|
||
|
||
## 概述
|
||
|
||
本文档记录了使用 hotpath + samply 对 easytier-core 发包链路进行 CPU 热点分析的全过程,包括工具链搭建、热点定位、优化实施、踩坑记录和最终 benchmark 结果。
|
||
|
||
## 最终 benchmark 数据
|
||
|
||
### 真实性能对比(不带 hotpath,3 runs average)
|
||
|
||
origin/main baseline 使用 `git worktree` 从 origin/main 构建,仅添加 bench example +
|
||
loopback bind fix(TCP/UDP convergence 需要)。无任何优化代码。
|
||
|
||
| Tunnel | origin/main baseline | 优化后 | **提升** | 带宽(优化后) |
|
||
| -------- | -------------------- | -------------- | --------- | -------------- |
|
||
| **Ring** | 293K pps / 3.3 Gbps | **1,124K pps** | **+284%** | 12.6 Gbps |
|
||
| **TCP** | 298K pps / 3.3 Gbps | **975K pps** | **+227%** | 10.9 Gbps |
|
||
| **UDP** | 630K pps / 7.1 Gbps | **1,066K pps** | **+69%** | 11.9 Gbps |
|
||
|
||
UDP baseline 本身较高(630K vs 293K/298K),因为 UDP tunnel 的 forward_from_ring_to_udp
|
||
独立 task 提供了天然的 pipeline overlap,部分隐藏了 channel 开销。
|
||
|
||
### 带 hotpath profiling(timing 可见,但有 observer effect)
|
||
|
||
| Tunnel | 原始 pps | 优化后 pps | 提升 | MpscTunnelSender::send |
|
||
| ------ | -------- | ---------- | ----- | ---------------------- |
|
||
| Ring | 234K | 478K | +104% | 138ns (原 2.23µs) |
|
||
| UDP | N/A | 440K | — | 294ns |
|
||
| TCP | N/A | 453K | — | 378ns |
|
||
|
||
### hotpath observer effect
|
||
|
||
**hotpath 测量基础设施引入了 ~54-57% 的性能开销:**
|
||
|
||
| Tunnel | 不带 hotpath | 带 hotpath | hotpath 开销 |
|
||
| ------ | ------------ | ---------- | ------------ |
|
||
| Ring | 1,124K pps | 478K pps | **-57%** |
|
||
| TCP | 975K pps | 453K pps | **-54%** |
|
||
|
||
**含义:**
|
||
|
||
- timing 数据里的 `send_msg_by_ip: 2.15µs` 是膨胀值,真实成本 ~0.9µs
|
||
- 所有 timing 数据需要按 ~2.3x 校准才能反映真实开销
|
||
- hotpath 适用于相对比较(优化前 vs 后),不适用于绝对性能评估
|
||
- 生产环境部署不应用 hotpath feature 编译
|
||
|
||
测试条件:4 threads, 1400B packets, 10s, 宿主机直跑。
|
||
|
||
---
|
||
|
||
## 工具链搭建
|
||
|
||
### hotpath + samply 安装
|
||
|
||
```bash
|
||
# hotpath TUI (console)
|
||
cargo install hotpath --version 0.18.0 --bin hotpath --features tui
|
||
|
||
# hotpath-samply (samply wrapper,autospawn 依赖)
|
||
cargo install hotpath --version 0.18.0 --bin hotpath-samply
|
||
|
||
# samply 本体(hotpath-samply 内部 spawn samply record)
|
||
cargo install samply
|
||
```
|
||
|
||
### 内核参数
|
||
|
||
samply 需要 perf_event 开销,需要调整内核参数:
|
||
|
||
```bash
|
||
echo '1' | sudo tee /proc/sys/kernel/perf_event_paranoid
|
||
echo '65536' | sudo tee /proc/sys/kernel/perf_event_mlock_kb
|
||
```
|
||
|
||
- `perf_event_paranoid` 默认 2(不允许非 root 采样),需降到 1。
|
||
- `perf_event_mlock_kb` 默认 516 KB,32 核机器上 samply 的 mmap buffer 总量超限,需增大到 65536。
|
||
|
||
### hotpath profile 编译
|
||
|
||
```toml
|
||
# Cargo.toml
|
||
[profile.hotpath]
|
||
inherits = "release"
|
||
strip = false
|
||
debug = "line-tables-only"
|
||
```
|
||
|
||
samply 需要 debug symbols 且不能 strip。release profile 默认 `strip = true`,必须用单独的 profile。
|
||
|
||
### Docker 隔离环境(可选,TCP/UDP bench)
|
||
|
||
修复 loopback bind 地址后(见坑 11),TCP/UDP bench 可以直接在宿主机上跑,不需要 Docker:
|
||
|
||
```bash
|
||
# Ring(进程内,无需隔离)
|
||
HOTPATH_TUNNEL=ring ./target/hotpath/examples/cpu_hotspot_ring
|
||
|
||
# TCP/UDP(修复后也支持宿主机直跑)
|
||
HOTPATH_TUNNEL=tcp ./target/hotpath/examples/cpu_hotspot_ring
|
||
```
|
||
|
||
如果仍有 convergence 问题(多网卡环境),用 Docker 提供独立 netns:
|
||
|
||
```bash
|
||
docker run --rm \
|
||
-v "$(pwd)/target/hotpath/examples/cpu_hotspot_ring:/bench:ro" \
|
||
-e HOTPATH_TUNNEL=tcp \
|
||
-e HOTPATH_BENCH_SECS=10 \
|
||
fedora:latest \
|
||
/bench
|
||
```
|
||
|
||
Docker 镜像需要匹配宿主机的 glibc 版本。Fedora 宿主用 `fedora:latest`。
|
||
|
||
---
|
||
|
||
## 踩坑记录
|
||
|
||
### 坑 1:samply 报 "failed to spawn samply: No such file or directory"
|
||
|
||
**现象**:hotpath CPU report 显示 `failed to spawn samply: No such file or directory (os error 2)`
|
||
|
||
**原因**:hotpath-samply 只是 wrapper,它内部 spawn `samply record --pid <pid>` 来采集 CPU 样本。samply 本体没装。
|
||
|
||
**解决**:
|
||
|
||
```bash
|
||
cargo install samply
|
||
```
|
||
|
||
如果 autospawn 找不到 hotpath-samply 本身,用环境变量指定完整路径:
|
||
|
||
```bash
|
||
export HOTPATH_SAMPLY_WRAPPER_BIN=~/.cargo/bin/hotpath-samply
|
||
```
|
||
|
||
### 坑 2:samply 报 "Failed to start profiling: mmap failed"
|
||
|
||
**现象**:samply 启动后立即报 mmap 失败。
|
||
|
||
**原因**:`perf_event_mlock_kb` 默认只有 516 KB。32 核机器上 samply 为每个 CPU core 创建 mmap buffer,总 mmap 量超过限制。
|
||
|
||
**解决**:
|
||
|
||
```bash
|
||
echo '65536' | sudo tee /proc/sys/kernel/perf_event_mlock_kb
|
||
```
|
||
|
||
### 坑 3:samply 报 "samply exited with status exit status: 1"
|
||
|
||
**现象**:samply 被 spawn 了但 exit 1。
|
||
|
||
**原因**:同坑 2——`perf_event_paranoid = 2` 时非 root 用户无法使用 perf_event_open。
|
||
|
||
**解决**:
|
||
|
||
```bash
|
||
echo '1' | sudo tee /proc/sys/kernel/perf_event_paranoid
|
||
```
|
||
|
||
### 坑 4:火焰图全是地址,看不到符号
|
||
|
||
**现象**:samply profile 打开后火焰图全是 `0x31dd24` 之类的地址。
|
||
|
||
**原因**:samply profile 里存储的是地址(不内联符号化)。符号化在查看时通过 symbol server 动态完成。如果直接下载 raw JSON 上传到 profiler.firefox.com,符号 server 无法访问本地二进制文件。
|
||
|
||
**解决**:必须用 `samply load` 本地打开(它启动 symbol server 自动做符号化):
|
||
|
||
```bash
|
||
samply load /tmp/hotpath/<session>/hp.json.gz
|
||
```
|
||
|
||
不要下载 JSON 再上传到 profiler.firefox.com。
|
||
|
||
### 坑 5:samply 符号化后 `_dl_mcount_wrapper` 占 18.1%
|
||
|
||
**现象**:send_msg_internal inclusive 分析显示 `_dl_mcount_wrapper` 占 18.1% CPU。
|
||
|
||
**原因**:nm 的动态符号表里 `_dl_mcount_wrapper`(0x1498d0)到下一个符号(0x1b3e9e)之间有 **425 KB gap**。nm 的 bisect 查找把 gap 内所有地址错误归因到 `_dl_mcount_wrapper`。gap 里实际是 AVX2 优化的 memmove/memcmp/memset 等函数。
|
||
|
||
**解决**:用 addr2line 精确解析(而非 nm bisect)。实际开销是 memmove 1.67% + memcmp 0.20% + memset 0.19% = 2.1%,不是 18%。**没有 profiling 钩子**。
|
||
|
||
### 坑 6:parking_lot::MutexGuard 不是 Send
|
||
|
||
**现象**:使用 `parking_lot::Mutex` 替代 `tokio::sync::Mutex` 后,编译报 31 个 "future cannot be sent between threads safely"。
|
||
|
||
**原因**:`parking_lot::MutexGuard` 刻意不实现 `Send`——锁必须在获取它的同一个线程上释放。在 async fn 里 guard 跨 await 点会导致 Future 不是 Send,tokio multi_thread runtime 拒绝 spawn。
|
||
|
||
**解决**:自定义 `SpinSink`(AtomicBool spinlock),`SpinGuard` 只持有 `&SpinSink` 引用(SpinSink: Sync via unsafe impl),是 Send。
|
||
|
||
### 坑 7:std::sync::MutexGuard 也不是 Send(在某些配置下)
|
||
|
||
**现象**:`std::sync::Mutex` 同样报 "future cannot be sent between threads safely"。
|
||
|
||
**原因**:Rust 标准库的 `MutexGuard` 的 Send 实现依赖于内部类型。`Pin<Box<dyn ZCPacketSink>>` 包含 trait object,某些配置下 guard 不是 Send。
|
||
|
||
**解决**:用自定义 SpinSink 绕过所有标准 Mutex 实现。
|
||
|
||
### 坑 8:direct sink path 没有性能提升
|
||
|
||
**现象**:去掉 channel 中转(MpscTunnelSender 直接持有 sink),从 3 个 await 点(lock + feed + flush)改为 try_lock + poll_fn 合并。MpscTunnelSender::send 仍然 ~2µs。
|
||
|
||
**原因**:瓶颈不在 lock 或 channel,而在 **async fn Future 状态机的固有开销**。每次 `.await` 创建一个 Future struct、poll 它、drop 它。即使 poll 立即返回 Ready,整个 async machinery 开销 ~2µs。RingSink 实际操作只有 ~40ns(2%)。
|
||
|
||
**解决**:用 `noop_waker()` 在 async fn 内部同步调用 Sink trait 方法(poll_ready + start_send + poll_flush)。async fn 在第一次 poll 就同步完成返回——绕过所有 async 调度开销。开销从 2µs 降到 ~140ns。
|
||
|
||
### 坑 9:sync send 破坏了 TCP/UDP tunnel
|
||
|
||
**现象**:把 `send` 从 `async fn` 改为 sync `fn` 后,所有 TCP/UDP 相关测试失败(452 个失败)。
|
||
|
||
**原因**:TCP/UDP tunnel 用 channel mode(`MpscTunnel::new`)。sync `send` 的 channel path 只做 `try_send`,channel 满时返回 `BufferFull`(丢包),而不是 `send().await`(等待背压)。丢包导致 TCP/UDP 连接握手失败。
|
||
|
||
**解决**:保持 `send` 为 async fn。direct path(ring/UDP/TCP)内部用 noop_waker 同步完成(不 yield)。channel path 仍然走 async `send_async().await`。async fn wrapper 对 direct path 只有 ~100ns 开销(Future struct 创建 + 单次 poll),因为不 yield。
|
||
|
||
### 坑 10:poll_flush Pending 返回 Shutdown 导致连接断开
|
||
|
||
**现象**:noop_waker 模式下,TCP tunnel 的 `poll_flush` 可能返回 Pending(TCP 写缓冲区满)。返回 `Err(Shutdown)` 导致 PeerConn 认为连接断开。
|
||
|
||
**原因**:TCP 的 `FramedWriter::poll_flush` 做实际 socket write(系统调用)。socket 缓冲区满时返回 Pending。数据已经在 BufList 里,不需要 panic。
|
||
|
||
**解决**:poll_flush Pending 时返回 `Ok(())`。数据已在 buffer(ring buffer 或 BufList),后续操作会消费它。Pending 只意味着 "还没 flush 到网络",不是 "错误"。
|
||
|
||
### 坑 11:TCP/UDP bench convergence 失败
|
||
|
||
**现象**:TCP/UDP tunnel 的 bench 中,两个实例无法建立连接(routes did not converge within 15s)。
|
||
|
||
**原因**:`set_bind_addr_for_peer_connector`(connector/mod.rs:70-77)收集所有本机 IP 作为 TCP bind 地址,但不包含 `127.0.0.1`。connector 绑定到 `172.17.0.2`(Docker eth0)后连接 `127.0.0.1` 路由不通 → 2 秒超时。
|
||
|
||
**解决**:在 bind 地址列表头部加入 `127.0.0.1:0`。connector 遍历所有 bind 地址,loopback 先被尝试,localhost 连接成功。
|
||
|
||
### 坑 12:ShardedCounter (#2385) 在高频路径引入回退
|
||
|
||
**现象**:cherry-pick PR #2385(ShardedCounter 替代 UnsafeCell)后,pps 下降 17%(246K → 203K)。
|
||
|
||
**原因**:ShardedCounter 的 TLS 分片设计优化多线程 contention,但每包调用 16 次 `ShardedCounter::add`(TLS load + store),单次 ~14ns,总 224ns/包。比原来的 `UnsafeCell`(~2ns/次)高 6 倍。每包 16 次的调用频率让 TLS 开销累积。
|
||
|
||
**教训**:TLS 分片策略适合 **低频高并发** 场景,不适合 **高频单线程** 的发包热路径。
|
||
|
||
### 坑 13:ZCPacket pool 不如 glibc tcache
|
||
|
||
**现象**:用 `crossbeam_queue::ArrayQueue` 做 BytesMut 对象池,每包从池取/归还。性能没有提升(甚至 -15%)。
|
||
|
||
**原因**:glibc malloc 对 ~1500 bytes 小块分配有 thread-local cache(tcache),单次 alloc ~10-15ns。ArrayQueue 的 pop/push 是 CAS 操作(~20-40ns),比 tcache 更慢。pool 还多了 capacity 检查和 clear 操作。
|
||
|
||
**教训**:手动对象池在现代 glibc tcache 面前没有优势。真正需要 pool 的场景是避免 munmap(大块 >128KB 分配),不是小块。
|
||
|
||
### 坑 14:Pipeline (FuturesUnordered) 效果微小
|
||
|
||
**现象**:用 FuturesUnordered 让多个 send_msg_by_ip 并发(pipeline_depth=4),pps 只提升 1.6%。
|
||
|
||
**原因**:try_send fast path 让 MpscTunnelSender::send 立即返回(不 await)。多个 send_msg_by_ip 之间没有自然的时间重叠——它们在 CPU 上是串行的。pipeline 需要利用 await 等待时间,但 fast path 消除了 await。
|
||
|
||
### 坑 15:hotpath 测量引入 54% observer effect
|
||
|
||
**现象**:同一 binary 带 hotpath feature 和不带 hotpath feature 跑 bench,pps 差距巨大。
|
||
|
||
**数据**:
|
||
|
||
| Tunnel | 不带 hotpath | 带 hotpath | hotpath 开销 |
|
||
| ------ | ------------ | ---------- | ------------ |
|
||
| Ring | 1,124K pps | 478K pps | **-57%** |
|
||
| TCP | 975K pps | 453K pps | **-54%** |
|
||
|
||
**原因**:hotpath `#[measure]` / `#[measure_all]` 在每个标注的 async fn 上包装 Future struct,每次 poll 记录开始/结束时间(quanta::Instant ~5ns × 2)、更新统计(atomic 操作)。measure_all 覆盖的 impl 块内所有方法都被插桩。当有 ~30 个 measure 点在发包热路径上时,累计开销超过 50%。
|
||
|
||
**教训**:
|
||
|
||
- hotpath timing 数据**适用于相对比较**(优化前 vs 后),**不适用于绝对性能评估**
|
||
- 生产环境**不应**用 hotpath feature 编译
|
||
- 要获取真实 pps,编译不带 `--features hotpath` 的版本
|
||
- timing 数据按 ~2.3x 校准可近似真实开销
|
||
|
||
---
|
||
|
||
## 优化实施记录
|
||
|
||
### 真实提升(不带 hotpath,origin/main baseline 对比)
|
||
|
||
baseline 构建:`git worktree` 从 origin/main,仅添加 bench example + loopback bind fix。
|
||
|
||
| Tunnel | baseline | 优化后 | 提升 |
|
||
| ------ | -------- | -------------- | --------- |
|
||
| Ring | 293K pps | **1,124K pps** | **+284%** |
|
||
| TCP | 298K pps | **975K pps** | **+227%** |
|
||
| UDP | 630K pps | **1,066K pps** | **+69%** |
|
||
|
||
### 有效优化(按贡献排序)
|
||
|
||
| 优化 | 带 hotpath pps 变化 | 真实提升来源 | 机制 |
|
||
| ------------------------ | ------------------- | ------------------------ | ---------------------------------------------------------- |
|
||
| **noop_waker sync send** | **+90%** | **核心突破** | RingSink/FramedWriter 直接 sync poll,绕过 async machinery |
|
||
| try_send fast path | +7% | 次要 | 跳过 tokio mpsc semaphore |
|
||
| #2385 ZCPacket safe init | +5% (TCP) | TCP 专属 | copy_nonoverlapping 无 aliasing 检查 |
|
||
| metrics batch + sync | +1.6% | 小幅 | batch CounterHandle + sync fast path |
|
||
| #2381 advance (零拷贝) | ~0% | 代码质量 | Buf::advance 消除 split_off Arc churn |
|
||
| channel 32→1024 | ~0% | 减少 fallback | 更大 buffer |
|
||
| 接收侧 try_recv | ~0% (单向) | 双向有价值 | 消除 recv().await async overhead |
|
||
| loopback bind fix | — | TCP/UDP convergence 必需 | 127.0.0.1 加入 bind 地址列表 |
|
||
|
||
### 验证无效并回退
|
||
|
||
| 尝试 | 结果 | 原因 |
|
||
| ---------------------------------- | -------- | ----------------------------------- |
|
||
| ShardedCounter (#2385) | -17% pps | TLS 分片高频开销 > UnsafeCell |
|
||
| ZCPacket pool | -15% pps | glibc tcache 比 ArrayQueue CAS 更快 |
|
||
| Allocator 切换 (jemalloc/mimalloc) | ~0% | 小块分配 tcache 都已足够 |
|
||
| Pipeline (FuturesUnordered) | +1.6% | try_send 消除了 await 空隙 |
|
||
| dashmap 合并 | ~0% | contains_key 本身 ~50ns |
|
||
|
||
### noop_waker 技术详解
|
||
|
||
核心原理:async fn `send()` 内部用 `noop_waker()` 构造 dummy Context,直接调 Sink trait 的 `poll_ready` + `start_send` + `poll_flush`。RingSink 在 ring buffer 不满时所有操作立即返回 Ready——noop_waker 永远不会被触发。
|
||
|
||
```rust
|
||
pub async fn send(&self, item: ZCPacket) -> Result<(), TunnelError> {
|
||
if let Some(sink) = &self.direct_sink {
|
||
if let Some(mut guard) = sink.try_lock() {
|
||
let waker = futures::task::noop_waker();
|
||
let mut cx = std::task::Context::from_waker(&waker);
|
||
match guard.as_mut().poll_ready(&mut cx) {
|
||
Poll::Ready(Ok(())) => {
|
||
guard.as_mut().start_send(item)?;
|
||
match guard.as_mut().poll_flush(&mut cx) {
|
||
Poll::Ready(Err(e)) => return Err(e),
|
||
_ => return Ok(()), // Ready(Ok) 或 Pending 都返回 Ok
|
||
}
|
||
}
|
||
// ...
|
||
}
|
||
}
|
||
return Err(TunnelError::BufferFull);
|
||
}
|
||
// Channel mode: async with backpressure
|
||
self.send_async(item).await
|
||
}
|
||
```
|
||
|
||
**为什么 Pending 返回 Ok**:poll_flush Pending 意味着数据已在 buffer(ring buffer 或 BufList)但还没 flush 到网络。forward task 或下一次 send 会消费它。这是安全的——数据不丢、不乱序。
|
||
|
||
**适用范围**:所有 Sink 的 `start_send` 是同步内存操作的 tunnel:
|
||
|
||
- Ring tunnel: RingSink → ring buffer(内存)
|
||
- UDP tunnel: RingSink → ring buffer → forward_from_ring_to_udp task → socket
|
||
- TCP tunnel: FramedWriter → BufList(内存)→ poll_flush 时 write socket
|
||
|
||
---
|
||
|
||
## hotpath measure 布点
|
||
|
||
### 当前覆盖
|
||
|
||
```
|
||
send_msg_by_ip ✅ measure
|
||
├─ try_compress_and_encrypt ✅ measure
|
||
├─ get_msg_dst_peer_ipv4 ✅ measure
|
||
├─ run_nic_packet_process_pipeline ✅ measure
|
||
├─ send_msg_internal ✅ measure
|
||
│ ├─ PeerMap::send_msg_directly ✅ measure_all
|
||
│ ├─ PeerMap::get_peer_by_id ✅ measure_all
|
||
│ ├─ PeerMap::get_gateway_peer_id ✅ measure_all
|
||
│ ├─ PeerMap::has_peer ✅ measure_all
|
||
│ ├─ record_tx_fast ❌ (sync fn, 无 measure)
|
||
│ └─ Peer::send_msg ✅ measure
|
||
│ └─ PeerConn::send_msg ✅ measure
|
||
│ └─ MpscTunnelSender::send ✅ measure
|
||
├─ MpscTunnel::forward_one_round ✅ measure
|
||
│ ├─ RingSink::poll_ready ✅ measure_all
|
||
│ ├─ RingSink::start_send ✅ measure_all
|
||
│ └─ RingSink::poll_flush ✅ measure_all
|
||
└─ CidrSet::* ✅ measure_all
|
||
```
|
||
|
||
### 布点排除项(避免与已有 PR 冲突)
|
||
|
||
| 文件 | 排除原因 |
|
||
| ---------------------- | -------------------- |
|
||
| stats_manager.rs | PR #2385 重写中 |
|
||
| traffic_metrics.rs | 依赖 stats_manager |
|
||
| peer_manager.rs (部分) | advisor/001-002 改动 |
|
||
| peer_conn.rs (部分) | advisor/001-002 改动 |
|
||
| tunnel/mpsc.rs (部分) | perf/001 改动 |
|
||
| packet_def.rs | perf/001-003 改动 |
|
||
| peer_ospf_route.rs | advisor/003-004 改动 |
|
||
|
||
---
|
||
|
||
## 运行方式
|
||
|
||
### Ring tunnel bench
|
||
|
||
```bash
|
||
cargo build --profile hotpath --features hotpath --example cpu_hotspot_ring
|
||
HOTPATH_BENCH_SECS=15 ./target/hotpath/examples/cpu_hotspot_ring
|
||
```
|
||
|
||
### TCP/UDP bench(需要 Docker 隔离)
|
||
|
||
```bash
|
||
docker run --rm \
|
||
-v "$(pwd)/target/hotpath/examples/cpu_hotspot_ring:/bench:ro" \
|
||
-e HOTPATH_TUNNEL=tcp \
|
||
-e HOTPATH_BENCH_SECS=10 \
|
||
fedora:latest \
|
||
/bench
|
||
```
|
||
|
||
### 带 samply CPU profiling
|
||
|
||
```bash
|
||
export PATH=$HOME/.cargo/bin:$PATH
|
||
cargo run --profile hotpath --features hotpath,hotpath-cpu --example cpu_hotspot_ring
|
||
|
||
# 另一终端查看 CPU top
|
||
hotpath console
|
||
```
|
||
|
||
### 环境变量
|
||
|
||
| 变量 | 默认 | 说明 |
|
||
| ---------------------------- | ---- | ----------------------- |
|
||
| `HOTPATH_BENCH_SECS` | 30 | 打流持续秒数 |
|
||
| `HOTPATH_PKT_SIZE` | 1400 | 包大小 |
|
||
| `HOTPATH_TUNNEL` | ring | ring / udp / tcp |
|
||
| `HOTPATH_PIPELINE` | 1 | pipeline 深度 |
|
||
| `HOTPATH_SAMPLY_WRAPPER_BIN` | — | hotpath-samply 完整路径 |
|
||
| `HOTPATH_SAMPLY_BIN` | — | samply 本体完整路径 |
|