1 // Copyright 2023 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.
13 "internal/trace/v2/testtrace"
22 func TestTraceAnnotations(t *testing.T) {
23 testTraceProg(t, "annotations.go", func(t *testing.T, tb, _ []byte, _ bool) {
30 {trace.EventTaskBegin, trace.TaskID(1), []string{"task0"}},
31 {trace.EventRegionBegin, trace.TaskID(1), []string{"region0"}},
32 {trace.EventRegionBegin, trace.TaskID(1), []string{"region1"}},
33 {trace.EventLog, trace.TaskID(1), []string{"key0", "0123456789abcdef"}},
34 {trace.EventRegionEnd, trace.TaskID(1), []string{"region1"}},
35 {trace.EventRegionEnd, trace.TaskID(1), []string{"region0"}},
36 {trace.EventTaskEnd, trace.TaskID(1), []string{"task0"}},
37 // Currently, pre-existing region is not recorded to avoid allocations.
38 {trace.EventRegionBegin, trace.NoTask, []string{"post-existing region"}},
40 r, err := trace.NewReader(bytes.NewReader(tb))
45 ev, err := r.ReadEvent()
52 for i, wantEv := range want {
53 if wantEv.kind != ev.Kind() {
58 case trace.EventTaskBegin, trace.EventTaskEnd:
60 match = task.ID == wantEv.task && task.Type == wantEv.args[0]
61 case trace.EventRegionBegin, trace.EventRegionEnd:
63 match = reg.Task == wantEv.task && reg.Type == wantEv.args[0]
66 match = log.Task == wantEv.task && log.Category == wantEv.args[0] && log.Message == wantEv.args[1]
69 want[i] = want[len(want)-1]
70 want = want[:len(want)-1]
76 for _, ev := range want {
77 t.Errorf("no match for %s TaskID=%d Args=%#v", ev.kind, ev.task, ev.args)
83 func TestTraceAnnotationsStress(t *testing.T) {
84 testTraceProg(t, "annotations-stress.go", nil)
87 func TestTraceCgoCallback(t *testing.T) {
88 testenv.MustHaveCGO(t)
91 case "plan9", "windows":
92 t.Skipf("cgo callback test requires pthreads and is not supported on %s", runtime.GOOS)
94 testTraceProg(t, "cgo-callback.go", nil)
97 func TestTraceCPUProfile(t *testing.T) {
98 testTraceProg(t, "cpu-profile.go", func(t *testing.T, tb, stderr []byte, _ bool) {
99 // Parse stderr which has a CPU profile summary, if everything went well.
100 // (If it didn't, we shouldn't even make it here.)
101 scanner := bufio.NewScanner(bytes.NewReader(stderr))
103 pprofStacks := make(map[string]int)
107 _, err := fmt.Sscanf(scanner.Text(), "%s\t%d", &stack, &samples)
109 t.Fatalf("failed to parse CPU profile summary in stderr: %s\n\tfull:\n%s", scanner.Text(), stderr)
111 pprofStacks[stack] = samples
112 pprofSamples += samples
114 if err := scanner.Err(); err != nil {
115 t.Fatalf("failed to parse CPU profile summary in stderr: %v", err)
117 if pprofSamples == 0 {
118 t.Skip("CPU profile did not include any samples while tracing was active")
121 // Examine the execution tracer's view of the CPU profile samples. Filter it
122 // to only include samples from the single test goroutine. Use the goroutine
123 // ID that was recorded in the events: that should reflect getg().m.curg,
124 // same as the profiler's labels (even when the M is using its g0 stack).
125 totalTraceSamples := 0
127 traceStacks := make(map[string]int)
128 r, err := trace.NewReader(bytes.NewReader(tb))
132 var hogRegion *trace.Event
133 var hogRegionClosed bool
135 ev, err := r.ReadEvent()
142 if ev.Kind() == trace.EventRegionBegin && ev.Region().Type == "cpuHogger" {
145 if ev.Kind() == trace.EventStackSample {
147 if hogRegion != nil && ev.Goroutine() == hogRegion.Goroutine() {
150 ev.Stack().Frames(func(frame trace.StackFrame) bool {
151 if frame.Func != "runtime.goexit" {
152 fns = append(fns, fmt.Sprintf("%s:%d", frame.Func, frame.Line))
156 stack := strings.Join(fns, "|")
160 if ev.Kind() == trace.EventRegionEnd && ev.Region().Type == "cpuHogger" {
161 hogRegionClosed = true
164 if hogRegion == nil {
165 t.Fatalf("execution trace did not identify cpuHogger goroutine")
166 } else if !hogRegionClosed {
167 t.Fatalf("execution trace did not close cpuHogger region")
170 // The execution trace may drop CPU profile samples if the profiling buffer
171 // overflows. Based on the size of profBufWordCount, that takes a bit over
172 // 1900 CPU samples or 19 thread-seconds at a 100 Hz sample rate. If we've
173 // hit that case, then we definitely have at least one full buffer's worth
174 // of CPU samples, so we'll call that success.
175 overflowed := totalTraceSamples >= 1900
176 if traceSamples < pprofSamples {
177 t.Logf("execution trace did not include all CPU profile samples; %d in profile, %d in trace", pprofSamples, traceSamples)
183 for stack, traceSamples := range traceStacks {
184 pprofSamples := pprofStacks[stack]
185 delete(pprofStacks, stack)
186 if traceSamples < pprofSamples {
187 t.Logf("execution trace did not include all CPU profile samples for stack %q; %d in profile, %d in trace",
188 stack, pprofSamples, traceSamples)
194 for stack, pprofSamples := range pprofStacks {
195 t.Logf("CPU profile included %d samples at stack %q not present in execution trace", pprofSamples, stack)
202 t.Logf("execution trace CPU samples:")
203 for stack, samples := range traceStacks {
204 t.Logf("%d: %q", samples, stack)
206 t.Logf("CPU profile:\n%s", stderr)
211 func TestTraceFutileWakeup(t *testing.T) {
212 testTraceProg(t, "futile-wakeup.go", func(t *testing.T, tb, _ []byte, _ bool) {
213 // Check to make sure that no goroutine in the "special" trace region
214 // ends up blocking, unblocking, then immediately blocking again.
216 // The goroutines are careful to call runtime.GoSched in between blocking,
217 // so there should never be a clean block/unblock on the goroutine unless
218 // the runtime was generating extraneous events.
225 gs := make(map[trace.GoID]int)
226 seenSpecialGoroutines := false
227 r, err := trace.NewReader(bytes.NewReader(tb))
232 ev, err := r.ReadEvent()
239 // Only track goroutines in the special region we control, so runtime
240 // goroutines don't interfere (it's totally valid in traces for a
241 // goroutine to block, run, and block again; that's not what we care about).
242 if ev.Kind() == trace.EventRegionBegin && ev.Region().Type == "special" {
243 seenSpecialGoroutines = true
244 gs[ev.Goroutine()] = entered
246 if ev.Kind() == trace.EventRegionEnd && ev.Region().Type == "special" {
247 delete(gs, ev.Goroutine())
249 // Track state transitions for goroutines we care about.
251 // The goroutines we care about will advance through the state machine
252 // of entered -> blocked -> runnable -> running. If in the running state
253 // we block, then we have a futile wakeup. Because of the runtime.Gosched
254 // on these specially marked goroutines, we should end up back in runnable
255 // first. If at any point we go to a different state, switch back to entered
256 // and wait for the next time the goroutine blocks.
257 if ev.Kind() != trace.EventStateTransition {
260 st := ev.StateTransition()
261 if st.Resource.Kind != trace.ResourceGoroutine {
264 id := st.Resource.Goroutine()
269 _, new := st.Goroutine()
272 if new == trace.GoWaiting {
278 if new == trace.GoRunnable {
284 if new == trace.GoRunning {
290 if new == trace.GoWaiting {
291 t.Fatalf("found futile wakeup on goroutine %d", id)
298 if !seenSpecialGoroutines {
299 t.Fatal("did not see a goroutine in a the region 'special'")
304 func TestTraceGCStress(t *testing.T) {
305 testTraceProg(t, "gc-stress.go", nil)
308 func TestTraceGOMAXPROCS(t *testing.T) {
309 testTraceProg(t, "gomaxprocs.go", nil)
312 func TestTraceStacks(t *testing.T) {
313 testTraceProg(t, "stacks.go", func(t *testing.T, tb, _ []byte, stress bool) {
323 // mainLine is the line number of `func main()` in testprog/stacks.go.
326 {trace.EventStateTransition, "Goroutine Running->Runnable", []frame{
327 {"main.main", mainLine + 82},
329 {trace.EventStateTransition, "Goroutine NotExist->Runnable", []frame{
330 {"main.main", mainLine + 11},
332 {trace.EventStateTransition, "Goroutine Running->Waiting", []frame{
333 {"runtime.block", 0},
334 {"main.main.func1", 0},
336 {trace.EventStateTransition, "Goroutine Running->Waiting", []frame{
337 {"runtime.chansend1", 0},
338 {"main.main.func2", 0},
340 {trace.EventStateTransition, "Goroutine Running->Waiting", []frame{
341 {"runtime.chanrecv1", 0},
342 {"main.main.func3", 0},
344 {trace.EventStateTransition, "Goroutine Running->Waiting", []frame{
345 {"runtime.chanrecv1", 0},
346 {"main.main.func4", 0},
348 {trace.EventStateTransition, "Goroutine Waiting->Runnable", []frame{
349 {"runtime.chansend1", 0},
350 {"main.main", mainLine + 84},
352 {trace.EventStateTransition, "Goroutine Running->Waiting", []frame{
353 {"runtime.chansend1", 0},
354 {"main.main.func5", 0},
356 {trace.EventStateTransition, "Goroutine Waiting->Runnable", []frame{
357 {"runtime.chanrecv1", 0},
358 {"main.main", mainLine + 85},
360 {trace.EventStateTransition, "Goroutine Running->Waiting", []frame{
361 {"runtime.selectgo", 0},
362 {"main.main.func6", 0},
364 {trace.EventStateTransition, "Goroutine Waiting->Runnable", []frame{
365 {"runtime.selectgo", 0},
366 {"main.main", mainLine + 86},
368 {trace.EventStateTransition, "Goroutine Running->Waiting", []frame{
369 {"sync.(*Mutex).Lock", 0},
370 {"main.main.func7", 0},
372 {trace.EventStateTransition, "Goroutine Waiting->Runnable", []frame{
373 {"sync.(*Mutex).Unlock", 0},
376 {trace.EventStateTransition, "Goroutine Running->Waiting", []frame{
377 {"sync.(*WaitGroup).Wait", 0},
378 {"main.main.func8", 0},
380 {trace.EventStateTransition, "Goroutine Waiting->Runnable", []frame{
381 {"sync.(*WaitGroup).Add", 0},
382 {"sync.(*WaitGroup).Done", 0},
383 {"main.main", mainLine + 91},
385 {trace.EventStateTransition, "Goroutine Running->Waiting", []frame{
386 {"sync.(*Cond).Wait", 0},
387 {"main.main.func9", 0},
389 {trace.EventStateTransition, "Goroutine Waiting->Runnable", []frame{
390 {"sync.(*Cond).Signal", 0},
393 {trace.EventStateTransition, "Goroutine Running->Waiting", []frame{
397 {trace.EventMetric, "/sched/gomaxprocs:threads", []frame{
398 {"runtime.startTheWorld", 0}, // this is when the current gomaxprocs is logged.
399 {"runtime.startTheWorldGC", 0},
400 {"runtime.GOMAXPROCS", 0},
405 // Only check for this stack if !stress because traceAdvance alone could
406 // allocate enough memory to trigger a GC if called frequently enough.
407 // This might cause the runtime.GC call we're trying to match against to
408 // coalesce with an active GC triggered this by traceAdvance. In that case
409 // we won't have an EventRangeBegin event that matches the stace trace we're
410 // looking for, since runtime.GC will not have triggered the GC.
411 gcEv := evDesc{trace.EventRangeBegin, "GC concurrent mark phase", []frame{
415 want = append(want, gcEv)
417 if runtime.GOOS != "windows" && runtime.GOOS != "plan9" {
418 want = append(want, []evDesc{
419 {trace.EventStateTransition, "Goroutine Running->Waiting", []frame{
420 {"internal/poll.(*FD).Accept", 0},
421 {"net.(*netFD).accept", 0},
422 {"net.(*TCPListener).accept", 0},
423 {"net.(*TCPListener).Accept", 0},
424 {"main.main.func10", 0},
426 {trace.EventStateTransition, "Goroutine Running->Syscall", []frame{
429 {"internal/poll.ignoringEINTRIO", 0},
430 {"internal/poll.(*FD).Read", 0},
431 {"os.(*File).read", 0},
432 {"os.(*File).Read", 0},
433 {"main.main.func11", 0},
437 stackMatches := func(stk trace.Stack, frames []frame) bool {
440 stk.Frames(func(f trace.StackFrame) bool {
441 if f.Func != frames[i].fn {
445 if line := uint64(frames[i].line); line != 0 && line != f.Line {
454 r, err := trace.NewReader(bytes.NewReader(tb))
459 ev, err := r.ReadEvent()
466 for i, wantEv := range want {
467 if wantEv.kind != ev.Kind() {
472 case trace.EventStateTransition:
473 st := ev.StateTransition()
475 switch st.Resource.Kind {
476 case trace.ResourceGoroutine:
477 old, new := st.Goroutine()
478 str = fmt.Sprintf("%s %s->%s", st.Resource.Kind, old, new)
480 match = str == wantEv.match
481 case trace.EventRangeBegin:
483 match = rng.Name == wantEv.match
484 case trace.EventMetric:
485 metric := ev.Metric()
486 match = metric.Name == wantEv.match
488 match = match && stackMatches(ev.Stack(), wantEv.frames)
490 want[i] = want[len(want)-1]
491 want = want[:len(want)-1]
497 for _, ev := range want {
498 t.Errorf("no match for %s Match=%s Stack=%#v", ev.kind, ev.match, ev.frames)
504 func TestTraceStress(t *testing.T) {
505 switch runtime.GOOS {
507 t.Skip("no os.Pipe on " + runtime.GOOS)
509 testTraceProg(t, "stress.go", nil)
512 func TestTraceStressStartStop(t *testing.T) {
513 switch runtime.GOOS {
515 t.Skip("no os.Pipe on " + runtime.GOOS)
517 testTraceProg(t, "stress-start-stop.go", nil)
520 func TestTraceManyStartStop(t *testing.T) {
521 testTraceProg(t, "many-start-stop.go", nil)
524 func testTraceProg(t *testing.T, progName string, extra func(t *testing.T, trace, stderr []byte, stress bool)) {
525 testenv.MustHaveGoRun(t)
527 if runtime.GOOS == "windows" {
528 t.Skip("temporarily disabled on Windows for #64061")
531 // Check if we're on a builder.
532 onBuilder := testenv.Builder() != ""
534 testPath := filepath.Join("./testdata/testprog", progName)
536 runTest := func(t *testing.T, stress bool) {
537 // Run the program and capture the trace, which is always written to stdout.
538 cmd := testenv.Command(t, testenv.GoToolPath(t), "run", testPath)
539 cmd.Env = append(os.Environ(), "GOEXPERIMENT=exectracer2")
541 // Advance a generation constantly.
542 cmd.Env = append(cmd.Env, "GODEBUG=traceadvanceperiod=0")
544 // Capture stdout and stderr.
546 // The protoocol for these programs is that stdout contains the trace data
547 // and stderr is an expectation in string format.
548 var traceBuf, errBuf bytes.Buffer
549 cmd.Stdout = &traceBuf
552 if err := cmd.Run(); err != nil {
553 if errBuf.Len() != 0 {
554 t.Logf("stderr: %s", string(errBuf.Bytes()))
558 tb := traceBuf.Bytes()
560 // Test the trace and the parser.
561 testReader(t, bytes.NewReader(tb), testtrace.ExpectSuccess())
563 // Run some extra validation.
564 if !t.Failed() && extra != nil {
565 extra(t, tb, errBuf.Bytes(), stress)
568 // Dump some more information on failure.
569 if t.Failed() && onBuilder {
570 // Dump directly to the test log on the builder, since this
571 // data is critical for debugging and this is the only way
572 // we can currently make sure it's retained.
573 t.Log("found bad trace; dumping to test log...")
574 t.Log(dumpTraceToText(t, tb))
575 } else if t.Failed() || *dumpTraces {
576 // We asked to dump the trace or failed. Write the trace to a file.
577 t.Logf("wrote trace to file: %s", dumpTraceToFile(t, testName, stress, tb))
580 t.Run("Default", func(t *testing.T) {
583 t.Run("Stress", func(t *testing.T) {
585 t.Skip("skipping trace reader stress tests in short mode")