Remote Profiling of go Programs
Introduction
The go
programming language’s
profiling system
supports an
interesting feature: http access. If your application already
has an HTTP interface, and it uses the net/http
default multiplexer
(i.e., it calls http.Handle()
and http.ListenAndServe()
), enabling http
profiling is as simple as:
import _ "net/http/pprof"
and the profiling APIs will be visible under /debug/pprof/
alongside
your application’s URL paths, where you can access them with:
go tool pprof http://host:port/debug/pprof/profile
While this is a slick way of profiling a running
program, the fact that this capability is installed by
an import
makes turning it off and on cumbersome: the
import either has to be added and removed, or placed
in a separate file with
build tags
to turn compilation
on and off. If left in shipped code, it has the side effect
of publishing the profiling hooks (with no access control)
on the application http listener. This is far from ideal,
but there are a few good workarounds.
Access Control
A simple and effective option is to put the pprof http server on a separate port on localhost, separate from the application http server. If the application does not use the http default multiplexer, starting the profiling http server is as simple as:
go func() {
log.Fatal(http.ListenAndServe("localhost:8081"))
}()
Otherwise, you can run the following additional setup prior to any http.Handle() calls:
pprofMux := http.DefaultServeMux
http.DefaultServeMux = http.NewServeMux()
go func() {
log.Fatal(&http.Server{
Addr: "localhost:8081",
Handler: pprofMux,
}.ListenAndServe())
}()
Either of the above snippets can be conditionally run, so the profiling server may be turned on or off by command line flags or application configuration. However, since they are now only locally accessible, there is no downside to leaving them on.
Remote Control
Now that you have the profiling hooks safely exposed via a localhost-only interface, you can invoke the following:
go tool pprof http://localhost:8081/debug/pprof/profile
and profile the program with the local go tools. However, since go compiles to a static binary which can be installed without any go-related dependencies, chances are you don’t have go tools where your program is running. But you probably have SSH, so:
ssh -L 8081:localhost:8081 user@remote-host
or if you are already logged in:
~C -L 8081:localhost:8081
will make the local go tool pprof
on your local machine profile
the remote code over the ssh tunnel.
Conclusion
If you are running go services on servers with only trusted local users, exposing the profiling http port through port forwarding limits access to the trusted local users, making it practical to leave profiling on as just another administrative feature of the system. This allows the developer to observe and/or troubleshoot the program under a real-world, and real, load.
Chris Mikkelson is a Senior Distributed Systems Engineer at Farsight Security, Inc.