Skip to content

Commit b839348

Browse files
committed
cmd/trace: refactor pprof HTTP SVG serving into traceviewer
For #60773. For #63960. Change-Id: Id97380f19267ec765b25a703ea3e2f284396ad75 Reviewed-on: https://go-review.googlesource.com/c/go/+/541998 Auto-Submit: Michael Knyszek <[email protected]> LUCI-TryBot-Result: Go LUCI <[email protected]> Reviewed-by: Michael Pratt <[email protected]>
1 parent ff07b73 commit b839348

File tree

4 files changed

+207
-167
lines changed

4 files changed

+207
-167
lines changed

src/cmd/trace/main.go

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ import (
1212
"fmt"
1313
"internal/trace"
1414
"internal/trace/traceviewer"
15-
"io"
1615
"log"
1716
"net"
1817
"net/http"
@@ -91,7 +90,7 @@ func main() {
9190
return
9291
}
9392

94-
var pprofFunc func(io.Writer, *http.Request) error
93+
var pprofFunc traceviewer.ProfileFunc
9594
switch *pprofFlag {
9695
case "net":
9796
pprofFunc = pprofByGoroutine(computePprofIO)
@@ -103,7 +102,11 @@ func main() {
103102
pprofFunc = pprofByGoroutine(computePprofSched)
104103
}
105104
if pprofFunc != nil {
106-
if err := pprofFunc(os.Stdout, &http.Request{}); err != nil {
105+
records, err := pprofFunc(&http.Request{})
106+
if err != nil {
107+
dief("failed to generate pprof: %v\n", err)
108+
}
109+
if err := traceviewer.BuildProfile(records).Write(os.Stdout); err != nil {
107110
dief("failed to generate pprof: %v\n", err)
108111
}
109112
os.Exit(0)

src/cmd/trace/pprof.go

Lines changed: 50 additions & 164 deletions
Original file line numberDiff line numberDiff line change
@@ -7,86 +7,60 @@
77
package main
88

99
import (
10-
"bufio"
1110
"fmt"
1211
"internal/trace"
13-
"io"
12+
"internal/trace/traceviewer"
1413
"net/http"
15-
"os"
16-
"os/exec"
17-
"path/filepath"
18-
"runtime"
1914
"sort"
2015
"strconv"
2116
"time"
22-
23-
"github.com/google/pprof/profile"
2417
)
2518

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-
3819
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)))
4924

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)))
5529
}
5630

5731
// interval represents a time interval in the trace.
5832
type interval struct {
5933
begin, end int64 // nanoseconds.
6034
}
6135

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) {
6438
id := r.FormValue("id")
6539
events, err := parseEvents()
6640
if err != nil {
67-
return err
41+
return nil, err
6842
}
6943
gToIntervals, err := pprofMatchingGoroutines(id, events)
7044
if err != nil {
71-
return err
45+
return nil, err
7246
}
73-
return compute(w, gToIntervals, events)
47+
return compute(gToIntervals, events)
7448
}
7549
}
7650

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) {
7953
filter, err := newRegionFilter(r)
8054
if err != nil {
81-
return err
55+
return nil, err
8256
}
8357
gToIntervals, err := pprofMatchingRegions(filter)
8458
if err != nil {
85-
return err
59+
return nil, err
8660
}
8761
events, _ := parseEvents()
8862

89-
return compute(w, gToIntervals, events)
63+
return compute(gToIntervals, events)
9064
}
9165
}
9266

@@ -170,28 +144,30 @@ func pprofMatchingRegions(filter *regionFilter) (map[uint64][]interval, error) {
170144
return gToIntervals, nil
171145
}
172146

147+
type computePprofFunc func(gToIntervals map[uint64][]interval, events []*trace.Event) ([]traceviewer.ProfileRecord, error)
148+
173149
// 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)
176152
for _, ev := range events {
177153
if ev.Type != trace.EvGoBlockNet || ev.Link == nil || ev.StkID == 0 || len(ev.Stk) == 0 {
178154
continue
179155
}
180156
overlapping := pprofOverlappingDuration(gToIntervals, ev)
181157
if overlapping > 0 {
182158
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
186162
prof[ev.StkID] = rec
187163
}
188164
}
189-
return buildProfile(prof).Write(w)
165+
return recordsOf(prof), nil
190166
}
191167

192168
// 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)
195171
for _, ev := range events {
196172
switch ev.Type {
197173
case trace.EvGoBlockSend, trace.EvGoBlockRecv, trace.EvGoBlockSelect,
@@ -208,38 +184,38 @@ func computePprofBlock(w io.Writer, gToIntervals map[uint64][]interval, events [
208184
overlapping := pprofOverlappingDuration(gToIntervals, ev)
209185
if overlapping > 0 {
210186
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
214190
prof[ev.StkID] = rec
215191
}
216192
}
217-
return buildProfile(prof).Write(w)
193+
return recordsOf(prof), nil
218194
}
219195

220196
// 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)
223199
for _, ev := range events {
224200
if ev.Type != trace.EvGoSysCall || ev.Link == nil || ev.StkID == 0 || len(ev.Stk) == 0 {
225201
continue
226202
}
227203
overlapping := pprofOverlappingDuration(gToIntervals, ev)
228204
if overlapping > 0 {
229205
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
233209
prof[ev.StkID] = rec
234210
}
235211
}
236-
return buildProfile(prof).Write(w)
212+
return recordsOf(prof), nil
237213
}
238214

239215
// computePprofSched generates scheduler latency pprof-like profile
240216
// (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)
243219
for _, ev := range events {
244220
if (ev.Type != trace.EvGoUnblock && ev.Type != trace.EvGoCreate) ||
245221
ev.Link == nil || ev.StkID == 0 || len(ev.Stk) == 0 {
@@ -248,13 +224,13 @@ func computePprofSched(w io.Writer, gToIntervals map[uint64][]interval, events [
248224
overlapping := pprofOverlappingDuration(gToIntervals, ev)
249225
if overlapping > 0 {
250226
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
254230
prof[ev.StkID] = rec
255231
}
256232
}
257-
return buildProfile(prof).Write(w)
233+
return recordsOf(prof), nil
258234
}
259235

260236
// pprofOverlappingDuration returns the overlapping duration between
@@ -278,100 +254,10 @@ func pprofOverlappingDuration(gToIntervals map[uint64][]interval, ev *trace.Even
278254
return overlapping
279255
}
280256

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)
375261
}
376-
return p
262+
return result
377263
}

src/go/build/deps_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -639,6 +639,7 @@ var depsRules = `
639639
embed,
640640
encoding/json,
641641
html/template,
642+
internal/profile,
642643
internal/trace,
643644
internal/trace/traceviewer/format,
644645
net/http

0 commit comments

Comments
 (0)