]> Cypherpunks repositories - gostls13.git/commitdiff
[dev.link] cmd/link: add pprof to benchmark tool
authorJeremy Faller <jeremy@golang.org>
Tue, 25 Feb 2020 01:47:25 +0000 (20:47 -0500)
committerJeremy Faller <jeremy@golang.org>
Tue, 25 Feb 2020 21:48:41 +0000 (21:48 +0000)
Add a per-phase pprof.

Change-Id: I0bb46e8e8f548941c1dd49685157f0500cbdf6cc
Reviewed-on: https://go-review.googlesource.com/c/go/+/220817
Run-TryBot: Jeremy Faller <jeremy@golang.org>
Reviewed-by: Than McIntosh <thanm@google.com>
Reviewed-by: Cherry Zhang <cherryyz@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>

src/cmd/link/internal/benchmark/bench.go
src/cmd/link/internal/benchmark/bench_test.go
src/cmd/link/internal/ld/main.go

index 8f23bf82fd31fc6bb66158ea3fbe521cd90e9295..52f98269a14d6e49a7c0b1a29ef7a746fbb62759 100644 (file)
@@ -12,7 +12,9 @@ package benchmark
 import (
        "fmt"
        "io"
+       "os"
        "runtime"
+       "runtime/pprof"
        "time"
        "unicode"
 )
@@ -25,9 +27,11 @@ const (
 )
 
 type Metrics struct {
-       gc      Flags
-       marks   []*mark
-       curMark *mark
+       gc        Flags
+       marks     []*mark
+       curMark   *mark
+       filebase  string
+       pprofFile *os.File
 }
 
 type mark struct {
@@ -41,7 +45,8 @@ type mark struct {
 // Typical usage should look like:
 //
 // func main() {
-//   bench := benchmark.New(benchmark.GC)
+//   filename := "" // Set to enable per-phase pprof file output.
+//   bench := benchmark.New(benchmark.GC, filename)
 //   defer bench.Report(os.Stdout)
 //   // etc
 //   bench.Start("foo")
@@ -63,11 +68,11 @@ type mark struct {
 //    bench.Start("foo")
 //    // etc.
 //  }
-func New(gc Flags) *Metrics {
+func New(gc Flags, filebase string) *Metrics {
        if gc == GC {
                runtime.GC()
        }
-       return &Metrics{gc: gc}
+       return &Metrics{gc: gc, filebase: filebase}
 }
 
 // Report reports the metrics.
@@ -110,6 +115,16 @@ func (m *Metrics) Start(name string) {
        m.closeMark()
        m.curMark = &mark{name: name}
        // Unlikely we need to a GC here, as one was likely just done in closeMark.
+       if m.shouldPProf() {
+               f, err := os.Create(makePProfFilename(m.filebase, name))
+               if err != nil {
+                       panic(err)
+               }
+               m.pprofFile = f
+               if err = pprof.StartCPUProfile(m.pprofFile); err != nil {
+                       panic(err)
+               }
+       }
        runtime.ReadMemStats(&m.curMark.startM)
        m.curMark.startT = time.Now()
 }
@@ -124,10 +139,20 @@ func (m *Metrics) closeMark() {
                runtime.GC()
                runtime.ReadMemStats(&m.curMark.gcM)
        }
+       if m.shouldPProf() {
+               pprof.StopCPUProfile()
+               m.pprofFile.Close()
+               m.pprofFile = nil
+       }
        m.marks = append(m.marks, m.curMark)
        m.curMark = nil
 }
 
+// shouldPProf returns true if we should be doing pprof runs.
+func (m *Metrics) shouldPProf() bool {
+       return m != nil && len(m.filebase) > 0
+}
+
 // makeBenchString makes a benchmark string consumable by Go's benchmarking tools.
 func makeBenchString(name string) string {
        needCap := true
@@ -145,3 +170,7 @@ func makeBenchString(name string) string {
        }
        return string(ret)
 }
+
+func makePProfFilename(filebase, name string) string {
+       return fmt.Sprintf("%s_%s.profile", filebase, makeBenchString(name))
+}
index 0448afb4d70ae3e47ecf136571f474078f8434e9..48d4d74046fde64d67a85a7e774745685fe63fe5 100644 (file)
@@ -22,6 +22,29 @@ func TestMakeBenchString(t *testing.T) {
        }
 }
 
+func TestPProfFlag(t *testing.T) {
+       tests := []struct {
+               name string
+               want bool
+       }{
+               {"", false},
+               {"foo", true},
+       }
+       for i, test := range tests {
+               b := New(GC, test.name)
+               if v := b.shouldPProf(); test.want != v {
+                       t.Errorf("test[%d] shouldPProf() == %v, want %v", i, v, test.want)
+               }
+       }
+}
+
+func TestPProfNames(t *testing.T) {
+       want := "foo_BenchmarkTest.profile"
+       if v := makePProfFilename("foo", "test"); v != want {
+               t.Errorf("makePProfFilename() == %q, want %q", v, want)
+       }
+}
+
 // Ensure that public APIs work with a nil Metrics object.
 func TestNilBenchmarkObject(t *testing.T) {
        var b *Metrics
index d791950202e3ae8e839ef255ecacdbf93fdbc9d5..5d27284aa1ee6c2d539741745252a3badf4557f0 100644 (file)
@@ -98,7 +98,8 @@ var (
        memprofile     = flag.String("memprofile", "", "write memory profile to `file`")
        memprofilerate = flag.Int64("memprofilerate", 0, "set runtime.MemProfileRate to `rate`")
 
-       benchmarkFlag = flag.String("benchmark", "", "set to 'mem' or 'cpu' to enable phase benchmarking")
+       benchmarkFlag     = flag.String("benchmark", "", "set to 'mem' or 'cpu' to enable phase benchmarking")
+       benchmarkFileFlag = flag.String("benchmarkprofile", "", "set to enable per-phase pprof profiling")
 )
 
 // Main is the main entry point for the linker code.
@@ -177,9 +178,9 @@ func Main(arch *sys.Arch, theArch Arch) {
        var bench *benchmark.Metrics
        if len(*benchmarkFlag) != 0 {
                if *benchmarkFlag == "mem" {
-                       bench = benchmark.New(benchmark.GC)
+                       bench = benchmark.New(benchmark.GC, *benchmarkFileFlag)
                } else if *benchmarkFlag == "cpu" {
-                       bench = benchmark.New(benchmark.NoGC)
+                       bench = benchmark.New(benchmark.NoGC, *benchmarkFileFlag)
                } else {
                        Errorf(nil, "unknown benchmark flag: %q", *benchmarkFlag)
                        usage()