如何转储 goroutine 堆栈跟踪?
Posted
技术标签:
【中文标题】如何转储 goroutine 堆栈跟踪?【英文标题】:How to dump goroutine stacktraces? 【发布时间】:2013-10-06 07:25:13 【问题描述】:我有 Java 背景,我喜欢使用信号 QUIT 来检查 Java 线程转储。
如何让 Golang 打印出所有的 goroutine 堆栈跟踪?
【问题讨论】:
这能回答你的问题吗? How to get the stacktrace of a panic (and store as a variable) 您可以kill -ABRT <pid>
杀死任何 Go 进程并获取 goroutine 堆栈跟踪转储。
【参考方案1】:
要打印 current goroutine 的堆栈跟踪,请使用 PrintStack()
from runtime/debug
。
PrintStack 将 Stack 返回的堆栈跟踪打印到标准错误。
例如:
import(
"runtime/debug"
)
...
debug.PrintStack()
要打印 所有 goroutines 的堆栈跟踪,请使用来自runtime/pprof
的Lookup
和WriteTo
。
func Lookup(name string) *Profile
// Lookup returns the profile with the given name,
// or nil if no such profile exists.
func (p *Profile) WriteTo(w io.Writer, debug int) error
// WriteTo writes a pprof-formatted snapshot of the profile to w.
// If a write to w returns an error, WriteTo returns that error.
// Otherwise, WriteTo returns nil.
每个配置文件都有一个唯一的名称。预定义了一些配置文件:
goroutine - 所有当前 goroutines 的堆栈跟踪 heap - 所有堆分配的采样 threadcreate - 导致创建新操作系统线程的堆栈跟踪 block - 导致同步原语阻塞的堆栈跟踪
例如:
pprof.Lookup("goroutine").WriteTo(os.Stdout, 1)
【讨论】:
是否打印所有 goroutine 的堆栈跟踪? 应该,它调用Stack
。 “堆栈返回调用它的 goroutine 的格式化堆栈跟踪。对于每个例程,它包括源代码行信息和 PC 值,然后尝试为 Go 函数发现调用函数或方法以及包含调用。”
对不起,它只打印当前的 goroutine 堆栈跟踪。
@HowardGuo 我添加了一个使用 runtime/pprof 转储所有堆栈跟踪的示例。
我认为这只会输出每个线程当前正在运行的 goroutine,而不是 所有 goroutine,例如:play.golang.org/p/0hVB0_LMdm【参考方案2】:
Intermernet 的回答中提到了 runtime/pprof
包的 HTTP 前端。导入net/http/pprof包为/debug/pprof
注册一个HTTP处理程序:
import _ "net/http/pprof"
import _ "net/http"
如果您还没有 HTTP 侦听器,请启动一个:
go func()
log.Println(http.ListenAndServe("localhost:6060", nil))
()
然后将浏览器指向http://localhost:6060/debug/pprof
以获取菜单,或http://localhost:6060/debug/pprof/goroutine?debug=2
以获取完整的 goroutine 堆栈转储。
您还可以通过这种方式了解有关正在运行的代码的其他有趣内容。查看博客文章以获取示例和更多详细信息: http://blog.golang.org/profiling-go-programs
【讨论】:
我让它运行它只显示我所看到的执行的 goroutines。有什么方法可以看到 main.go 启动后执行的所有“方法”?【参考方案3】:模拟 SIGQUIT 上堆栈转储的 Java 行为,但仍让程序运行:
go func()
sigs := make(chan os.Signal, 1)
signal.Notify(sigs, syscall.SIGQUIT)
buf := make([]byte, 1<<20)
for
<-sigs
stacklen := runtime.Stack(buf, true)
log.Printf("=== received SIGQUIT ===\n*** goroutine dump...\n%s\n*** end\n", buf[:stacklen])
()
【讨论】:
我认为这正是作者真正想要的——模仿 Java 在发送 kill -QUIT 时所做的事情。我必须做的一个小改动是将 for() 循环的第一行更改为:“ @Bryan,您是否愿意根据 BSD 或 *** 要求的 CC-BY-SA 3.0 之外的其他更宽松的条款许可这个? @CharlesDuffy 你可以在 Apache 许可证下找到很多相同的东西:github.com/weaveworks/weave/blob/… 如果收到 os.Interupt 信号,我稍微改进了打印调试,如果第二个信号快速进入(play.golang.org/p/dWgWrDFBOth【参考方案4】:与 Java 类似,SIGQUIT 可用于打印 Go 程序及其 goroutines 的堆栈跟踪。 然而,一个关键的区别是,默认情况下,向 Java 程序发送 SIGQUIT 不会终止它们,而 Go 程序会退出。
这种方法不需要更改代码即可打印现有程序的所有 goroutine 的堆栈跟踪。
环境变量 GOTRACEBACK (see documentation of the runtime package) 控制生成的输出量。例如,要包含所有 goroutine,请设置 GOTRACEBACK=all。
堆栈跟踪的打印是由意外的运行时条件(未处理的信号)触发的,最初记录在 this commit 中,至少从 Go 1.1 开始就可用。
或者,如果可以选择修改源代码,请参阅其他答案。
请注意,在 Linux 终端中,可以使用组合键 Ctrl+\ 方便地发送 SIGQUIT。
【讨论】:
在查看文档时,我没有发现任何关于 SIGQUIT 的内容,而是 SIGABRT。根据我自己的测试(使用 go 1.7),后者也优于前者。 这应该是最佳答案。 文档指的是“当 Go 程序由于未恢复的恐慌或意外的运行时条件而失败时”。未捕获的信号(SIGQUIT 等)是后者之一。为什么我提到了 SIGQUIT?因为 OP 表达了他们对在 Java 中使用 SIGQUIT 的喜爱,而这个答案强调了相似性。改写答案以使其更清晰。【参考方案5】:您可以使用runtime.Stack 获取所有goroutines的堆栈跟踪:
buf := make([]byte, 1<<16)
runtime.Stack(buf, true)
fmt.Printf("%s", buf)
来自文档:
func Stack(buf []byte, all bool) int
Stack 将调用 goroutine 的堆栈跟踪格式化为 buf 和 返回写入 buf 的字节数。如果一切都是真的,堆栈 在跟踪之后将所有其他 goroutine 的堆栈跟踪格式化为 buf 对于当前的 goroutine。
【讨论】:
这包括来自 所有 goroutine 的回溯,很好! 不要忘记添加字符串(buf),否则您将在那里打印原始字节。 也许我做错了什么,或者功能已经改变,但这并没有为我检索到任何内容,除了一个空的字节片? @koda 这里不需要string(buf)
,fmt.Printf("%s", buf)
和fmt.Printf("%s", string(buf))
做同样的事情(参见fmt
包的文档);这里唯一的区别是string
版本将不必要地从buf
复制字节
请注意 runtime.Stack
返回它在 buf
中实际写入的字节数,因此您应该在打印时手动将 buf
切片到该长度,以避免写入一堆 0 字节(这可能会被终端忽略,但会显示在输出重定向到的文件中)【参考方案6】:
按 CTRL+\
(如果您在终端中运行它并且只想杀死您的程序并转储 go 例程等)
我发现这个问题正在寻找关键序列。只是想要一种快速简便的方法来判断我的程序是否泄漏了 goroutine :)
【讨论】:
【参考方案7】:在 *NIX 系统(包括 OSX)上发送信号 abort SIGABRT
:
pkill -SIGABRT program_name
【讨论】:
显然,像 SIGABRT 一样将 SIGQUIT 发送到 Java 进程 does not terminate it。 我发现这是对原始问题最简单和最匹配的解决方案。通常您需要立即进行堆栈跟踪,而无需更改代码。【参考方案8】:默认情况下,按^\
键(CTRL+\)转储所有 goroutine 的堆栈跟踪。
否则,要进行更精细的控制,您可以使用panic
。 Go 1.6+的简单方法:
go func()
s := make(chan os.Signal, 1)
signal.Notify(s, syscall.SIGQUIT)
<-s
panic("give me the stack")
()
然后,像这样运行你的程序:
# Press ^\ to dump the stack traces of all the user-created goroutines
$ GOTRACEBACK=all go run main.go
如果你还想打印 go runtime goroutines:
$ GOTRACEBACK=system go run main.go
以下是所有 GOTRACEBACK 选项:
GOTRACEBACK=none
完全省略了 goroutine 堆栈跟踪。
GOTRACEBACK=single
(默认) 的行为如上所述。
GOTRACEBACK=all
为所有用户创建的 goroutine 添加堆栈跟踪。
GOTRACEBACK=system
与 all
类似,但为运行时函数添加了堆栈帧,并显示了运行时内部创建的 goroutine。
GOTRACEBACK=crash
类似于 system
,但以特定于操作系统的方式崩溃而不是退出。例如,在 Unix 系统上,崩溃引发 SIGABRT
以触发核心转储。
Here is the documentation
GOTRACEBACK 变量控制当 Go 程序由于未恢复的恐慌或意外的运行时条件而失败时生成的输出量。
默认情况下,失败会打印当前 goroutine 的堆栈跟踪,省略运行时系统内部的函数,然后以退出代码 2 退出。如果没有当前 goroutine,则失败会打印所有 goroutine 的堆栈跟踪,或者故障是运行时内部的。
由于历史原因,GOTRACEBACK 设置 0、1 和 2 分别是 none、all 和 system 的同义词。
runtime/debug 包的 SetTraceback 函数允许在运行时增加输出量,但它不能减少低于环境变量指定的量。见https://golang.org/pkg/runtime/debug/#SetTraceback。
【讨论】:
【参考方案9】:必须使用runtime.Stack()
返回的长度以避免在堆栈跟踪之后打印一堆空行。下面的恢复函数打印出格式良好的跟踪:
if r := recover(); r != nil
log.Printf("Internal error: %v", r))
buf := make([]byte, 1<<16)
stackSize := runtime.Stack(buf, true)
log.Printf("%s\n", string(buf[0:stackSize]))
【讨论】:
没有runtime.Trace
; runtime.Stack
was already mentioned a year and a half ago.
我从未见过;你在哪个平台上运行?
什么是你没见过的?代码应该在所有平台上运行;我已经在 Windows 7、Ubuntu 14.04 和 Mac 上对其进行了测试。
从未见过空行。【参考方案10】:
你可以用这个:
kill -3 YOUR_PROCESS_PID_ID
【讨论】:
以上是关于如何转储 goroutine 堆栈跟踪?的主要内容,如果未能解决你的问题,请参考以下文章