Inspecting Goroutine States

Sometimes it can be useful to get a view of all running goroutines: how many there are, which function they are currently executing, and what their current status is (running, waiting for a channel receive / send operation, waiting for a mutex, waiting for a syscall, etc.). For example, this information can help you figure out why certain parts of Prometheus (like a scrape loop) are stuck, or whether there is a goroutine leak.

The /debug/pprof/goroutine endpoint gives us exactly this information, and it is useful directly from the web browser.

By default, this endpoint will return a text format like this:

goroutine profile: total 46
7 @ 0x43e10e 0x44e5a5 0x27fe95f 0x471281
#	0x27fe95e	github.com/prometheus/prometheus/scrape.(*scrapeLoop).run+0x3fe	/app/scrape/scrape.go:1182

6 @ 0x43e10e 0x436b97 0x46b8a5 0x4e5287 0x4e657a 0x4e6568 0x52ab05 0x53bbe5 0x70d3ea 0x673be3 0x673d13 0x70e1d9 0x471281
#	0x46b8a4	internal/poll.runtime_pollWait+0x84		/usr/local/go/src/runtime/netpoll.go:343
#	0x4e5286	internal/poll.(*pollDesc).wait+0x26		/usr/local/go/src/internal/poll/fd_poll_runtime.go:84
#	0x4e6579	internal/poll.(*pollDesc).waitRead+0x279	/usr/local/go/src/internal/poll/fd_poll_runtime.go:89
#	0x4e6567	internal/poll.(*FD).Read+0x267			/usr/local/go/src/internal/poll/fd_unix.go:164
#	0x52ab04	net.(*netFD).Read+0x24				/usr/local/go/src/net/fd_posix.go:55
#	0x53bbe4	net.(*conn).Read+0x44				/usr/local/go/src/net/net.go:179
#	0x70d3e9	net/http.(*persistConn).Read+0x49		/usr/local/go/src/net/http/transport.go:1954
#	0x673be2	bufio.(*Reader).fill+0x102			/usr/local/go/src/bufio/bufio.go:113
#	0x673d12	bufio.(*Reader).Peek+0x52			/usr/local/go/src/bufio/bufio.go:151
#	0x70e1d8	net/http.(*persistConn).readLoop+0x1b8		/usr/local/go/src/net/http/transport.go:2118

6 @ 0x43e10e 0x44e5a5 0x70fba5 0x471281
#	0x70fba4	net/http.(*persistConn).writeLoop+0xe4	/usr/local/go/src/net/http/transport.go:2421

[...]

However, by appending a ?debug=2 parameter to the URL (yielding /debug/pprof/goroutine?debug=2), the format becomes somewhat more human-readable and useful:

goroutine 10742989 [running]:
runtime/pprof.writeGoroutineStacks({0x7efc81c29998, 0xc00255cc80})
	/usr/local/go/src/runtime/pprof/pprof.go:703 +0x6a
runtime/pprof.writeGoroutine({0x7efc81c29998?, 0xc00255cc80?}, 0x4103c5?)
	/usr/local/go/src/runtime/pprof/pprof.go:692 +0x25
runtime/pprof.(*Profile).WriteTo(0x3204180?, {0x7efc81c29998?, 0xc00255cc80?}, 0xc?)
	/usr/local/go/src/runtime/pprof/pprof.go:329 +0x146
net/http/pprof.handler.ServeHTTP({0xc003cb720d, 0x9}, {0x7efc81c28890, 0xc00255cc80}, 0x0?)
	/usr/local/go/src/net/http/pprof/pprof.go:267 +0x4a8
net/http/pprof.Index({0x7efc81c28890?, 0xc00255cc80}, 0xc002c62200?)
	/usr/local/go/src/net/http/pprof/pprof.go:384 +0xe5
github.com/prometheus/prometheus/web.serveDebug({0x7efc81c28890, 0xc00255cc80}, 0xc002c62200)
	/app/web/web.go:520 +0x2c5
net/http.HandlerFunc.ServeHTTP(0x7efc81c28890?, {0x7efc81c28890?, 0xc00255cc80?}, 0x40fe89?)
	/usr/local/go/src/net/http/server.go:2136 +0x29
github.com/prometheus/client_golang/prometheus/promhttp.InstrumentHandlerResponseSize.func1({0x7efc81c28890?, 0xc00255cc30?}, 0xc002c62200)
	/go/pkg/mod/github.com/prometheus/client_golang@v1.17.0/prometheus/promhttp/instrument_server.go:296 +0xca
net/http.HandlerFunc.ServeHTTP(0x48?, {0x7efc81c28890?, 0xc00255cc30?}, 0x7efc841764b8?)
	/usr/local/go/src/net/http/server.go:2136 +0x29
