Commit 5bd66e5e authored by Hana (Hyang-Ah) Kim's avatar Hana (Hyang-Ah) Kim Committed by Hyang-Ah Hana Kim

cmd/trace: compute pprof-style output per goroutine type

The trace command computes IO, Schedule, Block, and Syscall profiles
by following the unblocking links in the execution trace and summing
up the duration.  This change offers variations of those profiles
that include only selected goroutine types. The id parameter takes the
goroutine type - i.e. pc of the goroutine.

The output is available from the /goroutine view. So, users can see
where the goroutines of interest typically block.

Also, these profiles are available for download so users can use
pprof or other tools to interpret the output. This change adds links
for download of global profile in the main page.

Change-Id: I35699252056d164e60de282b0406caf96d629c85
Reviewed-on: https://go-review.googlesource.com/75710Reviewed-by: default avatarSameer Ajmani <sameer@golang.org>
parent 6be1c09e
...@@ -121,13 +121,16 @@ func httpGoroutine(w http.ResponseWriter, r *http.Request) { ...@@ -121,13 +121,16 @@ func httpGoroutine(w http.ResponseWriter, r *http.Request) {
analyzeGoroutines(events) analyzeGoroutines(events)
var glist gdescList var glist gdescList
for _, g := range gs { for _, g := range gs {
if g.PC != pc || g.ExecTime == 0 { if g.PC != pc {
continue continue
} }
glist = append(glist, g) glist = append(glist, g)
} }
sort.Sort(glist) sort.Sort(glist)
err = templGoroutine.Execute(w, glist) err = templGoroutine.Execute(w, struct {
PC uint64
GList gdescList
}{pc, glist})
if err != nil { if err != nil {
http.Error(w, fmt.Sprintf("failed to execute template: %v", err), http.StatusInternalServerError) http.Error(w, fmt.Sprintf("failed to execute template: %v", err), http.StatusInternalServerError)
return return
...@@ -142,14 +145,14 @@ var templGoroutine = template.Must(template.New("").Parse(` ...@@ -142,14 +145,14 @@ var templGoroutine = template.Must(template.New("").Parse(`
<th> Goroutine </th> <th> Goroutine </th>
<th> Total time, ns </th> <th> Total time, ns </th>
<th> Execution time, ns </th> <th> Execution time, ns </th>
<th> Network wait time, ns </th> <th> <a href="/io?id={{.PC}}">Network wait time, ns</a><a href="/io?id={{.PC}}&raw=1" download="io.profile">⬇</a> </th>
<th> Sync block time, ns </th> <th> <a href="/block?id={{.PC}}">Sync block time, ns</a><a href="/block?id={{.PC}}&raw=1" download="block.profile">⬇</a> </th>
<th> Blocking syscall time, ns </th> <th> <a href="/syscall?id={{.PC}}">Blocking syscall time, ns</a><a href="/syscall?id={{.PC}}&raw=1" download="syscall.profile">⬇</a> </th>
<th> Scheduler wait time, ns </th> <th> <a href="/sched?id={{.PC}}">Scheduler wait time, ns</a><a href="/sched?id={{.PC}}&raw=1" download="sched.profile">⬇</a> </th>
<th> GC sweeping time, ns </th> <th> GC sweeping time, ns </th>
<th> GC pause time, ns </th> <th> GC pause time, ns </th>
</tr> </tr>
{{range $}} {{range .GList}}
<tr> <tr>
<td> <a href="/trace?goid={{.ID}}">{{.ID}}</a> </td> <td> <a href="/trace?goid={{.ID}}">{{.ID}}</a> </td>
<td> {{.TotalTime}} </td> <td> {{.TotalTime}} </td>
......
...@@ -79,7 +79,7 @@ func main() { ...@@ -79,7 +79,7 @@ func main() {
flag.Usage() flag.Usage()
} }
var pprofFunc func(io.Writer) error var pprofFunc func(io.Writer, string) error
switch *pprofFlag { switch *pprofFlag {
case "net": case "net":
pprofFunc = pprofIO pprofFunc = pprofIO
...@@ -91,7 +91,7 @@ func main() { ...@@ -91,7 +91,7 @@ func main() {
pprofFunc = pprofSched pprofFunc = pprofSched
} }
if pprofFunc != nil { if pprofFunc != nil {
if err := pprofFunc(os.Stdout); err != nil { if err := pprofFunc(os.Stdout, ""); err != nil {
dief("failed to generate pprof: %v\n", err) dief("failed to generate pprof: %v\n", err)
} }
os.Exit(0) os.Exit(0)
...@@ -187,10 +187,10 @@ var templMain = template.Must(template.New("").Parse(` ...@@ -187,10 +187,10 @@ var templMain = template.Must(template.New("").Parse(`
<a href="/trace">View trace</a><br> <a href="/trace">View trace</a><br>
{{end}} {{end}}
<a href="/goroutines">Goroutine analysis</a><br> <a href="/goroutines">Goroutine analysis</a><br>
<a href="/io">Network blocking profile</a><br> <a href="/io">Network blocking profile</a> (<a href="/io?raw=1" download="io.profile">⬇</a>)<br>
<a href="/block">Synchronization blocking profile</a><br> <a href="/block">Synchronization blocking profile</a> (<a href="/block?raw=1" download="block.profile">⬇</a>)<br>
<a href="/syscall">Syscall blocking profile</a><br> <a href="/syscall">Syscall blocking profile</a> (<a href="/syscall?raw=1" download="syscall.profile">⬇</a>)<br>
<a href="/sched">Scheduler latency profile</a><br> <a href="/sched">Scheduler latency profile</a> (<a href="/sche?raw=1" download="sched.profile">⬇</a>)<br>
</body> </body>
</html> </html>
`)) `))
......
...@@ -17,6 +17,7 @@ import ( ...@@ -17,6 +17,7 @@ import (
"os/exec" "os/exec"
"path/filepath" "path/filepath"
"runtime" "runtime"
"strconv"
"github.com/google/pprof/profile" "github.com/google/pprof/profile"
) )
...@@ -47,17 +48,54 @@ type Record struct { ...@@ -47,17 +48,54 @@ type Record struct {
time int64 time int64
} }
// pprofIO generates IO pprof-like profile (time spent in IO wait). // pprofMatchingGoroutines parses the goroutine type id string (i.e. pc)
func pprofIO(w io.Writer) error { // and returns the ids of goroutines of the matching type.
// If the id string is empty, returns nil without an error.
func pprofMatchingGoroutines(id string, events []*trace.Event) (map[uint64]bool, error) {
if id == "" {
return nil, nil
}
pc, err := strconv.ParseUint(id, 10, 64) // id is string
if err != nil {
return nil, fmt.Errorf("invalid goroutine type: %v", id)
}
analyzeGoroutines(events)
var res map[uint64]bool
for _, g := range gs {
if g.PC != pc {
continue
}
if res == nil {
res = make(map[uint64]bool)
}
res[g.ID] = true
}
if len(res) == 0 && id != "" {
return nil, fmt.Errorf("failed to find matching goroutines for id: %s", id)
}
return res, nil
}
// pprofIO generates IO pprof-like profile (time spent in IO wait,
// currently only network blocking event).
func pprofIO(w io.Writer, id string) error {
events, err := parseEvents() events, err := parseEvents()
if err != nil { if err != nil {
return err return err
} }
goroutines, err := pprofMatchingGoroutines(id, events)
if err != nil {
return err
}
prof := make(map[uint64]Record) prof := make(map[uint64]Record)
for _, ev := range events { for _, ev := range events {
if ev.Type != trace.EvGoBlockNet || ev.Link == nil || ev.StkID == 0 || len(ev.Stk) == 0 { if ev.Type != trace.EvGoBlockNet || ev.Link == nil || ev.StkID == 0 || len(ev.Stk) == 0 {
continue continue
} }
if goroutines != nil && !goroutines[ev.G] {
continue
}
rec := prof[ev.StkID] rec := prof[ev.StkID]
rec.stk = ev.Stk rec.stk = ev.Stk
rec.n++ rec.n++
...@@ -68,22 +106,33 @@ func pprofIO(w io.Writer) error { ...@@ -68,22 +106,33 @@ func pprofIO(w io.Writer) error {
} }
// pprofBlock generates blocking pprof-like profile (time spent blocked on synchronization primitives). // pprofBlock generates blocking pprof-like profile (time spent blocked on synchronization primitives).
func pprofBlock(w io.Writer) error { func pprofBlock(w io.Writer, id string) error {
events, err := parseEvents() events, err := parseEvents()
if err != nil { if err != nil {
return err return err
} }
goroutines, err := pprofMatchingGoroutines(id, events)
if err != nil {
return err
}
prof := make(map[uint64]Record) prof := make(map[uint64]Record)
for _, ev := range events { for _, ev := range events {
switch ev.Type { switch ev.Type {
case trace.EvGoBlockSend, trace.EvGoBlockRecv, trace.EvGoBlockSelect, case trace.EvGoBlockSend, trace.EvGoBlockRecv, trace.EvGoBlockSelect,
trace.EvGoBlockSync, trace.EvGoBlockCond, trace.EvGoBlockGC: trace.EvGoBlockSync, trace.EvGoBlockCond, trace.EvGoBlockGC:
// TODO(hyangah): figure out why EvGoBlockGC should be here.
// EvGoBlockGC indicates the goroutine blocks on GC assist, not
// on synchronization primitives.
default: default:
continue continue
} }
if ev.Link == nil || ev.StkID == 0 || len(ev.Stk) == 0 { if ev.Link == nil || ev.StkID == 0 || len(ev.Stk) == 0 {
continue continue
} }
if goroutines != nil && !goroutines[ev.G] {
continue
}
rec := prof[ev.StkID] rec := prof[ev.StkID]
rec.stk = ev.Stk rec.stk = ev.Stk
rec.n++ rec.n++
...@@ -94,16 +143,25 @@ func pprofBlock(w io.Writer) error { ...@@ -94,16 +143,25 @@ func pprofBlock(w io.Writer) error {
} }
// pprofSyscall generates syscall pprof-like profile (time spent blocked in syscalls). // pprofSyscall generates syscall pprof-like profile (time spent blocked in syscalls).
func pprofSyscall(w io.Writer) error { func pprofSyscall(w io.Writer, id string) error {
events, err := parseEvents() events, err := parseEvents()
if err != nil { if err != nil {
return err return err
} }
goroutines, err := pprofMatchingGoroutines(id, events)
if err != nil {
return err
}
prof := make(map[uint64]Record) prof := make(map[uint64]Record)
for _, ev := range events { for _, ev := range events {
if ev.Type != trace.EvGoSysCall || ev.Link == nil || ev.StkID == 0 || len(ev.Stk) == 0 { if ev.Type != trace.EvGoSysCall || ev.Link == nil || ev.StkID == 0 || len(ev.Stk) == 0 {
continue continue
} }
if goroutines != nil && !goroutines[ev.G] {
continue
}
rec := prof[ev.StkID] rec := prof[ev.StkID]
rec.stk = ev.Stk rec.stk = ev.Stk
rec.n++ rec.n++
...@@ -115,17 +173,25 @@ func pprofSyscall(w io.Writer) error { ...@@ -115,17 +173,25 @@ func pprofSyscall(w io.Writer) error {
// pprofSched generates 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 pprofSched(w io.Writer) error { func pprofSched(w io.Writer, id string) error {
events, err := parseEvents() events, err := parseEvents()
if err != nil { if err != nil {
return err return err
} }
goroutines, err := pprofMatchingGoroutines(id, events)
if err != nil {
return err
}
prof := make(map[uint64]Record) prof := make(map[uint64]Record)
for _, ev := range events { for _, ev := range events {
if (ev.Type != trace.EvGoUnblock && ev.Type != trace.EvGoCreate) || if (ev.Type != trace.EvGoUnblock && ev.Type != trace.EvGoCreate) ||
ev.Link == nil || ev.StkID == 0 || len(ev.Stk) == 0 { ev.Link == nil || ev.StkID == 0 || len(ev.Stk) == 0 {
continue continue
} }
if goroutines != nil && !goroutines[ev.G] {
continue
}
rec := prof[ev.StkID] rec := prof[ev.StkID]
rec.stk = ev.Stk rec.stk = ev.Stk
rec.n++ rec.n++
...@@ -136,8 +202,20 @@ func pprofSched(w io.Writer) error { ...@@ -136,8 +202,20 @@ func pprofSched(w io.Writer) error {
} }
// serveSVGProfile serves pprof-like profile generated by prof as svg. // serveSVGProfile serves pprof-like profile generated by prof as svg.
func serveSVGProfile(prof func(w io.Writer) error) http.HandlerFunc { func serveSVGProfile(prof func(w io.Writer, id string) error) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
if r.FormValue("raw") != "" {
w.Header().Set("Content-Type", "application/octet-stream")
if err := prof(w, r.FormValue("id")); err != nil {
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
w.Header().Set("X-Go-Pprof", "1")
http.Error(w, fmt.Sprintf("failed to get profile: %v", err), http.StatusInternalServerError)
return
}
return
}
blockf, err := ioutil.TempFile("", "block") blockf, err := ioutil.TempFile("", "block")
if err != nil { if err != nil {
http.Error(w, fmt.Sprintf("failed to create temp file: %v", err), http.StatusInternalServerError) http.Error(w, fmt.Sprintf("failed to create temp file: %v", err), http.StatusInternalServerError)
...@@ -148,7 +226,7 @@ func serveSVGProfile(prof func(w io.Writer) error) http.HandlerFunc { ...@@ -148,7 +226,7 @@ func serveSVGProfile(prof func(w io.Writer) error) http.HandlerFunc {
os.Remove(blockf.Name()) os.Remove(blockf.Name())
}() }()
blockb := bufio.NewWriter(blockf) blockb := bufio.NewWriter(blockf)
if err := prof(blockb); err != nil { if err := prof(blockb, r.FormValue("id")); err != nil {
http.Error(w, fmt.Sprintf("failed to generate profile: %v", err), http.StatusInternalServerError) http.Error(w, fmt.Sprintf("failed to generate profile: %v", err), http.StatusInternalServerError)
return return
} }
......
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