Skip to content

Commit 20254c0

Browse files
committed
cmd/go: add rudimentary tracing support.
Updates #38714 Change-Id: I14da982d405074d65ccf5521d431df1bf1734f9a Reviewed-on: https://go-review.googlesource.com/c/go/+/230378 Run-TryBot: Michael Matloob <[email protected]> TryBot-Result: Gobot Gobot <[email protected]> Reviewed-by: Jay Conrod <[email protected]>
1 parent cc700bd commit 20254c0

File tree

1 file changed

+139
-0
lines changed

1 file changed

+139
-0
lines changed

src/cmd/go/internal/trace/trace.go

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
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+
package trace
5+
6+
import (
7+
"cmd/internal/traceviewer"
8+
"context"
9+
"encoding/json"
10+
"errors"
11+
"os"
12+
"strings"
13+
"sync/atomic"
14+
"time"
15+
)
16+
17+
var traceStarted int32
18+
19+
func getTraceContext(ctx context.Context) (traceContext, bool) {
20+
if atomic.LoadInt32(&traceStarted) == 0 {
21+
return traceContext{}, false
22+
}
23+
v := ctx.Value(traceKey{})
24+
if v == nil {
25+
return traceContext{}, false
26+
}
27+
return v.(traceContext), true
28+
}
29+
30+
// StartSpan starts a trace event with the given name. The Span ends when its Done method is called.
31+
func StartSpan(ctx context.Context, name string) (context.Context, *Span) {
32+
tc, ok := getTraceContext(ctx)
33+
if !ok {
34+
return ctx, nil
35+
}
36+
childSpan := &Span{t: tc.t, name: name, start: time.Now()}
37+
tc.t.writeEvent(&traceviewer.Event{
38+
Name: childSpan.name,
39+
Time: float64(childSpan.start.UnixNano()) / float64(time.Microsecond),
40+
Phase: "B",
41+
})
42+
ctx = context.WithValue(ctx, traceKey{}, traceContext{tc.t})
43+
return ctx, childSpan
44+
}
45+
46+
type Span struct {
47+
t *tracer
48+
49+
name string
50+
start time.Time
51+
end time.Time
52+
}
53+
54+
func (s *Span) Done() {
55+
if s == nil {
56+
return
57+
}
58+
s.end = time.Now()
59+
s.t.writeEvent(&traceviewer.Event{
60+
Name: s.name,
61+
Time: float64(s.end.UnixNano()) / float64(time.Microsecond),
62+
Phase: "E",
63+
})
64+
}
65+
66+
type tracer struct {
67+
file chan traceFile // 1-buffered
68+
}
69+
70+
func (t *tracer) writeEvent(ev *traceviewer.Event) error {
71+
f := <-t.file
72+
defer func() { t.file <- f }()
73+
var err error
74+
if f.entries == 0 {
75+
_, err = f.sb.WriteString("[\n")
76+
} else {
77+
_, err = f.sb.WriteString(",")
78+
}
79+
f.entries++
80+
if err != nil {
81+
return nil
82+
}
83+
84+
if err := f.enc.Encode(ev); err != nil {
85+
return err
86+
}
87+
88+
// Write event string to output file.
89+
_, err = f.f.WriteString(f.sb.String())
90+
f.sb.Reset()
91+
return err
92+
}
93+
94+
func (t *tracer) Close() error {
95+
f := <-t.file
96+
defer func() { t.file <- f }()
97+
98+
_, firstErr := f.f.WriteString("]")
99+
if err := f.f.Close(); firstErr == nil {
100+
firstErr = err
101+
}
102+
return firstErr
103+
}
104+
105+
// traceKey is the context key for tracing information. It is unexported to prevent collisions with context keys defined in
106+
// other packages.
107+
type traceKey struct{}
108+
109+
type traceContext struct {
110+
t *tracer
111+
}
112+
113+
// Start starts a trace which writes to the given file.
114+
func Start(ctx context.Context, file string) (context.Context, func() error, error) {
115+
atomic.StoreInt32(&traceStarted, 1)
116+
if file == "" {
117+
return nil, nil, errors.New("no trace file supplied")
118+
}
119+
f, err := os.Create(file)
120+
if err != nil {
121+
return nil, nil, err
122+
}
123+
t := &tracer{file: make(chan traceFile, 1)}
124+
sb := new(strings.Builder)
125+
t.file <- traceFile{
126+
f: f,
127+
sb: sb,
128+
enc: json.NewEncoder(sb),
129+
}
130+
ctx = context.WithValue(ctx, traceKey{}, traceContext{t: t})
131+
return ctx, t.Close, nil
132+
}
133+
134+
type traceFile struct {
135+
f *os.File
136+
sb *strings.Builder
137+
enc *json.Encoder
138+
entries int64
139+
}

0 commit comments

Comments
 (0)