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>
import (
"fmt"
"io"
+ "os"
"runtime"
+ "runtime/pprof"
"time"
"unicode"
)
)
type Metrics struct {
- gc Flags
- marks []*mark
- curMark *mark
+ gc Flags
+ marks []*mark
+ curMark *mark
+ filebase string
+ pprofFile *os.File
}
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")
// 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.
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()
}
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
}
return string(ret)
}
+
+func makePProfFilename(filebase, name string) string {
+ return fmt.Sprintf("%s_%s.profile", filebase, makeBenchString(name))
+}
}
}
+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
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.
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()