Browse Source

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

Wade Simba Khadder 8 years ago
parent
commit
f4315666c2
1 changed files with 72 additions and 1 deletions
  1. 72
    1
      internal/driver/fetch.go

+ 72
- 1
internal/driver/fetch.go View File

15
 package driver
15
 package driver
16
 
16
 
17
 import (
17
 import (
18
+	"encoding/base64"
18
 	"fmt"
19
 	"fmt"
19
 	"io"
20
 	"io"
21
+	"math/rand"
20
 	"net/http"
22
 	"net/http"
21
 	"net/url"
23
 	"net/url"
22
 	"os"
24
 	"os"
25
+	"os/exec"
23
 	"path/filepath"
26
 	"path/filepath"
24
 	"strconv"
27
 	"strconv"
25
 	"sync"
28
 	"sync"
357
 		f, err = fetchURL(sourceURL, timeout)
360
 		f, err = fetchURL(sourceURL, timeout)
358
 		src = sourceURL
361
 		src = sourceURL
359
 	} else {
362
 	} else {
360
-		f, err = os.Open(source)
363
+		f, err = profileProtoReader(source)
361
 	}
364
 	}
362
 	if err == nil {
365
 	if err == nil {
363
 		defer f.Close()
366
 		defer f.Close()
379
 	return resp.Body, nil
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
 // adjustURL validates if a profile source is a URL and returns an
453
 // adjustURL validates if a profile source is a URL and returns an
383
 // cleaned up URL and the timeout to use for retrieval over HTTP.
454
 // cleaned up URL and the timeout to use for retrieval over HTTP.
384
 // If the source cannot be recognized as a URL it returns an empty string.
455
 // If the source cannot be recognized as a URL it returns an empty string.