Skip to content

Commit 45b19d3

Browse files
committed
Improve cache related trace logging.
This adds trace logs to the solver's cache manager to assist debugging cache misses+hits. It also replace the LazyStackTrace struct with a function that returns a stack trace string only if trace level is enabled, an empty string otherwise. This makes it easier to use with logrus hooks that pass the fields around to different goroutines without needing to try to "unlazy" the fields first. Signed-off-by: Erik Sipsma <[email protected]>
1 parent a1deb3e commit 45b19d3

File tree

6 files changed

+148
-23
lines changed

6 files changed

+148
-23
lines changed

cache/refs.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -444,7 +444,7 @@ func (cr *cacheRecord) remove(ctx context.Context, removeSnapshot bool) (rerr er
444444
"id": cr.ID(),
445445
"refCount": len(cr.refs),
446446
"removeSnapshot": removeSnapshot,
447-
"stack": bklog.LazyStackTrace{},
447+
"stack": bklog.TraceLevelOnlyStack(),
448448
})
449449
if rerr != nil {
450450
l = l.WithError(rerr)
@@ -488,7 +488,7 @@ func (sr *immutableRef) traceLogFields() logrus.Fields {
488488
"refID": fmt.Sprintf("%p", sr),
489489
"newRefCount": len(sr.refs),
490490
"mutable": false,
491-
"stack": bklog.LazyStackTrace{},
491+
"stack": bklog.TraceLevelOnlyStack(),
492492
}
493493
if sr.equalMutable != nil {
494494
m["equalMutableID"] = sr.equalMutable.ID()
@@ -628,7 +628,7 @@ func (sr *mutableRef) traceLogFields() logrus.Fields {
628628
"refID": fmt.Sprintf("%p", sr),
629629
"newRefCount": len(sr.refs),
630630
"mutable": true,
631-
"stack": bklog.LazyStackTrace{},
631+
"stack": bklog.TraceLevelOnlyStack(),
632632
}
633633
if sr.equalMutable != nil {
634634
m["equalMutableID"] = sr.equalMutable.ID()

solver/cachekey.go

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,19 +24,56 @@ type CacheKeyWithSelector struct {
2424
CacheKey ExportableCacheKey
2525
}
2626

27+
func (ck CacheKeyWithSelector) TraceFields() map[string]any {
28+
fields := ck.CacheKey.TraceFields()
29+
fields["selector"] = ck.Selector.String()
30+
return fields
31+
}
32+
2733
type CacheKey struct {
2834
mu sync.RWMutex
2935

30-
ID string
31-
deps [][]CacheKeyWithSelector // only [][]*inMemoryCacheKey
36+
ID string
37+
deps [][]CacheKeyWithSelector
38+
// digest is the digest returned by the CacheMap implementation of this op
3239
digest digest.Digest
40+
// vtx is the LLB digest that this op was created for
3341
vtx digest.Digest
3442
output Index
3543
ids map[*cacheManager]string
3644

3745
indexIDs []string
3846
}
3947

48+
func (ck *CacheKey) TraceFields() map[string]any {
49+
ck.mu.RLock()
50+
defer ck.mu.RUnlock()
51+
idsMap := map[string]string{}
52+
for cm, id := range ck.ids {
53+
idsMap[cm.ID()] = id
54+
}
55+
56+
// don't recurse more than one level in showing deps
57+
depsMap := make([]map[string]string, len(ck.deps))
58+
for i, deps := range ck.deps {
59+
depsMap[i] = map[string]string{}
60+
for _, ck := range deps {
61+
depsMap[i]["id"] = ck.CacheKey.ID
62+
depsMap[i]["selector"] = ck.Selector.String()
63+
}
64+
}
65+
66+
return map[string]any{
67+
"id": ck.ID,
68+
"digest": ck.digest,
69+
"vtx": ck.vtx,
70+
"output": ck.output,
71+
"indexIDs": ck.indexIDs,
72+
"ids": idsMap,
73+
"deps": depsMap,
74+
}
75+
}
76+
4077
func (ck *CacheKey) Deps() [][]CacheKeyWithSelector {
4178
ck.mu.RLock()
4279
defer ck.mu.RUnlock()

solver/cachemanager.go

Lines changed: 83 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"github.com/moby/buildkit/identity"
1111
"github.com/moby/buildkit/util/bklog"
1212
digest "github.com/opencontainers/go-digest"
13+
"github.com/sirupsen/logrus"
1314
)
1415

1516
// NewInMemoryCacheManager creates a new in-memory cache manager
@@ -55,7 +56,28 @@ func (c *cacheManager) ID() string {
5556
return c.id
5657
}
5758

58-
func (c *cacheManager) Query(deps []CacheKeyWithSelector, input Index, dgst digest.Digest, output Index) ([]*CacheKey, error) {
59+
func (c *cacheManager) Query(deps []CacheKeyWithSelector, input Index, dgst digest.Digest, output Index) (rcks []*CacheKey, rerr error) {
60+
depsField := make([]map[string]any, len(deps))
61+
for i, dep := range deps {
62+
depsField[i] = dep.TraceFields()
63+
}
64+
lg := bklog.G(context.TODO()).WithFields(logrus.Fields{
65+
"cache_manager": c.id,
66+
"op": "query",
67+
"deps": depsField,
68+
"input": input,
69+
"digest": dgst,
70+
"output": output,
71+
"stack": bklog.TraceLevelOnlyStack(),
72+
})
73+
defer func() {
74+
rcksField := make([]map[string]any, len(rcks))
75+
for i, rck := range rcks {
76+
rcksField[i] = rck.TraceFields()
77+
}
78+
lg.WithError(rerr).WithField("return_cachekeys", rcksField).Trace("cache manager")
79+
}()
80+
5981
c.mu.RLock()
6082
defer c.mu.RUnlock()
6183

@@ -112,7 +134,21 @@ func (c *cacheManager) Query(deps []CacheKeyWithSelector, input Index, dgst dige
112134
return keys, nil
113135
}
114136

115-
func (c *cacheManager) Records(ctx context.Context, ck *CacheKey) ([]*CacheRecord, error) {
137+
func (c *cacheManager) Records(ctx context.Context, ck *CacheKey) (rrecs []*CacheRecord, rerr error) {
138+
lg := bklog.G(context.TODO()).WithFields(logrus.Fields{
139+
"cache_manager": c.id,
140+
"op": "records",
141+
"cachekey": ck.TraceFields(),
142+
"stack": bklog.TraceLevelOnlyStack(),
143+
})
144+
defer func() {
145+
rrercsField := make([]map[string]any, len(rrecs))
146+
for i, rrec := range rrecs {
147+
rrercsField[i] = rrec.TraceFields()
148+
}
149+
lg.WithError(rerr).WithField("return_records", rrercsField).Trace("cache manager")
150+
}()
151+
116152
outs := make([]*CacheRecord, 0)
117153
if err := c.backend.WalkResults(c.getID(ck), func(r CacheResult) error {
118154
if c.results.Exists(ctx, r.ID) {
@@ -132,7 +168,17 @@ func (c *cacheManager) Records(ctx context.Context, ck *CacheKey) ([]*CacheRecor
132168
return outs, nil
133169
}
134170

135-
func (c *cacheManager) Load(ctx context.Context, rec *CacheRecord) (Result, error) {
171+
func (c *cacheManager) Load(ctx context.Context, rec *CacheRecord) (rres Result, rerr error) {
172+
lg := bklog.G(context.TODO()).WithFields(logrus.Fields{
173+
"cache_manager": c.id,
174+
"op": "load",
175+
"record": rec.TraceFields(),
176+
"stack": bklog.TraceLevelOnlyStack(),
177+
})
178+
defer func() {
179+
lg.WithError(rerr).WithField("return_result", rres.ID()).Trace("cache manager")
180+
}()
181+
136182
c.mu.RLock()
137183
defer c.mu.RUnlock()
138184

@@ -150,6 +196,14 @@ type LoadedResult struct {
150196
CacheKey *CacheKey
151197
}
152198

199+
func (r *LoadedResult) TraceFields() map[string]any {
200+
return map[string]any{
201+
"result": r.Result.ID(),
202+
"cache_result": r.CacheResult.ID,
203+
"cache_key": r.CacheKey.TraceFields(),
204+
}
205+
}
206+
153207
func (c *cacheManager) filterResults(m map[string]Result, ck *CacheKey, visited map[string]struct{}) (results []LoadedResult, err error) {
154208
id := c.getID(ck)
155209
if _, ok := visited[id]; ok {
@@ -187,7 +241,21 @@ func (c *cacheManager) filterResults(m map[string]Result, ck *CacheKey, visited
187241
return
188242
}
189243

190-
func (c *cacheManager) LoadWithParents(ctx context.Context, rec *CacheRecord) ([]LoadedResult, error) {
244+
func (c *cacheManager) LoadWithParents(ctx context.Context, rec *CacheRecord) (rres []LoadedResult, rerr error) {
245+
lg := bklog.G(context.TODO()).WithFields(logrus.Fields{
246+
"cache_manager": c.id,
247+
"op": "load_with_parents",
248+
"record": rec.TraceFields(),
249+
"stack": bklog.TraceLevelOnlyStack(),
250+
})
251+
defer func() {
252+
rresField := make([]map[string]any, len(rres))
253+
for i, rres := range rres {
254+
rresField[i] = rres.TraceFields()
255+
}
256+
lg.WithError(rerr).WithField("return_results", rresField).Trace("cache manager")
257+
}()
258+
191259
lwp, ok := c.results.(interface {
192260
LoadWithParents(context.Context, CacheResult) (map[string]Result, error)
193261
})
@@ -226,7 +294,17 @@ func (c *cacheManager) LoadWithParents(ctx context.Context, rec *CacheRecord) ([
226294
return results, nil
227295
}
228296

229-
func (c *cacheManager) Save(k *CacheKey, r Result, createdAt time.Time) (*ExportableCacheKey, error) {
297+
func (c *cacheManager) Save(k *CacheKey, r Result, createdAt time.Time) (rck *ExportableCacheKey, rerr error) {
298+
lg := bklog.G(context.TODO()).WithFields(logrus.Fields{
299+
"cache_manager": c.id,
300+
"op": "save",
301+
"result": r.ID(),
302+
"stack": bklog.TraceLevelOnlyStack(),
303+
})
304+
defer func() {
305+
lg.WithError(rerr).WithField("return_cachekey", rck.TraceFields()).Trace("cache manager")
306+
}()
307+
230308
c.mu.Lock()
231309
defer c.mu.Unlock()
232310

solver/llbsolver/mounts/mount.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -483,7 +483,7 @@ type cacheRefShare struct {
483483
func (r *cacheRefShare) clone(ctx context.Context) cache.MutableRef {
484484
bklog.G(ctx).WithFields(map[string]any{
485485
"key": r.key,
486-
"stack": bklog.LazyStackTrace{},
486+
"stack": bklog.TraceLevelOnlyStack(),
487487
}).Trace("cloning cache mount ref share")
488488
cacheRef := &cacheRef{cacheRefShare: r}
489489
if cacheRefCloneHijack != nil {
@@ -498,7 +498,7 @@ func (r *cacheRefShare) clone(ctx context.Context) cache.MutableRef {
498498
func (r *cacheRefShare) release(ctx context.Context) error {
499499
bklog.G(ctx).WithFields(map[string]any{
500500
"key": r.key,
501-
"stack": bklog.LazyStackTrace{},
501+
"stack": bklog.TraceLevelOnlyStack(),
502502
}).Trace("releasing cache mount ref share main")
503503
if r.main != nil {
504504
delete(r.main.shares, r.key)
@@ -516,7 +516,7 @@ type cacheRef struct {
516516
func (r *cacheRef) Release(ctx context.Context) error {
517517
bklog.G(ctx).WithFields(map[string]any{
518518
"key": r.key,
519-
"stack": bklog.LazyStackTrace{},
519+
"stack": bklog.TraceLevelOnlyStack(),
520520
}).Trace("releasing cache mount ref share")
521521
if r.main != nil {
522522
r.main.mu.Lock()

solver/types.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -228,6 +228,17 @@ type CacheRecord struct {
228228
key *CacheKey
229229
}
230230

231+
func (ck *CacheRecord) TraceFields() map[string]any {
232+
return map[string]any{
233+
"id": ck.ID,
234+
"size": ck.Size,
235+
"createdAt": ck.CreatedAt,
236+
"priority": ck.Priority,
237+
"cache_manager": ck.cacheManager.ID(),
238+
"cache_key": ck.key.TraceFields(),
239+
}
240+
}
241+
231242
// CacheManager determines if there is a result that matches the cache keys
232243
// generated during the build that could be reused instead of fully
233244
// reevaluating the vertex and its inputs. There can be multiple cache

util/bklog/log.go

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -63,14 +63,13 @@ func GetLogger(ctx context.Context) (l *logrus.Entry) {
6363
return l
6464
}
6565

66-
// LazyStackTrace lets you include a stack trace as a field's value in a log but only
67-
// call it when the log level is actually enabled.
68-
type LazyStackTrace struct{}
69-
70-
func (LazyStackTrace) String() string {
71-
return string(debug.Stack())
72-
}
73-
74-
func (LazyStackTrace) MarshalText() ([]byte, error) {
75-
return debug.Stack(), nil
66+
// TraceLevelOnlyStack returns a stack trace for the current goroutine only if
67+
// trace level logs are enabled; otherwise it returns an empty string. This ensure
68+
// we only pay the cost of generating a stack trace when the log entry will actually
69+
// be emitted.
70+
func TraceLevelOnlyStack() string {
71+
if logrus.GetLevel() == logrus.TraceLevel {
72+
return string(debug.Stack())
73+
}
74+
return ""
7675
}

0 commit comments

Comments
 (0)