Skip to content

Commit 044e09c

Browse files
committed
buildkitd: add cache store debugging endpoints
These endpoints show the contents of current boltdb cache database together with debug plaintexts if they have been saved. Signed-off-by: Tonis Tiigi <[email protected]>
1 parent 4c9d94f commit 044e09c

File tree

7 files changed

+353
-31
lines changed

7 files changed

+353
-31
lines changed

cmd/buildkitd/debug.go

Lines changed: 154 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -4,21 +4,29 @@ import (
44
"context"
55
"encoding/json"
66
"expvar"
7+
"fmt"
8+
"io"
9+
"maps"
710
"net/http"
811
"net/http/pprof"
912
"os"
1013
"runtime"
14+
"slices"
1115
"strings"
1216
"time"
1317

18+
"github.com/moby/buildkit/solver"
1419
"github.com/moby/buildkit/util/bklog"
1520
"github.com/moby/buildkit/util/cachedigest"
21+
"github.com/moby/buildkit/util/cachestore"
1622
digest "github.com/opencontainers/go-digest"
1723
"github.com/pkg/errors"
1824
"github.com/prometheus/client_golang/prometheus/promhttp"
1925
"golang.org/x/net/trace"
2026
)
2127

28+
var cacheStoreForDebug solver.CacheKeyStorage
29+
2230
func setupDebugHandlers(addr string) error {
2331
m := http.NewServeMux()
2432
m.Handle("/debug/vars", expvar.Handler())
@@ -31,6 +39,7 @@ func setupDebugHandlers(addr string) error {
3139
m.Handle("/debug/events", http.HandlerFunc(trace.Events))
3240
m.Handle("/debug/cache/all", http.HandlerFunc(handleCacheAll))
3341
m.Handle("/debug/cache/lookup", http.HandlerFunc(handleCacheLookup))
42+
m.Handle("/debug/cache/store", http.HandlerFunc(handleDebugCacheStore))
3443

3544
m.Handle("/debug/gc", http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
3645
runtime.GC()
@@ -82,18 +91,7 @@ func handleCacheAll(w http.ResponseWriter, r *http.Request) {
8291
default:
8392
w.Header().Set("Content-Type", "text/plain")
8493
for _, rec := range records {
85-
w.Write([]byte(rec.Digest.String() + " (" + rec.Type.String() + "):\n"))
86-
for _, subRec := range rec.SubRecords {
87-
w.Write([]byte(" " + subRec.Digest.String() + " (" + subRec.Type.String() + "):\n"))
88-
}
89-
for _, frame := range rec.Data {
90-
switch frame.ID {
91-
case cachedigest.FrameIDData:
92-
w.Write([]byte(" " + frame.ID.String() + ": " + string(frame.Data) + "\n"))
93-
case cachedigest.FrameIDSkip:
94-
w.Write([]byte(" skipping " + string(frame.Data) + " bytes\n"))
95-
}
96-
}
94+
printCacheRecord(rec, w)
9795
w.Write([]byte("\n"))
9896
}
9997
}
@@ -127,22 +125,39 @@ func handleCacheLookup(w http.ResponseWriter, r *http.Request) {
127125
}
128126
default:
129127
w.Header().Set("Content-Type", "text/plain")
130-
w.Write([]byte(record.Digest.String() + " (" + record.Type.String() + "):\n"))
131-
for _, subRec := range record.SubRecords {
132-
w.Write([]byte(" " + subRec.Digest.String() + " (" + subRec.Type.String() + "):\n"))
133-
}
134-
for _, frame := range record.Data {
135-
switch frame.ID {
136-
case cachedigest.FrameIDData:
137-
w.Write([]byte(" " + frame.ID.String() + ": " + string(frame.Data) + "\n"))
138-
case cachedigest.FrameIDSkip:
139-
w.Write([]byte(" skipping " + string(frame.Data) + " bytes\n"))
140-
}
128+
printCacheRecord(record, w)
129+
}
130+
}
131+
132+
func printCacheRecord(record *cachedigest.Record, w io.Writer) {
133+
w.Write([]byte(record.Digest.String() + " (" + record.Type.String() + "):\n"))
134+
for _, subRec := range record.SubRecords {
135+
w.Write([]byte(" " + subRec.Digest.String() + " (" + subRec.Type.String() + "):\n"))
136+
}
137+
for _, frame := range record.Data {
138+
switch frame.ID {
139+
case cachedigest.FrameIDData:
140+
w.Write([]byte(" " + frame.ID.String() + ": " + string(frame.Data) + "\n"))
141+
case cachedigest.FrameIDSkip:
142+
w.Write([]byte(" skipping " + string(frame.Data) + " bytes\n"))
141143
}
142144
}
143145
}
144146

145147
func cacheRecordLookup(ctx context.Context, dgst digest.Digest) (*cachedigest.Record, error) {
148+
if dgst == "sha256:8a5edab282632443219e051e4ade2d1d5bbc671c781051bf1437897cbdfea0f1" {
149+
return &cachedigest.Record{
150+
Digest: dgst,
151+
Type: cachedigest.TypeString,
152+
Data: []cachedigest.Frame{
153+
{
154+
ID: cachedigest.FrameIDData,
155+
Data: []byte("/"),
156+
},
157+
},
158+
}, nil
159+
}
160+
146161
db := cachedigest.GetDefaultDB()
147162
typ, frames, err := db.Get(ctx, dgst.String())
148163
if err != nil {
@@ -200,3 +215,119 @@ func loadCacheAll(ctx context.Context) ([]*cachedigest.Record, error) {
200215
}
201216
return records, nil
202217
}
218+
219+
func handleDebugCacheStore(w http.ResponseWriter, r *http.Request) {
220+
if r.Method != http.MethodGet {
221+
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
222+
return
223+
}
224+
225+
recs, err := debugCacheStore(r.Context())
226+
if err != nil {
227+
http.Error(w, "Failed to debug cache store: "+err.Error(), http.StatusInternalServerError)
228+
return
229+
}
230+
231+
w.WriteHeader(http.StatusOK)
232+
233+
switch r.Header.Get("Accept") {
234+
case "application/json":
235+
w.Header().Set("Content-Type", "application/json")
236+
enc := json.NewEncoder(w)
237+
enc.SetIndent("", " ")
238+
if err := enc.Encode(recs); err != nil {
239+
http.Error(w, "Failed to encode cache records: "+err.Error(), http.StatusInternalServerError)
240+
return
241+
}
242+
default:
243+
w.Header().Set("Content-Type", "text/plain")
244+
for _, rec := range recs {
245+
randomSuffix := ""
246+
if rec.Random {
247+
randomSuffix = " (random)"
248+
}
249+
fmt.Fprintf(w, "ID: %d%s\n", rec.ID, randomSuffix)
250+
if rec.Digest != "" {
251+
fmt.Fprintf(w, "Digest: %s\n", rec.Digest)
252+
}
253+
if len(rec.Parents) > 0 {
254+
fmt.Fprintln(w, "Parents:")
255+
for input := range rec.Parents {
256+
ids := slices.Collect(maps.Keys(rec.ParentIDs[input]))
257+
s := make([]string, len(ids))
258+
for i, id := range ids {
259+
s[i] = fmt.Sprintf("%d", id)
260+
}
261+
fmt.Fprintf(w, " Input %d:\t %s\n", input, strings.Join(s, ", "))
262+
}
263+
}
264+
if len(rec.Children) > 0 {
265+
fmt.Fprintln(w, "Children:")
266+
for _, child := range rec.Children {
267+
fmt.Fprintf(w, " %d %s (input %d, output %d)\n", child.Record.ID, child.Digest, child.Input, child.Output)
268+
if child.Selector != "" {
269+
fmt.Fprintf(w, " Selector: %s\n", child.Selector)
270+
}
271+
}
272+
}
273+
if len(rec.Debug) > 0 {
274+
fmt.Fprintln(w, "Plaintexts:")
275+
for _, debugRec := range rec.Debug {
276+
printCacheRecord(debugRec, w)
277+
w.Write([]byte("\n"))
278+
}
279+
}
280+
w.Write([]byte("\n"))
281+
}
282+
}
283+
}
284+
285+
type recordWithDebug struct {
286+
*cachestore.Record
287+
Debug []*cachedigest.Record `json:"debug,omitempty"`
288+
}
289+
290+
func debugCacheStore(ctx context.Context) ([]*recordWithDebug, error) {
291+
store := cacheStoreForDebug
292+
if store == nil {
293+
return nil, errors.New("cache store is not initialized for debug")
294+
}
295+
296+
recs, err := cachestore.Records(ctx, store)
297+
if err != nil {
298+
return nil, errors.Wrap(err, "failed to get cache records")
299+
}
300+
301+
recsWithDebug := make([]*recordWithDebug, len(recs))
302+
for i, rec := range recs {
303+
debugRec := &recordWithDebug{
304+
Record: rec,
305+
}
306+
m := map[digest.Digest]*cachedigest.Record{}
307+
if rec.Digest != "" {
308+
m[rec.Digest] = nil
309+
}
310+
for _, link := range rec.Children {
311+
m[link.Digest] = nil
312+
if link.Selector != "" {
313+
m[link.Selector] = nil
314+
}
315+
}
316+
for dgst := range m {
317+
cr, err := cacheRecordLookup(ctx, dgst)
318+
if err != nil {
319+
bklog.L.Errorf("failed to lookup cache record for %s: %v", dgst, err)
320+
continue
321+
}
322+
m[dgst] = cr
323+
}
324+
for _, cr := range m {
325+
if cr != nil {
326+
debugRec.Debug = append(debugRec.Debug, cr)
327+
}
328+
}
329+
recsWithDebug[i] = debugRec
330+
}
331+
332+
return recsWithDebug, nil
333+
}

cmd/buildkitd/main.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -823,6 +823,7 @@ func newController(ctx context.Context, c *cli.Context, cfg *config.Config) (*co
823823
if err != nil {
824824
return nil, err
825825
}
826+
cacheStoreForDebug = cacheStorage
826827

827828
historyDB, err := boltutil.Open(filepath.Join(cfg.Root, "history.db"), 0600, nil)
828829
if err != nil {

solver/bboltcachestorage/storage.go

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -342,6 +342,47 @@ func (s *Store) AddLink(id string, link solver.CacheInfoLink, target string) err
342342
})
343343
}
344344

345+
func (s *Store) WalkLinksAll(id string, fn func(id string, link solver.CacheInfoLink) error) error {
346+
type linkEntry struct {
347+
id string
348+
link solver.CacheInfoLink
349+
}
350+
var links []linkEntry
351+
if err := s.db.View(func(tx *bolt.Tx) error {
352+
b := tx.Bucket([]byte(linksBucket))
353+
if b == nil {
354+
return nil
355+
}
356+
b = b.Bucket([]byte(id))
357+
if b == nil {
358+
return nil
359+
}
360+
return b.ForEach(func(k, v []byte) error {
361+
parts := bytes.Split(k, []byte("@"))
362+
if len(parts) != 2 {
363+
return errors.Errorf("invalid key %s", k)
364+
}
365+
var link solver.CacheInfoLink
366+
if err := json.Unmarshal(parts[0], &link); err != nil {
367+
return err
368+
}
369+
links = append(links, linkEntry{
370+
id: string(parts[1]),
371+
link: link,
372+
})
373+
return nil
374+
})
375+
}); err != nil {
376+
return err
377+
}
378+
for _, l := range links {
379+
if err := fn(l.id, l.link); err != nil {
380+
return err
381+
}
382+
}
383+
return nil
384+
}
385+
345386
func (s *Store) WalkLinks(id string, link solver.CacheInfoLink, fn func(id string) error) error {
346387
var links []string
347388
if err := s.db.View(func(tx *bolt.Tx) error {

solver/cachemanager.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -449,9 +449,9 @@ func (c *cacheManager) getIDFromDeps(k *CacheKey) string {
449449
}
450450

451451
func rootKey(dgst digest.Digest, output Index) digest.Digest {
452-
dgst, _ = cachedigest.FromBytes(fmt.Appendf(nil, "%s@%d", dgst, output), cachedigest.TypeString)
452+
out, _ := cachedigest.FromBytes(fmt.Appendf(nil, "%s@%d", dgst, output), cachedigest.TypeString)
453453
if strings.HasPrefix(dgst.String(), "random:") {
454454
return digest.Digest("random:" + dgst.Encoded())
455455
}
456-
return dgst
456+
return out
457457
}

util/cachedigest/digest.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -91,10 +91,10 @@ func (h *Hash) Sum() digest.Digest {
9191
}
9292

9393
type Record struct {
94-
Digest digest.Digest
95-
Type Type
96-
Data []Frame
97-
SubRecords []Record
94+
Digest digest.Digest `json:"digest"`
95+
Type Type `json:"type"`
96+
Data []Frame `json:"data,omitempty"`
97+
SubRecords []Record `json:"subRecords,omitempty"`
9898
}
9999

100100
var shaRegexpOnce = sync.OnceValue(func() *regexp.Regexp {

util/cachedigest/frame.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,8 @@ func (f FrameID) String() string {
2828
}
2929

3030
type Frame struct {
31-
ID FrameID
32-
Data []byte
31+
ID FrameID `json:"type"`
32+
Data []byte `json:"data,omitempty"`
3333
}
3434

3535
// encodeFrames encodes a series of frames: [frameID:uint32][len:uint32][data:len]

0 commit comments

Comments
 (0)