This endpoint return data regarding the space used by docker on disk
Signed-off-by: Kenfe-Mickael Laventure <mickael.laventure@gmail.com>
... | ... |
@@ -14,6 +14,7 @@ import ( |
14 | 14 |
type Backend interface { |
15 | 15 |
SystemInfo() (*types.Info, error) |
16 | 16 |
SystemVersion() types.Version |
17 |
+ SystemDiskUsage() (*types.DiskUsage, error) |
|
17 | 18 |
SubscribeToEvents(since, until time.Time, ef filters.Args) ([]events.Message, chan interface{}) |
18 | 19 |
UnsubscribeFromEvents(chan interface{}) |
19 | 20 |
AuthenticateToRegistry(ctx context.Context, authConfig *types.AuthConfig) (string, string, error) |
... | ... |
@@ -26,6 +26,7 @@ func NewRouter(b Backend, c *cluster.Cluster) router.Router { |
26 | 26 |
router.Cancellable(router.NewGetRoute("/events", r.getEvents)), |
27 | 27 |
router.NewGetRoute("/info", r.getInfo), |
28 | 28 |
router.NewGetRoute("/version", r.getVersion), |
29 |
+ router.NewGetRoute("/system/df", r.getDiskUsage), |
|
29 | 30 |
router.NewPostRoute("/auth", r.postAuth), |
30 | 31 |
} |
31 | 32 |
|
... | ... |
@@ -56,6 +56,15 @@ func (s *systemRouter) getVersion(ctx context.Context, w http.ResponseWriter, r |
56 | 56 |
return httputils.WriteJSON(w, http.StatusOK, info) |
57 | 57 |
} |
58 | 58 |
|
59 |
+func (s *systemRouter) getDiskUsage(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { |
|
60 |
+ du, err := s.backend.SystemDiskUsage() |
|
61 |
+ if err != nil { |
|
62 |
+ return err |
|
63 |
+ } |
|
64 |
+ |
|
65 |
+ return httputils.WriteJSON(w, http.StatusOK, du) |
|
66 |
+} |
|
67 |
+ |
|
59 | 68 |
func (s *systemRouter) getEvents(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { |
60 | 69 |
if err := httputils.ParseForm(r); err != nil { |
61 | 70 |
return err |
... | ... |
@@ -530,3 +530,12 @@ type Runtime struct { |
530 | 530 |
Path string `json:"path"` |
531 | 531 |
Args []string `json:"runtimeArgs,omitempty"` |
532 | 532 |
} |
533 |
+ |
|
534 |
+// DiskUsage contains response of Remote API: |
|
535 |
+// GET "/system/df" |
|
536 |
+type DiskUsage struct { |
|
537 |
+ LayersSize int64 |
|
538 |
+ Images []*Image |
|
539 |
+ Containers []*Container |
|
540 |
+ Volumes []*Volume |
|
541 |
+} |
533 | 542 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,100 @@ |
0 |
+package daemon |
|
1 |
+ |
|
2 |
+import ( |
|
3 |
+ "fmt" |
|
4 |
+ |
|
5 |
+ "github.com/Sirupsen/logrus" |
|
6 |
+ "github.com/docker/distribution/digest" |
|
7 |
+ "github.com/docker/docker/api/types" |
|
8 |
+ "github.com/docker/docker/layer" |
|
9 |
+ "github.com/docker/docker/pkg/directory" |
|
10 |
+ "github.com/docker/docker/volume" |
|
11 |
+) |
|
12 |
+ |
|
13 |
+func (daemon *Daemon) getLayerRefs() map[layer.ChainID]int { |
|
14 |
+ tmpImages := daemon.imageStore.Map() |
|
15 |
+ layerRefs := map[layer.ChainID]int{} |
|
16 |
+ for id, img := range tmpImages { |
|
17 |
+ dgst := digest.Digest(id) |
|
18 |
+ if len(daemon.referenceStore.References(dgst)) == 0 && len(daemon.imageStore.Children(id)) != 0 { |
|
19 |
+ continue |
|
20 |
+ } |
|
21 |
+ |
|
22 |
+ rootFS := *img.RootFS |
|
23 |
+ rootFS.DiffIDs = nil |
|
24 |
+ for _, id := range img.RootFS.DiffIDs { |
|
25 |
+ rootFS.Append(id) |
|
26 |
+ chid := rootFS.ChainID() |
|
27 |
+ layerRefs[chid]++ |
|
28 |
+ } |
|
29 |
+ } |
|
30 |
+ |
|
31 |
+ return layerRefs |
|
32 |
+} |
|
33 |
+ |
|
34 |
+// SystemDiskUsage returns information about the daemon data disk usage |
|
35 |
+func (daemon *Daemon) SystemDiskUsage() (*types.DiskUsage, error) { |
|
36 |
+ // Retrieve container list |
|
37 |
+ allContainers, err := daemon.Containers(&types.ContainerListOptions{ |
|
38 |
+ Size: true, |
|
39 |
+ All: true, |
|
40 |
+ }) |
|
41 |
+ if err != nil { |
|
42 |
+ return nil, fmt.Errorf("failed to retrieve container list: %v", err) |
|
43 |
+ } |
|
44 |
+ |
|
45 |
+ // Get all top images with extra attributes |
|
46 |
+ allImages, err := daemon.Images("", "", false, true) |
|
47 |
+ if err != nil { |
|
48 |
+ return nil, fmt.Errorf("failed to retrieve image list: %v", err) |
|
49 |
+ } |
|
50 |
+ |
|
51 |
+ // Get all local volumes |
|
52 |
+ allVolumes := []*types.Volume{} |
|
53 |
+ getLocalVols := func(v volume.Volume) error { |
|
54 |
+ name := v.Name() |
|
55 |
+ refs := daemon.volumes.Refs(v) |
|
56 |
+ |
|
57 |
+ tv := volumeToAPIType(v) |
|
58 |
+ tv.RefCount = len(refs) |
|
59 |
+ sz, err := directory.Size(v.Path()) |
|
60 |
+ if err != nil { |
|
61 |
+ logrus.Warnf("failed to determine size of volume %v", name) |
|
62 |
+ sz = -1 |
|
63 |
+ } |
|
64 |
+ tv.Size = sz |
|
65 |
+ allVolumes = append(allVolumes, tv) |
|
66 |
+ |
|
67 |
+ return nil |
|
68 |
+ } |
|
69 |
+ |
|
70 |
+ err = daemon.traverseLocalVolumes(getLocalVols) |
|
71 |
+ if err != nil { |
|
72 |
+ return nil, err |
|
73 |
+ } |
|
74 |
+ |
|
75 |
+ // Get total layers size on disk |
|
76 |
+ layerRefs := daemon.getLayerRefs() |
|
77 |
+ allLayers := daemon.layerStore.Map() |
|
78 |
+ var allLayersSize int64 |
|
79 |
+ for _, l := range allLayers { |
|
80 |
+ size, err := l.DiffSize() |
|
81 |
+ if err == nil { |
|
82 |
+ if _, ok := layerRefs[l.ChainID()]; ok { |
|
83 |
+ allLayersSize += size |
|
84 |
+ } else { |
|
85 |
+ logrus.Warnf("found leaked image layer %v", l.ChainID()) |
|
86 |
+ } |
|
87 |
+ } else { |
|
88 |
+ logrus.Warnf("failed to get diff size for layer %v", l.ChainID()) |
|
89 |
+ } |
|
90 |
+ |
|
91 |
+ } |
|
92 |
+ |
|
93 |
+ return &types.DiskUsage{ |
|
94 |
+ LayersSize: allLayersSize, |
|
95 |
+ Containers: allContainers, |
|
96 |
+ Volumes: allVolumes, |
|
97 |
+ Images: allImages, |
|
98 |
+ }, nil |
|
99 |
+} |
... | ... |
@@ -7,12 +7,14 @@ import ( |
7 | 7 |
"path/filepath" |
8 | 8 |
"strings" |
9 | 9 |
|
10 |
+ "github.com/Sirupsen/logrus" |
|
10 | 11 |
dockererrors "github.com/docker/docker/api/errors" |
11 | 12 |
"github.com/docker/docker/api/types" |
12 | 13 |
containertypes "github.com/docker/docker/api/types/container" |
13 | 14 |
mounttypes "github.com/docker/docker/api/types/mount" |
14 | 15 |
"github.com/docker/docker/container" |
15 | 16 |
"github.com/docker/docker/volume" |
17 |
+ "github.com/docker/docker/volume/drivers" |
|
16 | 18 |
"github.com/opencontainers/runc/libcontainer/label" |
17 | 19 |
) |
18 | 20 |
|
... | ... |
@@ -276,3 +278,29 @@ func backportMountSpec(container *container.Container) error { |
276 | 276 |
} |
277 | 277 |
return container.ToDiskLocking() |
278 | 278 |
} |
279 |
+ |
|
280 |
+func (daemon *Daemon) traverseLocalVolumes(fn func(volume.Volume) error) error { |
|
281 |
+ localVolumeDriver, err := volumedrivers.GetDriver(volume.DefaultDriverName) |
|
282 |
+ if err != nil { |
|
283 |
+ return fmt.Errorf("can't retrieve local volume driver: %v", err) |
|
284 |
+ } |
|
285 |
+ vols, err := localVolumeDriver.List() |
|
286 |
+ if err != nil { |
|
287 |
+ return fmt.Errorf("can't retrieve local volumes: %v", err) |
|
288 |
+ } |
|
289 |
+ |
|
290 |
+ for _, v := range vols { |
|
291 |
+ name := v.Name() |
|
292 |
+ _, err := daemon.volumes.Get(name) |
|
293 |
+ if err != nil { |
|
294 |
+ logrus.Warnf("failed to retrieve volume %s from store: %v", name, err) |
|
295 |
+ } |
|
296 |
+ |
|
297 |
+ err = fn(v) |
|
298 |
+ if err != nil { |
|
299 |
+ return err |
|
300 |
+ } |
|
301 |
+ } |
|
302 |
+ |
|
303 |
+ return nil |
|
304 |
+} |