概述
Go 提供了大量的诊断工具,但在大型、高度并发的现实应用中,使用100+个 Go 协程时,它们的用处有限。为了这个提案的目的,我正在使用一个真实的应用。这个应用使用 gRPC,有一个活动的 RPC 流。它还支持通过标准 TCP 套接字(没有活动连接)的“修复金融协议”。它也发出多播数据包。它有一个网页界面,还有网页套接字接口(两者都没有活动连接)。所以有很多网络端点在使用中。尽管这是一个真实的应用,但与大多数企业应用相比,它还是非常小的。
以下是一张性能分析图和操作系统 CPU 利用率的截图:
这里有一个树状图提供了更多的上下文:
10ms runtime.libcCall
runtime.pthread_mutex_lock
runtime.semawakeup
runtime.notewakeup
runtime.startm
runtime.wakep
runtime.ready
runtime.goready.func1
runtime.systemstack
runtime.goready
runtime.send
runtime.chansend
runtime.selectnbsend
google.golang.org/grpc/internal/transport.(*controlBuffer).executeAndPut
google.golang.org/grpc/internal/transport.(*controlBuffer).put
google.golang.org/grpc/internal/transport.(*http2Server).handleData
google.golang.org/grpc/internal/transport.(*http2Server).HandleStreams
google.golang.org/grpc.(*Server).serveStreams
google.golang.org/grpc.(*Server).handleRawConn.func1
这里是操作系统总 CPU % 的分解:
这里是性能分析图的“web”视图:
这里是一个带有 scheddetail=1 的 GODEBUG 采样。
SCHED 87116ms: gomaxprocs=4 idleprocs=1 threads=14 spinningthreads=1 idlethreads=4 runqueue=0 gcwaiting=0 nmidlelocked=0 stopwait=0 sysmonwait=0
P0: status=0 schedtick=580546 syscalltick=798347 m=-1 runqsize=0 gfreecnt=0
P1: status=2 schedtick=570346 syscalltick=805620 m=-1 runqsize=0 gfreecnt=0
P2: status=1 schedtick=571461 syscalltick=801749 m=4 runqsize=0 gfreecnt=0
P3: status=1 schedtick=567930 syscalltick=814616 m=3 runqsize=0 gfreecnt=0
M13: p=-1 curg=-1 mallocing=0 throwing=0 preemptoff= locks=0 dying=0 helpgc=0 spinning=false blocked=true lockedg=-1
M12: p=-1 curg=50 mallocing=0 throwing=0 preemptoff= locks=0 dying=0 helpgc=0 spinning=false blocked=true lockedg=-1
M11: p=-1 curg=19 mallocing=0 throwing=0 preemptoff= locks=0 dying=0 helpgc=0 spinning=false blocked=true lockedg=-1
M10: p=-1 curg=-1 mallocing=0 throwing=0 preemptoff= locks=0 dying=0 helpgc=0 spinning=false blocked=true lockedg=-1
M9: p=1 curg=16 mallocing=0 throwing=0 preemptoff= locks=0 dying=0 helpgc=0 spinning=false blocked=false lockedg=-1
M8: p=-1 curg=-1 mallocing=0 throwing=0 preemptoff= locks=0 dying=0 helpgc=0 spinning=false blocked=true lockedg=-1
M7: p=-1 curg=-1 mallocing=0 throwing=0 preemptoff= locks=0 dying=0 helpgc=0 spinning=false blocked=true lockedg=-1
M6: p=-1 curg=-1 mallocing=0 throwing=0 preemptoff= locks=0 dying=0 helpgc=0 spinning=false blocked=true lockedg=-1
M5: p=-1 curg=34 mallocing=0 throwing=0 preemptoff= locks=0 dying=0 helpgc=0 spinning=false blocked=true lockedg=-1
M4: p=2 curg=-1 mallocing=0 throwing=0 preemptoff= locks=1 dying=0 helpgc=0 spinning=false blocked=false lockedg=-1
M3: p=3 curg=-1 mallocing=0 throwing=0 preemptoff= locks=1 dying=0 helpgc=0 spinning=true blocked=false lockedg=-1
M2: p=-1 curg=-1 mallocing=0 throwing=0 preemptoff= locks=1 dying=0 helpgc=0 spinning=false blocked=false lockedg=-1
M1: p=-1 curg=17 mallocing=0 throwing=0 preemptoff= locks=0 dying=0 helpgc=0 spinning=false blocked=false lockedg=17
M0: p=0 curg=1 mallocing=0 throwing=0 preemptoff= locks=0 dying=0 helpgc=0 spinning=false blocked=false lockedg=-1
G1: status=3(chan receive) m=0 lockedm=-1
G17: status=6() m=1 lockedm=1
G2: status=4(force gc (idle)) m=-1 lockedm=-1
G18: status=4(GC sweep wait) m=-1 lockedm=-1
G3: status=4(finalizer wait) m=-1 lockedm=-1
G5: status=4(chan receive) m=-1 lockedm=-1
G6: status=4(IO wait) m=-1 lockedm=-1
G7: status=4(select) m=-1 lockedm=-1
G8: status=4(select) m=-1 lockedm=-1
G9: status=4(select) m=-1 lockedm=-1
G10: status=4(select) m=-1 lockedm=-1
G11: status=4(select) m=-1 lockedm=-1
G12: status=4(select) m=-1 lockedm=-1
... 20 entries exactly like above except Go routine number...
G28: status=4(GC worker (idle)) m=-1 lockedm=-1
G71: status=4(IO wait) m=-1 lockedm=-1
G72: status=4(IO wait) m=-1 lockedm=-1
G73: status=4(sleep) m=-1 lockedm=-1
G74: status=4(timer goroutine (idle)) m=-1 lockedm=-1
G41: status=4(select) m=-1 lockedm=-1
G42: status=4(IO wait) m=-1 lockedm=-1
G75: status=4(GC worker (idle)) m=-1 lockedm=-1
G29: status=4(GC worker (idle)) m=-1 lockedm=-1
G44: status=4(GC worker (idle)) m=-1 lockedm=-1
当使用“trace”时,更容易发现问题,因为“goroutine 分析”显示:
然后进入 startMarketData (示例 A) 函数的详细信息,我们可以看到:
检查同步块时间,我们可以看到:
但是检查调度器等待时间,我们可以看到:
困境
虽然性能分析图清楚地显示了许多操作系统活动,操作系统监视器显示了高 CPU 利用率,但没有简单的方法来分析导致系统 CPU% 高的原因。最大的问题是性能分析图没有按“go routine”分区。使用 trace 工具情况会好一些,但没有汇总视图。理想情况下,详细视图(上面的示例 A)应该显示在所有 go routines 的“分析页面”上——获取这些信息唯一的方法是逐个检查每个 routine。类似这样(作为 issue #29103 提交):
但更重要的是,需要进行汇总分析以了解“为什么”——而不是查看图表,例如,如果我打开上面 start market data 函数的详细信息,它应该显示(仅作示例):
startmarketdata.func1
network wait time 102 ms:
5% send on socket
70% poll
25% read on socket
scheduler wait 1 us
57% wait for GC cycle
43% wait for CPU
sync time 50 ms
67% wait on channel
100% sent by go routine xxxx
23% wait on mutex filename:line#
etc...
用户还可以使用“trace viewer”,它是完全手动的——非常强大,但很难看清森林中的树木。
具体建议
- 能够给 Go 协程添加一个“名称”,类似于:
go "myname" func(){...}
go stringexp func(){...}
与现有命名相比,更难做到这一点——你需要成为所有代码库的所有库的Maven才能对正在发生的事情有一个基本的理解。名称将允许库创建者提供关于其目的的重要信息。这需要附加到现有诊断工具中的现有“名称”。
- 作为替代方案,至少应将当前提供的 go 协程名称更改为包括源文件和行号。通常一个函数会生成多个 go 协程,而惯用的方法是匿名函数,因此你最终会得到 func1、func2 等。源/行号会使跟踪这些协程变得更容易。如果程序没有在第1点中提供名称,这也是同样的情况。
- trace 输出应该有一个选项来进行每个 go 协程堆栈采样,在采样间隔内将“堆栈跟踪事件”放置在 trace 文件中。可能可以基于时间戳同步捕获性能分析与同步捕获,但简化很多的东西是在单个捕获文件中包含所有内容。
- 应该有 Go 协程示例的 trace 报告。目前,使用 trace 时,同一类型的所有协程都被归类在一起。这不允许轻松检测处理瓶颈。例如,如果有4个消耗了15%的协程,是1个消耗了15%,还是3个消耗了1%和1个消耗了14%?
1.最重要的是,没有公共 API 可以读取 trace 文件以生成自定义分析报告,尤其是当存在一个 API 可以向 trace 添加“用户事件”时。内部/trace 包应该是公开的。目前,信息可能是从“工具 trace webserver”中以 json 形式获得的,但这是未记录的,可能对于某些分析需求来说效率太低。目前的解决方案涉及复制粘贴该包。
6条答案
按热度按时间yyyllmsg1#
这可能需要分成多个问题,但除了在文本中引用“父”事件之外,我没有看到一个简单的方法来将它们分组?
3bygqnnd2#
感谢您的报告/问题 @robaho!
请转告 @hyangah@rakyll@dvyukov@aclements
ykejflvf3#
问题4是错误的。当你追踪一个Go协程时,每个协程都有条目(图表等)。
问题在于这些层次太低了。通过库和应用程序将用户事件放在各个地方可能会有所帮助,但即使这样,分析仍然是手动的。
我感兴趣的是开发一种更宏观的东西,能够理解事件依赖关系并提供更容易理解的性能分析。
hkmswyz64#
跟踪器仍在开发中。最近进展缓慢。我们还不清楚何时能实现运行时/跟踪的改进。
我们确实已经记录了创建goroutine的位置(例如,它会出现在panic崩溃输出中)。如果可以很容易地将其添加到跟踪格式中,那么这是值得的。关于良好摘要和摘要呈现的较大问题是困难的。
ipakzgxi5#
如果将跟踪解析功能从内部包移到公共包,那么用户就可以开发更高级分析工具。现在你需要复制粘贴代码,随着事情的发展,这会变得脆弱。至少这个问题可以很容易地解决。这对于用户事件(注解)尤其重要,因为几乎肯定需要自定义分析
k2fxgqgv6#
@rsc顺便说一下,我已经在#29103中添加了一些聚合工作,通过查看代码,我可以想到许多非常有用的用于处理这些数据的演示文稿,甚至可以为分析创建一个实时流事件处理器。跟踪系统设计得非常好。