Source file
src/cmd/pprof/pprof.go
1
2
3
4
5
6
7
8
9
10 package main
11
12 import (
13 "crypto/tls"
14 "debug/dwarf"
15 "fmt"
16 "io"
17 "net/http"
18 "net/url"
19 "os"
20 "regexp"
21 "strconv"
22 "strings"
23 "sync"
24 "time"
25
26 "cmd/internal/objfile"
27
28 "github.com/google/pprof/driver"
29 "github.com/google/pprof/profile"
30 )
31
32 func main() {
33 options := &driver.Options{
34 Fetch: new(fetcher),
35 Obj: new(objTool),
36 UI: newUI(),
37 }
38 if err := driver.PProf(options); err != nil {
39 fmt.Fprintf(os.Stderr, "%v\n", err)
40 os.Exit(2)
41 }
42 }
43
44 type fetcher struct {
45 }
46
47 func (f *fetcher) Fetch(src string, duration, timeout time.Duration) (*profile.Profile, string, error) {
48 sourceURL, timeout := adjustURL(src, duration, timeout)
49 if sourceURL == "" {
50
51 return nil, "", nil
52 }
53 fmt.Fprintln(os.Stderr, "Fetching profile over HTTP from", sourceURL)
54 if duration > 0 {
55 fmt.Fprintf(os.Stderr, "Please wait... (%v)\n", duration)
56 }
57 p, err := getProfile(sourceURL, timeout)
58 return p, sourceURL, err
59 }
60
61 func getProfile(source string, timeout time.Duration) (*profile.Profile, error) {
62 url, err := url.Parse(source)
63 if err != nil {
64 return nil, err
65 }
66
67 var tlsConfig *tls.Config
68 if url.Scheme == "https+insecure" {
69 tlsConfig = &tls.Config{
70 InsecureSkipVerify: true,
71 }
72 url.Scheme = "https"
73 source = url.String()
74 }
75
76 client := &http.Client{
77 Transport: &http.Transport{
78 ResponseHeaderTimeout: timeout + 5*time.Second,
79 Proxy: http.ProxyFromEnvironment,
80 TLSClientConfig: tlsConfig,
81 },
82 }
83 resp, err := client.Get(source)
84 if err != nil {
85 return nil, err
86 }
87 if resp.StatusCode != http.StatusOK {
88 defer resp.Body.Close()
89 return nil, statusCodeError(resp)
90 }
91 return profile.Parse(resp.Body)
92 }
93
94 func statusCodeError(resp *http.Response) error {
95 if resp.Header.Get("X-Go-Pprof") != "" && strings.Contains(resp.Header.Get("Content-Type"), "text/plain") {
96
97 if body, err := io.ReadAll(resp.Body); err == nil {
98 return fmt.Errorf("server response: %s - %s", resp.Status, body)
99 }
100 }
101 return fmt.Errorf("server response: %s", resp.Status)
102 }
103
104
105 const cpuProfileHandler = "/debug/pprof/profile"
106
107
108 func adjustURL(source string, duration, timeout time.Duration) (string, time.Duration) {
109 u, err := url.Parse(source)
110 if err != nil || (u.Host == "" && u.Scheme != "" && u.Scheme != "file") {
111
112
113 u, err = url.Parse("http://" + source)
114 }
115 if err != nil || u.Host == "" {
116 return "", 0
117 }
118
119 if u.Path == "" || u.Path == "/" {
120 u.Path = cpuProfileHandler
121 }
122
123
124 values := u.Query()
125 if duration > 0 {
126 values.Set("seconds", fmt.Sprint(int(duration.Seconds())))
127 } else {
128 if urlSeconds := values.Get("seconds"); urlSeconds != "" {
129 if us, err := strconv.ParseInt(urlSeconds, 10, 32); err == nil {
130 duration = time.Duration(us) * time.Second
131 }
132 }
133 }
134 if timeout <= 0 {
135 if duration > 0 {
136 timeout = duration + duration/2
137 } else {
138 timeout = 60 * time.Second
139 }
140 }
141 u.RawQuery = values.Encode()
142 return u.String(), timeout
143 }
144
145
146
147 type objTool struct {
148 mu sync.Mutex
149 disasmCache map[string]*objfile.Disasm
150 }
151
152 func (*objTool) Open(name string, start, limit, offset uint64, relocationSymbol string) (driver.ObjFile, error) {
153 of, err := objfile.Open(name)
154 if err != nil {
155 return nil, err
156 }
157 f := &file{
158 name: name,
159 file: of,
160 }
161 if start != 0 {
162 if load, err := of.LoadAddress(); err == nil {
163 f.offset = start - load
164 }
165 }
166 return f, nil
167 }
168
169 func (*objTool) Demangle(names []string) (map[string]string, error) {
170
171 return make(map[string]string), nil
172 }
173
174 func (t *objTool) Disasm(file string, start, end uint64, intelSyntax bool) ([]driver.Inst, error) {
175 if intelSyntax {
176 return nil, fmt.Errorf("printing assembly in Intel syntax is not supported")
177 }
178 d, err := t.cachedDisasm(file)
179 if err != nil {
180 return nil, err
181 }
182 var asm []driver.Inst
183 d.Decode(start, end, nil, false, func(pc, size uint64, file string, line int, text string) {
184 asm = append(asm, driver.Inst{Addr: pc, File: file, Line: line, Text: text})
185 })
186 return asm, nil
187 }
188
189 func (t *objTool) cachedDisasm(file string) (*objfile.Disasm, error) {
190 t.mu.Lock()
191 defer t.mu.Unlock()
192 if t.disasmCache == nil {
193 t.disasmCache = make(map[string]*objfile.Disasm)
194 }
195 d := t.disasmCache[file]
196 if d != nil {
197 return d, nil
198 }
199 f, err := objfile.Open(file)
200 if err != nil {
201 return nil, err
202 }
203 d, err = f.Disasm()
204 f.Close()
205 if err != nil {
206 return nil, err
207 }
208 t.disasmCache[file] = d
209 return d, nil
210 }
211
212 func (*objTool) SetConfig(config string) {
213
214
215 }
216
217
218
219
220 type file struct {
221 name string
222 offset uint64
223 sym []objfile.Sym
224 file *objfile.File
225 pcln objfile.Liner
226
227 triedDwarf bool
228 dwarf *dwarf.Data
229 }
230
231 func (f *file) Name() string {
232 return f.name
233 }
234
235 func (f *file) ObjAddr(addr uint64) (uint64, error) {
236 return addr - f.offset, nil
237 }
238
239 func (f *file) BuildID() string {
240
241 return ""
242 }
243
244 func (f *file) SourceLine(addr uint64) ([]driver.Frame, error) {
245 if f.pcln == nil {
246 pcln, err := f.file.PCLineTable()
247 if err != nil {
248 return nil, err
249 }
250 f.pcln = pcln
251 }
252 addr -= f.offset
253 file, line, fn := f.pcln.PCToLine(addr)
254 if fn != nil {
255 frame := []driver.Frame{
256 {
257 Func: fn.Name,
258 File: file,
259 Line: line,
260 },
261 }
262 return frame, nil
263 }
264
265 frames := f.dwarfSourceLine(addr)
266 if frames != nil {
267 return frames, nil
268 }
269
270 return nil, fmt.Errorf("no line information for PC=%#x", addr)
271 }
272
273
274
275
276 func (f *file) dwarfSourceLine(addr uint64) []driver.Frame {
277 if f.dwarf == nil && !f.triedDwarf {
278
279
280 f.dwarf, _ = f.file.DWARF()
281 f.triedDwarf = true
282 }
283
284 if f.dwarf != nil {
285 r := f.dwarf.Reader()
286 unit, err := r.SeekPC(addr)
287 if err == nil {
288 if frames := f.dwarfSourceLineEntry(r, unit, addr); frames != nil {
289 return frames
290 }
291 }
292 }
293
294 return nil
295 }
296
297
298
299 func (f *file) dwarfSourceLineEntry(r *dwarf.Reader, entry *dwarf.Entry, addr uint64) []driver.Frame {
300 lines, err := f.dwarf.LineReader(entry)
301 if err != nil {
302 return nil
303 }
304 var lentry dwarf.LineEntry
305 if err := lines.SeekPC(addr, &lentry); err != nil {
306 return nil
307 }
308
309
310 name := ""
311 FindName:
312 for entry, err := r.Next(); entry != nil && err == nil; entry, err = r.Next() {
313 if entry.Tag == dwarf.TagSubprogram {
314 ranges, err := f.dwarf.Ranges(entry)
315 if err != nil {
316 return nil
317 }
318 for _, pcs := range ranges {
319 if pcs[0] <= addr && addr < pcs[1] {
320 var ok bool
321
322 name, ok = entry.Val(dwarf.AttrName).(string)
323 if ok {
324 break FindName
325 }
326 }
327 }
328 }
329 }
330
331
332
333 frames := []driver.Frame{
334 {
335 Func: name,
336 File: lentry.File.Name,
337 Line: lentry.Line,
338 },
339 }
340
341 return frames
342 }
343
344 func (f *file) Symbols(r *regexp.Regexp, addr uint64) ([]*driver.Sym, error) {
345 if f.sym == nil {
346 sym, err := f.file.Symbols()
347 if err != nil {
348 return nil, err
349 }
350 f.sym = sym
351 }
352 var out []*driver.Sym
353 for _, s := range f.sym {
354
355
356 if s.Addr == 0 && s.Size == 0 {
357 continue
358 }
359 if (r == nil || r.MatchString(s.Name)) && (addr == 0 || s.Addr <= addr && addr < s.Addr+uint64(s.Size)) {
360 out = append(out, &driver.Sym{
361 Name: []string{s.Name},
362 File: f.name,
363 Start: s.Addr,
364 End: s.Addr + uint64(s.Size) - 1,
365 })
366 }
367 }
368 return out, nil
369 }
370
371 func (f *file) Close() error {
372 f.file.Close()
373 return nil
374 }
375
376
377
378 var newUI = func() driver.UI { return nil }
379
View as plain text