mirror of
https://github.com/podman-container-tools/podman.git
synced 2026-09-05 07:07:51 +00:00
Listing images has shown increasing performance penalties with an increasing number of images. Unless `--all` is specified, Podman will filter intermediate images. Determining intermediate images has been done by finding (and comparing!) parent images which is expensive. We had to query the storage many times which turned it into a bottleneck. Instead, create a layer tree and assign one or more images to nodes that match the images' top layer. Determining the children of an image is now exponentially faster as we already know the child images from the layer graph and the images using the same top layer, which may also be considered child images based on their history. On my system with 510 images, a rootful image list drops from 6 secs down to 0.3 secs. Also use the tree to compute parent nodes, and to filter intermediate images for pruning. Signed-off-by: Valentin Rothberg <rothberg@redhat.com>
153 lines
3.6 KiB
Go
153 lines
3.6 KiB
Go
package image
|
|
|
|
import (
|
|
"context"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/containers/libpod/v2/libpod/events"
|
|
"github.com/containers/libpod/v2/pkg/timetype"
|
|
"github.com/containers/storage"
|
|
"github.com/pkg/errors"
|
|
"github.com/sirupsen/logrus"
|
|
)
|
|
|
|
func generatePruneFilterFuncs(filter, filterValue string) (ImageFilter, error) {
|
|
switch filter {
|
|
case "label":
|
|
var filterArray = strings.SplitN(filterValue, "=", 2)
|
|
var filterKey = filterArray[0]
|
|
if len(filterArray) > 1 {
|
|
filterValue = filterArray[1]
|
|
} else {
|
|
filterValue = ""
|
|
}
|
|
return func(i *Image) bool {
|
|
labels, err := i.Labels(context.Background())
|
|
if err != nil {
|
|
return false
|
|
}
|
|
for labelKey, labelValue := range labels {
|
|
if labelKey == filterKey && ("" == filterValue || labelValue == filterValue) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}, nil
|
|
|
|
case "until":
|
|
ts, err := timetype.GetTimestamp(filterValue, time.Now())
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
seconds, nanoseconds, err := timetype.ParseTimestamps(ts, 0)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
until := time.Unix(seconds, nanoseconds)
|
|
return func(i *Image) bool {
|
|
if !until.IsZero() && i.Created().After((until)) {
|
|
return true
|
|
}
|
|
return false
|
|
}, nil
|
|
|
|
}
|
|
return nil, nil
|
|
}
|
|
|
|
// GetPruneImages returns a slice of images that have no names/unused
|
|
func (ir *Runtime) GetPruneImages(ctx context.Context, all bool, filterFuncs []ImageFilter) ([]*Image, error) {
|
|
var (
|
|
pruneImages []*Image
|
|
)
|
|
|
|
allImages, err := ir.GetRWImages()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
tree, err := ir.layerTree()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
for _, i := range allImages {
|
|
// filter the images based on this.
|
|
for _, filterFunc := range filterFuncs {
|
|
if !filterFunc(i) {
|
|
continue
|
|
}
|
|
}
|
|
|
|
if all {
|
|
containers, err := i.Containers()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if len(containers) < 1 {
|
|
pruneImages = append(pruneImages, i)
|
|
continue
|
|
}
|
|
}
|
|
|
|
// skip the cache (i.e., with parent) and intermediate (i.e.,
|
|
// with children) images
|
|
intermediate, err := tree.hasChildrenAndParent(ctx, i)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if intermediate {
|
|
continue
|
|
}
|
|
|
|
if i.Dangling() {
|
|
pruneImages = append(pruneImages, i)
|
|
}
|
|
}
|
|
return pruneImages, nil
|
|
}
|
|
|
|
// PruneImages prunes dangling and optionally all unused images from the local
|
|
// image store
|
|
func (ir *Runtime) PruneImages(ctx context.Context, all bool, filter []string) ([]string, error) {
|
|
filterFuncs := make([]ImageFilter, 0, len(filter))
|
|
for _, f := range filter {
|
|
filterSplit := strings.SplitN(f, "=", 2)
|
|
if len(filterSplit) < 2 {
|
|
return nil, errors.Errorf("filter input must be in the form of filter=value: %s is invalid", f)
|
|
}
|
|
|
|
generatedFunc, err := generatePruneFilterFuncs(filterSplit[0], filterSplit[1])
|
|
if err != nil {
|
|
return nil, errors.Wrapf(err, "invalid filter")
|
|
}
|
|
filterFuncs = append(filterFuncs, generatedFunc)
|
|
}
|
|
|
|
pruneImages, err := ir.GetPruneImages(ctx, all, filterFuncs)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "unable to get images to prune")
|
|
}
|
|
prunedCids := make([]string, 0, len(pruneImages))
|
|
for _, p := range pruneImages {
|
|
repotags, err := p.RepoTags()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if err := p.Remove(ctx, true); err != nil {
|
|
if errors.Cause(err) == storage.ErrImageUsedByContainer {
|
|
logrus.Warnf("Failed to prune image %s as it is in use: %v", p.ID(), err)
|
|
continue
|
|
}
|
|
return nil, errors.Wrap(err, "failed to prune image")
|
|
}
|
|
defer p.newImageEvent(events.Prune)
|
|
nameOrID := p.ID()
|
|
if len(repotags) > 0 {
|
|
nameOrID = repotags[0]
|
|
}
|
|
prunedCids = append(prunedCids, nameOrID)
|
|
}
|
|
return prunedCids, nil
|
|
}
|