diff --git a/api/server/router/image/backend.go b/api/server/router/image/backend.go index f77dbedb06..b03234ab75 100644 --- a/api/server/router/image/backend.go +++ b/api/server/router/image/backend.go @@ -25,7 +25,7 @@ type containerBackend interface { type imageBackend interface { ImageDelete(imageRef string, force, prune bool) ([]types.ImageDelete, error) ImageHistory(imageName string) ([]*types.ImageHistory, error) - Images(filterArgs string, filter string, all bool) ([]*types.Image, error) + Images(filterArgs string, filter string, all bool, withExtraAttrs bool) ([]*types.Image, error) LookupImage(name string) (*types.ImageInspect, error) TagImage(imageName, repository, tag string) error } diff --git a/api/server/router/image/image_routes.go b/api/server/router/image/image_routes.go index c1fdc06709..d28892042a 100644 --- a/api/server/router/image/image_routes.go +++ b/api/server/router/image/image_routes.go @@ -248,7 +248,7 @@ func (s *imageRouter) getImagesJSON(ctx context.Context, w http.ResponseWriter, } // FIXME: The filter parameter could just be a match filter - images, err := s.backend.Images(r.Form.Get("filters"), r.Form.Get("filter"), httputils.BoolValue(r, "all")) + images, err := s.backend.Images(r.Form.Get("filters"), r.Form.Get("filter"), httputils.BoolValue(r, "all"), false) if err != nil { return err } diff --git a/api/types/types.go b/api/types/types.go index 60d1fc2c13..fd93467ea3 100644 --- a/api/types/types.go +++ b/api/types/types.go @@ -95,8 +95,10 @@ type Image struct { RepoDigests []string Created int64 Size int64 + SharedSize int64 VirtualSize int64 Labels map[string]string + Containers int64 } // GraphDriverData returns Image's graph driver config info diff --git a/cli/command/formatter/image.go b/cli/command/formatter/image.go index 54cb7b62fa..39e05378c7 100644 --- a/cli/command/formatter/image.go +++ b/cli/command/formatter/image.go @@ -225,5 +225,6 @@ func (c *imageContext) CreatedAt() string { func (c *imageContext) Size() string { c.AddHeader(sizeHeader) - return units.HumanSizeWithPrecision(float64(c.i.Size), 3) + //NOTE: For backward compatibility we need to return VirtualSize + return units.HumanSizeWithPrecision(float64(c.i.VirtualSize), 3) } diff --git a/daemon/images.go b/daemon/images.go index c9dd96cbe4..b7dc6444b1 100644 --- a/daemon/images.go +++ b/daemon/images.go @@ -7,6 +7,7 @@ import ( "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/filters" + "github.com/docker/docker/container" "github.com/docker/docker/image" "github.com/docker/docker/layer" "github.com/docker/docker/reference" @@ -37,7 +38,7 @@ func (daemon *Daemon) Map() map[image.ID]*image.Image { // filter is a shell glob string applied to repository names. The argument // named all controls whether all images in the graph are filtered, or just // the heads. -func (daemon *Daemon) Images(filterArgs, filter string, all bool) ([]*types.Image, error) { +func (daemon *Daemon) Images(filterArgs, filter string, all bool, withExtraAttrs bool) ([]*types.Image, error) { var ( allImages map[image.ID]*image.Image err error @@ -83,6 +84,10 @@ func (daemon *Daemon) Images(filterArgs, filter string, all bool) ([]*types.Imag } images := []*types.Image{} + var imagesMap map[*image.Image]*types.Image + var layerRefs map[layer.ChainID]int + var allLayers map[layer.ChainID]layer.Layer + var allContainers []*container.Container var filterTagged bool if filter != "" { @@ -171,21 +176,80 @@ func (daemon *Daemon) Images(filterArgs, filter string, all bool) ([]*types.Imag continue } + if withExtraAttrs { + // lazyly init variables + if len(allContainers) == 0 { + allContainers = daemon.List() + allLayers = daemon.layerStore.Map() + imagesMap = make(map[*image.Image]*types.Image) + layerRefs = make(map[layer.ChainID]int) + } + + // Get container count + newImage.Containers = 0 + for _, c := range allContainers { + if c.ImageID == id { + newImage.Containers++ + } + } + + // count layer references + rootFS := *img.RootFS + rootFS.DiffIDs = nil + for _, id := range img.RootFS.DiffIDs { + rootFS.Append(id) + chid := rootFS.ChainID() + layerRefs[chid]++ + if _, ok := allLayers[chid]; !ok { + return nil, fmt.Errorf("layer %v was not found (corruption?)", chid) + } + } + imagesMap[img] = newImage + } + images = append(images, newImage) } + if withExtraAttrs { + // Get Shared and Unique sizes + for img, newImage := range imagesMap { + rootFS := *img.RootFS + rootFS.DiffIDs = nil + + newImage.Size = 0 + newImage.SharedSize = 0 + for _, id := range img.RootFS.DiffIDs { + rootFS.Append(id) + chid := rootFS.ChainID() + + diffSize, err := allLayers[chid].DiffSize() + if err != nil { + return nil, err + } + + if layerRefs[chid] > 1 { + newImage.SharedSize += diffSize + } else { + newImage.Size += diffSize + } + } + } + } + sort.Sort(sort.Reverse(byCreated(images))) return images, nil } -func newImage(image *image.Image, size int64) *types.Image { +func newImage(image *image.Image, virtualSize int64) *types.Image { newImage := new(types.Image) newImage.ParentID = image.Parent.String() newImage.ID = image.ID().String() newImage.Created = image.Created.Unix() - newImage.Size = size - newImage.VirtualSize = size + newImage.Size = -1 + newImage.VirtualSize = virtualSize + newImage.SharedSize = -1 + newImage.Containers = -1 if image.Config != nil { newImage.Labels = image.Config.Labels }