瀏覽代碼

Adds hook to allow pprof to handle perf.data files.

Wade Simba Khadder 8 年之前
父節點
當前提交
f4315666c2
共有 1 個檔案被更改,包括 72 行新增1 行删除
  1. 72
    1
      internal/driver/fetch.go

+ 72
- 1
internal/driver/fetch.go 查看文件

@@ -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.