如何转储 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/pprofLookupWriteTo

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=systemall 类似,但为运行时函数添加了堆栈帧,并显示了运行时内部创建的 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.Stackwas already mentioned a year and a half ago. 我从未见过;你在哪个平台上运行? 什么是你没见过的?代码应该在所有平台上运行;我已经在 Windows 7、Ubuntu 14.04 和 Mac 上对其进行了测试。 从未见过空行。【参考方案10】:

你可以用这个:

kill -3 YOUR_PROCESS_PID_ID

【讨论】:

以上是关于如何转储 goroutine 堆栈跟踪?的主要内容,如果未能解决你的问题,请参考以下文章

从核心转储中获取堆栈跟踪

有没有办法在不抛出异常的情况下转储堆栈跟踪?

C++ 将堆栈跟踪转储到 *.exe.stackdump

在 App::abort(403) 之后抑制 Laravel 日志中的堆栈跟踪转储

如何在 Ruby 中获取堆栈跟踪对象?

如何让android打印掉崩溃系统应用程序的核心转储?