@@ -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+
2230func 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
145147func 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+ }
0 commit comments