golang.org/toolchain@v0.0.1-go1.9rc2.windows-amd64/src/cmd/vendor/github.com/google/pprof/internal/driver/fetch.go (about) 1 // Copyright 2014 Google Inc. All Rights Reserved. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package driver 16 17 import ( 18 "bytes" 19 "crypto/tls" 20 "fmt" 21 "io" 22 "io/ioutil" 23 "net/http" 24 "net/url" 25 "os" 26 "os/exec" 27 "path/filepath" 28 "runtime" 29 "strconv" 30 "strings" 31 "sync" 32 "time" 33 34 "github.com/google/pprof/internal/measurement" 35 "github.com/google/pprof/internal/plugin" 36 "github.com/google/pprof/profile" 37 ) 38 39 // fetchProfiles fetches and symbolizes the profiles specified by s. 40 // It will merge all the profiles it is able to retrieve, even if 41 // there are some failures. It will return an error if it is unable to 42 // fetch any profiles. 43 func fetchProfiles(s *source, o *plugin.Options) (*profile.Profile, error) { 44 sources := make([]profileSource, 0, len(s.Sources)+len(s.Base)) 45 for _, src := range s.Sources { 46 sources = append(sources, profileSource{ 47 addr: src, 48 source: s, 49 scale: 1, 50 }) 51 } 52 for _, src := range s.Base { 53 sources = append(sources, profileSource{ 54 addr: src, 55 source: s, 56 scale: -1, 57 }) 58 } 59 p, msrcs, save, cnt, err := chunkedGrab(sources, o.Fetch, o.Obj, o.UI) 60 if err != nil { 61 return nil, err 62 } 63 if cnt == 0 { 64 return nil, fmt.Errorf("failed to fetch any profiles") 65 } 66 if want, got := len(sources), cnt; want != got { 67 o.UI.PrintErr(fmt.Sprintf("fetched %d profiles out of %d", got, want)) 68 } 69 70 // Symbolize the merged profile. 71 if err := o.Sym.Symbolize(s.Symbolize, msrcs, p); err != nil { 72 return nil, err 73 } 74 p.RemoveUninteresting() 75 unsourceMappings(p) 76 77 // Save a copy of the merged profile if there is at least one remote source. 78 if save { 79 dir, err := setTmpDir(o.UI) 80 if err != nil { 81 return nil, err 82 } 83 84 prefix := "pprof." 85 if len(p.Mapping) > 0 && p.Mapping[0].File != "" { 86 prefix += filepath.Base(p.Mapping[0].File) + "." 87 } 88 for _, s := range p.SampleType { 89 prefix += s.Type + "." 90 } 91 92 tempFile, err := newTempFile(dir, prefix, ".pb.gz") 93 if err == nil { 94 if err = p.Write(tempFile); err == nil { 95 o.UI.PrintErr("Saved profile in ", tempFile.Name()) 96 } 97 } 98 if err != nil { 99 o.UI.PrintErr("Could not save profile: ", err) 100 } 101 } 102 103 if err := p.CheckValid(); err != nil { 104 return nil, err 105 } 106 107 return p, nil 108 } 109 110 // chunkedGrab fetches the profiles described in source and merges them into 111 // a single profile. It fetches a chunk of profiles concurrently, with a maximum 112 // chunk size to limit its memory usage. 113 func chunkedGrab(sources []profileSource, fetch plugin.Fetcher, obj plugin.ObjTool, ui plugin.UI) (*profile.Profile, plugin.MappingSources, bool, int, error) { 114 const chunkSize = 64 115 116 var p *profile.Profile 117 var msrc plugin.MappingSources 118 var save bool 119 var count int 120 121 for start := 0; start < len(sources); start += chunkSize { 122 end := start + chunkSize 123 if end > len(sources) { 124 end = len(sources) 125 } 126 chunkP, chunkMsrc, chunkSave, chunkCount, chunkErr := concurrentGrab(sources[start:end], fetch, obj, ui) 127 switch { 128 case chunkErr != nil: 129 return nil, nil, false, 0, chunkErr 130 case chunkP == nil: 131 continue 132 case p == nil: 133 p, msrc, save, count = chunkP, chunkMsrc, chunkSave, chunkCount 134 default: 135 p, msrc, chunkErr = combineProfiles([]*profile.Profile{p, chunkP}, []plugin.MappingSources{msrc, chunkMsrc}) 136 if chunkErr != nil { 137 return nil, nil, false, 0, chunkErr 138 } 139 if chunkSave { 140 save = true 141 } 142 count += chunkCount 143 } 144 } 145 return p, msrc, save, count, nil 146 } 147 148 // concurrentGrab fetches multiple profiles concurrently 149 func concurrentGrab(sources []profileSource, fetch plugin.Fetcher, obj plugin.ObjTool, ui plugin.UI) (*profile.Profile, plugin.MappingSources, bool, int, error) { 150 wg := sync.WaitGroup{} 151 wg.Add(len(sources)) 152 for i := range sources { 153 go func(s *profileSource) { 154 defer wg.Done() 155 s.p, s.msrc, s.remote, s.err = grabProfile(s.source, s.addr, s.scale, fetch, obj, ui) 156 }(&sources[i]) 157 } 158 wg.Wait() 159 160 var save bool 161 profiles := make([]*profile.Profile, 0, len(sources)) 162 msrcs := make([]plugin.MappingSources, 0, len(sources)) 163 for i := range sources { 164 s := &sources[i] 165 if err := s.err; err != nil { 166 ui.PrintErr(s.addr + ": " + err.Error()) 167 continue 168 } 169 save = save || s.remote 170 profiles = append(profiles, s.p) 171 msrcs = append(msrcs, s.msrc) 172 *s = profileSource{} 173 } 174 175 if len(profiles) == 0 { 176 return nil, nil, false, 0, nil 177 } 178 179 p, msrc, err := combineProfiles(profiles, msrcs) 180 if err != nil { 181 return nil, nil, false, 0, err 182 } 183 return p, msrc, save, len(profiles), nil 184 } 185 186 func combineProfiles(profiles []*profile.Profile, msrcs []plugin.MappingSources) (*profile.Profile, plugin.MappingSources, error) { 187 // Merge profiles. 188 if err := measurement.ScaleProfiles(profiles); err != nil { 189 return nil, nil, err 190 } 191 192 p, err := profile.Merge(profiles) 193 if err != nil { 194 return nil, nil, err 195 } 196 197 // Combine mapping sources. 198 msrc := make(plugin.MappingSources) 199 for _, ms := range msrcs { 200 for m, s := range ms { 201 msrc[m] = append(msrc[m], s...) 202 } 203 } 204 return p, msrc, nil 205 } 206 207 type profileSource struct { 208 addr string 209 source *source 210 scale float64 211 212 p *profile.Profile 213 msrc plugin.MappingSources 214 remote bool 215 err error 216 } 217 218 func homeEnv() string { 219 switch runtime.GOOS { 220 case "windows": 221 return "USERPROFILE" 222 case "plan9": 223 return "home" 224 default: 225 return "HOME" 226 } 227 } 228 229 // setTmpDir prepares the directory to use to save profiles retrieved 230 // remotely. It is selected from PPROF_TMPDIR, defaults to $HOME/pprof. 231 func setTmpDir(ui plugin.UI) (string, error) { 232 if profileDir := os.Getenv("PPROF_TMPDIR"); profileDir != "" { 233 return profileDir, nil 234 } 235 for _, tmpDir := range []string{os.Getenv(homeEnv()) + "/pprof", os.TempDir()} { 236 if err := os.MkdirAll(tmpDir, 0755); err != nil { 237 ui.PrintErr("Could not use temp dir ", tmpDir, ": ", err.Error()) 238 continue 239 } 240 return tmpDir, nil 241 } 242 return "", fmt.Errorf("failed to identify temp dir") 243 } 244 245 // grabProfile fetches a profile. Returns the profile, sources for the 246 // profile mappings, a bool indicating if the profile was fetched 247 // remotely, and an error. 248 func grabProfile(s *source, source string, scale float64, fetcher plugin.Fetcher, obj plugin.ObjTool, ui plugin.UI) (p *profile.Profile, msrc plugin.MappingSources, remote bool, err error) { 249 var src string 250 duration, timeout := time.Duration(s.Seconds)*time.Second, time.Duration(s.Timeout)*time.Second 251 if fetcher != nil { 252 p, src, err = fetcher.Fetch(source, duration, timeout) 253 if err != nil { 254 return 255 } 256 } 257 if err != nil || p == nil { 258 // Fetch the profile over HTTP or from a file. 259 p, src, err = fetch(source, duration, timeout, ui) 260 if err != nil { 261 return 262 } 263 } 264 265 if err = p.CheckValid(); err != nil { 266 return 267 } 268 269 // Apply local changes to the profile. 270 p.Scale(scale) 271 272 // Update the binary locations from command line and paths. 273 locateBinaries(p, s, obj, ui) 274 275 // Collect the source URL for all mappings. 276 if src != "" { 277 msrc = collectMappingSources(p, src) 278 remote = true 279 } 280 return 281 } 282 283 // collectMappingSources saves the mapping sources of a profile. 284 func collectMappingSources(p *profile.Profile, source string) plugin.MappingSources { 285 ms := plugin.MappingSources{} 286 for _, m := range p.Mapping { 287 src := struct { 288 Source string 289 Start uint64 290 }{ 291 source, m.Start, 292 } 293 key := m.BuildID 294 if key == "" { 295 key = m.File 296 } 297 if key == "" { 298 // If there is no build id or source file, use the source as the 299 // mapping file. This will enable remote symbolization for this 300 // mapping, in particular for Go profiles on the legacy format. 301 // The source is reset back to empty string by unsourceMapping 302 // which is called after symbolization is finished. 303 m.File = source 304 key = source 305 } 306 ms[key] = append(ms[key], src) 307 } 308 return ms 309 } 310 311 // unsourceMappings iterates over the mappings in a profile and replaces file 312 // set to the remote source URL by collectMappingSources back to empty string. 313 func unsourceMappings(p *profile.Profile) { 314 for _, m := range p.Mapping { 315 if m.BuildID == "" { 316 if u, err := url.Parse(m.File); err == nil && u.IsAbs() { 317 m.File = "" 318 } 319 } 320 } 321 } 322 323 // locateBinaries searches for binary files listed in the profile and, if found, 324 // updates the profile accordingly. 325 func locateBinaries(p *profile.Profile, s *source, obj plugin.ObjTool, ui plugin.UI) { 326 // Construct search path to examine 327 searchPath := os.Getenv("PPROF_BINARY_PATH") 328 if searchPath == "" { 329 // Use $HOME/pprof/binaries as default directory for local symbolization binaries 330 searchPath = filepath.Join(os.Getenv(homeEnv()), "pprof", "binaries") 331 } 332 mapping: 333 for _, m := range p.Mapping { 334 var baseName string 335 if m.File != "" { 336 baseName = filepath.Base(m.File) 337 } 338 339 for _, path := range filepath.SplitList(searchPath) { 340 var fileNames []string 341 if m.BuildID != "" { 342 fileNames = []string{filepath.Join(path, m.BuildID, baseName)} 343 if matches, err := filepath.Glob(filepath.Join(path, m.BuildID, "*")); err == nil { 344 fileNames = append(fileNames, matches...) 345 } 346 } 347 if m.File != "" { 348 // Try both the basename and the full path, to support the same directory 349 // structure as the perf symfs option. 350 if baseName != "" { 351 fileNames = append(fileNames, filepath.Join(path, baseName)) 352 } 353 fileNames = append(fileNames, filepath.Join(path, m.File)) 354 } 355 for _, name := range fileNames { 356 if f, err := obj.Open(name, m.Start, m.Limit, m.Offset); err == nil { 357 defer f.Close() 358 fileBuildID := f.BuildID() 359 if m.BuildID != "" && m.BuildID != fileBuildID { 360 ui.PrintErr("Ignoring local file " + name + ": build-id mismatch (" + m.BuildID + " != " + fileBuildID + ")") 361 } else { 362 m.File = name 363 continue mapping 364 } 365 } 366 } 367 } 368 } 369 // Replace executable filename/buildID with the overrides from source. 370 // Assumes the executable is the first Mapping entry. 371 if execName, buildID := s.ExecName, s.BuildID; execName != "" || buildID != "" { 372 if len(p.Mapping) == 0 { 373 // If there are no mappings, add a fake mapping to attempt symbolization. 374 // This is useful for some profiles generated by the golang runtime, which 375 // do not include any mappings. Symbolization with a fake mapping will only 376 // be successful against a non-PIE binary. 377 m := &profile.Mapping{ID: 1} 378 p.Mapping = []*profile.Mapping{m} 379 for _, l := range p.Location { 380 l.Mapping = m 381 } 382 } 383 m := p.Mapping[0] 384 if execName != "" { 385 m.File = execName 386 } 387 if buildID != "" { 388 m.BuildID = buildID 389 } 390 } 391 } 392 393 // fetch fetches a profile from source, within the timeout specified, 394 // producing messages through the ui. It returns the profile and the 395 // url of the actual source of the profile for remote profiles. 396 func fetch(source string, duration, timeout time.Duration, ui plugin.UI) (p *profile.Profile, src string, err error) { 397 var f io.ReadCloser 398 399 if sourceURL, timeout := adjustURL(source, duration, timeout); sourceURL != "" { 400 ui.Print("Fetching profile over HTTP from " + sourceURL) 401 if duration > 0 { 402 ui.Print(fmt.Sprintf("Please wait... (%v)", duration)) 403 } 404 f, err = fetchURL(sourceURL, timeout) 405 src = sourceURL 406 } else if isPerfFile(source) { 407 f, err = convertPerfData(source, ui) 408 } else { 409 f, err = os.Open(source) 410 } 411 if err == nil { 412 defer f.Close() 413 p, err = profile.Parse(f) 414 } 415 return 416 } 417 418 // fetchURL fetches a profile from a URL using HTTP. 419 func fetchURL(source string, timeout time.Duration) (io.ReadCloser, error) { 420 resp, err := httpGet(source, timeout) 421 if err != nil { 422 return nil, fmt.Errorf("http fetch: %v", err) 423 } 424 if resp.StatusCode != http.StatusOK { 425 defer resp.Body.Close() 426 return nil, statusCodeError(resp) 427 } 428 429 return resp.Body, nil 430 } 431 432 func statusCodeError(resp *http.Response) error { 433 if resp.Header.Get("X-Go-Pprof") != "" && strings.Contains(resp.Header.Get("Content-Type"), "text/plain") { 434 // error is from pprof endpoint 435 if body, err := ioutil.ReadAll(resp.Body); err == nil { 436 return fmt.Errorf("server response: %s - %s", resp.Status, body) 437 } 438 } 439 return fmt.Errorf("server response: %s", resp.Status) 440 } 441 442 // isPerfFile checks if a file is in perf.data format. It also returns false 443 // if it encounters an error during the check. 444 func isPerfFile(path string) bool { 445 sourceFile, openErr := os.Open(path) 446 if openErr != nil { 447 return false 448 } 449 defer sourceFile.Close() 450 451 // If the file is the output of a perf record command, it should begin 452 // with the string PERFILE2. 453 perfHeader := []byte("PERFILE2") 454 actualHeader := make([]byte, len(perfHeader)) 455 if _, readErr := sourceFile.Read(actualHeader); readErr != nil { 456 return false 457 } 458 return bytes.Equal(actualHeader, perfHeader) 459 } 460 461 // convertPerfData converts the file at path which should be in perf.data format 462 // using the perf_to_profile tool and returns the file containing the 463 // profile.proto formatted data. 464 func convertPerfData(perfPath string, ui plugin.UI) (*os.File, error) { 465 ui.Print(fmt.Sprintf( 466 "Converting %s to a profile.proto... (May take a few minutes)", 467 perfPath)) 468 profile, err := newTempFile(os.TempDir(), "pprof_", ".pb.gz") 469 if err != nil { 470 return nil, err 471 } 472 deferDeleteTempFile(profile.Name()) 473 cmd := exec.Command("perf_to_profile", perfPath, profile.Name()) 474 if err := cmd.Run(); err != nil { 475 profile.Close() 476 return nil, fmt.Errorf("failed to convert perf.data file. Try github.com/google/perf_data_converter: %v", err) 477 } 478 return profile, nil 479 } 480 481 // adjustURL validates if a profile source is a URL and returns an 482 // cleaned up URL and the timeout to use for retrieval over HTTP. 483 // If the source cannot be recognized as a URL it returns an empty string. 484 func adjustURL(source string, duration, timeout time.Duration) (string, time.Duration) { 485 u, err := url.Parse(source) 486 if err != nil || (u.Host == "" && u.Scheme != "" && u.Scheme != "file") { 487 // Try adding http:// to catch sources of the form hostname:port/path. 488 // url.Parse treats "hostname" as the scheme. 489 u, err = url.Parse("http://" + source) 490 } 491 if err != nil || u.Host == "" { 492 return "", 0 493 } 494 495 // Apply duration/timeout overrides to URL. 496 values := u.Query() 497 if duration > 0 { 498 values.Set("seconds", fmt.Sprint(int(duration.Seconds()))) 499 } else { 500 if urlSeconds := values.Get("seconds"); urlSeconds != "" { 501 if us, err := strconv.ParseInt(urlSeconds, 10, 32); err == nil { 502 duration = time.Duration(us) * time.Second 503 } 504 } 505 } 506 if timeout <= 0 { 507 if duration > 0 { 508 timeout = duration + duration/2 509 } else { 510 timeout = 60 * time.Second 511 } 512 } 513 u.RawQuery = values.Encode() 514 return u.String(), timeout 515 } 516 517 // httpGet is a wrapper around http.Get; it is defined as a variable 518 // so it can be redefined during for testing. 519 var httpGet = func(source string, timeout time.Duration) (*http.Response, error) { 520 url, err := url.Parse(source) 521 if err != nil { 522 return nil, err 523 } 524 525 var tlsConfig *tls.Config 526 if url.Scheme == "https+insecure" { 527 tlsConfig = &tls.Config{ 528 InsecureSkipVerify: true, 529 } 530 url.Scheme = "https" 531 source = url.String() 532 } 533 534 client := &http.Client{ 535 Transport: &http.Transport{ 536 ResponseHeaderTimeout: timeout + 5*time.Second, 537 Proxy: http.ProxyFromEnvironment, 538 TLSClientConfig: tlsConfig, 539 }, 540 } 541 return client.Get(source) 542 }