...

Source file src/cmd/trace/trace.go

     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		"io"
    12		"log"
    13		"math"
    14		"net/http"
    15		"path/filepath"
    16		"runtime"
    17		"runtime/debug"
    18		"sort"
    19		"strconv"
    20		"strings"
    21		"time"
    22	)
    23	
    24	func init() {
    25		http.HandleFunc("/trace", httpTrace)
    26		http.HandleFunc("/jsontrace", httpJsonTrace)
    27		http.HandleFunc("/trace_viewer_html", httpTraceViewerHTML)
    28	}
    29	
    30	// httpTrace serves either whole trace (goid==0) or trace for goid goroutine.
    31	func httpTrace(w http.ResponseWriter, r *http.Request) {
    32		_, err := parseTrace()
    33		if err != nil {
    34			http.Error(w, err.Error(), http.StatusInternalServerError)
    35			return
    36		}
    37		if err := r.ParseForm(); err != nil {
    38			http.Error(w, err.Error(), http.StatusInternalServerError)
    39			return
    40		}
    41		html := strings.ReplaceAll(templTrace, "{{PARAMS}}", r.Form.Encode())
    42		w.Write([]byte(html))
    43	
    44	}
    45	
    46	// See https://github.com/catapult-project/catapult/blob/master/tracing/docs/embedding-trace-viewer.md
    47	// This is almost verbatim copy of:
    48	// https://github.com/catapult-project/catapult/blob/master/tracing/bin/index.html
    49	// on revision 5f9e4c3eaa555bdef18218a89f38c768303b7b6e.
    50	var templTrace = `
    51	<html>
    52	<head>
    53	<link href="/trace_viewer_html" rel="import">
    54	<style type="text/css">
    55	  html, body {
    56	    box-sizing: border-box;
    57	    overflow: hidden;
    58	    margin: 0px;
    59	    padding: 0;
    60	    width: 100%;
    61	    height: 100%;
    62	  }
    63	  #trace-viewer {
    64	    width: 100%;
    65	    height: 100%;
    66	  }
    67	  #trace-viewer:focus {
    68	    outline: none;
    69	  }
    70	</style>
    71	<script>
    72	'use strict';
    73	(function() {
    74	  var viewer;
    75	  var url;
    76	  var model;
    77	
    78	  function load() {
    79	    var req = new XMLHttpRequest();
    80	    var is_binary = /[.]gz$/.test(url) || /[.]zip$/.test(url);
    81	    req.overrideMimeType('text/plain; charset=x-user-defined');
    82	    req.open('GET', url, true);
    83	    if (is_binary)
    84	      req.responseType = 'arraybuffer';
    85	
    86	    req.onreadystatechange = function(event) {
    87	      if (req.readyState !== 4)
    88	        return;
    89	
    90	      window.setTimeout(function() {
    91	        if (req.status === 200)
    92	          onResult(is_binary ? req.response : req.responseText);
    93	        else
    94	          onResultFail(req.status);
    95	      }, 0);
    96	    };
    97	    req.send(null);
    98	  }
    99	
   100	  function onResultFail(err) {
   101	    var overlay = new tr.ui.b.Overlay();
   102	    overlay.textContent = err + ': ' + url + ' could not be loaded';
   103	    overlay.title = 'Failed to fetch data';
   104	    overlay.visible = true;
   105	  }
   106	
   107	  function onResult(result) {
   108	    model = new tr.Model();
   109	    var opts = new tr.importer.ImportOptions();
   110	    opts.shiftWorldToZero = false;
   111	    var i = new tr.importer.Import(model, opts);
   112	    var p = i.importTracesWithProgressDialog([result]);
   113	    p.then(onModelLoaded, onImportFail);
   114	  }
   115	
   116	  function onModelLoaded() {
   117	    viewer.model = model;
   118	    viewer.viewTitle = "trace";
   119	
   120	    if (!model || model.bounds.isEmpty)
   121	      return;
   122	    var sel = window.location.hash.substr(1);
   123	    if (sel === '')
   124	      return;
   125	    var parts = sel.split(':');
   126	    var range = new (tr.b.Range || tr.b.math.Range)();
   127	    range.addValue(parseFloat(parts[0]));
   128	    range.addValue(parseFloat(parts[1]));
   129	    viewer.trackView.viewport.interestRange.set(range);
   130	  }
   131	
   132	  function onImportFail(err) {
   133	    var overlay = new tr.ui.b.Overlay();
   134	    overlay.textContent = tr.b.normalizeException(err).message;
   135	    overlay.title = 'Import error';
   136	    overlay.visible = true;
   137	  }
   138	
   139	  document.addEventListener('DOMContentLoaded', function() {
   140	    var container = document.createElement('track-view-container');
   141	    container.id = 'track_view_container';
   142	
   143	    viewer = document.createElement('tr-ui-timeline-view');
   144	    viewer.track_view_container = container;
   145	    viewer.appendChild(container);
   146	
   147	    viewer.id = 'trace-viewer';
   148	    viewer.globalMode = true;
   149	    document.body.appendChild(viewer);
   150	
   151	    url = '/jsontrace?{{PARAMS}}';
   152	    load();
   153	  });
   154	}());
   155	</script>
   156	</head>
   157	<body>
   158	</body>
   159	</html>
   160	`
   161	
   162	// httpTraceViewerHTML serves static part of trace-viewer.
   163	// This URL is queried from templTrace HTML.
   164	func httpTraceViewerHTML(w http.ResponseWriter, r *http.Request) {
   165		http.ServeFile(w, r, filepath.Join(runtime.GOROOT(), "misc", "trace", "trace_viewer_full.html"))
   166	}
   167	
   168	// httpJsonTrace serves json trace, requested from within templTrace HTML.
   169	func httpJsonTrace(w http.ResponseWriter, r *http.Request) {
   170		defer debug.FreeOSMemory()
   171		defer reportMemoryUsage("after httpJsonTrace")
   172		// This is an AJAX handler, so instead of http.Error we use log.Printf to log errors.
   173		res, err := parseTrace()
   174		if err != nil {
   175			log.Printf("failed to parse trace: %v", err)
   176			return
   177		}
   178	
   179		params := &traceParams{
   180			parsed:  res,
   181			endTime: math.MaxInt64,
   182		}
   183	
   184		if goids := r.FormValue("goid"); goids != "" {
   185			// If goid argument is present, we are rendering a trace for this particular goroutine.
   186			goid, err := strconv.ParseUint(goids, 10, 64)
   187			if err != nil {
   188				log.Printf("failed to parse goid parameter %q: %v", goids, err)
   189				return
   190			}
   191			analyzeGoroutines(res.Events)
   192			g, ok := gs[goid]
   193			if !ok {
   194				log.Printf("failed to find goroutine %d", goid)
   195				return
   196			}
   197			params.mode = modeGoroutineOriented
   198			params.startTime = g.StartTime
   199			if g.EndTime != 0 {
   200				params.endTime = g.EndTime
   201			} else { // The goroutine didn't end.
   202				params.endTime = lastTimestamp()
   203			}
   204			params.maing = goid
   205			params.gs = trace.RelatedGoroutines(res.Events, goid)
   206		} else if taskids := r.FormValue("taskid"); taskids != "" {
   207			taskid, err := strconv.ParseUint(taskids, 10, 64)
   208			if err != nil {
   209				log.Printf("failed to parse taskid parameter %q: %v", taskids, err)
   210				return
   211			}
   212			annotRes, _ := analyzeAnnotations()
   213			task, ok := annotRes.tasks[taskid]
   214			if !ok || len(task.events) == 0 {
   215				log.Printf("failed to find task with id %d", taskid)
   216				return
   217			}
   218			goid := task.events[0].G
   219			params.mode = modeGoroutineOriented | modeTaskOriented
   220			params.startTime = task.firstTimestamp() - 1
   221			params.endTime = task.lastTimestamp() + 1
   222			params.maing = goid
   223			params.tasks = task.descendants()
   224			gs := map[uint64]bool{}
   225			for _, t := range params.tasks {
   226				// find only directly involved goroutines
   227				for k, v := range t.RelatedGoroutines(res.Events, 0) {
   228					gs[k] = v
   229				}
   230			}
   231			params.gs = gs
   232		} else if taskids := r.FormValue("focustask"); taskids != "" {
   233			taskid, err := strconv.ParseUint(taskids, 10, 64)
   234			if err != nil {
   235				log.Printf("failed to parse focustask parameter %q: %v", taskids, err)
   236				return
   237			}
   238			annotRes, _ := analyzeAnnotations()
   239			task, ok := annotRes.tasks[taskid]
   240			if !ok || len(task.events) == 0 {
   241				log.Printf("failed to find task with id %d", taskid)
   242				return
   243			}
   244			params.mode = modeTaskOriented
   245			params.startTime = task.firstTimestamp() - 1
   246			params.endTime = task.lastTimestamp() + 1
   247			params.tasks = task.descendants()
   248		}
   249	
   250		start := int64(0)
   251		end := int64(math.MaxInt64)
   252		if startStr, endStr := r.FormValue("start"), r.FormValue("end"); startStr != "" && endStr != "" {
   253			// If start/end arguments are present, we are rendering a range of the trace.
   254			start, err = strconv.ParseInt(startStr, 10, 64)
   255			if err != nil {
   256				log.Printf("failed to parse start parameter %q: %v", startStr, err)
   257				return
   258			}
   259			end, err = strconv.ParseInt(endStr, 10, 64)
   260			if err != nil {
   261				log.Printf("failed to parse end parameter %q: %v", endStr, err)
   262				return
   263			}
   264		}
   265	
   266		c := viewerDataTraceConsumer(w, start, end)
   267		if err := generateTrace(params, c); err != nil {
   268			log.Printf("failed to generate trace: %v", err)
   269			return
   270		}
   271	}
   272	
   273	type Range struct {
   274		Name      string
   275		Start     int
   276		End       int
   277		StartTime int64
   278		EndTime   int64
   279	}
   280	
   281	func (r Range) URL() string {
   282		return fmt.Sprintf("/trace?start=%d&end=%d", r.Start, r.End)
   283	}
   284	
   285	// splitTrace splits the trace into a number of ranges,
   286	// each resulting in approx 100MB of json output
   287	// (trace viewer can hardly handle more).
   288	func splitTrace(res trace.ParseResult) []Range {
   289		params := &traceParams{
   290			parsed:  res,
   291			endTime: math.MaxInt64,
   292		}
   293		s, c := splittingTraceConsumer(100 << 20) // 100M
   294		if err := generateTrace(params, c); err != nil {
   295			dief("%v\n", err)
   296		}
   297		return s.Ranges
   298	}
   299	
   300	type splitter struct {
   301		Ranges []Range
   302	}
   303	
   304	func splittingTraceConsumer(max int) (*splitter, traceConsumer) {
   305		type eventSz struct {
   306			Time float64
   307			Sz   int
   308		}
   309	
   310		var (
   311			data = ViewerData{Frames: make(map[string]ViewerFrame)}
   312	
   313			sizes []eventSz
   314			cw    countingWriter
   315		)
   316	
   317		s := new(splitter)
   318	
   319		return s, traceConsumer{
   320			consumeTimeUnit: func(unit string) {
   321				data.TimeUnit = unit
   322			},
   323			consumeViewerEvent: func(v *ViewerEvent, required bool) {
   324				if required {
   325					// Store required events inside data
   326					// so flush can include them in the required
   327					// part of the trace.
   328					data.Events = append(data.Events, v)
   329					return
   330				}
   331				enc := json.NewEncoder(&cw)
   332				enc.Encode(v)
   333				sizes = append(sizes, eventSz{v.Time, cw.size + 1}) // +1 for ",".
   334				cw.size = 0
   335			},
   336			consumeViewerFrame: func(k string, v ViewerFrame) {
   337				data.Frames[k] = v
   338			},
   339			flush: func() {
   340				// Calculate size of the mandatory part of the trace.
   341				// This includes stack traces and thread names.
   342				cw.size = 0
   343				enc := json.NewEncoder(&cw)
   344				enc.Encode(data)
   345				minSize := cw.size
   346	
   347				// Then calculate size of each individual event
   348				// and group them into ranges.
   349				sum := minSize
   350				start := 0
   351				for i, ev := range sizes {
   352					if sum+ev.Sz > max {
   353						startTime := time.Duration(sizes[start].Time * 1000)
   354						endTime := time.Duration(ev.Time * 1000)
   355						ranges = append(ranges, Range{
   356							Name:      fmt.Sprintf("%v-%v", startTime, endTime),
   357							Start:     start,
   358							End:       i + 1,
   359							StartTime: int64(startTime),
   360							EndTime:   int64(endTime),
   361						})
   362						start = i + 1
   363						sum = minSize
   364					} else {
   365						sum += ev.Sz + 1
   366					}
   367				}
   368				if len(ranges) <= 1 {
   369					s.Ranges = nil
   370					return
   371				}
   372	
   373				if end := len(sizes) - 1; start < end {
   374					ranges = append(ranges, Range{
   375						Name:      fmt.Sprintf("%v-%v", time.Duration(sizes[start].Time*1000), time.Duration(sizes[end].Time*1000)),
   376						Start:     start,
   377						End:       end,
   378						StartTime: int64(sizes[start].Time * 1000),
   379						EndTime:   int64(sizes[end].Time * 1000),
   380					})
   381				}
   382				s.Ranges = ranges
   383			},
   384		}
   385	}
   386	
   387	type countingWriter struct {
   388		size int
   389	}
   390	
   391	func (cw *countingWriter) Write(data []byte) (int, error) {
   392		cw.size += len(data)
   393		return len(data), nil
   394	}
   395	
   396	type traceParams struct {
   397		parsed    trace.ParseResult
   398		mode      traceviewMode
   399		startTime int64
   400		endTime   int64
   401		maing     uint64          // for goroutine-oriented view, place this goroutine on the top row
   402		gs        map[uint64]bool // Goroutines to be displayed for goroutine-oriented or task-oriented view
   403		tasks     []*taskDesc     // Tasks to be displayed. tasks[0] is the top-most task
   404	}
   405	
   406	type traceviewMode uint
   407	
   408	const (
   409		modeGoroutineOriented traceviewMode = 1 << iota
   410		modeTaskOriented
   411	)
   412	
   413	type traceContext struct {
   414		*traceParams
   415		consumer  traceConsumer
   416		frameTree frameNode
   417		frameSeq  int
   418		arrowSeq  uint64
   419		gcount    uint64
   420	
   421		heapStats, prevHeapStats     heapStats
   422		threadStats, prevThreadStats threadStats
   423		gstates, prevGstates         [gStateCount]int64
   424	
   425		regionID int // last emitted region id. incremented in each emitRegion call.
   426	}
   427	
   428	type heapStats struct {
   429		heapAlloc uint64
   430		nextGC    uint64
   431	}
   432	
   433	type threadStats struct {
   434		insyscallRuntime int64 // system goroutine in syscall
   435		insyscall        int64 // user goroutine in syscall
   436		prunning         int64 // thread running P
   437	}
   438	
   439	type frameNode struct {
   440		id       int
   441		children map[uint64]frameNode
   442	}
   443	
   444	type gState int
   445	
   446	const (
   447		gDead gState = iota
   448		gRunnable
   449		gRunning
   450		gWaiting
   451		gWaitingGC
   452	
   453		gStateCount
   454	)
   455	
   456	type gInfo struct {
   457		state      gState // current state
   458		name       string // name chosen for this goroutine at first EvGoStart
   459		isSystemG  bool
   460		start      *trace.Event // most recent EvGoStart
   461		markAssist *trace.Event // if non-nil, the mark assist currently running.
   462	}
   463	
   464	type ViewerData struct {
   465		Events   []*ViewerEvent         `json:"traceEvents"`
   466		Frames   map[string]ViewerFrame `json:"stackFrames"`
   467		TimeUnit string                 `json:"displayTimeUnit"`
   468	
   469		// This is where mandatory part of the trace starts (e.g. thread names)
   470		footer int
   471	}
   472	
   473	type ViewerEvent struct {
   474		Name     string      `json:"name,omitempty"`
   475		Phase    string      `json:"ph"`
   476		Scope    string      `json:"s,omitempty"`
   477		Time     float64     `json:"ts"`
   478		Dur      float64     `json:"dur,omitempty"`
   479		Pid      uint64      `json:"pid"`
   480		Tid      uint64      `json:"tid"`
   481		ID       uint64      `json:"id,omitempty"`
   482		Stack    int         `json:"sf,omitempty"`
   483		EndStack int         `json:"esf,omitempty"`
   484		Arg      interface{} `json:"args,omitempty"`
   485		Cname    string      `json:"cname,omitempty"`
   486		Category string      `json:"cat,omitempty"`
   487	}
   488	
   489	type ViewerFrame struct {
   490		Name   string `json:"name"`
   491		Parent int    `json:"parent,omitempty"`
   492	}
   493	
   494	type NameArg struct {
   495		Name string `json:"name"`
   496	}
   497	
   498	type TaskArg struct {
   499		ID     uint64 `json:"id"`
   500		StartG uint64 `json:"start_g,omitempty"`
   501		EndG   uint64 `json:"end_g,omitempty"`
   502	}
   503	
   504	type RegionArg struct {
   505		TaskID uint64 `json:"taskid,omitempty"`
   506	}
   507	
   508	type SortIndexArg struct {
   509		Index int `json:"sort_index"`
   510	}
   511	
   512	type traceConsumer struct {
   513		consumeTimeUnit    func(unit string)
   514		consumeViewerEvent func(v *ViewerEvent, required bool)
   515		consumeViewerFrame func(key string, f ViewerFrame)
   516		flush              func()
   517	}
   518	
   519	const (
   520		procsSection = 0 // where Goroutines or per-P timelines are presented.
   521		statsSection = 1 // where counters are presented.
   522		tasksSection = 2 // where Task hierarchy & timeline is presented.
   523	)
   524	
   525	// generateTrace generates json trace for trace-viewer:
   526	// https://github.com/google/trace-viewer
   527	// Trace format is described at:
   528	// https://docs.google.com/document/d/1CvAClvFfyA5R-PhYUmn5OOQtYMH4h6I0nSsKchNAySU/view
   529	// If mode==goroutineMode, generate trace for goroutine goid, otherwise whole trace.
   530	// startTime, endTime determine part of the trace that we are interested in.
   531	// gset restricts goroutines that are included in the resulting trace.
   532	func generateTrace(params *traceParams, consumer traceConsumer) error {
   533		defer consumer.flush()
   534	
   535		ctx := &traceContext{traceParams: params}
   536		ctx.frameTree.children = make(map[uint64]frameNode)
   537		ctx.consumer = consumer
   538	
   539		ctx.consumer.consumeTimeUnit("ns")
   540		maxProc := 0
   541		ginfos := make(map[uint64]*gInfo)
   542		stacks := params.parsed.Stacks
   543	
   544		getGInfo := func(g uint64) *gInfo {
   545			info, ok := ginfos[g]
   546			if !ok {
   547				info = &gInfo{}
   548				ginfos[g] = info
   549			}
   550			return info
   551		}
   552	
   553		// Since we make many calls to setGState, we record a sticky
   554		// error in setGStateErr and check it after every event.
   555		var setGStateErr error
   556		setGState := func(ev *trace.Event, g uint64, oldState, newState gState) {
   557			info := getGInfo(g)
   558			if oldState == gWaiting && info.state == gWaitingGC {
   559				// For checking, gWaiting counts as any gWaiting*.
   560				oldState = info.state
   561			}
   562			if info.state != oldState && setGStateErr == nil {
   563				setGStateErr = fmt.Errorf("expected G %d to be in state %d, but got state %d", g, oldState, newState)
   564			}
   565			ctx.gstates[info.state]--
   566			ctx.gstates[newState]++
   567			info.state = newState
   568		}
   569	
   570		for _, ev := range ctx.parsed.Events {
   571			// Handle state transitions before we filter out events.
   572			switch ev.Type {
   573			case trace.EvGoStart, trace.EvGoStartLabel:
   574				setGState(ev, ev.G, gRunnable, gRunning)
   575				info := getGInfo(ev.G)
   576				info.start = ev
   577			case trace.EvProcStart:
   578				ctx.threadStats.prunning++
   579			case trace.EvProcStop:
   580				ctx.threadStats.prunning--
   581			case trace.EvGoCreate:
   582				newG := ev.Args[0]
   583				info := getGInfo(newG)
   584				if info.name != "" {
   585					return fmt.Errorf("duplicate go create event for go id=%d detected at offset %d", newG, ev.Off)
   586				}
   587	
   588				stk, ok := stacks[ev.Args[1]]
   589				if !ok || len(stk) == 0 {
   590					return fmt.Errorf("invalid go create event: missing stack information for go id=%d at offset %d", newG, ev.Off)
   591				}
   592	
   593				fname := stk[0].Fn
   594				info.name = fmt.Sprintf("G%v %s", newG, fname)
   595				info.isSystemG = isSystemGoroutine(fname)
   596	
   597				ctx.gcount++
   598				setGState(ev, newG, gDead, gRunnable)
   599			case trace.EvGoEnd:
   600				ctx.gcount--
   601				setGState(ev, ev.G, gRunning, gDead)
   602			case trace.EvGoUnblock:
   603				setGState(ev, ev.Args[0], gWaiting, gRunnable)
   604			case trace.EvGoSysExit:
   605				setGState(ev, ev.G, gWaiting, gRunnable)
   606				if getGInfo(ev.G).isSystemG {
   607					ctx.threadStats.insyscallRuntime--
   608				} else {
   609					ctx.threadStats.insyscall--
   610				}
   611			case trace.EvGoSysBlock:
   612				setGState(ev, ev.G, gRunning, gWaiting)
   613				if getGInfo(ev.G).isSystemG {
   614					ctx.threadStats.insyscallRuntime++
   615				} else {
   616					ctx.threadStats.insyscall++
   617				}
   618			case trace.EvGoSched, trace.EvGoPreempt:
   619				setGState(ev, ev.G, gRunning, gRunnable)
   620			case trace.EvGoStop,
   621				trace.EvGoSleep, trace.EvGoBlock, trace.EvGoBlockSend, trace.EvGoBlockRecv,
   622				trace.EvGoBlockSelect, trace.EvGoBlockSync, trace.EvGoBlockCond, trace.EvGoBlockNet:
   623				setGState(ev, ev.G, gRunning, gWaiting)
   624			case trace.EvGoBlockGC:
   625				setGState(ev, ev.G, gRunning, gWaitingGC)
   626			case trace.EvGCMarkAssistStart:
   627				getGInfo(ev.G).markAssist = ev
   628			case trace.EvGCMarkAssistDone:
   629				getGInfo(ev.G).markAssist = nil
   630			case trace.EvGoWaiting:
   631				setGState(ev, ev.G, gRunnable, gWaiting)
   632			case trace.EvGoInSyscall:
   633				// Cancel out the effect of EvGoCreate at the beginning.
   634				setGState(ev, ev.G, gRunnable, gWaiting)
   635				if getGInfo(ev.G).isSystemG {
   636					ctx.threadStats.insyscallRuntime++
   637				} else {
   638					ctx.threadStats.insyscall++
   639				}
   640			case trace.EvHeapAlloc:
   641				ctx.heapStats.heapAlloc = ev.Args[0]
   642			case trace.EvNextGC:
   643				ctx.heapStats.nextGC = ev.Args[0]
   644			}
   645			if setGStateErr != nil {
   646				return setGStateErr
   647			}
   648			if ctx.gstates[gRunnable] < 0 || ctx.gstates[gRunning] < 0 || ctx.threadStats.insyscall < 0 || ctx.threadStats.insyscallRuntime < 0 {
   649				return fmt.Errorf("invalid state after processing %v: runnable=%d running=%d insyscall=%d insyscallRuntime=%d", ev, ctx.gstates[gRunnable], ctx.gstates[gRunning], ctx.threadStats.insyscall, ctx.threadStats.insyscallRuntime)
   650			}
   651	
   652			// Ignore events that are from uninteresting goroutines
   653			// or outside of the interesting timeframe.
   654			if ctx.gs != nil && ev.P < trace.FakeP && !ctx.gs[ev.G] {
   655				continue
   656			}
   657			if !withinTimeRange(ev, ctx.startTime, ctx.endTime) {
   658				continue
   659			}
   660	
   661			if ev.P < trace.FakeP && ev.P > maxProc {
   662				maxProc = ev.P
   663			}
   664	
   665			// Emit trace objects.
   666			switch ev.Type {
   667			case trace.EvProcStart:
   668				if ctx.mode&modeGoroutineOriented != 0 {
   669					continue
   670				}
   671				ctx.emitInstant(ev, "proc start", "")
   672			case trace.EvProcStop:
   673				if ctx.mode&modeGoroutineOriented != 0 {
   674					continue
   675				}
   676				ctx.emitInstant(ev, "proc stop", "")
   677			case trace.EvGCStart:
   678				ctx.emitSlice(ev, "GC")
   679			case trace.EvGCDone:
   680			case trace.EvGCSTWStart:
   681				if ctx.mode&modeGoroutineOriented != 0 {
   682					continue
   683				}
   684				ctx.emitSlice(ev, fmt.Sprintf("STW (%s)", ev.SArgs[0]))
   685			case trace.EvGCSTWDone:
   686			case trace.EvGCMarkAssistStart:
   687				// Mark assists can continue past preemptions, so truncate to the
   688				// whichever comes first. We'll synthesize another slice if
   689				// necessary in EvGoStart.
   690				markFinish := ev.Link
   691				goFinish := getGInfo(ev.G).start.Link
   692				fakeMarkStart := *ev
   693				text := "MARK ASSIST"
   694				if markFinish == nil || markFinish.Ts > goFinish.Ts {
   695					fakeMarkStart.Link = goFinish
   696					text = "MARK ASSIST (unfinished)"
   697				}
   698				ctx.emitSlice(&fakeMarkStart, text)
   699			case trace.EvGCSweepStart:
   700				slice := ctx.makeSlice(ev, "SWEEP")
   701				if done := ev.Link; done != nil && done.Args[0] != 0 {
   702					slice.Arg = struct {
   703						Swept     uint64 `json:"Swept bytes"`
   704						Reclaimed uint64 `json:"Reclaimed bytes"`
   705					}{done.Args[0], done.Args[1]}
   706				}
   707				ctx.emit(slice)
   708			case trace.EvGoStart, trace.EvGoStartLabel:
   709				info := getGInfo(ev.G)
   710				if ev.Type == trace.EvGoStartLabel {
   711					ctx.emitSlice(ev, ev.SArgs[0])
   712				} else {
   713					ctx.emitSlice(ev, info.name)
   714				}
   715				if info.markAssist != nil {
   716					// If we're in a mark assist, synthesize a new slice, ending
   717					// either when the mark assist ends or when we're descheduled.
   718					markFinish := info.markAssist.Link
   719					goFinish := ev.Link
   720					fakeMarkStart := *ev
   721					text := "MARK ASSIST (resumed, unfinished)"
   722					if markFinish != nil && markFinish.Ts < goFinish.Ts {
   723						fakeMarkStart.Link = markFinish
   724						text = "MARK ASSIST (resumed)"
   725					}
   726					ctx.emitSlice(&fakeMarkStart, text)
   727				}
   728			case trace.EvGoCreate:
   729				ctx.emitArrow(ev, "go")
   730			case trace.EvGoUnblock:
   731				ctx.emitArrow(ev, "unblock")
   732			case trace.EvGoSysCall:
   733				ctx.emitInstant(ev, "syscall", "")
   734			case trace.EvGoSysExit:
   735				ctx.emitArrow(ev, "sysexit")
   736			case trace.EvUserLog:
   737				ctx.emitInstant(ev, formatUserLog(ev), "user event")
   738			case trace.EvUserTaskCreate:
   739				ctx.emitInstant(ev, "task start", "user event")
   740			case trace.EvUserTaskEnd:
   741				ctx.emitInstant(ev, "task end", "user event")
   742			}
   743			// Emit any counter updates.
   744			ctx.emitThreadCounters(ev)
   745			ctx.emitHeapCounters(ev)
   746			ctx.emitGoroutineCounters(ev)
   747		}
   748	
   749		ctx.emitSectionFooter(statsSection, "STATS", 0)
   750	
   751		if ctx.mode&modeTaskOriented != 0 {
   752			ctx.emitSectionFooter(tasksSection, "TASKS", 1)
   753		}
   754	
   755		if ctx.mode&modeGoroutineOriented != 0 {
   756			ctx.emitSectionFooter(procsSection, "G", 2)
   757		} else {
   758			ctx.emitSectionFooter(procsSection, "PROCS", 2)
   759		}
   760	
   761		ctx.emitFooter(&ViewerEvent{Name: "thread_name", Phase: "M", Pid: procsSection, Tid: trace.GCP, Arg: &NameArg{"GC"}})
   762		ctx.emitFooter(&ViewerEvent{Name: "thread_sort_index", Phase: "M", Pid: procsSection, Tid: trace.GCP, Arg: &SortIndexArg{-6}})
   763	
   764		ctx.emitFooter(&ViewerEvent{Name: "thread_name", Phase: "M", Pid: procsSection, Tid: trace.NetpollP, Arg: &NameArg{"Network"}})
   765		ctx.emitFooter(&ViewerEvent{Name: "thread_sort_index", Phase: "M", Pid: procsSection, Tid: trace.NetpollP, Arg: &SortIndexArg{-5}})
   766	
   767		ctx.emitFooter(&ViewerEvent{Name: "thread_name", Phase: "M", Pid: procsSection, Tid: trace.TimerP, Arg: &NameArg{"Timers"}})
   768		ctx.emitFooter(&ViewerEvent{Name: "thread_sort_index", Phase: "M", Pid: procsSection, Tid: trace.TimerP, Arg: &SortIndexArg{-4}})
   769	
   770		ctx.emitFooter(&ViewerEvent{Name: "thread_name", Phase: "M", Pid: procsSection, Tid: trace.SyscallP, Arg: &NameArg{"Syscalls"}})
   771		ctx.emitFooter(&ViewerEvent{Name: "thread_sort_index", Phase: "M", Pid: procsSection, Tid: trace.SyscallP, Arg: &SortIndexArg{-3}})
   772	
   773		// Display rows for Ps if we are in the default trace view mode (not goroutine-oriented presentation)
   774		if ctx.mode&modeGoroutineOriented == 0 {
   775			for i := 0; i <= maxProc; i++ {
   776				ctx.emitFooter(&ViewerEvent{Name: "thread_name", Phase: "M", Pid: procsSection, Tid: uint64(i), Arg: &NameArg{fmt.Sprintf("Proc %v", i)}})
   777				ctx.emitFooter(&ViewerEvent{Name: "thread_sort_index", Phase: "M", Pid: procsSection, Tid: uint64(i), Arg: &SortIndexArg{i}})
   778			}
   779		}
   780	
   781		// Display task and its regions if we are in task-oriented presentation mode.
   782		if ctx.mode&modeTaskOriented != 0 {
   783			// sort tasks based on the task start time.
   784			sortedTask := make([]*taskDesc, 0, len(ctx.tasks))
   785			for _, task := range ctx.tasks {
   786				sortedTask = append(sortedTask, task)
   787			}
   788			sort.SliceStable(sortedTask, func(i, j int) bool {
   789				ti, tj := sortedTask[i], sortedTask[j]
   790				if ti.firstTimestamp() == tj.firstTimestamp() {
   791					return ti.lastTimestamp() < tj.lastTimestamp()
   792				}
   793				return ti.firstTimestamp() < tj.firstTimestamp()
   794			})
   795	
   796			for i, task := range sortedTask {
   797				ctx.emitTask(task, i)
   798	
   799				// If we are in goroutine-oriented mode, we draw regions.
   800				// TODO(hyangah): add this for task/P-oriented mode (i.e., focustask view) too.
   801				if ctx.mode&modeGoroutineOriented != 0 {
   802					for _, s := range task.regions {
   803						ctx.emitRegion(s)
   804					}
   805				}
   806			}
   807		}
   808	
   809		// Display goroutine rows if we are either in goroutine-oriented mode.
   810		if ctx.mode&modeGoroutineOriented != 0 {
   811			for k, v := range ginfos {
   812				if !ctx.gs[k] {
   813					continue
   814				}
   815				ctx.emitFooter(&ViewerEvent{Name: "thread_name", Phase: "M", Pid: procsSection, Tid: k, Arg: &NameArg{v.name}})
   816			}
   817			// Row for the main goroutine (maing)
   818			ctx.emitFooter(&ViewerEvent{Name: "thread_sort_index", Phase: "M", Pid: procsSection, Tid: ctx.maing, Arg: &SortIndexArg{-2}})
   819			// Row for GC or global state (specified with G=0)
   820			ctx.emitFooter(&ViewerEvent{Name: "thread_sort_index", Phase: "M", Pid: procsSection, Tid: 0, Arg: &SortIndexArg{-1}})
   821		}
   822	
   823		return nil
   824	}
   825	
   826	func (ctx *traceContext) emit(e *ViewerEvent) {
   827		ctx.consumer.consumeViewerEvent(e, false)
   828	}
   829	
   830	func (ctx *traceContext) emitFooter(e *ViewerEvent) {
   831		ctx.consumer.consumeViewerEvent(e, true)
   832	}
   833	func (ctx *traceContext) emitSectionFooter(sectionID uint64, name string, priority int) {
   834		ctx.emitFooter(&ViewerEvent{Name: "process_name", Phase: "M", Pid: sectionID, Arg: &NameArg{name}})
   835		ctx.emitFooter(&ViewerEvent{Name: "process_sort_index", Phase: "M", Pid: sectionID, Arg: &SortIndexArg{priority}})
   836	}
   837	
   838	func (ctx *traceContext) time(ev *trace.Event) float64 {
   839		// Trace viewer wants timestamps in microseconds.
   840		return float64(ev.Ts) / 1000
   841	}
   842	
   843	func withinTimeRange(ev *trace.Event, s, e int64) bool {
   844		if evEnd := ev.Link; evEnd != nil {
   845			return ev.Ts <= e && evEnd.Ts >= s
   846		}
   847		return ev.Ts >= s && ev.Ts <= e
   848	}
   849	
   850	func tsWithinRange(ts, s, e int64) bool {
   851		return s <= ts && ts <= e
   852	}
   853	
   854	func (ctx *traceContext) proc(ev *trace.Event) uint64 {
   855		if ctx.mode&modeGoroutineOriented != 0 && ev.P < trace.FakeP {
   856			return ev.G
   857		} else {
   858			return uint64(ev.P)
   859		}
   860	}
   861	
   862	func (ctx *traceContext) emitSlice(ev *trace.Event, name string) {
   863		ctx.emit(ctx.makeSlice(ev, name))
   864	}
   865	
   866	func (ctx *traceContext) makeSlice(ev *trace.Event, name string) *ViewerEvent {
   867		// If ViewerEvent.Dur is not a positive value,
   868		// trace viewer handles it as a non-terminating time interval.
   869		// Avoid it by setting the field with a small value.
   870		durationUsec := ctx.time(ev.Link) - ctx.time(ev)
   871		if ev.Link.Ts-ev.Ts <= 0 {
   872			durationUsec = 0.0001 // 0.1 nanoseconds
   873		}
   874		sl := &ViewerEvent{
   875			Name:     name,
   876			Phase:    "X",
   877			Time:     ctx.time(ev),
   878			Dur:      durationUsec,
   879			Tid:      ctx.proc(ev),
   880			Stack:    ctx.stack(ev.Stk),
   881			EndStack: ctx.stack(ev.Link.Stk),
   882		}
   883	
   884		// grey out non-overlapping events if the event is not a global event (ev.G == 0)
   885		if ctx.mode&modeTaskOriented != 0 && ev.G != 0 {
   886			// include P information.
   887			if t := ev.Type; t == trace.EvGoStart || t == trace.EvGoStartLabel {
   888				type Arg struct {
   889					P int
   890				}
   891				sl.Arg = &Arg{P: ev.P}
   892			}
   893			// grey out non-overlapping events.
   894			overlapping := false
   895			for _, task := range ctx.tasks {
   896				if _, overlapped := task.overlappingDuration(ev); overlapped {
   897					overlapping = true
   898					break
   899				}
   900			}
   901			if !overlapping {
   902				sl.Cname = colorLightGrey
   903			}
   904		}
   905		return sl
   906	}
   907	
   908	func (ctx *traceContext) emitTask(task *taskDesc, sortIndex int) {
   909		taskRow := uint64(task.id)
   910		taskName := task.name
   911		durationUsec := float64(task.lastTimestamp()-task.firstTimestamp()) / 1e3
   912	
   913		ctx.emitFooter(&ViewerEvent{Name: "thread_name", Phase: "M", Pid: tasksSection, Tid: taskRow, Arg: &NameArg{fmt.Sprintf("T%d %s", task.id, taskName)}})
   914		ctx.emit(&ViewerEvent{Name: "thread_sort_index", Phase: "M", Pid: tasksSection, Tid: taskRow, Arg: &SortIndexArg{sortIndex}})
   915		ts := float64(task.firstTimestamp()) / 1e3
   916		sl := &ViewerEvent{
   917			Name:  taskName,
   918			Phase: "X",
   919			Time:  ts,
   920			Dur:   durationUsec,
   921			Pid:   tasksSection,
   922			Tid:   taskRow,
   923			Cname: pickTaskColor(task.id),
   924		}
   925		targ := TaskArg{ID: task.id}
   926		if task.create != nil {
   927			sl.Stack = ctx.stack(task.create.Stk)
   928			targ.StartG = task.create.G
   929		}
   930		if task.end != nil {
   931			sl.EndStack = ctx.stack(task.end.Stk)
   932			targ.EndG = task.end.G
   933		}
   934		sl.Arg = targ
   935		ctx.emit(sl)
   936	
   937		if task.create != nil && task.create.Type == trace.EvUserTaskCreate && task.create.Args[1] != 0 {
   938			ctx.arrowSeq++
   939			ctx.emit(&ViewerEvent{Name: "newTask", Phase: "s", Tid: task.create.Args[1], ID: ctx.arrowSeq, Time: ts, Pid: tasksSection})
   940			ctx.emit(&ViewerEvent{Name: "newTask", Phase: "t", Tid: taskRow, ID: ctx.arrowSeq, Time: ts, Pid: tasksSection})
   941		}
   942	}
   943	
   944	func (ctx *traceContext) emitRegion(s regionDesc) {
   945		if s.Name == "" {
   946			return
   947		}
   948	
   949		if !tsWithinRange(s.firstTimestamp(), ctx.startTime, ctx.endTime) &&
   950			!tsWithinRange(s.lastTimestamp(), ctx.startTime, ctx.endTime) {
   951			return
   952		}
   953	
   954		ctx.regionID++
   955		regionID := ctx.regionID
   956	
   957		id := s.TaskID
   958		scopeID := fmt.Sprintf("%x", id)
   959		name := s.Name
   960	
   961		sl0 := &ViewerEvent{
   962			Category: "Region",
   963			Name:     name,
   964			Phase:    "b",
   965			Time:     float64(s.firstTimestamp()) / 1e3,
   966			Tid:      s.G, // only in goroutine-oriented view
   967			ID:       uint64(regionID),
   968			Scope:    scopeID,
   969			Cname:    pickTaskColor(s.TaskID),
   970		}
   971		if s.Start != nil {
   972			sl0.Stack = ctx.stack(s.Start.Stk)
   973		}
   974		ctx.emit(sl0)
   975	
   976		sl1 := &ViewerEvent{
   977			Category: "Region",
   978			Name:     name,
   979			Phase:    "e",
   980			Time:     float64(s.lastTimestamp()) / 1e3,
   981			Tid:      s.G,
   982			ID:       uint64(regionID),
   983			Scope:    scopeID,
   984			Cname:    pickTaskColor(s.TaskID),
   985			Arg:      RegionArg{TaskID: s.TaskID},
   986		}
   987		if s.End != nil {
   988			sl1.Stack = ctx.stack(s.End.Stk)
   989		}
   990		ctx.emit(sl1)
   991	}
   992	
   993	type heapCountersArg struct {
   994		Allocated uint64
   995		NextGC    uint64
   996	}
   997	
   998	func (ctx *traceContext) emitHeapCounters(ev *trace.Event) {
   999		if ctx.prevHeapStats == ctx.heapStats {
  1000			return
  1001		}
  1002		diff := uint64(0)
  1003		if ctx.heapStats.nextGC > ctx.heapStats.heapAlloc {
  1004			diff = ctx.heapStats.nextGC - ctx.heapStats.heapAlloc
  1005		}
  1006		if tsWithinRange(ev.Ts, ctx.startTime, ctx.endTime) {
  1007			ctx.emit(&ViewerEvent{Name: "Heap", Phase: "C", Time: ctx.time(ev), Pid: 1, Arg: &heapCountersArg{ctx.heapStats.heapAlloc, diff}})
  1008		}
  1009		ctx.prevHeapStats = ctx.heapStats
  1010	}
  1011	
  1012	type goroutineCountersArg struct {
  1013		Running   uint64
  1014		Runnable  uint64
  1015		GCWaiting uint64
  1016	}
  1017	
  1018	func (ctx *traceContext) emitGoroutineCounters(ev *trace.Event) {
  1019		if ctx.prevGstates == ctx.gstates {
  1020			return
  1021		}
  1022		if tsWithinRange(ev.Ts, ctx.startTime, ctx.endTime) {
  1023			ctx.emit(&ViewerEvent{Name: "Goroutines", Phase: "C", Time: ctx.time(ev), Pid: 1, Arg: &goroutineCountersArg{uint64(ctx.gstates[gRunning]), uint64(ctx.gstates[gRunnable]), uint64(ctx.gstates[gWaitingGC])}})
  1024		}
  1025		ctx.prevGstates = ctx.gstates
  1026	}
  1027	
  1028	type threadCountersArg struct {
  1029		Running   int64
  1030		InSyscall int64
  1031	}
  1032	
  1033	func (ctx *traceContext) emitThreadCounters(ev *trace.Event) {
  1034		if ctx.prevThreadStats == ctx.threadStats {
  1035			return
  1036		}
  1037		if tsWithinRange(ev.Ts, ctx.startTime, ctx.endTime) {
  1038			ctx.emit(&ViewerEvent{Name: "Threads", Phase: "C", Time: ctx.time(ev), Pid: 1, Arg: &threadCountersArg{
  1039				Running:   ctx.threadStats.prunning,
  1040				InSyscall: ctx.threadStats.insyscall}})
  1041		}
  1042		ctx.prevThreadStats = ctx.threadStats
  1043	}
  1044	
  1045	func (ctx *traceContext) emitInstant(ev *trace.Event, name, category string) {
  1046		if !tsWithinRange(ev.Ts, ctx.startTime, ctx.endTime) {
  1047			return
  1048		}
  1049	
  1050		cname := ""
  1051		if ctx.mode&modeTaskOriented != 0 {
  1052			taskID, isUserAnnotation := isUserAnnotationEvent(ev)
  1053	
  1054			show := false
  1055			for _, task := range ctx.tasks {
  1056				if isUserAnnotation && task.id == taskID || task.overlappingInstant(ev) {
  1057					show = true
  1058					break
  1059				}
  1060			}
  1061			// grey out or skip if non-overlapping instant.
  1062			if !show {
  1063				if isUserAnnotation {
  1064					return // don't display unrelated user annotation events.
  1065				}
  1066				cname = colorLightGrey
  1067			}
  1068		}
  1069		var arg interface{}
  1070		if ev.Type == trace.EvProcStart {
  1071			type Arg struct {
  1072				ThreadID uint64
  1073			}
  1074			arg = &Arg{ev.Args[0]}
  1075		}
  1076		ctx.emit(&ViewerEvent{
  1077			Name:     name,
  1078			Category: category,
  1079			Phase:    "I",
  1080			Scope:    "t",
  1081			Time:     ctx.time(ev),
  1082			Tid:      ctx.proc(ev),
  1083			Stack:    ctx.stack(ev.Stk),
  1084			Cname:    cname,
  1085			Arg:      arg})
  1086	}
  1087	
  1088	func (ctx *traceContext) emitArrow(ev *trace.Event, name string) {
  1089		if ev.Link == nil {
  1090			// The other end of the arrow is not captured in the trace.
  1091			// For example, a goroutine was unblocked but was not scheduled before trace stop.
  1092			return
  1093		}
  1094		if ctx.mode&modeGoroutineOriented != 0 && (!ctx.gs[ev.Link.G] || ev.Link.Ts < ctx.startTime || ev.Link.Ts > ctx.endTime) {
  1095			return
  1096		}
  1097	
  1098		if ev.P == trace.NetpollP || ev.P == trace.TimerP || ev.P == trace.SyscallP {
  1099			// Trace-viewer discards arrows if they don't start/end inside of a slice or instant.
  1100			// So emit a fake instant at the start of the arrow.
  1101			ctx.emitInstant(&trace.Event{P: ev.P, Ts: ev.Ts}, "unblock", "")
  1102		}
  1103	
  1104		color := ""
  1105		if ctx.mode&modeTaskOriented != 0 {
  1106			overlapping := false
  1107			// skip non-overlapping arrows.
  1108			for _, task := range ctx.tasks {
  1109				if _, overlapped := task.overlappingDuration(ev); overlapped {
  1110					overlapping = true
  1111					break
  1112				}
  1113			}
  1114			if !overlapping {
  1115				return
  1116			}
  1117		}
  1118	
  1119		ctx.arrowSeq++
  1120		ctx.emit(&ViewerEvent{Name: name, Phase: "s", Tid: ctx.proc(ev), ID: ctx.arrowSeq, Time: ctx.time(ev), Stack: ctx.stack(ev.Stk), Cname: color})
  1121		ctx.emit(&ViewerEvent{Name: name, Phase: "t", Tid: ctx.proc(ev.Link), ID: ctx.arrowSeq, Time: ctx.time(ev.Link), Cname: color})
  1122	}
  1123	
  1124	func (ctx *traceContext) stack(stk []*trace.Frame) int {
  1125		return ctx.buildBranch(ctx.frameTree, stk)
  1126	}
  1127	
  1128	// buildBranch builds one branch in the prefix tree rooted at ctx.frameTree.
  1129	func (ctx *traceContext) buildBranch(parent frameNode, stk []*trace.Frame) int {
  1130		if len(stk) == 0 {
  1131			return parent.id
  1132		}
  1133		last := len(stk) - 1
  1134		frame := stk[last]
  1135		stk = stk[:last]
  1136	
  1137		node, ok := parent.children[frame.PC]
  1138		if !ok {
  1139			ctx.frameSeq++
  1140			node.id = ctx.frameSeq
  1141			node.children = make(map[uint64]frameNode)
  1142			parent.children[frame.PC] = node
  1143			ctx.consumer.consumeViewerFrame(strconv.Itoa(node.id), ViewerFrame{fmt.Sprintf("%v:%v", frame.Fn, frame.Line), parent.id})
  1144		}
  1145		return ctx.buildBranch(node, stk)
  1146	}
  1147	
  1148	func isSystemGoroutine(entryFn string) bool {
  1149		// This mimics runtime.isSystemGoroutine as closely as
  1150		// possible.
  1151		return entryFn != "runtime.main" && strings.HasPrefix(entryFn, "runtime.")
  1152	}
  1153	
  1154	// firstTimestamp returns the timestamp of the first event record.
  1155	func firstTimestamp() int64 {
  1156		res, _ := parseTrace()
  1157		if len(res.Events) > 0 {
  1158			return res.Events[0].Ts
  1159		}
  1160		return 0
  1161	}
  1162	
  1163	// lastTimestamp returns the timestamp of the last event record.
  1164	func lastTimestamp() int64 {
  1165		res, _ := parseTrace()
  1166		if n := len(res.Events); n > 1 {
  1167			return res.Events[n-1].Ts
  1168		}
  1169		return 0
  1170	}
  1171	
  1172	type jsonWriter struct {
  1173		w   io.Writer
  1174		enc *json.Encoder
  1175	}
  1176	
  1177	func viewerDataTraceConsumer(w io.Writer, start, end int64) traceConsumer {
  1178		frames := make(map[string]ViewerFrame)
  1179		enc := json.NewEncoder(w)
  1180		written := 0
  1181		index := int64(-1)
  1182	
  1183		io.WriteString(w, "{")
  1184		return traceConsumer{
  1185			consumeTimeUnit: func(unit string) {
  1186				io.WriteString(w, `"displayTimeUnit":`)
  1187				enc.Encode(unit)
  1188				io.WriteString(w, ",")
  1189			},
  1190			consumeViewerEvent: func(v *ViewerEvent, required bool) {
  1191				index++
  1192				if !required && (index < start || index > end) {
  1193					// not in the range. Skip!
  1194					return
  1195				}
  1196				if written == 0 {
  1197					io.WriteString(w, `"traceEvents": [`)
  1198				}
  1199				if written > 0 {
  1200					io.WriteString(w, ",")
  1201				}
  1202				enc.Encode(v)
  1203				// TODO: get rid of the extra \n inserted by enc.Encode.
  1204				// Same should be applied to splittingTraceConsumer.
  1205				written++
  1206			},
  1207			consumeViewerFrame: func(k string, v ViewerFrame) {
  1208				frames[k] = v
  1209			},
  1210			flush: func() {
  1211				io.WriteString(w, `], "stackFrames":`)
  1212				enc.Encode(frames)
  1213				io.WriteString(w, `}`)
  1214			},
  1215		}
  1216	}
  1217	
  1218	// Mapping from more reasonable color names to the reserved color names in
  1219	// https://github.com/catapult-project/catapult/blob/master/tracing/tracing/base/color_scheme.html#L50
  1220	// The chrome trace viewer allows only those as cname values.
  1221	const (
  1222		colorLightMauve     = "thread_state_uninterruptible" // 182, 125, 143
  1223		colorOrange         = "thread_state_iowait"          // 255, 140, 0
  1224		colorSeafoamGreen   = "thread_state_running"         // 126, 200, 148
  1225		colorVistaBlue      = "thread_state_runnable"        // 133, 160, 210
  1226		colorTan            = "thread_state_unknown"         // 199, 155, 125
  1227		colorIrisBlue       = "background_memory_dump"       // 0, 180, 180
  1228		colorMidnightBlue   = "light_memory_dump"            // 0, 0, 180
  1229		colorDeepMagenta    = "detailed_memory_dump"         // 180, 0, 180
  1230		colorBlue           = "vsync_highlight_color"        // 0, 0, 255
  1231		colorGrey           = "generic_work"                 // 125, 125, 125
  1232		colorGreen          = "good"                         // 0, 125, 0
  1233		colorDarkGoldenrod  = "bad"                          // 180, 125, 0
  1234		colorPeach          = "terrible"                     // 180, 0, 0
  1235		colorBlack          = "black"                        // 0, 0, 0
  1236		colorLightGrey      = "grey"                         // 221, 221, 221
  1237		colorWhite          = "white"                        // 255, 255, 255
  1238		colorYellow         = "yellow"                       // 255, 255, 0
  1239		colorOlive          = "olive"                        // 100, 100, 0
  1240		colorCornflowerBlue = "rail_response"                // 67, 135, 253
  1241		colorSunsetOrange   = "rail_animation"               // 244, 74, 63
  1242		colorTangerine      = "rail_idle"                    // 238, 142, 0
  1243		colorShamrockGreen  = "rail_load"                    // 13, 168, 97
  1244		colorGreenishYellow = "startup"                      // 230, 230, 0
  1245		colorDarkGrey       = "heap_dump_stack_frame"        // 128, 128, 128
  1246		colorTawny          = "heap_dump_child_node_arrow"   // 204, 102, 0
  1247		colorLemon          = "cq_build_running"             // 255, 255, 119
  1248		colorLime           = "cq_build_passed"              // 153, 238, 102
  1249		colorPink           = "cq_build_failed"              // 238, 136, 136
  1250		colorSilver         = "cq_build_abandoned"           // 187, 187, 187
  1251		colorManzGreen      = "cq_build_attempt_runnig"      // 222, 222, 75
  1252		colorKellyGreen     = "cq_build_attempt_passed"      // 108, 218, 35
  1253		colorAnotherGrey    = "cq_build_attempt_failed"      // 187, 187, 187
  1254	)
  1255	
  1256	var colorForTask = []string{
  1257		colorLightMauve,
  1258		colorOrange,
  1259		colorSeafoamGreen,
  1260		colorVistaBlue,
  1261		colorTan,
  1262		colorMidnightBlue,
  1263		colorIrisBlue,
  1264		colorDeepMagenta,
  1265		colorGreen,
  1266		colorDarkGoldenrod,
  1267		colorPeach,
  1268		colorOlive,
  1269		colorCornflowerBlue,
  1270		colorSunsetOrange,
  1271		colorTangerine,
  1272		colorShamrockGreen,
  1273		colorTawny,
  1274		colorLemon,
  1275		colorLime,
  1276		colorPink,
  1277		colorSilver,
  1278		colorManzGreen,
  1279		colorKellyGreen,
  1280	}
  1281	
  1282	func pickTaskColor(id uint64) string {
  1283		idx := id % uint64(len(colorForTask))
  1284		return colorForTask[idx]
  1285	}
  1286	

View as plain text