summaryrefslogtreecommitdiff
path: root/libgo/go/runtime/pprof/pprof.go
diff options
context:
space:
mode:
Diffstat (limited to 'libgo/go/runtime/pprof/pprof.go')
-rw-r--r--libgo/go/runtime/pprof/pprof.go221
1 files changed, 132 insertions, 89 deletions
diff --git a/libgo/go/runtime/pprof/pprof.go b/libgo/go/runtime/pprof/pprof.go
index 0a58bafd85f..0db1dedd7ac 100644
--- a/libgo/go/runtime/pprof/pprof.go
+++ b/libgo/go/runtime/pprof/pprof.go
@@ -73,13 +73,15 @@ import (
"bufio"
"bytes"
"fmt"
+ "internal/pprof/profile"
"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.
@@ -99,6 +101,7 @@ import (
// heap - a sampling of all heap allocations
// threadcreate - stack traces that led to the creation of new OS threads
// block - stack traces that led to blocking on synchronization primitives
+// mutex - stack traces of holders of contended mutexes
//
// These predefined profiles maintain themselves and panic on an explicit
// Add or Remove method call.
@@ -152,6 +155,12 @@ var blockProfile = &Profile{
write: writeBlock,
}
+var mutexProfile = &Profile{
+ name: "mutex",
+ count: countMutex,
+ write: writeMutex,
+}
+
func lockProfiles() {
profiles.mu.Lock()
if profiles.m == nil {
@@ -161,6 +170,7 @@ func lockProfiles() {
"threadcreate": threadcreateProfile,
"heap": heapProfile,
"block": blockProfile,
+ "mutex": mutexProfile,
}
}
}
@@ -202,21 +212,15 @@ func Profiles() []*Profile {
lockProfiles()
defer unlockProfiles()
- var all []*Profile
+ all := make([]*Profile, 0, len(profiles.m))
for _, p := range profiles.m {
all = append(all, p)
}
- sort.Sort(byName(all))
+ sort.Slice(all, func(i, j int) bool { return all[i].name < all[j].name })
return all
}
-type byName []*Profile
-
-func (x byName) Len() int { return len(x) }
-func (x byName) Swap(i, j int) { x[i], x[j] = x[j], x[i] }
-func (x byName) Less(i, j int) bool { return x[i].name < x[j].name }
-
// Name returns this profile's name, which can be passed to Lookup to reobtain the profile.
func (p *Profile) Name() string {
return p.name
@@ -299,7 +303,7 @@ func (p *Profile) WriteTo(w io.Writer, debug int) error {
}
// Obtain consistent snapshot under lock; then process without lock.
- var all [][]uintptr
+ all := make([][]uintptr, 0, len(p.m))
p.mu.Lock()
for _, stk := range p.m {
all = append(all, stk)
@@ -337,17 +341,8 @@ type countProfile interface {
}
// printCountProfile prints a countProfile at the specified debug level.
+// The profile will be in compressed proto format unless debug is nonzero.
func printCountProfile(w io.Writer, debug int, name string, p countProfile) error {
- b := bufio.NewWriter(w)
- var tw *tabwriter.Writer
- w = b
- if debug > 0 {
- tw = tabwriter.NewWriter(w, 1, 8, 1, '\t', 0)
- w = tw
- }
-
- fmt.Fprintf(w, "%s profile: total %d\n", name, p.Len())
-
// Build count of each stack.
var buf bytes.Buffer
key := func(stk []uintptr) string {
@@ -373,17 +368,47 @@ func printCountProfile(w io.Writer, debug int, name string, p countProfile) erro
sort.Sort(&keysByCount{keys, count})
- for _, k := range keys {
- fmt.Fprintf(w, "%d %s\n", count[k], k)
- if debug > 0 {
- printStackRecord(w, p.Stack(index[k]), false)
+ if debug > 0 {
+ // Print debug profile in legacy format
+ tw := tabwriter.NewWriter(w, 1, 8, 1, '\t', 0)
+ fmt.Fprintf(tw, "%s profile: total %d\n", name, p.Len())
+ for _, k := range keys {
+ fmt.Fprintf(tw, "%d %s\n", count[k], k)
+ printStackRecord(tw, p.Stack(index[k]), false)
}
+ return tw.Flush()
}
- if tw != nil {
- tw.Flush()
+ // Output profile in protobuf form.
+ prof := &profile.Profile{
+ PeriodType: &profile.ValueType{Type: name, Unit: "count"},
+ Period: 1,
+ Sample: make([]*profile.Sample, 0, len(keys)),
+ SampleType: []*profile.ValueType{{Type: name, Unit: "count"}},
}
- return b.Flush()
+ locMap := make(map[uintptr]*profile.Location)
+ for _, k := range keys {
+ stk := p.Stack(index[k])
+ c := count[k]
+ locs := make([]*profile.Location, len(stk))
+ for i, addr := range stk {
+ loc := locMap[addr]
+ if loc == nil {
+ loc = &profile.Location{
+ ID: uint64(len(locMap) + 1),
+ Address: uint64(addr - 1),
+ }
+ prof.Location = append(prof.Location, loc)
+ locMap[addr] = loc
+ }
+ locs[i] = loc
+ }
+ prof.Sample = append(prof.Sample, &profile.Sample{
+ Location: locs,
+ Value: []int64{int64(c)},
+ })
+ }
+ return prof.Write(w)
}
// keysByCount sorts keys with higher counts first, breaking ties by key string order.
@@ -449,12 +474,6 @@ func printStackRecord(w io.Writer, stk []uintptr, allFrames bool) {
// Interface to system profiles.
-type byInUseBytes []runtime.MemProfileRecord
-
-func (x byInUseBytes) Len() int { return len(x) }
-func (x byInUseBytes) Swap(i, j int) { x[i], x[j] = x[j], x[i] }
-func (x byInUseBytes) Less(i, j int) bool { return x[i].InUseBytes() > x[j].InUseBytes() }
-
// WriteHeapProfile is shorthand for Lookup("heap").WriteTo(w, 0).
// It is preserved for backwards compatibility.
func WriteHeapProfile(w io.Writer) error {
@@ -490,15 +509,16 @@ func writeHeap(w io.Writer, debug int) error {
// Profile grew; try again.
}
- sort.Sort(byInUseBytes(p))
+ if debug == 0 {
+ pp := protopprof.EncodeMemProfile(p, int64(runtime.MemProfileRate), time.Now())
+ return pp.Write(w)
+ }
+
+ sort.Slice(p, func(i, j int) bool { return p[i].InUseBytes() > p[j].InUseBytes() })
b := bufio.NewWriter(w)
- var tw *tabwriter.Writer
- w = b
- if debug > 0 {
- tw = tabwriter.NewWriter(w, 1, 8, 1, '\t', 0)
- w = tw
- }
+ tw := tabwriter.NewWriter(b, 1, 8, 1, '\t', 0)
+ w = tw
var total runtime.MemProfileRecord
for i := range p {
@@ -526,9 +546,7 @@ func writeHeap(w io.Writer, debug int) error {
fmt.Fprintf(w, " %#x", pc)
}
fmt.Fprintf(w, "\n")
- if debug > 0 {
- printStackRecord(w, r.Stack(), false)
- }
+ printStackRecord(w, r.Stack(), false)
}
// Print memstats information too.
@@ -554,15 +572,15 @@ func writeHeap(w io.Writer, debug int) error {
fmt.Fprintf(w, "# MSpan = %d / %d\n", s.MSpanInuse, s.MSpanSys)
fmt.Fprintf(w, "# MCache = %d / %d\n", s.MCacheInuse, s.MCacheSys)
fmt.Fprintf(w, "# BuckHashSys = %d\n", s.BuckHashSys)
+ fmt.Fprintf(w, "# GCSys = %d\n", s.GCSys)
+ fmt.Fprintf(w, "# OtherSys = %d\n", s.OtherSys)
fmt.Fprintf(w, "# NextGC = %d\n", s.NextGC)
fmt.Fprintf(w, "# PauseNs = %d\n", s.PauseNs)
fmt.Fprintf(w, "# NumGC = %d\n", s.NumGC)
fmt.Fprintf(w, "# DebugGC = %v\n", s.DebugGC)
- if tw != nil {
- tw.Flush()
- }
+ tw.Flush()
return b.Flush()
}
@@ -686,49 +704,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
}
@@ -747,18 +745,18 @@ func StopCPUProfile() {
<-cpu.done
}
-type byCycles []runtime.BlockProfileRecord
-
-func (x byCycles) Len() int { return len(x) }
-func (x byCycles) Swap(i, j int) { x[i], x[j] = x[j], x[i] }
-func (x byCycles) Less(i, j int) bool { return x[i].Cycles > x[j].Cycles }
-
// countBlock returns the number of records in the blocking profile.
func countBlock() int {
n, _ := runtime.BlockProfile(nil)
return n
}
+// countMutex returns the number of records in the mutex profile.
+func countMutex() int {
+ n, _ := runtime.MutexProfile(nil)
+ return n
+}
+
// writeBlock writes the current blocking profile to w.
func writeBlock(w io.Writer, debug int) error {
var p []runtime.BlockProfileRecord
@@ -772,7 +770,7 @@ func writeBlock(w io.Writer, debug int) error {
}
}
- sort.Sort(byCycles(p))
+ sort.Slice(p, func(i, j int) bool { return p[i].Cycles > p[j].Cycles })
b := bufio.NewWriter(w)
var tw *tabwriter.Writer
@@ -802,4 +800,49 @@ func writeBlock(w io.Writer, debug int) error {
return b.Flush()
}
+// writeMutex writes the current mutex profile to w.
+func writeMutex(w io.Writer, debug int) error {
+ // TODO(pjw): too much common code with writeBlock. FIX!
+ var p []runtime.BlockProfileRecord
+ n, ok := runtime.MutexProfile(nil)
+ for {
+ p = make([]runtime.BlockProfileRecord, n+50)
+ n, ok = runtime.MutexProfile(p)
+ if ok {
+ p = p[:n]
+ break
+ }
+ }
+
+ sort.Slice(p, func(i, j int) bool { return p[i].Cycles > p[j].Cycles })
+
+ b := bufio.NewWriter(w)
+ var tw *tabwriter.Writer
+ w = b
+ if debug > 0 {
+ tw = tabwriter.NewWriter(w, 1, 8, 1, '\t', 0)
+ w = tw
+ }
+
+ fmt.Fprintf(w, "--- mutex:\n")
+ fmt.Fprintf(w, "cycles/second=%v\n", runtime_cyclesPerSecond())
+ fmt.Fprintf(w, "sampling period=%d\n", runtime.SetMutexProfileFraction(-1))
+ for i := range p {
+ r := &p[i]
+ fmt.Fprintf(w, "%v %v @", r.Cycles, r.Count)
+ for _, pc := range r.Stack() {
+ fmt.Fprintf(w, " %#x", pc)
+ }
+ fmt.Fprint(w, "\n")
+ if debug > 0 {
+ printStackRecord(w, r.Stack(), true)
+ }
+ }
+
+ if tw != nil {
+ tw.Flush()
+ }
+ return b.Flush()
+}
+
func runtime_cyclesPerSecond() int64