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

     1  // Copyright 2020 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  	"context"
     9  	"encoding/json"
    10  	"errors"
    11  	"os"
    12  	"strings"
    13  	"sync/atomic"
    14  	"time"
    15  
    16  	"github.com/go-asm/go/trace/traceviewer/format"
    17  )
    18  
    19  // Constants used in event fields.
    20  // See https://docs.google.com/document/d/1CvAClvFfyA5R-PhYUmn5OOQtYMH4h6I0nSsKchNAySU
    21  // for more details.
    22  const (
    23  	phaseDurationBegin = "B"
    24  	phaseDurationEnd   = "E"
    25  	phaseFlowStart     = "s"
    26  	phaseFlowEnd       = "f"
    27  
    28  	bindEnclosingSlice = "e"
    29  )
    30  
    31  var traceStarted atomic.Bool
    32  
    33  func getTraceContext(ctx context.Context) (traceContext, bool) {
    34  	if !traceStarted.Load() {
    35  		return traceContext{}, false
    36  	}
    37  	v := ctx.Value(traceKey{})
    38  	if v == nil {
    39  		return traceContext{}, false
    40  	}
    41  	return v.(traceContext), true
    42  }
    43  
    44  // StartSpan starts a trace event with the given name. The Span ends when its Done method is called.
    45  func StartSpan(ctx context.Context, name string) (context.Context, *Span) {
    46  	tc, ok := getTraceContext(ctx)
    47  	if !ok {
    48  		return ctx, nil
    49  	}
    50  	childSpan := &Span{t: tc.t, name: name, tid: tc.tid, start: time.Now()}
    51  	tc.t.writeEvent(&format.Event{
    52  		Name:  childSpan.name,
    53  		Time:  float64(childSpan.start.UnixNano()) / float64(time.Microsecond),
    54  		TID:   childSpan.tid,
    55  		Phase: phaseDurationBegin,
    56  	})
    57  	ctx = context.WithValue(ctx, traceKey{}, traceContext{tc.t, tc.tid})
    58  	return ctx, childSpan
    59  }
    60  
    61  // StartGoroutine associates the context with a new Thread ID. The Chrome trace viewer associates each
    62  // trace event with a thread, and doesn't expect events with the same thread id to happen at the
    63  // same time.
    64  func StartGoroutine(ctx context.Context) context.Context {
    65  	tc, ok := getTraceContext(ctx)
    66  	if !ok {
    67  		return ctx
    68  	}
    69  	return context.WithValue(ctx, traceKey{}, traceContext{tc.t, tc.t.getNextTID()})
    70  }
    71  
    72  // Flow marks a flow indicating that the 'to' span depends on the 'from' span.
    73  // Flow should be called while the 'to' span is in progress.
    74  func Flow(ctx context.Context, from *Span, to *Span) {
    75  	tc, ok := getTraceContext(ctx)
    76  	if !ok || from == nil || to == nil {
    77  		return
    78  	}
    79  
    80  	id := tc.t.getNextFlowID()
    81  	tc.t.writeEvent(&format.Event{
    82  		Name:     from.name + " -> " + to.name,
    83  		Category: "flow",
    84  		ID:       id,
    85  		Time:     float64(from.end.UnixNano()) / float64(time.Microsecond),
    86  		Phase:    phaseFlowStart,
    87  		TID:      from.tid,
    88  	})
    89  	tc.t.writeEvent(&format.Event{
    90  		Name:      from.name + " -> " + to.name,
    91  		Category:  "flow", // TODO(matloob): Add Category to Flow?
    92  		ID:        id,
    93  		Time:      float64(to.start.UnixNano()) / float64(time.Microsecond),
    94  		Phase:     phaseFlowEnd,
    95  		TID:       to.tid,
    96  		BindPoint: bindEnclosingSlice,
    97  	})
    98  }
    99  
   100  type Span struct {
   101  	t *tracer
   102  
   103  	name  string
   104  	tid   uint64
   105  	start time.Time
   106  	end   time.Time
   107  }
   108  
   109  func (s *Span) Done() {
   110  	if s == nil {
   111  		return
   112  	}
   113  	s.end = time.Now()
   114  	s.t.writeEvent(&format.Event{
   115  		Name:  s.name,
   116  		Time:  float64(s.end.UnixNano()) / float64(time.Microsecond),
   117  		TID:   s.tid,
   118  		Phase: phaseDurationEnd,
   119  	})
   120  }
   121  
   122  type tracer struct {
   123  	file chan traceFile // 1-buffered
   124  
   125  	nextTID    atomic.Uint64
   126  	nextFlowID atomic.Uint64
   127  }
   128  
   129  func (t *tracer) writeEvent(ev *format.Event) error {
   130  	f := <-t.file
   131  	defer func() { t.file <- f }()
   132  	var err error
   133  	if f.entries == 0 {
   134  		_, err = f.sb.WriteString("[\n")
   135  	} else {
   136  		_, err = f.sb.WriteString(",")
   137  	}
   138  	f.entries++
   139  	if err != nil {
   140  		return nil
   141  	}
   142  
   143  	if err := f.enc.Encode(ev); err != nil {
   144  		return err
   145  	}
   146  
   147  	// Write event string to output file.
   148  	_, err = f.f.WriteString(f.sb.String())
   149  	f.sb.Reset()
   150  	return err
   151  }
   152  
   153  func (t *tracer) Close() error {
   154  	f := <-t.file
   155  	defer func() { t.file <- f }()
   156  
   157  	_, firstErr := f.f.WriteString("]")
   158  	if err := f.f.Close(); firstErr == nil {
   159  		firstErr = err
   160  	}
   161  	return firstErr
   162  }
   163  
   164  func (t *tracer) getNextTID() uint64 {
   165  	return t.nextTID.Add(1)
   166  }
   167  
   168  func (t *tracer) getNextFlowID() uint64 {
   169  	return t.nextFlowID.Add(1)
   170  }
   171  
   172  // traceKey is the context key for tracing information. It is unexported to prevent collisions with context keys defined in
   173  // other packages.
   174  type traceKey struct{}
   175  
   176  type traceContext struct {
   177  	t   *tracer
   178  	tid uint64
   179  }
   180  
   181  // Start starts a trace which writes to the given file.
   182  func Start(ctx context.Context, file string) (context.Context, func() error, error) {
   183  	traceStarted.Store(true)
   184  	if file == "" {
   185  		return nil, nil, errors.New("no trace file supplied")
   186  	}
   187  	f, err := os.Create(file)
   188  	if err != nil {
   189  		return nil, nil, err
   190  	}
   191  	t := &tracer{file: make(chan traceFile, 1)}
   192  	sb := new(strings.Builder)
   193  	t.file <- traceFile{
   194  		f:   f,
   195  		sb:  sb,
   196  		enc: json.NewEncoder(sb),
   197  	}
   198  	ctx = context.WithValue(ctx, traceKey{}, traceContext{t: t})
   199  	return ctx, t.Close, nil
   200  }
   201  
   202  type traceFile struct {
   203  	f       *os.File
   204  	sb      *strings.Builder
   205  	enc     *json.Encoder
   206  	entries int64
   207  }