1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package driver
16
17 import (
18 "bytes"
19 "fmt"
20 "io"
21 "io/ioutil"
22 "net/http"
23 "net/url"
24 "os"
25 "os/exec"
26 "path/filepath"
27 "runtime"
28 "strconv"
29 "strings"
30 "sync"
31 "time"
32
33 "github.com/google/pprof/internal/measurement"
34 "github.com/google/pprof/internal/plugin"
35 "github.com/google/pprof/profile"
36 )
37
38
39
40
41
42 func fetchProfiles(s *source, o *plugin.Options) (*profile.Profile, error) {
43 sources := make([]profileSource, 0, len(s.Sources))
44 for _, src := range s.Sources {
45 sources = append(sources, profileSource{
46 addr: src,
47 source: s,
48 })
49 }
50
51 bases := make([]profileSource, 0, len(s.Base))
52 for _, src := range s.Base {
53 bases = append(bases, profileSource{
54 addr: src,
55 source: s,
56 })
57 }
58
59 p, pbase, m, mbase, save, err := grabSourcesAndBases(sources, bases, o.Fetch, o.Obj, o.UI, o.HTTPTransport)
60 if err != nil {
61 return nil, err
62 }
63
64 if pbase != nil {
65 if s.DiffBase {
66 pbase.SetLabel("pprof::base", []string{"true"})
67 }
68 if s.Normalize {
69 err := p.Normalize(pbase)
70 if err != nil {
71 return nil, err
72 }
73 }
74 pbase.Scale(-1)
75 p, m, err = combineProfiles([]*profile.Profile{p, pbase}, []plugin.MappingSources{m, mbase})
76 if err != nil {
77 return nil, err
78 }
79 }
80
81
82 if err := o.Sym.Symbolize(s.Symbolize, m, p); err != nil {
83 return nil, err
84 }
85 p.RemoveUninteresting()
86 unsourceMappings(p)
87
88 if s.Comment != "" {
89 p.Comments = append(p.Comments, s.Comment)
90 }
91
92
93 if save {
94 dir, err := setTmpDir(o.UI)
95 if err != nil {
96 return nil, err
97 }
98
99 prefix := "pprof."
100 if len(p.Mapping) > 0 && p.Mapping[0].File != "" {
101 prefix += filepath.Base(p.Mapping[0].File) + "."
102 }
103 for _, s := range p.SampleType {
104 prefix += s.Type + "."
105 }
106
107 tempFile, err := newTempFile(dir, prefix, ".pb.gz")
108 if err == nil {
109 if err = p.Write(tempFile); err == nil {
110 o.UI.PrintErr("Saved profile in ", tempFile.Name())
111 }
112 }
113 if err != nil {
114 o.UI.PrintErr("Could not save profile: ", err)
115 }
116 }
117
118 if err := p.CheckValid(); err != nil {
119 return nil, err
120 }
121
122 return p, nil
123 }
124
125 func grabSourcesAndBases(sources, bases []profileSource, fetch plugin.Fetcher, obj plugin.ObjTool, ui plugin.UI, tr http.RoundTripper) (*profile.Profile, *profile.Profile, plugin.MappingSources, plugin.MappingSources, bool, error) {
126 wg := sync.WaitGroup{}
127 wg.Add(2)
128 var psrc, pbase *profile.Profile
129 var msrc, mbase plugin.MappingSources
130 var savesrc, savebase bool
131 var errsrc, errbase error
132 var countsrc, countbase int
133 go func() {
134 defer wg.Done()
135 psrc, msrc, savesrc, countsrc, errsrc = chunkedGrab(sources, fetch, obj, ui, tr)
136 }()
137 go func() {
138 defer wg.Done()
139 pbase, mbase, savebase, countbase, errbase = chunkedGrab(bases, fetch, obj, ui, tr)
140 }()
141 wg.Wait()
142 save := savesrc || savebase
143
144 if errsrc != nil {
145 return nil, nil, nil, nil, false, fmt.Errorf("problem fetching source profiles: %v", errsrc)
146 }
147 if errbase != nil {
148 return nil, nil, nil, nil, false, fmt.Errorf("problem fetching base profiles: %v,", errbase)
149 }
150 if countsrc == 0 {
151 return nil, nil, nil, nil, false, fmt.Errorf("failed to fetch any source profiles")
152 }
153 if countbase == 0 && len(bases) > 0 {
154 return nil, nil, nil, nil, false, fmt.Errorf("failed to fetch any base profiles")
155 }
156 if want, got := len(sources), countsrc; want != got {
157 ui.PrintErr(fmt.Sprintf("Fetched %d source profiles out of %d", got, want))
158 }
159 if want, got := len(bases), countbase; want != got {
160 ui.PrintErr(fmt.Sprintf("Fetched %d base profiles out of %d", got, want))
161 }
162
163 return psrc, pbase, msrc, mbase, save, nil
164 }
165
166
167
168
169 func chunkedGrab(sources []profileSource, fetch plugin.Fetcher, obj plugin.ObjTool, ui plugin.UI, tr http.RoundTripper) (*profile.Profile, plugin.MappingSources, bool, int, error) {
170 const chunkSize = 64
171
172 var p *profile.Profile
173 var msrc plugin.MappingSources
174 var save bool
175 var count int
176
177 for start := 0; start < len(sources); start += chunkSize {
178 end := start + chunkSize
179 if end > len(sources) {
180 end = len(sources)
181 }
182 chunkP, chunkMsrc, chunkSave, chunkCount, chunkErr := concurrentGrab(sources[start:end], fetch, obj, ui, tr)
183 switch {
184 case chunkErr != nil:
185 return nil, nil, false, 0, chunkErr
186 case chunkP == nil:
187 continue
188 case p == nil:
189 p, msrc, save, count = chunkP, chunkMsrc, chunkSave, chunkCount
190 default:
191 p, msrc, chunkErr = combineProfiles([]*profile.Profile{p, chunkP}, []plugin.MappingSources{msrc, chunkMsrc})
192 if chunkErr != nil {
193 return nil, nil, false, 0, chunkErr
194 }
195 if chunkSave {
196 save = true
197 }
198 count += chunkCount
199 }
200 }
201
202 return p, msrc, save, count, nil
203 }
204
205
206 func concurrentGrab(sources []profileSource, fetch plugin.Fetcher, obj plugin.ObjTool, ui plugin.UI, tr http.RoundTripper) (*profile.Profile, plugin.MappingSources, bool, int, error) {
207 wg := sync.WaitGroup{}
208 wg.Add(len(sources))
209 for i := range sources {
210 go func(s *profileSource) {
211 defer wg.Done()
212 s.p, s.msrc, s.remote, s.err = grabProfile(s.source, s.addr, fetch, obj, ui, tr)
213 }(&sources[i])
214 }
215 wg.Wait()
216
217 var save bool
218 profiles := make([]*profile.Profile, 0, len(sources))
219 msrcs := make([]plugin.MappingSources, 0, len(sources))
220 for i := range sources {
221 s := &sources[i]
222 if err := s.err; err != nil {
223 ui.PrintErr(s.addr + ": " + err.Error())
224 continue
225 }
226 save = save || s.remote
227 profiles = append(profiles, s.p)
228 msrcs = append(msrcs, s.msrc)
229 *s = profileSource{}
230 }
231
232 if len(profiles) == 0 {
233 return nil, nil, false, 0, nil
234 }
235
236 p, msrc, err := combineProfiles(profiles, msrcs)
237 if err != nil {
238 return nil, nil, false, 0, err
239 }
240 return p, msrc, save, len(profiles), nil
241 }
242
243 func combineProfiles(profiles []*profile.Profile, msrcs []plugin.MappingSources) (*profile.Profile, plugin.MappingSources, error) {
244
245 if err := measurement.ScaleProfiles(profiles); err != nil {
246 return nil, nil, err
247 }
248
249 p, err := profile.Merge(profiles)
250 if err != nil {
251 return nil, nil, err
252 }
253
254
255 msrc := make(plugin.MappingSources)
256 for _, ms := range msrcs {
257 for m, s := range ms {
258 msrc[m] = append(msrc[m], s...)
259 }
260 }
261 return p, msrc, nil
262 }
263
264 type profileSource struct {
265 addr string
266 source *source
267
268 p *profile.Profile
269 msrc plugin.MappingSources
270 remote bool
271 err error
272 }
273
274 func homeEnv() string {
275 switch runtime.GOOS {
276 case "windows":
277 return "USERPROFILE"
278 case "plan9":
279 return "home"
280 default:
281 return "HOME"
282 }
283 }
284
285
286
287
288 func setTmpDir(ui plugin.UI) (string, error) {
289 var dirs []string
290 if profileDir := os.Getenv("PPROF_TMPDIR"); profileDir != "" {
291 dirs = append(dirs, profileDir)
292 }
293 if homeDir := os.Getenv(homeEnv()); homeDir != "" {
294 dirs = append(dirs, filepath.Join(homeDir, "pprof"))
295 }
296 dirs = append(dirs, os.TempDir())
297 for _, tmpDir := range dirs {
298 if err := os.MkdirAll(tmpDir, 0755); err != nil {
299 ui.PrintErr("Could not use temp dir ", tmpDir, ": ", err.Error())
300 continue
301 }
302 return tmpDir, nil
303 }
304 return "", fmt.Errorf("failed to identify temp dir")
305 }
306
307 const testSourceAddress = "pproftest.local"
308
309
310
311
312 func grabProfile(s *source, source string, fetcher plugin.Fetcher, obj plugin.ObjTool, ui plugin.UI, tr http.RoundTripper) (p *profile.Profile, msrc plugin.MappingSources, remote bool, err error) {
313 var src string
314 duration, timeout := time.Duration(s.Seconds)*time.Second, time.Duration(s.Timeout)*time.Second
315 if fetcher != nil {
316 p, src, err = fetcher.Fetch(source, duration, timeout)
317 if err != nil {
318 return
319 }
320 }
321 if err != nil || p == nil {
322
323 p, src, err = fetch(source, duration, timeout, ui, tr)
324 if err != nil {
325 return
326 }
327 }
328
329 if err = p.CheckValid(); err != nil {
330 return
331 }
332
333
334 locateBinaries(p, s, obj, ui)
335
336
337 if src != "" {
338 msrc = collectMappingSources(p, src)
339 remote = true
340 if strings.HasPrefix(src, "http://"+testSourceAddress) {
341
342
343 remote = false
344 }
345 }
346 return
347 }
348
349
350 func collectMappingSources(p *profile.Profile, source string) plugin.MappingSources {
351 ms := plugin.MappingSources{}
352 for _, m := range p.Mapping {
353 src := struct {
354 Source string
355 Start uint64
356 }{
357 source, m.Start,
358 }
359 key := m.BuildID
360 if key == "" {
361 key = m.File
362 }
363 if key == "" {
364
365
366
367
368
369 m.File = source
370 key = source
371 }
372 ms[key] = append(ms[key], src)
373 }
374 return ms
375 }
376
377
378
379 func unsourceMappings(p *profile.Profile) {
380 for _, m := range p.Mapping {
381 if m.BuildID == "" {
382 if u, err := url.Parse(m.File); err == nil && u.IsAbs() {
383 m.File = ""
384 }
385 }
386 }
387 }
388
389
390
391 func locateBinaries(p *profile.Profile, s *source, obj plugin.ObjTool, ui plugin.UI) {
392
393 searchPath := os.Getenv("PPROF_BINARY_PATH")
394 if searchPath == "" {
395
396 searchPath = filepath.Join(os.Getenv(homeEnv()), "pprof", "binaries")
397 }
398 mapping:
399 for _, m := range p.Mapping {
400 var baseName string
401 if m.File != "" {
402 baseName = filepath.Base(m.File)
403 }
404
405 for _, path := range filepath.SplitList(searchPath) {
406 var fileNames []string
407 if m.BuildID != "" {
408 fileNames = []string{filepath.Join(path, m.BuildID, baseName)}
409 if matches, err := filepath.Glob(filepath.Join(path, m.BuildID, "*")); err == nil {
410 fileNames = append(fileNames, matches...)
411 }
412 fileNames = append(fileNames, filepath.Join(path, m.File, m.BuildID))
413 }
414 if m.File != "" {
415
416
417 if baseName != "" {
418 fileNames = append(fileNames, filepath.Join(path, baseName))
419 }
420 fileNames = append(fileNames, filepath.Join(path, m.File))
421 }
422 for _, name := range fileNames {
423 if f, err := obj.Open(name, m.Start, m.Limit, m.Offset); err == nil {
424 defer f.Close()
425 fileBuildID := f.BuildID()
426 if m.BuildID != "" && m.BuildID != fileBuildID {
427 ui.PrintErr("Ignoring local file " + name + ": build-id mismatch (" + m.BuildID + " != " + fileBuildID + ")")
428 } else {
429 m.File = name
430 continue mapping
431 }
432 }
433 }
434 }
435 }
436 if len(p.Mapping) == 0 {
437
438
439
440
441 m := &profile.Mapping{ID: 1}
442 p.Mapping = []*profile.Mapping{m}
443 for _, l := range p.Location {
444 l.Mapping = m
445 }
446 }
447
448
449 if execName, buildID := s.ExecName, s.BuildID; execName != "" || buildID != "" {
450 m := p.Mapping[0]
451 if execName != "" {
452 m.File = execName
453 }
454 if buildID != "" {
455 m.BuildID = buildID
456 }
457 }
458 }
459
460
461
462
463 func fetch(source string, duration, timeout time.Duration, ui plugin.UI, tr http.RoundTripper) (p *profile.Profile, src string, err error) {
464 var f io.ReadCloser
465
466 if sourceURL, timeout := adjustURL(source, duration, timeout); sourceURL != "" {
467 ui.Print("Fetching profile over HTTP from " + sourceURL)
468 if duration > 0 {
469 ui.Print(fmt.Sprintf("Please wait... (%v)", duration))
470 }
471 f, err = fetchURL(sourceURL, timeout, tr)
472 src = sourceURL
473 } else if isPerfFile(source) {
474 f, err = convertPerfData(source, ui)
475 } else {
476 f, err = os.Open(source)
477 }
478 if err == nil {
479 defer f.Close()
480 p, err = profile.Parse(f)
481 }
482 return
483 }
484
485
486 func fetchURL(source string, timeout time.Duration, tr http.RoundTripper) (io.ReadCloser, error) {
487 client := &http.Client{
488 Transport: tr,
489 Timeout: timeout + 5*time.Second,
490 }
491 resp, err := client.Get(source)
492 if err != nil {
493 return nil, fmt.Errorf("http fetch: %v", err)
494 }
495 if resp.StatusCode != http.StatusOK {
496 defer resp.Body.Close()
497 return nil, statusCodeError(resp)
498 }
499
500 return resp.Body, nil
501 }
502
503 func statusCodeError(resp *http.Response) error {
504 if resp.Header.Get("X-Go-Pprof") != "" && strings.Contains(resp.Header.Get("Content-Type"), "text/plain") {
505
506 if body, err := ioutil.ReadAll(resp.Body); err == nil {
507 return fmt.Errorf("server response: %s - %s", resp.Status, body)
508 }
509 }
510 return fmt.Errorf("server response: %s", resp.Status)
511 }
512
513
514
515 func isPerfFile(path string) bool {
516 sourceFile, openErr := os.Open(path)
517 if openErr != nil {
518 return false
519 }
520 defer sourceFile.Close()
521
522
523
524 perfHeader := []byte("PERFILE2")
525 actualHeader := make([]byte, len(perfHeader))
526 if _, readErr := sourceFile.Read(actualHeader); readErr != nil {
527 return false
528 }
529 return bytes.Equal(actualHeader, perfHeader)
530 }
531
532
533
534
535 func convertPerfData(perfPath string, ui plugin.UI) (*os.File, error) {
536 ui.Print(fmt.Sprintf(
537 "Converting %s to a profile.proto... (May take a few minutes)",
538 perfPath))
539 profile, err := newTempFile(os.TempDir(), "pprof_", ".pb.gz")
540 if err != nil {
541 return nil, err
542 }
543 deferDeleteTempFile(profile.Name())
544 cmd := exec.Command("perf_to_profile", "-i", perfPath, "-o", profile.Name(), "-f")
545 cmd.Stdout, cmd.Stderr = os.Stdout, os.Stderr
546 if err := cmd.Run(); err != nil {
547 profile.Close()
548 return nil, fmt.Errorf("failed to convert perf.data file. Try github.com/google/perf_data_converter: %v", err)
549 }
550 return profile, nil
551 }
552
553
554
555
556 func adjustURL(source string, duration, timeout time.Duration) (string, time.Duration) {
557 u, err := url.Parse(source)
558 if err != nil || (u.Host == "" && u.Scheme != "" && u.Scheme != "file") {
559
560
561 u, err = url.Parse("http://" + source)
562 }
563 if err != nil || u.Host == "" {
564 return "", 0
565 }
566
567
568 values := u.Query()
569 if duration > 0 {
570 values.Set("seconds", fmt.Sprint(int(duration.Seconds())))
571 } else {
572 if urlSeconds := values.Get("seconds"); urlSeconds != "" {
573 if us, err := strconv.ParseInt(urlSeconds, 10, 32); err == nil {
574 duration = time.Duration(us) * time.Second
575 }
576 }
577 }
578 if timeout <= 0 {
579 if duration > 0 {
580 timeout = duration + duration/2
581 } else {
582 timeout = 60 * time.Second
583 }
584 }
585 u.RawQuery = values.Encode()
586 return u.String(), timeout
587 }
588
View as plain text