mirror of
https://github.com/golang/go.git
synced 2025-12-08 06:10:04 +00:00
cmd/trace: add new command
Trace command allows to visualize and analyze traces. Run as: $ go tool trace binary trace.file The commands opens web browser with the main page, which contains links for trace visualization, blocking profiler, network IO profiler and per-goroutine traces. Also move trace parser from runtime/pprof/trace_parser_test.go to internal/trace/parser.go, so that it can be shared between tests and the command. Change-Id: Ic97ed59ad6e4c7e1dc9eca5e979701a2b4aed7cf Reviewed-on: https://go-review.googlesource.com/3601 Reviewed-by: Andrew Gerrand <adg@golang.org>
This commit is contained in:
parent
58125ffe73
commit
edadffa2f3
11 changed files with 4768 additions and 666 deletions
6
misc/trace/README.md
Normal file
6
misc/trace/README.md
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
This directory contains helper file for trace viewer (go tool trace).
|
||||
|
||||
trace_viewer_lean.html was generated following instructions in:
|
||||
https://github.com/google/trace-viewer/wiki/Embedding
|
||||
on revision 895aa74558d19d91906fb720df6458244ef160c6 using:
|
||||
trace-viewer$ ./vulcanize_trace_viewer --config=lean
|
||||
2868
misc/trace/trace_viewer_lean.html
Normal file
2868
misc/trace/trace_viewer_lean.html
Normal file
File diff suppressed because one or more lines are too long
|
|
@ -400,6 +400,7 @@ var goTools = map[string]targetDir{
|
|||
"cmd/objdump": toTool,
|
||||
"cmd/pack": toTool,
|
||||
"cmd/pprof": toTool,
|
||||
"cmd/trace": toTool,
|
||||
"cmd/yacc": toTool,
|
||||
"golang.org/x/tools/cmd/cover": toTool,
|
||||
"golang.org/x/tools/cmd/godoc": toBin,
|
||||
|
|
|
|||
328
src/cmd/trace/goroutines.go
Normal file
328
src/cmd/trace/goroutines.go
Normal file
|
|
@ -0,0 +1,328 @@
|
|||
// Copyright 2014 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Goroutine-related profiles.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"html/template"
|
||||
"internal/trace"
|
||||
"net/http"
|
||||
"sort"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
func init() {
|
||||
http.HandleFunc("/goroutines", httpGoroutines)
|
||||
http.HandleFunc("/goroutine", httpGoroutine)
|
||||
}
|
||||
|
||||
// gtype describes a group of goroutines grouped by start PC.
|
||||
type gtype struct {
|
||||
ID uint64 // Unique identifier (PC).
|
||||
Name string // Start function.
|
||||
N int // Total number of goroutines in this group.
|
||||
ExecTime int64 // Total execution time of all goroutines in this group.
|
||||
}
|
||||
|
||||
type gtypeList []gtype
|
||||
|
||||
func (l gtypeList) Len() int {
|
||||
return len(l)
|
||||
}
|
||||
|
||||
func (l gtypeList) Less(i, j int) bool {
|
||||
return l[i].ExecTime > l[j].ExecTime
|
||||
}
|
||||
|
||||
func (l gtypeList) Swap(i, j int) {
|
||||
l[i], l[j] = l[j], l[i]
|
||||
}
|
||||
|
||||
// gdesc desribes a single goroutine.
|
||||
type gdesc struct {
|
||||
ID uint64
|
||||
Name string
|
||||
PC uint64
|
||||
CreateTime int64
|
||||
StartTime int64
|
||||
EndTime int64
|
||||
LastStart int64
|
||||
|
||||
ExecTime int64
|
||||
SchedWaitTime int64
|
||||
IOTime int64
|
||||
BlockTime int64
|
||||
SyscallTime int64
|
||||
GCTime int64
|
||||
SweepTime int64
|
||||
TotalTime int64
|
||||
|
||||
blockNetTime int64
|
||||
blockSyncTime int64
|
||||
blockSyscallTime int64
|
||||
blockSweepTime int64
|
||||
blockGCTime int64
|
||||
blockSchedTime int64
|
||||
}
|
||||
|
||||
type gdescList []*gdesc
|
||||
|
||||
func (l gdescList) Len() int {
|
||||
return len(l)
|
||||
}
|
||||
|
||||
func (l gdescList) Less(i, j int) bool {
|
||||
return l[i].TotalTime > l[j].TotalTime
|
||||
}
|
||||
|
||||
func (l gdescList) Swap(i, j int) {
|
||||
l[i], l[j] = l[j], l[i]
|
||||
}
|
||||
|
||||
var gs = make(map[uint64]*gdesc)
|
||||
|
||||
// analyzeGoroutines generates list gdesc's from the trace and stores it in gs.
|
||||
func analyzeGoroutines(events []*trace.Event) {
|
||||
if len(gs) > 0 { //!!! racy
|
||||
return
|
||||
}
|
||||
var lastTs int64
|
||||
var gcStartTime int64
|
||||
for _, ev := range events {
|
||||
lastTs = ev.Ts
|
||||
switch ev.Type {
|
||||
case trace.EvGoCreate:
|
||||
g := &gdesc{CreateTime: ev.Ts}
|
||||
g.blockSchedTime = ev.Ts
|
||||
gs[ev.Args[0]] = g
|
||||
case trace.EvGoStart:
|
||||
g := gs[ev.G]
|
||||
if g.PC == 0 {
|
||||
g.PC = ev.Stk[0].PC
|
||||
g.Name = ev.Stk[0].Fn
|
||||
}
|
||||
g.LastStart = ev.Ts
|
||||
if g.StartTime == 0 {
|
||||
g.StartTime = ev.Ts
|
||||
}
|
||||
if g.blockSchedTime != 0 {
|
||||
g.SchedWaitTime += ev.Ts - g.blockSchedTime
|
||||
g.blockSchedTime = 0
|
||||
}
|
||||
case trace.EvGoEnd, trace.EvGoStop:
|
||||
g := gs[ev.G]
|
||||
g.ExecTime += ev.Ts - g.LastStart
|
||||
g.TotalTime = ev.Ts - g.CreateTime
|
||||
g.EndTime = ev.Ts
|
||||
case trace.EvGoBlockSend, trace.EvGoBlockRecv, trace.EvGoBlockSelect,
|
||||
trace.EvGoBlockSync, trace.EvGoBlockCond:
|
||||
g := gs[ev.G]
|
||||
g.ExecTime += ev.Ts - g.LastStart
|
||||
g.blockSyncTime = ev.Ts
|
||||
case trace.EvGoSched, trace.EvGoPreempt:
|
||||
g := gs[ev.G]
|
||||
g.ExecTime += ev.Ts - g.LastStart
|
||||
g.blockSchedTime = ev.Ts
|
||||
case trace.EvGoSleep, trace.EvGoBlock:
|
||||
g := gs[ev.G]
|
||||
g.ExecTime += ev.Ts - g.LastStart
|
||||
case trace.EvGoBlockNet:
|
||||
g := gs[ev.G]
|
||||
g.ExecTime += ev.Ts - g.LastStart
|
||||
g.blockNetTime = ev.Ts
|
||||
case trace.EvGoUnblock:
|
||||
g := gs[ev.Args[0]]
|
||||
if g.blockNetTime != 0 {
|
||||
g.IOTime += ev.Ts - g.blockNetTime
|
||||
g.blockNetTime = 0
|
||||
}
|
||||
if g.blockSyncTime != 0 {
|
||||
g.BlockTime += ev.Ts - g.blockSyncTime
|
||||
g.blockSyncTime = 0
|
||||
}
|
||||
g.blockSchedTime = ev.Ts
|
||||
case trace.EvGoSysBlock:
|
||||
g := gs[ev.G]
|
||||
g.ExecTime += ev.Ts - g.LastStart
|
||||
g.blockSyscallTime = ev.Ts
|
||||
case trace.EvGoSysExit:
|
||||
g := gs[ev.G]
|
||||
if g.blockSyscallTime != 0 {
|
||||
g.SyscallTime += ev.Ts - g.blockSyscallTime
|
||||
g.blockSyscallTime = 0
|
||||
}
|
||||
g.blockSchedTime = ev.Ts
|
||||
case trace.EvGCSweepStart:
|
||||
g := gs[ev.G]
|
||||
if g != nil {
|
||||
// Sweep can happen during GC on system goroutine.
|
||||
g.blockSweepTime = ev.Ts
|
||||
}
|
||||
case trace.EvGCSweepDone:
|
||||
g := gs[ev.G]
|
||||
if g != nil && g.blockSweepTime != 0 {
|
||||
g.SweepTime += ev.Ts - g.blockSweepTime
|
||||
g.blockSweepTime = 0
|
||||
}
|
||||
case trace.EvGCStart:
|
||||
gcStartTime = ev.Ts
|
||||
case trace.EvGCDone:
|
||||
for _, g := range gs {
|
||||
if g.EndTime == 0 {
|
||||
g.GCTime += ev.Ts - gcStartTime
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, g := range gs {
|
||||
if g.TotalTime == 0 {
|
||||
g.TotalTime = lastTs - g.CreateTime
|
||||
}
|
||||
if g.EndTime == 0 {
|
||||
g.EndTime = lastTs
|
||||
}
|
||||
if g.blockNetTime != 0 {
|
||||
g.IOTime += lastTs - g.blockNetTime
|
||||
g.blockNetTime = 0
|
||||
}
|
||||
if g.blockSyncTime != 0 {
|
||||
g.BlockTime += lastTs - g.blockSyncTime
|
||||
g.blockSyncTime = 0
|
||||
}
|
||||
if g.blockSyscallTime != 0 {
|
||||
g.SyscallTime += lastTs - g.blockSyscallTime
|
||||
g.blockSyscallTime = 0
|
||||
}
|
||||
if g.blockSchedTime != 0 {
|
||||
g.SchedWaitTime += lastTs - g.blockSchedTime
|
||||
g.blockSchedTime = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// httpGoroutines serves list of goroutine groups.
|
||||
func httpGoroutines(w http.ResponseWriter, r *http.Request) {
|
||||
events, err := parseEvents()
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
analyzeGoroutines(events)
|
||||
gss := make(map[uint64]gtype)
|
||||
for _, g := range gs {
|
||||
gs1 := gss[g.PC]
|
||||
gs1.ID = g.PC
|
||||
gs1.Name = g.Name
|
||||
gs1.N++
|
||||
gs1.ExecTime += g.ExecTime
|
||||
gss[g.PC] = gs1
|
||||
}
|
||||
var glist gtypeList
|
||||
for k, v := range gss {
|
||||
v.ID = k
|
||||
glist = append(glist, v)
|
||||
}
|
||||
sort.Sort(glist)
|
||||
templGoroutines.Execute(w, glist)
|
||||
}
|
||||
|
||||
var templGoroutines = template.Must(template.New("").Parse(`
|
||||
<html>
|
||||
<body>
|
||||
Goroutines: <br>
|
||||
{{range $}}
|
||||
<a href="/goroutine?id={{.ID}}">{{.Name}}</a> N={{.N}} <br>
|
||||
{{end}}
|
||||
</body>
|
||||
</html>
|
||||
`))
|
||||
|
||||
// httpGoroutine serves list of goroutines in a particular group.
|
||||
func httpGoroutine(w http.ResponseWriter, r *http.Request) {
|
||||
events, err := parseEvents()
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
pc, err := strconv.ParseUint(r.FormValue("id"), 10, 64)
|
||||
if err != nil {
|
||||
http.Error(w, fmt.Sprintf("failed to parse id parameter '%v': %v", r.FormValue("id"), err), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
analyzeGoroutines(events)
|
||||
var glist gdescList
|
||||
for gid, g := range gs {
|
||||
if g.PC != pc || g.ExecTime == 0 {
|
||||
continue
|
||||
}
|
||||
g.ID = gid
|
||||
glist = append(glist, g)
|
||||
}
|
||||
sort.Sort(glist)
|
||||
err = templGoroutine.Execute(w, glist)
|
||||
if err != nil {
|
||||
http.Error(w, fmt.Sprintf("failed to execute template: %v", err), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
var templGoroutine = template.Must(template.New("").Parse(`
|
||||
<html>
|
||||
<body>
|
||||
<table border="1" sortable="1">
|
||||
<tr>
|
||||
<th> Goroutine </th>
|
||||
<th> Total time, ns </th>
|
||||
<th> Execution time, ns </th>
|
||||
<th> Network wait time, ns </th>
|
||||
<th> Sync block time, ns </th>
|
||||
<th> Blocking syscall time, ns </th>
|
||||
<th> Scheduler wait time, ns </th>
|
||||
<th> GC sweeping time, ns </th>
|
||||
<th> GC pause time, ns </th>
|
||||
</tr>
|
||||
{{range $}}
|
||||
<tr>
|
||||
<td> <a href="/trace?goid={{.ID}}">{{.ID}}</a> </td>
|
||||
<td> {{.TotalTime}} </td>
|
||||
<td> {{.ExecTime}} </td>
|
||||
<td> {{.IOTime}} </td>
|
||||
<td> {{.BlockTime}} </td>
|
||||
<td> {{.SyscallTime}} </td>
|
||||
<td> {{.SchedWaitTime}} </td>
|
||||
<td> {{.SweepTime}} </td>
|
||||
<td> {{.GCTime}} </td>
|
||||
</tr>
|
||||
{{end}}
|
||||
</table>
|
||||
</body>
|
||||
</html>
|
||||
`))
|
||||
|
||||
// relatedGoroutines finds set of related goroutines that we need to include
|
||||
// into trace for goroutine goid.
|
||||
func relatedGoroutines(events []*trace.Event, goid uint64) map[uint64]bool {
|
||||
// BFS of depth 2 over "unblock" edges
|
||||
// (what goroutines unblock goroutine goid?).
|
||||
gmap := make(map[uint64]bool)
|
||||
gmap[goid] = true
|
||||
for i := 0; i < 2; i++ {
|
||||
gmap1 := make(map[uint64]bool)
|
||||
for g := range gmap {
|
||||
gmap1[g] = true
|
||||
}
|
||||
for _, ev := range events {
|
||||
if ev.Type == trace.EvGoUnblock && gmap[ev.Args[0]] {
|
||||
gmap1[ev.G] = true
|
||||
}
|
||||
}
|
||||
gmap = gmap1
|
||||
}
|
||||
gmap[0] = true // for GC events
|
||||
return gmap
|
||||
}
|
||||
156
src/cmd/trace/main.go
Normal file
156
src/cmd/trace/main.go
Normal file
|
|
@ -0,0 +1,156 @@
|
|||
// Copyright 2014 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
/*
|
||||
Trace is a tool for viewing trace files.
|
||||
|
||||
Trace files can be generated with:
|
||||
- runtime/pprof.StartTrace
|
||||
- net/http/pprof package
|
||||
- go test -trace
|
||||
|
||||
Example usage:
|
||||
Generate a trace file with 'go test':
|
||||
go test -trace trace.out pkg
|
||||
View the trace in a web browser:
|
||||
go tool trace pkg.test trace.out
|
||||
*/
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"flag"
|
||||
"fmt"
|
||||
"internal/trace"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/exec"
|
||||
"runtime"
|
||||
"sync"
|
||||
)
|
||||
|
||||
const usageMessage = "" +
|
||||
`Usage of 'go tool trace':
|
||||
Given a trace file produced by 'go test':
|
||||
go test -trace=trace.out pkg
|
||||
|
||||
Open a web browser displaying trace:
|
||||
go tool trace [flags] pkg.test trace.out
|
||||
|
||||
Flags:
|
||||
-http=addr: HTTP service address (e.g., ':6060')
|
||||
`
|
||||
|
||||
var (
|
||||
httpFlag = flag.String("http", "localhost:0", "HTTP service address (e.g., ':6060')")
|
||||
|
||||
// The binary file name, left here for serveSVGProfile.
|
||||
programBinary string
|
||||
traceFile string
|
||||
)
|
||||
|
||||
func main() {
|
||||
flag.Usage = func() {
|
||||
fmt.Fprintln(os.Stderr, usageMessage)
|
||||
os.Exit(2)
|
||||
}
|
||||
flag.Parse()
|
||||
|
||||
// Usage information when no arguments.
|
||||
if flag.NArg() != 2 {
|
||||
flag.Usage()
|
||||
}
|
||||
programBinary = flag.Arg(0)
|
||||
traceFile = flag.Arg(1)
|
||||
|
||||
ln, err := net.Listen("tcp", *httpFlag)
|
||||
if err != nil {
|
||||
dief("failed to create server socket: %v\n", err)
|
||||
}
|
||||
// Open browser.
|
||||
if !startBrowser("http://" + ln.Addr().String()) {
|
||||
dief("failed to start browser\n")
|
||||
}
|
||||
|
||||
// Parse and symbolize trace asynchronously while browser opens.
|
||||
go parseEvents()
|
||||
|
||||
// Start http server.
|
||||
http.HandleFunc("/", httpMain)
|
||||
err = http.Serve(ln, nil)
|
||||
dief("failed to start http server: %v\n", err)
|
||||
}
|
||||
|
||||
var loader struct {
|
||||
once sync.Once
|
||||
events []*trace.Event
|
||||
err error
|
||||
}
|
||||
|
||||
func parseEvents() ([]*trace.Event, error) {
|
||||
loader.once.Do(func() {
|
||||
tracef, err := os.Open(flag.Arg(1))
|
||||
if err != nil {
|
||||
loader.err = fmt.Errorf("failed to open trace file: %v", err)
|
||||
return
|
||||
}
|
||||
defer tracef.Close()
|
||||
|
||||
// Parse and symbolize.
|
||||
events, err := trace.Parse(bufio.NewReader(tracef))
|
||||
if err != nil {
|
||||
loader.err = fmt.Errorf("failed to parse trace: %v", err)
|
||||
return
|
||||
}
|
||||
err = trace.Symbolize(events, programBinary)
|
||||
if err != nil {
|
||||
loader.err = fmt.Errorf("failed to symbolize trace: %v", err)
|
||||
return
|
||||
}
|
||||
loader.events = events
|
||||
})
|
||||
return loader.events, loader.err
|
||||
}
|
||||
|
||||
// httpMain serves the starting page.
|
||||
func httpMain(w http.ResponseWriter, r *http.Request) {
|
||||
w.Write(templMain)
|
||||
}
|
||||
|
||||
var templMain = []byte(`
|
||||
<html>
|
||||
<body>
|
||||
<a href="/trace">View trace</a><br>
|
||||
<a href="/goroutines">Goroutine analysis</a><br>
|
||||
<a href="/io">IO blocking profile</a><br>
|
||||
<a href="/block">Synchronization blocking profile</a><br>
|
||||
<a href="/syscall">Syscall blocking profile</a><br>
|
||||
<a href="/sched">Scheduler latency profile</a><br>
|
||||
</body>
|
||||
</html>
|
||||
`)
|
||||
|
||||
// startBrowser tries to open the URL in a browser
|
||||
// and reports whether it succeeds.
|
||||
// Note: copied from x/tools/cmd/cover/html.go
|
||||
func startBrowser(url string) bool {
|
||||
// try to start the browser
|
||||
var args []string
|
||||
switch runtime.GOOS {
|
||||
case "darwin":
|
||||
args = []string{"open"}
|
||||
case "windows":
|
||||
args = []string{"cmd", "/c", "start"}
|
||||
default:
|
||||
args = []string{"xdg-open"}
|
||||
}
|
||||
cmd := exec.Command(args[0], append(args[1:], url)...)
|
||||
return cmd.Start() == nil
|
||||
}
|
||||
|
||||
func dief(msg string, args ...interface{}) {
|
||||
fmt.Fprintf(os.Stderr, msg, args...)
|
||||
os.Exit(1)
|
||||
}
|
||||
162
src/cmd/trace/pprof.go
Normal file
162
src/cmd/trace/pprof.go
Normal file
|
|
@ -0,0 +1,162 @@
|
|||
// Copyright 2014 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Serving of pprof-like profiles.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"internal/trace"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/exec"
|
||||
)
|
||||
|
||||
func init() {
|
||||
http.HandleFunc("/io", httpIO)
|
||||
http.HandleFunc("/block", httpBlock)
|
||||
http.HandleFunc("/syscall", httpSyscall)
|
||||
http.HandleFunc("/sched", httpSched)
|
||||
}
|
||||
|
||||
// Record represents one entry in pprof-like profiles.
|
||||
type Record struct {
|
||||
stk []*trace.Frame
|
||||
n uint64
|
||||
time int64
|
||||
}
|
||||
|
||||
// httpIO serves IO pprof-like profile (time spent in IO wait).
|
||||
func httpIO(w http.ResponseWriter, r *http.Request) {
|
||||
events, err := parseEvents()
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
prof := make(map[uint64]Record)
|
||||
for _, ev := range events {
|
||||
if ev.Type != trace.EvGoBlockNet || ev.Link == nil || ev.StkID == 0 || len(ev.Stk) == 0 {
|
||||
continue
|
||||
}
|
||||
rec := prof[ev.StkID]
|
||||
rec.stk = ev.Stk
|
||||
rec.n++
|
||||
rec.time += ev.Link.Ts - ev.Ts
|
||||
prof[ev.StkID] = rec
|
||||
}
|
||||
serveSVGProfile(w, r, prof)
|
||||
}
|
||||
|
||||
// httpBlock serves blocking pprof-like profile (time spent blocked on synchronization primitives).
|
||||
func httpBlock(w http.ResponseWriter, r *http.Request) {
|
||||
events, err := parseEvents()
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
prof := make(map[uint64]Record)
|
||||
for _, ev := range events {
|
||||
switch ev.Type {
|
||||
case trace.EvGoBlockSend, trace.EvGoBlockRecv, trace.EvGoBlockSelect,
|
||||
trace.EvGoBlockSync, trace.EvGoBlockCond:
|
||||
default:
|
||||
continue
|
||||
}
|
||||
if ev.Link == nil || ev.StkID == 0 || len(ev.Stk) == 0 {
|
||||
continue
|
||||
}
|
||||
rec := prof[ev.StkID]
|
||||
rec.stk = ev.Stk
|
||||
rec.n++
|
||||
rec.time += ev.Link.Ts - ev.Ts
|
||||
prof[ev.StkID] = rec
|
||||
}
|
||||
serveSVGProfile(w, r, prof)
|
||||
}
|
||||
|
||||
// httpSyscall serves syscall pprof-like profile (time spent blocked in syscalls).
|
||||
func httpSyscall(w http.ResponseWriter, r *http.Request) {
|
||||
events, err := parseEvents()
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
prof := make(map[uint64]Record)
|
||||
for _, ev := range events {
|
||||
if ev.Type != trace.EvGoSysCall || ev.Link == nil || ev.StkID == 0 || len(ev.Stk) == 0 {
|
||||
continue
|
||||
}
|
||||
rec := prof[ev.StkID]
|
||||
rec.stk = ev.Stk
|
||||
rec.n++
|
||||
rec.time += ev.Link.Ts - ev.Ts
|
||||
prof[ev.StkID] = rec
|
||||
}
|
||||
serveSVGProfile(w, r, prof)
|
||||
}
|
||||
|
||||
// httpSched serves scheduler latency pprof-like profile
|
||||
// (time between a goroutine become runnable and actually scheduled for execution).
|
||||
func httpSched(w http.ResponseWriter, r *http.Request) {
|
||||
events, err := parseEvents()
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
prof := make(map[uint64]Record)
|
||||
for _, ev := range events {
|
||||
if (ev.Type != trace.EvGoUnblock && ev.Type != trace.EvGoCreate) ||
|
||||
ev.Link == nil || ev.StkID == 0 || len(ev.Stk) == 0 {
|
||||
continue
|
||||
}
|
||||
rec := prof[ev.StkID]
|
||||
rec.stk = ev.Stk
|
||||
rec.n++
|
||||
rec.time += ev.Link.Ts - ev.Ts
|
||||
prof[ev.StkID] = rec
|
||||
}
|
||||
serveSVGProfile(w, r, prof)
|
||||
}
|
||||
|
||||
// generateSVGProfile generates pprof-like profile stored in prof and writes in to w.
|
||||
func serveSVGProfile(w http.ResponseWriter, r *http.Request, prof map[uint64]Record) {
|
||||
blockf, err := ioutil.TempFile("", "block")
|
||||
if err != nil {
|
||||
http.Error(w, fmt.Sprintf("failed to create temp file: %v", err), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
defer os.Remove(blockf.Name())
|
||||
blockb := bufio.NewWriter(blockf)
|
||||
fmt.Fprintf(blockb, "--- contention:\ncycles/second=1000000000\n")
|
||||
for _, rec := range prof {
|
||||
fmt.Fprintf(blockb, "%v %v @", rec.time, rec.n)
|
||||
for _, f := range rec.stk {
|
||||
fmt.Fprintf(blockb, " 0x%x", f.PC)
|
||||
}
|
||||
fmt.Fprintf(blockb, "\n")
|
||||
}
|
||||
err = blockb.Flush()
|
||||
if err != nil {
|
||||
http.Error(w, fmt.Sprintf("failed to flush temp file: %v", err), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
err = blockf.Close()
|
||||
if err != nil {
|
||||
http.Error(w, fmt.Sprintf("failed to close temp file: %v", err), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
svgFilename := blockf.Name() + ".svg"
|
||||
_, err = exec.Command("go", "tool", "pprof", "-svg", "-output", svgFilename, programBinary, blockf.Name()).CombinedOutput()
|
||||
if err != nil {
|
||||
http.Error(w, fmt.Sprintf("failed to execute go tool pprof: %v", err), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
defer os.Remove(svgFilename)
|
||||
w.Header().Set("Content-Type", "image/svg+xml")
|
||||
http.ServeFile(w, r, svgFilename)
|
||||
}
|
||||
434
src/cmd/trace/trace.go
Normal file
434
src/cmd/trace/trace.go
Normal file
|
|
@ -0,0 +1,434 @@
|
|||
// Copyright 2014 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"internal/trace"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func init() {
|
||||
http.HandleFunc("/trace", httpTrace)
|
||||
http.HandleFunc("/jsontrace", httpJsonTrace)
|
||||
http.HandleFunc("/trace_viewer_html", httpTraceViewerHTML)
|
||||
}
|
||||
|
||||
// httpTrace serves either whole trace (goid==0) or trace for goid goroutine.
|
||||
func httpTrace(w http.ResponseWriter, r *http.Request) {
|
||||
_, err := parseEvents()
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
params := ""
|
||||
if goids := r.FormValue("goid"); goids != "" {
|
||||
goid, err := strconv.ParseUint(goids, 10, 64)
|
||||
if err != nil {
|
||||
http.Error(w, fmt.Sprintf("failed to parse goid parameter '%v': %v", goids, err), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
params = fmt.Sprintf("?goid=%v", goid)
|
||||
}
|
||||
html := strings.Replace(templTrace, "{{PARAMS}}", params, -1)
|
||||
w.Write([]byte(html))
|
||||
|
||||
}
|
||||
|
||||
var templTrace = `
|
||||
<html>
|
||||
<head>
|
||||
<link href="/trace_viewer_html" rel="import">
|
||||
<script>
|
||||
document.addEventListener("DOMContentLoaded", function(event) {
|
||||
var viewer = new tv.TraceViewer('/jsontrace{{PARAMS}}');
|
||||
document.body.appendChild(viewer);
|
||||
});
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
</html>
|
||||
`
|
||||
|
||||
// httpTraceViewerHTML serves static part of trace-viewer.
|
||||
// This URL is queried from templTrace HTML.
|
||||
func httpTraceViewerHTML(w http.ResponseWriter, r *http.Request) {
|
||||
http.ServeFile(w, r, filepath.Join(os.Getenv("GOROOT"), "misc", "trace", "trace_viewer_lean.html"))
|
||||
}
|
||||
|
||||
// httpJsonTrace serves json trace, requested from within templTrace HTML.
|
||||
func httpJsonTrace(w http.ResponseWriter, r *http.Request) {
|
||||
events, err := parseEvents()
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
params := &traceParams{
|
||||
events: events,
|
||||
endTime: int64(1<<63 - 1),
|
||||
}
|
||||
|
||||
if goids := r.FormValue("goid"); goids != "" {
|
||||
goid, err := strconv.ParseUint(goids, 10, 64)
|
||||
if err != nil {
|
||||
http.Error(w, fmt.Sprintf("failed to parse goid parameter '%v': %v", goids, err), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
analyzeGoroutines(events)
|
||||
g := gs[goid]
|
||||
params.gtrace = true
|
||||
params.startTime = g.StartTime
|
||||
params.endTime = g.EndTime
|
||||
params.maing = goid
|
||||
params.gs = relatedGoroutines(events, goid)
|
||||
}
|
||||
|
||||
err = json.NewEncoder(w).Encode(generateTrace(params))
|
||||
if err != nil {
|
||||
http.Error(w, fmt.Sprintf("failed to serialize trace: %v", err), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
type traceParams struct {
|
||||
events []*trace.Event
|
||||
gtrace bool
|
||||
startTime int64
|
||||
endTime int64
|
||||
maing uint64
|
||||
gs map[uint64]bool
|
||||
}
|
||||
|
||||
type traceContext struct {
|
||||
*traceParams
|
||||
data ViewerData
|
||||
frameTree frameNode
|
||||
frameSeq int
|
||||
arrowSeq uint64
|
||||
heapAlloc uint64
|
||||
nextGC uint64
|
||||
gcount uint64
|
||||
grunnable uint64
|
||||
grunning uint64
|
||||
insyscall uint64
|
||||
prunning uint64
|
||||
}
|
||||
|
||||
type frameNode struct {
|
||||
id int
|
||||
children map[uint64]frameNode
|
||||
}
|
||||
|
||||
type ViewerData struct {
|
||||
Events []*ViewerEvent `json:"traceEvents"`
|
||||
Frames map[string]ViewerFrame `json:"stackFrames"`
|
||||
}
|
||||
|
||||
type ViewerEvent struct {
|
||||
Name string `json:"name,omitempty"`
|
||||
Phase string `json:"ph"`
|
||||
Scope string `json:"s,omitempty"`
|
||||
Time int64 `json:"ts"`
|
||||
Dur int64 `json:"dur,omitempty"`
|
||||
Pid uint64 `json:"pid"`
|
||||
Tid uint64 `json:"tid"`
|
||||
ID uint64 `json:"id,omitempty"`
|
||||
Stack int `json:"sf,omitempty"`
|
||||
EndStack int `json:"esf,omitempty"`
|
||||
Arg interface{} `json:"args,omitempty"`
|
||||
}
|
||||
|
||||
type ViewerFrame struct {
|
||||
Name string `json:"name"`
|
||||
Parent int `json:"parent,omitempty"`
|
||||
}
|
||||
|
||||
type NameArg struct {
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
type SortIndexArg struct {
|
||||
Index int `json:"sort_index"`
|
||||
}
|
||||
|
||||
// generateTrace generates json trace for trace-viewer:
|
||||
// https://github.com/google/trace-viewer
|
||||
// Trace format is described at:
|
||||
// https://docs.google.com/document/d/1CvAClvFfyA5R-PhYUmn5OOQtYMH4h6I0nSsKchNAySU/view
|
||||
// If gtrace=true, generate trace for goroutine goid, otherwise whole trace.
|
||||
// startTime, endTime determine part of the trace that we are interested in.
|
||||
// gset restricts goroutines that are included in the resulting trace.
|
||||
func generateTrace(params *traceParams) ViewerData {
|
||||
ctx := &traceContext{traceParams: params}
|
||||
ctx.frameTree.children = make(map[uint64]frameNode)
|
||||
ctx.data.Frames = make(map[string]ViewerFrame)
|
||||
maxProc := 0
|
||||
gnames := make(map[uint64]string)
|
||||
for _, ev := range ctx.events {
|
||||
// Handle trace.EvGoStart separately, because we need the goroutine name
|
||||
// even if ignore the event otherwise.
|
||||
if ev.Type == trace.EvGoStart {
|
||||
if _, ok := gnames[ev.G]; !ok {
|
||||
if len(ev.Stk) > 0 {
|
||||
gnames[ev.G] = fmt.Sprintf("G%v %s", ev.G, ev.Stk[0].Fn)
|
||||
} else {
|
||||
gnames[ev.G] = fmt.Sprintf("G%v", ev.G)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Ignore events that are from uninteresting goroutines
|
||||
// or outside of the interesting timeframe.
|
||||
if ctx.gs != nil && ev.P < trace.FakeP && !ctx.gs[ev.G] {
|
||||
continue
|
||||
}
|
||||
if ev.Ts < ctx.startTime || ev.Ts > ctx.endTime {
|
||||
continue
|
||||
}
|
||||
|
||||
if ev.P < trace.FakeP && ev.P > maxProc {
|
||||
maxProc = ev.P
|
||||
}
|
||||
|
||||
switch ev.Type {
|
||||
case trace.EvProcStart:
|
||||
if ctx.gtrace {
|
||||
continue
|
||||
}
|
||||
ctx.prunning++
|
||||
ctx.emitThreadCounters(ev)
|
||||
ctx.emitInstant(ev, "proc start")
|
||||
case trace.EvProcStop:
|
||||
if ctx.gtrace {
|
||||
continue
|
||||
}
|
||||
ctx.prunning--
|
||||
ctx.emitThreadCounters(ev)
|
||||
ctx.emitInstant(ev, "proc stop")
|
||||
case trace.EvGCStart:
|
||||
ctx.emitSlice(ev, "GC")
|
||||
case trace.EvGCDone:
|
||||
case trace.EvGCScanStart:
|
||||
if ctx.gtrace {
|
||||
continue
|
||||
}
|
||||
ctx.emitSlice(ev, "MARK")
|
||||
case trace.EvGCScanDone:
|
||||
case trace.EvGCSweepStart:
|
||||
ctx.emitSlice(ev, "SWEEP")
|
||||
case trace.EvGCSweepDone:
|
||||
case trace.EvGoStart:
|
||||
ctx.grunnable--
|
||||
ctx.grunning++
|
||||
ctx.emitGoroutineCounters(ev)
|
||||
ctx.emitSlice(ev, gnames[ev.G])
|
||||
case trace.EvGoCreate:
|
||||
ctx.gcount++
|
||||
ctx.grunnable++
|
||||
ctx.emitGoroutineCounters(ev)
|
||||
ctx.emitArrow(ev, "go")
|
||||
case trace.EvGoEnd:
|
||||
ctx.gcount--
|
||||
ctx.grunning--
|
||||
ctx.emitGoroutineCounters(ev)
|
||||
case trace.EvGoUnblock:
|
||||
ctx.grunnable++
|
||||
ctx.emitGoroutineCounters(ev)
|
||||
ctx.emitArrow(ev, "unblock")
|
||||
case trace.EvGoSysCall:
|
||||
ctx.emitInstant(ev, "syscall")
|
||||
case trace.EvGoSysExit:
|
||||
ctx.grunnable++
|
||||
ctx.emitGoroutineCounters(ev)
|
||||
ctx.insyscall--
|
||||
ctx.emitThreadCounters(ev)
|
||||
ctx.emitArrow(ev, "sysexit")
|
||||
case trace.EvGoSysBlock:
|
||||
ctx.grunning--
|
||||
ctx.emitGoroutineCounters(ev)
|
||||
ctx.insyscall++
|
||||
ctx.emitThreadCounters(ev)
|
||||
case trace.EvGoSched, trace.EvGoPreempt:
|
||||
ctx.grunnable++
|
||||
ctx.grunning--
|
||||
ctx.emitGoroutineCounters(ev)
|
||||
case trace.EvGoStop,
|
||||
trace.EvGoSleep, trace.EvGoBlock, trace.EvGoBlockSend, trace.EvGoBlockRecv,
|
||||
trace.EvGoBlockSelect, trace.EvGoBlockSync, trace.EvGoBlockCond, trace.EvGoBlockNet:
|
||||
ctx.grunning--
|
||||
ctx.emitGoroutineCounters(ev)
|
||||
case trace.EvGoWaiting:
|
||||
ctx.grunnable--
|
||||
ctx.emitGoroutineCounters(ev)
|
||||
case trace.EvGoInSyscall:
|
||||
ctx.insyscall++
|
||||
ctx.emitThreadCounters(ev)
|
||||
case trace.EvHeapAlloc:
|
||||
ctx.heapAlloc = ev.Args[0]
|
||||
ctx.emitHeapCounters(ev)
|
||||
case trace.EvNextGC:
|
||||
ctx.nextGC = ev.Args[0]
|
||||
ctx.emitHeapCounters(ev)
|
||||
}
|
||||
}
|
||||
|
||||
ctx.emit(&ViewerEvent{Name: "process_name", Phase: "M", Pid: 0, Arg: &NameArg{"PROCS"}})
|
||||
ctx.emit(&ViewerEvent{Name: "process_sort_index", Phase: "M", Pid: 0, Arg: &SortIndexArg{1}})
|
||||
|
||||
ctx.emit(&ViewerEvent{Name: "process_name", Phase: "M", Pid: 1, Arg: &NameArg{"STATS"}})
|
||||
ctx.emit(&ViewerEvent{Name: "process_sort_index", Phase: "M", Pid: 1, Arg: &SortIndexArg{0}})
|
||||
|
||||
ctx.emit(&ViewerEvent{Name: "thread_name", Phase: "M", Pid: 0, Tid: trace.NetpollP, Arg: &NameArg{"Network"}})
|
||||
ctx.emit(&ViewerEvent{Name: "thread_sort_index", Phase: "M", Pid: 0, Tid: trace.NetpollP, Arg: &SortIndexArg{-5}})
|
||||
|
||||
ctx.emit(&ViewerEvent{Name: "thread_name", Phase: "M", Pid: 0, Tid: trace.TimerP, Arg: &NameArg{"Timers"}})
|
||||
ctx.emit(&ViewerEvent{Name: "thread_sort_index", Phase: "M", Pid: 0, Tid: trace.TimerP, Arg: &SortIndexArg{-4}})
|
||||
|
||||
ctx.emit(&ViewerEvent{Name: "thread_name", Phase: "M", Pid: 0, Tid: trace.SyscallP, Arg: &NameArg{"Syscalls"}})
|
||||
ctx.emit(&ViewerEvent{Name: "thread_sort_index", Phase: "M", Pid: 0, Tid: trace.SyscallP, Arg: &SortIndexArg{-3}})
|
||||
|
||||
if !ctx.gtrace {
|
||||
for i := 0; i <= maxProc; i++ {
|
||||
ctx.emit(&ViewerEvent{Name: "thread_name", Phase: "M", Pid: 0, Tid: uint64(i), Arg: &NameArg{fmt.Sprintf("Proc %v", i)}})
|
||||
}
|
||||
}
|
||||
|
||||
if ctx.gtrace && ctx.gs != nil {
|
||||
for k, v := range gnames {
|
||||
if !ctx.gs[k] {
|
||||
continue
|
||||
}
|
||||
ctx.emit(&ViewerEvent{Name: "thread_name", Phase: "M", Pid: 0, Tid: k, Arg: &NameArg{v}})
|
||||
}
|
||||
ctx.emit(&ViewerEvent{Name: "thread_sort_index", Phase: "M", Pid: 0, Tid: ctx.maing, Arg: &SortIndexArg{-2}})
|
||||
ctx.emit(&ViewerEvent{Name: "thread_sort_index", Phase: "M", Pid: 0, Tid: 0, Arg: &SortIndexArg{-1}})
|
||||
}
|
||||
|
||||
return ctx.data
|
||||
}
|
||||
|
||||
func (ctx *traceContext) emit(e *ViewerEvent) {
|
||||
ctx.data.Events = append(ctx.data.Events, e)
|
||||
}
|
||||
|
||||
func (ctx *traceContext) time(ev *trace.Event) int64 {
|
||||
if ev.Ts < ctx.startTime || ev.Ts > ctx.endTime {
|
||||
fmt.Printf("ts=%v startTime=%v endTime\n", ev.Ts, ctx.startTime, ctx.endTime)
|
||||
panic("timestamp is outside of trace range")
|
||||
}
|
||||
// NOTE: trace viewer wants timestamps in microseconds and it does not
|
||||
// handle fractional timestamps (rounds them). We give it timestamps
|
||||
// in nanoseconds to avoid rounding. See:
|
||||
// https://github.com/google/trace-viewer/issues/624
|
||||
return ev.Ts - ctx.startTime
|
||||
}
|
||||
|
||||
func (ctx *traceContext) proc(ev *trace.Event) uint64 {
|
||||
if ctx.gtrace && ev.P < trace.FakeP {
|
||||
return ev.G
|
||||
} else {
|
||||
return uint64(ev.P)
|
||||
}
|
||||
}
|
||||
|
||||
func (ctx *traceContext) emitSlice(ev *trace.Event, name string) {
|
||||
ctx.emit(&ViewerEvent{
|
||||
Name: name,
|
||||
Phase: "X",
|
||||
Time: ctx.time(ev),
|
||||
Dur: ctx.time(ev.Link) - ctx.time(ev),
|
||||
Tid: ctx.proc(ev),
|
||||
//Stack: ctx.stack(ev.Stk),
|
||||
EndStack: ctx.stack(ev.Link.Stk),
|
||||
})
|
||||
}
|
||||
|
||||
func (ctx *traceContext) emitHeapCounters(ev *trace.Event) {
|
||||
type Arg struct {
|
||||
Allocated uint64
|
||||
NextGC uint64
|
||||
}
|
||||
if ctx.gtrace {
|
||||
return
|
||||
}
|
||||
diff := uint64(0)
|
||||
if ctx.nextGC > ctx.heapAlloc {
|
||||
diff = ctx.nextGC - ctx.heapAlloc
|
||||
}
|
||||
ctx.emit(&ViewerEvent{Name: "Heap", Phase: "C", Time: ctx.time(ev), Pid: 1, Arg: &Arg{ctx.heapAlloc, diff}})
|
||||
}
|
||||
|
||||
func (ctx *traceContext) emitGoroutineCounters(ev *trace.Event) {
|
||||
type Arg struct {
|
||||
Running uint64
|
||||
Runnable uint64
|
||||
}
|
||||
if ctx.gtrace {
|
||||
return
|
||||
}
|
||||
ctx.emit(&ViewerEvent{Name: "Goroutines", Phase: "C", Time: ctx.time(ev), Pid: 1, Arg: &Arg{ctx.grunning, ctx.grunnable}})
|
||||
}
|
||||
|
||||
func (ctx *traceContext) emitThreadCounters(ev *trace.Event) {
|
||||
type Arg struct {
|
||||
Running uint64
|
||||
InSyscall uint64
|
||||
}
|
||||
if ctx.gtrace {
|
||||
return
|
||||
}
|
||||
ctx.emit(&ViewerEvent{Name: "Threads", Phase: "C", Time: ctx.time(ev), Pid: 1, Arg: &Arg{ctx.prunning, ctx.insyscall}})
|
||||
}
|
||||
|
||||
func (ctx *traceContext) emitInstant(ev *trace.Event, name string) {
|
||||
ctx.emit(&ViewerEvent{Name: name, Phase: "I", Scope: "t", Time: ctx.time(ev), Tid: ctx.proc(ev), Stack: ctx.stack(ev.Stk)})
|
||||
}
|
||||
|
||||
func (ctx *traceContext) emitArrow(ev *trace.Event, name string) {
|
||||
if ev.Link == nil {
|
||||
// The other end of the arrow is not captured in the trace.
|
||||
// For example, a goroutine was unblocked but was not scheduled before trace stop.
|
||||
return
|
||||
}
|
||||
if ctx.gtrace && (!ctx.gs[ev.Link.G] || ev.Link.Ts < ctx.startTime || ev.Link.Ts > ctx.endTime) {
|
||||
return
|
||||
}
|
||||
|
||||
ctx.arrowSeq++
|
||||
ctx.emit(&ViewerEvent{Name: name, Phase: "s", Tid: ctx.proc(ev), ID: ctx.arrowSeq, Time: ctx.time(ev), Stack: ctx.stack(ev.Stk)})
|
||||
ctx.emit(&ViewerEvent{Name: name, Phase: "t", Tid: ctx.proc(ev.Link), ID: ctx.arrowSeq, Time: ctx.time(ev.Link)})
|
||||
}
|
||||
|
||||
func (ctx *traceContext) stack(stk []*trace.Frame) int {
|
||||
return ctx.buildBranch(ctx.frameTree, stk)
|
||||
}
|
||||
|
||||
// buildBranch builds one branch in the prefix tree rooted at ctx.frameTree.
|
||||
func (ctx *traceContext) buildBranch(parent frameNode, stk []*trace.Frame) int {
|
||||
if len(stk) == 0 {
|
||||
return parent.id
|
||||
}
|
||||
last := len(stk) - 1
|
||||
frame := stk[last]
|
||||
stk = stk[:last]
|
||||
|
||||
node, ok := parent.children[frame.PC]
|
||||
if !ok {
|
||||
ctx.frameSeq++
|
||||
node.id = ctx.frameSeq
|
||||
node.children = make(map[uint64]frameNode)
|
||||
parent.children[frame.PC] = node
|
||||
ctx.data.Frames[strconv.Itoa(node.id)] = ViewerFrame{fmt.Sprintf("%v:%v", frame.Fn, frame.Line), parent.id}
|
||||
}
|
||||
return ctx.buildBranch(node, stk)
|
||||
}
|
||||
662
src/internal/trace/parser.go
Normal file
662
src/internal/trace/parser.go
Normal file
|
|
@ -0,0 +1,662 @@
|
|||
// Copyright 2014 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package trace
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Event describes one event in the trace.
|
||||
type Event struct {
|
||||
Off int // offset in input file (for debugging and error reporting)
|
||||
Type byte // one of Ev*
|
||||
Ts int64 // timestamp in nanoseconds
|
||||
P int // P on which the event happened (can be one of TimerP, NetpollP, SyscallP)
|
||||
G uint64 // G on which the event happened
|
||||
StkID uint64 // unique stack ID
|
||||
Stk []*Frame // stack trace (can be empty)
|
||||
Args [2]uint64 // event-type-specific arguments
|
||||
// linked event (can be nil), depends on event type:
|
||||
// for GCStart: the GCStop
|
||||
// for GCScanStart: the GCScanDone
|
||||
// for GCSweepStart: the GCSweepDone
|
||||
// for GoCreate: first GoStart of the created goroutine
|
||||
// for GoStart: the associated GoEnd, GoBlock or other blocking event
|
||||
// for GoSched/GoPreempt: the next GoStart
|
||||
// for GoBlock and other blocking events: the unblock event
|
||||
// for GoUnblock: the associated GoStart
|
||||
// for blocking GoSysCall: the associated GoSysExit
|
||||
// for GoSysExit: the next GoStart
|
||||
Link *Event
|
||||
}
|
||||
|
||||
// Frame is a frame in stack traces.
|
||||
type Frame struct {
|
||||
PC uint64
|
||||
Fn string
|
||||
File string
|
||||
Line int
|
||||
}
|
||||
|
||||
const (
|
||||
// Special P identifiers:
|
||||
FakeP = 1000000 + iota
|
||||
TimerP // depicts timer unblocks
|
||||
NetpollP // depicts network unblocks
|
||||
SyscallP // depicts returns from syscalls
|
||||
)
|
||||
|
||||
// parseTrace parses, post-processes and verifies the trace.
|
||||
func Parse(r io.Reader) ([]*Event, error) {
|
||||
rawEvents, err := readTrace(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
events, err := parseEvents(rawEvents)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = postProcessTrace(events)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return events, nil
|
||||
}
|
||||
|
||||
// rawEvent is a helper type used during parsing.
|
||||
type rawEvent struct {
|
||||
off int
|
||||
typ byte
|
||||
args []uint64
|
||||
}
|
||||
|
||||
// readTrace does wire-format parsing and verification.
|
||||
// It does not care about specific event types and argument meaning.
|
||||
func readTrace(r io.Reader) ([]rawEvent, error) {
|
||||
// Read and validate trace header.
|
||||
var buf [8]byte
|
||||
off, err := r.Read(buf[:])
|
||||
if off != 8 || err != nil {
|
||||
return nil, fmt.Errorf("failed to read header: read %v, err %v", off, err)
|
||||
}
|
||||
if bytes.Compare(buf[:], []byte("gotrace\x00")) != 0 {
|
||||
return nil, fmt.Errorf("not a trace file")
|
||||
}
|
||||
|
||||
// Read events.
|
||||
var events []rawEvent
|
||||
for {
|
||||
// Read event type and number of arguments (1 byte).
|
||||
off0 := off
|
||||
n, err := r.Read(buf[:1])
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
if err != nil || n != 1 {
|
||||
return nil, fmt.Errorf("failed to read trace at offset 0x%x: n=%v err=%v", off0, n, err)
|
||||
}
|
||||
off += n
|
||||
typ := buf[0] << 2 >> 2
|
||||
narg := buf[0]>>6 + 1
|
||||
ev := rawEvent{typ: typ, off: off0}
|
||||
if narg <= 3 {
|
||||
for i := 0; i < int(narg); i++ {
|
||||
var v uint64
|
||||
v, off, err = readVal(r, off)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ev.args = append(ev.args, v)
|
||||
}
|
||||
} else {
|
||||
// If narg == 4, the first value is length of the event in bytes.
|
||||
var v uint64
|
||||
v, off, err = readVal(r, off)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
evLen := v
|
||||
off1 := off
|
||||
for evLen > uint64(off-off1) {
|
||||
v, off, err = readVal(r, off)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ev.args = append(ev.args, v)
|
||||
}
|
||||
if evLen != uint64(off-off1) {
|
||||
return nil, fmt.Errorf("event has wrong length at offset 0x%x: want %v, got %v", off0, evLen, off-off1)
|
||||
}
|
||||
}
|
||||
events = append(events, ev)
|
||||
}
|
||||
return events, nil
|
||||
}
|
||||
|
||||
// Parse events transforms raw events into events.
|
||||
// It does analyze and verify per-event-type arguments.
|
||||
func parseEvents(rawEvents []rawEvent) (events []*Event, err error) {
|
||||
var ticksPerSec, lastTs int64
|
||||
var lastG, timerGoid uint64
|
||||
var lastP int
|
||||
lastGs := make(map[int]uint64) // last goroutine running on P
|
||||
stacks := make(map[uint64][]*Frame)
|
||||
for _, raw := range rawEvents {
|
||||
if raw.typ == EvNone || raw.typ >= EvCount {
|
||||
err = fmt.Errorf("unknown event type %v at offset 0x%x", raw.typ, raw.off)
|
||||
return
|
||||
}
|
||||
desc := EventDescriptions[raw.typ]
|
||||
if desc.Name == "" {
|
||||
err = fmt.Errorf("missing description for event type %v", raw.typ)
|
||||
return
|
||||
}
|
||||
if raw.typ != EvStack {
|
||||
narg := len(desc.Args)
|
||||
if desc.Stack {
|
||||
narg++
|
||||
}
|
||||
if raw.typ != EvBatch && raw.typ != EvFrequency && raw.typ != EvTimerGoroutine {
|
||||
narg++ // timestamp
|
||||
}
|
||||
if len(raw.args) != narg {
|
||||
err = fmt.Errorf("%v has wrong number of arguments at offset 0x%x: want %v, got %v",
|
||||
desc.Name, raw.off, narg, len(raw.args))
|
||||
return
|
||||
}
|
||||
}
|
||||
switch raw.typ {
|
||||
case EvBatch:
|
||||
lastGs[lastP] = lastG
|
||||
lastP = int(raw.args[0])
|
||||
lastG = lastGs[lastP]
|
||||
lastTs = int64(raw.args[1])
|
||||
case EvFrequency:
|
||||
ticksPerSec = int64(raw.args[0])
|
||||
if ticksPerSec <= 0 {
|
||||
err = fmt.Errorf("EvFrequency contains invalid frequency %v at offset 0x%x",
|
||||
ticksPerSec, raw.off)
|
||||
return
|
||||
}
|
||||
case EvTimerGoroutine:
|
||||
timerGoid = raw.args[0]
|
||||
case EvStack:
|
||||
if len(raw.args) < 2 {
|
||||
err = fmt.Errorf("EvStack has wrong number of arguments at offset 0x%x: want at least 2, got %v",
|
||||
raw.off, len(raw.args))
|
||||
return
|
||||
}
|
||||
size := raw.args[1]
|
||||
if size > 1000 {
|
||||
err = fmt.Errorf("EvStack has bad number of frames at offset 0x%x: %v",
|
||||
raw.off, size)
|
||||
return
|
||||
}
|
||||
id := raw.args[0]
|
||||
if id != 0 && size > 0 {
|
||||
stk := make([]*Frame, size)
|
||||
for i := 0; i < int(size); i++ {
|
||||
stk[i] = &Frame{PC: raw.args[i+2]}
|
||||
}
|
||||
stacks[id] = stk
|
||||
}
|
||||
default:
|
||||
e := &Event{Off: raw.off, Type: raw.typ, P: lastP, G: lastG}
|
||||
e.Ts = lastTs + int64(raw.args[0])
|
||||
lastTs = e.Ts
|
||||
for i := range desc.Args {
|
||||
e.Args[i] = raw.args[i+1]
|
||||
}
|
||||
if desc.Stack {
|
||||
e.StkID = raw.args[len(desc.Args)+1]
|
||||
}
|
||||
switch raw.typ {
|
||||
case EvGoStart:
|
||||
lastG = e.Args[0]
|
||||
e.G = lastG
|
||||
case EvGCStart, EvGCDone, EvGCScanStart, EvGCScanDone:
|
||||
e.G = 0
|
||||
case EvGoEnd, EvGoStop, EvGoSched, EvGoPreempt,
|
||||
EvGoSleep, EvGoBlock, EvGoBlockSend, EvGoBlockRecv,
|
||||
EvGoBlockSelect, EvGoBlockSync, EvGoBlockCond, EvGoBlockNet,
|
||||
EvGoSysBlock:
|
||||
lastG = 0
|
||||
}
|
||||
events = append(events, e)
|
||||
}
|
||||
}
|
||||
|
||||
// Attach stack traces.
|
||||
for _, ev := range events {
|
||||
if ev.StkID != 0 {
|
||||
ev.Stk = stacks[ev.StkID]
|
||||
}
|
||||
}
|
||||
|
||||
// Sort by time and translate cpu ticks to real time.
|
||||
sort.Sort(eventList(events))
|
||||
if ticksPerSec == 0 {
|
||||
err = fmt.Errorf("no EvFrequency event")
|
||||
return
|
||||
}
|
||||
minTs := events[0].Ts
|
||||
for _, ev := range events {
|
||||
ev.Ts = (ev.Ts - minTs) * 1e9 / ticksPerSec
|
||||
// Move timers and syscalls to separate fake Ps.
|
||||
if timerGoid != 0 && ev.G == timerGoid && ev.Type == EvGoUnblock {
|
||||
ev.P = TimerP
|
||||
}
|
||||
if ev.Type == EvGoSysExit {
|
||||
ev.P = SyscallP
|
||||
ev.G = ev.Args[0]
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// postProcessTrace does inter-event verification and information restoration.
|
||||
// The resulting trace is guaranteed to be consistent
|
||||
// (for example, a P does not run two Gs at the same time, or a G is indeed
|
||||
// blocked before an unblock event).
|
||||
func postProcessTrace(events []*Event) error {
|
||||
const (
|
||||
gDead = iota
|
||||
gRunnable
|
||||
gRunning
|
||||
gWaiting
|
||||
)
|
||||
type gdesc struct {
|
||||
state int
|
||||
ev *Event
|
||||
evStart *Event
|
||||
}
|
||||
type pdesc struct {
|
||||
running bool
|
||||
g uint64
|
||||
evGC *Event
|
||||
evScan *Event
|
||||
evSweep *Event
|
||||
}
|
||||
|
||||
gs := make(map[uint64]gdesc)
|
||||
ps := make(map[int]pdesc)
|
||||
gs[0] = gdesc{state: gRunning}
|
||||
|
||||
checkRunning := func(p pdesc, g gdesc, ev *Event) error {
|
||||
name := EventDescriptions[ev.Type].Name
|
||||
if g.state != gRunning {
|
||||
return fmt.Errorf("g %v is not running while %v (offset %v, time %v)", ev.G, name, ev.Off, ev.Ts)
|
||||
}
|
||||
if p.g != ev.G {
|
||||
return fmt.Errorf("p %v is not running g %v while %v (offset %v, time %v)", ev.P, ev.G, name, ev.Off, ev.Ts)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, ev := range events {
|
||||
g := gs[ev.G]
|
||||
p := ps[ev.P]
|
||||
|
||||
switch ev.Type {
|
||||
case EvProcStart:
|
||||
if p.running {
|
||||
return fmt.Errorf("p %v is running before start (offset %v, time %v)", ev.P, ev.Off, ev.Ts)
|
||||
}
|
||||
p.running = true
|
||||
case EvProcStop:
|
||||
if !p.running {
|
||||
return fmt.Errorf("p %v is not running before stop (offset %v, time %v)", ev.P, ev.Off, ev.Ts)
|
||||
}
|
||||
if p.g != 0 {
|
||||
return fmt.Errorf("p %v is running a goroutine %v during stop (offset %v, time %v)", ev.P, p.g, ev.Off, ev.Ts)
|
||||
}
|
||||
p.running = false
|
||||
case EvGCStart:
|
||||
if p.evGC != nil {
|
||||
return fmt.Errorf("previous GC is not ended before a new one (offset %v, time %v)", ev.Off, ev.Ts)
|
||||
}
|
||||
p.evGC = ev
|
||||
case EvGCDone:
|
||||
if p.evGC == nil {
|
||||
return fmt.Errorf("bogus GC end (offset %v, time %v)", ev.Off, ev.Ts)
|
||||
}
|
||||
p.evGC.Link = ev
|
||||
p.evGC = nil
|
||||
case EvGCScanStart:
|
||||
if p.evScan != nil {
|
||||
return fmt.Errorf("previous scanning is not ended before a new one (offset %v, time %v)", ev.Off, ev.Ts)
|
||||
}
|
||||
p.evScan = ev
|
||||
case EvGCScanDone:
|
||||
if p.evScan == nil {
|
||||
return fmt.Errorf("bogus scanning end (offset %v, time %v)", ev.Off, ev.Ts)
|
||||
}
|
||||
p.evScan.Link = ev
|
||||
p.evScan = nil
|
||||
case EvGCSweepStart:
|
||||
if p.evSweep != nil {
|
||||
return fmt.Errorf("previous sweeping is not ended before a new one (offset %v, time %v)", ev.Off, ev.Ts)
|
||||
}
|
||||
p.evSweep = ev
|
||||
case EvGCSweepDone:
|
||||
if p.evSweep == nil {
|
||||
return fmt.Errorf("bogus sweeping end (offset %v, time %v)", ev.Off, ev.Ts)
|
||||
}
|
||||
p.evSweep.Link = ev
|
||||
p.evSweep = nil
|
||||
case EvGoWaiting:
|
||||
g1 := gs[ev.Args[0]]
|
||||
if g1.state != gRunnable {
|
||||
return fmt.Errorf("g %v is not runnable before EvGoWaiting (offset %v, time %v)", ev.Args[0], ev.Off, ev.Ts)
|
||||
}
|
||||
g1.state = gWaiting
|
||||
gs[ev.Args[0]] = g1
|
||||
case EvGoInSyscall:
|
||||
// this case is intentionally left blank
|
||||
case EvGoCreate:
|
||||
if err := checkRunning(p, g, ev); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, ok := gs[ev.Args[0]]; ok {
|
||||
return fmt.Errorf("g %v already exists (offset %v, time %v)", ev.Args[0], ev.Off, ev.Ts)
|
||||
}
|
||||
gs[ev.Args[0]] = gdesc{state: gRunnable, ev: ev}
|
||||
case EvGoStart:
|
||||
if g.state != gRunnable {
|
||||
return fmt.Errorf("g %v is not runnable before start (offset %v, time %v)", ev.G, ev.Off, ev.Ts)
|
||||
}
|
||||
if p.g != 0 {
|
||||
return fmt.Errorf("p %v is already running g %v while start g %v (offset %v, time %v)", ev.P, p.g, ev.G, ev.Off, ev.Ts)
|
||||
}
|
||||
g.state = gRunning
|
||||
g.evStart = ev
|
||||
p.g = ev.G
|
||||
if g.ev != nil {
|
||||
if g.ev.Type == EvGoCreate {
|
||||
// +1 because symblizer expects return pc.
|
||||
ev.Stk = []*Frame{&Frame{PC: g.ev.Args[1] + 1}}
|
||||
}
|
||||
g.ev.Link = ev
|
||||
g.ev = nil
|
||||
}
|
||||
case EvGoEnd, EvGoStop:
|
||||
if err := checkRunning(p, g, ev); err != nil {
|
||||
return err
|
||||
}
|
||||
g.evStart.Link = ev
|
||||
g.evStart = nil
|
||||
g.state = gDead
|
||||
p.g = 0
|
||||
case EvGoSched, EvGoPreempt:
|
||||
if err := checkRunning(p, g, ev); err != nil {
|
||||
return err
|
||||
}
|
||||
g.state = gRunnable
|
||||
g.evStart.Link = ev
|
||||
g.evStart = nil
|
||||
p.g = 0
|
||||
g.ev = ev
|
||||
case EvGoUnblock:
|
||||
if g.state != gRunning {
|
||||
return fmt.Errorf("g %v is not running while unpark (offset %v, time %v)", ev.G, ev.Off, ev.Ts)
|
||||
}
|
||||
if ev.P != TimerP && p.g != ev.G {
|
||||
return fmt.Errorf("p %v is not running g %v while unpark (offset %v, time %v)", ev.P, ev.G, ev.Off, ev.Ts)
|
||||
}
|
||||
g1 := gs[ev.Args[0]]
|
||||
if g1.state != gWaiting {
|
||||
return fmt.Errorf("g %v is not waiting before unpark (offset %v, time %v)", ev.Args[0], ev.Off, ev.Ts)
|
||||
}
|
||||
if g1.ev != nil && g1.ev.Type == EvGoBlockNet && ev.P != TimerP {
|
||||
ev.P = NetpollP
|
||||
}
|
||||
if g1.ev != nil {
|
||||
g1.ev.Link = ev
|
||||
}
|
||||
g1.state = gRunnable
|
||||
g1.ev = ev
|
||||
gs[ev.Args[0]] = g1
|
||||
case EvGoSysCall:
|
||||
if err := checkRunning(p, g, ev); err != nil {
|
||||
return err
|
||||
}
|
||||
g.ev = ev
|
||||
case EvGoSysBlock:
|
||||
if err := checkRunning(p, g, ev); err != nil {
|
||||
return err
|
||||
}
|
||||
g.state = gRunnable
|
||||
g.evStart.Link = ev
|
||||
g.evStart = nil
|
||||
p.g = 0
|
||||
case EvGoSysExit:
|
||||
if g.state != gRunnable {
|
||||
return fmt.Errorf("g %v is not runnable during syscall exit (offset %v, time %v)", ev.G, ev.Off, ev.Ts)
|
||||
}
|
||||
if g.ev != nil && g.ev.Type == EvGoSysCall {
|
||||
g.ev.Link = ev
|
||||
}
|
||||
g.ev = ev
|
||||
case EvGoSleep, EvGoBlock, EvGoBlockSend, EvGoBlockRecv,
|
||||
EvGoBlockSelect, EvGoBlockSync, EvGoBlockCond, EvGoBlockNet:
|
||||
if err := checkRunning(p, g, ev); err != nil {
|
||||
return err
|
||||
}
|
||||
g.state = gWaiting
|
||||
g.ev = ev
|
||||
g.evStart.Link = ev
|
||||
g.evStart = nil
|
||||
p.g = 0
|
||||
}
|
||||
|
||||
gs[ev.G] = g
|
||||
ps[ev.P] = p
|
||||
}
|
||||
|
||||
// TODO(dvyukov): restore stacks for EvGoStart events.
|
||||
// TODO(dvyukov): test that all EvGoStart events has non-nil Link.
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// symbolizeTrace attaches func/file/line info to stack traces.
|
||||
func Symbolize(events []*Event, bin string) error {
|
||||
// First, collect and dedup all pcs.
|
||||
pcs := make(map[uint64]*Frame)
|
||||
for _, ev := range events {
|
||||
for _, f := range ev.Stk {
|
||||
pcs[f.PC] = nil
|
||||
}
|
||||
}
|
||||
|
||||
// Start addr2line.
|
||||
cmd := exec.Command("go", "tool", "addr2line", bin)
|
||||
in, err := cmd.StdinPipe()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to pipe addr2line stdin: %v", err)
|
||||
}
|
||||
cmd.Stderr = os.Stderr
|
||||
out, err := cmd.StdoutPipe()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to pipe addr2line stdout: %v", err)
|
||||
}
|
||||
err = cmd.Start()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to start addr2line: %v", err)
|
||||
}
|
||||
outb := bufio.NewReader(out)
|
||||
|
||||
// Write all pcs to addr2line.
|
||||
// Need to copy pcs to an array, because map iteration order is non-deterministic.
|
||||
var pcArray []uint64
|
||||
for pc := range pcs {
|
||||
pcArray = append(pcArray, pc)
|
||||
_, err := fmt.Fprintf(in, "0x%x\n", pc-1)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to write to addr2line: %v", err)
|
||||
}
|
||||
}
|
||||
in.Close()
|
||||
|
||||
// Read in answers.
|
||||
for _, pc := range pcArray {
|
||||
fn, err := outb.ReadString('\n')
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read from addr2line: %v", err)
|
||||
}
|
||||
file, err := outb.ReadString('\n')
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read from addr2line: %v", err)
|
||||
}
|
||||
f := &Frame{PC: pc}
|
||||
f.Fn = fn[:len(fn)-1]
|
||||
f.File = file[:len(file)-1]
|
||||
if colon := strings.LastIndex(f.File, ":"); colon != -1 {
|
||||
ln, err := strconv.Atoi(f.File[colon+1:])
|
||||
if err == nil {
|
||||
f.File = f.File[:colon]
|
||||
f.Line = ln
|
||||
}
|
||||
}
|
||||
pcs[pc] = f
|
||||
}
|
||||
cmd.Wait()
|
||||
|
||||
// Replace frames in events array.
|
||||
for _, ev := range events {
|
||||
for i, f := range ev.Stk {
|
||||
ev.Stk[i] = pcs[f.PC]
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// readVal reads unsigned base-128 value from r.
|
||||
func readVal(r io.Reader, off0 int) (v uint64, off int, err error) {
|
||||
off = off0
|
||||
for i := 0; i < 10; i++ {
|
||||
var buf [1]byte
|
||||
var n int
|
||||
n, err = r.Read(buf[:])
|
||||
if err != nil || n != 1 {
|
||||
return 0, 0, fmt.Errorf("failed to read trace at offset: read %v, error %v", off0, n, err)
|
||||
}
|
||||
off++
|
||||
v |= uint64(buf[0]&0x7f) << (uint(i) * 7)
|
||||
if buf[0]&0x80 == 0 {
|
||||
return
|
||||
}
|
||||
}
|
||||
return 0, 0, fmt.Errorf("bad value at offset 0x%x", off0)
|
||||
}
|
||||
|
||||
type eventList []*Event
|
||||
|
||||
func (l eventList) Len() int {
|
||||
return len(l)
|
||||
}
|
||||
|
||||
func (l eventList) Less(i, j int) bool {
|
||||
return l[i].Ts < l[j].Ts
|
||||
}
|
||||
|
||||
func (l eventList) Swap(i, j int) {
|
||||
l[i], l[j] = l[j], l[i]
|
||||
}
|
||||
|
||||
// Event types in the trace.
|
||||
// Verbatim copy from src/runtime/trace.go.
|
||||
const (
|
||||
EvNone = 0 // unused
|
||||
EvBatch = 1 // start of per-P batch of events [pid, timestamp]
|
||||
EvFrequency = 2 // contains tracer timer frequency [frequency (ticks per second)]
|
||||
EvStack = 3 // stack [stack id, number of PCs, array of PCs]
|
||||
EvGomaxprocs = 4 // current value of GOMAXPROCS [timestamp, GOMAXPROCS, stack id]
|
||||
EvProcStart = 5 // start of P [timestamp]
|
||||
EvProcStop = 6 // stop of P [timestamp]
|
||||
EvGCStart = 7 // GC start [timestamp, stack id]
|
||||
EvGCDone = 8 // GC done [timestamp]
|
||||
EvGCScanStart = 9 // GC scan start [timestamp]
|
||||
EvGCScanDone = 10 // GC scan done [timestamp]
|
||||
EvGCSweepStart = 11 // GC sweep start [timestamp, stack id]
|
||||
EvGCSweepDone = 12 // GC sweep done [timestamp]
|
||||
EvGoCreate = 13 // goroutine creation [timestamp, new goroutine id, start PC, stack id]
|
||||
EvGoStart = 14 // goroutine starts running [timestamp, goroutine id]
|
||||
EvGoEnd = 15 // goroutine ends [timestamp]
|
||||
EvGoStop = 16 // goroutine stops (like in select{}) [timestamp, stack]
|
||||
EvGoSched = 17 // goroutine calls Gosched [timestamp, stack]
|
||||
EvGoPreempt = 18 // goroutine is preempted [timestamp, stack]
|
||||
EvGoSleep = 19 // goroutine calls Sleep [timestamp, stack]
|
||||
EvGoBlock = 20 // goroutine blocks [timestamp, stack]
|
||||
EvGoUnblock = 21 // goroutine is unblocked [timestamp, goroutine id, stack]
|
||||
EvGoBlockSend = 22 // goroutine blocks on chan send [timestamp, stack]
|
||||
EvGoBlockRecv = 23 // goroutine blocks on chan recv [timestamp, stack]
|
||||
EvGoBlockSelect = 24 // goroutine blocks on select [timestamp, stack]
|
||||
EvGoBlockSync = 25 // goroutine blocks on Mutex/RWMutex [timestamp, stack]
|
||||
EvGoBlockCond = 26 // goroutine blocks on Cond [timestamp, stack]
|
||||
EvGoBlockNet = 27 // goroutine blocks on network [timestamp, stack]
|
||||
EvGoSysCall = 28 // syscall enter [timestamp, stack]
|
||||
EvGoSysExit = 29 // syscall exit [timestamp, goroutine id]
|
||||
EvGoSysBlock = 30 // syscall blocks [timestamp, stack]
|
||||
EvGoWaiting = 31 // denotes that goroutine is blocked when tracing starts [goroutine id]
|
||||
EvGoInSyscall = 32 // denotes that goroutine is in syscall when tracing starts [goroutine id]
|
||||
EvHeapAlloc = 33 // memstats.heap_alloc change [timestamp, heap_alloc]
|
||||
EvNextGC = 34 // memstats.next_gc change [timestamp, next_gc]
|
||||
EvTimerGoroutine = 35 // denotes timer goroutine [timer goroutine id]
|
||||
EvCount = 36
|
||||
)
|
||||
|
||||
var EventDescriptions = [EvCount]struct {
|
||||
Name string
|
||||
Stack bool
|
||||
Args []string
|
||||
}{
|
||||
EvNone: {"None", false, []string{}},
|
||||
EvBatch: {"Batch", false, []string{"p", "ticks"}},
|
||||
EvFrequency: {"Frequency", false, []string{"freq"}},
|
||||
EvStack: {"Stack", false, []string{"id", "siz"}},
|
||||
EvGomaxprocs: {"Gomaxprocs", true, []string{"procs"}},
|
||||
EvProcStart: {"ProcStart", false, []string{}},
|
||||
EvProcStop: {"ProcStop", false, []string{}},
|
||||
EvGCStart: {"GCStart", true, []string{}},
|
||||
EvGCDone: {"GCDone", false, []string{}},
|
||||
EvGCScanStart: {"GCScanStart", false, []string{}},
|
||||
EvGCScanDone: {"GCScanDone", false, []string{}},
|
||||
EvGCSweepStart: {"GCSweepStart", true, []string{}},
|
||||
EvGCSweepDone: {"GCSweepDone", false, []string{}},
|
||||
EvGoCreate: {"GoCreate", true, []string{"g", "pc"}},
|
||||
EvGoStart: {"GoStart", false, []string{"g"}},
|
||||
EvGoEnd: {"GoEnd", false, []string{}},
|
||||
EvGoStop: {"GoStop", true, []string{}},
|
||||
EvGoSched: {"GoSched", true, []string{}},
|
||||
EvGoPreempt: {"GoPreempt", true, []string{}},
|
||||
EvGoSleep: {"GoSleep", true, []string{}},
|
||||
EvGoBlock: {"GoBlock", true, []string{}},
|
||||
EvGoUnblock: {"GoUnblock", true, []string{"g"}},
|
||||
EvGoBlockSend: {"GoBlockSend", true, []string{}},
|
||||
EvGoBlockRecv: {"GoBlockRecv", true, []string{}},
|
||||
EvGoBlockSelect: {"GoBlockSelect", true, []string{}},
|
||||
EvGoBlockSync: {"GoBlockSync", true, []string{}},
|
||||
EvGoBlockCond: {"GoBlockCond", true, []string{}},
|
||||
EvGoBlockNet: {"GoBlockNet", true, []string{}},
|
||||
EvGoSysCall: {"GoSysCall", true, []string{}},
|
||||
EvGoSysExit: {"GoSysExit", false, []string{"g"}},
|
||||
EvGoSysBlock: {"GoSysBlock", true, []string{}},
|
||||
EvGoWaiting: {"GoWaiting", false, []string{"g"}},
|
||||
EvGoInSyscall: {"GoInSyscall", false, []string{"g"}},
|
||||
EvHeapAlloc: {"HeapAlloc", false, []string{"mem"}},
|
||||
EvNextGC: {"NextGC", false, []string{"mem"}},
|
||||
EvTimerGoroutine: {"TimerGoroutine", false, []string{"g"}},
|
||||
}
|
||||
|
|
@ -1,656 +0,0 @@
|
|||
// Copyright 2014 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package pprof_test
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"os/exec"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Event describes one event in the trace.
|
||||
type Event struct {
|
||||
off int // offset in input file (for debugging and error reporting)
|
||||
typ byte // one of traceEv*
|
||||
ts int64 // timestamp in nanoseconds
|
||||
p int // P on which the event happened (can be one of timerP, netpollP, syscallP)
|
||||
g uint64 // G on which the event happened
|
||||
stkID uint64 // unique stack ID
|
||||
stk []*Frame // stack trace (can be empty)
|
||||
args [2]uint64 // event-type-specific arguments
|
||||
// linked event (can be nil), depends on event type:
|
||||
// for GCStart: the GCStop
|
||||
// for GCScanStart: the GCScanDone
|
||||
// for GCSweepStart: the GCSweepDone
|
||||
// for GoCreate: first GoStart of the created goroutine
|
||||
// for GoStart: the associated GoEnd, GoBlock or other blocking event
|
||||
// for GoSched/GoPreempt: the next GoStart
|
||||
// for GoBlock and other blocking events: the unblock event
|
||||
// for GoUnblock: the associated GoStart
|
||||
// for blocking GoSysCall: the associated GoSysExit
|
||||
// for GoSysExit: the next GoStart
|
||||
link *Event
|
||||
}
|
||||
|
||||
// Frame is a frame in stack traces.
|
||||
type Frame struct {
|
||||
pc uint64
|
||||
fn string
|
||||
file string
|
||||
line int
|
||||
}
|
||||
|
||||
const (
|
||||
// Special P identifiers:
|
||||
timerP = 1000000 + iota // depicts timer unblocks
|
||||
netpollP // depicts network unblocks
|
||||
syscallP // depicts returns from syscalls
|
||||
)
|
||||
|
||||
// parseTrace parses, post-processes and verifies the trace.
|
||||
func parseTrace(r io.Reader) ([]*Event, error) {
|
||||
rawEvents, err := readTrace(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
events, err := parseEvents(rawEvents)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = postProcessTrace(events)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return events, nil
|
||||
}
|
||||
|
||||
// RawEvent is a helper type used during parsing.
|
||||
type RawEvent struct {
|
||||
off int
|
||||
typ byte
|
||||
args []uint64
|
||||
}
|
||||
|
||||
// readTrace does wire-format parsing and verification.
|
||||
// It does not care about specific event types and argument meaning.
|
||||
func readTrace(r io.Reader) ([]RawEvent, error) {
|
||||
// Read and validate trace header.
|
||||
var buf [8]byte
|
||||
off, err := r.Read(buf[:])
|
||||
if off != 8 || err != nil {
|
||||
return nil, fmt.Errorf("failed to read header: read %v, err %v", off, err)
|
||||
}
|
||||
if bytes.Compare(buf[:], []byte("gotrace\x00")) != 0 {
|
||||
return nil, fmt.Errorf("not a trace file")
|
||||
}
|
||||
|
||||
// Read events.
|
||||
var events []RawEvent
|
||||
for {
|
||||
// Read event type and number of arguments (1 byte).
|
||||
off0 := off
|
||||
n, err := r.Read(buf[:1])
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
if err != nil || n != 1 {
|
||||
return nil, fmt.Errorf("failed to read trace at offset 0x%x: n=%v err=%v", off0, n, err)
|
||||
}
|
||||
off += n
|
||||
typ := buf[0] << 2 >> 2
|
||||
narg := buf[0]>>6 + 1
|
||||
ev := RawEvent{typ: typ, off: off0}
|
||||
if narg <= 3 {
|
||||
for i := 0; i < int(narg); i++ {
|
||||
var v uint64
|
||||
v, off, err = readVal(r, off)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ev.args = append(ev.args, v)
|
||||
}
|
||||
} else {
|
||||
// If narg == 4, the first value is length of the event in bytes.
|
||||
var v uint64
|
||||
v, off, err = readVal(r, off)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
evLen := v
|
||||
off1 := off
|
||||
for evLen > uint64(off-off1) {
|
||||
v, off, err = readVal(r, off)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ev.args = append(ev.args, v)
|
||||
}
|
||||
if evLen != uint64(off-off1) {
|
||||
return nil, fmt.Errorf("event has wrong length at offset 0x%x: want %v, got %v", off0, evLen, off-off1)
|
||||
}
|
||||
}
|
||||
events = append(events, ev)
|
||||
}
|
||||
return events, nil
|
||||
}
|
||||
|
||||
// Parse events transforms raw events into events.
|
||||
// It does analyze and verify per-event-type arguments.
|
||||
func parseEvents(rawEvents []RawEvent) (events []*Event, err error) {
|
||||
var ticksPerSec, lastTs int64
|
||||
var lastG, timerGoid uint64
|
||||
var lastP int
|
||||
lastGs := make(map[int]uint64) // last goroutine running on P
|
||||
stacks := make(map[uint64][]*Frame)
|
||||
for _, raw := range rawEvents {
|
||||
if raw.typ == traceEvNone || raw.typ >= traceEvCount {
|
||||
err = fmt.Errorf("unknown event type %v at offset 0x%x", raw.typ, raw.off)
|
||||
return
|
||||
}
|
||||
desc := evDescriptions[raw.typ]
|
||||
if desc.name == "" {
|
||||
err = fmt.Errorf("missing description for event type %v", raw.typ)
|
||||
return
|
||||
}
|
||||
if raw.typ != traceEvStack {
|
||||
narg := len(desc.args)
|
||||
if desc.stack {
|
||||
narg++
|
||||
}
|
||||
if raw.typ != traceEvBatch && raw.typ != traceEvFrequency && raw.typ != traceEvTimerGoroutine {
|
||||
narg++ // timestamp
|
||||
}
|
||||
if len(raw.args) != narg {
|
||||
err = fmt.Errorf("%v has wrong number of arguments at offset 0x%x: want %v, got %v",
|
||||
desc.name, raw.off, narg, len(raw.args))
|
||||
return
|
||||
}
|
||||
}
|
||||
switch raw.typ {
|
||||
case traceEvBatch:
|
||||
lastGs[lastP] = lastG
|
||||
lastP = int(raw.args[0])
|
||||
lastG = lastGs[lastP]
|
||||
lastTs = int64(raw.args[1])
|
||||
case traceEvFrequency:
|
||||
ticksPerSec = int64(raw.args[0])
|
||||
if ticksPerSec <= 0 {
|
||||
err = fmt.Errorf("traceEvFrequency contains invalid frequency %v at offset 0x%x",
|
||||
ticksPerSec, raw.off)
|
||||
return
|
||||
}
|
||||
case traceEvTimerGoroutine:
|
||||
timerGoid = raw.args[0]
|
||||
case traceEvStack:
|
||||
if len(raw.args) < 2 {
|
||||
err = fmt.Errorf("traceEvStack has wrong number of arguments at offset 0x%x: want at least 2, got %v",
|
||||
raw.off, len(raw.args))
|
||||
return
|
||||
}
|
||||
size := raw.args[1]
|
||||
if size > 1000 {
|
||||
err = fmt.Errorf("traceEvStack has bad number of frames at offset 0x%x: %v",
|
||||
raw.off, size)
|
||||
return
|
||||
}
|
||||
id := raw.args[0]
|
||||
if id != 0 && size > 0 {
|
||||
stk := make([]*Frame, size)
|
||||
for i := 0; i < int(size); i++ {
|
||||
stk[i] = &Frame{pc: raw.args[i+2]}
|
||||
}
|
||||
stacks[id] = stk
|
||||
}
|
||||
default:
|
||||
e := &Event{off: raw.off, typ: raw.typ, p: lastP, g: lastG}
|
||||
e.ts = lastTs + int64(raw.args[0])
|
||||
lastTs = e.ts
|
||||
for i := range desc.args {
|
||||
e.args[i] = raw.args[i+1]
|
||||
}
|
||||
if desc.stack {
|
||||
e.stkID = raw.args[len(desc.args)+1]
|
||||
}
|
||||
switch raw.typ {
|
||||
case traceEvGoStart:
|
||||
lastG = e.args[0]
|
||||
e.g = lastG
|
||||
case traceEvGCStart, traceEvGCDone, traceEvGCScanStart, traceEvGCScanDone:
|
||||
e.g = 0
|
||||
case traceEvGoEnd, traceEvGoStop, traceEvGoSched, traceEvGoPreempt,
|
||||
traceEvGoSleep, traceEvGoBlock, traceEvGoBlockSend, traceEvGoBlockRecv,
|
||||
traceEvGoBlockSelect, traceEvGoBlockSync, traceEvGoBlockCond, traceEvGoBlockNet,
|
||||
traceEvGoSysBlock:
|
||||
lastG = 0
|
||||
}
|
||||
events = append(events, e)
|
||||
}
|
||||
}
|
||||
|
||||
// Attach stack traces.
|
||||
for _, ev := range events {
|
||||
if ev.stkID != 0 {
|
||||
ev.stk = stacks[ev.stkID]
|
||||
}
|
||||
}
|
||||
|
||||
// Sort by time and translate cpu ticks to real time.
|
||||
sort.Sort(EventList(events))
|
||||
if ticksPerSec == 0 {
|
||||
err = fmt.Errorf("no traceEvFrequency event")
|
||||
return
|
||||
}
|
||||
minTs := events[0].ts
|
||||
for _, ev := range events {
|
||||
ev.ts = (ev.ts - minTs) * 1e9 / ticksPerSec
|
||||
// Move timers and syscalls to separate fake Ps.
|
||||
if timerGoid != 0 && ev.g == timerGoid && ev.typ == traceEvGoUnblock {
|
||||
ev.p = timerP
|
||||
}
|
||||
if ev.typ == traceEvGoSysExit {
|
||||
ev.p = syscallP
|
||||
ev.g = ev.args[0]
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// postProcessTrace does inter-event verification and information restoration.
|
||||
// The resulting trace is guaranteed to be consistent
|
||||
// (for example, a P does not run two Gs at the same time, or a G is indeed
|
||||
// blocked before an unblock event).
|
||||
func postProcessTrace(events []*Event) error {
|
||||
const (
|
||||
gDead = iota
|
||||
gRunnable
|
||||
gRunning
|
||||
gWaiting
|
||||
)
|
||||
type gdesc struct {
|
||||
state int
|
||||
ev *Event
|
||||
evStart *Event
|
||||
}
|
||||
type pdesc struct {
|
||||
running bool
|
||||
g uint64
|
||||
evGC *Event
|
||||
evScan *Event
|
||||
evSweep *Event
|
||||
}
|
||||
|
||||
gs := make(map[uint64]gdesc)
|
||||
ps := make(map[int]pdesc)
|
||||
gs[0] = gdesc{state: gRunning}
|
||||
|
||||
checkRunning := func(p pdesc, g gdesc, ev *Event) error {
|
||||
name := evDescriptions[ev.typ].name
|
||||
if g.state != gRunning {
|
||||
return fmt.Errorf("g %v is not running while %v (offset %v, time %v)", ev.g, name, ev.off, ev.ts)
|
||||
}
|
||||
if p.g != ev.g {
|
||||
return fmt.Errorf("p %v is not running g %v while %v (offset %v, time %v)", ev.p, ev.g, name, ev.off, ev.ts)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, ev := range events {
|
||||
g := gs[ev.g]
|
||||
p := ps[ev.p]
|
||||
|
||||
switch ev.typ {
|
||||
case traceEvProcStart:
|
||||
if p.running {
|
||||
return fmt.Errorf("p %v is running before start (offset %v, time %v)", ev.p, ev.off, ev.ts)
|
||||
}
|
||||
p.running = true
|
||||
case traceEvProcStop:
|
||||
if !p.running {
|
||||
return fmt.Errorf("p %v is not running before stop (offset %v, time %v)", ev.p, ev.off, ev.ts)
|
||||
}
|
||||
if p.g != 0 {
|
||||
return fmt.Errorf("p %v is running a goroutine %v during stop (offset %v, time %v)", ev.p, p.g, ev.off, ev.ts)
|
||||
}
|
||||
p.running = false
|
||||
case traceEvGCStart:
|
||||
if p.evGC != nil {
|
||||
return fmt.Errorf("previous GC is not ended before a new one (offset %v, time %v)", ev.off, ev.ts)
|
||||
}
|
||||
p.evGC = ev
|
||||
case traceEvGCDone:
|
||||
if p.evGC == nil {
|
||||
return fmt.Errorf("bogus GC end (offset %v, time %v)", ev.off, ev.ts)
|
||||
}
|
||||
p.evGC.link = ev
|
||||
p.evGC = nil
|
||||
case traceEvGCScanStart:
|
||||
if p.evScan != nil {
|
||||
return fmt.Errorf("previous scanning is not ended before a new one (offset %v, time %v)", ev.off, ev.ts)
|
||||
}
|
||||
p.evScan = ev
|
||||
case traceEvGCScanDone:
|
||||
if p.evScan == nil {
|
||||
return fmt.Errorf("bogus scanning end (offset %v, time %v)", ev.off, ev.ts)
|
||||
}
|
||||
p.evScan.link = ev
|
||||
p.evScan = nil
|
||||
case traceEvGCSweepStart:
|
||||
if p.evSweep != nil {
|
||||
return fmt.Errorf("previous sweeping is not ended before a new one (offset %v, time %v)", ev.off, ev.ts)
|
||||
}
|
||||
p.evSweep = ev
|
||||
case traceEvGCSweepDone:
|
||||
if p.evSweep == nil {
|
||||
return fmt.Errorf("bogus sweeping end (offset %v, time %v)", ev.off, ev.ts)
|
||||
}
|
||||
p.evSweep.link = ev
|
||||
p.evSweep = nil
|
||||
case traceEvGoWaiting:
|
||||
g1 := gs[ev.args[0]]
|
||||
if g1.state != gRunnable {
|
||||
return fmt.Errorf("g %v is not runnable before traceEvGoWaiting (offset %v, time %v)", ev.args[0], ev.off, ev.ts)
|
||||
}
|
||||
g1.state = gWaiting
|
||||
gs[ev.args[0]] = g1
|
||||
case traceEvGoInSyscall:
|
||||
// this case is intentionally left blank
|
||||
case traceEvGoCreate:
|
||||
if err := checkRunning(p, g, ev); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, ok := gs[ev.args[0]]; ok {
|
||||
return fmt.Errorf("g %v already exists (offset %v, time %v)", ev.args[0], ev.off, ev.ts)
|
||||
}
|
||||
gs[ev.args[0]] = gdesc{state: gRunnable, ev: ev}
|
||||
case traceEvGoStart:
|
||||
if g.state != gRunnable {
|
||||
return fmt.Errorf("g %v is not runnable before start (offset %v, time %v)", ev.g, ev.off, ev.ts)
|
||||
}
|
||||
if p.g != 0 {
|
||||
return fmt.Errorf("p %v is already running g %v while start g %v (offset %v, time %v)", ev.p, p.g, ev.g, ev.off, ev.ts)
|
||||
}
|
||||
g.state = gRunning
|
||||
g.evStart = ev
|
||||
p.g = ev.g
|
||||
if g.ev != nil {
|
||||
if g.ev.typ == traceEvGoCreate {
|
||||
// +1 because symblizer expects return pc.
|
||||
ev.stk = []*Frame{&Frame{pc: g.ev.args[1] + 1}}
|
||||
}
|
||||
g.ev.link = ev
|
||||
g.ev = nil
|
||||
}
|
||||
case traceEvGoEnd, traceEvGoStop:
|
||||
if err := checkRunning(p, g, ev); err != nil {
|
||||
return err
|
||||
}
|
||||
g.evStart.link = ev
|
||||
g.evStart = nil
|
||||
g.state = gDead
|
||||
p.g = 0
|
||||
case traceEvGoSched, traceEvGoPreempt:
|
||||
if err := checkRunning(p, g, ev); err != nil {
|
||||
return err
|
||||
}
|
||||
g.state = gRunnable
|
||||
g.evStart.link = ev
|
||||
g.evStart = nil
|
||||
p.g = 0
|
||||
g.ev = ev
|
||||
case traceEvGoUnblock:
|
||||
if g.state != gRunning {
|
||||
return fmt.Errorf("g %v is not running while unpark (offset %v, time %v)", ev.g, ev.off, ev.ts)
|
||||
}
|
||||
if ev.p != timerP && p.g != ev.g {
|
||||
return fmt.Errorf("p %v is not running g %v while unpark (offset %v, time %v)", ev.p, ev.g, ev.off, ev.ts)
|
||||
}
|
||||
g1 := gs[ev.args[0]]
|
||||
if g1.state != gWaiting {
|
||||
return fmt.Errorf("g %v is not waiting before unpark (offset %v, time %v)", ev.args[0], ev.off, ev.ts)
|
||||
}
|
||||
if g1.ev != nil && g1.ev.typ == traceEvGoBlockNet && ev.p != timerP {
|
||||
ev.p = netpollP
|
||||
}
|
||||
if g1.ev != nil {
|
||||
g1.ev.link = ev
|
||||
}
|
||||
g1.state = gRunnable
|
||||
g1.ev = ev
|
||||
gs[ev.args[0]] = g1
|
||||
case traceEvGoSysCall:
|
||||
if err := checkRunning(p, g, ev); err != nil {
|
||||
return err
|
||||
}
|
||||
g.ev = ev
|
||||
case traceEvGoSysBlock:
|
||||
if err := checkRunning(p, g, ev); err != nil {
|
||||
return err
|
||||
}
|
||||
g.state = gRunnable
|
||||
g.evStart.link = ev
|
||||
g.evStart = nil
|
||||
p.g = 0
|
||||
case traceEvGoSysExit:
|
||||
if g.state != gRunnable {
|
||||
return fmt.Errorf("g %v is not runnable during syscall exit (offset %v, time %v)", ev.g, ev.off, ev.ts)
|
||||
}
|
||||
if g.ev != nil && g.ev.typ == traceEvGoSysCall {
|
||||
g.ev.link = ev
|
||||
}
|
||||
g.ev = ev
|
||||
case traceEvGoSleep, traceEvGoBlock, traceEvGoBlockSend, traceEvGoBlockRecv,
|
||||
traceEvGoBlockSelect, traceEvGoBlockSync, traceEvGoBlockCond, traceEvGoBlockNet:
|
||||
if err := checkRunning(p, g, ev); err != nil {
|
||||
return err
|
||||
}
|
||||
g.state = gWaiting
|
||||
g.ev = ev
|
||||
g.evStart.link = ev
|
||||
g.evStart = nil
|
||||
p.g = 0
|
||||
}
|
||||
|
||||
gs[ev.g] = g
|
||||
ps[ev.p] = p
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// symbolizeTrace attaches func/file/line info to stack traces.
|
||||
func symbolizeTrace(events []*Event, bin string) error {
|
||||
// First, collect and dedup all pcs.
|
||||
pcs := make(map[uint64]*Frame)
|
||||
for _, ev := range events {
|
||||
for _, f := range ev.stk {
|
||||
pcs[f.pc] = nil
|
||||
}
|
||||
}
|
||||
|
||||
// Start addr2line.
|
||||
cmd := exec.Command("go", "tool", "addr2line", bin)
|
||||
in, err := cmd.StdinPipe()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to pipe addr2line stdin: %v", err)
|
||||
}
|
||||
out, err := cmd.StdoutPipe()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to pipe addr2line stdout: %v", err)
|
||||
}
|
||||
err = cmd.Start()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to start addr2line: %v", err)
|
||||
}
|
||||
outb := bufio.NewReader(out)
|
||||
|
||||
// Write all pcs to addr2line.
|
||||
// Need to copy pcs to an array, because map iteration order is non-deterministic.
|
||||
var pcArray []uint64
|
||||
for pc := range pcs {
|
||||
pcArray = append(pcArray, pc)
|
||||
_, err := fmt.Fprintf(in, "0x%x\n", pc-1)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to write to addr2line: %v", err)
|
||||
}
|
||||
}
|
||||
in.Close()
|
||||
|
||||
// Read in answers.
|
||||
for _, pc := range pcArray {
|
||||
fn, err := outb.ReadString('\n')
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read from addr2line: %v", err)
|
||||
}
|
||||
file, err := outb.ReadString('\n')
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read from addr2line: %v", err)
|
||||
}
|
||||
f := &Frame{pc: pc}
|
||||
f.fn = fn[:len(fn)-1]
|
||||
f.file = file[:len(file)-1]
|
||||
if colon := strings.LastIndex(f.file, ":"); colon != -1 {
|
||||
ln, err := strconv.Atoi(f.file[colon+1:])
|
||||
if err == nil {
|
||||
f.file = f.file[:colon]
|
||||
f.line = ln
|
||||
}
|
||||
}
|
||||
pcs[pc] = f
|
||||
}
|
||||
cmd.Wait()
|
||||
|
||||
// Replace frames in events array.
|
||||
for _, ev := range events {
|
||||
for i, f := range ev.stk {
|
||||
ev.stk[i] = pcs[f.pc]
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// readVal reads unsigned base-128 value from r.
|
||||
func readVal(r io.Reader, off0 int) (v uint64, off int, err error) {
|
||||
off = off0
|
||||
for i := 0; i < 10; i++ {
|
||||
var buf [1]byte
|
||||
var n int
|
||||
n, err = r.Read(buf[:])
|
||||
if err != nil || n != 1 {
|
||||
return 0, 0, fmt.Errorf("failed to read trace at offset: read %v, error %v", off0, n, err)
|
||||
}
|
||||
off++
|
||||
v |= uint64(buf[0]&0x7f) << (uint(i) * 7)
|
||||
if buf[0]&0x80 == 0 {
|
||||
return
|
||||
}
|
||||
}
|
||||
return 0, 0, fmt.Errorf("bad value at offset 0x%x", off0)
|
||||
}
|
||||
|
||||
type EventList []*Event
|
||||
|
||||
func (l EventList) Len() int {
|
||||
return len(l)
|
||||
}
|
||||
|
||||
func (l EventList) Less(i, j int) bool {
|
||||
return l[i].ts < l[j].ts
|
||||
}
|
||||
|
||||
func (l EventList) Swap(i, j int) {
|
||||
l[i], l[j] = l[j], l[i]
|
||||
}
|
||||
|
||||
// Event types in the trace.
|
||||
// Verbatim copy from src/runtime/trace.go.
|
||||
const (
|
||||
traceEvNone = 0 // unused
|
||||
traceEvBatch = 1 // start of per-P batch of events [pid, timestamp]
|
||||
traceEvFrequency = 2 // contains tracer timer frequency [frequency (ticks per second)]
|
||||
traceEvStack = 3 // stack [stack id, number of PCs, array of PCs]
|
||||
traceEvGomaxprocs = 4 // current value of GOMAXPROCS [timestamp, GOMAXPROCS, stack id]
|
||||
traceEvProcStart = 5 // start of P [timestamp]
|
||||
traceEvProcStop = 6 // stop of P [timestamp]
|
||||
traceEvGCStart = 7 // GC start [timestamp, stack id]
|
||||
traceEvGCDone = 8 // GC done [timestamp]
|
||||
traceEvGCScanStart = 9 // GC scan start [timestamp]
|
||||
traceEvGCScanDone = 10 // GC scan done [timestamp]
|
||||
traceEvGCSweepStart = 11 // GC sweep start [timestamp, stack id]
|
||||
traceEvGCSweepDone = 12 // GC sweep done [timestamp]
|
||||
traceEvGoCreate = 13 // goroutine creation [timestamp, new goroutine id, start PC, stack id]
|
||||
traceEvGoStart = 14 // goroutine starts running [timestamp, goroutine id]
|
||||
traceEvGoEnd = 15 // goroutine ends [timestamp]
|
||||
traceEvGoStop = 16 // goroutine stops (like in select{}) [timestamp, stack]
|
||||
traceEvGoSched = 17 // goroutine calls Gosched [timestamp, stack]
|
||||
traceEvGoPreempt = 18 // goroutine is preempted [timestamp, stack]
|
||||
traceEvGoSleep = 19 // goroutine calls Sleep [timestamp, stack]
|
||||
traceEvGoBlock = 20 // goroutine blocks [timestamp, stack]
|
||||
traceEvGoUnblock = 21 // goroutine is unblocked [timestamp, goroutine id, stack]
|
||||
traceEvGoBlockSend = 22 // goroutine blocks on chan send [timestamp, stack]
|
||||
traceEvGoBlockRecv = 23 // goroutine blocks on chan recv [timestamp, stack]
|
||||
traceEvGoBlockSelect = 24 // goroutine blocks on select [timestamp, stack]
|
||||
traceEvGoBlockSync = 25 // goroutine blocks on Mutex/RWMutex [timestamp, stack]
|
||||
traceEvGoBlockCond = 26 // goroutine blocks on Cond [timestamp, stack]
|
||||
traceEvGoBlockNet = 27 // goroutine blocks on network [timestamp, stack]
|
||||
traceEvGoSysCall = 28 // syscall enter [timestamp, stack]
|
||||
traceEvGoSysExit = 29 // syscall exit [timestamp, goroutine id]
|
||||
traceEvGoSysBlock = 30 // syscall blocks [timestamp, stack]
|
||||
traceEvGoWaiting = 31 // denotes that goroutine is blocked when tracing starts [goroutine id]
|
||||
traceEvGoInSyscall = 32 // denotes that goroutine is in syscall when tracing starts [goroutine id]
|
||||
traceEvHeapAlloc = 33 // memstats.heap_alloc change [timestamp, heap_alloc]
|
||||
traceEvNextGC = 34 // memstats.next_gc change [timestamp, next_gc]
|
||||
traceEvTimerGoroutine = 35 // denotes timer goroutine [timer goroutine id]
|
||||
traceEvCount = 36
|
||||
)
|
||||
|
||||
var evDescriptions = [traceEvCount]struct {
|
||||
name string
|
||||
stack bool
|
||||
args []string
|
||||
}{
|
||||
traceEvNone: {"None", false, []string{}},
|
||||
traceEvBatch: {"Batch", false, []string{"p", "ticks"}},
|
||||
traceEvFrequency: {"Frequency", false, []string{"freq"}},
|
||||
traceEvStack: {"Stack", false, []string{"id", "siz"}},
|
||||
traceEvGomaxprocs: {"Gomaxprocs", true, []string{"procs"}},
|
||||
traceEvProcStart: {"ProcStart", false, []string{}},
|
||||
traceEvProcStop: {"ProcStop", false, []string{}},
|
||||
traceEvGCStart: {"GCStart", true, []string{}},
|
||||
traceEvGCDone: {"GCDone", false, []string{}},
|
||||
traceEvGCScanStart: {"GCScanStart", false, []string{}},
|
||||
traceEvGCScanDone: {"GCScanDone", false, []string{}},
|
||||
traceEvGCSweepStart: {"GCSweepStart", true, []string{}},
|
||||
traceEvGCSweepDone: {"GCSweepDone", false, []string{}},
|
||||
traceEvGoCreate: {"GoCreate", true, []string{"g", "pc"}},
|
||||
traceEvGoStart: {"GoStart", false, []string{"g"}},
|
||||
traceEvGoEnd: {"GoEnd", false, []string{}},
|
||||
traceEvGoStop: {"GoStop", true, []string{}},
|
||||
traceEvGoSched: {"GoSched", true, []string{}},
|
||||
traceEvGoPreempt: {"GoPreempt", true, []string{}},
|
||||
traceEvGoSleep: {"GoSleep", true, []string{}},
|
||||
traceEvGoBlock: {"GoBlock", true, []string{}},
|
||||
traceEvGoUnblock: {"GoUnblock", true, []string{"g"}},
|
||||
traceEvGoBlockSend: {"GoBlockSend", true, []string{}},
|
||||
traceEvGoBlockRecv: {"GoBlockRecv", true, []string{}},
|
||||
traceEvGoBlockSelect: {"GoBlockSelect", true, []string{}},
|
||||
traceEvGoBlockSync: {"GoBlockSync", true, []string{}},
|
||||
traceEvGoBlockCond: {"GoBlockCond", true, []string{}},
|
||||
traceEvGoBlockNet: {"GoBlockNet", true, []string{}},
|
||||
traceEvGoSysCall: {"GoSysCall", true, []string{}},
|
||||
traceEvGoSysExit: {"GoSysExit", false, []string{"g"}},
|
||||
traceEvGoSysBlock: {"GoSysBlock", true, []string{}},
|
||||
traceEvGoWaiting: {"GoWaiting", false, []string{"g"}},
|
||||
traceEvGoInSyscall: {"GoInSyscall", false, []string{"g"}},
|
||||
traceEvHeapAlloc: {"HeapAlloc", false, []string{"mem"}},
|
||||
traceEvNextGC: {"NextGC", false, []string{"mem"}},
|
||||
traceEvTimerGoroutine: {"TimerGoroutine", false, []string{"g"}},
|
||||
}
|
||||
|
|
@ -6,6 +6,7 @@ package pprof_test
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"internal/trace"
|
||||
"net"
|
||||
"os"
|
||||
"runtime"
|
||||
|
|
@ -66,7 +67,7 @@ func TestTrace(t *testing.T) {
|
|||
t.Fatalf("failed to start tracing: %v", err)
|
||||
}
|
||||
StopTrace()
|
||||
_, err := parseTrace(buf)
|
||||
_, err := trace.Parse(buf)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to parse trace: %v", err)
|
||||
}
|
||||
|
|
@ -198,12 +199,153 @@ func TestTraceStress(t *testing.T) {
|
|||
runtime.GOMAXPROCS(procs)
|
||||
|
||||
StopTrace()
|
||||
_, err = parseTrace(buf)
|
||||
_, err = trace.Parse(buf)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to parse trace: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Do a bunch of various stuff (timers, GC, network, etc) in a separate goroutine.
|
||||
// And concurrently with all that start/stop trace 3 times.
|
||||
func TestTraceStressStartStop(t *testing.T) {
|
||||
skipTraceTestsIfNeeded(t)
|
||||
|
||||
defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(8))
|
||||
outerDone := make(chan bool)
|
||||
|
||||
go func() {
|
||||
defer func() {
|
||||
outerDone <- true
|
||||
}()
|
||||
|
||||
var wg sync.WaitGroup
|
||||
done := make(chan bool)
|
||||
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
<-done
|
||||
wg.Done()
|
||||
}()
|
||||
|
||||
rp, wp, err := os.Pipe()
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create pipe: %v", err)
|
||||
}
|
||||
defer func() {
|
||||
rp.Close()
|
||||
wp.Close()
|
||||
}()
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
var tmp [1]byte
|
||||
rp.Read(tmp[:])
|
||||
<-done
|
||||
wg.Done()
|
||||
}()
|
||||
time.Sleep(time.Millisecond)
|
||||
|
||||
go func() {
|
||||
runtime.LockOSThread()
|
||||
for {
|
||||
select {
|
||||
case <-done:
|
||||
return
|
||||
default:
|
||||
runtime.Gosched()
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
runtime.GC()
|
||||
// Trigger GC from malloc.
|
||||
for i := 0; i < 1e3; i++ {
|
||||
_ = make([]byte, 1<<20)
|
||||
}
|
||||
|
||||
// Create a bunch of busy goroutines to load all Ps.
|
||||
for p := 0; p < 10; p++ {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
// Do something useful.
|
||||
tmp := make([]byte, 1<<16)
|
||||
for i := range tmp {
|
||||
tmp[i]++
|
||||
}
|
||||
_ = tmp
|
||||
<-done
|
||||
wg.Done()
|
||||
}()
|
||||
}
|
||||
|
||||
// Block in syscall.
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
var tmp [1]byte
|
||||
rp.Read(tmp[:])
|
||||
<-done
|
||||
wg.Done()
|
||||
}()
|
||||
|
||||
runtime.GOMAXPROCS(runtime.GOMAXPROCS(1))
|
||||
|
||||
// Test timers.
|
||||
timerDone := make(chan bool)
|
||||
go func() {
|
||||
time.Sleep(time.Millisecond)
|
||||
timerDone <- true
|
||||
}()
|
||||
<-timerDone
|
||||
|
||||
// A bit of network.
|
||||
ln, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
if err != nil {
|
||||
t.Fatalf("listen failed: %v", err)
|
||||
}
|
||||
defer ln.Close()
|
||||
go func() {
|
||||
c, err := ln.Accept()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
time.Sleep(time.Millisecond)
|
||||
var buf [1]byte
|
||||
c.Write(buf[:])
|
||||
c.Close()
|
||||
}()
|
||||
c, err := net.Dial("tcp", ln.Addr().String())
|
||||
if err != nil {
|
||||
t.Fatalf("dial failed: %v", err)
|
||||
}
|
||||
var tmp [1]byte
|
||||
c.Read(tmp[:])
|
||||
c.Close()
|
||||
|
||||
go func() {
|
||||
runtime.Gosched()
|
||||
select {}
|
||||
}()
|
||||
|
||||
// Unblock helper goroutines and wait them to finish.
|
||||
wp.Write(tmp[:])
|
||||
wp.Write(tmp[:])
|
||||
close(done)
|
||||
wg.Wait()
|
||||
}()
|
||||
|
||||
for i := 0; i < 3; i++ {
|
||||
buf := new(bytes.Buffer)
|
||||
if err := StartTrace(buf); err != nil {
|
||||
t.Fatalf("failed to start tracing: %v", err)
|
||||
}
|
||||
time.Sleep(time.Millisecond)
|
||||
StopTrace()
|
||||
if _, err := trace.Parse(buf); err != nil {
|
||||
t.Fatalf("failed to parse trace: %v", err)
|
||||
}
|
||||
}
|
||||
<-outerDone
|
||||
}
|
||||
|
||||
func TestTraceSymbolize(t *testing.T) {
|
||||
skipTraceTestsIfNeeded(t)
|
||||
if runtime.GOOS == "nacl" {
|
||||
|
|
@ -215,24 +357,24 @@ func TestTraceSymbolize(t *testing.T) {
|
|||
}
|
||||
runtime.GC()
|
||||
StopTrace()
|
||||
events, err := parseTrace(buf)
|
||||
events, err := trace.Parse(buf)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to parse trace: %v", err)
|
||||
}
|
||||
err = symbolizeTrace(events, os.Args[0])
|
||||
err = trace.Symbolize(events, os.Args[0])
|
||||
if err != nil {
|
||||
t.Fatalf("failed to symbolize trace: %v", err)
|
||||
}
|
||||
found := false
|
||||
eventLoop:
|
||||
for _, ev := range events {
|
||||
if ev.typ != traceEvGCStart {
|
||||
if ev.Type != trace.EvGCStart {
|
||||
continue
|
||||
}
|
||||
for _, f := range ev.stk {
|
||||
if strings.HasSuffix(f.file, "trace_test.go") &&
|
||||
strings.HasSuffix(f.fn, "pprof_test.TestTraceSymbolize") &&
|
||||
f.line == 216 {
|
||||
for _, f := range ev.Stk {
|
||||
if strings.HasSuffix(f.File, "trace_test.go") &&
|
||||
strings.HasSuffix(f.Fn, "pprof_test.TestTraceSymbolize") &&
|
||||
f.Line == 358 {
|
||||
found = true
|
||||
break eventLoop
|
||||
}
|
||||
|
|
|
|||
|
|
@ -190,7 +190,6 @@ func StopTrace() {
|
|||
}
|
||||
|
||||
traceGoSched()
|
||||
traceGoStart()
|
||||
|
||||
for _, p := range &allp {
|
||||
if p == nil {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue