aboutsummaryrefslogtreecommitdiff
path: root/src/cmd/trace/trace.go
diff options
context:
space:
mode:
Diffstat (limited to 'src/cmd/trace/trace.go')
-rw-r--r--src/cmd/trace/trace.go434
1 files changed, 434 insertions, 0 deletions
diff --git a/src/cmd/trace/trace.go b/src/cmd/trace/trace.go
new file mode 100644
index 0000000000..8f8ef14687
--- /dev/null
+++ b/src/cmd/trace/trace.go
@@ -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)
+}