Browse code

Update Images() to allow retrieving specific image size data

Those data include:
- size of data shared with other images
- size of data unique to a given image
- how many containers are using a given image

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

Kenfe-Mickael Laventure authored on 2016/08/24 08:19:37
Showing 5 changed files
... ...
@@ -25,7 +25,7 @@ type containerBackend interface {
25 25
 type imageBackend interface {
26 26
 	ImageDelete(imageRef string, force, prune bool) ([]types.ImageDelete, error)
27 27
 	ImageHistory(imageName string) ([]*types.ImageHistory, error)
28
-	Images(filterArgs string, filter string, all bool) ([]*types.Image, error)
28
+	Images(filterArgs string, filter string, all bool, withExtraAttrs bool) ([]*types.Image, error)
29 29
 	LookupImage(name string) (*types.ImageInspect, error)
30 30
 	TagImage(imageName, repository, tag string) error
31 31
 }
... ...
@@ -248,7 +248,7 @@ func (s *imageRouter) getImagesJSON(ctx context.Context, w http.ResponseWriter,
248 248
 	}
249 249
 
250 250
 	// FIXME: The filter parameter could just be a match filter
251
-	images, err := s.backend.Images(r.Form.Get("filters"), r.Form.Get("filter"), httputils.BoolValue(r, "all"))
251
+	images, err := s.backend.Images(r.Form.Get("filters"), r.Form.Get("filter"), httputils.BoolValue(r, "all"), false)
252 252
 	if err != nil {
253 253
 		return err
254 254
 	}
... ...
@@ -95,8 +95,10 @@ type Image struct {
95 95
 	RepoDigests []string
96 96
 	Created     int64
97 97
 	Size        int64
98
+	SharedSize  int64
98 99
 	VirtualSize int64
99 100
 	Labels      map[string]string
101
+	Containers  int64
100 102
 }
101 103
 
102 104
 // GraphDriverData returns Image's graph driver config info
... ...
@@ -225,5 +225,6 @@ func (c *imageContext) CreatedAt() string {
225 225
 
226 226
 func (c *imageContext) Size() string {
227 227
 	c.AddHeader(sizeHeader)
228
-	return units.HumanSizeWithPrecision(float64(c.i.Size), 3)
228
+	//NOTE: For backward compatibility we need to return VirtualSize
229
+	return units.HumanSizeWithPrecision(float64(c.i.VirtualSize), 3)
229 230
 }
... ...
@@ -7,6 +7,7 @@ import (
7 7
 
8 8
 	"github.com/docker/docker/api/types"
9 9
 	"github.com/docker/docker/api/types/filters"
10
+	"github.com/docker/docker/container"
10 11
 	"github.com/docker/docker/image"
11 12
 	"github.com/docker/docker/layer"
12 13
 	"github.com/docker/docker/reference"
... ...
@@ -37,7 +38,7 @@ func (daemon *Daemon) Map() map[image.ID]*image.Image {
37 37
 // filter is a shell glob string applied to repository names. The argument
38 38
 // named all controls whether all images in the graph are filtered, or just
39 39
 // the heads.
40
-func (daemon *Daemon) Images(filterArgs, filter string, all bool) ([]*types.Image, error) {
40
+func (daemon *Daemon) Images(filterArgs, filter string, all bool, withExtraAttrs bool) ([]*types.Image, error) {
41 41
 	var (
42 42
 		allImages    map[image.ID]*image.Image
43 43
 		err          error
... ...
@@ -83,6 +84,10 @@ func (daemon *Daemon) Images(filterArgs, filter string, all bool) ([]*types.Imag
83 83
 	}
84 84
 
85 85
 	images := []*types.Image{}
86
+	var imagesMap map[*image.Image]*types.Image
87
+	var layerRefs map[layer.ChainID]int
88
+	var allLayers map[layer.ChainID]layer.Layer
89
+	var allContainers []*container.Container
86 90
 
87 91
 	var filterTagged bool
88 92
 	if filter != "" {
... ...
@@ -171,21 +176,80 @@ func (daemon *Daemon) Images(filterArgs, filter string, all bool) ([]*types.Imag
171 171
 			continue
172 172
 		}
173 173
 
174
+		if withExtraAttrs {
175
+			// lazyly init variables
176
+			if len(allContainers) == 0 {
177
+				allContainers = daemon.List()
178
+				allLayers = daemon.layerStore.Map()
179
+				imagesMap = make(map[*image.Image]*types.Image)
180
+				layerRefs = make(map[layer.ChainID]int)
181
+			}
182
+
183
+			// Get container count
184
+			newImage.Containers = 0
185
+			for _, c := range allContainers {
186
+				if c.ImageID == id {
187
+					newImage.Containers++
188
+				}
189
+			}
190
+
191
+			// count layer references
192
+			rootFS := *img.RootFS
193
+			rootFS.DiffIDs = nil
194
+			for _, id := range img.RootFS.DiffIDs {
195
+				rootFS.Append(id)
196
+				chid := rootFS.ChainID()
197
+				layerRefs[chid]++
198
+				if _, ok := allLayers[chid]; !ok {
199
+					return nil, fmt.Errorf("layer %v was not found (corruption?)", chid)
200
+				}
201
+			}
202
+			imagesMap[img] = newImage
203
+		}
204
+
174 205
 		images = append(images, newImage)
175 206
 	}
176 207
 
208
+	if withExtraAttrs {
209
+		// Get Shared and Unique sizes
210
+		for img, newImage := range imagesMap {
211
+			rootFS := *img.RootFS
212
+			rootFS.DiffIDs = nil
213
+
214
+			newImage.Size = 0
215
+			newImage.SharedSize = 0
216
+			for _, id := range img.RootFS.DiffIDs {
217
+				rootFS.Append(id)
218
+				chid := rootFS.ChainID()
219
+
220
+				diffSize, err := allLayers[chid].DiffSize()
221
+				if err != nil {
222
+					return nil, err
223
+				}
224
+
225
+				if layerRefs[chid] > 1 {
226
+					newImage.SharedSize += diffSize
227
+				} else {
228
+					newImage.Size += diffSize
229
+				}
230
+			}
231
+		}
232
+	}
233
+
177 234
 	sort.Sort(sort.Reverse(byCreated(images)))
178 235
 
179 236
 	return images, nil
180 237
 }
181 238
 
182
-func newImage(image *image.Image, size int64) *types.Image {
239
+func newImage(image *image.Image, virtualSize int64) *types.Image {
183 240
 	newImage := new(types.Image)
184 241
 	newImage.ParentID = image.Parent.String()
185 242
 	newImage.ID = image.ID().String()
186 243
 	newImage.Created = image.Created.Unix()
187
-	newImage.Size = size
188
-	newImage.VirtualSize = size
244
+	newImage.Size = -1
245
+	newImage.VirtualSize = virtualSize
246
+	newImage.SharedSize = -1
247
+	newImage.Containers = -1
189 248
 	if image.Config != nil {
190 249
 		newImage.Labels = image.Config.Labels
191 250
 	}