Commit f64c6701 authored by Filippo Valsorda's avatar Filippo Valsorda Committed by Russ Cox

cmd/trace: add option to output pprof files

The trace tool can generate some interesting profiles, but it was only
exposing them as svg through the web UI.  This adds command line options
to generate the raw pprof file.

Change-Id: I52e4f909fdca6f65c3616add444e3892783640f4
Reviewed-on: https://go-review.googlesource.com/23324Reviewed-by: default avatarRuss Cox <rsc@golang.org>
parent 7f6eadb6
...@@ -15,6 +15,14 @@ Generate a trace file with 'go test': ...@@ -15,6 +15,14 @@ Generate a trace file with 'go test':
go test -trace trace.out pkg go test -trace trace.out pkg
View the trace in a web browser: View the trace in a web browser:
go tool trace trace.out go tool trace trace.out
Generate a pprof-like profile from the trace:
go tool trace -pprof=TYPE trace.out > TYPE.pprof
Supported profile types are:
- net: network blocking profile
- sync: synchronization blocking profile
- syscall: syscall blocking profile
- sched: scheduler latency profile
*/ */
package main package main
...@@ -25,6 +33,7 @@ import ( ...@@ -25,6 +33,7 @@ import (
"fmt" "fmt"
"html/template" "html/template"
"internal/trace" "internal/trace"
"io"
"log" "log"
"net" "net"
"net/http" "net/http"
...@@ -39,15 +48,27 @@ Given a trace file produced by 'go test': ...@@ -39,15 +48,27 @@ Given a trace file produced by 'go test':
Open a web browser displaying trace: Open a web browser displaying trace:
go tool trace [flags] [pkg.test] trace.out go tool trace [flags] [pkg.test] trace.out
Generate a pprof-like profile from the trace:
go tool trace -pprof=TYPE [pkg.test] trace.out
[pkg.test] argument is required for traces produced by Go 1.6 and below. [pkg.test] argument is required for traces produced by Go 1.6 and below.
Go 1.7 does not require the binary argument. Go 1.7 does not require the binary argument.
Supported profile types are:
- net: network blocking profile
- sync: synchronization blocking profile
- syscall: syscall blocking profile
- sched: scheduler latency profile
Flags: Flags:
-http=addr: HTTP service address (e.g., ':6060') -http=addr: HTTP service address (e.g., ':6060')
-pprof=type: print a pprof-like profile instead
` `
var ( var (
httpFlag = flag.String("http", "localhost:0", "HTTP service address (e.g., ':6060')") httpFlag = flag.String("http", "localhost:0", "HTTP service address (e.g., ':6060')")
pprofFlag = flag.String("pprof", "", "print a pprof-like profile instead")
// The binary file name, left here for serveSVGProfile. // The binary file name, left here for serveSVGProfile.
programBinary string programBinary string
...@@ -73,6 +94,27 @@ func main() { ...@@ -73,6 +94,27 @@ func main() {
flag.Usage() flag.Usage()
} }
var pprofFunc func(io.Writer) error
switch *pprofFlag {
case "net":
pprofFunc = pprofIO
case "sync":
pprofFunc = pprofBlock
case "syscall":
pprofFunc = pprofSyscall
case "sched":
pprofFunc = pprofSched
}
if pprofFunc != nil {
if err := pprofFunc(os.Stdout); err != nil {
dief("failed to generate pprof: %v\n", err)
}
os.Exit(0)
}
if *pprofFlag != "" {
dief("unknown pprof type %s\n", *pprofFlag)
}
ln, err := net.Listen("tcp", *httpFlag) ln, err := net.Listen("tcp", *httpFlag)
if err != nil { if err != nil {
dief("failed to create server socket: %v\n", err) dief("failed to create server socket: %v\n", err)
......
...@@ -11,6 +11,7 @@ import ( ...@@ -11,6 +11,7 @@ import (
"cmd/internal/pprof/profile" "cmd/internal/pprof/profile"
"fmt" "fmt"
"internal/trace" "internal/trace"
"io"
"io/ioutil" "io/ioutil"
"net/http" "net/http"
"os" "os"
...@@ -18,10 +19,10 @@ import ( ...@@ -18,10 +19,10 @@ import (
) )
func init() { func init() {
http.HandleFunc("/io", httpIO) http.HandleFunc("/io", serveSVGProfile(pprofIO))
http.HandleFunc("/block", httpBlock) http.HandleFunc("/block", serveSVGProfile(pprofBlock))
http.HandleFunc("/syscall", httpSyscall) http.HandleFunc("/syscall", serveSVGProfile(pprofSyscall))
http.HandleFunc("/sched", httpSched) http.HandleFunc("/sched", serveSVGProfile(pprofSched))
} }
// Record represents one entry in pprof-like profiles. // Record represents one entry in pprof-like profiles.
...@@ -31,12 +32,11 @@ type Record struct { ...@@ -31,12 +32,11 @@ type Record struct {
time int64 time int64
} }
// httpIO serves IO pprof-like profile (time spent in IO wait). // pprofIO generates IO pprof-like profile (time spent in IO wait).
func httpIO(w http.ResponseWriter, r *http.Request) { func pprofIO(w io.Writer) error {
events, err := parseEvents() events, err := parseEvents()
if err != nil { if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError) return err
return
} }
prof := make(map[uint64]Record) prof := make(map[uint64]Record)
for _, ev := range events { for _, ev := range events {
...@@ -49,15 +49,14 @@ func httpIO(w http.ResponseWriter, r *http.Request) { ...@@ -49,15 +49,14 @@ func httpIO(w http.ResponseWriter, r *http.Request) {
rec.time += ev.Link.Ts - ev.Ts rec.time += ev.Link.Ts - ev.Ts
prof[ev.StkID] = rec prof[ev.StkID] = rec
} }
serveSVGProfile(w, r, prof) return buildProfile(prof).Write(w)
} }
// httpBlock serves blocking pprof-like profile (time spent blocked on synchronization primitives). // pprofBlock generates blocking pprof-like profile (time spent blocked on synchronization primitives).
func httpBlock(w http.ResponseWriter, r *http.Request) { func pprofBlock(w io.Writer) error {
events, err := parseEvents() events, err := parseEvents()
if err != nil { if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError) return err
return
} }
prof := make(map[uint64]Record) prof := make(map[uint64]Record)
for _, ev := range events { for _, ev := range events {
...@@ -76,15 +75,14 @@ func httpBlock(w http.ResponseWriter, r *http.Request) { ...@@ -76,15 +75,14 @@ func httpBlock(w http.ResponseWriter, r *http.Request) {
rec.time += ev.Link.Ts - ev.Ts rec.time += ev.Link.Ts - ev.Ts
prof[ev.StkID] = rec prof[ev.StkID] = rec
} }
serveSVGProfile(w, r, prof) return buildProfile(prof).Write(w)
} }
// httpSyscall serves syscall pprof-like profile (time spent blocked in syscalls). // pprofSyscall generates syscall pprof-like profile (time spent blocked in syscalls).
func httpSyscall(w http.ResponseWriter, r *http.Request) { func pprofSyscall(w io.Writer) error {
events, err := parseEvents() events, err := parseEvents()
if err != nil { if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError) return err
return
} }
prof := make(map[uint64]Record) prof := make(map[uint64]Record)
for _, ev := range events { for _, ev := range events {
...@@ -97,16 +95,15 @@ func httpSyscall(w http.ResponseWriter, r *http.Request) { ...@@ -97,16 +95,15 @@ func httpSyscall(w http.ResponseWriter, r *http.Request) {
rec.time += ev.Link.Ts - ev.Ts rec.time += ev.Link.Ts - ev.Ts
prof[ev.StkID] = rec prof[ev.StkID] = rec
} }
serveSVGProfile(w, r, prof) return buildProfile(prof).Write(w)
} }
// httpSched serves scheduler latency pprof-like profile // pprofSched generates scheduler latency pprof-like profile
// (time between a goroutine become runnable and actually scheduled for execution). // (time between a goroutine become runnable and actually scheduled for execution).
func httpSched(w http.ResponseWriter, r *http.Request) { func pprofSched(w io.Writer) error {
events, err := parseEvents() events, err := parseEvents()
if err != nil { if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError) return err
return
} }
prof := make(map[uint64]Record) prof := make(map[uint64]Record)
for _, ev := range events { for _, ev := range events {
...@@ -120,45 +117,43 @@ func httpSched(w http.ResponseWriter, r *http.Request) { ...@@ -120,45 +117,43 @@ func httpSched(w http.ResponseWriter, r *http.Request) {
rec.time += ev.Link.Ts - ev.Ts rec.time += ev.Link.Ts - ev.Ts
prof[ev.StkID] = rec prof[ev.StkID] = rec
} }
serveSVGProfile(w, r, prof) return buildProfile(prof).Write(w)
} }
// generateSVGProfile generates pprof-like profile stored in prof and writes in to w. // serveSVGProfile serves pprof-like profile generated by prof as svg.
func serveSVGProfile(w http.ResponseWriter, r *http.Request, prof map[uint64]Record) { func serveSVGProfile(prof func(w io.Writer) error) http.HandlerFunc {
if len(prof) == 0 { return func(w http.ResponseWriter, r *http.Request) {
http.Error(w, "The profile is empty", http.StatusNotFound) blockf, err := ioutil.TempFile("", "block")
return if err != nil {
} http.Error(w, fmt.Sprintf("failed to create temp file: %v", err), http.StatusInternalServerError)
blockf, err := ioutil.TempFile("", "block") return
if err != nil { }
http.Error(w, fmt.Sprintf("failed to create temp file: %v", err), http.StatusInternalServerError) defer func() {
return blockf.Close()
} os.Remove(blockf.Name())
defer func() { }()
blockf.Close() blockb := bufio.NewWriter(blockf)
os.Remove(blockf.Name()) if err := prof(blockb); err != nil {
}() http.Error(w, fmt.Sprintf("failed to generate profile: %v", err), http.StatusInternalServerError)
blockb := bufio.NewWriter(blockf) return
if err := buildProfile(prof).Write(blockb); err != nil { }
http.Error(w, fmt.Sprintf("failed to write profile: %v", err), http.StatusInternalServerError) if err := blockb.Flush(); err != nil {
return http.Error(w, fmt.Sprintf("failed to flush temp file: %v", err), http.StatusInternalServerError)
} return
if err := blockb.Flush(); err != nil { }
http.Error(w, fmt.Sprintf("failed to flush temp file: %v", err), http.StatusInternalServerError) if err := blockf.Close(); err != nil {
return http.Error(w, fmt.Sprintf("failed to close temp file: %v", err), http.StatusInternalServerError)
} return
if err := blockf.Close(); err != nil { }
http.Error(w, fmt.Sprintf("failed to close temp file: %v", err), http.StatusInternalServerError) svgFilename := blockf.Name() + ".svg"
return if output, err := exec.Command("go", "tool", "pprof", "-svg", "-output", svgFilename, blockf.Name()).CombinedOutput(); err != nil {
} http.Error(w, fmt.Sprintf("failed to execute go tool pprof: %v\n%s", err, output), http.StatusInternalServerError)
svgFilename := blockf.Name() + ".svg" return
if output, err := exec.Command("go", "tool", "pprof", "-svg", "-output", svgFilename, blockf.Name()).CombinedOutput(); err != nil { }
http.Error(w, fmt.Sprintf("failed to execute go tool pprof: %v\n%s", err, output), http.StatusInternalServerError) defer os.Remove(svgFilename)
return w.Header().Set("Content-Type", "image/svg+xml")
http.ServeFile(w, r, svgFilename)
} }
defer os.Remove(svgFilename)
w.Header().Set("Content-Type", "image/svg+xml")
http.ServeFile(w, r, svgFilename)
} }
func buildProfile(prof map[uint64]Record) *profile.Profile { func buildProfile(prof map[uint64]Record) *profile.Profile {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment