11package main
22
33import (
4+ "context"
5+ "encoding/json"
46 "expvar"
57 "net/http"
68 "net/http/pprof"
@@ -10,6 +12,9 @@ import (
1012 "time"
1113
1214 "github.com/moby/buildkit/util/bklog"
15+ "github.com/moby/buildkit/util/cachedigest"
16+ digest "github.com/opencontainers/go-digest"
17+ "github.com/pkg/errors"
1318 "github.com/prometheus/client_golang/prometheus/promhttp"
1419 "golang.org/x/net/trace"
1520)
@@ -24,6 +29,8 @@ func setupDebugHandlers(addr string) error {
2429 m .Handle ("/debug/pprof/trace" , http .HandlerFunc (pprof .Trace ))
2530 m .Handle ("/debug/requests" , http .HandlerFunc (trace .Traces ))
2631 m .Handle ("/debug/events" , http .HandlerFunc (trace .Events ))
32+ m .Handle ("/debug/cache/all" , http .HandlerFunc (handleCacheAll ))
33+ m .Handle ("/debug/cache/lookup" , http .HandlerFunc (handleCacheLookup ))
2734
2835 m .Handle ("/debug/gc" , http .HandlerFunc (func (rw http.ResponseWriter , req * http.Request ) {
2936 runtime .GC ()
@@ -59,3 +66,137 @@ func setupDebugHandlers(addr string) error {
5966 }()
6067 return nil
6168}
69+
70+ func handleCacheAll (w http.ResponseWriter , r * http.Request ) {
71+ records , err := loadCacheAll (r .Context ())
72+ if err != nil {
73+ http .Error (w , err .Error (), http .StatusInternalServerError )
74+ return
75+ }
76+ switch r .Header .Get ("Accept" ) {
77+ case "application/json" :
78+ w .Header ().Set ("Content-Type" , "application/json" )
79+ enc := json .NewEncoder (w )
80+ enc .SetIndent ("" , " " )
81+ enc .Encode (records )
82+ default :
83+ w .Header ().Set ("Content-Type" , "text/plain" )
84+ 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+ }
97+ w .Write ([]byte ("\n " ))
98+ }
99+ }
100+ }
101+
102+ func handleCacheLookup (w http.ResponseWriter , r * http.Request ) {
103+ dgstStr := r .URL .Query ().Get ("digest" )
104+ if dgstStr == "" {
105+ http .Error (w , "digest query parameter is required" , http .StatusBadRequest )
106+ return
107+ }
108+
109+ dgst , err := digest .Parse (dgstStr )
110+ if err != nil {
111+ http .Error (w , "invalid digest: " + err .Error (), http .StatusBadRequest )
112+ return
113+ }
114+
115+ record , err := cacheRecordLookup (r .Context (), dgst )
116+ if err != nil {
117+ http .Error (w , err .Error (), http .StatusInternalServerError )
118+ return
119+ }
120+
121+ switch r .Header .Get ("Accept" ) {
122+ case "application/json" :
123+ w .Header ().Set ("Content-Type" , "application/json" )
124+ if err := json .NewEncoder (w ).Encode (record ); err != nil {
125+ http .Error (w , "failed to encode record: " + err .Error (), http .StatusInternalServerError )
126+ return
127+ }
128+ default :
129+ 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+ }
141+ }
142+ }
143+ }
144+
145+ func cacheRecordLookup (ctx context.Context , dgst digest.Digest ) (* cachedigest.Record , error ) {
146+ db := cachedigest .GetDefaultDB ()
147+ typ , frames , err := db .Get (ctx , dgst .String ())
148+ if err != nil {
149+ return nil , errors .Wrapf (err , "failed to get digest %s from cache" , dgst .String ())
150+ }
151+ record := & cachedigest.Record {
152+ Digest : dgst ,
153+ Type : typ ,
154+ Data : frames ,
155+ }
156+ if err := record .LoadSubRecords (func (d digest.Digest ) (cachedigest.Type , []cachedigest.Frame , error ) {
157+ typ , frames , err := db .Get (ctx , d .String ())
158+ if err != nil {
159+ return "" , nil , errors .Wrapf (err , "failed to load sub-record for %s" , d .String ())
160+ }
161+ return typ , frames , nil
162+ }); err != nil {
163+ return nil , errors .Wrapf (err , "failed to load sub-records for %s" , dgst .String ())
164+ }
165+ return record , nil
166+ }
167+
168+ func loadCacheAll (ctx context.Context ) ([]* cachedigest.Record , error ) {
169+ var records []* cachedigest.Record
170+ m := map [digest.Digest ]* cachedigest.Record {}
171+ db := cachedigest .GetDefaultDB ()
172+ err := db .All (ctx , func (key string , typ cachedigest.Type , frames []cachedigest.Frame ) error {
173+ dgst , err := digest .Parse (key )
174+ if err != nil {
175+ return errors .Wrapf (err , "failed to parse digest %q" , key )
176+ }
177+ r := & cachedigest.Record {
178+ Digest : dgst ,
179+ Type : typ ,
180+ Data : frames ,
181+ }
182+ records = append (records , r )
183+ m [dgst ] = r
184+ return nil
185+ })
186+ if err != nil {
187+ return nil , err
188+ }
189+
190+ for _ , rec := range records {
191+ if err := rec .LoadSubRecords (func (d digest.Digest ) (cachedigest.Type , []cachedigest.Frame , error ) {
192+ rec , ok := m [d ]
193+ if ! ok {
194+ return "" , nil , errors .Errorf ("digest %s not found in cache" , d )
195+ }
196+ return rec .Type , rec .Data , nil
197+ }); err != nil {
198+ return nil , errors .Wrapf (err , "failed to load sub-records for %s" , rec .Digest .String ())
199+ }
200+ }
201+ return records , nil
202+ }
0 commit comments