Help us shape the future of Go! Take the 2026 Go Developer Survey

Source file src/cmd/dist/debugtrace.go

     1  // Copyright 2026 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 main
     6  
     7  import (
     8  	"encoding/json"
     9  	"io"
    10  	"os"
    11  	"strings"
    12  	"sync"
    13  	"time"
    14  )
    15  
    16  var (
    17  	debugTrace       string
    18  	debugTraceAppend bool // append trace events instead of overwriting
    19  	traceMu          sync.Mutex
    20  	debugTraces      []string
    21  	distEvents       []*traceEvent
    22  )
    23  
    24  // This is a subset of the fields on internal/trace/traceviewer/format.go,
    25  // which we can't use here because it's an internal package and cmd/dist
    26  // needs to be buildable using another Go toolchain. That struct is in turn
    27  // used to represent the Chrome Trace Viewer JSON format whose canonical
    28  // documentation is this Google doc:
    29  // https://docs.google.com/document/d/1CvAClvFfyA5R-PhYUmn5OOQtYMH4h6I0nSsKchNAySU/preview
    30  type traceEvent struct {
    31  	Name  string  `json:"name,omitempty"`
    32  	Phase string  `json:"ph"`
    33  	Time  float64 `json:"ts"`
    34  	PID   uint64  `json:"pid"`
    35  	TID   uint64  `json:"tid"`
    36  }
    37  
    38  func maybeTraceFlag(name string) []string {
    39  	if debugTrace == "" {
    40  		return nil
    41  	}
    42  	f := pathf("%s/%s.trace", workdir, name)
    43  	debugTraces = append(debugTraces, f)
    44  	return []string{"-debug-trace=" + f}
    45  }
    46  
    47  func timeMicros(t time.Time) float64 {
    48  	return float64(t.UnixNano()) / float64(time.Microsecond)
    49  }
    50  
    51  type span struct {
    52  	name string
    53  	tid  uint64
    54  }
    55  
    56  func startSpan(name string) span {
    57  	return startSpanTID(name, 0)
    58  }
    59  
    60  func startSpanTID(name string, tid uint64) span {
    61  	if debugTrace == "" {
    62  		return span{}
    63  	}
    64  	traceMu.Lock()
    65  	distEvents = append(distEvents, &traceEvent{Name: name, Phase: "B", Time: timeMicros(time.Now()), TID: tid})
    66  	traceMu.Unlock()
    67  	return span{name: name, tid: tid}
    68  }
    69  
    70  func (s span) done() {
    71  	if s.name == "" {
    72  		return
    73  	}
    74  	traceMu.Lock()
    75  	distEvents = append(distEvents, &traceEvent{Name: s.name, Phase: "E", Time: timeMicros(time.Now()), TID: s.tid})
    76  	traceMu.Unlock()
    77  }
    78  
    79  // writeTrace merges the -debug-trace outputs from the go command invocations and adds
    80  // dist's own trace events in too. Because the traces have absolute timpstamps, we can
    81  // essentially just concatenate them.
    82  func writeTrace() {
    83  	var f *os.File
    84  	var err error
    85  	if debugTraceAppend {
    86  		f, err = os.OpenFile(debugTrace, os.O_RDWR|os.O_CREATE, 0o777)
    87  		if err != nil {
    88  			fatalf("open trace file for read/write: %v", err)
    89  		}
    90  		ok := func() bool {
    91  			// Try to seek before close brace so we can append.
    92  			fi, err := f.Stat()
    93  			if err != nil {
    94  				return false
    95  			}
    96  			size := fi.Size()
    97  			var lastTwo [2]byte
    98  			if size < 2 {
    99  				return false
   100  			}
   101  			if _, err := f.ReadAt(lastTwo[:], size-2); err != nil {
   102  				return false
   103  			}
   104  			if lastTwo != [2]byte{']', '\n'} {
   105  				return false
   106  			}
   107  			if _, err = f.Seek(-2, io.SeekEnd); err != nil {
   108  				return false
   109  			}
   110  			if _, err = f.WriteString(",\n"); err != nil {
   111  				return false
   112  			}
   113  			return true
   114  		}()
   115  		if !ok {
   116  			if err = f.Truncate(0); err != nil {
   117  				fatalf("failed to truncate trace file for writing: %v", err)
   118  			}
   119  			f.Seek(0, io.SeekStart)
   120  			f.WriteString("[\n")
   121  		}
   122  	} else {
   123  		// Not appending to already existing file. Create a new file.
   124  		f, err = os.Create(debugTrace)
   125  		if err != nil {
   126  			fatalf("opening trace file for write: %v", err)
   127  		}
   128  		f.WriteString("[\n")
   129  	}
   130  
   131  	enc := json.NewEncoder(f)
   132  	for _, ev := range distEvents {
   133  		if err := enc.Encode(ev); err != nil {
   134  			fatalf("marshaling trace event: %v", err)
   135  		}
   136  		f.WriteString(",")
   137  	}
   138  
   139  	for i, name := range debugTraces {
   140  		data, err := os.ReadFile(name)
   141  		if err != nil {
   142  			fatalf("reading trace file: %v", err)
   143  		}
   144  		// The trace is a json list: trim the start and end brackets.
   145  		events := strings.TrimSpace(string(data))
   146  		events = strings.TrimSuffix(strings.TrimPrefix(events, "["), "]")
   147  		f.WriteString(events)
   148  		if i != len(debugTraces)-1 {
   149  			f.WriteString(",")
   150  		}
   151  	}
   152  
   153  	f.WriteString("]\n")
   154  	if err := f.Close(); err != nil {
   155  		fatalf("closing trace file: %v", err)
   156  	}
   157  }
   158  

View as plain text