@@ -2,18 +2,15 @@ package commands
2
2
3
3
import (
4
4
"archive/zip"
5
- "context"
6
- "encoding/json"
7
5
"fmt"
8
6
"io"
9
7
"os"
10
- "runtime"
11
- "runtime/pprof"
12
8
"strings"
13
9
"time"
14
10
15
11
cmds "github.com/ipfs/go-ipfs-cmds"
16
12
"github.com/ipfs/go-ipfs/core/commands/e"
13
+ "github.com/ipfs/go-ipfs/profile"
17
14
)
18
15
19
16
// time format that works in filenames on windows.
@@ -23,22 +20,26 @@ type profileResult struct {
23
20
File string
24
21
}
25
22
26
- const cpuProfileTimeOption = "cpu-profile-time"
23
+ const (
24
+ profileTimeOption = "profile-time"
25
+ mutexProfileFractionOption = "mutex-profile-fraction"
26
+ blockProfileRateOption = "block-profile-rate"
27
+ )
27
28
28
29
var sysProfileCmd = & cmds.Command {
29
30
Helptext : cmds.HelpText {
30
31
Tagline : "Collect a performance profile for debugging." ,
31
32
ShortDescription : `
32
- Collects cpu, heap, and goroutine profiles from a running go-ipfs daemon
33
- into a single zip file. To aid in debugging, this command also attempts to
34
- include a copy of the running go-ipfs binary.
33
+ Collects profiles from a running go-ipfs daemon into a single zip file.
34
+ To aid in debugging, this command also attempts to include a copy of
35
+ the running go-ipfs binary.
35
36
` ,
36
37
LongDescription : `
37
- Collects cpu, heap, and goroutine profiles from a running go-ipfs daemon
38
- into a single zipfile. To aid in debugging, this command also attempts to
39
- include a copy of the running go-ipfs binary.
38
+ Collects profiles from a running go-ipfs daemon into a single zipfile.
39
+ To aid in debugging, this command also attempts to include a copy of
40
+ the running go-ipfs binary.
40
41
41
- Profile's can be examined using 'go tool pprof', some tips can be found at
42
+ Profiles can be examined using 'go tool pprof', some tips can be found at
42
43
https://github.com/ipfs/go-ipfs/blob/master/docs/debug-guide.md.
43
44
44
45
Privacy Notice:
@@ -48,6 +49,8 @@ The output file includes:
48
49
- A list of running goroutines.
49
50
- A CPU profile.
50
51
- A heap profile.
52
+ - A mutex profile.
53
+ - A block profile.
51
54
- Your copy of go-ipfs.
52
55
- The output of 'ipfs version --all'.
53
56
@@ -69,18 +72,36 @@ However, it could reveal:
69
72
NoLocal : true ,
70
73
Options : []cmds.Option {
71
74
cmds .StringOption (outputOptionName , "o" , "The path where the output should be stored." ),
72
- cmds .StringOption (cpuProfileTimeOption , "The amount of time spent profiling CPU usage." ).WithDefault ("30s" ),
75
+ cmds .StringOption (profileTimeOption , "The amount of time spent profiling. If this is set to 0, then sampling profiles are skipped." ).WithDefault ("30s" ),
76
+ cmds .IntOption (mutexProfileFractionOption , "The fraction 1/n of mutex contention events that are reported in the mutex profile." ).WithDefault (4 ),
77
+ cmds .StringOption (blockProfileRateOption , "The duration to wait between sampling goroutine-blocking events for the blocking profile." ).WithDefault ("1ms" ),
73
78
},
74
79
Run : func (req * cmds.Request , res cmds.ResponseEmitter , env cmds.Environment ) error {
75
- cpuProfileTimeStr , _ := req .Options [cpuProfileTimeOption ].(string )
76
- cpuProfileTime , err := time .ParseDuration (cpuProfileTimeStr )
80
+ profileTimeStr , _ := req .Options [profileTimeOption ].(string )
81
+ profileTime , err := time .ParseDuration (profileTimeStr )
82
+ if err != nil {
83
+ return fmt .Errorf ("failed to parse profile duration %q: %w" , profileTimeStr , err )
84
+ }
85
+
86
+ blockProfileRateStr , _ := req .Options [blockProfileRateOption ].(string )
87
+ blockProfileRate , err := time .ParseDuration (blockProfileRateStr )
77
88
if err != nil {
78
- return fmt .Errorf ("failed to parse CPU profile duration %q: %w" , cpuProfileTimeStr , err )
89
+ return fmt .Errorf ("failed to parse block profile rate %q: %w" , blockProfileRateStr , err )
79
90
}
80
91
92
+ mutexProfileFraction , _ := req .Options [mutexProfileFractionOption ].(int )
93
+
81
94
r , w := io .Pipe ()
95
+
82
96
go func () {
83
- _ = w .CloseWithError (writeProfiles (req .Context , cpuProfileTime , w ))
97
+ archive := zip .NewWriter (w )
98
+ err = profile .WriteProfiles (req .Context , archive , profile.Options {
99
+ ProfileDuration : profileTime ,
100
+ MutexProfileFraction : mutexProfileFraction ,
101
+ BlockProfileRate : blockProfileRate ,
102
+ })
103
+ archive .Close ()
104
+ _ = w .CloseWithError (err )
84
105
}()
85
106
return res .Emit (r )
86
107
},
@@ -120,148 +141,3 @@ However, it could reveal:
120
141
}),
121
142
},
122
143
}
123
-
124
- func WriteAllGoroutineStacks (w io.Writer ) error {
125
- // this is based on pprof.writeGoroutineStacks, and removes the 64 MB limit
126
- buf := make ([]byte , 1 << 20 )
127
- for i := 0 ; ; i ++ {
128
- n := runtime .Stack (buf , true )
129
- if n < len (buf ) {
130
- buf = buf [:n ]
131
- break
132
- }
133
- // if len(buf) >= 64<<20 {
134
- // // Filled 64 MB - stop there.
135
- // break
136
- // }
137
- buf = make ([]byte , 2 * len (buf ))
138
- }
139
- _ , err := w .Write (buf )
140
- return err
141
- }
142
-
143
- func writeProfiles (ctx context.Context , cpuProfileTime time.Duration , w io.Writer ) error {
144
- archive := zip .NewWriter (w )
145
-
146
- // Take some profiles.
147
- type profile struct {
148
- name string
149
- file string
150
- debug int
151
- }
152
-
153
- profiles := []profile {{
154
- name : "goroutine" ,
155
- file : "goroutines.stacks" ,
156
- debug : 2 ,
157
- }, {
158
- name : "goroutine" ,
159
- file : "goroutines.pprof" ,
160
- }, {
161
- name : "heap" ,
162
- file : "heap.pprof" ,
163
- }}
164
-
165
- {
166
- out , err := archive .Create ("goroutines-all.stacks" )
167
- if err != nil {
168
- return err
169
- }
170
- err = WriteAllGoroutineStacks (out )
171
- if err != nil {
172
- return err
173
- }
174
- }
175
-
176
- for _ , profile := range profiles {
177
- prof := pprof .Lookup (profile .name )
178
- out , err := archive .Create (profile .file )
179
- if err != nil {
180
- return err
181
- }
182
- err = prof .WriteTo (out , profile .debug )
183
- if err != nil {
184
- return err
185
- }
186
- }
187
-
188
- // Take a CPU profile.
189
- if cpuProfileTime != 0 {
190
- out , err := archive .Create ("cpu.pprof" )
191
- if err != nil {
192
- return err
193
- }
194
-
195
- err = writeCPUProfile (ctx , cpuProfileTime , out )
196
- if err != nil {
197
- return err
198
- }
199
- }
200
-
201
- // Collect version info
202
- // I'd use diag sysinfo, but that includes some more sensitive information
203
- // (GOPATH, etc.).
204
- {
205
- out , err := archive .Create ("version.json" )
206
- if err != nil {
207
- return err
208
- }
209
-
210
- err = json .NewEncoder (out ).Encode (getVersionInfo ())
211
- if err != nil {
212
- return err
213
- }
214
- }
215
-
216
- // Collect binary
217
- if fi , err := openIPFSBinary (); err == nil {
218
- fname := "ipfs"
219
- if runtime .GOOS == "windows" {
220
- fname += ".exe"
221
- }
222
-
223
- out , err := archive .Create (fname )
224
- if err != nil {
225
- return err
226
- }
227
-
228
- _ , err = io .Copy (out , fi )
229
- _ = fi .Close ()
230
- if err != nil {
231
- return err
232
- }
233
- }
234
- return archive .Close ()
235
- }
236
-
237
- func writeCPUProfile (ctx context.Context , d time.Duration , w io.Writer ) error {
238
- if err := pprof .StartCPUProfile (w ); err != nil {
239
- return err
240
- }
241
- defer pprof .StopCPUProfile ()
242
-
243
- timer := time .NewTimer (d )
244
- defer timer .Stop ()
245
-
246
- select {
247
- case <- timer .C :
248
- case <- ctx .Done ():
249
- return ctx .Err ()
250
- }
251
- return nil
252
- }
253
-
254
- func openIPFSBinary () (* os.File , error ) {
255
- if runtime .GOOS == "linux" {
256
- pid := os .Getpid ()
257
- fi , err := os .Open (fmt .Sprintf ("/proc/%d/exe" , pid ))
258
- if err == nil {
259
- return fi , nil
260
- }
261
- }
262
- path , err := os .Executable ()
263
- if err != nil {
264
- return nil , err
265
- }
266
- return os .Open (path )
267
- }
0 commit comments