Source file
src/cmd/trace/annotations_test.go
1
2
3
4
5
6
7 package main
8
9 import (
10 "bytes"
11 "context"
12 "flag"
13 "fmt"
14 traceparser "internal/trace"
15 "os"
16 "reflect"
17 "runtime/debug"
18 "runtime/trace"
19 "sort"
20 "sync"
21 "testing"
22 "time"
23 )
24
25 var saveTraces = flag.Bool("savetraces", false, "save traces collected by tests")
26
27 func TestOverlappingDuration(t *testing.T) {
28 cases := []struct {
29 start0, end0, start1, end1 int64
30 want time.Duration
31 }{
32 {
33 1, 10, 11, 20, 0,
34 },
35 {
36 1, 10, 5, 20, 5 * time.Nanosecond,
37 },
38 {
39 1, 10, 2, 8, 6 * time.Nanosecond,
40 },
41 }
42
43 for _, tc := range cases {
44 s0, e0, s1, e1 := tc.start0, tc.end0, tc.start1, tc.end1
45 if got := overlappingDuration(s0, e0, s1, e1); got != tc.want {
46 t.Errorf("overlappingDuration(%d, %d, %d, %d)=%v; want %v", s0, e0, s1, e1, got, tc.want)
47 }
48 if got := overlappingDuration(s1, e1, s0, e0); got != tc.want {
49 t.Errorf("overlappingDuration(%d, %d, %d, %d)=%v; want %v", s1, e1, s0, e0, got, tc.want)
50 }
51 }
52 }
53
54
55
56
57
58
59 func prog0() {
60 ctx := context.Background()
61
62 var wg sync.WaitGroup
63
64 wg.Add(1)
65 go func() {
66 defer wg.Done()
67 trace.WithRegion(ctx, "taskless.region", func() {
68 trace.Log(ctx, "key0", "val0")
69 })
70 }()
71
72 wg.Add(1)
73 go func() {
74 defer wg.Done()
75 ctx, task := trace.NewTask(ctx, "task0")
76 trace.WithRegion(ctx, "task0.region0", func() {
77 wg.Add(1)
78 go func() {
79 defer wg.Done()
80 defer task.End()
81 trace.WithRegion(ctx, "task0.region1", func() {
82 trace.WithRegion(ctx, "task0.region2", func() {
83 trace.Log(ctx, "key2", "val2")
84 })
85 trace.Log(ctx, "key1", "val1")
86 })
87 }()
88 })
89 ctx2, task2 := trace.NewTask(ctx, "task1")
90 trace.Log(ctx2, "key3", "val3")
91 task2.End()
92 }()
93 wg.Wait()
94 }
95
96 func TestAnalyzeAnnotations(t *testing.T) {
97
98
99
100 if err := traceProgram(t, prog0, "TestAnalyzeAnnotations"); err != nil {
101 t.Fatalf("failed to trace the program: %v", err)
102 }
103
104 res, err := analyzeAnnotations()
105 if err != nil {
106 t.Fatalf("failed to analyzeAnnotations: %v", err)
107 }
108
109
110
111
112 wantTasks := map[string]struct {
113 complete bool
114 goroutines int
115 regions []string
116 }{
117 "task0": {
118 complete: true,
119 goroutines: 2,
120 regions: []string{"task0.region0", "", "task0.region1", "task0.region2"},
121 },
122 "task1": {
123 complete: true,
124 goroutines: 1,
125 },
126 }
127
128 for _, task := range res.tasks {
129 want, ok := wantTasks[task.name]
130 if !ok {
131 t.Errorf("unexpected task: %s", task)
132 continue
133 }
134 if task.complete() != want.complete || len(task.goroutines) != want.goroutines || !reflect.DeepEqual(regionNames(task), want.regions) {
135 t.Errorf("got task %v; want %+v", task, want)
136 }
137
138 delete(wantTasks, task.name)
139 }
140 if len(wantTasks) > 0 {
141 t.Errorf("no more tasks; want %+v", wantTasks)
142 }
143
144 wantRegions := []string{
145 "",
146 "taskless.region",
147 "task0.region0",
148 "task0.region1",
149 "task0.region2",
150 }
151 var gotRegions []string
152 for regionID := range res.regions {
153 gotRegions = append(gotRegions, regionID.Type)
154 }
155
156 sort.Strings(wantRegions)
157 sort.Strings(gotRegions)
158 if !reflect.DeepEqual(gotRegions, wantRegions) {
159 t.Errorf("got regions %q, want regions %q", gotRegions, wantRegions)
160 }
161 }
162
163
164 func prog1() {
165 ctx := context.Background()
166 ctx1, task1 := trace.NewTask(ctx, "task1")
167 defer task1.End()
168 trace.WithRegion(ctx1, "task1.region", func() {
169 ctx2, task2 := trace.NewTask(ctx1, "task2")
170 defer task2.End()
171 trace.WithRegion(ctx2, "task2.region", func() {
172 ctx3, task3 := trace.NewTask(ctx2, "task3")
173 defer task3.End()
174 trace.WithRegion(ctx3, "task3.region", func() {
175 })
176 })
177 })
178 }
179
180 func TestAnalyzeAnnotationTaskTree(t *testing.T) {
181
182 if err := traceProgram(t, prog1, "TestAnalyzeAnnotationTaskTree"); err != nil {
183 t.Fatalf("failed to trace the program: %v", err)
184 }
185
186 res, err := analyzeAnnotations()
187 if err != nil {
188 t.Fatalf("failed to analyzeAnnotations: %v", err)
189 }
190 tasks := res.tasks
191
192
193
194
195 wantTasks := map[string]struct {
196 parent string
197 children []string
198 regions []string
199 }{
200 "task1": {
201 parent: "",
202 children: []string{"task2"},
203 regions: []string{"task1.region"},
204 },
205 "task2": {
206 parent: "task1",
207 children: []string{"task3"},
208 regions: []string{"task2.region"},
209 },
210 "task3": {
211 parent: "task2",
212 children: nil,
213 regions: []string{"task3.region"},
214 },
215 }
216
217 for _, task := range tasks {
218 want, ok := wantTasks[task.name]
219 if !ok {
220 t.Errorf("unexpected task: %s", task)
221 continue
222 }
223 delete(wantTasks, task.name)
224
225 if parentName(task) != want.parent ||
226 !reflect.DeepEqual(childrenNames(task), want.children) ||
227 !reflect.DeepEqual(regionNames(task), want.regions) {
228 t.Errorf("got %v; want %+v", task, want)
229 }
230 }
231
232 if len(wantTasks) > 0 {
233 t.Errorf("no more tasks; want %+v", wantTasks)
234 }
235 }
236
237
238
239
240
241 func prog2() (gcTime time.Duration) {
242 ch := make(chan bool)
243 ctx1, task := trace.NewTask(context.Background(), "taskWithGC")
244 trace.WithRegion(ctx1, "taskWithGC.region1", func() {
245 go func() {
246 defer trace.StartRegion(ctx1, "taskWithGC.region2").End()
247 <-ch
248 }()
249 s := time.Now()
250 debug.FreeOSMemory()
251 gcTime = time.Since(s)
252 close(ch)
253 })
254 task.End()
255
256 ctx2, task2 := trace.NewTask(context.Background(), "taskWithoutGC")
257 trace.WithRegion(ctx2, "taskWithoutGC.region1", func() {
258
259 })
260 task2.End()
261 return gcTime
262 }
263
264 func TestAnalyzeAnnotationGC(t *testing.T) {
265 err := traceProgram(t, func() {
266 oldGC := debug.SetGCPercent(10000)
267 defer debug.SetGCPercent(oldGC)
268 prog2()
269 }, "TestAnalyzeAnnotationGC")
270 if err != nil {
271 t.Fatalf("failed to trace the program: %v", err)
272 }
273
274 res, err := analyzeAnnotations()
275 if err != nil {
276 t.Fatalf("failed to analyzeAnnotations: %v", err)
277 }
278
279
280 lastTS := int64(0)
281 for i, ev := range res.gcEvents {
282 if ev.Type != traceparser.EvGCStart {
283 t.Errorf("unwanted event in gcEvents: %v", ev)
284 }
285 if i > 0 && lastTS > ev.Ts {
286 t.Errorf("overlapping GC events:\n%d: %v\n%d: %v", i-1, res.gcEvents[i-1], i, res.gcEvents[i])
287 }
288 if ev.Link != nil {
289 lastTS = ev.Link.Ts
290 }
291 }
292
293
294 for _, task := range res.tasks {
295 got := task.overlappingGCDuration(res.gcEvents)
296 switch task.name {
297 case "taskWithoutGC":
298 if got != 0 {
299 t.Errorf("%s reported %v as overlapping GC time; want 0: %v", task.name, got, task)
300 }
301 case "taskWithGC":
302 upperBound := task.duration()
303
304
305
306
307
308 if got <= 0 || got > upperBound {
309 t.Errorf("%s reported %v as overlapping GC time; want (0, %v):\n%v", task.name, got, upperBound, task)
310 buf := new(bytes.Buffer)
311 fmt.Fprintln(buf, "GC Events")
312 for _, ev := range res.gcEvents {
313 fmt.Fprintf(buf, " %s -> %s\n", ev, ev.Link)
314 }
315 fmt.Fprintln(buf, "Events in Task")
316 for i, ev := range task.events {
317 fmt.Fprintf(buf, " %d: %s\n", i, ev)
318 }
319
320 t.Logf("\n%s", buf)
321 }
322 }
323 }
324 }
325
326
327
328
329
330
331 func traceProgram(t *testing.T, f func(), name string) error {
332 t.Helper()
333 buf := new(bytes.Buffer)
334 if err := trace.Start(buf); err != nil {
335 return err
336 }
337 f()
338 trace.Stop()
339
340 saveTrace(buf, name)
341 res, err := traceparser.Parse(buf, name+".faketrace")
342 if err == traceparser.ErrTimeOrder {
343 t.Skipf("skipping due to golang.org/issue/16755: %v", err)
344 } else if err != nil {
345 return err
346 }
347
348 swapLoaderData(res, err)
349 return nil
350 }
351
352 func regionNames(task *taskDesc) (ret []string) {
353 for _, s := range task.regions {
354 ret = append(ret, s.Name)
355 }
356 return ret
357 }
358
359 func parentName(task *taskDesc) string {
360 if task.parent != nil {
361 return task.parent.name
362 }
363 return ""
364 }
365
366 func childrenNames(task *taskDesc) (ret []string) {
367 for _, s := range task.children {
368 ret = append(ret, s.name)
369 }
370 return ret
371 }
372
373 func swapLoaderData(res traceparser.ParseResult, err error) {
374
375 parseTrace()
376
377 loader.res = res
378 loader.err = err
379
380 analyzeGoroutines(nil)
381 gs = traceparser.GoroutineStats(res.Events)
382
383 }
384
385 func saveTrace(buf *bytes.Buffer, name string) {
386 if !*saveTraces {
387 return
388 }
389 if err := os.WriteFile(name+".trace", buf.Bytes(), 0600); err != nil {
390 panic(fmt.Errorf("failed to write trace file: %v", err))
391 }
392 }
393
View as plain text