-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathcpu-profile.go
137 lines (123 loc) · 3.83 KB
/
cpu-profile.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
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
}