mirror of
https://github.com/golang/go.git
synced 2025-12-08 06:10:04 +00:00
Change-Id: I1cb7c8b70a5ae16386f6abb577c23d821f7ff7f0 Reviewed-on: https://go-review.googlesource.com/112197 Reviewed-by: Peter Weinberger <pjw@google.com> Run-TryBot: Hyang-Ah Hana Kim <hyangah@gmail.com>
1269 lines
37 KiB
Go
1269 lines
37 KiB
Go
// 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"
|
|
"io"
|
|
"log"
|
|
"math"
|
|
"net/http"
|
|
"path/filepath"
|
|
"runtime"
|
|
"runtime/debug"
|
|
"sort"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
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 := parseTrace()
|
|
if err != nil {
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
if err := r.ParseForm(); err != nil {
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
html := strings.Replace(templTrace, "{{PARAMS}}", r.Form.Encode(), -1)
|
|
w.Write([]byte(html))
|
|
|
|
}
|
|
|
|
// See https://github.com/catapult-project/catapult/blob/master/tracing/docs/embedding-trace-viewer.md
|
|
// This is almost verbatim copy of:
|
|
// https://github.com/catapult-project/catapult/blob/master/tracing/bin/index.html
|
|
// on revision 5f9e4c3eaa555bdef18218a89f38c768303b7b6e.
|
|
var templTrace = `
|
|
<html>
|
|
<head>
|
|
<link href="/trace_viewer_html" rel="import">
|
|
<style type="text/css">
|
|
html, body {
|
|
box-sizing: border-box;
|
|
overflow: hidden;
|
|
margin: 0px;
|
|
padding: 0;
|
|
width: 100%;
|
|
height: 100%;
|
|
}
|
|
#trace-viewer {
|
|
width: 100%;
|
|
height: 100%;
|
|
}
|
|
#trace-viewer:focus {
|
|
outline: none;
|
|
}
|
|
</style>
|
|
<script>
|
|
'use strict';
|
|
(function() {
|
|
var viewer;
|
|
var url;
|
|
var model;
|
|
|
|
function load() {
|
|
var req = new XMLHttpRequest();
|
|
var is_binary = /[.]gz$/.test(url) || /[.]zip$/.test(url);
|
|
req.overrideMimeType('text/plain; charset=x-user-defined');
|
|
req.open('GET', url, true);
|
|
if (is_binary)
|
|
req.responseType = 'arraybuffer';
|
|
|
|
req.onreadystatechange = function(event) {
|
|
if (req.readyState !== 4)
|
|
return;
|
|
|
|
window.setTimeout(function() {
|
|
if (req.status === 200)
|
|
onResult(is_binary ? req.response : req.responseText);
|
|
else
|
|
onResultFail(req.status);
|
|
}, 0);
|
|
};
|
|
req.send(null);
|
|
}
|
|
|
|
function onResultFail(err) {
|
|
var overlay = new tr.ui.b.Overlay();
|
|
overlay.textContent = err + ': ' + url + ' could not be loaded';
|
|
overlay.title = 'Failed to fetch data';
|
|
overlay.visible = true;
|
|
}
|
|
|
|
function onResult(result) {
|
|
model = new tr.Model();
|
|
var opts = new tr.importer.ImportOptions();
|
|
opts.shiftWorldToZero = false;
|
|
var i = new tr.importer.Import(model, opts);
|
|
var p = i.importTracesWithProgressDialog([result]);
|
|
p.then(onModelLoaded, onImportFail);
|
|
}
|
|
|
|
function onModelLoaded() {
|
|
viewer.model = model;
|
|
viewer.viewTitle = "trace";
|
|
|
|
if (!model || model.bounds.isEmpty)
|
|
return;
|
|
var sel = window.location.hash.substr(1);
|
|
if (sel === '')
|
|
return;
|
|
var parts = sel.split(':');
|
|
var range = new (tr.b.Range || tr.b.math.Range)();
|
|
range.addValue(parseFloat(parts[0]));
|
|
range.addValue(parseFloat(parts[1]));
|
|
viewer.trackView.viewport.interestRange.set(range);
|
|
}
|
|
|
|
function onImportFail(err) {
|
|
var overlay = new tr.ui.b.Overlay();
|
|
overlay.textContent = tr.b.normalizeException(err).message;
|
|
overlay.title = 'Import error';
|
|
overlay.visible = true;
|
|
}
|
|
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
var container = document.createElement('track-view-container');
|
|
container.id = 'track_view_container';
|
|
|
|
viewer = document.createElement('tr-ui-timeline-view');
|
|
viewer.track_view_container = container;
|
|
viewer.appendChild(container);
|
|
|
|
viewer.id = 'trace-viewer';
|
|
viewer.globalMode = true;
|
|
document.body.appendChild(viewer);
|
|
|
|
url = '/jsontrace?{{PARAMS}}';
|
|
load();
|
|
});
|
|
}());
|
|
</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(runtime.GOROOT(), "misc", "trace", "trace_viewer_full.html"))
|
|
}
|
|
|
|
// httpJsonTrace serves json trace, requested from within templTrace HTML.
|
|
func httpJsonTrace(w http.ResponseWriter, r *http.Request) {
|
|
defer debug.FreeOSMemory()
|
|
defer reportMemoryUsage("after httpJsonTrace")
|
|
// This is an AJAX handler, so instead of http.Error we use log.Printf to log errors.
|
|
res, err := parseTrace()
|
|
if err != nil {
|
|
log.Printf("failed to parse trace: %v", err)
|
|
return
|
|
}
|
|
|
|
params := &traceParams{
|
|
parsed: res,
|
|
endTime: math.MaxInt64,
|
|
}
|
|
|
|
if goids := r.FormValue("goid"); goids != "" {
|
|
// If goid argument is present, we are rendering a trace for this particular goroutine.
|
|
goid, err := strconv.ParseUint(goids, 10, 64)
|
|
if err != nil {
|
|
log.Printf("failed to parse goid parameter %q: %v", goids, err)
|
|
return
|
|
}
|
|
analyzeGoroutines(res.Events)
|
|
g, ok := gs[goid]
|
|
if !ok {
|
|
log.Printf("failed to find goroutine %d", goid)
|
|
return
|
|
}
|
|
params.mode = modeGoroutineOriented
|
|
params.startTime = g.StartTime
|
|
if g.EndTime != 0 {
|
|
params.endTime = g.EndTime
|
|
} else { // The goroutine didn't end.
|
|
params.endTime = lastTimestamp()
|
|
}
|
|
params.maing = goid
|
|
params.gs = trace.RelatedGoroutines(res.Events, goid)
|
|
} else if taskids := r.FormValue("taskid"); taskids != "" {
|
|
taskid, err := strconv.ParseUint(taskids, 10, 64)
|
|
if err != nil {
|
|
log.Printf("failed to parse taskid parameter %q: %v", taskids, err)
|
|
return
|
|
}
|
|
annotRes, _ := analyzeAnnotations()
|
|
task, ok := annotRes.tasks[taskid]
|
|
if !ok || len(task.events) == 0 {
|
|
log.Printf("failed to find task with id %d", taskid)
|
|
return
|
|
}
|
|
goid := task.events[0].G
|
|
params.mode = modeGoroutineOriented | modeTaskOriented
|
|
params.startTime = task.firstTimestamp() - 1
|
|
params.endTime = task.lastTimestamp() + 1
|
|
params.maing = goid
|
|
params.tasks = task.decendents()
|
|
gs := map[uint64]bool{}
|
|
for _, t := range params.tasks {
|
|
// find only directly involved goroutines
|
|
for k, v := range t.RelatedGoroutines(res.Events, 0) {
|
|
gs[k] = v
|
|
}
|
|
}
|
|
params.gs = gs
|
|
} else if taskids := r.FormValue("focustask"); taskids != "" {
|
|
taskid, err := strconv.ParseUint(taskids, 10, 64)
|
|
if err != nil {
|
|
log.Printf("failed to parse focustask parameter %q: %v", taskids, err)
|
|
return
|
|
}
|
|
annotRes, _ := analyzeAnnotations()
|
|
task, ok := annotRes.tasks[taskid]
|
|
if !ok || len(task.events) == 0 {
|
|
log.Printf("failed to find task with id %d", taskid)
|
|
return
|
|
}
|
|
params.mode = modeTaskOriented
|
|
params.startTime = task.firstTimestamp() - 1
|
|
params.endTime = task.lastTimestamp() + 1
|
|
params.tasks = task.decendents()
|
|
}
|
|
|
|
start := int64(0)
|
|
end := int64(math.MaxInt64)
|
|
if startStr, endStr := r.FormValue("start"), r.FormValue("end"); startStr != "" && endStr != "" {
|
|
// If start/end arguments are present, we are rendering a range of the trace.
|
|
start, err = strconv.ParseInt(startStr, 10, 64)
|
|
if err != nil {
|
|
log.Printf("failed to parse start parameter %q: %v", startStr, err)
|
|
return
|
|
}
|
|
end, err = strconv.ParseInt(endStr, 10, 64)
|
|
if err != nil {
|
|
log.Printf("failed to parse end parameter %q: %v", endStr, err)
|
|
return
|
|
}
|
|
}
|
|
|
|
c := viewerDataTraceConsumer(w, start, end)
|
|
if err := generateTrace(params, c); err != nil {
|
|
log.Printf("failed to generate trace: %v", err)
|
|
return
|
|
}
|
|
}
|
|
|
|
type Range struct {
|
|
Name string
|
|
Start int
|
|
End int
|
|
}
|
|
|
|
// splitTrace splits the trace into a number of ranges,
|
|
// each resulting in approx 100MB of json output
|
|
// (trace viewer can hardly handle more).
|
|
func splitTrace(res trace.ParseResult) []Range {
|
|
params := &traceParams{
|
|
parsed: res,
|
|
endTime: math.MaxInt64,
|
|
}
|
|
s, c := splittingTraceConsumer(100 << 20) // 100M
|
|
if err := generateTrace(params, c); err != nil {
|
|
dief("%v\n", err)
|
|
}
|
|
return s.Ranges
|
|
}
|
|
|
|
type splitter struct {
|
|
Ranges []Range
|
|
}
|
|
|
|
func splittingTraceConsumer(max int) (*splitter, traceConsumer) {
|
|
type eventSz struct {
|
|
Time float64
|
|
Sz int
|
|
}
|
|
|
|
var (
|
|
data = ViewerData{Frames: make(map[string]ViewerFrame)}
|
|
|
|
sizes []eventSz
|
|
cw countingWriter
|
|
)
|
|
|
|
s := new(splitter)
|
|
|
|
return s, traceConsumer{
|
|
consumeTimeUnit: func(unit string) {
|
|
data.TimeUnit = unit
|
|
},
|
|
consumeViewerEvent: func(v *ViewerEvent, required bool) {
|
|
if required {
|
|
// Store required events inside data
|
|
// so flush can include them in the required
|
|
// part of the trace.
|
|
data.Events = append(data.Events, v)
|
|
return
|
|
}
|
|
enc := json.NewEncoder(&cw)
|
|
enc.Encode(v)
|
|
sizes = append(sizes, eventSz{v.Time, cw.size + 1}) // +1 for ",".
|
|
cw.size = 0
|
|
},
|
|
consumeViewerFrame: func(k string, v ViewerFrame) {
|
|
data.Frames[k] = v
|
|
},
|
|
flush: func() {
|
|
// Calculate size of the mandatory part of the trace.
|
|
// This includes stack traces and thread names.
|
|
cw.size = 0
|
|
enc := json.NewEncoder(&cw)
|
|
enc.Encode(data)
|
|
minSize := cw.size
|
|
|
|
// Then calculate size of each individual event
|
|
// and group them into ranges.
|
|
sum := minSize
|
|
start := 0
|
|
for i, ev := range sizes {
|
|
if sum+ev.Sz > max {
|
|
ranges = append(ranges, Range{
|
|
Name: fmt.Sprintf("%v-%v", time.Duration(sizes[start].Time*1000), time.Duration(ev.Time*1000)),
|
|
Start: start,
|
|
End: i + 1,
|
|
})
|
|
start = i + 1
|
|
sum = minSize
|
|
} else {
|
|
sum += ev.Sz + 1
|
|
}
|
|
}
|
|
if len(ranges) <= 1 {
|
|
s.Ranges = nil
|
|
return
|
|
}
|
|
|
|
if end := len(sizes) - 1; start < end {
|
|
ranges = append(ranges, Range{
|
|
Name: fmt.Sprintf("%v-%v", time.Duration(sizes[start].Time*1000), time.Duration(sizes[end].Time*1000)),
|
|
Start: start,
|
|
End: end,
|
|
})
|
|
}
|
|
s.Ranges = ranges
|
|
},
|
|
}
|
|
}
|
|
|
|
type countingWriter struct {
|
|
size int
|
|
}
|
|
|
|
func (cw *countingWriter) Write(data []byte) (int, error) {
|
|
cw.size += len(data)
|
|
return len(data), nil
|
|
}
|
|
|
|
type traceParams struct {
|
|
parsed trace.ParseResult
|
|
mode traceviewMode
|
|
startTime int64
|
|
endTime int64
|
|
maing uint64 // for goroutine-oriented view, place this goroutine on the top row
|
|
gs map[uint64]bool // Goroutines to be displayed for goroutine-oriented or task-oriented view
|
|
tasks []*taskDesc // Tasks to be displayed. tasks[0] is the top-most task
|
|
}
|
|
|
|
type traceviewMode uint
|
|
|
|
const (
|
|
modeGoroutineOriented traceviewMode = 1 << iota
|
|
modeTaskOriented
|
|
)
|
|
|
|
type traceContext struct {
|
|
*traceParams
|
|
consumer traceConsumer
|
|
frameTree frameNode
|
|
frameSeq int
|
|
arrowSeq uint64
|
|
gcount uint64
|
|
|
|
heapStats, prevHeapStats heapStats
|
|
threadStats, prevThreadStats threadStats
|
|
gstates, prevGstates [gStateCount]int64
|
|
|
|
regionID int // last emitted region id. incremented in each emitRegion call.
|
|
}
|
|
|
|
type heapStats struct {
|
|
heapAlloc uint64
|
|
nextGC uint64
|
|
}
|
|
|
|
type threadStats struct {
|
|
insyscallRuntime uint64 // system goroutine in syscall
|
|
insyscall uint64 // user goroutine in syscall
|
|
prunning uint64 // thread running P
|
|
}
|
|
|
|
type frameNode struct {
|
|
id int
|
|
children map[uint64]frameNode
|
|
}
|
|
|
|
type gState int
|
|
|
|
const (
|
|
gDead gState = iota
|
|
gRunnable
|
|
gRunning
|
|
gWaiting
|
|
gWaitingGC
|
|
|
|
gStateCount
|
|
)
|
|
|
|
type gInfo struct {
|
|
state gState // current state
|
|
name string // name chosen for this goroutine at first EvGoStart
|
|
isSystemG bool
|
|
start *trace.Event // most recent EvGoStart
|
|
markAssist *trace.Event // if non-nil, the mark assist currently running.
|
|
}
|
|
|
|
type ViewerData struct {
|
|
Events []*ViewerEvent `json:"traceEvents"`
|
|
Frames map[string]ViewerFrame `json:"stackFrames"`
|
|
TimeUnit string `json:"displayTimeUnit"`
|
|
|
|
// This is where mandatory part of the trace starts (e.g. thread names)
|
|
footer int
|
|
}
|
|
|
|
type ViewerEvent struct {
|
|
Name string `json:"name,omitempty"`
|
|
Phase string `json:"ph"`
|
|
Scope string `json:"s,omitempty"`
|
|
Time float64 `json:"ts"`
|
|
Dur float64 `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"`
|
|
Cname string `json:"cname,omitempty"`
|
|
Category string `json:"cat,omitempty"`
|
|
}
|
|
|
|
type ViewerFrame struct {
|
|
Name string `json:"name"`
|
|
Parent int `json:"parent,omitempty"`
|
|
}
|
|
|
|
type NameArg struct {
|
|
Name string `json:"name"`
|
|
}
|
|
|
|
type TaskArg struct {
|
|
ID uint64 `json:"id"`
|
|
StartG uint64 `json:"start_g,omitempty"`
|
|
EndG uint64 `json:"end_g,omitempty"`
|
|
}
|
|
|
|
type RegionArg struct {
|
|
TaskID uint64 `json:"taskid,omitempty"`
|
|
}
|
|
|
|
type SortIndexArg struct {
|
|
Index int `json:"sort_index"`
|
|
}
|
|
|
|
type traceConsumer struct {
|
|
consumeTimeUnit func(unit string)
|
|
consumeViewerEvent func(v *ViewerEvent, required bool)
|
|
consumeViewerFrame func(key string, f ViewerFrame)
|
|
flush func()
|
|
}
|
|
|
|
const (
|
|
procsSection = 0 // where Goroutines or per-P timelines are presented.
|
|
statsSection = 1 // where counters are presented.
|
|
tasksSection = 2 // where Task hierarchy & timeline is presented.
|
|
)
|
|
|
|
// 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 mode==goroutineMode, 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, consumer traceConsumer) error {
|
|
defer consumer.flush()
|
|
|
|
ctx := &traceContext{traceParams: params}
|
|
ctx.frameTree.children = make(map[uint64]frameNode)
|
|
ctx.consumer = consumer
|
|
|
|
ctx.consumer.consumeTimeUnit("ns")
|
|
maxProc := 0
|
|
ginfos := make(map[uint64]*gInfo)
|
|
stacks := params.parsed.Stacks
|
|
|
|
getGInfo := func(g uint64) *gInfo {
|
|
info, ok := ginfos[g]
|
|
if !ok {
|
|
info = &gInfo{}
|
|
ginfos[g] = info
|
|
}
|
|
return info
|
|
}
|
|
|
|
// Since we make many calls to setGState, we record a sticky
|
|
// error in setGStateErr and check it after every event.
|
|
var setGStateErr error
|
|
setGState := func(ev *trace.Event, g uint64, oldState, newState gState) {
|
|
info := getGInfo(g)
|
|
if oldState == gWaiting && info.state == gWaitingGC {
|
|
// For checking, gWaiting counts as any gWaiting*.
|
|
oldState = info.state
|
|
}
|
|
if info.state != oldState && setGStateErr == nil {
|
|
setGStateErr = fmt.Errorf("expected G %d to be in state %d, but got state %d", g, oldState, newState)
|
|
}
|
|
ctx.gstates[info.state]--
|
|
ctx.gstates[newState]++
|
|
info.state = newState
|
|
}
|
|
|
|
for _, ev := range ctx.parsed.Events {
|
|
// Handle state transitions before we filter out events.
|
|
switch ev.Type {
|
|
case trace.EvGoStart, trace.EvGoStartLabel:
|
|
setGState(ev, ev.G, gRunnable, gRunning)
|
|
info := getGInfo(ev.G)
|
|
info.start = ev
|
|
case trace.EvProcStart:
|
|
ctx.threadStats.prunning++
|
|
case trace.EvProcStop:
|
|
ctx.threadStats.prunning--
|
|
case trace.EvGoCreate:
|
|
newG := ev.Args[0]
|
|
info := getGInfo(newG)
|
|
if info.name != "" {
|
|
return fmt.Errorf("duplicate go create event for go id=%d detected at offset %d", newG, ev.Off)
|
|
}
|
|
|
|
stk, ok := stacks[ev.Args[1]]
|
|
if !ok || len(stk) == 0 {
|
|
return fmt.Errorf("invalid go create event: missing stack information for go id=%d at offset %d", newG, ev.Off)
|
|
}
|
|
|
|
fname := stk[0].Fn
|
|
info.name = fmt.Sprintf("G%v %s", newG, fname)
|
|
info.isSystemG = isSystemGoroutine(fname)
|
|
|
|
ctx.gcount++
|
|
setGState(ev, newG, gDead, gRunnable)
|
|
case trace.EvGoEnd:
|
|
ctx.gcount--
|
|
setGState(ev, ev.G, gRunning, gDead)
|
|
case trace.EvGoUnblock:
|
|
setGState(ev, ev.Args[0], gWaiting, gRunnable)
|
|
case trace.EvGoSysExit:
|
|
setGState(ev, ev.G, gWaiting, gRunnable)
|
|
if getGInfo(ev.G).isSystemG {
|
|
ctx.threadStats.insyscallRuntime--
|
|
} else {
|
|
ctx.threadStats.insyscall--
|
|
}
|
|
case trace.EvGoSysBlock:
|
|
setGState(ev, ev.G, gRunning, gWaiting)
|
|
if getGInfo(ev.G).isSystemG {
|
|
ctx.threadStats.insyscallRuntime++
|
|
} else {
|
|
ctx.threadStats.insyscall++
|
|
}
|
|
case trace.EvGoSched, trace.EvGoPreempt:
|
|
setGState(ev, ev.G, gRunning, gRunnable)
|
|
case trace.EvGoStop,
|
|
trace.EvGoSleep, trace.EvGoBlock, trace.EvGoBlockSend, trace.EvGoBlockRecv,
|
|
trace.EvGoBlockSelect, trace.EvGoBlockSync, trace.EvGoBlockCond, trace.EvGoBlockNet:
|
|
setGState(ev, ev.G, gRunning, gWaiting)
|
|
case trace.EvGoBlockGC:
|
|
setGState(ev, ev.G, gRunning, gWaitingGC)
|
|
case trace.EvGCMarkAssistStart:
|
|
getGInfo(ev.G).markAssist = ev
|
|
case trace.EvGCMarkAssistDone:
|
|
getGInfo(ev.G).markAssist = nil
|
|
case trace.EvGoWaiting:
|
|
setGState(ev, ev.G, gRunnable, gWaiting)
|
|
case trace.EvGoInSyscall:
|
|
// Cancel out the effect of EvGoCreate at the beginning.
|
|
setGState(ev, ev.G, gRunnable, gWaiting)
|
|
if getGInfo(ev.G).isSystemG {
|
|
ctx.threadStats.insyscallRuntime++
|
|
} else {
|
|
ctx.threadStats.insyscall++
|
|
}
|
|
case trace.EvHeapAlloc:
|
|
ctx.heapStats.heapAlloc = ev.Args[0]
|
|
case trace.EvNextGC:
|
|
ctx.heapStats.nextGC = ev.Args[0]
|
|
}
|
|
if setGStateErr != nil {
|
|
return setGStateErr
|
|
}
|
|
if ctx.gstates[gRunnable] < 0 || ctx.gstates[gRunning] < 0 || ctx.threadStats.insyscall < 0 || ctx.threadStats.insyscallRuntime < 0 {
|
|
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)
|
|
}
|
|
|
|
// 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 !withinTimeRange(ev, ctx.startTime, ctx.endTime) {
|
|
continue
|
|
}
|
|
|
|
if ev.P < trace.FakeP && ev.P > maxProc {
|
|
maxProc = ev.P
|
|
}
|
|
|
|
// Emit trace objects.
|
|
switch ev.Type {
|
|
case trace.EvProcStart:
|
|
if ctx.mode&modeGoroutineOriented != 0 {
|
|
continue
|
|
}
|
|
ctx.emitInstant(ev, "proc start", "")
|
|
case trace.EvProcStop:
|
|
if ctx.mode&modeGoroutineOriented != 0 {
|
|
continue
|
|
}
|
|
ctx.emitInstant(ev, "proc stop", "")
|
|
case trace.EvGCStart:
|
|
ctx.emitSlice(ev, "GC")
|
|
case trace.EvGCDone:
|
|
case trace.EvGCSTWStart:
|
|
if ctx.mode&modeGoroutineOriented != 0 {
|
|
continue
|
|
}
|
|
ctx.emitSlice(ev, fmt.Sprintf("STW (%s)", ev.SArgs[0]))
|
|
case trace.EvGCSTWDone:
|
|
case trace.EvGCMarkAssistStart:
|
|
// Mark assists can continue past preemptions, so truncate to the
|
|
// whichever comes first. We'll synthesize another slice if
|
|
// necessary in EvGoStart.
|
|
markFinish := ev.Link
|
|
goFinish := getGInfo(ev.G).start.Link
|
|
fakeMarkStart := *ev
|
|
text := "MARK ASSIST"
|
|
if markFinish == nil || markFinish.Ts > goFinish.Ts {
|
|
fakeMarkStart.Link = goFinish
|
|
text = "MARK ASSIST (unfinished)"
|
|
}
|
|
ctx.emitSlice(&fakeMarkStart, text)
|
|
case trace.EvGCSweepStart:
|
|
slice := ctx.emitSlice(ev, "SWEEP")
|
|
if done := ev.Link; done != nil && done.Args[0] != 0 {
|
|
slice.Arg = struct {
|
|
Swept uint64 `json:"Swept bytes"`
|
|
Reclaimed uint64 `json:"Reclaimed bytes"`
|
|
}{done.Args[0], done.Args[1]}
|
|
}
|
|
case trace.EvGoStart, trace.EvGoStartLabel:
|
|
info := getGInfo(ev.G)
|
|
if ev.Type == trace.EvGoStartLabel {
|
|
ctx.emitSlice(ev, ev.SArgs[0])
|
|
} else {
|
|
ctx.emitSlice(ev, info.name)
|
|
}
|
|
if info.markAssist != nil {
|
|
// If we're in a mark assist, synthesize a new slice, ending
|
|
// either when the mark assist ends or when we're descheduled.
|
|
markFinish := info.markAssist.Link
|
|
goFinish := ev.Link
|
|
fakeMarkStart := *ev
|
|
text := "MARK ASSIST (resumed, unfinished)"
|
|
if markFinish != nil && markFinish.Ts < goFinish.Ts {
|
|
fakeMarkStart.Link = markFinish
|
|
text = "MARK ASSIST (resumed)"
|
|
}
|
|
ctx.emitSlice(&fakeMarkStart, text)
|
|
}
|
|
case trace.EvGoCreate:
|
|
ctx.emitArrow(ev, "go")
|
|
case trace.EvGoUnblock:
|
|
ctx.emitArrow(ev, "unblock")
|
|
case trace.EvGoSysCall:
|
|
ctx.emitInstant(ev, "syscall", "")
|
|
case trace.EvGoSysExit:
|
|
ctx.emitArrow(ev, "sysexit")
|
|
case trace.EvUserLog:
|
|
ctx.emitInstant(ev, formatUserLog(ev), "user event")
|
|
case trace.EvUserTaskCreate:
|
|
ctx.emitInstant(ev, "task start", "user event")
|
|
case trace.EvUserTaskEnd:
|
|
ctx.emitInstant(ev, "task end", "user event")
|
|
}
|
|
// Emit any counter updates.
|
|
ctx.emitThreadCounters(ev)
|
|
ctx.emitHeapCounters(ev)
|
|
ctx.emitGoroutineCounters(ev)
|
|
}
|
|
|
|
ctx.emitSectionFooter(statsSection, "STATS", 0)
|
|
|
|
if ctx.mode&modeTaskOriented != 0 {
|
|
ctx.emitSectionFooter(tasksSection, "TASKS", 1)
|
|
}
|
|
|
|
if ctx.mode&modeGoroutineOriented != 0 {
|
|
ctx.emitSectionFooter(procsSection, "G", 2)
|
|
} else {
|
|
ctx.emitSectionFooter(procsSection, "PROCS", 2)
|
|
}
|
|
|
|
ctx.emitFooter(&ViewerEvent{Name: "thread_name", Phase: "M", Pid: procsSection, Tid: trace.GCP, Arg: &NameArg{"GC"}})
|
|
ctx.emitFooter(&ViewerEvent{Name: "thread_sort_index", Phase: "M", Pid: procsSection, Tid: trace.GCP, Arg: &SortIndexArg{-6}})
|
|
|
|
ctx.emitFooter(&ViewerEvent{Name: "thread_name", Phase: "M", Pid: procsSection, Tid: trace.NetpollP, Arg: &NameArg{"Network"}})
|
|
ctx.emitFooter(&ViewerEvent{Name: "thread_sort_index", Phase: "M", Pid: procsSection, Tid: trace.NetpollP, Arg: &SortIndexArg{-5}})
|
|
|
|
ctx.emitFooter(&ViewerEvent{Name: "thread_name", Phase: "M", Pid: procsSection, Tid: trace.TimerP, Arg: &NameArg{"Timers"}})
|
|
ctx.emitFooter(&ViewerEvent{Name: "thread_sort_index", Phase: "M", Pid: procsSection, Tid: trace.TimerP, Arg: &SortIndexArg{-4}})
|
|
|
|
ctx.emitFooter(&ViewerEvent{Name: "thread_name", Phase: "M", Pid: procsSection, Tid: trace.SyscallP, Arg: &NameArg{"Syscalls"}})
|
|
ctx.emitFooter(&ViewerEvent{Name: "thread_sort_index", Phase: "M", Pid: procsSection, Tid: trace.SyscallP, Arg: &SortIndexArg{-3}})
|
|
|
|
// Display rows for Ps if we are in the default trace view mode (not goroutine-oriented presentation)
|
|
if ctx.mode&modeGoroutineOriented == 0 {
|
|
for i := 0; i <= maxProc; i++ {
|
|
ctx.emitFooter(&ViewerEvent{Name: "thread_name", Phase: "M", Pid: procsSection, Tid: uint64(i), Arg: &NameArg{fmt.Sprintf("Proc %v", i)}})
|
|
ctx.emitFooter(&ViewerEvent{Name: "thread_sort_index", Phase: "M", Pid: procsSection, Tid: uint64(i), Arg: &SortIndexArg{i}})
|
|
}
|
|
}
|
|
|
|
// Display task and its regions if we are in task-oriented presentation mode.
|
|
if ctx.mode&modeTaskOriented != 0 {
|
|
// sort tasks based on the task start time.
|
|
sortedTask := make([]*taskDesc, 0, len(ctx.tasks))
|
|
for _, task := range ctx.tasks {
|
|
sortedTask = append(sortedTask, task)
|
|
}
|
|
sort.SliceStable(sortedTask, func(i, j int) bool {
|
|
ti, tj := sortedTask[i], sortedTask[j]
|
|
if ti.firstTimestamp() == tj.firstTimestamp() {
|
|
return ti.lastTimestamp() < tj.lastTimestamp()
|
|
}
|
|
return ti.firstTimestamp() < tj.firstTimestamp()
|
|
})
|
|
|
|
for i, task := range sortedTask {
|
|
ctx.emitTask(task, i)
|
|
|
|
// If we are in goroutine-oriented mode, we draw regions.
|
|
// TODO(hyangah): add this for task/P-oriented mode (i.e., focustask view) too.
|
|
if ctx.mode&modeGoroutineOriented != 0 {
|
|
for _, s := range task.regions {
|
|
ctx.emitRegion(s)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Display goroutine rows if we are either in goroutine-oriented mode.
|
|
if ctx.mode&modeGoroutineOriented != 0 {
|
|
for k, v := range ginfos {
|
|
if !ctx.gs[k] {
|
|
continue
|
|
}
|
|
ctx.emitFooter(&ViewerEvent{Name: "thread_name", Phase: "M", Pid: procsSection, Tid: k, Arg: &NameArg{v.name}})
|
|
}
|
|
// Row for the main goroutine (maing)
|
|
ctx.emitFooter(&ViewerEvent{Name: "thread_sort_index", Phase: "M", Pid: procsSection, Tid: ctx.maing, Arg: &SortIndexArg{-2}})
|
|
// Row for GC or global state (specified with G=0)
|
|
ctx.emitFooter(&ViewerEvent{Name: "thread_sort_index", Phase: "M", Pid: procsSection, Tid: 0, Arg: &SortIndexArg{-1}})
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (ctx *traceContext) emit(e *ViewerEvent) {
|
|
ctx.consumer.consumeViewerEvent(e, false)
|
|
}
|
|
|
|
func (ctx *traceContext) emitFooter(e *ViewerEvent) {
|
|
ctx.consumer.consumeViewerEvent(e, true)
|
|
}
|
|
func (ctx *traceContext) emitSectionFooter(sectionID uint64, name string, priority int) {
|
|
ctx.emitFooter(&ViewerEvent{Name: "process_name", Phase: "M", Pid: sectionID, Arg: &NameArg{name}})
|
|
ctx.emitFooter(&ViewerEvent{Name: "process_sort_index", Phase: "M", Pid: sectionID, Arg: &SortIndexArg{priority}})
|
|
}
|
|
|
|
func (ctx *traceContext) time(ev *trace.Event) float64 {
|
|
// Trace viewer wants timestamps in microseconds.
|
|
return float64(ev.Ts) / 1000
|
|
}
|
|
|
|
func withinTimeRange(ev *trace.Event, s, e int64) bool {
|
|
if evEnd := ev.Link; evEnd != nil {
|
|
return ev.Ts <= e && evEnd.Ts >= s
|
|
}
|
|
return ev.Ts >= s && ev.Ts <= e
|
|
}
|
|
|
|
func tsWithinRange(ts, s, e int64) bool {
|
|
return s <= ts && ts <= e
|
|
}
|
|
|
|
func (ctx *traceContext) proc(ev *trace.Event) uint64 {
|
|
if ctx.mode&modeGoroutineOriented != 0 && ev.P < trace.FakeP {
|
|
return ev.G
|
|
} else {
|
|
return uint64(ev.P)
|
|
}
|
|
}
|
|
|
|
func (ctx *traceContext) emitSlice(ev *trace.Event, name string) *ViewerEvent {
|
|
// If ViewerEvent.Dur is not a positive value,
|
|
// trace viewer handles it as a non-terminating time interval.
|
|
// Avoid it by setting the field with a small value.
|
|
durationUsec := ctx.time(ev.Link) - ctx.time(ev)
|
|
if ev.Link.Ts-ev.Ts <= 0 {
|
|
durationUsec = 0.0001 // 0.1 nanoseconds
|
|
}
|
|
sl := &ViewerEvent{
|
|
Name: name,
|
|
Phase: "X",
|
|
Time: ctx.time(ev),
|
|
Dur: durationUsec,
|
|
Tid: ctx.proc(ev),
|
|
Stack: ctx.stack(ev.Stk),
|
|
EndStack: ctx.stack(ev.Link.Stk),
|
|
}
|
|
|
|
// grey out non-overlapping events if the event is not a global event (ev.G == 0)
|
|
if ctx.mode&modeTaskOriented != 0 && ev.G != 0 {
|
|
// include P information.
|
|
if t := ev.Type; t == trace.EvGoStart || t == trace.EvGoStartLabel {
|
|
type Arg struct {
|
|
P int
|
|
}
|
|
sl.Arg = &Arg{P: ev.P}
|
|
}
|
|
// grey out non-overlapping events.
|
|
overlapping := false
|
|
for _, task := range ctx.tasks {
|
|
if _, overlapped := task.overlappingDuration(ev); overlapped {
|
|
overlapping = true
|
|
break
|
|
}
|
|
}
|
|
if !overlapping {
|
|
sl.Cname = colorLightGrey
|
|
}
|
|
}
|
|
ctx.emit(sl)
|
|
return sl
|
|
}
|
|
|
|
func (ctx *traceContext) emitTask(task *taskDesc, sortIndex int) {
|
|
taskRow := uint64(task.id)
|
|
taskName := task.name
|
|
durationUsec := float64(task.lastTimestamp()-task.firstTimestamp()) / 1e3
|
|
|
|
ctx.emitFooter(&ViewerEvent{Name: "thread_name", Phase: "M", Pid: tasksSection, Tid: taskRow, Arg: &NameArg{fmt.Sprintf("T%d %s", task.id, taskName)}})
|
|
ctx.emit(&ViewerEvent{Name: "thread_sort_index", Phase: "M", Pid: tasksSection, Tid: taskRow, Arg: &SortIndexArg{sortIndex}})
|
|
ts := float64(task.firstTimestamp()) / 1e3
|
|
sl := &ViewerEvent{
|
|
Name: taskName,
|
|
Phase: "X",
|
|
Time: ts,
|
|
Dur: durationUsec,
|
|
Pid: tasksSection,
|
|
Tid: taskRow,
|
|
Cname: pickTaskColor(task.id),
|
|
}
|
|
targ := TaskArg{ID: task.id}
|
|
if task.create != nil {
|
|
sl.Stack = ctx.stack(task.create.Stk)
|
|
targ.StartG = task.create.G
|
|
}
|
|
if task.end != nil {
|
|
sl.EndStack = ctx.stack(task.end.Stk)
|
|
targ.EndG = task.end.G
|
|
}
|
|
sl.Arg = targ
|
|
ctx.emit(sl)
|
|
|
|
if task.create != nil && task.create.Type == trace.EvUserTaskCreate && task.create.Args[1] != 0 {
|
|
ctx.arrowSeq++
|
|
ctx.emit(&ViewerEvent{Name: "newTask", Phase: "s", Tid: task.create.Args[1], ID: ctx.arrowSeq, Time: ts, Pid: tasksSection})
|
|
ctx.emit(&ViewerEvent{Name: "newTask", Phase: "t", Tid: taskRow, ID: ctx.arrowSeq, Time: ts, Pid: tasksSection})
|
|
}
|
|
}
|
|
|
|
func (ctx *traceContext) emitRegion(s regionDesc) {
|
|
if s.Name == "" {
|
|
return
|
|
}
|
|
|
|
if !tsWithinRange(s.firstTimestamp(), ctx.startTime, ctx.endTime) &&
|
|
!tsWithinRange(s.lastTimestamp(), ctx.startTime, ctx.endTime) {
|
|
return
|
|
}
|
|
|
|
ctx.regionID++
|
|
regionID := ctx.regionID
|
|
|
|
id := s.TaskID
|
|
scopeID := fmt.Sprintf("%x", id)
|
|
name := s.Name
|
|
|
|
sl0 := &ViewerEvent{
|
|
Category: "Region",
|
|
Name: name,
|
|
Phase: "b",
|
|
Time: float64(s.firstTimestamp()) / 1e3,
|
|
Tid: s.G, // only in goroutine-oriented view
|
|
ID: uint64(regionID),
|
|
Scope: scopeID,
|
|
Cname: pickTaskColor(s.TaskID),
|
|
}
|
|
if s.Start != nil {
|
|
sl0.Stack = ctx.stack(s.Start.Stk)
|
|
}
|
|
ctx.emit(sl0)
|
|
|
|
sl1 := &ViewerEvent{
|
|
Category: "Region",
|
|
Name: name,
|
|
Phase: "e",
|
|
Time: float64(s.lastTimestamp()) / 1e3,
|
|
Tid: s.G,
|
|
ID: uint64(regionID),
|
|
Scope: scopeID,
|
|
Cname: pickTaskColor(s.TaskID),
|
|
Arg: RegionArg{TaskID: s.TaskID},
|
|
}
|
|
if s.End != nil {
|
|
sl1.Stack = ctx.stack(s.End.Stk)
|
|
}
|
|
ctx.emit(sl1)
|
|
}
|
|
|
|
type heapCountersArg struct {
|
|
Allocated uint64
|
|
NextGC uint64
|
|
}
|
|
|
|
func (ctx *traceContext) emitHeapCounters(ev *trace.Event) {
|
|
if ctx.prevHeapStats == ctx.heapStats {
|
|
return
|
|
}
|
|
diff := uint64(0)
|
|
if ctx.heapStats.nextGC > ctx.heapStats.heapAlloc {
|
|
diff = ctx.heapStats.nextGC - ctx.heapStats.heapAlloc
|
|
}
|
|
if tsWithinRange(ev.Ts, ctx.startTime, ctx.endTime) {
|
|
ctx.emit(&ViewerEvent{Name: "Heap", Phase: "C", Time: ctx.time(ev), Pid: 1, Arg: &heapCountersArg{ctx.heapStats.heapAlloc, diff}})
|
|
}
|
|
ctx.prevHeapStats = ctx.heapStats
|
|
}
|
|
|
|
type goroutineCountersArg struct {
|
|
Running uint64
|
|
Runnable uint64
|
|
GCWaiting uint64
|
|
}
|
|
|
|
func (ctx *traceContext) emitGoroutineCounters(ev *trace.Event) {
|
|
if ctx.prevGstates == ctx.gstates {
|
|
return
|
|
}
|
|
if tsWithinRange(ev.Ts, ctx.startTime, ctx.endTime) {
|
|
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])}})
|
|
}
|
|
ctx.prevGstates = ctx.gstates
|
|
}
|
|
|
|
type threadCountersArg struct {
|
|
Running uint64
|
|
InSyscall uint64
|
|
}
|
|
|
|
func (ctx *traceContext) emitThreadCounters(ev *trace.Event) {
|
|
if ctx.prevThreadStats == ctx.threadStats {
|
|
return
|
|
}
|
|
if tsWithinRange(ev.Ts, ctx.startTime, ctx.endTime) {
|
|
ctx.emit(&ViewerEvent{Name: "Threads", Phase: "C", Time: ctx.time(ev), Pid: 1, Arg: &threadCountersArg{
|
|
Running: ctx.threadStats.prunning,
|
|
InSyscall: ctx.threadStats.insyscall}})
|
|
}
|
|
ctx.prevThreadStats = ctx.threadStats
|
|
}
|
|
|
|
func (ctx *traceContext) emitInstant(ev *trace.Event, name, category string) {
|
|
if !tsWithinRange(ev.Ts, ctx.startTime, ctx.endTime) {
|
|
return
|
|
}
|
|
|
|
cname := ""
|
|
if ctx.mode&modeTaskOriented != 0 {
|
|
taskID, isUserAnnotation := isUserAnnotationEvent(ev)
|
|
|
|
show := false
|
|
for _, task := range ctx.tasks {
|
|
if isUserAnnotation && task.id == taskID || task.overlappingInstant(ev) {
|
|
show = true
|
|
break
|
|
}
|
|
}
|
|
// grey out or skip if non-overlapping instant.
|
|
if !show {
|
|
if isUserAnnotation {
|
|
return // don't display unrelated user annotation events.
|
|
}
|
|
cname = colorLightGrey
|
|
}
|
|
}
|
|
var arg interface{}
|
|
if ev.Type == trace.EvProcStart {
|
|
type Arg struct {
|
|
ThreadID uint64
|
|
}
|
|
arg = &Arg{ev.Args[0]}
|
|
}
|
|
ctx.emit(&ViewerEvent{
|
|
Name: name,
|
|
Category: category,
|
|
Phase: "I",
|
|
Scope: "t",
|
|
Time: ctx.time(ev),
|
|
Tid: ctx.proc(ev),
|
|
Stack: ctx.stack(ev.Stk),
|
|
Cname: cname,
|
|
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.mode&modeGoroutineOriented != 0 && (!ctx.gs[ev.Link.G] || ev.Link.Ts < ctx.startTime || ev.Link.Ts > ctx.endTime) {
|
|
return
|
|
}
|
|
|
|
if ev.P == trace.NetpollP || ev.P == trace.TimerP || ev.P == trace.SyscallP {
|
|
// Trace-viewer discards arrows if they don't start/end inside of a slice or instant.
|
|
// So emit a fake instant at the start of the arrow.
|
|
ctx.emitInstant(&trace.Event{P: ev.P, Ts: ev.Ts}, "unblock", "")
|
|
}
|
|
|
|
color := ""
|
|
if ctx.mode&modeTaskOriented != 0 {
|
|
overlapping := false
|
|
// skip non-overlapping arrows.
|
|
for _, task := range ctx.tasks {
|
|
if _, overlapped := task.overlappingDuration(ev); overlapped {
|
|
overlapping = true
|
|
break
|
|
}
|
|
}
|
|
if !overlapping {
|
|
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), Cname: color})
|
|
ctx.emit(&ViewerEvent{Name: name, Phase: "t", Tid: ctx.proc(ev.Link), ID: ctx.arrowSeq, Time: ctx.time(ev.Link), Cname: color})
|
|
}
|
|
|
|
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.consumer.consumeViewerFrame(strconv.Itoa(node.id), ViewerFrame{fmt.Sprintf("%v:%v", frame.Fn, frame.Line), parent.id})
|
|
}
|
|
return ctx.buildBranch(node, stk)
|
|
}
|
|
|
|
func isSystemGoroutine(entryFn string) bool {
|
|
// This mimics runtime.isSystemGoroutine as closely as
|
|
// possible.
|
|
return entryFn != "runtime.main" && strings.HasPrefix(entryFn, "runtime.")
|
|
}
|
|
|
|
// firstTimestamp returns the timestamp of the first event record.
|
|
func firstTimestamp() int64 {
|
|
res, _ := parseTrace()
|
|
if len(res.Events) > 0 {
|
|
return res.Events[0].Ts
|
|
}
|
|
return 0
|
|
}
|
|
|
|
// lastTimestamp returns the timestamp of the last event record.
|
|
func lastTimestamp() int64 {
|
|
res, _ := parseTrace()
|
|
if n := len(res.Events); n > 1 {
|
|
return res.Events[n-1].Ts
|
|
}
|
|
return 0
|
|
}
|
|
|
|
type jsonWriter struct {
|
|
w io.Writer
|
|
enc *json.Encoder
|
|
}
|
|
|
|
func viewerDataTraceConsumer(w io.Writer, start, end int64) traceConsumer {
|
|
frames := make(map[string]ViewerFrame)
|
|
enc := json.NewEncoder(w)
|
|
written := 0
|
|
index := int64(-1)
|
|
|
|
io.WriteString(w, "{")
|
|
return traceConsumer{
|
|
consumeTimeUnit: func(unit string) {
|
|
io.WriteString(w, `"displayTimeUnit":`)
|
|
enc.Encode(unit)
|
|
io.WriteString(w, ",")
|
|
},
|
|
consumeViewerEvent: func(v *ViewerEvent, required bool) {
|
|
index++
|
|
if !required && (index < start || index > end) {
|
|
// not in the range. Skip!
|
|
return
|
|
}
|
|
if written == 0 {
|
|
io.WriteString(w, `"traceEvents": [`)
|
|
}
|
|
if written > 0 {
|
|
io.WriteString(w, ",")
|
|
}
|
|
enc.Encode(v)
|
|
// TODO: get rid of the extra \n inserted by enc.Encode.
|
|
// Same should be applied to splittingTraceConsumer.
|
|
written++
|
|
},
|
|
consumeViewerFrame: func(k string, v ViewerFrame) {
|
|
frames[k] = v
|
|
},
|
|
flush: func() {
|
|
io.WriteString(w, `], "stackFrames":`)
|
|
enc.Encode(frames)
|
|
io.WriteString(w, `}`)
|
|
},
|
|
}
|
|
}
|
|
|
|
// Mapping from more reasonable color names to the reserved color names in
|
|
// https://github.com/catapult-project/catapult/blob/master/tracing/tracing/base/color_scheme.html#L50
|
|
// The chrome trace viewer allows only those as cname values.
|
|
const (
|
|
colorLightMauve string = "thread_state_uninterruptible" // 182, 125, 143
|
|
colorOrange = "thread_state_iowait" // 255, 140, 0
|
|
colorSeafoamGreen = "thread_state_running" // 126, 200, 148
|
|
colorVistaBlue = "thread_state_runnable" // 133, 160, 210
|
|
colorTan = "thread_state_unknown" // 199, 155, 125
|
|
colorIrisBlue = "background_memory_dump" // 0, 180, 180
|
|
colorMidnightBlue = "light_memory_dump" // 0, 0, 180
|
|
colorDeepMagenta = "detailed_memory_dump" // 180, 0, 180
|
|
colorBlue = "vsync_highlight_color" // 0, 0, 255
|
|
colorGrey = "generic_work" // 125, 125, 125
|
|
colorGreen = "good" // 0, 125, 0
|
|
colorDarkGoldenrod = "bad" // 180, 125, 0
|
|
colorPeach = "terrible" // 180, 0, 0
|
|
colorBlack = "black" // 0, 0, 0
|
|
colorLightGrey = "grey" // 221, 221, 221
|
|
colorWhite = "white" // 255, 255, 255
|
|
colorYellow = "yellow" // 255, 255, 0
|
|
colorOlive = "olive" // 100, 100, 0
|
|
colorCornflowerBlue = "rail_response" // 67, 135, 253
|
|
colorSunsetOrange = "rail_animation" // 244, 74, 63
|
|
colorTangerine = "rail_idle" // 238, 142, 0
|
|
colorShamrockGreen = "rail_load" // 13, 168, 97
|
|
colorGreenishYellow = "startup" // 230, 230, 0
|
|
colorDarkGrey = "heap_dump_stack_frame" // 128, 128, 128
|
|
colorTawny = "heap_dump_child_node_arrow" // 204, 102, 0
|
|
colorLemon = "cq_build_running" // 255, 255, 119
|
|
colorLime = "cq_build_passed" // 153, 238, 102
|
|
colorPink = "cq_build_failed" // 238, 136, 136
|
|
colorSilver = "cq_build_abandoned" // 187, 187, 187
|
|
colorManzGreen = "cq_build_attempt_runnig" // 222, 222, 75
|
|
colorKellyGreen = "cq_build_attempt_passed" // 108, 218, 35
|
|
colorAnotherGrey = "cq_build_attempt_failed" // 187, 187, 187
|
|
)
|
|
|
|
var colorForTask = []string{
|
|
colorLightMauve,
|
|
colorOrange,
|
|
colorSeafoamGreen,
|
|
colorVistaBlue,
|
|
colorTan,
|
|
colorMidnightBlue,
|
|
colorIrisBlue,
|
|
colorDeepMagenta,
|
|
colorGreen,
|
|
colorDarkGoldenrod,
|
|
colorPeach,
|
|
colorOlive,
|
|
colorCornflowerBlue,
|
|
colorSunsetOrange,
|
|
colorTangerine,
|
|
colorShamrockGreen,
|
|
colorTawny,
|
|
colorLemon,
|
|
colorLime,
|
|
colorPink,
|
|
colorSilver,
|
|
colorManzGreen,
|
|
colorKellyGreen,
|
|
}
|
|
|
|
func pickTaskColor(id uint64) string {
|
|
idx := id % uint64(len(colorForTask))
|
|
return colorForTask[idx]
|
|
}
|