| // 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 |
| } |