-
Notifications
You must be signed in to change notification settings - Fork 0
/
sync.go
147 lines (132 loc) · 3.25 KB
/
sync.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
// Package sync records the stack when locks are taken, and when locks are
// blocked on and exports them as pprof profiles "lockHolders" and
// "lockBlockers". if "net/http/pprof" is imported, you can view them at
// /debug/pprof/ on the default HTTP muxer.
//
// The API mirrors that of stdlib "sync". The package can be imported in place
// of "sync", and is enabled by setting the envvar PPROF_SYNC non-empty.
//
// Note that currently RWMutex is treated like a Mutex when the package is
// enabled.
package sync
import (
"fmt"
"io"
"net/http"
"os"
"runtime"
"runtime/pprof"
"sort"
"sync"
"text/tabwriter"
"time"
"github.com/anacrolix/missinggo"
)
var (
// Protects initialization and enabling of the package.
enableMu sync.Mutex
// Whether any of this package is to be active.
enabled = false
// Current lock holders.
lockHolders *pprof.Profile
// Those blocked on acquiring a lock.
lockBlockers *pprof.Profile
// Protects lockTimes.
lockTimesMu sync.Mutex
// The longest time the lock is held for any unique stack.
lockTimes map[[32]uintptr]time.Duration
)
type lockTimesSorter struct {
entries []lockTime
}
func (me *lockTimesSorter) Len() int { return len(me.entries) }
func (me *lockTimesSorter) Less(i, j int) bool {
return me.entries[i].held < me.entries[j].held
}
func (me *lockTimesSorter) Swap(i, j int) {
me.entries[i], me.entries[j] = me.entries[j], me.entries[i]
}
type lockTime struct {
stack [32]uintptr
held time.Duration
}
func sortedLockTimes() []lockTime {
var lts lockTimesSorter
lockTimesMu.Lock()
for stack, held := range lockTimes {
lts.entries = append(lts.entries, lockTime{stack, held})
}
lockTimesMu.Unlock()
sort.Sort(sort.Reverse(<s))
return lts.entries
}
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\n", elem.held)
missinggo.WriteStack(w, elem.stack[:])
}
}
func Enable() {
enableMu.Lock()
defer enableMu.Unlock()
if enabled {
return
}
lockTimes = make(map[[32]uintptr]time.Duration)
lockHolders = pprof.NewProfile("lockHolders")
lockBlockers = pprof.NewProfile("lockBlockers")
http.DefaultServeMux.HandleFunc("/debug/lockTimes", func(w http.ResponseWriter, r *http.Request) {
PrintLockTimes(w)
})
enabled = true
}
func init() {
if os.Getenv("PPROF_SYNC") != "" {
Enable()
}
}
type Mutex struct {
mu sync.Mutex
hold *int // Unique value for passing to pprof.
stack [32]uintptr // The stack for the current holder.
start time.Time // When the lock was obtained.
entries int // Number of entries returned from runtime.Callers.
}
func (m *Mutex) Lock() {
if !enabled {
m.mu.Lock()
return
}
v := new(int)
lockBlockers.Add(v, 0)
m.mu.Lock()
lockBlockers.Remove(v)
m.hold = v
lockHolders.Add(v, 0)
m.entries = runtime.Callers(2, m.stack[:])
m.start = time.Now()
}
func (m *Mutex) Unlock() {
if enabled {
lockHeld := time.Since(m.start)
var key [32]uintptr
copy(key[:], m.stack[:m.entries])
lockTimesMu.Lock()
if lockHeld > lockTimes[key] {
lockTimes[key] = lockHeld
}
lockTimesMu.Unlock()
lockHolders.Remove(m.hold)
}
m.mu.Unlock()
}
type WaitGroup struct {
sync.WaitGroup
}
type Cond struct {
sync.Cond
}