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