1 // Copyright 2014 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.
12 "internal/goexperiment"
30 saveTraces = flag.Bool("savetraces", false, "save traces collected by tests")
33 // TestEventBatch tests Flush calls that happen during Start
34 // don't produce corrupted traces.
35 func TestEventBatch(t *testing.T) {
37 t.Skip("skipping in race mode")
40 t.Skip("skipping because -test.trace is set")
43 t.Skip("skipping in short mode")
45 if goexperiment.ExecTracer2 {
46 t.Skip("skipping because this test is incompatible with the new tracer")
48 // During Start, bunch of records are written to reflect the current
49 // snapshot of the program, including state of each goroutines.
50 // And some string constants are written to the trace to aid trace
51 // parsing. This test checks Flush of the buffer occurred during
52 // this process doesn't cause corrupted traces.
53 // When a Flush is called during Start is complicated
54 // so we test with a range of number of goroutines hoping that one
55 // of them triggers Flush.
56 // This range was chosen to fill up a ~64KB buffer with traceEvGoCreate
57 // and traceEvGoWaiting events (12~13bytes per goroutine).
58 for g := 4950; g < 5050; g++ {
60 t.Run("G="+strconv.Itoa(n), func(t *testing.T) {
64 in := make(chan bool, 1000)
65 for i := 0; i < n; i++ {
71 buf := new(bytes.Buffer)
72 if err := Start(buf); err != nil {
73 t.Fatalf("failed to start tracing: %v", err)
76 for i := 0; i < n; i++ {
82 _, err := trace.Parse(buf, "")
83 if err == trace.ErrTimeOrder {
84 t.Skipf("skipping trace: %v", err)
88 t.Fatalf("failed to parse trace: %v", err)
94 func TestTraceStartStop(t *testing.T) {
96 t.Skip("skipping because -test.trace is set")
98 buf := new(bytes.Buffer)
99 if err := Start(buf); err != nil {
100 t.Fatalf("failed to start tracing: %v", err)
105 t.Fatalf("trace is empty")
107 time.Sleep(100 * time.Millisecond)
108 if size != buf.Len() {
109 t.Fatalf("trace writes after stop: %v -> %v", size, buf.Len())
111 saveTrace(t, buf, "TestTraceStartStop")
114 func TestTraceDoubleStart(t *testing.T) {
116 t.Skip("skipping because -test.trace is set")
119 buf := new(bytes.Buffer)
120 if err := Start(buf); err != nil {
121 t.Fatalf("failed to start tracing: %v", err)
123 if err := Start(buf); err == nil {
124 t.Fatalf("succeed to start tracing second time")
130 func TestTrace(t *testing.T) {
132 t.Skip("skipping because -test.trace is set")
134 if goexperiment.ExecTracer2 {
135 // An equivalent test exists in internal/trace/v2.
136 t.Skip("skipping because this test is incompatible with the new tracer")
138 buf := new(bytes.Buffer)
139 if err := Start(buf); err != nil {
140 t.Fatalf("failed to start tracing: %v", err)
143 saveTrace(t, buf, "TestTrace")
144 _, err := trace.Parse(buf, "")
145 if err == trace.ErrTimeOrder {
146 t.Skipf("skipping trace: %v", err)
149 t.Fatalf("failed to parse trace: %v", err)
153 func parseTrace(t *testing.T, r io.Reader) ([]*trace.Event, map[uint64]*trace.GDesc) {
154 res, err := trace.Parse(r, "")
155 if err == trace.ErrTimeOrder {
156 t.Skipf("skipping trace: %v", err)
159 t.Fatalf("failed to parse trace: %v", err)
161 gs := trace.GoroutineStats(res.Events)
162 for goid := range gs {
163 // We don't do any particular checks on the result at the moment.
164 // But still check that RelatedGoroutines does not crash, hang, etc.
165 _ = trace.RelatedGoroutines(res.Events, goid)
167 return res.Events, gs
170 func testBrokenTimestamps(t *testing.T, data []byte) {
171 // On some processors cputicks (used to generate trace timestamps)
172 // produce non-monotonic timestamps. It is important that the parser
173 // distinguishes logically inconsistent traces (e.g. missing, excessive
174 // or misordered events) from broken timestamps. The former is a bug
175 // in tracer, the latter is a machine issue.
176 // So now that we have a consistent trace, test that (1) parser does
177 // not return a logical error in case of broken timestamps
178 // and (2) broken timestamps are eventually detected and reported.
179 trace.BreakTimestampsForTesting = true
181 trace.BreakTimestampsForTesting = false
183 for i := 0; i < 1e4; i++ {
184 _, err := trace.Parse(bytes.NewReader(data), "")
185 if err == trace.ErrTimeOrder {
189 t.Fatalf("failed to parse trace: %v", err)
194 func TestTraceStress(t *testing.T) {
195 switch runtime.GOOS {
197 t.Skip("no os.Pipe on " + runtime.GOOS)
200 t.Skip("skipping because -test.trace is set")
203 t.Skip("skipping in -short mode")
205 if goexperiment.ExecTracer2 {
206 // An equivalent test exists in internal/trace/v2.
207 t.Skip("skipping because this test is incompatible with the new tracer")
210 var wg sync.WaitGroup
211 done := make(chan bool)
213 // Create a goroutine blocked before tracing.
220 // Create a goroutine blocked in syscall before tracing.
221 rp, wp, err := os.Pipe()
223 t.Fatalf("failed to create pipe: %v", err)
236 time.Sleep(time.Millisecond) // give the goroutine above time to block
238 buf := new(bytes.Buffer)
239 if err := Start(buf); err != nil {
240 t.Fatalf("failed to start tracing: %v", err)
243 procs := runtime.GOMAXPROCS(10)
244 time.Sleep(50 * time.Millisecond) // test proc stop/start events
247 runtime.LockOSThread()
259 // Trigger GC from malloc.
261 if isMemoryConstrained() {
262 // Reduce allocation to avoid running out of
263 // memory on the builder - see issue/12032.
266 for i := 0; i < n; i++ {
267 _ = make([]byte, 1<<20)
270 // Create a bunch of busy goroutines to load all Ps.
271 for p := 0; p < 10; p++ {
274 // Do something useful.
275 tmp := make([]byte, 1<<16)
295 timerDone := make(chan bool)
297 time.Sleep(time.Millisecond)
303 ln, err := net.Listen("tcp", "127.0.0.1:0")
305 t.Fatalf("listen failed: %v", err)
309 c, err := ln.Accept()
313 time.Sleep(time.Millisecond)
318 c, err := net.Dial("tcp", ln.Addr().String())
320 t.Fatalf("dial failed: %v", err)
331 // Unblock helper goroutines and wait them to finish.
337 runtime.GOMAXPROCS(procs)
340 saveTrace(t, buf, "TestTraceStress")
343 testBrokenTimestamps(t, trace)
346 // isMemoryConstrained reports whether the current machine is likely
347 // to be memory constrained.
348 // This was originally for the openbsd/arm builder (Issue 12032).
349 // TODO: move this to testenv? Make this look at memory? Look at GO_BUILDER_NAME?
350 func isMemoryConstrained() bool {
351 if runtime.GOOS == "plan9" {
354 switch runtime.GOARCH {
355 case "arm", "mips", "mipsle":
361 // Do a bunch of various stuff (timers, GC, network, etc) in a separate goroutine.
362 // And concurrently with all that start/stop trace 3 times.
363 func TestTraceStressStartStop(t *testing.T) {
364 switch runtime.GOOS {
366 t.Skip("no os.Pipe on " + runtime.GOOS)
369 t.Skip("skipping because -test.trace is set")
371 if goexperiment.ExecTracer2 {
372 // An equivalent test exists in internal/trace/v2.
373 t.Skip("skipping because this test is incompatible with the new tracer")
375 defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(8))
376 outerDone := make(chan bool)
383 var wg sync.WaitGroup
384 done := make(chan bool)
392 rp, wp, err := os.Pipe()
394 t.Errorf("failed to create pipe: %v", err)
408 time.Sleep(time.Millisecond)
411 runtime.LockOSThread()
423 // Trigger GC from malloc.
425 if isMemoryConstrained() {
426 // Reduce allocation to avoid running out of
427 // memory on the builder.
430 for i := 0; i < n; i++ {
431 _ = make([]byte, 1<<20)
434 // Create a bunch of busy goroutines to load all Ps.
435 for p := 0; p < 10; p++ {
438 // Do something useful.
439 tmp := make([]byte, 1<<16)
458 runtime.GOMAXPROCS(runtime.GOMAXPROCS(1))
461 timerDone := make(chan bool)
463 time.Sleep(time.Millisecond)
469 ln, err := net.Listen("tcp", "127.0.0.1:0")
471 t.Errorf("listen failed: %v", err)
476 c, err := ln.Accept()
480 time.Sleep(time.Millisecond)
485 c, err := net.Dial("tcp", ln.Addr().String())
487 t.Errorf("dial failed: %v", err)
499 // Unblock helper goroutines and wait them to finish.
506 for i := 0; i < 3; i++ {
507 buf := new(bytes.Buffer)
508 if err := Start(buf); err != nil {
509 t.Fatalf("failed to start tracing: %v", err)
511 time.Sleep(time.Millisecond)
513 saveTrace(t, buf, "TestTraceStressStartStop")
516 testBrokenTimestamps(t, trace)
521 func TestTraceFutileWakeup(t *testing.T) {
523 t.Skip("skipping because -test.trace is set")
525 if goexperiment.ExecTracer2 {
526 t.Skip("skipping because this test is incompatible with the new tracer")
528 buf := new(bytes.Buffer)
529 if err := Start(buf); err != nil {
530 t.Fatalf("failed to start tracing: %v", err)
533 defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(8))
534 c0 := make(chan int, 1)
535 c1 := make(chan int, 1)
536 c2 := make(chan int, 1)
538 var done sync.WaitGroup
540 for p := 0; p < procs; p++ {
543 for i := 0; i < iters; i++ {
550 for i := 0; i < iters; i++ {
557 for i := 0; i < iters; i++ {
567 for i := 0; i < iters; i++ {
580 saveTrace(t, buf, "TestTraceFutileWakeup")
581 events, _ := parseTrace(t, buf)
582 // Check that (1) trace does not contain EvFutileWakeup events and
583 // (2) there are no consecutive EvGoBlock/EvGCStart/EvGoBlock events
584 // (we call runtime.Gosched between all operations, so these would be futile wakeups).
585 gs := make(map[uint64]int)
586 for _, ev := range events {
588 case trace.EvFutileWakeup:
589 t.Fatalf("found EvFutileWakeup event")
590 case trace.EvGoBlockSend, trace.EvGoBlockRecv, trace.EvGoBlockSelect:
592 t.Fatalf("goroutine %v blocked on %v at %v right after start",
593 ev.G, trace.EventDescriptions[ev.Type].Name, ev.Ts)
596 t.Fatalf("goroutine %v blocked on %v at %v while blocked",
597 ev.G, trace.EventDescriptions[ev.Type].Name, ev.Ts)
600 case trace.EvGoStart:
610 func TestTraceCPUProfile(t *testing.T) {
612 t.Skip("skipping because -test.trace is set")
614 if goexperiment.ExecTracer2 {
615 // An equivalent test exists in internal/trace/v2.
616 t.Skip("skipping because this test is incompatible with the new tracer")
619 cpuBuf := new(bytes.Buffer)
620 if err := pprof.StartCPUProfile(cpuBuf); err != nil {
621 t.Skipf("failed to start CPU profile: %v", err)
624 buf := new(bytes.Buffer)
625 if err := Start(buf); err != nil {
626 t.Fatalf("failed to start tracing: %v", err)
629 dur := 100 * time.Millisecond
631 // Create a region in the execution trace. Set and clear goroutine
632 // labels fully within that region, so we know that any CPU profile
633 // sample with the label must also be eligible for inclusion in the
635 ctx := context.Background()
636 defer StartRegion(ctx, "cpuHogger").End()
637 pprof.Do(ctx, pprof.Labels("tracing", "on"), func(ctx context.Context) {
638 cpuHogger(cpuHog1, &salt1, dur)
640 // Be sure the execution trace's view, when filtered to this goroutine
641 // via the explicit goroutine ID in each event, gets many more samples
642 // than the CPU profiler when filtered to this goroutine via labels.
643 cpuHogger(cpuHog1, &salt1, dur)
647 pprof.StopCPUProfile()
648 saveTrace(t, buf, "TestTraceCPUProfile")
650 prof, err := profile.Parse(cpuBuf)
652 t.Fatalf("failed to parse CPU profile: %v", err)
654 // Examine the CPU profiler's view. Filter it to only include samples from
655 // the single test goroutine. Use labels to execute that filter: they should
656 // apply to all work done while that goroutine is getg().m.curg, and they
657 // should apply to no other goroutines.
659 pprofStacks := make(map[string]int)
660 for _, s := range prof.Sample {
661 if s.Label["tracing"] != nil {
664 for _, loc := range s.Location {
665 for _, line := range loc.Line {
666 fns = append(fns, fmt.Sprintf("%s:%d", line.Function.Name, line.Line))
667 leaf = line.Function.Name
670 // runtime.sigprof synthesizes call stacks when "normal traceback is
671 // impossible or has failed", using particular placeholder functions
672 // to represent common failure cases. Look for those functions in
673 // the leaf position as a sign that the call stack and its
674 // symbolization are more complex than this test can handle.
676 // TODO: Make the symbolization done by the execution tracer and CPU
677 // profiler match up even in these harder cases. See #53378.
679 case "runtime._System", "runtime._GC", "runtime._ExternalCode", "runtime._VDSO":
682 stack := strings.Join(fns, " ")
683 samples := int(s.Value[0])
684 pprofSamples += samples
685 pprofStacks[stack] += samples
688 if pprofSamples == 0 {
689 t.Skipf("CPU profile did not include any samples while tracing was active\n%s", prof)
692 // Examine the execution tracer's view of the CPU profile samples. Filter it
693 // to only include samples from the single test goroutine. Use the goroutine
694 // ID that was recorded in the events: that should reflect getg().m.curg,
695 // same as the profiler's labels (even when the M is using its g0 stack).
696 totalTraceSamples := 0
698 traceStacks := make(map[string]int)
699 events, _ := parseTrace(t, buf)
700 var hogRegion *trace.Event
701 for _, ev := range events {
702 if ev.Type == trace.EvUserRegion && ev.Args[1] == 0 && ev.SArgs[0] == "cpuHogger" {
703 // mode "0" indicates region start
707 if hogRegion == nil {
708 t.Fatalf("execution trace did not identify cpuHogger goroutine")
709 } else if hogRegion.Link == nil {
710 t.Fatalf("execution trace did not close cpuHogger region")
712 for _, ev := range events {
713 if ev.Type == trace.EvCPUSample {
715 if ev.G == hogRegion.G {
718 for _, frame := range ev.Stk {
719 if frame.Fn != "runtime.goexit" {
720 fns = append(fns, fmt.Sprintf("%s:%d", frame.Fn, frame.Line))
723 stack := strings.Join(fns, " ")
729 // The execution trace may drop CPU profile samples if the profiling buffer
730 // overflows. Based on the size of profBufWordCount, that takes a bit over
731 // 1900 CPU samples or 19 thread-seconds at a 100 Hz sample rate. If we've
732 // hit that case, then we definitely have at least one full buffer's worth
733 // of CPU samples, so we'll call that success.
734 overflowed := totalTraceSamples >= 1900
735 if traceSamples < pprofSamples {
736 t.Logf("execution trace did not include all CPU profile samples; %d in profile, %d in trace", pprofSamples, traceSamples)
742 for stack, traceSamples := range traceStacks {
743 pprofSamples := pprofStacks[stack]
744 delete(pprofStacks, stack)
745 if traceSamples < pprofSamples {
746 t.Logf("execution trace did not include all CPU profile samples for stack %q; %d in profile, %d in trace",
747 stack, pprofSamples, traceSamples)
753 for stack, pprofSamples := range pprofStacks {
754 t.Logf("CPU profile included %d samples at stack %q not present in execution trace", pprofSamples, stack)
761 t.Logf("execution trace CPU samples:")
762 for stack, samples := range traceStacks {
763 t.Logf("%d: %q", samples, stack)
765 t.Logf("CPU profile:\n%v", prof)
769 func cpuHogger(f func(x int) int, y *int, dur time.Duration) {
770 // We only need to get one 100 Hz clock tick, so we've got
771 // a large safety buffer.
772 // But do at least 500 iterations (which should take about 100ms),
773 // otherwise TestCPUProfileMultithreaded can fail if only one
774 // thread is scheduled during the testing period.
777 for i := 0; i < 500 || time.Since(t0) < dur; i++ {
787 // The actual CPU hogging function.
788 // Must not call other functions nor access heap/globals in the loop,
789 // otherwise under race detector the samples will be in the race runtime.
790 func cpuHog1(x int) int {
791 return cpuHog0(x, 1e5)
794 func cpuHog0(x, n int) int {
796 for i := 0; i < n; i++ {
798 // Spend time in mcall, stored as gp.m.curg, with g0 running
810 func saveTrace(t *testing.T, buf *bytes.Buffer, name string) {
814 if err := os.WriteFile(name+".trace", buf.Bytes(), 0600); err != nil {
815 t.Errorf("failed to write trace file: %s", err)