blob: 5f0d6f612b692e666ccdcc1e1dadb42404340551 [file] [log] [blame]
// 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
}