// 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 = ` ` // 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) { var arg interface{} if ev.Type == trace.EvProcStart { type Arg struct { ThreadID uint64 } arg = &Arg{ev.Args[0]} } ctx.emit(&ViewerEvent{Name: name, Phase: "I", Scope: "t", Time: ctx.time(ev), Tid: ctx.proc(ev), Stack: ctx.stack(ev.Stk), Arg: arg}) } 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) }