Home | History | Annotate | Download | only in trace
      1 // Copyright 2014 The Go Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style
      3 // license that can be found in the LICENSE file.
      4 
      5 package main
      6 
      7 import (
      8 	"encoding/json"
      9 	"fmt"
     10 	"internal/trace"
     11 	"log"
     12 	"net/http"
     13 	"path/filepath"
     14 	"runtime"
     15 	"strconv"
     16 	"strings"
     17 	"time"
     18 )
     19 
     20 func init() {
     21 	http.HandleFunc("/trace", httpTrace)
     22 	http.HandleFunc("/jsontrace", httpJsonTrace)
     23 	http.HandleFunc("/trace_viewer_html", httpTraceViewerHTML)
     24 }
     25 
     26 // httpTrace serves either whole trace (goid==0) or trace for goid goroutine.
     27 func httpTrace(w http.ResponseWriter, r *http.Request) {
     28 	_, err := parseEvents()
     29 	if err != nil {
     30 		http.Error(w, err.Error(), http.StatusInternalServerError)
     31 		return
     32 	}
     33 	if err := r.ParseForm(); err != nil {
     34 		http.Error(w, err.Error(), http.StatusInternalServerError)
     35 		return
     36 	}
     37 	html := strings.Replace(templTrace, "{{PARAMS}}", r.Form.Encode(), -1)
     38 	w.Write([]byte(html))
     39 
     40 }
     41 
     42 // See https://github.com/catapult-project/catapult/blob/master/tracing/docs/embedding-trace-viewer.md
     43 // This is almost verbatim copy of:
     44 // https://github.com/catapult-project/catapult/blob/master/tracing/bin/index.html
     45 // on revision 623a005a3ffa9de13c4b92bc72290e7bcd1ca591.
     46 var templTrace = `
     47 <html>
     48 <head>
     49 <link href="/trace_viewer_html" rel="import">
     50 <script>
     51 (function() {
     52   var viewer;
     53   var url;
     54   var model;
     55 
     56   function load() {
     57     var req = new XMLHttpRequest();
     58     var is_binary = /[.]gz$/.test(url) || /[.]zip$/.test(url);
     59     req.overrideMimeType('text/plain; charset=x-user-defined');
     60     req.open('GET', url, true);
     61     if (is_binary)
     62       req.responseType = 'arraybuffer';
     63 
     64     req.onreadystatechange = function(event) {
     65       if (req.readyState !== 4)
     66         return;
     67 
     68       window.setTimeout(function() {
     69         if (req.status === 200)
     70           onResult(is_binary ? req.response : req.responseText);
     71         else
     72           onResultFail(req.status);
     73       }, 0);
     74     };
     75     req.send(null);
     76   }
     77 
     78   function onResultFail(err) {
     79     var overlay = new tr.ui.b.Overlay();
     80     overlay.textContent = err + ': ' + url + ' could not be loaded';
     81     overlay.title = 'Failed to fetch data';
     82     overlay.visible = true;
     83   }
     84 
     85   function onResult(result) {
     86     model = new tr.Model();
     87     var i = new tr.importer.Import(model);
     88     var p = i.importTracesWithProgressDialog([result]);
     89     p.then(onModelLoaded, onImportFail);
     90   }
     91 
     92   function onModelLoaded() {
     93     viewer.model = model;
     94     viewer.viewTitle = "trace";
     95   }
     96 
     97   function onImportFail() {
     98     var overlay = new tr.ui.b.Overlay();
     99     overlay.textContent = tr.b.normalizeException(err).message;
    100     overlay.title = 'Import error';
    101     overlay.visible = true;
    102   }
    103 
    104   document.addEventListener('DOMContentLoaded', function() {
    105     var container = document.createElement('track-view-container');
    106     container.id = 'track_view_container';
    107 
    108     viewer = document.createElement('tr-ui-timeline-view');
    109     viewer.track_view_container = container;
    110     viewer.appendChild(container);
    111 
    112     viewer.id = 'trace-viewer';
    113     viewer.globalMode = true;
    114     document.body.appendChild(viewer);
    115 
    116     url = '/jsontrace?{{PARAMS}}';
    117     load();
    118   });
    119 }());
    120 </script>
    121 </head>
    122 <body>
    123 </body>
    124 </html>
    125 `
    126 
    127 // httpTraceViewerHTML serves static part of trace-viewer.
    128 // This URL is queried from templTrace HTML.
    129 func httpTraceViewerHTML(w http.ResponseWriter, r *http.Request) {
    130 	http.ServeFile(w, r, filepath.Join(runtime.GOROOT(), "misc", "trace", "trace_viewer_lean.html"))
    131 }
    132 
    133 // httpJsonTrace serves json trace, requested from within templTrace HTML.
    134 func httpJsonTrace(w http.ResponseWriter, r *http.Request) {
    135 	// This is an AJAX handler, so instead of http.Error we use log.Printf to log errors.
    136 	events, err := parseEvents()
    137 	if err != nil {
    138 		log.Printf("failed to parse trace: %v", err)
    139 		return
    140 	}
    141 
    142 	params := &traceParams{
    143 		events:  events,
    144 		endTime: int64(1<<63 - 1),
    145 	}
    146 
    147 	if goids := r.FormValue("goid"); goids != "" {
    148 		// If goid argument is present, we are rendering a trace for this particular goroutine.
    149 		goid, err := strconv.ParseUint(goids, 10, 64)
    150 		if err != nil {
    151 			log.Printf("failed to parse goid parameter '%v': %v", goids, err)
    152 			return
    153 		}
    154 		analyzeGoroutines(events)
    155 		g := gs[goid]
    156 		params.gtrace = true
    157 		params.startTime = g.StartTime
    158 		params.endTime = g.EndTime
    159 		params.maing = goid
    160 		params.gs = trace.RelatedGoroutines(events, goid)
    161 	}
    162 
    163 	data, err := generateTrace(params)
    164 	if err != nil {
    165 		log.Printf("failed to generate trace: %v", err)
    166 		return
    167 	}
    168 
    169 	if startStr, endStr := r.FormValue("start"), r.FormValue("end"); startStr != "" && endStr != "" {
    170 		// If start/end arguments are present, we are rendering a range of the trace.
    171 		start, err := strconv.ParseUint(startStr, 10, 64)
    172 		if err != nil {
    173 			log.Printf("failed to parse start parameter '%v': %v", startStr, err)
    174 			return
    175 		}
    176 		end, err := strconv.ParseUint(endStr, 10, 64)
    177 		if err != nil {
    178 			log.Printf("failed to parse end parameter '%v': %v", endStr, err)
    179 			return
    180 		}
    181 		if start >= uint64(len(data.Events)) || end <= start || end > uint64(len(data.Events)) {
    182 			log.Printf("bogus start/end parameters: %v/%v, trace size %v", start, end, len(data.Events))
    183 			return
    184 		}
    185 		data.Events = append(data.Events[start:end], data.Events[data.footer:]...)
    186 	}
    187 	err = json.NewEncoder(w).Encode(data)
    188 	if err != nil {
    189 		log.Printf("failed to serialize trace: %v", err)
    190 		return
    191 	}
    192 }
    193 
    194 type Range struct {
    195 	Name  string
    196 	Start int
    197 	End   int
    198 }
    199 
    200 // splitTrace splits the trace into a number of ranges,
    201 // each resulting in approx 100MB of json output (trace viewer can hardly handle more).
    202 func splitTrace(data ViewerData) []Range {
    203 	const rangeSize = 100 << 20
    204 	var ranges []Range
    205 	cw := new(countingWriter)
    206 	enc := json.NewEncoder(cw)
    207 	// First calculate size of the mandatory part of the trace.
    208 	// This includes stack traces and thread names.
    209 	data1 := data
    210 	data1.Events = data.Events[data.footer:]
    211 	enc.Encode(data1)
    212 	auxSize := cw.size
    213 	cw.size = 0
    214 	// Then calculate size of each individual event and group them into ranges.
    215 	for i, start := 0, 0; i < data.footer; i++ {
    216 		enc.Encode(data.Events[i])
    217 		if cw.size+auxSize > rangeSize || i == data.footer-1 {
    218 			ranges = append(ranges, Range{
    219 				Name:  fmt.Sprintf("%v-%v", time.Duration(data.Events[start].Time*1000), time.Duration(data.Events[i].Time*1000)),
    220 				Start: start,
    221 				End:   i + 1,
    222 			})
    223 			start = i + 1
    224 			cw.size = 0
    225 		}
    226 	}
    227 	if len(ranges) == 1 {
    228 		ranges = nil
    229 	}
    230 	return ranges
    231 }
    232 
    233 type countingWriter struct {
    234 	size int
    235 }
    236 
    237 func (cw *countingWriter) Write(data []byte) (int, error) {
    238 	cw.size += len(data)
    239 	return len(data), nil
    240 }
    241 
    242 type traceParams struct {
    243 	events    []*trace.Event
    244 	gtrace    bool
    245 	startTime int64
    246 	endTime   int64
    247 	maing     uint64
    248 	gs        map[uint64]bool
    249 }
    250 
    251 type traceContext struct {
    252 	*traceParams
    253 	data      ViewerData
    254 	frameTree frameNode
    255 	frameSeq  int
    256 	arrowSeq  uint64
    257 	gcount    uint64
    258 
    259 	heapStats, prevHeapStats     heapStats
    260 	threadStats, prevThreadStats threadStats
    261 	gstates, prevGstates         [gStateCount]uint64
    262 }
    263 
    264 type heapStats struct {
    265 	heapAlloc uint64
    266 	nextGC    uint64
    267 }
    268 
    269 type threadStats struct {
    270 	insyscall uint64
    271 	prunning  uint64
    272 }
    273 
    274 type frameNode struct {
    275 	id       int
    276 	children map[uint64]frameNode
    277 }
    278 
    279 type gState int
    280 
    281 const (
    282 	gDead gState = iota
    283 	gRunnable
    284 	gRunning
    285 	gWaiting
    286 	gWaitingGC
    287 
    288 	gStateCount
    289 )
    290 
    291 type ViewerData struct {
    292 	Events   []*ViewerEvent         `json:"traceEvents"`
    293 	Frames   map[string]ViewerFrame `json:"stackFrames"`
    294 	TimeUnit string                 `json:"displayTimeUnit"`
    295 
    296 	// This is where mandatory part of the trace starts (e.g. thread names)
    297 	footer int
    298 }
    299 
    300 type ViewerEvent struct {
    301 	Name     string      `json:"name,omitempty"`
    302 	Phase    string      `json:"ph"`
    303 	Scope    string      `json:"s,omitempty"`
    304 	Time     float64     `json:"ts"`
    305 	Dur      float64     `json:"dur,omitempty"`
    306 	Pid      uint64      `json:"pid"`
    307 	Tid      uint64      `json:"tid"`
    308 	ID       uint64      `json:"id,omitempty"`
    309 	Stack    int         `json:"sf,omitempty"`
    310 	EndStack int         `json:"esf,omitempty"`
    311 	Arg      interface{} `json:"args,omitempty"`
    312 }
    313 
    314 type ViewerFrame struct {
    315 	Name   string `json:"name"`
    316 	Parent int    `json:"parent,omitempty"`
    317 }
    318 
    319 type NameArg struct {
    320 	Name string `json:"name"`
    321 }
    322 
    323 type SortIndexArg struct {
    324 	Index int `json:"sort_index"`
    325 }
    326 
    327 // generateTrace generates json trace for trace-viewer:
    328 // https://github.com/google/trace-viewer
    329 // Trace format is described at:
    330 // https://docs.google.com/document/d/1CvAClvFfyA5R-PhYUmn5OOQtYMH4h6I0nSsKchNAySU/view
    331 // If gtrace=true, generate trace for goroutine goid, otherwise whole trace.
    332 // startTime, endTime determine part of the trace that we are interested in.
    333 // gset restricts goroutines that are included in the resulting trace.
    334 func generateTrace(params *traceParams) (ViewerData, error) {
    335 	ctx := &traceContext{traceParams: params}
    336 	ctx.frameTree.children = make(map[uint64]frameNode)
    337 	ctx.data.Frames = make(map[string]ViewerFrame)
    338 	ctx.data.TimeUnit = "ns"
    339 	maxProc := 0
    340 	gnames := make(map[uint64]string)
    341 	gstates := make(map[uint64]gState)
    342 	// Since we make many calls to setGState, we record a sticky
    343 	// error in setGStateErr and check it after every event.
    344 	var setGStateErr error
    345 	setGState := func(ev *trace.Event, g uint64, oldState, newState gState) {
    346 		if oldState == gWaiting && gstates[g] == gWaitingGC {
    347 			// For checking, gWaiting counts as any gWaiting*.
    348 			oldState = gstates[g]
    349 		}
    350 		if gstates[g] != oldState && setGStateErr == nil {
    351 			setGStateErr = fmt.Errorf("expected G %d to be in state %d, but got state %d", g, oldState, newState)
    352 		}
    353 		ctx.gstates[gstates[g]]--
    354 		ctx.gstates[newState]++
    355 		gstates[g] = newState
    356 	}
    357 	for _, ev := range ctx.events {
    358 		// Handle state transitions before we filter out events.
    359 		switch ev.Type {
    360 		case trace.EvGoStart, trace.EvGoStartLabel:
    361 			setGState(ev, ev.G, gRunnable, gRunning)
    362 			if _, ok := gnames[ev.G]; !ok {
    363 				if len(ev.Stk) > 0 {
    364 					gnames[ev.G] = fmt.Sprintf("G%v %s", ev.G, ev.Stk[0].Fn)
    365 				} else {
    366 					gnames[ev.G] = fmt.Sprintf("G%v", ev.G)
    367 				}
    368 			}
    369 		case trace.EvProcStart:
    370 			ctx.threadStats.prunning++
    371 		case trace.EvProcStop:
    372 			ctx.threadStats.prunning--
    373 		case trace.EvGoCreate:
    374 			ctx.gcount++
    375 			setGState(ev, ev.Args[0], gDead, gRunnable)
    376 		case trace.EvGoEnd:
    377 			ctx.gcount--
    378 			setGState(ev, ev.G, gRunning, gDead)
    379 		case trace.EvGoUnblock:
    380 			setGState(ev, ev.Args[0], gWaiting, gRunnable)
    381 		case trace.EvGoSysExit:
    382 			setGState(ev, ev.G, gWaiting, gRunnable)
    383 			ctx.threadStats.insyscall--
    384 		case trace.EvGoSysBlock:
    385 			setGState(ev, ev.G, gRunning, gWaiting)
    386 			ctx.threadStats.insyscall++
    387 		case trace.EvGoSched, trace.EvGoPreempt:
    388 			setGState(ev, ev.G, gRunning, gRunnable)
    389 		case trace.EvGoStop,
    390 			trace.EvGoSleep, trace.EvGoBlock, trace.EvGoBlockSend, trace.EvGoBlockRecv,
    391 			trace.EvGoBlockSelect, trace.EvGoBlockSync, trace.EvGoBlockCond, trace.EvGoBlockNet:
    392 			setGState(ev, ev.G, gRunning, gWaiting)
    393 		case trace.EvGoBlockGC:
    394 			setGState(ev, ev.G, gRunning, gWaitingGC)
    395 		case trace.EvGoWaiting:
    396 			setGState(ev, ev.G, gRunnable, gWaiting)
    397 		case trace.EvGoInSyscall:
    398 			// Cancel out the effect of EvGoCreate at the beginning.
    399 			setGState(ev, ev.G, gRunnable, gWaiting)
    400 			ctx.threadStats.insyscall++
    401 		case trace.EvHeapAlloc:
    402 			ctx.heapStats.heapAlloc = ev.Args[0]
    403 		case trace.EvNextGC:
    404 			ctx.heapStats.nextGC = ev.Args[0]
    405 		}
    406 		if setGStateErr != nil {
    407 			return ctx.data, setGStateErr
    408 		}
    409 		if ctx.gstates[gRunnable] < 0 || ctx.gstates[gRunning] < 0 || ctx.threadStats.insyscall < 0 {
    410 			return ctx.data, fmt.Errorf("invalid state after processing %v: runnable=%d running=%d insyscall=%d", ev, ctx.gstates[gRunnable], ctx.gstates[gRunning], ctx.threadStats.insyscall)
    411 		}
    412 
    413 		// Ignore events that are from uninteresting goroutines
    414 		// or outside of the interesting timeframe.
    415 		if ctx.gs != nil && ev.P < trace.FakeP && !ctx.gs[ev.G] {
    416 			continue
    417 		}
    418 		if ev.Ts < ctx.startTime || ev.Ts > ctx.endTime {
    419 			continue
    420 		}
    421 
    422 		if ev.P < trace.FakeP && ev.P > maxProc {
    423 			maxProc = ev.P
    424 		}
    425 
    426 		// Emit trace objects.
    427 		switch ev.Type {
    428 		case trace.EvProcStart:
    429 			if ctx.gtrace {
    430 				continue
    431 			}
    432 			ctx.emitInstant(ev, "proc start")
    433 		case trace.EvProcStop:
    434 			if ctx.gtrace {
    435 				continue
    436 			}
    437 			ctx.emitInstant(ev, "proc stop")
    438 		case trace.EvGCStart:
    439 			ctx.emitSlice(ev, "GC")
    440 		case trace.EvGCDone:
    441 		case trace.EvGCScanStart:
    442 			if ctx.gtrace {
    443 				continue
    444 			}
    445 			ctx.emitSlice(ev, "MARK TERMINATION")
    446 		case trace.EvGCScanDone:
    447 		case trace.EvGCSweepStart:
    448 			ctx.emitSlice(ev, "SWEEP")
    449 		case trace.EvGCSweepDone:
    450 		case trace.EvGoStart:
    451 			ctx.emitSlice(ev, gnames[ev.G])
    452 		case trace.EvGoStartLabel:
    453 			ctx.emitSlice(ev, ev.SArgs[0])
    454 		case trace.EvGoCreate:
    455 			ctx.emitArrow(ev, "go")
    456 		case trace.EvGoUnblock:
    457 			ctx.emitArrow(ev, "unblock")
    458 		case trace.EvGoSysCall:
    459 			ctx.emitInstant(ev, "syscall")
    460 		case trace.EvGoSysExit:
    461 			ctx.emitArrow(ev, "sysexit")
    462 		}
    463 		// Emit any counter updates.
    464 		ctx.emitThreadCounters(ev)
    465 		ctx.emitHeapCounters(ev)
    466 		ctx.emitGoroutineCounters(ev)
    467 	}
    468 
    469 	ctx.data.footer = len(ctx.data.Events)
    470 	ctx.emit(&ViewerEvent{Name: "process_name", Phase: "M", Pid: 0, Arg: &NameArg{"PROCS"}})
    471 	ctx.emit(&ViewerEvent{Name: "process_sort_index", Phase: "M", Pid: 0, Arg: &SortIndexArg{1}})
    472 
    473 	ctx.emit(&ViewerEvent{Name: "process_name", Phase: "M", Pid: 1, Arg: &NameArg{"STATS"}})
    474 	ctx.emit(&ViewerEvent{Name: "process_sort_index", Phase: "M", Pid: 1, Arg: &SortIndexArg{0}})
    475 
    476 	ctx.emit(&ViewerEvent{Name: "thread_name", Phase: "M", Pid: 0, Tid: trace.GCP, Arg: &NameArg{"GC"}})
    477 	ctx.emit(&ViewerEvent{Name: "thread_sort_index", Phase: "M", Pid: 0, Tid: trace.GCP, Arg: &SortIndexArg{-6}})
    478 
    479 	ctx.emit(&ViewerEvent{Name: "thread_name", Phase: "M", Pid: 0, Tid: trace.NetpollP, Arg: &NameArg{"Network"}})
    480 	ctx.emit(&ViewerEvent{Name: "thread_sort_index", Phase: "M", Pid: 0, Tid: trace.NetpollP, Arg: &SortIndexArg{-5}})
    481 
    482 	ctx.emit(&ViewerEvent{Name: "thread_name", Phase: "M", Pid: 0, Tid: trace.TimerP, Arg: &NameArg{"Timers"}})
    483 	ctx.emit(&ViewerEvent{Name: "thread_sort_index", Phase: "M", Pid: 0, Tid: trace.TimerP, Arg: &SortIndexArg{-4}})
    484 
    485 	ctx.emit(&ViewerEvent{Name: "thread_name", Phase: "M", Pid: 0, Tid: trace.SyscallP, Arg: &NameArg{"Syscalls"}})
    486 	ctx.emit(&ViewerEvent{Name: "thread_sort_index", Phase: "M", Pid: 0, Tid: trace.SyscallP, Arg: &SortIndexArg{-3}})
    487 
    488 	if !ctx.gtrace {
    489 		for i := 0; i <= maxProc; i++ {
    490 			ctx.emit(&ViewerEvent{Name: "thread_name", Phase: "M", Pid: 0, Tid: uint64(i), Arg: &NameArg{fmt.Sprintf("Proc %v", i)}})
    491 			ctx.emit(&ViewerEvent{Name: "thread_sort_index", Phase: "M", Pid: 0, Tid: uint64(i), Arg: &SortIndexArg{i}})
    492 		}
    493 	}
    494 
    495 	if ctx.gtrace && ctx.gs != nil {
    496 		for k, v := range gnames {
    497 			if !ctx.gs[k] {
    498 				continue
    499 			}
    500 			ctx.emit(&ViewerEvent{Name: "thread_name", Phase: "M", Pid: 0, Tid: k, Arg: &NameArg{v}})
    501 		}
    502 		ctx.emit(&ViewerEvent{Name: "thread_sort_index", Phase: "M", Pid: 0, Tid: ctx.maing, Arg: &SortIndexArg{-2}})
    503 		ctx.emit(&ViewerEvent{Name: "thread_sort_index", Phase: "M", Pid: 0, Tid: 0, Arg: &SortIndexArg{-1}})
    504 	}
    505 
    506 	return ctx.data, nil
    507 }
    508 
    509 func (ctx *traceContext) emit(e *ViewerEvent) {
    510 	ctx.data.Events = append(ctx.data.Events, e)
    511 }
    512 
    513 func (ctx *traceContext) time(ev *trace.Event) float64 {
    514 	// Trace viewer wants timestamps in microseconds.
    515 	return float64(ev.Ts-ctx.startTime) / 1000
    516 }
    517 
    518 func (ctx *traceContext) proc(ev *trace.Event) uint64 {
    519 	if ctx.gtrace && ev.P < trace.FakeP {
    520 		return ev.G
    521 	} else {
    522 		return uint64(ev.P)
    523 	}
    524 }
    525 
    526 func (ctx *traceContext) emitSlice(ev *trace.Event, name string) {
    527 	ctx.emit(&ViewerEvent{
    528 		Name:     name,
    529 		Phase:    "X",
    530 		Time:     ctx.time(ev),
    531 		Dur:      ctx.time(ev.Link) - ctx.time(ev),
    532 		Tid:      ctx.proc(ev),
    533 		Stack:    ctx.stack(ev.Stk),
    534 		EndStack: ctx.stack(ev.Link.Stk),
    535 	})
    536 }
    537 
    538 type heapCountersArg struct {
    539 	Allocated uint64
    540 	NextGC    uint64
    541 }
    542 
    543 func (ctx *traceContext) emitHeapCounters(ev *trace.Event) {
    544 	if ctx.gtrace {
    545 		return
    546 	}
    547 	if ctx.prevHeapStats == ctx.heapStats {
    548 		return
    549 	}
    550 	diff := uint64(0)
    551 	if ctx.heapStats.nextGC > ctx.heapStats.heapAlloc {
    552 		diff = ctx.heapStats.nextGC - ctx.heapStats.heapAlloc
    553 	}
    554 	ctx.emit(&ViewerEvent{Name: "Heap", Phase: "C", Time: ctx.time(ev), Pid: 1, Arg: &heapCountersArg{ctx.heapStats.heapAlloc, diff}})
    555 	ctx.prevHeapStats = ctx.heapStats
    556 }
    557 
    558 type goroutineCountersArg struct {
    559 	Running   uint64
    560 	Runnable  uint64
    561 	GCWaiting uint64
    562 }
    563 
    564 func (ctx *traceContext) emitGoroutineCounters(ev *trace.Event) {
    565 	if ctx.gtrace {
    566 		return
    567 	}
    568 	if ctx.prevGstates == ctx.gstates {
    569 		return
    570 	}
    571 	ctx.emit(&ViewerEvent{Name: "Goroutines", Phase: "C", Time: ctx.time(ev), Pid: 1, Arg: &goroutineCountersArg{ctx.gstates[gRunning], ctx.gstates[gRunnable], ctx.gstates[gWaitingGC]}})
    572 	ctx.prevGstates = ctx.gstates
    573 }
    574 
    575 type threadCountersArg struct {
    576 	Running   uint64
    577 	InSyscall uint64
    578 }
    579 
    580 func (ctx *traceContext) emitThreadCounters(ev *trace.Event) {
    581 	if ctx.gtrace {
    582 		return
    583 	}
    584 	if ctx.prevThreadStats == ctx.threadStats {
    585 		return
    586 	}
    587 	ctx.emit(&ViewerEvent{Name: "Threads", Phase: "C", Time: ctx.time(ev), Pid: 1, Arg: &threadCountersArg{ctx.threadStats.prunning, ctx.threadStats.insyscall}})
    588 	ctx.prevThreadStats = ctx.threadStats
    589 }
    590 
    591 func (ctx *traceContext) emitInstant(ev *trace.Event, name string) {
    592 	var arg interface{}
    593 	if ev.Type == trace.EvProcStart {
    594 		type Arg struct {
    595 			ThreadID uint64
    596 		}
    597 		arg = &Arg{ev.Args[0]}
    598 	}
    599 	ctx.emit(&ViewerEvent{Name: name, Phase: "I", Scope: "t", Time: ctx.time(ev), Tid: ctx.proc(ev), Stack: ctx.stack(ev.Stk), Arg: arg})
    600 }
    601 
    602 func (ctx *traceContext) emitArrow(ev *trace.Event, name string) {
    603 	if ev.Link == nil {
    604 		// The other end of the arrow is not captured in the trace.
    605 		// For example, a goroutine was unblocked but was not scheduled before trace stop.
    606 		return
    607 	}
    608 	if ctx.gtrace && (!ctx.gs[ev.Link.G] || ev.Link.Ts < ctx.startTime || ev.Link.Ts > ctx.endTime) {
    609 		return
    610 	}
    611 
    612 	if ev.P == trace.NetpollP || ev.P == trace.TimerP || ev.P == trace.SyscallP {
    613 		// Trace-viewer discards arrows if they don't start/end inside of a slice or instant.
    614 		// So emit a fake instant at the start of the arrow.
    615 		ctx.emitInstant(&trace.Event{P: ev.P, Ts: ev.Ts}, "unblock")
    616 	}
    617 
    618 	ctx.arrowSeq++
    619 	ctx.emit(&ViewerEvent{Name: name, Phase: "s", Tid: ctx.proc(ev), ID: ctx.arrowSeq, Time: ctx.time(ev), Stack: ctx.stack(ev.Stk)})
    620 	ctx.emit(&ViewerEvent{Name: name, Phase: "t", Tid: ctx.proc(ev.Link), ID: ctx.arrowSeq, Time: ctx.time(ev.Link)})
    621 }
    622 
    623 func (ctx *traceContext) stack(stk []*trace.Frame) int {
    624 	return ctx.buildBranch(ctx.frameTree, stk)
    625 }
    626 
    627 // buildBranch builds one branch in the prefix tree rooted at ctx.frameTree.
    628 func (ctx *traceContext) buildBranch(parent frameNode, stk []*trace.Frame) int {
    629 	if len(stk) == 0 {
    630 		return parent.id
    631 	}
    632 	last := len(stk) - 1
    633 	frame := stk[last]
    634 	stk = stk[:last]
    635 
    636 	node, ok := parent.children[frame.PC]
    637 	if !ok {
    638 		ctx.frameSeq++
    639 		node.id = ctx.frameSeq
    640 		node.children = make(map[uint64]frameNode)
    641 		parent.children[frame.PC] = node
    642 		ctx.data.Frames[strconv.Itoa(node.id)] = ViewerFrame{fmt.Sprintf("%v:%v", frame.Fn, frame.Line), parent.id}
    643 	}
    644 	return ctx.buildBranch(node, stk)
    645 }
    646