v2中文文档
项目

Caddy性能剖析

**程序剖析(profile)**是程序运行时资源使用情况的快照。它对定位问题区域、排查bug/崩溃、优化代码非常有帮助。

Caddy使用Go自带的剖析工具pprof采集profile。

profile可以反映CPU与内存消耗、goroutine栈信息,也有助于定位死锁或高竞争同步点。

当你向Caddy报告某些问题时,我们可能会要求提供profile。本文说明如何在Caddy中采集profile,以及如何大致阅读pprof输出。

开始前先知道两点:

  1. **Caddy profile不是安全敏感信息。**它包含技术统计信息,不包含内存内容,不会赋予系统访问权限,可安全分享。
  2. **profile开销较轻,可在生产环境采集。**很多用户都这么做,这是推荐实践。

获取profile

profile可通过管理接口/debug/pprof/访问。在运行Caddy的机器上打开:

http://localhost:2019/debug/pprof/

你会看到一个简单列表,例如allocsgoroutineheapprofile等。

这些计数可用于快速识别泄漏:持续刷新页面,如果某个计数一直上涨,就可能有泄漏。例如heap增长可能是内存泄漏,goroutine增长可能是协程泄漏。

常用的profile是:

  • goroutine:函数栈
  • heap:内存
  • profile:CPU

其余profile可用于分析锁竞争、阻塞、死锁等问题。

下载profile

pprof索引页里直接点击链接通常是文本格式(便于快速阅读);但二进制才是默认格式。索引页多数链接会附带?debug=把它变成文本,CPU profile(profile)没有文本表示。

常见查询参数(见Go文档):

  • debug=N(除CPU外):N=0返回二进制(默认);N>0返回纯文本
  • gc=N(heap):N>0表示采样前先执行一次GC
  • seconds=N(allocs/block/goroutine/heap/mutex/threadcreate):返回差分profile
  • seconds=N(cpu/trace):采样持续N秒

这些都是HTTP端点,所以你也可以用curl/wget下载。

下载后可上传到GitHub issue评论,或用pprof.me在线查看。CPU profile也可用flamegraph.com

远程访问

如果你已能本地访问管理API,可跳过本节。

默认管理API只监听回环地址。你可用以下方式远程访问/debug/pprof

通过站点反向代理

reverse_proxy /debug/pprof/* localhost:2019 {
	header_up Host {upstream_hostport}
}

这会把profile暴露给可访问站点的人。若不希望公开,请增加认证模块。

(注意必须加/debug/pprof/*匹配器,否则会代理整个管理API。)

SSH隧道

在本机执行:

ssh -N username@example.com -L 8123:localhost:2019

它会把你本地localhost:8123转发到远端example.com上的localhost:2019

然后在另一个终端请求:

curl -v http://localhost:8123/debug/pprof/ -H "Host: localhost:2019"

如两端都使用2019端口(且本地未占用),可省略Host头。

如果想后台长期运行隧道:

ssh -f -N -M -S /tmp/caddy-tunnel.sock username@example.com -L 8123:localhost:2019

关闭隧道:

ssh -S /tmp/caddy-tunnel.sock -O exit e

远程管理API

你也可以把管理API配置为允许授权客户端远程访问。(待补充专门文章。)

goroutine profile

goroutine dump用于查看当前有哪些goroutine及其调用栈,即哪些代码正在执行或阻塞等待。

访问/debug/pprof/goroutine?debug=1会看到按调用栈聚合的输出。首行例如:

goroutine profile: total 88

含义是当前总共有88个goroutine。

后续goroutine条目格式大致为:

<count> @ <addresses...>
  • <count>:拥有相同调用栈的goroutine数量
  • @后地址:函数调用地址(调用帧)

#开头的行是给读者看的注释,显示当前栈轨迹,格式为:

<address> <package/func>+<offset> <filename>:<line>

完整goroutine dump

使用?debug=2可获取完整dump:每个goroutine都展开,不合并同栈条目,输出会很大。

完整dump中每个goroutine首行还会提供:

  • goroutine编号
  • 状态(如runningIO waitselectchan receivesemacquiresyscall等)
  • 存活时长

这些信息对于排查goroutine泄漏、连接堆积等问题很有价值。

内存profile

内存(heap)profile追踪堆分配,是系统内存消耗的核心来源之一。频繁分配也是常见性能问题来源。

heap输出与goroutine输出类似,首行格式一般是:

<live objects> <live memory> [<allocations>: <allocation memory>] @ <addresses...>

通过调用栈你通常可以判断是哪条热点路径产生了大量分配,以及是否有对象池在生效。

CPU profile

CPU profile用于分析Go程序把CPU时间花在了哪里。

CPU profile没有纯文本格式。下载方式:

/debug/pprof/profile?seconds=N

采样N秒后会得到二进制文件(常见文件名为profile)。采样期间可能有轻微性能影响(其他profile影响通常很小)。

go tool pprof

使用Go内置分析器读取profile(不仅限CPU):

go tool pprof profile

进入交互模式后,常用命令:

  • top:查看最耗资源的函数
  • web:浏览器打开调用图
  • svg:生成调用图SVG
  • tree:表格形式查看调用树

例如:

(pprof) top

你可进一步过滤,例如忽略runtime查看业务代码热点:

(pprof) top -runtime

可视化

执行svgweb可得到图形化视图:

CPU profile visualization

如何阅读图请参考pprof文档

对比profile(Diff)

改完代码后,可用前后profile做差分分析:

go tool pprof -diff_base=before.prof after.prof

在交互里再执行top可看到增减最明显的函数。

差分同样可视化:

CPU profile visualization

这有助于直观看到改动对性能的影响。

延伸阅读