|
@@ -15,11 +15,14 @@
|
15
|
15
|
package driver
|
16
|
16
|
|
17
|
17
|
import (
|
|
18
|
+ "encoding/base64"
|
18
|
19
|
"fmt"
|
19
|
20
|
"io"
|
|
21
|
+ "math/rand"
|
20
|
22
|
"net/http"
|
21
|
23
|
"net/url"
|
22
|
24
|
"os"
|
|
25
|
+ "os/exec"
|
23
|
26
|
"path/filepath"
|
24
|
27
|
"strconv"
|
25
|
28
|
"sync"
|
|
@@ -357,7 +360,7 @@ func fetch(source string, duration, timeout time.Duration, ui plugin.UI) (p *pro
|
357
|
360
|
f, err = fetchURL(sourceURL, timeout)
|
358
|
361
|
src = sourceURL
|
359
|
362
|
} else {
|
360
|
|
- f, err = os.Open(source)
|
|
363
|
+ f, err = profileProtoReader(source)
|
361
|
364
|
}
|
362
|
365
|
if err == nil {
|
363
|
366
|
defer f.Close()
|
|
@@ -379,6 +382,74 @@ func fetchURL(source string, timeout time.Duration) (io.ReadCloser, error) {
|
379
|
382
|
return resp.Body, nil
|
380
|
383
|
}
|
381
|
384
|
|
|
385
|
+// profileProtoReader takes a path, and using heuristics, will try to convert
|
|
386
|
+// the file to profile.proto format. It returns a ReadCloser to the
|
|
387
|
+// profile.proto data; however, if the file contents were unknown or conversion
|
|
388
|
+// failed, it may still not be a valid profile.proto.
|
|
389
|
+func profileProtoReader(path string) (io.ReadCloser, error) {
|
|
390
|
+ sourceFile, openErr := os.Open(path)
|
|
391
|
+ if openErr != nil {
|
|
392
|
+ return nil, openErr
|
|
393
|
+ }
|
|
394
|
+
|
|
395
|
+ // If the file is the output of a perf record command, it should begin
|
|
396
|
+ // with the string PERFILE2.
|
|
397
|
+ perfHeader := []byte("PERFILE2")
|
|
398
|
+ actualHeader := make([]byte, len(perfHeader))
|
|
399
|
+ _, readErr := sourceFile.Read(actualHeader)
|
|
400
|
+ _, seekErr := sourceFile.Seek(0, 0)
|
|
401
|
+ if seekErr != nil {
|
|
402
|
+ return nil, seekErr
|
|
403
|
+ } else if readErr != nil && readErr != io.EOF{
|
|
404
|
+ return nil, readErr
|
|
405
|
+ } else if string(actualHeader) == string(perfHeader) {
|
|
406
|
+ sourceFile.Close()
|
|
407
|
+ profileFile, convertErr := convertPerfData(path)
|
|
408
|
+ if convertErr != nil {
|
|
409
|
+ return nil, convertErr
|
|
410
|
+ }
|
|
411
|
+ return os.Open(profileFile)
|
|
412
|
+ }
|
|
413
|
+ return sourceFile, nil
|
|
414
|
+}
|
|
415
|
+
|
|
416
|
+// convertPerfData converts the file at path which should be in perf.data format
|
|
417
|
+// using the perf_to_profile tool. It prints the stderr and stdout of
|
|
418
|
+// perf_to_profile to the stderr and stdout of this process, then returns the
|
|
419
|
+// path to a file containing the profile.proto formatted data.
|
|
420
|
+func convertPerfData(perfPath string) (string, error) {
|
|
421
|
+ randomBytes := make([]byte, 32)
|
|
422
|
+ _, randErr := rand.Read(randomBytes)
|
|
423
|
+ if randErr != nil {
|
|
424
|
+ return "", randErr
|
|
425
|
+ }
|
|
426
|
+ randomFileName := "/tmp/pprof_" +
|
|
427
|
+ base64.StdEncoding.EncodeToString(randomBytes)
|
|
428
|
+ cmd := exec.Command("perf_to_profile", perfPath, randomFileName)
|
|
429
|
+
|
|
430
|
+ stdout, stdoutErr := cmd.StdoutPipe()
|
|
431
|
+ if stdoutErr != nil {
|
|
432
|
+ return "", stdoutErr
|
|
433
|
+ }
|
|
434
|
+ go func() {
|
|
435
|
+ io.Copy(os.Stdout, stdout)
|
|
436
|
+ }()
|
|
437
|
+
|
|
438
|
+ stderr, stderrErr := cmd.StderrPipe()
|
|
439
|
+ if stderrErr != nil {
|
|
440
|
+ return "", stderrErr
|
|
441
|
+ }
|
|
442
|
+ go func() {
|
|
443
|
+ io.Copy(os.Stderr, stderr)
|
|
444
|
+ }()
|
|
445
|
+
|
|
446
|
+ if err := cmd.Run(); err != nil {
|
|
447
|
+ return "", err
|
|
448
|
+ }
|
|
449
|
+
|
|
450
|
+ return randomFileName, nil
|
|
451
|
+}
|
|
452
|
+
|
382
|
453
|
// adjustURL validates if a profile source is a URL and returns an
|
383
|
454
|
// cleaned up URL and the timeout to use for retrieval over HTTP.
|
384
|
455
|
// If the source cannot be recognized as a URL it returns an empty string.
|