Browse code

Add /system/df API endpoint

This endpoint return data regarding the space used by docker on disk

Signed-off-by: Kenfe-Mickael Laventure <mickael.laventure@gmail.com>

Kenfe-Mickael Laventure authored on 2016/08/24 08:24:15
Showing 6 changed files
... ...
@@ -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
+}