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/ioutil"
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 := ioutil.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) ([]driver.Inst, error) {
175 d, err := t.cachedDisasm(file)
176 if err != nil {
177 return nil, err
178 }
179 var asm []driver.Inst
180 d.Decode(start, end, nil, func(pc, size uint64, file string, line int, text string) {
181 asm = append(asm, driver.Inst{Addr: pc, File: file, Line: line, Text: text})
182 })
183 return asm, nil
184 }
185
186 func (t *objTool) cachedDisasm(file string) (*objfile.Disasm, error) {
187 t.mu.Lock()
188 defer t.mu.Unlock()
189 if t.disasmCache == nil {
190 t.disasmCache = make(map[string]*objfile.Disasm)
191 }
192 d := t.disasmCache[file]
193 if d != nil {
194 return d, nil
195 }
196 f, err := objfile.Open(file)
197 if err != nil {
198 return nil, err
199 }
200 d, err = f.Disasm()
201 f.Close()
202 if err != nil {
203 return nil, err
204 }
205 t.disasmCache[file] = d
206 return d, nil
207 }
208
209 func (*objTool) SetConfig(config string) {
210
211
212 }
213
214
215
216
217 type file struct {
218 name string
219 offset uint64
220 sym []objfile.Sym
221 file *objfile.File
222 pcln objfile.Liner
223
224 triedDwarf bool
225 dwarf *dwarf.Data
226 }
227
228 func (f *file) Name() string {
229 return f.name
230 }
231
232 func (f *file) Base() uint64 {
233
234 return 0
235 }
236
237 func (f *file) BuildID() string {
238
239 return ""
240 }
241
242 func (f *file) SourceLine(addr uint64) ([]driver.Frame, error) {
243 if f.pcln == nil {
244 pcln, err := f.file.PCLineTable()
245 if err != nil {
246 return nil, err
247 }
248 f.pcln = pcln
249 }
250 addr -= f.offset
251 file, line, fn := f.pcln.PCToLine(addr)
252 if fn != nil {
253 frame := []driver.Frame{
254 {
255 Func: fn.Name,
256 File: file,
257 Line: line,
258 },
259 }
260 return frame, nil
261 }
262
263 frames := f.dwarfSourceLine(addr)
264 if frames != nil {
265 return frames, nil
266 }
267
268 return nil, fmt.Errorf("no line information for PC=%#x", addr)
269 }
270
271
272
273
274 func (f *file) dwarfSourceLine(addr uint64) []driver.Frame {
275 if f.dwarf == nil && !f.triedDwarf {
276
277
278 f.dwarf, _ = f.file.DWARF()
279 f.triedDwarf = true
280 }
281
282 if f.dwarf != nil {
283 r := f.dwarf.Reader()
284 unit, err := r.SeekPC(addr)
285 if err == nil {
286 if frames := f.dwarfSourceLineEntry(r, unit, addr); frames != nil {
287 return frames
288 }
289 }
290 }
291
292 return nil
293 }
294
295
296
297 func (f *file) dwarfSourceLineEntry(r *dwarf.Reader, entry *dwarf.Entry, addr uint64) []driver.Frame {
298 lines, err := f.dwarf.LineReader(entry)
299 if err != nil {
300 return nil
301 }
302 var lentry dwarf.LineEntry
303 if err := lines.SeekPC(addr, &lentry); err != nil {
304 return nil
305 }
306
307
308 name := ""
309 FindName:
310 for entry, err := r.Next(); entry != nil && err == nil; entry, err = r.Next() {
311 if entry.Tag == dwarf.TagSubprogram {
312 ranges, err := f.dwarf.Ranges(entry)
313 if err != nil {
314 return nil
315 }
316 for _, pcs := range ranges {
317 if pcs[0] <= addr && addr < pcs[1] {
318 var ok bool
319
320 name, ok = entry.Val(dwarf.AttrName).(string)
321 if ok {
322 break FindName
323 }
324 }
325 }
326 }
327 }
328
329
330
331 frames := []driver.Frame{
332 {
333 Func: name,
334 File: lentry.File.Name,
335 Line: lentry.Line,
336 },
337 }
338
339 return frames
340 }
341
342 func (f *file) Symbols(r *regexp.Regexp, addr uint64) ([]*driver.Sym, error) {
343 if f.sym == nil {
344 sym, err := f.file.Symbols()
345 if err != nil {
346 return nil, err
347 }
348 f.sym = sym
349 }
350 var out []*driver.Sym
351 for _, s := range f.sym {
352
353
354 if s.Addr == 0 && s.Size == 0 {
355 continue
356 }
357 if (r == nil || r.MatchString(s.Name)) && (addr == 0 || s.Addr <= addr && addr < s.Addr+uint64(s.Size)) {
358 out = append(out, &driver.Sym{
359 Name: []string{s.Name},
360 File: f.name,
361 Start: s.Addr,
362 End: s.Addr + uint64(s.Size) - 1,
363 })
364 }
365 }
366 return out, nil
367 }
368
369 func (f *file) Close() error {
370 f.file.Close()
371 return nil
372 }
373
374
375
376 var newUI = func() driver.UI { return nil }
377
View as plain text