Source file
src/runtime/trace/flightrecorder_test.go
1
2
3
4
5 package trace_test
6
7 import (
8 "bytes"
9 "context"
10 inttrace "internal/trace"
11 "internal/trace/testtrace"
12 "io"
13 "runtime/trace"
14 "slices"
15 "sync"
16 "sync/atomic"
17 "testing"
18 "time"
19 )
20
21 func TestFlightRecorderDoubleStart(t *testing.T) {
22 fr := trace.NewFlightRecorder(trace.FlightRecorderConfig{})
23 if err := fr.Start(); err != nil {
24 t.Fatalf("unexpected error on Start: %v", err)
25 }
26 if err := fr.Start(); err == nil {
27 t.Fatalf("expected error from double Start: %v", err)
28 }
29 fr.Stop()
30 }
31
32 func TestFlightRecorderEnabled(t *testing.T) {
33 fr := trace.NewFlightRecorder(trace.FlightRecorderConfig{})
34
35 if fr.Enabled() {
36 t.Fatal("flight recorder is enabled, but never started")
37 }
38 if err := fr.Start(); err != nil {
39 t.Fatalf("unexpected error on Start: %v", err)
40 }
41 if !fr.Enabled() {
42 t.Fatal("flight recorder is not enabled, but started")
43 }
44 fr.Stop()
45 if fr.Enabled() {
46 t.Fatal("flight recorder is enabled, but stopped")
47 }
48 }
49
50 func TestFlightRecorderWriteToDisabled(t *testing.T) {
51 var buf bytes.Buffer
52
53 fr := trace.NewFlightRecorder(trace.FlightRecorderConfig{})
54 if n, err := fr.WriteTo(&buf); err == nil {
55 t.Fatalf("successfully wrote %d bytes from disabled flight recorder", n)
56 }
57 if err := fr.Start(); err != nil {
58 t.Fatalf("unexpected error on Start: %v", err)
59 }
60 fr.Stop()
61 if n, err := fr.WriteTo(&buf); err == nil {
62 t.Fatalf("successfully wrote %d bytes from disabled flight recorder", n)
63 }
64 }
65
66 func TestFlightRecorderConcurrentWriteTo(t *testing.T) {
67 fr := trace.NewFlightRecorder(trace.FlightRecorderConfig{})
68 if err := fr.Start(); err != nil {
69 t.Fatalf("unexpected error on Start: %v", err)
70 }
71
72
73
74
75
76
77 var bufs [2]bytes.Buffer
78 var wg sync.WaitGroup
79 var successes atomic.Uint32
80 for i := range bufs {
81 wg.Add(1)
82 go func() {
83 defer wg.Done()
84
85 n, err := fr.WriteTo(&bufs[i])
86
87 if err != nil && err.Error() == "call to WriteTo for trace.FlightRecorder already in progress" {
88 if n != 0 {
89 t.Errorf("(goroutine %d) WriteTo bytes written is non-zero for early bail out: %d", i, n)
90 }
91 return
92 }
93 if err != nil {
94 t.Errorf("(goroutine %d) failed to write snapshot for unexpected reason: %v", i, err)
95 }
96 successes.Add(1)
97
98 if n == 0 {
99 t.Errorf("(goroutine %d) wrote invalid trace of zero bytes in size", i)
100 }
101 if n != int64(bufs[i].Len()) {
102 t.Errorf("(goroutine %d) trace length doesn't match WriteTo result: got %d, want %d", i, n, int64(bufs[i].Len()))
103 }
104 }()
105 }
106 wg.Wait()
107
108
109 fr.Stop()
110
111
112 if successes.Load() == 0 {
113 t.Fatal("expected at least one success to write a snapshot, got zero")
114 }
115
116
117 for i := range bufs {
118 buf := &bufs[i]
119 if buf.Len() == 0 {
120 continue
121 }
122 testReader(t, buf, testtrace.ExpectSuccess())
123 }
124 }
125
126 func TestFlightRecorder(t *testing.T) {
127 testFlightRecorder(t, trace.NewFlightRecorder(trace.FlightRecorderConfig{}), func(snapshot func()) {
128 snapshot()
129 })
130 }
131
132 func TestFlightRecorderStartStop(t *testing.T) {
133 fr := trace.NewFlightRecorder(trace.FlightRecorderConfig{})
134 for i := 0; i < 5; i++ {
135 testFlightRecorder(t, fr, func(snapshot func()) {
136 snapshot()
137 })
138 }
139 }
140
141 func TestFlightRecorderLog(t *testing.T) {
142 tr := testFlightRecorder(t, trace.NewFlightRecorder(trace.FlightRecorderConfig{}), func(snapshot func()) {
143 trace.Log(context.Background(), "message", "hello")
144 snapshot()
145 })
146
147
148 r, err := inttrace.NewReader(bytes.NewReader(tr))
149 if err != nil {
150 t.Fatalf("unexpected error creating trace reader: %v", err)
151 }
152
153
154 found := false
155 for {
156 ev, err := r.ReadEvent()
157 if err == io.EOF {
158 break
159 }
160 if err != nil {
161 t.Fatalf("unexpected error reading trace: %v", err)
162 }
163 if !found && ev.Kind() == inttrace.EventLog {
164 log := ev.Log()
165 found = log.Category == "message" && log.Message == "hello"
166 }
167 }
168 if !found {
169 t.Errorf("failed to find expected log message (%q, %q) in snapshot", "message", "hello")
170 }
171 }
172
173 func TestFlightRecorderGenerationCount(t *testing.T) {
174 test := func(t *testing.T, fr *trace.FlightRecorder) {
175 tr := testFlightRecorder(t, fr, func(snapshot func()) {
176
177 time.Sleep(3 * time.Second)
178 snapshot()
179 })
180
181
182 r, err := inttrace.NewReader(bytes.NewReader(tr))
183 if err != nil {
184 t.Fatalf("unexpected error creating trace reader: %v", err)
185 }
186
187
188 var syncs []int
189 evs := 0
190 for {
191 ev, err := r.ReadEvent()
192 if err == io.EOF {
193 break
194 }
195 if err != nil {
196 t.Fatalf("unexpected error reading trace: %v", err)
197 }
198 if ev.Kind() == inttrace.EventSync {
199 syncs = append(syncs, evs)
200 }
201 evs++
202 }
203 const wantMaxSyncs = 3
204 if len(syncs) > wantMaxSyncs {
205 t.Errorf("expected at most %d sync events, found %d at %d",
206 wantMaxSyncs, len(syncs), syncs)
207 }
208 ends := []int{syncs[0], syncs[len(syncs)-1]}
209 if wantEnds := []int{0, evs - 1}; !slices.Equal(wantEnds, ends) {
210 t.Errorf("expected a sync event at each end of the trace, found sync events at %d instead of %d",
211 ends, wantEnds)
212 }
213 }
214 t.Run("MinAge", func(t *testing.T) {
215 fr := trace.NewFlightRecorder(trace.FlightRecorderConfig{MinAge: time.Millisecond})
216 test(t, fr)
217 })
218 t.Run("MaxBytes", func(t *testing.T) {
219 fr := trace.NewFlightRecorder(trace.FlightRecorderConfig{MaxBytes: 16})
220 test(t, fr)
221 })
222 }
223
224 type flightRecorderTestFunc func(snapshot func())
225
226 func testFlightRecorder(t *testing.T, fr *trace.FlightRecorder, f flightRecorderTestFunc) []byte {
227 if trace.IsEnabled() {
228 t.Skip("cannot run flight recorder tests when tracing is enabled")
229 }
230
231
232 if err := fr.Start(); err != nil {
233 t.Fatalf("unexpected error on Start: %v", err)
234 }
235
236
237 var buf bytes.Buffer
238 callback := func() {
239 n, err := fr.WriteTo(&buf)
240 if err != nil {
241 t.Errorf("unexpected failure during flight recording: %v", err)
242 return
243 }
244 if n < 16 {
245 t.Errorf("expected a trace size of at least 16 bytes, got %d", n)
246 }
247 if n != int64(buf.Len()) {
248 t.Errorf("WriteTo result doesn't match trace size: got %d, want %d", n, int64(buf.Len()))
249 }
250 }
251
252
253 f(callback)
254
255
256 fr.Stop()
257
258
259
260 traceBytes := buf.Bytes()
261
262
263 testReader(t, bytes.NewReader(traceBytes), testtrace.ExpectSuccess())
264 return traceBytes
265 }
266
267 func testReader(t *testing.T, tr io.Reader, exp *testtrace.Expectation) {
268 r, err := inttrace.NewReader(tr)
269 if err != nil {
270 if err := exp.Check(err); err != nil {
271 t.Error(err)
272 }
273 return
274 }
275 v := testtrace.NewValidator()
276 v.SkipClockSnapshotChecks()
277 for {
278 ev, err := r.ReadEvent()
279 if err == io.EOF {
280 break
281 }
282 if err != nil {
283 if err := exp.Check(err); err != nil {
284 t.Error(err)
285 }
286 return
287 }
288 if err := v.Event(ev); err != nil {
289 t.Error(err)
290 }
291 }
292 if err := exp.Check(nil); err != nil {
293 t.Error(err)
294 }
295 }
296
297 func TestTraceAndFlightRecorder(t *testing.T) {
298 var tBuf, frBuf bytes.Buffer
299 if err := trace.Start(&tBuf); err != nil {
300 t.Errorf("unable to start execution tracer: %s", err)
301 }
302 fr := trace.NewFlightRecorder(trace.FlightRecorderConfig{MaxBytes: 16})
303 fr.Start()
304 fr.WriteTo(&frBuf)
305 fr.Stop()
306 trace.Stop()
307 if tBuf.Len() == 0 || frBuf.Len() == 0 {
308 t.Errorf("None of these should be equal to zero: %d %d", tBuf.Len(), frBuf.Len())
309 }
310 if tBuf.Len() <= frBuf.Len() {
311 t.Errorf("trace should be longer than the flight recorder: trace=%d flight record=%d", tBuf.Len(), frBuf.Len())
312 }
313 }
314
View as plain text