| // 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 |
| } |