Source file
src/cmd/trace/pprof.go
1
2
3
4
5
6
7 package main
8
9 import (
10 "bufio"
11 "fmt"
12 exec "internal/execabs"
13 "internal/trace"
14 "io"
15 "net/http"
16 "os"
17 "path/filepath"
18 "runtime"
19 "sort"
20 "strconv"
21 "time"
22
23 "github.com/google/pprof/profile"
24 )
25
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 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 }
49
50
51 type Record struct {
52 stk []*trace.Frame
53 n uint64
54 time int64
55 }
56
57
58 type interval struct {
59 begin, end int64
60 }
61
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 {
64 id := r.FormValue("id")
65 events, err := parseEvents()
66 if err != nil {
67 return err
68 }
69 gToIntervals, err := pprofMatchingGoroutines(id, events)
70 if err != nil {
71 return err
72 }
73 return compute(w, gToIntervals, events)
74 }
75 }
76
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 {
79 filter, err := newRegionFilter(r)
80 if err != nil {
81 return err
82 }
83 gToIntervals, err := pprofMatchingRegions(filter)
84 if err != nil {
85 return err
86 }
87 events, _ := parseEvents()
88
89 return compute(w, gToIntervals, events)
90 }
91 }
92
93
94
95
96 func pprofMatchingGoroutines(id string, events []*trace.Event) (map[uint64][]interval, error) {
97 if id == "" {
98 return nil, nil
99 }
100 pc, err := strconv.ParseUint(id, 10, 64)
101 if err != nil {
102 return nil, fmt.Errorf("invalid goroutine type: %v", id)
103 }
104 analyzeGoroutines(events)
105 var res map[uint64][]interval
106 for _, g := range gs {
107 if g.PC != pc {
108 continue
109 }
110 if res == nil {
111 res = make(map[uint64][]interval)
112 }
113 endTime := g.EndTime
114 if g.EndTime == 0 {
115 endTime = lastTimestamp()
116 }
117 res[g.ID] = []interval{{begin: g.StartTime, end: endTime}}
118 }
119 if len(res) == 0 && id != "" {
120 return nil, fmt.Errorf("failed to find matching goroutines for id: %s", id)
121 }
122 return res, nil
123 }
124
125
126
127 func pprofMatchingRegions(filter *regionFilter) (map[uint64][]interval, error) {
128 res, err := analyzeAnnotations()
129 if err != nil {
130 return nil, err
131 }
132 if filter == nil {
133 return nil, nil
134 }
135
136 gToIntervals := make(map[uint64][]interval)
137 for id, regions := range res.regions {
138 for _, s := range regions {
139 if filter.match(id, s) {
140 gToIntervals[s.G] = append(gToIntervals[s.G], interval{begin: s.firstTimestamp(), end: s.lastTimestamp()})
141 }
142 }
143 }
144
145 for g, intervals := range gToIntervals {
146
147
148
149
150 sort.Slice(intervals, func(i, j int) bool {
151 x := intervals[i].begin
152 y := intervals[j].begin
153 if x == y {
154 return intervals[i].end < intervals[j].end
155 }
156 return x < y
157 })
158 var lastTimestamp int64
159 var n int
160
161 for _, i := range intervals {
162 if lastTimestamp <= i.begin {
163 intervals[n] = i
164 lastTimestamp = i.end
165 n++
166 }
167 }
168 gToIntervals[g] = intervals[:n]
169 }
170 return gToIntervals, nil
171 }
172
173
174 func computePprofIO(w io.Writer, gToIntervals map[uint64][]interval, events []*trace.Event) error {
175 prof := make(map[uint64]Record)
176 for _, ev := range events {
177 if ev.Type != trace.EvGoBlockNet || ev.Link == nil || ev.StkID == 0 || len(ev.Stk) == 0 {
178 continue
179 }
180 overlapping := pprofOverlappingDuration(gToIntervals, ev)
181 if overlapping > 0 {
182 rec := prof[ev.StkID]
183 rec.stk = ev.Stk
184 rec.n++
185 rec.time += overlapping.Nanoseconds()
186 prof[ev.StkID] = rec
187 }
188 }
189 return buildProfile(prof).Write(w)
190 }
191
192
193 func computePprofBlock(w io.Writer, gToIntervals map[uint64][]interval, events []*trace.Event) error {
194 prof := make(map[uint64]Record)
195 for _, ev := range events {
196 switch ev.Type {
197 case trace.EvGoBlockSend, trace.EvGoBlockRecv, trace.EvGoBlockSelect,
198 trace.EvGoBlockSync, trace.EvGoBlockCond, trace.EvGoBlockGC:
199
200
201
202 default:
203 continue
204 }
205 if ev.Link == nil || ev.StkID == 0 || len(ev.Stk) == 0 {
206 continue
207 }
208 overlapping := pprofOverlappingDuration(gToIntervals, ev)
209 if overlapping > 0 {
210 rec := prof[ev.StkID]
211 rec.stk = ev.Stk
212 rec.n++
213 rec.time += overlapping.Nanoseconds()
214 prof[ev.StkID] = rec
215 }
216 }
217 return buildProfile(prof).Write(w)
218 }
219
220
221 func computePprofSyscall(w io.Writer, gToIntervals map[uint64][]interval, events []*trace.Event) error {
222 prof := make(map[uint64]Record)
223 for _, ev := range events {
224 if ev.Type != trace.EvGoSysCall || ev.Link == nil || ev.StkID == 0 || len(ev.Stk) == 0 {
225 continue
226 }
227 overlapping := pprofOverlappingDuration(gToIntervals, ev)
228 if overlapping > 0 {
229 rec := prof[ev.StkID]
230 rec.stk = ev.Stk
231 rec.n++
232 rec.time += overlapping.Nanoseconds()
233 prof[ev.StkID] = rec
234 }
235 }
236 return buildProfile(prof).Write(w)
237 }
238
239
240
241 func computePprofSched(w io.Writer, gToIntervals map[uint64][]interval, events []*trace.Event) error {
242 prof := make(map[uint64]Record)
243 for _, ev := range events {
244 if (ev.Type != trace.EvGoUnblock && ev.Type != trace.EvGoCreate) ||
245 ev.Link == nil || ev.StkID == 0 || len(ev.Stk) == 0 {
246 continue
247 }
248 overlapping := pprofOverlappingDuration(gToIntervals, ev)
249 if overlapping > 0 {
250 rec := prof[ev.StkID]
251 rec.stk = ev.Stk
252 rec.n++
253 rec.time += overlapping.Nanoseconds()
254 prof[ev.StkID] = rec
255 }
256 }
257 return buildProfile(prof).Write(w)
258 }
259
260
261
262
263 func pprofOverlappingDuration(gToIntervals map[uint64][]interval, ev *trace.Event) time.Duration {
264 if gToIntervals == nil {
265 return time.Duration(ev.Link.Ts-ev.Ts) * time.Nanosecond
266 }
267 intervals := gToIntervals[ev.G]
268 if len(intervals) == 0 {
269 return 0
270 }
271
272 var overlapping time.Duration
273 for _, i := range intervals {
274 if o := overlappingDuration(i.begin, i.end, ev.Ts, ev.Link.Ts); o > 0 {
275 overlapping += o
276 }
277 }
278 return overlapping
279 }
280
281
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 })
375 }
376 return p
377 }
378
View as plain text