diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-16 19:25:22 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-16 19:25:22 +0000 |
commit | f6ad4dcef54c5ce997a4bad5a6d86de229015700 (patch) | |
tree | 7cfa4e31ace5c2bd95c72b154d15af494b2bcbef /src/internal/trace/v2/testdata/testprog | |
parent | Initial commit. (diff) | |
download | golang-1.22-f6ad4dcef54c5ce997a4bad5a6d86de229015700.tar.xz golang-1.22-f6ad4dcef54c5ce997a4bad5a6d86de229015700.zip |
Adding upstream version 1.22.1.upstream/1.22.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/internal/trace/v2/testdata/testprog')
12 files changed, 1112 insertions, 0 deletions
diff --git a/src/internal/trace/v2/testdata/testprog/annotations-stress.go b/src/internal/trace/v2/testdata/testprog/annotations-stress.go new file mode 100644 index 0000000..511d6ed --- /dev/null +++ b/src/internal/trace/v2/testdata/testprog/annotations-stress.go @@ -0,0 +1,84 @@ +// Copyright 2023 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Tests user tasks, regions, and logging. + +//go:build ignore + +package main + +import ( + "context" + "fmt" + "log" + "os" + "runtime/trace" + "time" +) + +func main() { + baseCtx := context.Background() + + // Create a task that starts and ends entirely outside of the trace. + ctx0, t0 := trace.NewTask(baseCtx, "parent") + + // Create a task that starts before the trace and ends during the trace. + ctx1, t1 := trace.NewTask(ctx0, "type1") + + // Start tracing. + if err := trace.Start(os.Stdout); err != nil { + log.Fatalf("failed to start tracing: %v", err) + } + t1.End() + + // Create a task that starts during the trace and ends after. + ctx2, t2 := trace.NewTask(ctx0, "type2") + + // Create a task that starts and ends during the trace. + ctx3, t3 := trace.NewTask(baseCtx, "type3") + + // Generate some events. + for i := 0; i < 2; i++ { + do(baseCtx, 4) + do(ctx0, 2) + do(ctx1, 3) + do(ctx2, 6) + do(ctx3, 5) + } + + // Finish up tasks according to their lifetime relative to the trace. + t3.End() + trace.Stop() + t2.End() + t0.End() +} + +func do(ctx context.Context, k int) { + trace.Log(ctx, "log", "before do") + + var t *trace.Task + ctx, t = trace.NewTask(ctx, "do") + defer t.End() + + trace.Log(ctx, "log2", "do") + + // Create a region and spawn more tasks and more workers. + trace.WithRegion(ctx, "fanout", func() { + for i := 0; i < k; i++ { + go func(i int) { + trace.WithRegion(ctx, fmt.Sprintf("region%d", i), func() { + trace.Logf(ctx, "log", "fanout region%d", i) + if i == 2 { + do(ctx, 0) + return + } + }) + }(i) + } + }) + + // Sleep to let things happen, but also increase the chance that we + // advance a generation. + time.Sleep(10 * time.Millisecond) +} diff --git a/src/internal/trace/v2/testdata/testprog/annotations.go b/src/internal/trace/v2/testdata/testprog/annotations.go new file mode 100644 index 0000000..2507bc4 --- /dev/null +++ b/src/internal/trace/v2/testdata/testprog/annotations.go @@ -0,0 +1,60 @@ +// Copyright 2023 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Tests user tasks, regions, and logging. + +//go:build ignore + +package main + +import ( + "context" + "log" + "os" + "runtime/trace" + "sync" +) + +func main() { + bgctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + // Create a pre-existing region. This won't end up in the trace. + preExistingRegion := trace.StartRegion(bgctx, "pre-existing region") + + // Start tracing. + if err := trace.Start(os.Stdout); err != nil { + log.Fatalf("failed to start tracing: %v", err) + } + + // Beginning of traced execution. + var wg sync.WaitGroup + ctx, task := trace.NewTask(bgctx, "task0") // EvUserTaskCreate("task0") + trace.StartRegion(ctx, "task0 region") + + wg.Add(1) + go func() { + defer wg.Done() + defer task.End() // EvUserTaskEnd("task0") + + trace.StartRegion(ctx, "unended region") + + trace.WithRegion(ctx, "region0", func() { + // EvUserRegionBegin("region0", start) + trace.WithRegion(ctx, "region1", func() { + trace.Log(ctx, "key0", "0123456789abcdef") // EvUserLog("task0", "key0", "0....f") + }) + // EvUserRegionEnd("region0", end) + }) + }() + wg.Wait() + + preExistingRegion.End() + postExistingRegion := trace.StartRegion(bgctx, "post-existing region") + + // End of traced execution. + trace.Stop() + + postExistingRegion.End() +} diff --git a/src/internal/trace/v2/testdata/testprog/cgo-callback.go b/src/internal/trace/v2/testdata/testprog/cgo-callback.go new file mode 100644 index 0000000..d636500 --- /dev/null +++ b/src/internal/trace/v2/testdata/testprog/cgo-callback.go @@ -0,0 +1,80 @@ +// Copyright 2023 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Tests CPU profiling. + +//go:build ignore + +package main + +/* +#include <pthread.h> + +void go_callback(); +void go_callback2(); + +static void *thr(void *arg) { + go_callback(); + return 0; +} + +static void foo() { + pthread_t th; + pthread_attr_t attr; + pthread_attr_init(&attr); + pthread_attr_setstacksize(&attr, 256 << 10); + pthread_create(&th, &attr, thr, 0); + pthread_join(th, 0); +} + +static void bar() { + go_callback2(); +} +*/ +import "C" + +import ( + "log" + "os" + "runtime" + "runtime/trace" +) + +//export go_callback +func go_callback() { + // Do another call into C, just to test that path too. + C.bar() +} + +//export go_callback2 +func go_callback2() { + runtime.GC() +} + +func main() { + // Start tracing. + if err := trace.Start(os.Stdout); err != nil { + log.Fatalf("failed to start tracing: %v", err) + } + + // Do a whole bunch of cgocallbacks. + const n = 10 + done := make(chan bool) + for i := 0; i < n; i++ { + go func() { + C.foo() + done <- true + }() + } + for i := 0; i < n; i++ { + <-done + } + + // Do something to steal back any Ps from the Ms, just + // for coverage. + runtime.GC() + + // End of traced execution. + trace.Stop() +} diff --git a/src/internal/trace/v2/testdata/testprog/cpu-profile.go b/src/internal/trace/v2/testdata/testprog/cpu-profile.go new file mode 100644 index 0000000..293a2ac --- /dev/null +++ b/src/internal/trace/v2/testdata/testprog/cpu-profile.go @@ -0,0 +1,137 @@ +// Copyright 2023 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Tests CPU profiling. + +//go:build ignore + +package main + +import ( + "bytes" + "context" + "fmt" + "internal/profile" + "log" + "os" + "runtime" + "runtime/pprof" + "runtime/trace" + "strings" + "time" +) + +func main() { + cpuBuf := new(bytes.Buffer) + if err := pprof.StartCPUProfile(cpuBuf); err != nil { + log.Fatalf("failed to start CPU profile: %v", err) + } + + if err := trace.Start(os.Stdout); err != nil { + log.Fatalf("failed to start tracing: %v", err) + } + + dur := 100 * time.Millisecond + func() { + // Create a region in the execution trace. Set and clear goroutine + // labels fully within that region, so we know that any CPU profile + // sample with the label must also be eligible for inclusion in the + // execution trace. + ctx := context.Background() + defer trace.StartRegion(ctx, "cpuHogger").End() + pprof.Do(ctx, pprof.Labels("tracing", "on"), func(ctx context.Context) { + cpuHogger(cpuHog1, &salt1, dur) + }) + // Be sure the execution trace's view, when filtered to this goroutine + // via the explicit goroutine ID in each event, gets many more samples + // than the CPU profiler when filtered to this goroutine via labels. + cpuHogger(cpuHog1, &salt1, dur) + }() + + trace.Stop() + pprof.StopCPUProfile() + + // Summarize the CPU profile to stderr so the test can check against it. + + prof, err := profile.Parse(cpuBuf) + if err != nil { + log.Fatalf("failed to parse CPU profile: %v", err) + } + // Examine the CPU profiler's view. Filter it to only include samples from + // the single test goroutine. Use labels to execute that filter: they should + // apply to all work done while that goroutine is getg().m.curg, and they + // should apply to no other goroutines. + pprofStacks := make(map[string]int) + for _, s := range prof.Sample { + if s.Label["tracing"] != nil { + var fns []string + var leaf string + for _, loc := range s.Location { + for _, line := range loc.Line { + fns = append(fns, fmt.Sprintf("%s:%d", line.Function.Name, line.Line)) + leaf = line.Function.Name + } + } + // runtime.sigprof synthesizes call stacks when "normal traceback is + // impossible or has failed", using particular placeholder functions + // to represent common failure cases. Look for those functions in + // the leaf position as a sign that the call stack and its + // symbolization are more complex than this test can handle. + // + // TODO: Make the symbolization done by the execution tracer and CPU + // profiler match up even in these harder cases. See #53378. + switch leaf { + case "runtime._System", "runtime._GC", "runtime._ExternalCode", "runtime._VDSO": + continue + } + stack := strings.Join(fns, "|") + samples := int(s.Value[0]) + pprofStacks[stack] += samples + } + } + for stack, samples := range pprofStacks { + fmt.Fprintf(os.Stderr, "%s\t%d\n", stack, samples) + } +} + +func cpuHogger(f func(x int) int, y *int, dur time.Duration) { + // We only need to get one 100 Hz clock tick, so we've got + // a large safety buffer. + // But do at least 500 iterations (which should take about 100ms), + // otherwise TestCPUProfileMultithreaded can fail if only one + // thread is scheduled during the testing period. + t0 := time.Now() + accum := *y + for i := 0; i < 500 || time.Since(t0) < dur; i++ { + accum = f(accum) + } + *y = accum +} + +var ( + salt1 = 0 +) + +// The actual CPU hogging function. +// Must not call other functions nor access heap/globals in the loop, +// otherwise under race detector the samples will be in the race runtime. +func cpuHog1(x int) int { + return cpuHog0(x, 1e5) +} + +func cpuHog0(x, n int) int { + foo := x + for i := 0; i < n; i++ { + if i%1000 == 0 { + // Spend time in mcall, stored as gp.m.curg, with g0 running + runtime.Gosched() + } + if foo > 0 { + foo *= foo + } else { + foo *= foo + 1 + } + } + return foo +} diff --git a/src/internal/trace/v2/testdata/testprog/futile-wakeup.go b/src/internal/trace/v2/testdata/testprog/futile-wakeup.go new file mode 100644 index 0000000..cc48981 --- /dev/null +++ b/src/internal/trace/v2/testdata/testprog/futile-wakeup.go @@ -0,0 +1,84 @@ +// Copyright 2023 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Tests to make sure the runtime doesn't generate futile wakeups. For example, +// it makes sure that a block on a channel send that unblocks briefly only to +// immediately go back to sleep (in such a way that doesn't reveal any useful +// information, and is purely an artifact of the runtime implementation) doesn't +// make it into the trace. + +//go:build ignore + +package main + +import ( + "context" + "log" + "os" + "runtime" + "runtime/trace" + "sync" +) + +func main() { + if err := trace.Start(os.Stdout); err != nil { + log.Fatalf("failed to start tracing: %v", err) + } + + defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(8)) + c0 := make(chan int, 1) + c1 := make(chan int, 1) + c2 := make(chan int, 1) + const procs = 2 + var done sync.WaitGroup + done.Add(4 * procs) + for p := 0; p < procs; p++ { + const iters = 1e3 + go func() { + trace.WithRegion(context.Background(), "special", func() { + for i := 0; i < iters; i++ { + runtime.Gosched() + c0 <- 0 + } + done.Done() + }) + }() + go func() { + trace.WithRegion(context.Background(), "special", func() { + for i := 0; i < iters; i++ { + runtime.Gosched() + <-c0 + } + done.Done() + }) + }() + go func() { + trace.WithRegion(context.Background(), "special", func() { + for i := 0; i < iters; i++ { + runtime.Gosched() + select { + case c1 <- 0: + case c2 <- 0: + } + } + done.Done() + }) + }() + go func() { + trace.WithRegion(context.Background(), "special", func() { + for i := 0; i < iters; i++ { + runtime.Gosched() + select { + case <-c1: + case <-c2: + } + } + done.Done() + }) + }() + } + done.Wait() + + trace.Stop() +} diff --git a/src/internal/trace/v2/testdata/testprog/gc-stress.go b/src/internal/trace/v2/testdata/testprog/gc-stress.go new file mode 100644 index 0000000..70d3a24 --- /dev/null +++ b/src/internal/trace/v2/testdata/testprog/gc-stress.go @@ -0,0 +1,76 @@ +// Copyright 2023 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Tests a GC-heavy program. This is useful for shaking out +// all sorts of corner cases about GC-related ranges. + +//go:build ignore + +package main + +import ( + "log" + "math/rand" + "os" + "runtime" + "runtime/trace" + "time" +) + +type node struct { + children [4]*node + data [128]byte +} + +func makeTree(depth int) *node { + if depth == 0 { + return new(node) + } + return &node{ + children: [4]*node{ + makeTree(depth - 1), + makeTree(depth - 1), + makeTree(depth - 1), + makeTree(depth - 1), + }, + } +} + +var trees [16]*node +var ballast *[16]*[8192]*node +var sink []byte + +func main() { + for i := range trees { + trees[i] = makeTree(6) + } + ballast = new([16]*[8192]*node) + for i := range ballast { + ballast[i] = new([8192]*node) + for j := range ballast[i] { + ballast[i][j] = &node{ + data: [128]byte{1, 2, 3, 4}, + } + } + } + for i := 0; i < runtime.GOMAXPROCS(-1); i++ { + go func() { + for { + sink = make([]byte, rand.Intn(32<<10)) + } + }() + } + // Increase the chance that we end up starting and stopping + // mid-GC by only starting to trace after a few milliseconds. + time.Sleep(5 * time.Millisecond) + + // Start tracing. + if err := trace.Start(os.Stdout); err != nil { + log.Fatalf("failed to start tracing: %v", err) + } + defer trace.Stop() + + // Let the tracing happen for a bit. + time.Sleep(400 * time.Millisecond) +} diff --git a/src/internal/trace/v2/testdata/testprog/gomaxprocs.go b/src/internal/trace/v2/testdata/testprog/gomaxprocs.go new file mode 100644 index 0000000..2651207 --- /dev/null +++ b/src/internal/trace/v2/testdata/testprog/gomaxprocs.go @@ -0,0 +1,46 @@ +// Copyright 2023 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Tests increasing and decreasing GOMAXPROCS to try and +// catch issues with stale proc state. + +//go:build ignore + +package main + +import ( + "log" + "os" + "runtime" + "runtime/trace" + "time" +) + +func main() { + // Start a goroutine that calls runtime.GC to try and + // introduce some interesting events in between the + // GOMAXPROCS calls. + go func() { + for { + runtime.GC() + time.Sleep(1 * time.Millisecond) + } + }() + + // Start tracing. + if err := trace.Start(os.Stdout); err != nil { + log.Fatalf("failed to start tracing: %v", err) + } + // Run GOMAXPROCS a bunch of times, up and down. + for i := 1; i <= 16; i *= 2 { + runtime.GOMAXPROCS(i) + time.Sleep(1 * time.Millisecond) + } + for i := 16; i >= 1; i /= 2 { + runtime.GOMAXPROCS(i) + time.Sleep(1 * time.Millisecond) + } + // Stop tracing. + trace.Stop() +} diff --git a/src/internal/trace/v2/testdata/testprog/many-start-stop.go b/src/internal/trace/v2/testdata/testprog/many-start-stop.go new file mode 100644 index 0000000..2d5d063 --- /dev/null +++ b/src/internal/trace/v2/testdata/testprog/many-start-stop.go @@ -0,0 +1,38 @@ +// Copyright 2023 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Tests simply starting and stopping tracing multiple times. +// +// This is useful for finding bugs in trace state reset. + +//go:build ignore + +package main + +import ( + "bytes" + "log" + "os" + "runtime" + "runtime/trace" +) + +func main() { + // Trace a few times. + for i := 0; i < 10; i++ { + var buf bytes.Buffer + if err := trace.Start(&buf); err != nil { + log.Fatalf("failed to start tracing: %v", err) + } + runtime.GC() + trace.Stop() + } + + // Start tracing again, this time writing out the result. + if err := trace.Start(os.Stdout); err != nil { + log.Fatalf("failed to start tracing: %v", err) + } + runtime.GC() + trace.Stop() +} diff --git a/src/internal/trace/v2/testdata/testprog/stacks.go b/src/internal/trace/v2/testdata/testprog/stacks.go new file mode 100644 index 0000000..e64bc86 --- /dev/null +++ b/src/internal/trace/v2/testdata/testprog/stacks.go @@ -0,0 +1,129 @@ +// Copyright 2023 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Tests stack symbolization. + +//go:build ignore + +package main + +import ( + "log" + "net" + "os" + "runtime" + "runtime/trace" + "sync" + "time" +) + +func main() { + if err := trace.Start(os.Stdout); err != nil { + log.Fatalf("failed to start tracing: %v", err) + } + defer trace.Stop() // in case of early return + + // Now we will do a bunch of things for which we verify stacks later. + // It is impossible to ensure that a goroutine has actually blocked + // on a channel, in a select or otherwise. So we kick off goroutines + // that need to block first in the hope that while we are executing + // the rest of the test, they will block. + go func() { // func1 + select {} + }() + go func() { // func2 + var c chan int + c <- 0 + }() + go func() { // func3 + var c chan int + <-c + }() + done1 := make(chan bool) + go func() { // func4 + <-done1 + }() + done2 := make(chan bool) + go func() { // func5 + done2 <- true + }() + c1 := make(chan int) + c2 := make(chan int) + go func() { // func6 + select { + case <-c1: + case <-c2: + } + }() + var mu sync.Mutex + mu.Lock() + go func() { // func7 + mu.Lock() + mu.Unlock() + }() + var wg sync.WaitGroup + wg.Add(1) + go func() { // func8 + wg.Wait() + }() + cv := sync.NewCond(&sync.Mutex{}) + go func() { // func9 + cv.L.Lock() + cv.Wait() + cv.L.Unlock() + }() + ln, err := net.Listen("tcp", "127.0.0.1:0") + if err != nil { + log.Fatalf("failed to listen: %v", err) + } + go func() { // func10 + c, err := ln.Accept() + if err != nil { + log.Printf("failed to accept: %v", err) + return + } + c.Close() + }() + rp, wp, err := os.Pipe() + if err != nil { + log.Fatalf("failed to create a pipe: %v", err) + } + defer rp.Close() + defer wp.Close() + pipeReadDone := make(chan bool) + go func() { // func11 + var data [1]byte + rp.Read(data[:]) + pipeReadDone <- true + }() + + time.Sleep(100 * time.Millisecond) + runtime.GC() + runtime.Gosched() + time.Sleep(100 * time.Millisecond) // the last chance for the goroutines above to block + done1 <- true + <-done2 + select { + case c1 <- 0: + case c2 <- 0: + } + mu.Unlock() + wg.Done() + cv.Signal() + c, err := net.Dial("tcp", ln.Addr().String()) + if err != nil { + log.Fatalf("failed to dial: %v", err) + } + c.Close() + var data [1]byte + wp.Write(data[:]) + <-pipeReadDone + + oldGoMaxProcs := runtime.GOMAXPROCS(0) + runtime.GOMAXPROCS(oldGoMaxProcs + 1) + + trace.Stop() + + runtime.GOMAXPROCS(oldGoMaxProcs) +} diff --git a/src/internal/trace/v2/testdata/testprog/stress-start-stop.go b/src/internal/trace/v2/testdata/testprog/stress-start-stop.go new file mode 100644 index 0000000..72c1c59 --- /dev/null +++ b/src/internal/trace/v2/testdata/testprog/stress-start-stop.go @@ -0,0 +1,166 @@ +// Copyright 2023 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Tests a many interesting cases (network, syscalls, a little GC, busy goroutines, +// blocked goroutines, LockOSThread, pipes, and GOMAXPROCS). + +//go:build ignore + +package main + +import ( + "bytes" + "io" + "log" + "net" + "os" + "runtime" + "runtime/trace" + "sync" + "time" +) + +func main() { + defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(8)) + outerDone := make(chan bool) + + go func() { + defer func() { + outerDone <- true + }() + + var wg sync.WaitGroup + done := make(chan bool) + + wg.Add(1) + go func() { + <-done + wg.Done() + }() + + rp, wp, err := os.Pipe() + if err != nil { + log.Fatalf("failed to create pipe: %v", err) + return + } + defer func() { + rp.Close() + wp.Close() + }() + wg.Add(1) + go func() { + var tmp [1]byte + rp.Read(tmp[:]) + <-done + wg.Done() + }() + time.Sleep(time.Millisecond) + + go func() { + runtime.LockOSThread() + for { + select { + case <-done: + return + default: + runtime.Gosched() + } + } + }() + + runtime.GC() + // Trigger GC from malloc. + n := 512 + for i := 0; i < n; i++ { + _ = make([]byte, 1<<20) + } + + // Create a bunch of busy goroutines to load all Ps. + for p := 0; p < 10; p++ { + wg.Add(1) + go func() { + // Do something useful. + tmp := make([]byte, 1<<16) + for i := range tmp { + tmp[i]++ + } + _ = tmp + <-done + wg.Done() + }() + } + + // Block in syscall. + wg.Add(1) + go func() { + var tmp [1]byte + rp.Read(tmp[:]) + <-done + wg.Done() + }() + + runtime.GOMAXPROCS(runtime.GOMAXPROCS(1)) + + // Test timers. + timerDone := make(chan bool) + go func() { + time.Sleep(time.Millisecond) + timerDone <- true + }() + <-timerDone + + // A bit of network. + ln, err := net.Listen("tcp", "127.0.0.1:0") + if err != nil { + log.Fatalf("listen failed: %v", err) + return + } + defer ln.Close() + go func() { + c, err := ln.Accept() + if err != nil { + return + } + time.Sleep(time.Millisecond) + var buf [1]byte + c.Write(buf[:]) + c.Close() + }() + c, err := net.Dial("tcp", ln.Addr().String()) + if err != nil { + log.Fatalf("dial failed: %v", err) + return + } + var tmp [1]byte + c.Read(tmp[:]) + c.Close() + + go func() { + runtime.Gosched() + select {} + }() + + // Unblock helper goroutines and wait them to finish. + wp.Write(tmp[:]) + wp.Write(tmp[:]) + close(done) + wg.Wait() + }() + + const iters = 5 + for i := 0; i < iters; i++ { + var w io.Writer + if i == iters-1 { + w = os.Stdout + } else { + w = new(bytes.Buffer) + } + if err := trace.Start(w); err != nil { + log.Fatalf("failed to start tracing: %v", err) + } + time.Sleep(time.Millisecond) + trace.Stop() + } + <-outerDone +} diff --git a/src/internal/trace/v2/testdata/testprog/stress.go b/src/internal/trace/v2/testdata/testprog/stress.go new file mode 100644 index 0000000..99696d1 --- /dev/null +++ b/src/internal/trace/v2/testdata/testprog/stress.go @@ -0,0 +1,146 @@ +// Copyright 2023 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Tests a many interesting cases (network, syscalls, a little GC, busy goroutines, +// blocked goroutines, LockOSThread, pipes, and GOMAXPROCS). + +//go:build ignore + +package main + +import ( + "log" + "net" + "os" + "runtime" + "runtime/trace" + "sync" + "time" +) + +func main() { + var wg sync.WaitGroup + done := make(chan bool) + + // Create a goroutine blocked before tracing. + wg.Add(1) + go func() { + <-done + wg.Done() + }() + + // Create a goroutine blocked in syscall before tracing. + rp, wp, err := os.Pipe() + if err != nil { + log.Fatalf("failed to create pipe: %v", err) + } + defer func() { + rp.Close() + wp.Close() + }() + wg.Add(1) + go func() { + var tmp [1]byte + rp.Read(tmp[:]) + <-done + wg.Done() + }() + time.Sleep(time.Millisecond) // give the goroutine above time to block + + if err := trace.Start(os.Stdout); err != nil { + log.Fatalf("failed to start tracing: %v", err) + } + defer trace.Stop() + + procs := runtime.GOMAXPROCS(10) + time.Sleep(50 * time.Millisecond) // test proc stop/start events + + go func() { + runtime.LockOSThread() + for { + select { + case <-done: + return + default: + runtime.Gosched() + } + } + }() + + runtime.GC() + // Trigger GC from malloc. + n := 512 + for i := 0; i < n; i++ { + _ = make([]byte, 1<<20) + } + + // Create a bunch of busy goroutines to load all Ps. + for p := 0; p < 10; p++ { + wg.Add(1) + go func() { + // Do something useful. + tmp := make([]byte, 1<<16) + for i := range tmp { + tmp[i]++ + } + _ = tmp + <-done + wg.Done() + }() + } + + // Block in syscall. + wg.Add(1) + go func() { + var tmp [1]byte + rp.Read(tmp[:]) + <-done + wg.Done() + }() + + // Test timers. + timerDone := make(chan bool) + go func() { + time.Sleep(time.Millisecond) + timerDone <- true + }() + <-timerDone + + // A bit of network. + ln, err := net.Listen("tcp", "127.0.0.1:0") + if err != nil { + log.Fatalf("listen failed: %v", err) + } + defer ln.Close() + go func() { + c, err := ln.Accept() + if err != nil { + return + } + time.Sleep(time.Millisecond) + var buf [1]byte + c.Write(buf[:]) + c.Close() + }() + c, err := net.Dial("tcp", ln.Addr().String()) + if err != nil { + log.Fatalf("dial failed: %v", err) + } + var tmp [1]byte + c.Read(tmp[:]) + c.Close() + + go func() { + runtime.Gosched() + select {} + }() + + // Unblock helper goroutines and wait them to finish. + wp.Write(tmp[:]) + wp.Write(tmp[:]) + close(done) + wg.Wait() + + runtime.GOMAXPROCS(procs) +} diff --git a/src/internal/trace/v2/testdata/testprog/wait-on-pipe.go b/src/internal/trace/v2/testdata/testprog/wait-on-pipe.go new file mode 100644 index 0000000..912f5dd --- /dev/null +++ b/src/internal/trace/v2/testdata/testprog/wait-on-pipe.go @@ -0,0 +1,66 @@ +// Copyright 2023 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Tests a goroutine sitting blocked in a syscall for +// an entire generation. This is a regression test for +// #65196. + +//go:build ignore + +package main + +import ( + "log" + "os" + "runtime/trace" + "syscall" + "time" +) + +func main() { + // Create a pipe to block on. + var p [2]int + if err := syscall.Pipe(p[:]); err != nil { + log.Fatalf("failed to create pipe: %v", err) + } + rfd, wfd := p[0], p[1] + + // Create a goroutine that blocks on the pipe. + done := make(chan struct{}) + go func() { + var data [1]byte + _, err := syscall.Read(rfd, data[:]) + if err != nil { + log.Fatalf("failed to read from pipe: %v", err) + } + done <- struct{}{} + }() + + // Give the goroutine ample chance to block on the pipe. + time.Sleep(10 * time.Millisecond) + + // Start tracing. + if err := trace.Start(os.Stdout); err != nil { + log.Fatalf("failed to start tracing: %v", err) + } + + // This isn't enough to have a full generation pass by default, + // but it is generally enough in stress mode. + time.Sleep(100 * time.Millisecond) + + // Write to the pipe to unblock it. + if _, err := syscall.Write(wfd, []byte{10}); err != nil { + log.Fatalf("failed to write to pipe: %v", err) + } + + // Wait for the goroutine to unblock and start running. + // This is helpful to catch incorrect information written + // down for the syscall-blocked goroutine, since it'll start + // executing, and that execution information will be + // inconsistent. + <-done + + // Stop tracing. + trace.Stop() +} |