diff --git a/api/server/router/system/backend.go b/api/server/router/system/backend.go index 56e98e05d7..6946c4e2d1 100644 --- a/api/server/router/system/backend.go +++ b/api/server/router/system/backend.go @@ -14,6 +14,7 @@ import ( type Backend interface { SystemInfo() (*types.Info, error) SystemVersion() types.Version + SystemDiskUsage() (*types.DiskUsage, error) SubscribeToEvents(since, until time.Time, ef filters.Args) ([]events.Message, chan interface{}) UnsubscribeFromEvents(chan interface{}) AuthenticateToRegistry(ctx context.Context, authConfig *types.AuthConfig) (string, string, error) diff --git a/api/server/router/system/system.go b/api/server/router/system/system.go index e5742c9fe8..ed23d3bdee 100644 --- a/api/server/router/system/system.go +++ b/api/server/router/system/system.go @@ -26,6 +26,7 @@ func NewRouter(b Backend, c *cluster.Cluster) router.Router { router.Cancellable(router.NewGetRoute("/events", r.getEvents)), router.NewGetRoute("/info", r.getInfo), router.NewGetRoute("/version", r.getVersion), + router.NewGetRoute("/system/df", r.getDiskUsage), router.NewPostRoute("/auth", r.postAuth), } diff --git a/api/server/router/system/system_routes.go b/api/server/router/system/system_routes.go index 2a1cf384a1..a3ff4df7c8 100644 --- a/api/server/router/system/system_routes.go +++ b/api/server/router/system/system_routes.go @@ -56,6 +56,15 @@ func (s *systemRouter) getVersion(ctx context.Context, w http.ResponseWriter, r return httputils.WriteJSON(w, http.StatusOK, info) } +func (s *systemRouter) getDiskUsage(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { + du, err := s.backend.SystemDiskUsage() + if err != nil { + return err + } + + return httputils.WriteJSON(w, http.StatusOK, du) +} + func (s *systemRouter) getEvents(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if err := httputils.ParseForm(r); err != nil { return err diff --git a/api/types/types.go b/api/types/types.go index fd93467ea3..69a0ed5486 100644 --- a/api/types/types.go +++ b/api/types/types.go @@ -530,3 +530,12 @@ type Runtime struct { Path string `json:"path"` Args []string `json:"runtimeArgs,omitempty"` } + +// DiskUsage contains response of Remote API: +// GET "/system/df" +type DiskUsage struct { + LayersSize int64 + Images []*Image + Containers []*Container + Volumes []*Volume +} diff --git a/daemon/disk_usage.go b/daemon/disk_usage.go new file mode 100644 index 0000000000..d821470725 --- /dev/null +++ b/daemon/disk_usage.go @@ -0,0 +1,100 @@ +package daemon + +import ( + "fmt" + + "github.com/Sirupsen/logrus" + "github.com/docker/distribution/digest" + "github.com/docker/docker/api/types" + "github.com/docker/docker/layer" + "github.com/docker/docker/pkg/directory" + "github.com/docker/docker/volume" +) + +func (daemon *Daemon) getLayerRefs() map[layer.ChainID]int { + tmpImages := daemon.imageStore.Map() + layerRefs := map[layer.ChainID]int{} + for id, img := range tmpImages { + dgst := digest.Digest(id) + if len(daemon.referenceStore.References(dgst)) == 0 && len(daemon.imageStore.Children(id)) != 0 { + continue + } + + rootFS := *img.RootFS + rootFS.DiffIDs = nil + for _, id := range img.RootFS.DiffIDs { + rootFS.Append(id) + chid := rootFS.ChainID() + layerRefs[chid]++ + } + } + + return layerRefs +} + +// SystemDiskUsage returns information about the daemon data disk usage +func (daemon *Daemon) SystemDiskUsage() (*types.DiskUsage, error) { + // Retrieve container list + allContainers, err := daemon.Containers(&types.ContainerListOptions{ + Size: true, + All: true, + }) + if err != nil { + return nil, fmt.Errorf("failed to retrieve container list: %v", err) + } + + // Get all top images with extra attributes + allImages, err := daemon.Images("", "", false, true) + if err != nil { + return nil, fmt.Errorf("failed to retrieve image list: %v", err) + } + + // Get all local volumes + allVolumes := []*types.Volume{} + getLocalVols := func(v volume.Volume) error { + name := v.Name() + refs := daemon.volumes.Refs(v) + + tv := volumeToAPIType(v) + tv.RefCount = len(refs) + sz, err := directory.Size(v.Path()) + if err != nil { + logrus.Warnf("failed to determine size of volume %v", name) + sz = -1 + } + tv.Size = sz + allVolumes = append(allVolumes, tv) + + return nil + } + + err = daemon.traverseLocalVolumes(getLocalVols) + if err != nil { + return nil, err + } + + // Get total layers size on disk + layerRefs := daemon.getLayerRefs() + allLayers := daemon.layerStore.Map() + var allLayersSize int64 + for _, l := range allLayers { + size, err := l.DiffSize() + if err == nil { + if _, ok := layerRefs[l.ChainID()]; ok { + allLayersSize += size + } else { + logrus.Warnf("found leaked image layer %v", l.ChainID()) + } + } else { + logrus.Warnf("failed to get diff size for layer %v", l.ChainID()) + } + + } + + return &types.DiskUsage{ + LayersSize: allLayersSize, + Containers: allContainers, + Volumes: allVolumes, + Images: allImages, + }, nil +} diff --git a/daemon/volumes.go b/daemon/volumes.go index 92b8f70200..478938fd0f 100644 --- a/daemon/volumes.go +++ b/daemon/volumes.go @@ -7,12 +7,14 @@ import ( "path/filepath" "strings" + "github.com/Sirupsen/logrus" dockererrors "github.com/docker/docker/api/errors" "github.com/docker/docker/api/types" containertypes "github.com/docker/docker/api/types/container" mounttypes "github.com/docker/docker/api/types/mount" "github.com/docker/docker/container" "github.com/docker/docker/volume" + "github.com/docker/docker/volume/drivers" "github.com/opencontainers/runc/libcontainer/label" ) @@ -276,3 +278,29 @@ func backportMountSpec(container *container.Container) error { } return container.ToDiskLocking() } + +func (daemon *Daemon) traverseLocalVolumes(fn func(volume.Volume) error) error { + localVolumeDriver, err := volumedrivers.GetDriver(volume.DefaultDriverName) + if err != nil { + return fmt.Errorf("can't retrieve local volume driver: %v", err) + } + vols, err := localVolumeDriver.List() + if err != nil { + return fmt.Errorf("can't retrieve local volumes: %v", err) + } + + for _, v := range vols { + name := v.Name() + _, err := daemon.volumes.Get(name) + if err != nil { + logrus.Warnf("failed to retrieve volume %s from store: %v", name, err) + } + + err = fn(v) + if err != nil { + return err + } + } + + return nil +}