针对单个 HTTP 请求可以做 Profiling 吗?怎么知道这次请求的 CPU 和内存情况?

185 天前
 RedisMasterNode

Trace 可以反映一次请求的实际花费时间,但是包含了所有的 IO 阻塞等等.

Profiling (pprof) 可以分析运行中的程序的 CPU 使用时间,例如采集 30 秒内的样本.

如果有个奇怪的需求,想知道一次请求所花费的 CPU 时间, Go 语言里面有办法捕获吗? 其他编程语言里呢, 是怎么实现的?

小白感谢各位大佬赐教 orz

1934 次点击
所在节点    Go 编程语言
21 条回复
securityCoding
185 天前
基础不过关啊,pprof go 自带啊
RedisMasterNode
185 天前
@securityCoding 可以指引一下具体怎么做的吗?因为这个 server 同时在 serve 的请求可能有成千上万个, 如何 profiling 到我需要的那一个请求呢?
securityCoding
185 天前
@RedisMasterNode 你灰度一台节点摘掉流量,自己触发不就好了
RedisMasterNode
185 天前
@securityCoding 可能是我背景描述得比较误导.

现实场景是这样的, 咱不说有成千上万个请求, 就只说有 100 个同时在处理的请求(也不一定是 HTTP 请求, 可以是 MQ 的 Handler, 可以是各种触发的操作,不必局限), 开发者不知道哪个才是消耗了最多资源的那个. 如何获取它们各自的 profiling 呢?
securityCoding
185 天前
@RedisMasterNode #4 profile 里面有 func 关键字
RedisMasterNode
185 天前
@securityCoding 唉. 我再等等看其他的大佬有没有更好的答案吧. 您这个答案似乎并不符合, 可能是我描述得不够清晰.
ippolito
185 天前
RedisMasterNode
185 天前
@ippolito 看起来很有趣,但是 enable 之后会采集当前进程的 profile ,进程中一直都是在同时处理多个请求的,所以怎么样才能让它仅关注某个 goroutine 产生的开销呢?

这个好像跟在一个运行中的程序里调用 /pprof/heap 采集 30 秒样本没什么区别,只是采样开始结束时间由一个请求开始结束时间来控制,但是采样的目标并不是针对单个请求。
ippolito
185 天前
@RedisMasterNode #8 每个 goroutine 并没有单独的 CPU 时间,而是共享 CPU 资源。调度器会在多个 goroutine 和多个线程之间进行调度,从而难以直接为每个 goroutine 计算出精确的 CPU 使用情况。

或许你可以使用 runtime.GOMAXPROCS(1) 来限制 CPU 。再通过 benchmark 来测试每个接口的 CPU 使用情况。
kneo
185 天前
自己写个 unittest 去测试。
PTLin
185 天前
ebpf ,一个请求的时间用这个 bcc 脚本就行 https://github.com/iovisor/bcc/blob/master/tools/tcplife.py ,追踪一个链接需要的完整内存使用情况多少就有点麻烦了,tcp 队列跟踪和 skb 系类调用都要打 kprobe 。
thevita
185 天前
cpu time 采样有 call stack, 但采不到 goroutine, 要能在 profile 上看到特定 goroutine 的信息, 要么让 某个 callstack 只跑一个 goroutine, 要么让特定的任务的 callstack 不一样,比如让某个 http 请求多一个特定的 middleware
virusdefender
185 天前
比较难,因为这东西都是进程级别的数据
Orlion
185 天前
提供个思路:
CPU 耗时 = 总耗时 - IO 耗时
通过 trace 能拿到一次请求的总耗时,再通过 trace 埋到 IO 耗时(退一步埋到系统调用的耗时),那么应该能拿到 CPU 耗时了
zzhirong
185 天前
OpenTelemetry + Jaeger 能够详细量化单个请求中各阶段所耗费的时间.
lysShub
185 天前
没那么精细,可以请求相同的地址
RedisMasterNode
185 天前
@Orlion 仔细思考了一下这里在多个 thread 共同抢占 cpu 资源的时候其实没有办法知道实际运行了多少时间. 当然, 还是很感谢这个想法!

@thevita 感谢!

@PTLin 感谢信息! 不知道是否可以达到期望但是可以测试一下, 感谢感谢!

@ippolito 谢谢! 确实是因为特定的背景, 类似限制 CPU 让它变成不需要抢占 CPU 的做法在现在的上下文里不可行, 这也较为类似灰度 qps=1 的流量单独给在 Profiling 的机器. 思路有帮助, 但是如果有其他的方法会更好!
RedisMasterNode
185 天前
@zzhirong 这个不对哈, tracing 的 span 时间是 end-start 的耗时, 在这个过程中实际使用了多少 CPU, IO 和其他杂七杂八时间是不知道的. 在帖子最开始的地方就已经说过了.
zzhirong
184 天前
@RedisMasterNode 问了下 GPT ,得到了一个我之前也没注意到的新功能( go1.17 引入): pprof labels
```go
func handler(w http.ResponseWriter, r *http.Request) {
// 为当前请求创建一个带标签的 Context
ctx := pprof.WithLabels(r.Context(), pprof.Labels("request_id", r.URL.Path))
pprof.Do(ctx, pprof.Labels("request_id", r.URL.Path), func(ctx context.Context) {
// 在这里执行业务逻辑,该段代码调用的所有 CPU 占用情况会带有指定的标签
doSomeWork()
})
w.Write([]byte("ok"))
}
```
后续可以通过`(pprof) tags `查看所在 label 所占用的 CPU 。
hxzhouh1
182 天前
我能想到的两个办法就是楼上说的 灰度一台机器只接受那个请求,或者 unittest

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/1116570

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX