diff options
Diffstat (limited to 'src/cmd/trace/trace.go')
| -rw-r--r-- | src/cmd/trace/trace.go | 434 |
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) +} |
