diff --git a/libpod/image/df.go b/libpod/image/df.go new file mode 100644 index 0000000000..84cf7af9ec --- /dev/null +++ b/libpod/image/df.go @@ -0,0 +1,126 @@ +package image + +import ( + "context" + "time" + + "github.com/containers/image/v5/docker/reference" +) + +// DiskUsageStat gives disk-usage statistics for a specific image. +type DiskUsageStat struct { + // ID of the image. + ID string + // Repository of the first recorded name of the image. + Repository string + // Tag of the first recorded name of the image. + Tag string + // Created is the creation time of the image. + Created time.Time + // SharedSize is the amount of space shared with another image. + SharedSize uint64 + // UniqueSize is the amount of space used only by this image. + UniqueSize uint64 + // Size is the total size of the image (i.e., the sum of the shared and + // unique size). + Size uint64 + // Number of containers using the image. + Containers int +} + +// DiskUsage returns disk-usage statistics for the specified slice of images. +func (ir *Runtime) DiskUsage(ctx context.Context, images []*Image) ([]DiskUsageStat, error) { + stats := make([]DiskUsageStat, len(images)) + + // Build a layerTree to quickly compute (and cache!) parent/child + // relations. + tree, err := ir.layerTree() + if err != nil { + return nil, err + } + + // Calculate the stats for each image. + for i, img := range images { + stat, err := diskUsageForImage(ctx, img, tree) + if err != nil { + return nil, err + } + stats[i] = *stat + } + + return stats, nil +} + +// diskUsageForImage returns the disk-usage statistics for the spcified image. +func diskUsageForImage(ctx context.Context, image *Image, tree *layerTree) (*DiskUsageStat, error) { + stat := DiskUsageStat{ + ID: image.ID(), + Created: image.Created(), + } + + // Repository and tag. + var name, repository, tag string + for _, n := range image.Names() { + if len(n) > 0 { + name = n + break + } + } + if len(name) > 0 { + named, err := reference.ParseNormalizedNamed(name) + if err != nil { + return nil, err + } + repository = named.Name() + if tagged, isTagged := named.(reference.NamedTagged); isTagged { + tag = tagged.Tag() + } + } else { + repository = "" + tag = "" + } + stat.Repository = repository + stat.Tag = tag + + // 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(ctx) + if err != nil { + return nil, err + } + stat.UniqueSize = *size + + if len(childIDs) > 0 { + // If we have children, we share everything. + stat.SharedSize = stat.UniqueSize + stat.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(ctx) + if err != nil { + return nil, err + } + stat.UniqueSize -= *size + stat.SharedSize = *size + } + + stat.Size = stat.SharedSize + stat.UniqueSize + + // Number of containers using the image. + containers, err := image.Containers() + if err != nil { + return nil, err + } + stat.Containers = len(containers) + + return &stat, nil +} diff --git a/pkg/domain/infra/abi/system.go b/pkg/domain/infra/abi/system.go index 914a7681dc..57c098166d 100644 --- a/pkg/domain/infra/abi/system.go +++ b/pkg/domain/infra/abi/system.go @@ -17,7 +17,6 @@ import ( "github.com/containers/podman/v2/pkg/rootless" "github.com/containers/podman/v2/pkg/util" "github.com/containers/podman/v2/utils" - "github.com/docker/distribution/reference" "github.com/pkg/errors" "github.com/sirupsen/logrus" "github.com/spf13/cobra" @@ -199,71 +198,32 @@ func (ic *ContainerEngine) SystemDf(ctx context.Context, options entities.System dfImages = []*entities.SystemDfImageReport{} ) - // Get Images and iterate them + // Compute disk-usage stats for all local images. imgs, err := ic.Libpod.ImageRuntime().GetImages() if err != nil { return nil, err } - for _, i := range imgs { - var sharedSize uint64 - cons, err := i.Containers() - if err != nil { - return nil, err - } - imageSize, err := i.Size(ctx) - if err != nil { - return nil, err - } - uniqueSize := *imageSize - parent, err := i.GetParent(ctx) - if err != nil { - return nil, err - } - if parent != nil { - parentSize, err := parent.Size(ctx) - if err != nil { - return nil, err - } - uniqueSize = *parentSize - *imageSize - sharedSize = *imageSize - uniqueSize - } - var name, repository, tag string - for _, n := range i.Names() { - if len(n) > 0 { - name = n - break - } - } - - if len(name) > 0 { - named, err := reference.ParseNormalizedNamed(name) - if err != nil { - return nil, err - } - repository = named.Name() - if tagged, isTagged := named.(reference.NamedTagged); isTagged { - tag = tagged.Tag() - } - } else { - repository = "" - tag = "" - } + imageStats, err := ic.Libpod.ImageRuntime().DiskUsage(ctx, imgs) + if err != nil { + return nil, err + } + for _, stat := range imageStats { report := entities.SystemDfImageReport{ - Repository: repository, - Tag: tag, - ImageID: i.ID(), - Created: i.Created(), - Size: int64(*imageSize), - SharedSize: int64(sharedSize), - UniqueSize: int64(uniqueSize), - Containers: len(cons), + Repository: stat.Repository, + Tag: stat.Tag, + ImageID: stat.ID, + Created: stat.Created, + Size: int64(stat.Size), + SharedSize: int64(stat.SharedSize), + UniqueSize: int64(stat.UniqueSize), + Containers: stat.Containers, } dfImages = append(dfImages, &report) } - // GetContainers and iterate them + // Get Containers and iterate them cons, err := ic.Libpod.GetAllContainers() if err != nil { return nil, err