Skip to content

Commit bbeefdd

Browse files
committed
ctxlock: add tracing and profiling hooks
Signed-off-by: Hank Donnay <hdonnay@redhat.com>
1 parent d7ef4a1 commit bbeefdd

3 files changed

Lines changed: 44 additions & 8 deletions

File tree

pkg/ctxlock/ctxlock.go

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ import (
1212
"errors"
1313
"fmt"
1414
"runtime"
15+
"runtime/pprof"
16+
"runtime/trace"
1517
"strconv"
1618
"sync"
1719
"time"
@@ -37,11 +39,12 @@ func New(ctx context.Context, p *pgxpool.Pool) (*Locker, error) {
3739
panic(fmt.Sprintf("%s:%d: db lock pool not closed", file, line))
3840
})
3941
go l.run(ctx)
40-
go l.ping()
42+
go l.ping(ctx)
4143

4244
// Wait until a connection is established or the passed context times out.
4345
ready := make(chan struct{})
4446
go func() {
47+
pprof.SetGoroutineLabels(pprof.WithLabels(ctx, pprof.Labels(tracelabel, `ready`)))
4548
l.rc.L.Lock()
4649
defer l.rc.L.Unlock()
4750
for l.conn == nil && l.gen != -1 {
@@ -89,6 +92,8 @@ var (
8992

9093
// Run pulls a connection out of the pool and runs the reconnect loop.
9194
func (l *Locker) run(ctx context.Context) {
95+
ctx = pprof.WithLabels(ctx, pprof.Labels(tracelabel, `run`))
96+
pprof.SetGoroutineLabels(ctx)
9297
ctx = zlog.ContextWithValues(ctx, "component", "internal/ctxlock/Locker.run")
9398
for {
9499
tctx, done := context.WithTimeout(ctx, 5*time.Second)
@@ -165,7 +170,8 @@ func (l *Locker) reconnect(ctx context.Context) func(*pgxpool.Conn) error {
165170
}
166171

167172
// Ping wakes up the reconnect loop periodically.
168-
func (l *Locker) ping() {
173+
func (l *Locker) ping(ctx context.Context) {
174+
pprof.SetGoroutineLabels(pprof.WithLabels(ctx, pprof.Labels(tracelabel, `ping`)))
169175
t := time.NewTicker(5 * time.Second)
170176
defer t.Stop()
171177
leave := false
@@ -189,12 +195,13 @@ for tests. Currently, the logs always happen and throw off benchmarks.
189195

190196
// TryLock attempts to lock on the provided key.
191197
//
192-
// If unsuccessful, an already-cancelled Context will be returned.
198+
// If unsuccessful, an already-canceled Context will be returned.
193199
//
194200
// If successful, the returned Context will be parented to the passed-in Context
195201
// and also to the underlying connection used for the lock.
196202
func (l *Locker) TryLock(parent context.Context, key string) (context.Context, context.CancelFunc) {
197203
// zlog.Debug(parent).Str("key", key).Msg("trying lock")
204+
defer trace.StartRegion(parent, pkgname+".TryLock").End()
198205
child, done := context.WithCancel(parent)
199206
w, err := l.try(parent, key, done)
200207
switch {
@@ -218,9 +225,10 @@ func (l *Locker) TryLock(parent context.Context, key string) (context.Context, c
218225
}
219226

220227
// Lock attempts to obtain the named lock until it succeeds or the passed
221-
// Context is cancelled.
228+
// Context is canceled.
222229
func (l *Locker) Lock(parent context.Context, key string) (context.Context, context.CancelFunc) {
223230
// zlog.Debug(parent).Str("key", key).Msg("locking")
231+
defer trace.StartRegion(parent, pkgname+".Lock").End()
224232
child, done := context.WithCancel(parent)
225233
for wait := time.Duration(500 * time.Millisecond); ; backoff(&wait) {
226234
w, err := l.try(parent, key, done)
@@ -271,6 +279,9 @@ func backoff(w *time.Duration) {
271279
func (l *Locker) try(ctx context.Context, key string, cf context.CancelFunc) (*watcher, error) {
272280
const query = `SELECT lock FROM pg_try_advisory_lock($1) lock WHERE lock = true;`
273281
kb := keyify(key)
282+
// Ideally we'd set a profiling label for the key, but labels are not
283+
// recorded for user profiles.
284+
trace.Logf(ctx, pkgname+".try", "trying lock for %q (%016x)", key, kb)
274285
l.rc.L.Lock()
275286
defer l.rc.L.Unlock()
276287
var err error
@@ -297,7 +308,7 @@ func (l *Locker) try(ctx context.Context, key string, cf context.CancelFunc) (*w
297308
}
298309
l.cur[key] = struct{}{}
299310
w := newWatcher(l.unlock(ctx, key, kb, l.gen, cf))
300-
go w.Watch(l.gone)
311+
go w.Watch(ctx, l.gone)
301312
return w, nil
302313
}
303314

pkg/ctxlock/metrics.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package ctxlock
2+
3+
import "runtime/pprof"
4+
5+
// As a style note, this package avoids using the pprof.Do helper because it
6+
// makes the stack trace names *much* worse.
7+
8+
const (
9+
pkgname = `github.com/quay/claircore/pkg/ctxlock`
10+
tracelabel = pkgname + `.Locker`
11+
)
12+
13+
var profile = pprof.NewProfile(pkgname + `.Lock`)

pkg/ctxlock/watcher.go

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
package ctxlock
22

3-
import "sync"
3+
import (
4+
"context"
5+
"runtime/pprof"
6+
"sync"
7+
)
48

59
// A Watcher waits on two cancellation sources and makes sure to call the
610
// wrapped function as soon as possible.
@@ -13,17 +17,24 @@ type watcher struct {
1317
}
1418

1519
func newWatcher(onCancel func()) *watcher {
16-
return &watcher{
20+
w := &watcher{
1721
onCancel: onCancel,
1822
done: make(chan struct{}),
1923
}
24+
// Capture the call to Lock or TryLock.
25+
profile.Add(w, 3)
26+
return w
2027
}
2128

2229
// Watch on the provided channel.
23-
func (w *watcher) Watch(ch <-chan struct{}) {
30+
//
31+
// This function should be called as a new goroutine.
32+
// The provided context is used only for setting pprof labels.
33+
func (w *watcher) Watch(ctx context.Context, ch <-chan struct{}) {
2434
if ch == nil {
2535
panic("nil channel")
2636
}
37+
pprof.SetGoroutineLabels(pprof.WithLabels(ctx, pprof.Labels(tracelabel, `watch`)))
2738

2839
select {
2940
case <-ch:
@@ -37,4 +48,5 @@ func (w *watcher) Watch(ch <-chan struct{}) {
3748
func (w *watcher) Unwatch() {
3849
w.once.Do(w.onCancel)
3950
close(w.done)
51+
profile.Remove(w)
4052
}

0 commit comments

Comments
 (0)