// 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 ( "bufio" "cmd/internal/browser" cmdv2 "cmd/trace/v2" "flag" "fmt" "internal/trace" "internal/trace/traceviewer" "log" "net" "net/http" "os" "runtime" "runtime/debug" "sync" _ "net/http/pprof" // Required to use pprof ) const usageMessage = "" + `Usage of 'go tool trace': Given a trace file produced by 'go test': go test -trace=trace.out pkg Open a web browser displaying trace: go tool trace [flags] [pkg.test] trace.out Generate a pprof-like profile from the trace: go tool trace -pprof=TYPE [pkg.test] trace.out [pkg.test] argument is required for traces produced by Go 1.6 and below. Go 1.7 does not require the binary argument. Supported profile types are: - net: network blocking profile - sync: synchronization blocking profile - syscall: syscall blocking profile - sched: scheduler latency profile Flags: -http=addr: HTTP service address (e.g., ':6060') -pprof=type: print a pprof-like profile instead -d=int: print debug info such as parsed events (1 for high-level, 2 for low-level) Note that while the various profiles available when launching 'go tool trace' work on every browser, the trace viewer itself (the 'view trace' page) comes from the Chrome/Chromium project and is only actively tested on that browser. ` var ( httpFlag = flag.String("http", "localhost:0", "HTTP service address (e.g., ':6060')") pprofFlag = flag.String("pprof", "", "print a pprof-like profile instead") debugFlag = flag.Int("d", 0, "print debug information (1 for basic debug info, 2 for lower-level info)") // The binary file name, left here for serveSVGProfile. programBinary string traceFile string ) func main() { flag.Usage = func() { fmt.Fprint(os.Stderr, usageMessage) os.Exit(2) } flag.Parse() // Go 1.7 traces embed symbol info and does not require the binary. // But we optionally accept binary as first arg for Go 1.5 traces. switch flag.NArg() { case 1: traceFile = flag.Arg(0) case 2: programBinary = flag.Arg(0) traceFile = flag.Arg(1) default: flag.Usage() } if isTraceV2(traceFile) { if err := cmdv2.Main(traceFile, *httpFlag, *pprofFlag, *debugFlag); err != nil { dief("%s\n", err) } return } var pprofFunc traceviewer.ProfileFunc switch *pprofFlag { case "net": pprofFunc = pprofByGoroutine(computePprofIO) case "sync": pprofFunc = pprofByGoroutine(computePprofBlock) case "syscall": pprofFunc = pprofByGoroutine(computePprofSyscall) case "sched": pprofFunc = pprofByGoroutine(computePprofSched) } if pprofFunc != nil { records, err := pprofFunc(&http.Request{}) if err != nil { dief("failed to generate pprof: %v\n", err) } if err := traceviewer.BuildProfile(records).Write(os.Stdout); err != nil { dief("failed to generate pprof: %v\n", err) } os.Exit(0) } if *pprofFlag != "" { dief("unknown pprof type %s\n", *pprofFlag) } ln, err := net.Listen("tcp", *httpFlag) if err != nil { dief("failed to create server socket: %v\n", err) } log.Print("Parsing trace...") res, err := parseTrace() if err != nil { dief("%v\n", err) } if *debugFlag != 0 { trace.Print(res.Events) os.Exit(0) } reportMemoryUsage("after parsing trace") debug.FreeOSMemory() log.Print("Splitting trace...") ranges = splitTrace(res) reportMemoryUsage("after splitting trace") debug.FreeOSMemory() addr := "http://" + ln.Addr().String() log.Printf("Opening browser. Trace viewer is listening on %s", addr) browser.Open(addr) // Install MMU handler. http.HandleFunc("/mmu", traceviewer.MMUHandlerFunc(ranges, mutatorUtil)) // Install main handler. http.Handle("/", traceviewer.MainHandler([]traceviewer.View{ {Type: traceviewer.ViewProc, Ranges: ranges}, })) // Start http server. err = http.Serve(ln, nil) dief("failed to start http server: %v\n", err) } // isTraceV2 returns true if filename holds a v2 trace. func isTraceV2(filename string) bool { file, err := os.Open(filename) if err != nil { return false } defer file.Close() ver, _, err := trace.ReadVersion(file) if err != nil { return false } return ver >= 1022 } var ranges []traceviewer.Range var loader struct { once sync.Once res trace.ParseResult err error } // parseEvents is a compatibility wrapper that returns only // the Events part of trace.ParseResult returned by parseTrace. func parseEvents() ([]*trace.Event, error) { res, err := parseTrace() if err != nil { return nil, err } return res.Events, err } func parseTrace() (trace.ParseResult, error) { loader.once.Do(func() { tracef, err := os.Open(traceFile) if err != nil { loader.err = fmt.Errorf("failed to open trace file: %v", err) return } defer tracef.Close() // Parse and symbolize. res, err := trace.Parse(bufio.NewReader(tracef), programBinary) if err != nil { loader.err = fmt.Errorf("failed to parse trace: %v", err) return } loader.res = res }) return loader.res, loader.err } func dief(msg string, args ...any) { fmt.Fprintf(os.Stderr, msg, args...) os.Exit(1) } var debugMemoryUsage bool func init() { v := os.Getenv("DEBUG_MEMORY_USAGE") debugMemoryUsage = v != "" } func reportMemoryUsage(msg string) { if !debugMemoryUsage { return } var s runtime.MemStats runtime.ReadMemStats(&s) w := os.Stderr fmt.Fprintf(w, "%s\n", msg) fmt.Fprintf(w, " Alloc:\t%d Bytes\n", s.Alloc) fmt.Fprintf(w, " Sys:\t%d Bytes\n", s.Sys) fmt.Fprintf(w, " HeapReleased:\t%d Bytes\n", s.HeapReleased) fmt.Fprintf(w, " HeapSys:\t%d Bytes\n", s.HeapSys) fmt.Fprintf(w, " HeapInUse:\t%d Bytes\n", s.HeapInuse) fmt.Fprintf(w, " HeapAlloc:\t%d Bytes\n", s.HeapAlloc) var dummy string fmt.Printf("Enter to continue...") fmt.Scanf("%s", &dummy) } func mutatorUtil(flags trace.UtilFlags) ([][]trace.MutatorUtil, error) { events, err := parseEvents() if err != nil { return nil, err } return trace.MutatorUtilization(events, flags), nil }