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 }