7
7
package main
8
8
9
9
import (
10
- "bufio"
11
10
"fmt"
12
11
"internal/trace"
13
- "io "
12
+ "internal/trace/traceviewer "
14
13
"net/http"
15
- "os"
16
- "os/exec"
17
- "path/filepath"
18
- "runtime"
19
14
"sort"
20
15
"strconv"
21
16
"time"
22
-
23
- "github.com/google/pprof/profile"
24
17
)
25
18
26
- func goCmd () string {
27
- var exeSuffix string
28
- if runtime .GOOS == "windows" {
29
- exeSuffix = ".exe"
30
- }
31
- path := filepath .Join (runtime .GOROOT (), "bin" , "go" + exeSuffix )
32
- if _ , err := os .Stat (path ); err == nil {
33
- return path
34
- }
35
- return "go"
36
- }
37
-
38
19
func init () {
39
- http .HandleFunc ("/io" , serveSVGProfile (pprofByGoroutine (computePprofIO )))
40
- http .HandleFunc ("/block" , serveSVGProfile (pprofByGoroutine (computePprofBlock )))
41
- http .HandleFunc ("/syscall" , serveSVGProfile (pprofByGoroutine (computePprofSyscall )))
42
- http .HandleFunc ("/sched" , serveSVGProfile (pprofByGoroutine (computePprofSched )))
43
-
44
- http .HandleFunc ("/regionio" , serveSVGProfile (pprofByRegion (computePprofIO )))
45
- http .HandleFunc ("/regionblock" , serveSVGProfile (pprofByRegion (computePprofBlock )))
46
- http .HandleFunc ("/regionsyscall" , serveSVGProfile (pprofByRegion (computePprofSyscall )))
47
- http .HandleFunc ("/regionsched" , serveSVGProfile (pprofByRegion (computePprofSched )))
48
- }
20
+ http .HandleFunc ("/io" , traceviewer .SVGProfileHandlerFunc (pprofByGoroutine (computePprofIO )))
21
+ http .HandleFunc ("/block" , traceviewer .SVGProfileHandlerFunc (pprofByGoroutine (computePprofBlock )))
22
+ http .HandleFunc ("/syscall" , traceviewer .SVGProfileHandlerFunc (pprofByGoroutine (computePprofSyscall )))
23
+ http .HandleFunc ("/sched" , traceviewer .SVGProfileHandlerFunc (pprofByGoroutine (computePprofSched )))
49
24
50
- // Record represents one entry in pprof-like profiles.
51
- type Record struct {
52
- stk []* trace.Frame
53
- n uint64
54
- time int64
25
+ http .HandleFunc ("/regionio" , traceviewer .SVGProfileHandlerFunc (pprofByRegion (computePprofIO )))
26
+ http .HandleFunc ("/regionblock" , traceviewer .SVGProfileHandlerFunc (pprofByRegion (computePprofBlock )))
27
+ http .HandleFunc ("/regionsyscall" , traceviewer .SVGProfileHandlerFunc (pprofByRegion (computePprofSyscall )))
28
+ http .HandleFunc ("/regionsched" , traceviewer .SVGProfileHandlerFunc (pprofByRegion (computePprofSched )))
55
29
}
56
30
57
31
// interval represents a time interval in the trace.
58
32
type interval struct {
59
33
begin , end int64 // nanoseconds.
60
34
}
61
35
62
- func pprofByGoroutine (compute func (io. Writer , map [ uint64 ][] interval , [] * trace. Event ) error ) func ( w io. Writer , r * http. Request ) error {
63
- return func (w io. Writer , r * http.Request ) error {
36
+ func pprofByGoroutine (compute computePprofFunc ) traceviewer. ProfileFunc {
37
+ return func (r * http.Request ) ([]traceviewer. ProfileRecord , error ) {
64
38
id := r .FormValue ("id" )
65
39
events , err := parseEvents ()
66
40
if err != nil {
67
- return err
41
+ return nil , err
68
42
}
69
43
gToIntervals , err := pprofMatchingGoroutines (id , events )
70
44
if err != nil {
71
- return err
45
+ return nil , err
72
46
}
73
- return compute (w , gToIntervals , events )
47
+ return compute (gToIntervals , events )
74
48
}
75
49
}
76
50
77
- func pprofByRegion (compute func (io. Writer , map [ uint64 ][] interval , [] * trace. Event ) error ) func ( w io. Writer , r * http. Request ) error {
78
- return func (w io. Writer , r * http.Request ) error {
51
+ func pprofByRegion (compute computePprofFunc ) traceviewer. ProfileFunc {
52
+ return func (r * http.Request ) ([]traceviewer. ProfileRecord , error ) {
79
53
filter , err := newRegionFilter (r )
80
54
if err != nil {
81
- return err
55
+ return nil , err
82
56
}
83
57
gToIntervals , err := pprofMatchingRegions (filter )
84
58
if err != nil {
85
- return err
59
+ return nil , err
86
60
}
87
61
events , _ := parseEvents ()
88
62
89
- return compute (w , gToIntervals , events )
63
+ return compute (gToIntervals , events )
90
64
}
91
65
}
92
66
@@ -170,28 +144,30 @@ func pprofMatchingRegions(filter *regionFilter) (map[uint64][]interval, error) {
170
144
return gToIntervals , nil
171
145
}
172
146
147
+ type computePprofFunc func (gToIntervals map [uint64 ][]interval , events []* trace.Event ) ([]traceviewer.ProfileRecord , error )
148
+
173
149
// computePprofIO generates IO pprof-like profile (time spent in IO wait, currently only network blocking event).
174
- func computePprofIO (w io. Writer , gToIntervals map [uint64 ][]interval , events []* trace.Event ) error {
175
- prof := make (map [uint64 ]Record )
150
+ func computePprofIO (gToIntervals map [uint64 ][]interval , events []* trace.Event ) ([]traceviewer. ProfileRecord , error ) {
151
+ prof := make (map [uint64 ]traceviewer. ProfileRecord )
176
152
for _ , ev := range events {
177
153
if ev .Type != trace .EvGoBlockNet || ev .Link == nil || ev .StkID == 0 || len (ev .Stk ) == 0 {
178
154
continue
179
155
}
180
156
overlapping := pprofOverlappingDuration (gToIntervals , ev )
181
157
if overlapping > 0 {
182
158
rec := prof [ev .StkID ]
183
- rec .stk = ev .Stk
184
- rec .n ++
185
- rec .time += overlapping . Nanoseconds ()
159
+ rec .Stack = ev .Stk
160
+ rec .Count ++
161
+ rec .Time += overlapping
186
162
prof [ev .StkID ] = rec
187
163
}
188
164
}
189
- return buildProfile (prof ). Write ( w )
165
+ return recordsOf (prof ), nil
190
166
}
191
167
192
168
// computePprofBlock generates blocking pprof-like profile (time spent blocked on synchronization primitives).
193
- func computePprofBlock (w io. Writer , gToIntervals map [uint64 ][]interval , events []* trace.Event ) error {
194
- prof := make (map [uint64 ]Record )
169
+ func computePprofBlock (gToIntervals map [uint64 ][]interval , events []* trace.Event ) ([]traceviewer. ProfileRecord , error ) {
170
+ prof := make (map [uint64 ]traceviewer. ProfileRecord )
195
171
for _ , ev := range events {
196
172
switch ev .Type {
197
173
case trace .EvGoBlockSend , trace .EvGoBlockRecv , trace .EvGoBlockSelect ,
@@ -208,38 +184,38 @@ func computePprofBlock(w io.Writer, gToIntervals map[uint64][]interval, events [
208
184
overlapping := pprofOverlappingDuration (gToIntervals , ev )
209
185
if overlapping > 0 {
210
186
rec := prof [ev .StkID ]
211
- rec .stk = ev .Stk
212
- rec .n ++
213
- rec .time += overlapping . Nanoseconds ()
187
+ rec .Stack = ev .Stk
188
+ rec .Count ++
189
+ rec .Time += overlapping
214
190
prof [ev .StkID ] = rec
215
191
}
216
192
}
217
- return buildProfile (prof ). Write ( w )
193
+ return recordsOf (prof ), nil
218
194
}
219
195
220
196
// computePprofSyscall generates syscall pprof-like profile (time spent blocked in syscalls).
221
- func computePprofSyscall (w io. Writer , gToIntervals map [uint64 ][]interval , events []* trace.Event ) error {
222
- prof := make (map [uint64 ]Record )
197
+ func computePprofSyscall (gToIntervals map [uint64 ][]interval , events []* trace.Event ) ([]traceviewer. ProfileRecord , error ) {
198
+ prof := make (map [uint64 ]traceviewer. ProfileRecord )
223
199
for _ , ev := range events {
224
200
if ev .Type != trace .EvGoSysCall || ev .Link == nil || ev .StkID == 0 || len (ev .Stk ) == 0 {
225
201
continue
226
202
}
227
203
overlapping := pprofOverlappingDuration (gToIntervals , ev )
228
204
if overlapping > 0 {
229
205
rec := prof [ev .StkID ]
230
- rec .stk = ev .Stk
231
- rec .n ++
232
- rec .time += overlapping . Nanoseconds ()
206
+ rec .Stack = ev .Stk
207
+ rec .Count ++
208
+ rec .Time += overlapping
233
209
prof [ev .StkID ] = rec
234
210
}
235
211
}
236
- return buildProfile (prof ). Write ( w )
212
+ return recordsOf (prof ), nil
237
213
}
238
214
239
215
// computePprofSched generates scheduler latency pprof-like profile
240
216
// (time between a goroutine become runnable and actually scheduled for execution).
241
- func computePprofSched (w io. Writer , gToIntervals map [uint64 ][]interval , events []* trace.Event ) error {
242
- prof := make (map [uint64 ]Record )
217
+ func computePprofSched (gToIntervals map [uint64 ][]interval , events []* trace.Event ) ([]traceviewer. ProfileRecord , error ) {
218
+ prof := make (map [uint64 ]traceviewer. ProfileRecord )
243
219
for _ , ev := range events {
244
220
if (ev .Type != trace .EvGoUnblock && ev .Type != trace .EvGoCreate ) ||
245
221
ev .Link == nil || ev .StkID == 0 || len (ev .Stk ) == 0 {
@@ -248,13 +224,13 @@ func computePprofSched(w io.Writer, gToIntervals map[uint64][]interval, events [
248
224
overlapping := pprofOverlappingDuration (gToIntervals , ev )
249
225
if overlapping > 0 {
250
226
rec := prof [ev .StkID ]
251
- rec .stk = ev .Stk
252
- rec .n ++
253
- rec .time += overlapping . Nanoseconds ()
227
+ rec .Stack = ev .Stk
228
+ rec .Count ++
229
+ rec .Time += overlapping
254
230
prof [ev .StkID ] = rec
255
231
}
256
232
}
257
- return buildProfile (prof ). Write ( w )
233
+ return recordsOf (prof ), nil
258
234
}
259
235
260
236
// pprofOverlappingDuration returns the overlapping duration between
@@ -278,100 +254,10 @@ func pprofOverlappingDuration(gToIntervals map[uint64][]interval, ev *trace.Even
278
254
return overlapping
279
255
}
280
256
281
- // serveSVGProfile serves pprof-like profile generated by prof as svg.
282
- func serveSVGProfile (prof func (w io.Writer , r * http.Request ) error ) http.HandlerFunc {
283
- return func (w http.ResponseWriter , r * http.Request ) {
284
-
285
- if r .FormValue ("raw" ) != "" {
286
- w .Header ().Set ("Content-Type" , "application/octet-stream" )
287
- if err := prof (w , r ); err != nil {
288
- w .Header ().Set ("Content-Type" , "text/plain; charset=utf-8" )
289
- w .Header ().Set ("X-Go-Pprof" , "1" )
290
- http .Error (w , fmt .Sprintf ("failed to get profile: %v" , err ), http .StatusInternalServerError )
291
- return
292
- }
293
- return
294
- }
295
-
296
- blockf , err := os .CreateTemp ("" , "block" )
297
- if err != nil {
298
- http .Error (w , fmt .Sprintf ("failed to create temp file: %v" , err ), http .StatusInternalServerError )
299
- return
300
- }
301
- defer func () {
302
- blockf .Close ()
303
- os .Remove (blockf .Name ())
304
- }()
305
- blockb := bufio .NewWriter (blockf )
306
- if err := prof (blockb , r ); err != nil {
307
- http .Error (w , fmt .Sprintf ("failed to generate profile: %v" , err ), http .StatusInternalServerError )
308
- return
309
- }
310
- if err := blockb .Flush (); err != nil {
311
- http .Error (w , fmt .Sprintf ("failed to flush temp file: %v" , err ), http .StatusInternalServerError )
312
- return
313
- }
314
- if err := blockf .Close (); err != nil {
315
- http .Error (w , fmt .Sprintf ("failed to close temp file: %v" , err ), http .StatusInternalServerError )
316
- return
317
- }
318
- svgFilename := blockf .Name () + ".svg"
319
- if output , err := exec .Command (goCmd (), "tool" , "pprof" , "-svg" , "-output" , svgFilename , blockf .Name ()).CombinedOutput (); err != nil {
320
- http .Error (w , fmt .Sprintf ("failed to execute go tool pprof: %v\n %s" , err , output ), http .StatusInternalServerError )
321
- return
322
- }
323
- defer os .Remove (svgFilename )
324
- w .Header ().Set ("Content-Type" , "image/svg+xml" )
325
- http .ServeFile (w , r , svgFilename )
326
- }
327
- }
328
-
329
- func buildProfile (prof map [uint64 ]Record ) * profile.Profile {
330
- p := & profile.Profile {
331
- PeriodType : & profile.ValueType {Type : "trace" , Unit : "count" },
332
- Period : 1 ,
333
- SampleType : []* profile.ValueType {
334
- {Type : "contentions" , Unit : "count" },
335
- {Type : "delay" , Unit : "nanoseconds" },
336
- },
337
- }
338
- locs := make (map [uint64 ]* profile.Location )
339
- funcs := make (map [string ]* profile.Function )
340
- for _ , rec := range prof {
341
- var sloc []* profile.Location
342
- for _ , frame := range rec .stk {
343
- loc := locs [frame .PC ]
344
- if loc == nil {
345
- fn := funcs [frame .File + frame .Fn ]
346
- if fn == nil {
347
- fn = & profile.Function {
348
- ID : uint64 (len (p .Function ) + 1 ),
349
- Name : frame .Fn ,
350
- SystemName : frame .Fn ,
351
- Filename : frame .File ,
352
- }
353
- p .Function = append (p .Function , fn )
354
- funcs [frame .File + frame .Fn ] = fn
355
- }
356
- loc = & profile.Location {
357
- ID : uint64 (len (p .Location ) + 1 ),
358
- Address : frame .PC ,
359
- Line : []profile.Line {
360
- {
361
- Function : fn ,
362
- Line : int64 (frame .Line ),
363
- },
364
- },
365
- }
366
- p .Location = append (p .Location , loc )
367
- locs [frame .PC ] = loc
368
- }
369
- sloc = append (sloc , loc )
370
- }
371
- p .Sample = append (p .Sample , & profile.Sample {
372
- Value : []int64 {int64 (rec .n ), rec .time },
373
- Location : sloc ,
374
- })
257
+ func recordsOf (records map [uint64 ]traceviewer.ProfileRecord ) []traceviewer.ProfileRecord {
258
+ result := make ([]traceviewer.ProfileRecord , 0 , len (records ))
259
+ for _ , record := range records {
260
+ result = append (result , record )
375
261
}
376
- return p
262
+ return result
377
263
}
0 commit comments