1
2
3
4
5
6
7 package traceviewer
8
9 import (
10 "bufio"
11 "fmt"
12 "internal/profile"
13 "internal/trace"
14 "net/http"
15 "os"
16 "os/exec"
17 "path/filepath"
18 "runtime"
19 "time"
20 )
21
22 type ProfileFunc func(r *http.Request) ([]ProfileRecord, error)
23
24
25 func SVGProfileHandlerFunc(f ProfileFunc) http.HandlerFunc {
26 return func(w http.ResponseWriter, r *http.Request) {
27 if r.FormValue("raw") != "" {
28 w.Header().Set("Content-Type", "application/octet-stream")
29
30 failf := func(s string, args ...any) {
31 w.Header().Set("Content-Type", "text/plain; charset=utf-8")
32 w.Header().Set("X-Go-Pprof", "1")
33 http.Error(w, fmt.Sprintf(s, args...), http.StatusInternalServerError)
34 }
35 records, err := f(r)
36 if err != nil {
37 failf("failed to get records: %v", err)
38 return
39 }
40 if err := BuildProfile(records).Write(w); err != nil {
41 failf("failed to write profile: %v", err)
42 return
43 }
44 return
45 }
46
47 blockf, err := os.CreateTemp("", "block")
48 if err != nil {
49 http.Error(w, fmt.Sprintf("failed to create temp file: %v", err), http.StatusInternalServerError)
50 return
51 }
52 defer func() {
53 blockf.Close()
54 os.Remove(blockf.Name())
55 }()
56 records, err := f(r)
57 if err != nil {
58 http.Error(w, fmt.Sprintf("failed to generate profile: %v", err), http.StatusInternalServerError)
59 return
60 }
61 blockb := bufio.NewWriter(blockf)
62 if err := BuildProfile(records).Write(blockb); err != nil {
63 http.Error(w, fmt.Sprintf("failed to write profile: %v", err), http.StatusInternalServerError)
64 return
65 }
66 if err := blockb.Flush(); err != nil {
67 http.Error(w, fmt.Sprintf("failed to flush temp file: %v", err), http.StatusInternalServerError)
68 return
69 }
70 if err := blockf.Close(); err != nil {
71 http.Error(w, fmt.Sprintf("failed to close temp file: %v", err), http.StatusInternalServerError)
72 return
73 }
74 svgFilename := blockf.Name() + ".svg"
75 if output, err := exec.Command(goCmd(), "tool", "pprof", "-svg", "-output", svgFilename, blockf.Name()).CombinedOutput(); err != nil {
76 http.Error(w, fmt.Sprintf("failed to execute go tool pprof: %v\n%s", err, output), http.StatusInternalServerError)
77 return
78 }
79 defer os.Remove(svgFilename)
80 w.Header().Set("Content-Type", "image/svg+xml")
81 http.ServeFile(w, r, svgFilename)
82 }
83 }
84
85 type ProfileRecord struct {
86 Stack []trace.StackFrame
87 Count uint64
88 Time time.Duration
89 }
90
91 func BuildProfile(prof []ProfileRecord) *profile.Profile {
92 p := &profile.Profile{
93 PeriodType: &profile.ValueType{Type: "trace", Unit: "count"},
94 Period: 1,
95 SampleType: []*profile.ValueType{
96 {Type: "contentions", Unit: "count"},
97 {Type: "delay", Unit: "nanoseconds"},
98 },
99 }
100 locs := make(map[uint64]*profile.Location)
101 funcs := make(map[string]*profile.Function)
102 for _, rec := range prof {
103 var sloc []*profile.Location
104 for _, frame := range rec.Stack {
105 loc := locs[frame.PC]
106 if loc == nil {
107 fn := funcs[frame.File+frame.Func]
108 if fn == nil {
109 fn = &profile.Function{
110 ID: uint64(len(p.Function) + 1),
111 Name: frame.Func,
112 SystemName: frame.Func,
113 Filename: frame.File,
114 }
115 p.Function = append(p.Function, fn)
116 funcs[frame.File+frame.Func] = fn
117 }
118 loc = &profile.Location{
119 ID: uint64(len(p.Location) + 1),
120 Address: frame.PC,
121 Line: []profile.Line{
122 {
123 Function: fn,
124 Line: int64(frame.Line),
125 },
126 },
127 }
128 p.Location = append(p.Location, loc)
129 locs[frame.PC] = loc
130 }
131 sloc = append(sloc, loc)
132 }
133 p.Sample = append(p.Sample, &profile.Sample{
134 Value: []int64{int64(rec.Count), int64(rec.Time)},
135 Location: sloc,
136 })
137 }
138 return p
139 }
140
141 func goCmd() string {
142 var exeSuffix string
143 if runtime.GOOS == "windows" {
144 exeSuffix = ".exe"
145 }
146 path := filepath.Join(runtime.GOROOT(), "bin", "go"+exeSuffix)
147 if _, err := os.Stat(path); err == nil {
148 return path
149 }
150 return "go"
151 }
152
View as plain text