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