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 |
+} |