github.com/prometheus/client_golang/prometheus/promhttp.InstrumentHandlerDuration.func2({0x7efc81c28890, 0xc00255cc30}, 0xc002c62200)
	/go/pkg/mod/github.com/prometheus/client_golang@v1.17.0/prometheus/promhttp/instrument_server.go:109 +0xc2
net/http.HandlerFunc.ServeHTTP(0x3f64c80?, {0x7efc81c28890?, 0xc00255cc30?}, 0x32d7480?)
	/usr/local/go/src/net/http/server.go:2136 +0x29
github.com/prometheus/client_golang/prometheus/promhttp.InstrumentHandlerCounter.func1({0x3f64c80?, 0xc004304180?}, 0xc002c62200)
	/go/pkg/mod/github.com/prometheus/client_golang@v1.17.0/prometheus/promhttp/instrument_server.go:147 +0xb6
github.com/prometheus/prometheus/web.New.setPathWithPrefix.func20.1({0x3f64c80, 0xc004304180}, 0xc002c62100)
	/app/web/web.go:805 +0xb3
github.com/prometheus/common/route.(*Router).handle.func1({0x3f64c80, 0xc004304180}, 0xc002c62000, {0xc002141c00, 0x1, 0x10040dea5?})
	/go/pkg/mod/github.com/prometheus/common@v0.45.0/route/route.go:83 +0x1f6
github.com/julienschmidt/httprouter.(*Router).ServeHTTP(0xc0007f97a0, {0x3f64c80, 0xc004304180}, 0xc002c62000)
	/go/pkg/mod/github.com/julienschmidt/httprouter@v1.3.0/router.go:387 +0x7eb
github.com/prometheus/common/route.(*Router).ServeHTTP(0xc0099cc400?, {0x3f64c80?, 0xc004304180?}, 0xc000f05408?)
	/go/pkg/mod/github.com/prometheus/common@v0.45.0/route/route.go:126 +0x25
net/http.(*ServeMux).ServeHTTP(0x3f6e2e0?, {0x3f64c80, 0xc004304180}, 0xc002c62000)
	/usr/local/go/src/net/http/server.go:2514 +0x142
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp.(*middleware).serveHTTP(0xc0004a5810, {0x3f5d410?, 0xc002e610a0}, 0xc002b9bf00, {0x3f3e7e0, 0xc000733240})
	/go/pkg/mod/go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp@v0.46.1/handler.go:229 +0x1202
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp.NewMiddleware.func1.1({0x3f5d410?, 0xc002e610a0?}, 0x10?)
	/go/pkg/mod/go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp@v0.46.1/handler.go:81 +0x35
net/http.HandlerFunc.ServeHTTP(0x7f013d5c7150?, {0x3f5d410?, 0xc002e610a0?}, 0x46f472?)
	/usr/local/go/src/net/http/server.go:2136 +0x29
github.com/prometheus/prometheus/web.(*Handler).Run.withStackTracer.func9({0x3f5d410?, 0xc002e610a0?}, 0x0?)
	/app/web/web.go:104 +0x8c
net/http.HandlerFunc.ServeHTTP(0x5b578c0?, {0x3f5d410?, 0xc002e610a0?}, 0xc000f05b50?)
	/usr/local/go/src/net/http/server.go:2136 +0x29
net/http.serverHandler.ServeHTTP({0xc001835a10?}, {0x3f5d410?, 0xc002e610a0?}, 0x6?)
	/usr/local/go/src/net/http/server.go:2938 +0x8e
net/http.(*conn).serve(0xc002f797a0, {0x3f6e2e0, 0xc0009b2480})
	/usr/local/go/src/net/http/server.go:2009 +0x5f4
created by net/http.(*Server).Serve in goroutine 349
	/usr/local/go/src/net/http/server.go:3086 +0x5cb

goroutine 1 [chan receive, 57724 minutes]:
github.com/oklog/run.(*Group).Run(0xc00250d850)
	/go/pkg/mod/github.com/oklog/run@v1.1.0/group.go:43 +0x155
main.main()
	/app/cmd/prometheus/main.go:1179 +0xa98c

goroutine 17 [select]:
go.opencensus.io/stats/view.(*worker).start(0xc000132280)
	/go/pkg/mod/go.opencensus.io@v0.24.0/stats/view/worker.go:292 +0x9f
created by go.opencensus.io/stats/view.init.0 in goroutine 1
	/go/pkg/mod/go.opencensus.io@v0.24.0/stats/view/worker.go:34 +0x8d

[...]

This output lists the call stack for each goroutine, preceded by a goroutine <goroutine-number> [<goroutine status>] line. The status of a goroutine indicates whether it is currently running or waiting for some external action or condition (e.g. [chan receive, 158485 minutes], [select], etc.). If you see a goroutine here that is waiting for an unexpectedly long time, this might point to a problem.