github.com/likebike/go--@v0.0.0-20190911215757-0bd925d16e96/go/src/cmd/pprof/pprof.go (about) 1 // Copyright 2014 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 // pprof is a tool for visualization of profile.data. It is based on 6 // the upstream version at github.com/google/pprof, with minor 7 // modifications specific to the Go distribution. Please consider 8 // upstreaming any modifications to these packages. 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 } 37 if err := driver.PProf(options); err != nil { 38 fmt.Fprintf(os.Stderr, "%v\n", err) 39 os.Exit(2) 40 } 41 } 42 43 type fetcher struct { 44 } 45 46 func (f *fetcher) Fetch(src string, duration, timeout time.Duration) (*profile.Profile, string, error) { 47 sourceURL, timeout := adjustURL(src, duration, timeout) 48 if sourceURL == "" { 49 // Could not recognize URL, let regular pprof attempt to fetch the profile (eg. from a file) 50 return nil, "", nil 51 } 52 fmt.Fprintln(os.Stderr, "Fetching profile over HTTP from", sourceURL) 53 if duration > 0 { 54 fmt.Fprintf(os.Stderr, "Please wait... (%v)\n", duration) 55 } 56 p, err := getProfile(sourceURL, timeout) 57 return p, sourceURL, err 58 } 59 60 func getProfile(source string, timeout time.Duration) (*profile.Profile, error) { 61 url, err := url.Parse(source) 62 if err != nil { 63 return nil, err 64 } 65 66 var tlsConfig *tls.Config 67 if url.Scheme == "https+insecure" { 68 tlsConfig = &tls.Config{ 69 InsecureSkipVerify: true, 70 } 71 url.Scheme = "https" 72 source = url.String() 73 } 74 75 client := &http.Client{ 76 Transport: &http.Transport{ 77 ResponseHeaderTimeout: timeout + 5*time.Second, 78 Proxy: http.ProxyFromEnvironment, 79 TLSClientConfig: tlsConfig, 80 }, 81 } 82 resp, err := client.Get(source) 83 if err != nil { 84 return nil, err 85 } 86 if resp.StatusCode != http.StatusOK { 87 defer resp.Body.Close() 88 return nil, statusCodeError(resp) 89 } 90 return profile.Parse(resp.Body) 91 } 92 93 func statusCodeError(resp *http.Response) error { 94 if resp.Header.Get("X-Go-Pprof") != "" && strings.Contains(resp.Header.Get("Content-Type"), "text/plain") { 95 // error is from pprof endpoint 96 if body, err := ioutil.ReadAll(resp.Body); err == nil { 97 return fmt.Errorf("server response: %s - %s", resp.Status, body) 98 } 99 } 100 return fmt.Errorf("server response: %s", resp.Status) 101 } 102 103 // cpuProfileHandler is the Go pprof CPU profile handler URL. 104 const cpuProfileHandler = "/debug/pprof/profile" 105 106 // adjustURL applies the duration/timeout values and Go specific defaults 107 func adjustURL(source string, duration, timeout time.Duration) (string, time.Duration) { 108 u, err := url.Parse(source) 109 if err != nil || (u.Host == "" && u.Scheme != "" && u.Scheme != "file") { 110 // Try adding http:// to catch sources of the form hostname:port/path. 111 // url.Parse treats "hostname" as the scheme. 112 u, err = url.Parse("http://" + source) 113 } 114 if err != nil || u.Host == "" { 115 return "", 0 116 } 117 118 if u.Path == "" || u.Path == "/" { 119 u.Path = cpuProfileHandler 120 } 121 122 // Apply duration/timeout overrides to URL. 123 values := u.Query() 124 if duration > 0 { 125 values.Set("seconds", fmt.Sprint(int(duration.Seconds()))) 126 } else { 127 if urlSeconds := values.Get("seconds"); urlSeconds != "" { 128 if us, err := strconv.ParseInt(urlSeconds, 10, 32); err == nil { 129 duration = time.Duration(us) * time.Second 130 } 131 } 132 } 133 if timeout <= 0 { 134 if duration > 0 { 135 timeout = duration + duration/2 136 } else { 137 timeout = 60 * time.Second 138 } 139 } 140 u.RawQuery = values.Encode() 141 return u.String(), timeout 142 } 143 144 // objTool implements driver.ObjTool using Go libraries 145 // (instead of invoking GNU binutils). 146 type objTool struct { 147 mu sync.Mutex 148 disasmCache map[string]*objfile.Disasm 149 } 150 151 func (*objTool) Open(name string, start, limit, offset uint64) (driver.ObjFile, error) { 152 of, err := objfile.Open(name) 153 if err != nil { 154 return nil, err 155 } 156 f := &file{ 157 name: name, 158 file: of, 159 } 160 if start != 0 { 161 if load, err := of.LoadAddress(); err == nil { 162 f.offset = start - load 163 } 164 } 165 return f, nil 166 } 167 168 func (*objTool) Demangle(names []string) (map[string]string, error) { 169 // No C++, nothing to demangle. 170 return make(map[string]string), nil 171 } 172 173 func (t *objTool) Disasm(file string, start, end uint64) ([]driver.Inst, error) { 174 d, err := t.cachedDisasm(file) 175 if err != nil { 176 return nil, err 177 } 178 var asm []driver.Inst 179 d.Decode(start, end, nil, func(pc, size uint64, file string, line int, text string) { 180 asm = append(asm, driver.Inst{Addr: pc, File: file, Line: line, Text: text}) 181 }) 182 return asm, nil 183 } 184 185 func (t *objTool) cachedDisasm(file string) (*objfile.Disasm, error) { 186 t.mu.Lock() 187 defer t.mu.Unlock() 188 if t.disasmCache == nil { 189 t.disasmCache = make(map[string]*objfile.Disasm) 190 } 191 d := t.disasmCache[file] 192 if d != nil { 193 return d, nil 194 } 195 f, err := objfile.Open(file) 196 if err != nil { 197 return nil, err 198 } 199 d, err = f.Disasm() 200 f.Close() 201 if err != nil { 202 return nil, err 203 } 204 t.disasmCache[file] = d 205 return d, nil 206 } 207 208 func (*objTool) SetConfig(config string) { 209 // config is usually used to say what binaries to invoke. 210 // Ignore entirely. 211 } 212 213 // file implements driver.ObjFile using Go libraries 214 // (instead of invoking GNU binutils). 215 // A file represents a single executable being analyzed. 216 type file struct { 217 name string 218 offset uint64 219 sym []objfile.Sym 220 file *objfile.File 221 pcln objfile.Liner 222 223 triedDwarf bool 224 dwarf *dwarf.Data 225 } 226 227 func (f *file) Name() string { 228 return f.name 229 } 230 231 func (f *file) Base() uint64 { 232 // No support for shared libraries. 233 return 0 234 } 235 236 func (f *file) BuildID() string { 237 // No support for build ID. 238 return "" 239 } 240 241 func (f *file) SourceLine(addr uint64) ([]driver.Frame, error) { 242 if f.pcln == nil { 243 pcln, err := f.file.PCLineTable() 244 if err != nil { 245 return nil, err 246 } 247 f.pcln = pcln 248 } 249 addr -= f.offset 250 file, line, fn := f.pcln.PCToLine(addr) 251 if fn != nil { 252 frame := []driver.Frame{ 253 { 254 Func: fn.Name, 255 File: file, 256 Line: line, 257 }, 258 } 259 return frame, nil 260 } 261 262 frames := f.dwarfSourceLine(addr) 263 if frames != nil { 264 return frames, nil 265 } 266 267 return nil, fmt.Errorf("no line information for PC=%#x", addr) 268 } 269 270 // dwarfSourceLine tries to get file/line information using DWARF. 271 // This is for C functions that appear in the profile. 272 // Returns nil if there is no information available. 273 func (f *file) dwarfSourceLine(addr uint64) []driver.Frame { 274 if f.dwarf == nil && !f.triedDwarf { 275 // Ignore any error--we don't care exactly why there 276 // is no DWARF info. 277 f.dwarf, _ = f.file.DWARF() 278 f.triedDwarf = true 279 } 280 281 if f.dwarf != nil { 282 r := f.dwarf.Reader() 283 unit, err := r.SeekPC(addr) 284 if err == nil { 285 if frames := f.dwarfSourceLineEntry(r, unit, addr); frames != nil { 286 return frames 287 } 288 } 289 } 290 291 return nil 292 } 293 294 // dwarfSourceLineEntry tries to get file/line information from a 295 // DWARF compilation unit. Returns nil if it doesn't find anything. 296 func (f *file) dwarfSourceLineEntry(r *dwarf.Reader, entry *dwarf.Entry, addr uint64) []driver.Frame { 297 lines, err := f.dwarf.LineReader(entry) 298 if err != nil { 299 return nil 300 } 301 var lentry dwarf.LineEntry 302 if err := lines.SeekPC(addr, &lentry); err != nil { 303 return nil 304 } 305 306 // Try to find the function name. 307 name := "" 308 FindName: 309 for entry, err := r.Next(); entry != nil && err == nil; entry, err = r.Next() { 310 if entry.Tag == dwarf.TagSubprogram { 311 ranges, err := f.dwarf.Ranges(entry) 312 if err != nil { 313 return nil 314 } 315 for _, pcs := range ranges { 316 if pcs[0] <= addr && addr < pcs[1] { 317 var ok bool 318 // TODO: AT_linkage_name, AT_MIPS_linkage_name. 319 name, ok = entry.Val(dwarf.AttrName).(string) 320 if ok { 321 break FindName 322 } 323 } 324 } 325 } 326 } 327 328 // TODO: Report inlined functions. 329 330 frames := []driver.Frame{ 331 { 332 Func: name, 333 File: lentry.File.Name, 334 Line: lentry.Line, 335 }, 336 } 337 338 return frames 339 } 340 341 func (f *file) Symbols(r *regexp.Regexp, addr uint64) ([]*driver.Sym, error) { 342 if f.sym == nil { 343 sym, err := f.file.Symbols() 344 if err != nil { 345 return nil, err 346 } 347 f.sym = sym 348 } 349 var out []*driver.Sym 350 for _, s := range f.sym { 351 // Ignore a symbol with address 0 and size 0. 352 // An ELF STT_FILE symbol will look like that. 353 if s.Addr == 0 && s.Size == 0 { 354 continue 355 } 356 if (r == nil || r.MatchString(s.Name)) && (addr == 0 || s.Addr <= addr && addr < s.Addr+uint64(s.Size)) { 357 out = append(out, &driver.Sym{ 358 Name: []string{s.Name}, 359 File: f.name, 360 Start: s.Addr, 361 End: s.Addr + uint64(s.Size) - 1, 362 }) 363 } 364 } 365 return out, nil 366 } 367 368 func (f *file) Close() error { 369 f.file.Close() 370 return nil 371 }