summaryrefslogtreecommitdiff
path: root/vendor/github.com/containers/common/libimage/disk_usage.go
blob: f27edc03bd5909cb1af9b6fafba2553d79574955 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
package libimage

import (
	"context"
	"time"
)

// ImageDiskUsage reports the total size of an image.  That is the size
type ImageDiskUsage struct {
	// Number of containers using the image.
	Containers int
	// ID of the image.
	ID string
	// Repository of the image.
	Repository string
	// Tag of the image.
	Tag string
	// Created time stamp.
	Created time.Time
	// The amount of space that an image shares with another one (i.e. their common data).
	SharedSize int64
	// The the amount of space that is only used by a given image.
	UniqueSize int64
	// Sum of shared an unique size.
	Size int64
}

// DiskUsage calculates the disk usage for each image in the local containers
// storage.  Note that a single image may yield multiple usage reports, one for
// each repository tag.
func (r *Runtime) DiskUsage(ctx context.Context) ([]ImageDiskUsage, int64, error) {
	images, err := r.ListImages(ctx, nil, nil)
	if err != nil {
		return nil, -1, err
	}

	layerTree, err := r.layerTree(images)
	if err != nil {
		return nil, -1, err
	}

	var totalSize int64
	visitedImages := make(map[string]bool)
	visistedLayers := make(map[string]bool)

	var allUsages []ImageDiskUsage
	for _, image := range images {
		usages, err := diskUsageForImage(ctx, image, layerTree)
		if err != nil {
			return nil, -1, err
		}
		allUsages = append(allUsages, usages...)

		if _, ok := visitedImages[image.ID()]; ok {
			// Do not count an image twice
			continue
		}
		visitedImages[image.ID()] = true

		size, err := image.Size()
		if err != nil {
			return nil, -1, err
		}
		for _, layer := range layerTree.layersOf(image) {
			if _, ok := visistedLayers[layer.ID]; ok {
				// Do not count a layer twice, so remove its
				// size from the image size.
				size -= layer.UncompressedSize
				continue
			}
			visistedLayers[layer.ID] = true
		}
		totalSize += size
	}
	return allUsages, totalSize, err
}

// diskUsageForImage returns the disk-usage baseistics for the specified image.
func diskUsageForImage(ctx context.Context, image *Image, tree *layerTree) ([]ImageDiskUsage, error) {
	if err := image.isCorrupted(""); err != nil {
		return nil, err
	}

	base := ImageDiskUsage{
		ID:         image.ID(),
		Created:    image.Created(),
		Repository: "<none>",
		Tag:        "<none>",
	}

	// Shared, unique and total size.
	parent, err := tree.parent(ctx, image)
	if err != nil {
		return nil, err
	}
	childIDs, err := tree.children(ctx, image, false)
	if err != nil {
		return nil, err
	}

	// Optimistically set unique size to the full size of the image.
	size, err := image.Size()
	if err != nil {
		return nil, err
	}
	base.UniqueSize = size

	if len(childIDs) > 0 {
		// If we have children, we share everything.
		base.SharedSize = base.UniqueSize
		base.UniqueSize = 0
	} else if parent != nil {
		// If we have no children but a parent, remove the parent
		// (shared) size from the unique one.
		size, err := parent.Size()
		if err != nil {
			return nil, err
		}
		base.UniqueSize -= size
		base.SharedSize = size
	}

	base.Size = base.SharedSize + base.UniqueSize

	// Number of containers using the image.
	containers, err := image.Containers()
	if err != nil {
		return nil, err
	}
	base.Containers = len(containers)

	repoTags, err := image.NamedRepoTags()
	if err != nil {
		return nil, err
	}

	if len(repoTags) == 0 {
		return []ImageDiskUsage{base}, nil
	}

	pairs, err := ToNameTagPairs(repoTags)
	if err != nil {
		return nil, err
	}

	results := make([]ImageDiskUsage, len(pairs))
	for i, pair := range pairs {
		res := base
		res.Repository = pair.Name
		res.Tag = pair.Tag
		results[i] = res
	}

	return results, nil
}