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. 4 5// Tests CPU profiling. 6 7//go:build ignore 8 9package main 10 11import ( 12 "bytes" 13 "context" 14 "fmt" 15 "internal/profile" 16 "log" 17 "os" 18 "runtime" 19 "runtime/pprof" 20 "runtime/trace" 21 "strings" 22 "time" 23) 24 25func main() { 26 cpuBuf := new(bytes.Buffer) 27 if err := pprof.StartCPUProfile(cpuBuf); err != nil { 28 log.Fatalf("failed to start CPU profile: %v", err) 29 } 30 31 if err := trace.Start(os.Stdout); err != nil { 32 log.Fatalf("failed to start tracing: %v", err) 33 } 34 35 dur := 100 * time.Millisecond 36 func() { 37 // Create a region in the execution trace. Set and clear goroutine 38 // labels fully within that region, so we know that any CPU profile 39 // sample with the label must also be eligible for inclusion in the 40 // execution trace. 41 ctx := context.Background() 42 defer trace.StartRegion(ctx, "cpuHogger").End() 43 pprof.Do(ctx, pprof.Labels("tracing", "on"), func(ctx context.Context) { 44 cpuHogger(cpuHog1, &salt1, dur) 45 }) 46 // Be sure the execution trace's view, when filtered to this goroutine 47 // via the explicit goroutine ID in each event, gets many more samples 48 // than the CPU profiler when filtered to this goroutine via labels. 49 cpuHogger(cpuHog1, &salt1, dur) 50 }() 51 52 trace.Stop() 53 pprof.StopCPUProfile() 54 55 // Summarize the CPU profile to stderr so the test can check against it. 56 57 prof, err := profile.Parse(cpuBuf) 58 if err != nil { 59 log.Fatalf("failed to parse CPU profile: %v", err) 60 } 61 // Examine the CPU profiler's view. Filter it to only include samples from 62 // the single test goroutine. Use labels to execute that filter: they should 63 // apply to all work done while that goroutine is getg().m.curg, and they 64 // should apply to no other goroutines. 65 pprofStacks := make(map[string]int) 66 for _, s := range prof.Sample { 67 if s.Label["tracing"] != nil { 68 var fns []string 69 var leaf string 70 for _, loc := range s.Location { 71 for _, line := range loc.Line { 72 fns = append(fns, fmt.Sprintf("%s:%d", line.Function.Name, line.Line)) 73 leaf = line.Function.Name 74 } 75 } 76 // runtime.sigprof synthesizes call stacks when "normal traceback is 77 // impossible or has failed", using particular placeholder functions 78 // to represent common failure cases. Look for those functions in 79 // the leaf position as a sign that the call stack and its 80 // symbolization are more complex than this test can handle. 81 // 82 // TODO: Make the symbolization done by the execution tracer and CPU 83 // profiler match up even in these harder cases. See #53378. 84 switch leaf { 85 case "runtime._System", "runtime._GC", "runtime._ExternalCode", "runtime._VDSO": 86 continue 87 } 88 stack := strings.Join(fns, "|") 89 samples := int(s.Value[0]) 90 pprofStacks[stack] += samples 91 } 92 } 93 for stack, samples := range pprofStacks { 94 fmt.Fprintf(os.Stderr, "%s\t%d\n", stack, samples) 95 } 96} 97 98func cpuHogger(f func(x int) int, y *int, dur time.Duration) { 99 // We only need to get one 100 Hz clock tick, so we've got 100 // a large safety buffer. 101 // But do at least 500 iterations (which should take about 100ms), 102 // otherwise TestCPUProfileMultithreaded can fail if only one 103 // thread is scheduled during the testing period. 104 t0 := time.Now() 105 accum := *y 106 for i := 0; i < 500 || time.Since(t0) < dur; i++ { 107 accum = f(accum) 108 } 109 *y = accum 110} 111 112var ( 113 salt1 = 0 114) 115 116// The actual CPU hogging function. 117// Must not call other functions nor access heap/globals in the loop, 118// otherwise under race detector the samples will be in the race runtime. 119func cpuHog1(x int) int { 120 return cpuHog0(x, 1e5) 121} 122 123func cpuHog0(x, n int) int { 124 foo := x 125 for i := 0; i < n; i++ { 126 if i%1000 == 0 { 127 // Spend time in mcall, stored as gp.m.curg, with g0 running 128 runtime.Gosched() 129 } 130 if foo > 0 { 131 foo *= foo 132 } else { 133 foo *= foo + 1 134 } 135 } 136 return foo 137} 138