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) (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
237 return addr, nil
238 }
239
240 func (f *file) BuildID() string {
241
242 return ""
243 }
244
245 func (f *file) SourceLine(addr uint64) ([]driver.Frame, error) {
246 if f.pcln == nil {
247 pcln, err := f.file.PCLineTable()
248 if err != nil {
249 return nil, err
250 }
251 f.pcln = pcln
252 }
253 addr -= f.offset
254 file, line, fn := f.pcln.PCToLine(addr)
255 if fn != nil {
256 frame := []driver.Frame{
257 {
258 Func: fn.Name,
259 File: file,
260 Line: line,
261 },
262 }
263 return frame, nil
264 }
265
266 frames := f.dwarfSourceLine(addr)
267 if frames != nil {
268 return frames, nil
269 }
270
271 return nil, fmt.Errorf("no line information for PC=%#x", addr)
272 }
273
274
275
276
277 func (f *file) dwarfSourceLine(addr uint64) []driver.Frame {
278 if f.dwarf == nil && !f.triedDwarf {
279
280
281 f.dwarf, _ = f.file.DWARF()
282 f.triedDwarf = true
283 }
284
285 if f.dwarf != nil {
286 r := f.dwarf.Reader()
287 unit, err := r.SeekPC(addr)
288 if err == nil {
289 if frames := f.dwarfSourceLineEntry(r, unit, addr); frames != nil {
290 return frames
291 }
292 }
293 }
294
295 return nil
296 }
297
298
299
300 func (f *file) dwarfSourceLineEntry(r *dwarf.Reader, entry *dwarf.Entry, addr uint64) []driver.Frame {
301 lines, err := f.dwarf.LineReader(entry)
302 if err != nil {
303 return nil
304 }
305 var lentry dwarf.LineEntry
306 if err := lines.SeekPC(addr, &lentry); err != nil {
307 return nil
308 }
309
310
311 name := ""
312 FindName:
313 for entry, err := r.Next(); entry != nil && err == nil; entry, err = r.Next() {
314 if entry.Tag == dwarf.TagSubprogram {
315 ranges, err := f.dwarf.Ranges(entry)
316 if err != nil {
317 return nil
318 }
319 for _, pcs := range ranges {
320 if pcs[0] <= addr && addr < pcs[1] {
321 var ok bool
322
323 name, ok = entry.Val(dwarf.AttrName).(string)
324 if ok {
325 break FindName
326 }
327 }
328 }
329 }
330 }
331
332
333
334 frames := []driver.Frame{
335 {
336 Func: name,
337 File: lentry.File.Name,
338 Line: lentry.Line,
339 },
340 }
341
342 return frames
343 }
344
345 func (f *file) Symbols(r *regexp.Regexp, addr uint64) ([]*driver.Sym, error) {
346 if f.sym == nil {
347 sym, err := f.file.Symbols()
348 if err != nil {
349 return nil, err
350 }
351 f.sym = sym
352 }
353 var out []*driver.Sym
354 for _, s := range f.sym {
355
356
357 if s.Addr == 0 && s.Size == 0 {
358 continue
359 }
360 if (r == nil || r.MatchString(s.Name)) && (addr == 0 || s.Addr <= addr && addr < s.Addr+uint64(s.Size)) {
361 out = append(out, &driver.Sym{
362 Name: []string{s.Name},
363 File: f.name,
364 Start: s.Addr,
365 End: s.Addr + uint64(s.Size) - 1,
366 })
367 }
368 }
369 return out, nil
370 }
371
372 func (f *file) Close() error {
373 f.file.Close()
374 return nil
375 }
376
377
378
379 var newUI = func() driver.UI { return nil }
380
View as plain text