logo
logo

DeepFlow Wasm Plugin 性能调优实战

向阳,易德贤 2023-12-27

在之前的文章使用 DeepFlow Wasm 插件实现业务可观测性中,我们介绍了 DeepFlow 中的二次开发利器 —— Wasm Plugin。利用插件,我们可以实现很多个性化的应用协议解析目标,例如:

  • 提取 HTTP Payload 中的错误码,并重写调用日志中的 response_coderesponse_result 等字段
  • 提取 Payload 中的交易流水号、用户 ID 等业务信息,用于增强调用日志和分布式追踪
  • 解析使用 Protobuf、Thrift 等协议序列化的 Payload 信息
  • 解析私有协议,生成调用日志

原生的 DeepFlow Agent 有着极高的应用协议解析性能,而 Wasm 程序通常也因其高性能著称。考虑到许多可观测性领域的工程师更熟悉 Golang,DeepFlow 特意提供了编写插件的 Golang SDK。在性能压测的过程中,我们发现针对插件代码的 GC 行为进行优化能有显著的成效,本文将优化思路分享给大家,帮助大家编写高性能的 Wasm Plugin。

0x0: 资源消耗摸底

我们使用这个 GitHub 仓库中的代码构造压测流量。压测程序会产生指定 TPS 速率的 HTTP 调用。我们将 client 和 server 运行在同一个虚拟机上,并在虚拟机上运行 deepflow-agent。在此基础上,我们向 deepflow-agent 下发一个 Wasm Plugin,它实现了解析 HTTP Json Payload 并提取错误码的功能,插件的代码在这个 GitHub 仓库里。

使用如下命令编译 Wasm Plugin:

1
2
3
4
5
6
7
8
# 建议 go 版本不低于 1.21,tinygo 版本不低于 0.29
tinygo build -o wasm.wasm \
-target wasi \
-gc=precise \
-panic=trap \
-scheduler=none \
-no-debug \
*.go

压力测试结果:

TPS 是否开启 eBPF 是否加载 Wasm 插件 deepflow-agent CPU deepflow-agent Memory
1.2K 43.5% 112MB
2.5K 97.6% 112MB
1.2K 97.8% 171MB
1.2K 0.71% 102MB
2.5K 1.31% 102MB
1.2K 3.54% 144MB

从前三行可以看到:当关闭 eBPF 时,deepflow-agent 若从 loopback 网卡上采集 1.2K TPS 的 HTTP 请求,此时消耗的 CPU 为单核的 43.5%;当开启 eBPF 时,压测程序生成的 1.2K TPS 会被 deepflow-agent 采集到三遍,loopback 网卡、客户端进程、服务端进程,此时 deepflow-agent 的 CPU 消耗为 97.8%。

从后三行可以看到,同样的 HTTP TPS 速率下,不加载 Wasm 插件时 deepflow-agent 的 CPU 消耗连 1/10 都不到,这说明插件造成了 deepflow-agent CPU 开销的显著增长。

0x1: 优化 TinyGo GC

调整 Wasm Plugin 的编译参数后我们发现,-gc 参数对资源消耗造成了显著的影响。我们找到了 nottinygc 库,希望能降低 GC 对资源开销的影响。

使用 nottinygc 需要做两个改造,首先在插件代码中需要进行 import:

1
import _ "github.com/wasilibs/nottinygc"

其次在编译插件时需要增加 -gc=custom-tags=custommalloc 参数:

1
2
3
4
5
6
7
8
tinygo build -o wasm.wasm \
-target wasi \
-panic=trap \
-scheduler=none \
-gc=custom \
-tags=custommalloc \
-no-debug \
*.go

上述编译参数解释如下:

  • -panic=trap 必须加,表示插件遇到 panic 不会传递到上层,否则插件 panic 会导致 deepflow-agent panic。
  • -scheduler=none 使用 nottinygc 必须加, 否则不能加载。即使不使用 nottinygc 也建议加上。
  • -gc 表示要使用的 gc,目前只有 nottinygc 和 precise 能避免内存泄漏,但是 precise 性能代价巨大。
  • -no-debug 去掉 debug 信息,减少 wasm 文件大小

除此之外,还有一个 llvm 的编译参数 -llvm-features "+bulk-memory",理论上能提高性能,但实际测试差别不大,感兴趣的同学也可尝试看看对你的场景是否有提升帮助。

在使用 nottinygc 的过程中,我们还发现了一个 divide by zero 的 Bug,提交给了作者。上个月这个 Bug 已经被修复了,请注意使用正确的版本。

0x2: 使用 nottinygc 之后的压测结果

使用 nottinygc 之后我们重复了压测,结果如下:

TPS 是否开启 eBPF 是否加载 Wasm 插件 deepflow-agent CPU deepflow-agent Memory
1.2K 7.83% 110MB
2.5K 17.6% 113MB
1.2K 27.5% 162MB

对比之前的测试结果,我们发现 nottinygc 能将 deepflow-agent 的 CPU 消耗从 43.5%、97.6%、97.8% 分别降低到 7.83%、17.6%、27.5%,效果显著。

0x3: 使用原生 Golang 编写插件

Tinygo 在编写插件的过程中有一些库的使用局限,相信大家一定希望可以使用原生 Golang 编写插件。今年 9 月分发布的 Golang v1.21 中第一次支持了 WASI。但是,目前这个特性还只是 Preview 版本,期待社区小伙伴的尝鲜使用报告,也让我们静候 Golang 官方对 WASI 的正式支持。

0x4: 什么是 DeepFlow

DeepFlow 是云杉网络开发的一款可观测性产品,旨在为复杂的云基础设施及云原生应用提供深度可观测性。DeepFlow 基于 eBPF 实现了应用性能指标、分布式追踪、持续性能剖析等观测信号的零侵扰Zero Code)采集,并结合智能标签SmartEncoding)技术实现了所有观测信号的全栈Full Stack)关联和高效存取。使用 DeepFlow,可以让云原生应用自动具有深度可观测性,从而消除开发者不断插桩的沉重负担,并为 DevOps/SRE 团队提供从代码到基础设施的监控及诊断能力。

GitHub 地址:https://github.com/deepflowio/deepflow

访问 DeepFlow Demo,体验零插桩、全覆盖、全关联的可观测性。