mirror of
https://github.com/golang/go.git
synced 2025-12-08 06:10:04 +00:00
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 <hyangah@gmail.com>
This commit is contained in:
parent
ccf2c01992
commit
7ae273923c
2 changed files with 116 additions and 20 deletions
|
|
@ -22,7 +22,9 @@ import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"html/template"
|
||||||
"internal/trace"
|
"internal/trace"
|
||||||
|
"log"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
|
|
@ -76,20 +78,36 @@ func main() {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
dief("failed to create server socket: %v\n", err)
|
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()) {
|
if !startBrowser("http://" + ln.Addr().String()) {
|
||||||
fmt.Fprintf(os.Stderr, "Trace viewer is listening on http://%s\n", 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.
|
// Start http server.
|
||||||
http.HandleFunc("/", httpMain)
|
http.HandleFunc("/", httpMain)
|
||||||
err = http.Serve(ln, nil)
|
err = http.Serve(ln, nil)
|
||||||
dief("failed to start http server: %v\n", err)
|
dief("failed to start http server: %v\n", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var ranges []Range
|
||||||
|
|
||||||
var loader struct {
|
var loader struct {
|
||||||
once sync.Once
|
once sync.Once
|
||||||
events []*trace.Event
|
events []*trace.Event
|
||||||
|
|
@ -118,13 +136,23 @@ func parseEvents() ([]*trace.Event, error) {
|
||||||
|
|
||||||
// httpMain serves the starting page.
|
// httpMain serves the starting page.
|
||||||
func httpMain(w http.ResponseWriter, r *http.Request) {
|
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(`
|
||||||
<html>
|
<html>
|
||||||
<body>
|
<body>
|
||||||
|
{{if $}}
|
||||||
|
{{range $e := $}}
|
||||||
|
<a href="/trace?start={{$e.Start}}&end={{$e.End}}">View trace ({{$e.Name}})</a><br>
|
||||||
|
{{end}}
|
||||||
|
<br>
|
||||||
|
{{else}}
|
||||||
<a href="/trace">View trace</a><br>
|
<a href="/trace">View trace</a><br>
|
||||||
|
{{end}}
|
||||||
<a href="/goroutines">Goroutine analysis</a><br>
|
<a href="/goroutines">Goroutine analysis</a><br>
|
||||||
<a href="/io">Network blocking profile</a><br>
|
<a href="/io">Network blocking profile</a><br>
|
||||||
<a href="/block">Synchronization blocking profile</a><br>
|
<a href="/block">Synchronization blocking profile</a><br>
|
||||||
|
|
@ -132,7 +160,7 @@ var templMain = []byte(`
|
||||||
<a href="/sched">Scheduler latency profile</a><br>
|
<a href="/sched">Scheduler latency profile</a><br>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
`)
|
`))
|
||||||
|
|
||||||
// startBrowser tries to open the URL in a browser
|
// startBrowser tries to open the URL in a browser
|
||||||
// and reports whether it succeeds.
|
// and reports whether it succeeds.
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ import (
|
||||||
"runtime"
|
"runtime"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
|
@ -29,17 +30,11 @@ func httpTrace(w http.ResponseWriter, r *http.Request) {
|
||||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if err := r.ParseForm(); err != nil {
|
||||||
params := ""
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
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
|
return
|
||||||
}
|
}
|
||||||
params = fmt.Sprintf("?goid=%v", goid)
|
html := strings.Replace(templTrace, "{{PARAMS}}", r.Form.Encode(), -1)
|
||||||
}
|
|
||||||
html := strings.Replace(templTrace, "{{PARAMS}}", params, -1)
|
|
||||||
w.Write([]byte(html))
|
w.Write([]byte(html))
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -118,7 +113,7 @@ var templTrace = `
|
||||||
viewer.globalMode = true;
|
viewer.globalMode = true;
|
||||||
document.body.appendChild(viewer);
|
document.body.appendChild(viewer);
|
||||||
|
|
||||||
url = '/jsontrace{{PARAMS}}';
|
url = '/jsontrace?{{PARAMS}}';
|
||||||
load();
|
load();
|
||||||
});
|
});
|
||||||
}());
|
}());
|
||||||
|
|
@ -150,6 +145,7 @@ func httpJsonTrace(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if goids := r.FormValue("goid"); goids != "" {
|
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)
|
goid, err := strconv.ParseUint(goids, 10, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("failed to parse goid parameter '%v': %v", goids, err)
|
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)
|
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 {
|
if err != nil {
|
||||||
log.Printf("failed to serialize trace: %v", err)
|
log.Printf("failed to serialize trace: %v", err)
|
||||||
return
|
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 {
|
type traceParams struct {
|
||||||
events []*trace.Event
|
events []*trace.Event
|
||||||
gtrace bool
|
gtrace bool
|
||||||
|
|
@ -204,6 +268,9 @@ type ViewerData struct {
|
||||||
Events []*ViewerEvent `json:"traceEvents"`
|
Events []*ViewerEvent `json:"traceEvents"`
|
||||||
Frames map[string]ViewerFrame `json:"stackFrames"`
|
Frames map[string]ViewerFrame `json:"stackFrames"`
|
||||||
TimeUnit string `json:"displayTimeUnit"`
|
TimeUnit string `json:"displayTimeUnit"`
|
||||||
|
|
||||||
|
// This is where mandatory part of the trace starts (e.g. thread names)
|
||||||
|
footer int
|
||||||
}
|
}
|
||||||
|
|
||||||
type ViewerEvent struct {
|
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_name", Phase: "M", Pid: 0, Arg: &NameArg{"PROCS"}})
|
||||||
ctx.emit(&ViewerEvent{Name: "process_sort_index", Phase: "M", Pid: 0, Arg: &SortIndexArg{1}})
|
ctx.emit(&ViewerEvent{Name: "process_sort_index", Phase: "M", Pid: 0, Arg: &SortIndexArg{1}})
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue