Source file src/pkg/cmd/trace/pprof.go
1
2
3
4
5
6
7 package main
8
9 import (
10 "bufio"
11 "fmt"
12 "internal/trace"
13 "io"
14 "io/ioutil"
15 "net/http"
16 "os"
17 "os/exec"
18 "path/filepath"
19 "runtime"
20 "sort"
21 "strconv"
22 "time"
23
24 "github.com/google/pprof/profile"
25 )
26
27 func goCmd() string {
28 var exeSuffix string
29 if runtime.GOOS == "windows" {
30 exeSuffix = ".exe"
31 }
32 path := filepath.Join(runtime.GOROOT(), "bin", "go"+exeSuffix)
33 if _, err := os.Stat(path); err == nil {
34 return path
35 }
36 return "go"
37 }
38
39 func init() {
40 http.HandleFunc("/io", serveSVGProfile(pprofByGoroutine(computePprofIO)))
41 http.HandleFunc("/block", serveSVGProfile(pprofByGoroutine(computePprofBlock)))
42 http.HandleFunc("/syscall", serveSVGProfile(pprofByGoroutine(computePprofSyscall)))
43 http.HandleFunc("/sched", serveSVGProfile(pprofByGoroutine(computePprofSched)))
44
45 http.HandleFunc("/regionio", serveSVGProfile(pprofByRegion(computePprofIO)))
46 http.HandleFunc("/regionblock", serveSVGProfile(pprofByRegion(computePprofBlock)))
47 http.HandleFunc("/regionsyscall", serveSVGProfile(pprofByRegion(computePprofSyscall)))
48 http.HandleFunc("/regionsched", serveSVGProfile(pprofByRegion(computePprofSched)))
49 }
50
51
52 type Record struct {
53 stk []*trace.Frame
54 n uint64
55 time int64
56 }
57
58
59 type interval struct {
60 begin, end int64
61 }
62
63 func pprofByGoroutine(compute func(io.Writer, map[uint64][]interval, []*trace.Event) error) func(w io.Writer, r *http.Request) error {
64 return func(w io.Writer, r *http.Request) error {
65 id := r.FormValue("id")
66 events, err := parseEvents()
67 if err != nil {
68 return err
69 }
70 gToIntervals, err := pprofMatchingGoroutines(id, events)
71 if err != nil {
72 return err
73 }
74 return compute(w, gToIntervals, events)
75 }
76 }
77
78 func pprofByRegion(compute func(io.Writer, map[uint64][]interval, []*trace.Event) error) func(w io.Writer, r *http.Request) error {
79 return func(w io.Writer, r *http.Request) error {
80 filter, err := newRegionFilter(r)
81 if err != nil {
82 return err
83 }
84 gToIntervals, err := pprofMatchingRegions(filter)
85 if err != nil {
86 return err
87 }
88 events, _ := parseEvents()
89
90 return compute(w, gToIntervals, events)
91 }
92 }
93
94
95
96
97 func pprofMatchingGoroutines(id string, events []*trace.Event) (map[uint64][]interval, error) {
98 if id == "" {
99 return nil, nil
100 }
101 pc, err := strconv.ParseUint(id, 10, 64)
102 if err != nil {
103 return nil, fmt.Errorf("invalid goroutine type: %v", id)
104 }
105 analyzeGoroutines(events)
106 var res map[uint64][]interval
107 for _, g := range gs {
108 if g.PC != pc {
109 continue
110 }
111 if res == nil {
112 res = make(map[uint64][]interval)
113 }
114 endTime := g.EndTime
115 if g.EndTime == 0 {
116 endTime = lastTimestamp()
117 }
118 res[g.ID] = []interval{{begin: g.StartTime, end: endTime}}
119 }
120 if len(res) == 0 && id != "" {
121 return nil, fmt.Errorf("failed to find matching goroutines for id: %s", id)
122 }
123 return res, nil
124 }
125
126
127
128 func pprofMatchingRegions(filter *regionFilter) (map[uint64][]interval, error) {
129 res, err := analyzeAnnotations()
130 if err != nil {
131 return nil, err
132 }
133 if filter == nil {
134 return nil, nil
135 }
136
137 gToIntervals := make(map[uint64][]interval)
138 for id, regions := range res.regions {
139 for _, s := range regions {
140 if filter.match(id, s) {
141 gToIntervals[s.G] = append(gToIntervals[s.G], interval{begin: s.firstTimestamp(), end: s.lastTimestamp()})
142 }
143 }
144 }
145
146 for g, intervals := range gToIntervals {
147
148
149
150
151 sort.Slice(intervals, func(i, j int) bool {
152 x := intervals[i].begin
153 y := intervals[j].begin
154 if x == y {
155 return intervals[i].end < intervals[j].end
156 }
157 return x < y
158 })
159 var lastTimestamp int64
160 var n int
161
162 for _, i := range intervals {
163 if lastTimestamp <= i.begin {
164 intervals[n] = i
165 lastTimestamp = i.end
166 n++
167 }
168 }
169 gToIntervals[g] = intervals[:n]
170 }
171 return gToIntervals, nil
172 }
173
174
175 func computePprofIO(w io.Writer, gToIntervals map[uint64][]interval, events []*trace.Event) error {
176 prof := make(map[uint64]Record)
177 for _, ev := range events {
178 if ev.Type != trace.EvGoBlockNet || ev.Link == nil || ev.StkID == 0 || len(ev.Stk) == 0 {
179 continue
180 }
181 overlapping := pprofOverlappingDuration(gToIntervals, ev)
182 if overlapping > 0 {
183 rec := prof[ev.StkID]
184 rec.stk = ev.Stk
185 rec.n++
186 rec.time += overlapping.Nanoseconds()
187 prof[ev.StkID] = rec
188 }
189 }
190 return buildProfile(prof).Write(w)
191 }
192
193
194 func computePprofBlock(w io.Writer, gToIntervals map[uint64][]interval, events []*trace.Event) error {
195 prof := make(map[uint64]Record)
196 for _, ev := range events {
197 switch ev.Type {
198 case trace.EvGoBlockSend, trace.EvGoBlockRecv, trace.EvGoBlockSelect,
199 trace.EvGoBlockSync, trace.EvGoBlockCond, trace.EvGoBlockGC:
200
201
202
203 default:
204 continue
205 }
206 if ev.Link == nil || ev.StkID == 0 || len(ev.Stk) == 0 {
207 continue
208 }
209 overlapping := pprofOverlappingDuration(gToIntervals, ev)
210 if overlapping > 0 {
211 rec := prof[ev.StkID]
212 rec.stk = ev.Stk
213 rec.n++
214 rec.time += overlapping.Nanoseconds()
215 prof[ev.StkID] = rec
216 }
217 }
218 return buildProfile(prof).Write(w)
219 }
220
221
222 func computePprofSyscall(w io.Writer, gToIntervals map[uint64][]interval, events []*trace.Event) error {
223 prof := make(map[uint64]Record)
224 for _, ev := range events {
225 if ev.Type != trace.EvGoSysCall || ev.Link == nil || ev.StkID == 0 || len(ev.Stk) == 0 {
226 continue
227 }
228 overlapping := pprofOverlappingDuration(gToIntervals, ev)
229 if overlapping > 0 {
230 rec := prof[ev.StkID]
231 rec.stk = ev.Stk
232 rec.n++
233 rec.time += overlapping.Nanoseconds()
234 prof[ev.StkID] = rec
235 }
236 }
237 return buildProfile(prof).Write(w)
238 }
239
240
241
242 func computePprofSched(w io.Writer, gToIntervals map[uint64][]interval, events []*trace.Event) error {
243 prof := make(map[uint64]Record)
244 for _, ev := range events {
245 if (ev.Type != trace.EvGoUnblock && ev.Type != trace.EvGoCreate) ||
246 ev.Link == nil || ev.StkID == 0 || len(ev.Stk) == 0 {
247 continue
248 }
249 overlapping := pprofOverlappingDuration(gToIntervals, ev)
250 if overlapping > 0 {
251 rec := prof[ev.StkID]
252 rec.stk = ev.Stk
253 rec.n++
254 rec.time += overlapping.Nanoseconds()
255 prof[ev.StkID] = rec
256 }
257 }
258 return buildProfile(prof).Write(w)
259 }
260
261
262
263
264 func pprofOverlappingDuration(gToIntervals map[uint64][]interval, ev *trace.Event) time.Duration {
265 if gToIntervals == nil {
266 return time.Duration(ev.Link.Ts-ev.Ts) * time.Nanosecond
267 }
268 intervals := gToIntervals[ev.G]
269 if len(intervals) == 0 {
270 return 0
271 }
272
273 var overlapping time.Duration
274 for _, i := range intervals {
275 if o := overlappingDuration(i.begin, i.end, ev.Ts, ev.Link.Ts); o > 0 {
276 overlapping += o
277 }
278 }
279 return overlapping
280 }
281
282
283 func serveSVGProfile(prof func(w io.Writer, r *http.Request) error) http.HandlerFunc {
284 return func(w http.ResponseWriter, r *http.Request) {
285
286 if r.FormValue("raw") != "" {
287 w.Header().Set("Content-Type", "application/octet-stream")
288 if err := prof(w, r); err != nil {
289 w.Header().Set("Content-Type", "text/plain; charset=utf-8")
290 w.Header().Set("X-Go-Pprof", "1")
291 http.Error(w, fmt.Sprintf("failed to get profile: %v", err), http.StatusInternalServerError)
292 return
293 }
294 return
295 }
296
297 blockf, err := ioutil.TempFile("", "block")
298 if err != nil {
299 http.Error(w, fmt.Sprintf("failed to create temp file: %v", err), http.StatusInternalServerError)
300 return
301 }
302 defer func() {
303 blockf.Close()
304 os.Remove(blockf.Name())
305 }()
306 blockb := bufio.NewWriter(blockf)
307 if err := prof(blockb, r); err != nil {
308 http.Error(w, fmt.Sprintf("failed to generate profile: %v", err), http.StatusInternalServerError)
309 return
310 }
311 if err := blockb.Flush(); err != nil {
312 http.Error(w, fmt.Sprintf("failed to flush temp file: %v", err), http.StatusInternalServerError)
313 return
314 }
315 if err := blockf.Close(); err != nil {
316 http.Error(w, fmt.Sprintf("failed to close temp file: %v", err), http.StatusInternalServerError)
317 return
318 }
319 svgFilename := blockf.Name() + ".svg"
320 if output, err := exec.Command(goCmd(), "tool", "pprof", "-svg", "-output", svgFilename, blockf.Name()).CombinedOutput(); err != nil {
321 http.Error(w, fmt.Sprintf("failed to execute go tool pprof: %v\n%s", err, output), http.StatusInternalServerError)
322 return
323 }
324 defer os.Remove(svgFilename)
325 w.Header().Set("Content-Type", "image/svg+xml")
326 http.ServeFile(w, r, svgFilename)
327 }
328 }
329
330 func buildProfile(prof map[uint64]Record) *profile.Profile {
331 p := &profile.Profile{
332 PeriodType: &profile.ValueType{Type: "trace", Unit: "count"},
333 Period: 1,
334 SampleType: []*profile.ValueType{
335 {Type: "contentions", Unit: "count"},
336 {Type: "delay", Unit: "nanoseconds"},
337 },
338 }
339 locs := make(map[uint64]*profile.Location)
340 funcs := make(map[string]*profile.Function)
341 for _, rec := range prof {
342 var sloc []*profile.Location
343 for _, frame := range rec.stk {
344 loc := locs[frame.PC]
345 if loc == nil {
346 fn := funcs[frame.File+frame.Fn]
347 if fn == nil {
348 fn = &profile.Function{
349 ID: uint64(len(p.Function) + 1),
350 Name: frame.Fn,
351 SystemName: frame.Fn,
352 Filename: frame.File,
353 }
354 p.Function = append(p.Function, fn)
355 funcs[frame.File+frame.Fn] = fn
356 }
357 loc = &profile.Location{
358 ID: uint64(len(p.Location) + 1),
359 Address: frame.PC,
360 Line: []profile.Line{
361 {
362 Function: fn,
363 Line: int64(frame.Line),
364 },
365 },
366 }
367 p.Location = append(p.Location, loc)
368 locs[frame.PC] = loc
369 }
370 sloc = append(sloc, loc)
371 }
372 p.Sample = append(p.Sample, &profile.Sample{
373 Value: []int64{int64(rec.n), rec.time},
374 Location: sloc,
375 })
376 }
377 return p
378 }
379
View as plain text