From: Dmitry Vyukov Date: Tue, 3 May 2016 14:44:25 +0000 (+0200) Subject: cmd/trace: split large traces into parts X-Git-Tag: go1.7beta1~231 X-Git-Url: http://www.git.cypherpunks.su/?a=commitdiff_plain;h=7ae273923cdd5d00b72c293b57ade8a1e290a4a3;p=gostls13.git cmd/trace: split large traces into parts Trace viewer cannot handle traces larger than 256MB (limit on js string size): https://github.com/catapult-project/catapult/issues/627 And even that is problematic (chrome hangs and crashes). Split large traces into 100MB parts. Somewhat clumsy, but I don't see any other solution (other than rewriting trace viewer). At least it works reliably now. Fixes #15482 Change-Id: I993b5f43d22072c6f5bd041ab5888ce176f272b2 Reviewed-on: https://go-review.googlesource.com/22731 Reviewed-by: Hyang-Ah Hana Kim --- diff --git a/src/cmd/trace/main.go b/src/cmd/trace/main.go index 2735bf13ea..893719edbf 100644 --- a/src/cmd/trace/main.go +++ b/src/cmd/trace/main.go @@ -22,7 +22,9 @@ import ( "bufio" "flag" "fmt" + "html/template" "internal/trace" + "log" "net" "net/http" "os" @@ -76,20 +78,36 @@ func main() { if err != nil { dief("failed to create server socket: %v\n", err) } - // Open browser. + + log.Printf("Parsing trace...") + events, err := parseEvents() + if err != nil { + dief("%v\n", err) + } + + log.Printf("Serializing trace...") + params := &traceParams{ + events: events, + endTime: int64(1<<63 - 1), + } + data := generateTrace(params) + + log.Printf("Splitting trace...") + ranges = splitTrace(data) + + log.Printf("Opening browser") if !startBrowser("http://" + ln.Addr().String()) { fmt.Fprintf(os.Stderr, "Trace viewer is listening on http://%s\n", ln.Addr().String()) } - // Parse and symbolize trace asynchronously while browser opens. - go parseEvents() - // Start http server. http.HandleFunc("/", httpMain) err = http.Serve(ln, nil) dief("failed to start http server: %v\n", err) } +var ranges []Range + var loader struct { once sync.Once events []*trace.Event @@ -118,13 +136,23 @@ func parseEvents() ([]*trace.Event, error) { // httpMain serves the starting page. func httpMain(w http.ResponseWriter, r *http.Request) { - w.Write(templMain) + if err := templMain.Execute(w, ranges); err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } } -var templMain = []byte(` +var templMain = template.Must(template.New("").Parse(` -View trace
+{{if $}} + {{range $e := $}} + View trace ({{$e.Name}})
+ {{end}} +
+{{else}} + View trace
+{{end}} Goroutine analysis
Network blocking profile
Synchronization blocking profile
@@ -132,7 +160,7 @@ var templMain = []byte(` Scheduler latency profile
-`) +`)) // startBrowser tries to open the URL in a browser // and reports whether it succeeds. diff --git a/src/cmd/trace/trace.go b/src/cmd/trace/trace.go index 7782a5efc8..2b6a37bfd8 100644 --- a/src/cmd/trace/trace.go +++ b/src/cmd/trace/trace.go @@ -14,6 +14,7 @@ import ( "runtime" "strconv" "strings" + "time" ) func init() { @@ -29,17 +30,11 @@ func httpTrace(w http.ResponseWriter, r *http.Request) { http.Error(w, err.Error(), http.StatusInternalServerError) return } - - params := "" - if goids := r.FormValue("goid"); goids != "" { - goid, err := strconv.ParseUint(goids, 10, 64) - if err != nil { - http.Error(w, fmt.Sprintf("failed to parse goid parameter '%v': %v", goids, err), http.StatusInternalServerError) - return - } - params = fmt.Sprintf("?goid=%v", goid) + if err := r.ParseForm(); err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return } - html := strings.Replace(templTrace, "{{PARAMS}}", params, -1) + html := strings.Replace(templTrace, "{{PARAMS}}", r.Form.Encode(), -1) w.Write([]byte(html)) } @@ -118,7 +113,7 @@ var templTrace = ` viewer.globalMode = true; document.body.appendChild(viewer); - url = '/jsontrace{{PARAMS}}'; + url = '/jsontrace?{{PARAMS}}'; load(); }); }()); @@ -150,6 +145,7 @@ func httpJsonTrace(w http.ResponseWriter, r *http.Request) { } 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 '%v': %v", goids, err) @@ -164,13 +160,81 @@ func httpJsonTrace(w http.ResponseWriter, r *http.Request) { params.gs = trace.RelatedGoroutines(events, goid) } - err = json.NewEncoder(w).Encode(generateTrace(params)) + data := generateTrace(params) + + 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.ParseUint(startStr, 10, 64) + if err != nil { + log.Printf("failed to parse start parameter '%v': %v", startStr, err) + return + } + end, err := strconv.ParseUint(endStr, 10, 64) + if err != nil { + log.Printf("failed to parse end parameter '%v': %v", endStr, err) + return + } + if start >= uint64(len(data.Events)) || end <= start || end > uint64(len(data.Events)) { + log.Printf("bogus start/end parameters: %v/%v, trace size %v", start, end, len(data.Events)) + return + } + data.Events = append(data.Events[start:end], data.Events[data.footer:]...) + } + err = json.NewEncoder(w).Encode(data) if err != nil { log.Printf("failed to serialize 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(data ViewerData) []Range { + const rangeSize = 100 << 20 + var ranges []Range + cw := new(countingWriter) + enc := json.NewEncoder(cw) + // First calculate size of the mandatory part of the trace. + // This includes stack traces and thread names. + data1 := data + data1.Events = data.Events[data.footer:] + enc.Encode(data1) + auxSize := cw.size + cw.size = 0 + // Then calculate size of each individual event and group them into ranges. + for i, start := 0, 0; i < data.footer; i++ { + enc.Encode(data.Events[i]) + if cw.size+auxSize > rangeSize || i == data.footer-1 { + ranges = append(ranges, Range{ + Name: fmt.Sprintf("%v-%v", time.Duration(data.Events[start].Time*1000), time.Duration(data.Events[i].Time*1000)), + Start: start, + End: i + 1, + }) + start = i + 1 + cw.size = 0 + } + } + if len(ranges) == 1 { + ranges = nil + } + return 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 { events []*trace.Event gtrace bool @@ -204,6 +268,9 @@ 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 { @@ -355,6 +422,7 @@ func generateTrace(params *traceParams) ViewerData { } } + ctx.data.footer = len(ctx.data.Events) ctx.emit(&ViewerEvent{Name: "process_name", Phase: "M", Pid: 0, Arg: &NameArg{"PROCS"}}) ctx.emit(&ViewerEvent{Name: "process_sort_index", Phase: "M", Pid: 0, Arg: &SortIndexArg{1}})