]> Cypherpunks repositories - gostls13.git/commitdiff
runtime/pprof: output CPU profiles in pprof protobuf format
authorMichael Matloob <matloob@golang.org>
Thu, 10 Nov 2016 18:31:41 +0000 (13:31 -0500)
committerMichael Matloob <matloob@golang.org>
Thu, 10 Nov 2016 21:07:48 +0000 (21:07 +0000)
This change buffers the entire profile and converts in one shot
in the profile writer, and could use more memory than necessary
to output protocol buffer formatted profiles. It should be
possible to convert each chunk in a stream (maybe maintaining
some minimal state to output in the end) which could save on
memory usage.

Fixes #16093

Change-Id: I946c6a2b044ae644c72c8bb2d3bd82c415b1a847
Reviewed-on: https://go-review.googlesource.com/33071
Run-TryBot: Michael Matloob <matloob@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Russ Cox <rsc@golang.org>
src/go/build/deps_test.go
src/runtime/pprof/internal/protopprof/protopprof.go
src/runtime/pprof/internal/protopprof/protopprof_test.go
src/runtime/pprof/pprof.go
src/runtime/pprof/pprof_test.go

index 9177daa8adb576a0bbef4cf0f12f40006f1b5e49..39ed9de039bf3fd94e45fae7b493c3ec2c44b140 100644 (file)
@@ -176,7 +176,7 @@ var pkgDeps = map[string][]string{
        "runtime/debug":                     {"L2", "fmt", "io/ioutil", "os", "time"},
        "runtime/pprof/internal/gzip0":      {"L2"},
        "runtime/pprof/internal/protopprof": {"L2", "fmt", "internal/pprof/profile", "os", "time"},
-       "runtime/pprof":                     {"L2", "fmt", "os", "text/tabwriter"},
+       "runtime/pprof":                     {"L2", "fmt", "os", "runtime/pprof/internal/protopprof", "text/tabwriter", "time"},
        "runtime/trace":                     {"L0"},
        "text/tabwriter":                    {"L2"},
 
index 6d799d921fe1195e565f18b663637e1e49aca8f5..5d269c4f652ea84022282b705de005d6078c5a90 100644 (file)
@@ -49,7 +49,6 @@ func TranslateCPUProfile(b []byte, startTime time.Time) (*profile.Profile, error
                }
                count := data[0]
                nstk := data[1]
-               fmt.Printf("count:%v nstk: %v\n", count, nstk)
                if uintptr(len(data)) < 2+nstk {
                        return nil, fmt.Errorf("truncated profile")
                }
index beecefe04c62af9610fa9e5b1b4892a93b8faad4..ad8b04bd376d3f5398f19b8101254d9d2029ad86 100644 (file)
@@ -8,37 +8,14 @@ import (
        "bytes"
        "fmt"
        "internal/pprof/profile"
-       "io"
        "io/ioutil"
        "reflect"
        "runtime"
-       "runtime/pprof"
        "testing"
        "time"
        "unsafe"
 )
 
-// Profile collects a CPU utilization profile and
-// writes it to w as a compressed profile.proto. It's used by
-// TestProfileParse.
-func Profile(w io.Writer, seconds int) error {
-       var buf bytes.Buffer
-       // Collect the CPU profile in legacy format in buf.
-       startTime := time.Now()
-       if err := pprof.StartCPUProfile(&buf); err != nil {
-               return fmt.Errorf("Could not enable CPU profiling: %s\n", err)
-       }
-       time.Sleep(time.Duration(seconds) * time.Second)
-       pprof.StopCPUProfile()
-
-       const untagged = false
-       p, err := TranslateCPUProfile(buf.Bytes(), startTime)
-       if err != nil {
-               return err
-       }
-       return p.Write(w)
-}
-
 // Helper function to initialize empty cpu profile with sampling period provided.
 func createEmptyProfileWithPeriod(t *testing.T, periodMs uint64) bytes.Buffer {
        // Mock the sample header produced by cpu profiler. Write a sample
@@ -85,21 +62,6 @@ func createProfileWithTwoSamples(t *testing.T, periodMs uintptr, count1 uintptr,
        return *buf
 }
 
-// Tests that server creates a cpu profile handler that outputs a parsable Profile profile.
-func TestCPUProfileParse(t *testing.T) {
-       var before, after runtime.MemStats
-       runtime.ReadMemStats(&before)
-       var buf bytes.Buffer
-       if err := Profile(&buf, 30); err != nil {
-               t.Fatalf("Profile failed: %v", err)
-       }
-       runtime.ReadMemStats(&after)
-       _, err := profile.Parse(&buf)
-       if err != nil {
-               t.Fatalf("Could not parse Profile profile: %v", err)
-       }
-}
-
 // Tests TranslateCPUProfile parses correct sampling period in an otherwise empty cpu profile.
 func TestTranlateCPUProfileSamplingPeriod(t *testing.T) {
        // A test server with mock cpu profile data.
index 20ccb51b00c71acf8acfa26e87758cd619e3c37d..70544acde19e10cb37ca8bc4fdf02b2e0b1cf304 100644 (file)
@@ -74,12 +74,13 @@ import (
        "bytes"
        "fmt"
        "io"
-       "os"
        "runtime"
+       "runtime/pprof/internal/protopprof"
        "sort"
        "strings"
        "sync"
        "text/tabwriter"
+       "time"
 )
 
 // BUG(rsc): Profiles are only as good as the kernel support used to generate them.
@@ -670,49 +671,29 @@ func StartCPUProfile(w io.Writer) error {
 }
 
 func profileWriter(w io.Writer) {
+       startTime := time.Now()
+       // This will buffer the entire profile into buf and then
+       // translate it into a profile.Profile structure. This will
+       // create two copies of all the data in the profile in memory.
+       // TODO(matloob): Convert each chunk of the proto output and
+       // stream it out instead of converting the entire profile.
+       var buf bytes.Buffer
        for {
                data := runtime.CPUProfile()
                if data == nil {
                        break
                }
-               w.Write(data)
-       }
-
-       // We are emitting the legacy profiling format, which permits
-       // a memory map following the CPU samples. The memory map is
-       // simply a copy of the GNU/Linux /proc/self/maps file. The
-       // profiler uses the memory map to map PC values in shared
-       // libraries to a shared library in the filesystem, in order
-       // to report the correct function and, if the shared library
-       // has debug info, file/line. This is particularly useful for
-       // PIE (position independent executables) as on ELF systems a
-       // PIE is simply an executable shared library.
-       //
-       // Because the profiling format expects the memory map in
-       // GNU/Linux format, we only do this on GNU/Linux for now. To
-       // add support for profiling PIE on other ELF-based systems,
-       // it may be necessary to map the system-specific mapping
-       // information to the GNU/Linux format. For a reasonably
-       // portable C++ version, see the FillProcSelfMaps function in
-       // https://github.com/gperftools/gperftools/blob/master/src/base/sysinfo.cc
-       //
-       // The code that parses this mapping for the pprof tool is
-       // ParseMemoryMap in cmd/internal/pprof/legacy_profile.go, but
-       // don't change that code, as similar code exists in other
-       // (non-Go) pprof readers. Change this code so that that code works.
-       //
-       // We ignore errors reading or copying the memory map; the
-       // profile is likely usable without it, and we have no good way
-       // to report errors.
-       if runtime.GOOS == "linux" {
-               f, err := os.Open("/proc/self/maps")
-               if err == nil {
-                       io.WriteString(w, "\nMAPPED_LIBRARIES:\n")
-                       io.Copy(w, f)
-                       f.Close()
-               }
+               buf.Write(data)
+       }
+
+       profile, err := protopprof.TranslateCPUProfile(buf.Bytes(), startTime)
+       if err != nil {
+               // The runtime should never produce an invalid or truncated profile.
+               // It drops records that can't fit into its log buffers.
+               panic(fmt.Errorf("could not translate binary profile to proto format: %v", err))
        }
 
+       profile.Write(w)
        cpu.done <- true
 }
 
index a683a03211659cc7fb33b201ada9f2e156b3cd1a..c19ea3507d1ac38171c6c09e389cbd5f8e3ea393 100644 (file)
@@ -9,6 +9,7 @@ package pprof_test
 import (
        "bytes"
        "fmt"
+       "internal/pprof/profile"
        "internal/testenv"
        "math/big"
        "os"
@@ -20,7 +21,6 @@ import (
        "sync"
        "testing"
        "time"
-       "unsafe"
 )
 
 func cpuHogger(f func(), dur time.Duration) {
@@ -87,40 +87,17 @@ func TestCPUProfileMultithreaded(t *testing.T) {
 }
 
 func parseProfile(t *testing.T, valBytes []byte, f func(uintptr, []uintptr)) {
-       // Convert []byte to []uintptr.
-       l := len(valBytes)
-       if i := bytes.Index(valBytes, []byte("\nMAPPED_LIBRARIES:\n")); i >= 0 {
-               l = i
-       }
-       l /= int(unsafe.Sizeof(uintptr(0)))
-       val := *(*[]uintptr)(unsafe.Pointer(&valBytes))
-       val = val[:l]
-
-       // 5 for the header, 3 for the trailer.
-       if l < 5+3 {
-               t.Logf("profile too short: %#x", val)
-               if badOS[runtime.GOOS] {
-                       t.Skipf("ignoring failure on %s; see golang.org/issue/13841", runtime.GOOS)
-                       return
-               }
-               t.FailNow()
-       }
-
-       hd, val, tl := val[:5], val[5:l-3], val[l-3:]
-       if hd[0] != 0 || hd[1] != 3 || hd[2] != 0 || hd[3] != 1e6/100 || hd[4] != 0 {
-               t.Fatalf("unexpected header %#x", hd)
-       }
-
-       if tl[0] != 0 || tl[1] != 1 || tl[2] != 0 {
-               t.Fatalf("malformed end-of-data marker %#x", tl)
+       p, err := profile.Parse(bytes.NewReader(valBytes))
+       if err != nil {
+               t.Fatal(err)
        }
-
-       for len(val) > 0 {
-               if len(val) < 2 || val[0] < 1 || val[1] < 1 || uintptr(len(val)) < 2+val[1] {
-                       t.Fatalf("malformed profile.  leftover: %#x", val)
+       for _, sample := range p.Sample {
+               count := uintptr(sample.Value[0])
+               stk := make([]uintptr, len(sample.Location))
+               for i := range sample.Location {
+                       stk[i] = uintptr(sample.Location[i].Address)
                }
-               f(val[0], val[2:2+val[1]])
-               val = val[2+val[1]:]
+               f(count, stk)
        }
 }
 
@@ -674,3 +651,23 @@ func containsInOrder(s string, all ...string) bool {
        }
        return true
 }
+
+// Tests that the profiler outputs a parsable protobuf Profile profile.
+func TestCPUProfileParse(t *testing.T) {
+
+       var before, after runtime.MemStats
+       runtime.ReadMemStats(&before)
+       var buf bytes.Buffer
+
+       if err := StartCPUProfile(&buf); err != nil {
+               t.Fatalf("Profile failed: Could not enable CPU profiling: %s\n", err)
+       }
+       time.Sleep(5 * time.Second)
+       StopCPUProfile()
+
+       runtime.ReadMemStats(&after)
+       _, err := profile.Parse(&buf)
+       if err != nil {
+               t.Fatalf("Could not parse Profile profile: %v", err)
+       }
+}