github.com/gagliardetto/golang-go@v0.0.0-20201020153340-53909ea70814/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 "github.com/gagliardetto/golang-go/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 // Could not recognize URL, let regular pprof attempt to fetch the profile (eg. from a file) 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 // error is from pprof endpoint 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 // cpuProfileHandler is the Go pprof CPU profile handler URL. 105 const cpuProfileHandler = "/debug/pprof/profile" 106 107 // adjustURL applies the duration/timeout values and Go specific defaults 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 // Try adding http:// to catch sources of the form hostname:port/path. 112 // url.Parse treats "hostname" as the scheme. 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 // Apply duration/timeout overrides to URL. 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 // objTool implements driver.ObjTool using Go libraries 146 // (instead of invoking GNU binutils). 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 // No C++, nothing to demangle. 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 // config is usually used to say what binaries to invoke. 211 // Ignore entirely. 212 } 213 214 // file implements driver.ObjFile using Go libraries 215 // (instead of invoking GNU binutils). 216 // A file represents a single executable being analyzed. 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 // No support for shared libraries. 234 return 0 235 } 236 237 func (f *file) BuildID() string { 238 // No support for build ID. 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 // dwarfSourceLine tries to get file/line information using DWARF. 272 // This is for C functions that appear in the profile. 273 // Returns nil if there is no information available. 274 func (f *file) dwarfSourceLine(addr uint64) []driver.Frame { 275 if f.dwarf == nil && !f.triedDwarf { 276 // Ignore any error--we don't care exactly why there 277 // is no DWARF info. 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 // dwarfSourceLineEntry tries to get file/line information from a 296 // DWARF compilation unit. Returns nil if it doesn't find anything. 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 // Try to find the function name. 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 // TODO: AT_linkage_name, AT_MIPS_linkage_name. 320 name, ok = entry.Val(dwarf.AttrName).(string) 321 if ok { 322 break FindName 323 } 324 } 325 } 326 } 327 } 328 329 // TODO: Report inlined functions. 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 // Ignore a symbol with address 0 and size 0. 353 // An ELF STT_FILE symbol will look like that. 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 // newUI will be set in readlineui.go in some platforms 375 // for interactive readline functionality. 376 var newUI = func() driver.UI { return nil }