@@ -23,8 +23,35 @@ import (
23
23
type StopFn func (testing.TB )
24
24
25
25
// Stop stops profiling.
26
- func (f StopFn ) Stop (b testing.TB ) {
27
- f (b )
26
+ func (f StopFn ) Stop (tb testing.TB ) {
27
+ if f != nil {
28
+ f (tb )
29
+ }
30
+ }
31
+
32
+ // StartAllProfiles starts collection of CPU, heap, and mutex profiles, if the
33
+ // "-test.cpuprofile", "-test.memprofile", or "-test.mutexprofile" flags are
34
+ // set. See StartCPUProfile, StartMemProfile, and StartMutexProfile for more
35
+ // details.
36
+ //
37
+ // Example usage:
38
+ //
39
+ // func BenchmarkFoo(b *testing.B) {
40
+ // // ..
41
+ // b.Run("case", func(b *testing.B) {
42
+ // defer benchprof.StartAllProfiles(b).Stop(b)
43
+ // // Benchmark loop.
44
+ // })
45
+ // }
46
+ func StartAllProfiles (tb testing.TB ) StopFn {
47
+ cpuStop := StartCPUProfile (tb )
48
+ memStop := StartMemProfile (tb )
49
+ mutexStop := StartMutexProfile (tb )
50
+ return func (b testing.TB ) {
51
+ cpuStop (b )
52
+ memStop (b )
53
+ mutexStop (b )
54
+ }
28
55
}
29
56
30
57
// StartCPUProfile starts collection of a CPU profile if the "-test.cpuprofile"
@@ -56,29 +83,25 @@ func StartCPUProfile(tb testing.TB) StopFn {
56
83
}
57
84
if cpuProfFile == "" {
58
85
// Not CPU profile requested.
59
- return func ( b testing. TB ) {}
86
+ return nil
60
87
}
61
88
62
89
prefix := profilePrefix (cpuProfFile )
90
+ dir := outputDir (tb )
91
+ if dir != "" {
92
+ cpuProfFile = filepath .Join (dir , cpuProfFile )
93
+ }
63
94
64
95
// Hijack the harness's profile to make a clean profile.
65
96
// The flag is set, so likely a CPU profile started by the Go harness is
66
97
// running (unless -count is specified, but StopCPUProfile is idempotent).
67
98
runtimepprof .StopCPUProfile ()
68
99
69
- var outputDir string
70
- if err := sniffarg .DoEnv ("test.outputdir" , & outputDir ); err != nil {
71
- tb .Fatal (err )
72
- }
73
- if outputDir != "" {
74
- cpuProfFile = filepath .Join (outputDir , cpuProfFile )
75
- }
76
-
77
- // Remove the harness's profile file to avoid confusion.
100
+ // Remove the harness's profile file. It would not be an accurate profile.
78
101
_ = os .Remove (cpuProfFile )
79
102
80
103
// Create a new profile file.
81
- fileName := profileFileName (tb , outputDir , prefix , "cpu" )
104
+ fileName := profileFileName (tb , dir , prefix , "cpu" )
82
105
f , err := os .OpenFile (fileName , os .O_WRONLY | os .O_CREATE | os .O_TRUNC , 0644 )
83
106
if err != nil {
84
107
tb .Fatal (err )
@@ -126,25 +149,75 @@ func StartMemProfile(tb testing.TB) StopFn {
126
149
}
127
150
if memProfFile == "" {
128
151
// No heap profile requested.
129
- return func ( b testing. TB ) {}
152
+ return nil
130
153
}
131
154
132
155
prefix := profilePrefix (memProfFile )
156
+ dir := outputDir (tb )
157
+ if dir != "" {
158
+ memProfFile = filepath .Join (dir , memProfFile )
159
+ }
133
160
134
- var outputDir string
135
- if err := sniffarg .DoEnv ("test.outputdir" , & outputDir ); err != nil {
161
+ // Create a new profile file.
162
+ fileName := profileFileName (tb , dir , prefix , "mem" )
163
+ diffAllocs := diffProfile (func () []byte {
164
+ p := runtimepprof .Lookup ("allocs" )
165
+ var buf bytes.Buffer
166
+ runtime .GC ()
167
+ if err := p .WriteTo (& buf , 0 ); err != nil {
168
+ tb .Fatal (err )
169
+ }
170
+ return buf .Bytes ()
171
+ })
172
+
173
+ return func (b testing.TB ) {
174
+ if sl := diffAllocs (b ); len (sl ) > 0 {
175
+ if err := os .WriteFile (fileName , sl , 0644 ); err != nil {
176
+ b .Fatal (err )
177
+ }
178
+ }
179
+ }
180
+ }
181
+
182
+ // StartMutexProfile starts collection of a mutex profile if the
183
+ // "-test.cpuprofile" flag has been set. The profile will only collect data
184
+ // between calling StartMutexProfile and calling the returned StopFn.
185
+ //
186
+ // Example usage:
187
+ //
188
+ // func BenchmarkFoo(b *testing.B) {
189
+ // // ..
190
+ // b.Run("case", func(b *testing.B) {
191
+ // defer benchprof.StartMutexProfile(b).Stop(b)
192
+ // // Benchmark loop.
193
+ // })
194
+ // }
195
+ //
196
+ // The file name of the profile will include the prefix of the profile flags and
197
+ // the benchmark names. For example, "foo_benchmark_thing_1.mutex" would be
198
+ // created for a "BenchmarkThing/1" benchmark if the
199
+ // "-test.mutexprofile=foo.mutex" flag is set.
200
+ func StartMutexProfile (tb testing.TB ) StopFn {
201
+ var mutexProfFile string
202
+ if err := sniffarg .DoEnv ("test.mutexprofile" , & mutexProfFile ); err != nil {
136
203
tb .Fatal (err )
137
204
}
138
- if outputDir != "" {
139
- memProfFile = filepath .Join (outputDir , memProfFile )
205
+ if mutexProfFile == "" {
206
+ // No mutex profile requested.
207
+ return nil
208
+ }
209
+
210
+ prefix := profilePrefix (mutexProfFile )
211
+ dir := outputDir (tb )
212
+ if dir != "" {
213
+ mutexProfFile = filepath .Join (dir , mutexProfFile )
140
214
}
141
215
142
216
// Create a new profile file.
143
- fileName := profileFileName (tb , outputDir , prefix , "mem " )
217
+ fileName := profileFileName (tb , dir , prefix , "mutex " )
144
218
diffAllocs := diffProfile (func () []byte {
145
- p := runtimepprof .Lookup ("allocs " )
219
+ p := runtimepprof .Lookup ("mutex " )
146
220
var buf bytes.Buffer
147
- runtime .GC ()
148
221
if err := p .WriteTo (& buf , 0 ); err != nil {
149
222
tb .Fatal (err )
150
223
}
@@ -193,6 +266,14 @@ func diffProfile(take func() []byte) func(testing.TB) []byte {
193
266
}
194
267
}
195
268
269
+ func outputDir (tb testing.TB ) string {
270
+ var dir string
271
+ if err := sniffarg .DoEnv ("test.outputdir" , & dir ); err != nil {
272
+ tb .Fatal (err )
273
+ }
274
+ return dir
275
+ }
276
+
196
277
func profilePrefix (profileArg string ) string {
197
278
i := strings .Index (profileArg , "." )
198
279
if i == - 1 {
0 commit comments