Peter Weinberger uploaded a change:
https://go-review.googlesource.com/29650
runtime: Profile goroutines holding contended mutexes.
runtime.SetMutexProfileFraction(n int) will capture 1/n-th of stack
traces of goroutines holding contended mutexes. From runtime/pprof
pprot.Lookup("mutex").WriteTo(w, 1) write the accumulated
stack traces to w (in essentially the same format that blocking
profiling uses).
Change-Id: Ie0b54fa4226853d99aa42c14cb529ae586a8335a
---
M src/cmd/go/test.go
M src/cmd/go/testflag.go
M src/cmd/internal/pprof/profile/legacy_profile.go
M src/runtime/mgc.go
M src/runtime/mprof.go
M src/runtime/pprof/pprof.go
M src/runtime/pprof/pprof_test.go
M src/runtime/proc.go
M src/runtime/runtime2.go
M src/runtime/sema.go
M src/runtime/trace.go
M src/sync/mutex.go
M src/sync/mutex_test.go
M src/testing/testing.go
14 files changed, 374 insertions(+), 126 deletions(-)
diff --git a/src/cmd/go/test.go b/src/cmd/go/test.go
index 138d46c..7a670fd 100644
--- a/src/cmd/go/test.go
+++ b/src/cmd/go/test.go
@@ -237,6 +237,15 @@
To profile all memory allocations, use -test.memprofilerate=1
and pass --alloc_space flag to the pprof tool.
+ -mutexprotfile mutex.out
+ Write a mutex contention profile to the specified file
+ when all tests are complete.
+ Writes test binary as -c would.
+
+ -mutexprofilefraction n
+ Sample 1 in n stack traces of goroutines holding a
+ contended mutex.
+
-outputdir directory
Place output files from profiling in the specified directory,
by default the directory in which "go test" is running.
diff --git a/src/cmd/go/testflag.go b/src/cmd/go/testflag.go
index a65ed1f..cf4d2b4 100644
--- a/src/cmd/go/testflag.go
+++ b/src/cmd/go/testflag.go
@@ -50,6 +50,8 @@
{name: "memprofilerate", passToTest: true},
{name: "blockprofile", passToTest: true},
{name: "blockprofilerate", passToTest: true},
+ {name: "mutexprofile", passToTest: true},
+ {name: "mutexprofilefraction", passToTest: true},
{name: "outputdir", passToTest: true},
{name: "parallel", passToTest: true},
{name: "run", passToTest: true},
@@ -152,7 +154,7 @@
case "blockprofile", "cpuprofile", "memprofile":
testProfile = true
testNeedBinary = true
- case "trace":
+ case "mutexprofile", "trace":
testProfile = true
case "coverpkg":
testCover = true
diff --git a/src/cmd/internal/pprof/profile/legacy_profile.go
b/src/cmd/internal/pprof/profile/legacy_profile.go
index 8ccfe45..7cb8645 100644
--- a/src/cmd/internal/pprof/profile/legacy_profile.go
+++ b/src/cmd/internal/pprof/profile/legacy_profile.go
@@ -12,6 +12,7 @@
"bytes"
"fmt"
"io"
+ "log"
"math"
"regexp"
"strconv"
@@ -26,6 +27,7 @@
heapSampleRE = regexp.MustCompile(`(-?\d+): *(-?\d+) *\[ *(\d+): *(\d+)
*] @([ x0-9a-f]*)`)
contentionSampleRE = regexp.MustCompile(`(\d+) *(\d+) @([ x0-9a-f]*)`)
+ contentionAddrRE =
regexp.MustCompile(`^#\W+(\w+)\W+([^+]+)[+](\w+)\W+([^:]+):(.+)`)
hexNumberRE = regexp.MustCompile(`0x[0-9a-f]+`)
@@ -679,130 +681,151 @@
return int64(float64(count) * scale), int64(float64(size) * scale)
}
-// parseContention parses a contentionz profile and returns a newly
-// populated Profile.
+// parseContention parses a debug mutex or contention profile. There may
or may
+// not be a sampling period line, and there may or may not be detailed
stack traces
+// The order of the test corresponds to what synchonization_profiling.cc
writes.
+// It returns a new Profile.
func parseContention(b []byte) (p *Profile, err error) {
- r := bytes.NewBuffer(b)
- l, err := r.ReadString('\n')
- if err != nil {
- return nil, errUnrecognized
- }
-
- if !strings.HasPrefix(l, "--- contention") {
- return nil, errUnrecognized
- }
-
p = &Profile{
PeriodType: &ValueType{Type: "contentions", Unit: "count"},
- Period: 1,
SampleType: []*ValueType{
{Type: "contentions", Unit: "count"},
{Type: "delay", Unit: "nanoseconds"},
},
}
-
+ lines := strings.Split(string(b), "\n") // memory expensive
+ if len(lines) == 0 {
+ return nil, errUnrecognized
+ }
+ var nxtline int
+ l := lines[nxtline]
+ if len(lines) < 3 || (l != "--- mutex:" && l != "--- contention:") {
+ return nil, errUnrecognized
+ }
+ // attribute = values lines, without assumptions on the ordering
+ nxtline++
var cpuHz int64
- // Parse text of the form "attribute = value" before the samples.
- const delimiter = "="
- for {
- l, err = r.ReadString('\n')
+ for ; strings.Index(lines[nxtline], "=") != -1; nxtline++ {
+ flds := strings.Split(lines[nxtline], "=")
+ if len(flds) != 2 {
+ return nil, errUnrecognized
+ }
+ key, val := strings.TrimSpace(flds[0]), strings.TrimSpace(flds[1])
+ value, err := strconv.ParseInt(val, 0, 64)
if err != nil {
- if err != io.EOF {
- return nil, err
- }
-
- if l == "" {
- break
- }
+ return nil, errUnrecognized
}
-
- if l = strings.TrimSpace(l); l == "" {
- continue
- }
-
- if strings.HasPrefix(l, "---") {
- break
- }
-
- attr := strings.SplitN(l, delimiter, 2)
- if len(attr) != 2 {
- break
- }
- key, val := strings.TrimSpace(attr[0]), strings.TrimSpace(attr[1])
- var err error
switch key {
case "cycles/second":
- if cpuHz, err = strconv.ParseInt(val, 0, 64); err != nil {
- return nil, errUnrecognized
- }
+ cpuHz = value
case "sampling period":
- if p.Period, err = strconv.ParseInt(val, 0, 64); err != nil {
- return nil, errUnrecognized
- }
+ p.Period = value
case "ms since reset":
- ms, err := strconv.ParseInt(val, 0, 64)
- if err != nil {
- return nil, errUnrecognized
- }
- p.DurationNanos = ms * 1000 * 1000
- case "format":
- // CPP contentionz profiles don't have format.
- return nil, errUnrecognized
- case "resolution":
- // CPP contentionz profiles don't have resolution.
- return nil, errUnrecognized
+ p.DurationNanos = value * 1000 * 1000
case "discarded samples":
- default:
- return nil, errUnrecognized
}
}
-
- locs := make(map[uint64]*Location)
- for {
- if l = strings.TrimSpace(l); strings.HasPrefix(l, "---") {
- break
- }
- value, addrs, err := parseContentionSample(l, p.Period, cpuHz)
- if err != nil {
- return nil, err
- }
- var sloc []*Location
- for _, addr := range addrs {
- // Addresses from stack traces point to the next instruction after
- // each call. Adjust by -1 to land somewhere on the actual call.
- addr--
- loc := locs[addr]
- if locs[addr] == nil {
- loc = &Location{
- Address: addr,
- }
- p.Location = append(p.Location, loc)
- locs[addr] = loc
+ if p.Period == 0 {
+ p.Period = 1
+ }
+ infos := map[uint64]addrInfo{}
+ stacks := []stackRec{}
+ // now there are blank lines, lines starting # and count lines
+ for i := nxtline; i < len(lines); i++ {
+ l := lines[i]
+ if l == "" || l == "#\t0x0" { // the latter case from go1.7
+ continue
+ } else if l[0] == '#' {
+ m := contentionAddrRE.FindStringSubmatch(l)
+ if len(m) != 6 {
+ log.Fatalf("found %d, %v, %q", len(m), m, l)
}
- sloc = append(sloc, loc)
- }
- p.Sample = append(p.Sample, &Sample{
- Value: value,
- Location: sloc,
- })
-
- if l, err = r.ReadString('\n'); err != nil {
- if err != io.EOF {
+ adr, err := strconv.ParseUint(m[1], 0, 64)
+ if err != nil {
+ log.Fatal(err)
+ }
+ if _, ok := infos[adr]; ok {
+ continue // already seen it
+ }
+ ln, err := strconv.ParseInt(m[5], 0, 64)
+ if err != nil {
+ log.Fatal(err)
+ }
+ infos[adr] = addrInfo{m[1], m[2], m[3], m[4], ln}
+ } else {
+ value, addrs, err := parseContentionSample(l, p.Period, cpuHz)
+ if err != nil {
+ // log something here?
return nil, err
}
- if l == "" {
- break
+ stacks = append(stacks, stackRec{value[0], value[1], addrs})
+ }
+ }
+ // we might not have had any # lines, just stacks, so fake the infos
+ if len(infos) == 0 {
+ for _, s := range stacks {
+ for _, adr := range s.stack {
+ adr-- // Address points to 1 after call
+ // (for # lines, the behavior changes at 1.7, so we did nothing)
+ infos[adr] = addrInfo{addr: fmt.Sprintf("0x%x", adr)}
}
}
}
+ // we now have everything. Fill in the Profile.
+ // We won't have any Mapping or Label.
+ // Function: self-contained. Line: Function
+ // Location: Line (maybe). Sample: Location
- if err = parseAdditionalSections(l, r, p); err != nil {
- return nil, err
+ // Does it matter if several Functions correspond to one real function?
+ // Do Function.IDs have to have any properties? Location.IDs?
+ // (other than uniqueness)
+ locmap := map[uint64]*Location{}
+ for k, v := range infos {
+ f := Function{ID: k, Name:
v.name, Filename: v.file, StartLine: v.line}
+ line := Line{Function: &f, Line: v.line}
+ loc := Location{ID: k, Address: k, Line: []Line{line}}
+ locmap[k] = &loc
+ p.Location = append(p.Location, &loc)
+ p.Function = append(p.Function, &f)
}
-
+ for _, st := range stacks {
+ s := Sample{Value: []int64{st.value, st.count}}
+ for _, i := range st.stack {
+ // sometimes the addresses match exactly, sometimes off by one
+ // This should be figured out!
+ if v, ok := locmap[i]; ok { // before go1.7
+ s.Location = append(s.Location, v)
+ } else if v, ok := locmap[i-1]; ok { // go 1.7
+ s.Location = append(s.Location, v)
+ }
+ }
+ p.Sample = append(p.Sample, &s)
+ }
return p, nil
}
+type stackRec struct {
+ value, count int64
+ stack []uint64
+}
+
+func (s stackRec) String() string {
+ ans := []string{fmt.Sprintf("%d,%d", s.value, s.count), "["}
+ for _, x := range s.stack {
+ ans = append(ans, fmt.Sprintf("0x%x", x))
+ }
+ ans = append(ans, "]")
+ return strings.Join(ans, " ")
+}
+
+type addrInfo struct {
+ addr string // 0x4b8254
+ name string // sync_test.BenchmarkMutexSpin.func1
+ offset string // 0xdb
+ file string
+ line int64 // 191
+}
+
// parseContentionSample parses a single row from a contention profile
// into a new Sample.
func parseContentionSample(line string, period, cpuHz int64) (value
[]int64, addrs []uint64, err error) {
diff --git a/src/runtime/mgc.go b/src/runtime/mgc.go
index ce7ac63..8a1fbef 100644
--- a/src/runtime/mgc.go
+++ b/src/runtime/mgc.go
@@ -927,7 +927,7 @@
// another thread.
useStartSema := mode == gcBackgroundMode
if useStartSema {
- semacquire(&work.startSema, false)
+ semacquire(&work.startSema, 0)
// Re-check transition condition under transition lock.
if !gcShouldStart(forceTrigger) {
semrelease(&work.startSema)
@@ -948,7 +948,7 @@
}
// Ok, we're doing it! Stop everybody else
- semacquire(&worldsema, false)
+ semacquire(&worldsema, 0)
if trace.enabled {
traceGCStart()
@@ -1062,7 +1062,7 @@
// by mark termination.
func gcMarkDone() {
top:
- semacquire(&work.markDoneSema, false)
+ semacquire(&work.markDoneSema, 0)
// Re-check transition condition under transition lock.
if !(gcphase == _GCmark && work.nwait == work.nproc
&& !gcMarkWorkAvailable(nil)) {
diff --git a/src/runtime/mprof.go b/src/runtime/mprof.go
index 37b5e1b..d09b154 100644
--- a/src/runtime/mprof.go
+++ b/src/runtime/mprof.go
@@ -22,6 +22,7 @@
// profile types
memProfile bucketType = 1 + iota
blockProfile
+ mutexProfile
// size of bucket hash table
buckHashSize = 179999
@@ -43,7 +44,7 @@
type bucket struct {
next *bucket
allnext *bucket
- typ bucketType // memBucket or blockBucket
+ typ bucketType // memBucket or blockBucket (includes mutexProfile)
hash uintptr
size uintptr
nstk uintptr
@@ -83,7 +84,7 @@
}
// A blockRecord is the bucket data for a bucket of type blockProfile,
-// part of the blocking profile.
+// part of the blocking profile. (also used for mutex profiles)
type blockRecord struct {
count int64
cycles int64
@@ -92,6 +93,7 @@
var (
mbuckets *bucket // memory profile buckets
bbuckets *bucket // blocking profile buckets
+ xbuckets *bucket // mutex profile buckets
buckhash *[179999]*bucket
bucketmem uintptr
)
@@ -104,7 +106,7 @@
throw("invalid profile bucket type")
case memProfile:
size += unsafe.Sizeof(memRecord{})
- case blockProfile:
+ case blockProfile, mutexProfile:
size += unsafe.Sizeof(blockRecord{})
}
@@ -132,7 +134,7 @@
// bp returns the blockRecord associated with the blockProfile bucket b.
func (b *bucket) bp() *blockRecord {
- if b.typ != blockProfile {
+ if b.typ != blockProfile && b.typ != mutexProfile {
throw("bad use of bucket.bp")
}
data := add(unsafe.Pointer(b),
unsafe.Sizeof(*b)+b.nstk*unsafe.Sizeof(uintptr(0)))
@@ -184,6 +186,9 @@
if typ == memProfile {
b.allnext = mbuckets
mbuckets = b
+ } else if typ == mutexProfile {
+ b.allnext = xbuckets
+ xbuckets = b
} else {
b.allnext = bbuckets
bbuckets = b
@@ -288,10 +293,20 @@
if cycles <= 0 {
cycles = 1
}
+ if blocksampled(cycles) {
+ saveevent(cycles, skip+1, blockProfile, &blockprofilerate)
+ }
+}
+
+func blocksampled(cycles int64) bool {
rate := int64(atomic.Load64(&blockprofilerate))
if rate <= 0 || (rate > cycles && int64(fastrand())%rate > cycles) {
- return
+ return false
}
+ return true
+}
+
+func saveevent(cycles int64, skip int, which bucketType, ratep *uint64) {
gp := getg()
var nstk int
var stk [maxStack]uintptr
@@ -301,10 +316,38 @@
nstk = gcallers(gp.m.curg, skip, stk[:])
}
lock(&proflock)
- b := stkbucket(blockProfile, 0, stk[:nstk], true)
+ b := stkbucket(which, 0, stk[:nstk], true)
b.bp().count++
b.bp().cycles += cycles
unlock(&proflock)
+}
+
+var mutexprofilerate uint64 // fraction sampled
+
+// SetMutexProfileFraction controls the fraction of mutex contention events
+// that are reported in the mutex profile. On average 1/rate events are
+// reported. The previous rate is returned.
+//
+// To turn off profiling entirely, pass rate 0.
+// To just read the current rate, pass rate -1.
+func SetMutexProfileFraction(rate int) int {
+ if rate < 0 {
+ return int(mutexprofilerate)
+ }
+ old := mutexprofilerate
+ atomic.Store64(&mutexprofilerate, uint64(rate))
+ return int(old)
+}
+
+//go:linkname mutexevent sync.event
+func mutexevent(cycles int64, skip int) {
+ rate := int64(atomic.Load64(&mutexprofilerate))
+ if rate > 0 && int64(fastrand())%rate == 0 {
+ if cycles <= 0 {
+ cycles = 1
+ }
+ saveevent(cycles, skip+1, mutexProfile, &mutexprofilerate)
+ }
}
// Go interface to profile data.
@@ -491,6 +534,35 @@
return
}
+// MutexProfile returns n, the number of records in the current mutex
profile.
+// If len(p) >= n, MutexPrifle copies the profile into p and return s,
true.
+// Otherwise, MutexProfile does not change p, and returns n, false.
+//
+// Most clients should use the runtime/pprof package
+// instead of calling MutexProfile directly.
+func MutexProfile(p []BlockProfileRecord) (n int, ok bool) {
+ lock(&proflock)
+ for b := xbuckets; b != nil; b = b.allnext {
+ n++
+ }
+ if n <= len(p) {
+ ok = true
+ for b := xbuckets; b != nil; b = b.allnext {
+ bp := b.bp()
+ r := &p[0]
+ r.Count = int64(bp.count)
+ r.Cycles = bp.cycles
+ i := copy(r.Stack0[:], b.stk())
+ for ; i < len(r.Stack0); i++ {
+ r.Stack0[i] = 0
+ }
+ p = p[1:]
+ }
+ }
+ unlock(&proflock)
+ return
+}
+
// ThreadCreateProfile returns n, the number of records in the thread
creation profile.
// If len(p) >= n, ThreadCreateProfile copies the profile into p and
returns n, true.
// If len(p) < n, ThreadCreateProfile does not change p and returns n,
false.
diff --git a/src/runtime/pprof/pprof.go b/src/runtime/pprof/pprof.go
index 1fc9568..3c71fdc 100644
--- a/src/runtime/pprof/pprof.go
+++ b/src/runtime/pprof/pprof.go
@@ -152,6 +152,12 @@
write: writeBlock,
}
+var mutexProfile = &Profile{
+ name: "mutex",
+ count: countMutex,
+ write: writeMutex,
+}
+
func lockProfiles() {
profiles.mu.Lock()
if profiles.m == nil {
@@ -161,6 +167,7 @@
"threadcreate": threadcreateProfile,
"heap": heapProfile,
"block": blockProfile,
+ "mutex": mutexProfile,
}
}
}
@@ -747,6 +754,12 @@
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
@@ -790,4 +803,49 @@
return b.Flush()
}
+// writeMutex writes the current mutex profile to w.
+func writeMutex(w io.Writer, debug int) error {
+ // 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.Sort(byCycles(p))
+
+ 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
diff --git a/src/runtime/pprof/pprof_test.go
b/src/runtime/pprof/pprof_test.go
index a093015..4f2a490 100644
--- a/src/runtime/pprof/pprof_test.go
+++ b/src/runtime/pprof/pprof_test.go
@@ -592,6 +592,40 @@
mu.Unlock()
}
+func TestMutexProfile(t *testing.T) {
+ old := runtime.SetMutexProfileFraction(1)
+ defer runtime.SetMutexProfileFraction(old)
+ if old != 0 {
+ t.Fatalf("need MutexProfileRate 0, got %d", old)
+ }
+
+ blockMutex()
+
+ var w bytes.Buffer
+ Lookup("mutex").WriteTo(&w, 1)
+ prof := w.String()
+
+ if !strings.HasPrefix(prof, "--- mutex:\ncycles/second=") {
+ t.Errorf("Bad profile header:\n%v", prof)
+ }
+ prof = strings.Trim(prof, "\n")
+ lines := strings.Split(prof, "\n")
+ if len(lines) != 6 {
+ t.Errorf("expected 6 lines, got %d %q\n%s", len(lines), prof, prof)
+ }
+ if len(lines) < 6 {
+ return
+ }
+ r2 := "^[0-9]+ 1 @ 0x[0-9a-f x]+$"
+ if ok, err := regexp.MatchString(r2, lines[3]); err != nil || !ok {
+ t.Errorf("%q didn't match %q", lines[3], r2)
+ }
+ r3 := "^#.*runtime/pprof_test.blockMutex.*$"
+ if ok, err := regexp.MatchString(r3, lines[5]); err != nil || !ok {
+ t.Errorf("%q didn't match %q", lines[5], r3)
+ }
+}
+
func func1(c chan int) { <-c }
func func2(c chan int) { <-c }
func func3(c chan int) { <-c }
diff --git a/src/runtime/proc.go b/src/runtime/proc.go
index 4dd2bd2..92f5a3a 100644
--- a/src/runtime/proc.go
+++ b/src/runtime/proc.go
@@ -903,7 +903,7 @@
// in panic or being exited, this may not reliably stop all
// goroutines.
func stopTheWorld(reason string) {
- semacquire(&worldsema, false)
+ semacquire(&worldsema, 0)
getg().m.preemptoff = reason
systemstack(stopTheWorldWithSema)
}
@@ -926,7 +926,7 @@
// preemption first and then should stopTheWorldWithSema on the system
// stack:
//
-// semacquire(&worldsema, false)
+// semacquire(&worldsema, 0)
// m.preemptoff = "reason"
// systemstack(stopTheWorldWithSema)
//
diff --git a/src/runtime/runtime2.go b/src/runtime/runtime2.go
index 2e88674..683156d 100644
--- a/src/runtime/runtime2.go
+++ b/src/runtime/runtime2.go
@@ -256,6 +256,7 @@
// The following fields are never accessed concurrently.
// waitlink is only accessed by g.
+ acquiretime int64
releasetime int64
ticket uint32
waitlink *sudog // g.waiting list
diff --git a/src/runtime/sema.go b/src/runtime/sema.go
index 45fbbca..cbb3e35 100644
--- a/src/runtime/sema.go
+++ b/src/runtime/sema.go
@@ -44,17 +44,22 @@
//go:linkname sync_runtime_Semacquire sync.runtime_Semacquire
func sync_runtime_Semacquire(addr *uint32) {
- semacquire(addr, true)
+ semacquire(addr, semaBlockProfile)
}
//go:linkname net_runtime_Semacquire net.runtime_Semacquire
func net_runtime_Semacquire(addr *uint32) {
- semacquire(addr, true)
+ semacquire(addr, semaBlockProfile)
}
//go:linkname sync_runtime_Semrelease sync.runtime_Semrelease
func sync_runtime_Semrelease(addr *uint32) {
semrelease(addr)
+}
+
+//go:linkname sync_runtime_SemacquireMutex sync.runtime_SemacquireMutex
+func sync_runtime_SemacquireMutex(addr *uint32) {
+ semacquire(addr, semaBlockProfile|semaMutexProfile)
}
//go:linkname net_runtime_Semrelease net.runtime_Semrelease
@@ -69,8 +74,13 @@
goready(s.g, traceskip)
}
+const (
+ semaBlockProfile = 1 << iota
+ semaMutexProfile
+)
+
// Called from runtime.
-func semacquire(addr *uint32, profile bool) {
+func semacquire(addr *uint32, profile int) {
gp := getg()
if gp != gp.m.curg {
throw("semacquire not on the G stack")
@@ -91,9 +101,16 @@
root := semroot(addr)
t0 := int64(0)
s.releasetime = 0
- if profile && blockprofilerate > 0 {
+ s.acquiretime = 0
+ if profile&semaBlockProfile != 0 && blockprofilerate > 0 {
t0 = cputicks()
s.releasetime = -1
+ }
+ if profile&semaMutexProfile != 0 && mutexprofilerate > 0 {
+ if t0 == 0 {
+ t0 = cputicks()
+ }
+ s.acquiretime = t0
}
for {
lock(&root.lock)
@@ -148,6 +165,15 @@
}
unlock(&root.lock)
if s != nil {
+ if s.acquiretime != 0 {
+ t0 := cputicks()
+ for x := root.head; x != nil; x = x.next {
+ if x.elem == unsafe.Pointer(addr) {
+ x.acquiretime = t0
+ }
+ }
+ mutexevent(t0-s.acquiretime, 3)
+ }
readyWithTime(s, 5)
}
}
diff --git a/src/runtime/trace.go b/src/runtime/trace.go
index 0b3741f..684b37d 100644
--- a/src/runtime/trace.go
+++ b/src/runtime/trace.go
@@ -288,7 +288,7 @@
// The world is started but we've set trace.shutdown, so new tracing
can't start.
// Wait for the trace reader to flush pending buffers and stop.
- semacquire(&trace.shutdownSema, false)
+ semacquire(&trace.shutdownSema, 0)
if raceenabled {
raceacquire(unsafe.Pointer(&trace.shutdownSema))
}
diff --git a/src/sync/mutex.go b/src/sync/mutex.go
index 9089279..2693578 100644
--- a/src/sync/mutex.go
+++ b/src/sync/mutex.go
@@ -82,7 +82,7 @@
if old&mutexLocked == 0 {
break
}
- runtime_Semacquire(&m.sema)
+ runtime_SemacquireMutex(&m.sema)
awoke = true
iter = 0
}
@@ -127,3 +127,5 @@
old = m.state
}
}
+
+func runtime_SemacquireMutex(*uint32)
diff --git a/src/sync/mutex_test.go b/src/sync/mutex_test.go
index 91a4855..e8bbd38 100644
--- a/src/sync/mutex_test.go
+++ b/src/sync/mutex_test.go
@@ -61,6 +61,10 @@
}
func TestMutex(t *testing.T) {
+ if n := runtime.SetMutexProfileFraction(1); n != 0 {
+ t.Logf("got mutexrate %d expected 0", n)
+ }
+ defer runtime.SetMutexProfileFraction(0)
m := new(Mutex)
c := make(chan bool)
for i := 0; i < 10; i++ {
diff --git a/src/testing/testing.go b/src/testing/testing.go
index 4a4cbcc..08bd877 100644
--- a/src/testing/testing.go
+++ b/src/testing/testing.go
@@ -233,19 +233,21 @@
outputDir = flag.String("test.outputdir", "", "directory in which to
write profiles")
// Report as tests are run; default is silent for success.
- chatty = flag.Bool("test.v", false, "verbose: print additional
output")
- count = flag.Uint("test.count", 1, "run tests and benchmarks
`n` times")
- coverProfile = flag.String("test.coverprofile", "", "write a coverage
profile to the named file after execution")
- match = flag.String("test.run", "", "regular expression to
select tests and examples to run")
- memProfile = flag.String("test.memprofile", "", "write a memory
profile to the named file after execution")
- memProfileRate = flag.Int("test.memprofilerate", 0, "if >=0, sets
runtime.MemProfileRate")
- cpuProfile = flag.String("test.cpuprofile", "", "write a cpu
profile to the named file during execution")
- blockProfile = flag.String("test.blockprofile", "", "write a
goroutine blocking profile to the named file after execution")
- blockProfileRate = flag.Int("test.blockprofilerate", 1, "if >= 0, calls
runtime.SetBlockProfileRate()")
- traceFile = flag.String("test.trace", "", "write an execution
trace to the named file after execution")
- timeout = flag.Duration("test.timeout", 0, "if positive, sets an
aggregate time limit for all tests")
- cpuListStr = flag.String("test.cpu", "", "comma-separated list of
number of CPUs to use for each test")
- parallel = flag.Int("test.parallel",
runtime.GOMAXPROCS(0), "maximum test parallelism")
+ chatty = flag.Bool("test.v", false, "verbose: print
additional output")
+ count = flag.Uint("test.count", 1, "run tests and
benchmarks `n` times")
+ coverProfile = flag.String("test.coverprofile", "", "write a
coverage profile to the named file after execution")
+ match = flag.String("test.run", "", "regular expression to
select tests and examples to run")
+ memProfile = flag.String("test.memprofile", "", "write a memory
profile to the named file after execution")
+ memProfileRate = flag.Int("test.memprofilerate", 0, "if >=0, sets
runtime.MemProfileRate")
+ cpuProfile = flag.String("test.cpuprofile", "", "write a cpu
profile to the named file during execution")
+ blockProfile = flag.String("test.blockprofile", "", "write a
goroutine blocking profile to the named file after execution")
+ blockProfileRate = flag.Int("test.blockprofilerate", 1, "if >= 0,
calls runtime.SetBlockProfileRate()")
+ mutexProfile = flag.String("test.mutexprofile", "", "write a
mutex contention profile to the named file after execution")
+ mutexProfileFraction = flag.Int("test.mutexprofilefraction", 1, "if >= 0,
calls runtime.SetMutexProfileFraction()")
+ traceFile = flag.String("test.trace", "", "write an execution
trace to the named file after execution")
+ timeout = flag.Duration("test.timeout", 0, "if positive,
sets an aggregate time limit for all tests")
+ cpuListStr = flag.String("test.cpu", "", "comma-separated list
of number of CPUs to use for each test")
+ parallel = flag.Int("test.parallel",
runtime.GOMAXPROCS(0), "maximum test parallelism")
haveExamples bool // are there examples?
@@ -840,6 +842,9 @@
if *blockProfile != "" && *blockProfileRate >= 0 {
runtime.SetBlockProfileRate(*blockProfileRate)
}
+ if *mutexProfile != "" && *mutexProfileFraction > 0 {
+ runtime.SetMutexProfileFraction(*mutexProfileFraction)
+ }
if *coverProfile != "" && cover.Mode == "" {
fmt.Fprintf(os.Stderr, "testing: cannot use -test.coverprofile because
test binary was not built with coverage enabled\n")
os.Exit(2)
@@ -879,6 +884,18 @@
}
f.Close()
}
+ if *mutexProfile != "" && *mutexProfileFraction >= 0 {
+ f, err := os.Create(toOutputDir(*mutexProfile))
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "testing: %s\n", err)
+ os.Exit(2)
+ }
+ if err = pprof.Lookup("mutex").WriteTo(f, 1); err != nil {
+ fmt.Fprintf(os.Stderr, "testing: can't write %s: %s\n", *blockProfile,
err)
+ os.Exit(2)
+ }
+ f.Close()
+ }
if cover.Mode != "" {
coverReport()
}
--
https://go-review.googlesource.com/29650