原文[1]:MichaelKnyszek-2024.03.14
追踪的魔力在于,它可以轻易地揭示出程序中那些难以通过其他方式看到的信息。例如,一个并发瓶颈可能是很多goroutine在同一个channel上阻塞,这在CPUprofile中可能很难被看到,因为没有执行样本可以采样。但在执行追踪中,执行的缺失或不足将以惊人的清晰度展现出来,被阻塞的goroutine的堆栈追踪将迅速指向问题根源。
问题
不幸的是,执行追踪中的丰富信息往往无法触及。历史上,四个主要问题阻碍了追踪的使用。
追踪的开销很大。追踪的扩展性不强,可能会变得过大而无法分析。往往不清楚何时开始追踪以捕获特定的不良行为。鉴于缺乏用于解析和解释执行追踪的公共包,只有最具冒险精神的gopher才能编程分析追踪。
低开销的追踪
在Go1.21之前,许多应用的追踪运行时开销大约在10-20%的CPU之间,这限制了追踪的使用情况,不能像CPUprofiling那样持续使用。事实证明,追踪的大部分成本都归结于traceback。运行时产生的许多事件都附带有堆栈追踪,这些对于实际识别goroutine在关键执行时刻的行为是非常有价值的。
可扩展的追踪
追踪格式及其事件的设计主要考虑到了相对高效的数据生成和输出(emission),但需要工具来解析和保留整个追踪的状态。几百MiB的追踪可能需要几GiB的RAM来分析!
不幸的是,这个问题对于跟踪的生成方式至关重要。为了保持运行时开销低,所有事件都被写入等同于线程局部缓冲区的地方。但这意味着事件出现的顺序并非其真实顺序,追踪工具需要负责弄清楚究竟发生了什么。
使追踪在保持开销低的同时具有可扩展性的关键是,不定期地切分正在生成的追踪。每个切分点都会表现得有点像同时禁用和重新启用追踪。到目前为止的所有追踪数据都会代表一个完整且自包含的追踪,而新的追踪数据将无缝地接续前者。
你可能想象到,修复这个问题需要在运行时重新思考和重写追踪实现的很多基础部分[9]。我们很高兴地说,这项工作在Go1.22中完成,并且现在已经普遍可用。许多很好的改进[10]随着重写一起出现,包括对gotooltrace命令[11]的一些改进。如果你感到好奇,所有的细节都在设计文档[12]中。
(注意:gotooltrace仍然将完整的追踪加载到内存中,但现在已经可以移除这个限制[13],用于Go1.22+程序生成的追踪。)
Flightrecording
有一种可以帮助解决这个问题的技术,叫做Flightrecording,你可能已经在其他编程环境中熟悉了。Flightrecording的关键理念是,持续进行追踪,并始终保留最新的追踪数据,以便随时使用。然后,一旦发生了有趣的事情,程序就可以直接写出它所拥有的所有内容!
在追踪可以被切分之前,这基本上是无法实现的。但是,由于开销较低,连续跟踪现在是可行的,以及运行时现在可以在需要的时候随时切分追踪,结果,实现Flightrecording非常简单。
因此,我们很高兴宣布一个Flightrecorder实验特性,可在golang.org/x/exp/trace包[14]中找到。
追踪readerAPI
我们认为它足够好以开始在其之上构建东西,所以请试试看!下面是一个示例,它测量了阻塞等待网络的goroutine阻塞事件的比例。
//从STDIN开始读取。r,err:=trace.NewReader(os.Stdin)iferr!=nil{log.Fatal(err)}varblockedintvarblockedOnNetworkintfor{//读取事件。ev,err:=r.ReadEvent()iferr==io.EOF{break}elseiferr!=nil{log.Fatal(err)}//处理事件。ifev.Kind()==trace.EventStateTransition{st:=ev.StateTransition()ifst.Resource.Kind==trace.ResourceGoroutine{id:=st.Resource.Goroutine()from,to:=st.GoroutineTransition()//寻找阻塞的goroutines,并计数。iffrom.Executing()&&to==trace.GoWaiting{blocked++ifstrings.Contains(st.Reason,"network"){blockedOnNetwork++}}}}}//打印我们找到的内容。p:=100*float64(blockedOnNetwork)/float64(blocked)fmt.Printf("%2.3f%%instancesofgoroutinesblockingweretoblockonthenetwork\n",p)
就像Flightrecorder一样,proposalissue[16]将是留下反馈的好地方!
我们要特别提一下DominikHonnef,他是早期的试用者,提供了很好的反馈,并且对API的旧版追踪提供了支持。
感谢你们!
你们所有人的讨论、反馈和投入的工作,对我们取得今天的成就发挥了重要作用,谢谢你们!