github.com/go-asm/go@v1.21.1-0.20240213172139-40c5ead50c48/cmd/trace/v2/main.go (about)

     1  // Copyright 2023 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package trace
     6  
     7  import (
     8  	"fmt"
     9  	"io"
    10  	"log"
    11  	"net"
    12  	"net/http"
    13  	"os"
    14  
    15  	"github.com/go-asm/go/trace"
    16  	"github.com/go-asm/go/trace/traceviewer"
    17  	tracev2 "github.com/go-asm/go/trace/v2"
    18  
    19  	"github.com/go-asm/go/trace/v2/raw"
    20  
    21  	"github.com/go-asm/go/cmd/browser"
    22  )
    23  
    24  // Main is the main function for cmd/trace v2.
    25  func Main(traceFile, httpAddr, pprof string, debug int) error {
    26  	tracef, err := os.Open(traceFile)
    27  	if err != nil {
    28  		return fmt.Errorf("failed to read trace file: %w", err)
    29  	}
    30  	defer tracef.Close()
    31  
    32  	// Debug flags.
    33  	switch debug {
    34  	case 1:
    35  		return debugProcessedEvents(tracef)
    36  	case 2:
    37  		return debugRawEvents(tracef)
    38  	}
    39  
    40  	ln, err := net.Listen("tcp", httpAddr)
    41  	if err != nil {
    42  		return fmt.Errorf("failed to create server socket: %w", err)
    43  	}
    44  	addr := "http://" + ln.Addr().String()
    45  
    46  	log.Print("Preparing trace for viewer...")
    47  	parsed, err := parseTrace(tracef)
    48  	if err != nil {
    49  		return err
    50  	}
    51  	// N.B. tracef not needed after this point.
    52  	// We might double-close, but that's fine; we ignore the error.
    53  	tracef.Close()
    54  
    55  	log.Print("Splitting trace for viewer...")
    56  	ranges, err := splitTrace(parsed)
    57  	if err != nil {
    58  		return err
    59  	}
    60  
    61  	log.Printf("Opening browser. Trace viewer is listening on %s", addr)
    62  	browser.Open(addr)
    63  
    64  	mutatorUtil := func(flags trace.UtilFlags) ([][]trace.MutatorUtil, error) {
    65  		return trace.MutatorUtilizationV2(parsed.events, flags), nil
    66  	}
    67  
    68  	mux := http.NewServeMux()
    69  
    70  	// Main endpoint.
    71  	mux.Handle("/", traceviewer.MainHandler([]traceviewer.View{
    72  		{Type: traceviewer.ViewProc, Ranges: ranges},
    73  		// N.B. Use the same ranges for threads. It takes a long time to compute
    74  		// the split a second time, but the makeup of the events are similar enough
    75  		// that this is still a good split.
    76  		{Type: traceviewer.ViewThread, Ranges: ranges},
    77  	}))
    78  
    79  	// Catapult handlers.
    80  	mux.Handle("/trace", traceviewer.TraceHandler())
    81  	mux.Handle("/jsontrace", JSONTraceHandler(parsed))
    82  	mux.Handle("/static/", traceviewer.StaticHandler())
    83  
    84  	// Goroutines handlers.
    85  	mux.HandleFunc("/goroutines", GoroutinesHandlerFunc(parsed.summary.Goroutines))
    86  	mux.HandleFunc("/goroutine", GoroutineHandler(parsed.summary.Goroutines))
    87  
    88  	// MMU handler.
    89  	mux.HandleFunc("/mmu", traceviewer.MMUHandlerFunc(ranges, mutatorUtil))
    90  
    91  	// Basic pprof endpoints.
    92  	mux.HandleFunc("/io", traceviewer.SVGProfileHandlerFunc(pprofByGoroutine(computePprofIO(), parsed)))
    93  	mux.HandleFunc("/block", traceviewer.SVGProfileHandlerFunc(pprofByGoroutine(computePprofBlock(), parsed)))
    94  	mux.HandleFunc("/syscall", traceviewer.SVGProfileHandlerFunc(pprofByGoroutine(computePprofSyscall(), parsed)))
    95  	mux.HandleFunc("/sched", traceviewer.SVGProfileHandlerFunc(pprofByGoroutine(computePprofSched(), parsed)))
    96  
    97  	// Region-based pprof endpoints.
    98  	mux.HandleFunc("/regionio", traceviewer.SVGProfileHandlerFunc(pprofByRegion(computePprofIO(), parsed)))
    99  	mux.HandleFunc("/regionblock", traceviewer.SVGProfileHandlerFunc(pprofByRegion(computePprofBlock(), parsed)))
   100  	mux.HandleFunc("/regionsyscall", traceviewer.SVGProfileHandlerFunc(pprofByRegion(computePprofSyscall(), parsed)))
   101  	mux.HandleFunc("/regionsched", traceviewer.SVGProfileHandlerFunc(pprofByRegion(computePprofSched(), parsed)))
   102  
   103  	// Region endpoints.
   104  	mux.HandleFunc("/userregions", UserRegionsHandlerFunc(parsed))
   105  	mux.HandleFunc("/userregion", UserRegionHandlerFunc(parsed))
   106  
   107  	// Task endpoints.
   108  	mux.HandleFunc("/usertasks", UserTasksHandlerFunc(parsed))
   109  	mux.HandleFunc("/usertask", UserTaskHandlerFunc(parsed))
   110  
   111  	err = http.Serve(ln, mux)
   112  	return fmt.Errorf("failed to start http server: %w", err)
   113  }
   114  
   115  type parsedTrace struct {
   116  	events  []tracev2.Event
   117  	summary *trace.Summary
   118  }
   119  
   120  func parseTrace(tr io.Reader) (*parsedTrace, error) {
   121  	r, err := tracev2.NewReader(tr)
   122  	if err != nil {
   123  		return nil, fmt.Errorf("failed to create trace reader: %w", err)
   124  	}
   125  	s := trace.NewSummarizer()
   126  	t := new(parsedTrace)
   127  	for {
   128  		ev, err := r.ReadEvent()
   129  		if err == io.EOF {
   130  			break
   131  		} else if err != nil {
   132  			return nil, fmt.Errorf("failed to read event: %w", err)
   133  		}
   134  		t.events = append(t.events, ev)
   135  		s.Event(&t.events[len(t.events)-1])
   136  	}
   137  	t.summary = s.Finalize()
   138  	return t, nil
   139  }
   140  
   141  func (t *parsedTrace) startTime() tracev2.Time {
   142  	return t.events[0].Time()
   143  }
   144  
   145  func (t *parsedTrace) endTime() tracev2.Time {
   146  	return t.events[len(t.events)-1].Time()
   147  }
   148  
   149  // splitTrace splits the trace into a number of ranges, each resulting in approx 100 MiB of
   150  // json output (the trace viewer can hardly handle more).
   151  func splitTrace(parsed *parsedTrace) ([]traceviewer.Range, error) {
   152  	// TODO(mknyszek): Split traces by generation by doing a quick first pass over the
   153  	// trace to identify all the generation boundaries.
   154  	s, c := traceviewer.SplittingTraceConsumer(100 << 20) // 100 MiB
   155  	if err := generateTrace(parsed, defaultGenOpts(), c); err != nil {
   156  		return nil, err
   157  	}
   158  	return s.Ranges, nil
   159  }
   160  
   161  func debugProcessedEvents(trace io.Reader) error {
   162  	tr, err := tracev2.NewReader(trace)
   163  	if err != nil {
   164  		return err
   165  	}
   166  	for {
   167  		ev, err := tr.ReadEvent()
   168  		if err == io.EOF {
   169  			return nil
   170  		} else if err != nil {
   171  			return err
   172  		}
   173  		fmt.Println(ev.String())
   174  	}
   175  }
   176  
   177  func debugRawEvents(trace io.Reader) error {
   178  	rr, err := raw.NewReader(trace)
   179  	if err != nil {
   180  		return err
   181  	}
   182  	for {
   183  		ev, err := rr.ReadEvent()
   184  		if err == io.EOF {
   185  			return nil
   186  		} else if err != nil {
   187  			return err
   188  		}
   189  		fmt.Println(ev.String())
   190  	}
   191  }