93 lines
2.4 KiB
Go
93 lines
2.4 KiB
Go
|
// Package sync is an extension of the stdlib "sync" package. It has extra
|
||
|
// functionality that helps debug the use of synchronization primitives. The
|
||
|
// package should be importable in place of "sync". The extra functionality
|
||
|
// can be enabled by calling Enable() or passing a non-empty PPROF_SYNC
|
||
|
// environment variable to the process.
|
||
|
//
|
||
|
// Several profiles are exposed on the default HTTP muxer (and to
|
||
|
// "/debug/pprof" when "net/http/pprof" is imported by the process).
|
||
|
// "lockHolders" lists the stack traces of goroutines that called Mutex.Lock
|
||
|
// that haven't subsequently been Unlocked. "lockBlockers" contains goroutines
|
||
|
// that are waiting to obtain locks. "/debug/lockTimes" or PrintLockTimes()
|
||
|
// shows the longest time a lock is held for each stack trace.
|
||
|
//
|
||
|
// Note that currently RWMutex is treated like a Mutex when the package is
|
||
|
// enabled.
|
||
|
package sync
|
||
|
|
||
|
import (
|
||
|
"fmt"
|
||
|
"io"
|
||
|
"net/http"
|
||
|
"os"
|
||
|
"runtime/pprof"
|
||
|
"strings"
|
||
|
"sync"
|
||
|
"text/tabwriter"
|
||
|
|
||
|
"github.com/anacrolix/missinggo"
|
||
|
)
|
||
|
|
||
|
var (
|
||
|
// Protects initialization and enabling of the package.
|
||
|
enableMu sync.Mutex
|
||
|
// Whether shared locks must be handled as exclusive locks.
|
||
|
noSharedLocking = false
|
||
|
contentionOn = false
|
||
|
lockTimesOn = false
|
||
|
// Current lock holders.
|
||
|
lockHolders *pprof.Profile
|
||
|
// Those blocked on acquiring a lock.
|
||
|
lockBlockers *pprof.Profile
|
||
|
)
|
||
|
|
||
|
// Writes out the longest time a Mutex remains locked for each stack trace
|
||
|
// that locks a Mutex.
|
||
|
func PrintLockTimes(w io.Writer) {
|
||
|
lockTimes := sortedLockTimes()
|
||
|
tw := tabwriter.NewWriter(w, 1, 8, 1, '\t', 0)
|
||
|
defer tw.Flush()
|
||
|
w = tw
|
||
|
for _, elem := range lockTimes {
|
||
|
fmt.Fprintf(w, "%s (%s * %d [%s, %s])\n", elem.Total, elem.MeanTime(), elem.Count, elem.Min, elem.Max)
|
||
|
missinggo.WriteStack(w, elem.stack[:])
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func Enable() {
|
||
|
EnableContention()
|
||
|
EnableLockTimes()
|
||
|
}
|
||
|
|
||
|
func EnableContention() {
|
||
|
lockHolders = pprof.NewProfile("lockHolders")
|
||
|
lockBlockers = pprof.NewProfile("lockBlockers")
|
||
|
noSharedLocking = true
|
||
|
contentionOn = true
|
||
|
}
|
||
|
|
||
|
func EnableLockTimes() {
|
||
|
lockStatsByStack = make(map[lockStackKey]lockStats)
|
||
|
http.DefaultServeMux.HandleFunc("/debug/lockTimes", func(w http.ResponseWriter, r *http.Request) {
|
||
|
PrintLockTimes(w)
|
||
|
})
|
||
|
noSharedLocking = true
|
||
|
lockTimesOn = true
|
||
|
}
|
||
|
|
||
|
func init() {
|
||
|
env := os.Getenv("PPROF_SYNC")
|
||
|
all := true
|
||
|
if strings.Contains(env, "times") {
|
||
|
EnableLockTimes()
|
||
|
all = false
|
||
|
}
|
||
|
if strings.Contains(env, "contention") {
|
||
|
EnableContention()
|
||
|
all = false
|
||
|
}
|
||
|
if all && env != "" {
|
||
|
Enable()
|
||
|
}
|
||
|
}
|