Skip to content
This repository has been archived by the owner on Nov 1, 2022. It is now read-only.

Commit

Permalink
Cache the result of sorting images by timestamp
Browse files Browse the repository at this point in the history
Much of the time, the images returned from the ListImagesWithOptions
API are sorted according to timestamp. As a cheap and simple
optimisation for this case, when we're constructing a result, cache
both the slice of image.Info records and those records sorted by
timestamp.

This means, for any given request, images that occur in more than one
container will be sorted by timestamp only once.
  • Loading branch information
squaremo committed Aug 8, 2019
1 parent 247613a commit d229dd3
Show file tree
Hide file tree
Showing 4 changed files with 87 additions and 18 deletions.
16 changes: 12 additions & 4 deletions api/v6/container.go
Expand Up @@ -26,8 +26,16 @@ type Container struct {
NewFilteredImagesCount int `json:",omitempty"`
}

type imageSorter interface {
// SortedImages returns the known images, sorted according to the
// pattern given
SortedImages(policy.Pattern) update.SortedImageInfos
// Images returns the images in no defined order
Images() []image.Info
}

// NewContainer creates a Container given a list of images and the current image
func NewContainer(name string, images []image.Info, currentImage image.Info, tagPattern policy.Pattern, fields []string) (Container, error) {
func NewContainer(name string, images imageSorter, currentImage image.Info, tagPattern policy.Pattern, fields []string) (Container, error) {
// Default fields
if len(fields) == 0 {
fields = []string{
Expand Down Expand Up @@ -57,7 +65,7 @@ func NewContainer(name string, images []image.Info, currentImage image.Info, tag

getFilteredImages := func() []image.Info {
if filteredImages == nil {
filteredImages = update.FilterImages(images, tagPattern)
filteredImages = update.FilterImages(images.Images(), tagPattern)
}
return filteredImages
}
Expand All @@ -71,7 +79,7 @@ func NewContainer(name string, images []image.Info, currentImage image.Info, tag

getSortedImages := func() update.SortedImageInfos {
if sortedImages == nil {
sortedImages = update.SortImages(images, tagPattern)
sortedImages = images.SortedImages(tagPattern)
// now that we have the sorted images anyway, the fastest
// way to get sorted, filtered images will be to filter
// the already sorted images
Expand Down Expand Up @@ -104,7 +112,7 @@ func NewContainer(name string, images []image.Info, currentImage image.Info, tag
c.AvailableError = registry.ErrNoImageData.Error()
}
case "AvailableImagesCount":
c.AvailableImagesCount = len(images)
c.AvailableImagesCount = len(images.Images())

// these required the sorted images, which we can get
// straight away
Expand Down
13 changes: 12 additions & 1 deletion api/v6/container_test.go
Expand Up @@ -7,8 +7,19 @@ import (

"github.com/weaveworks/flux/image"
"github.com/weaveworks/flux/policy"
"github.com/weaveworks/flux/update"
)

type justSlice []image.Info

func (j justSlice) Images() []image.Info {
return []image.Info(j)
}

func (j justSlice) SortedImages(p policy.Pattern) update.SortedImageInfos {
return update.SortImages(j.Images(), p)
}

func TestNewContainer(t *testing.T) {

testImage := image.Info{ImageID: "test"}
Expand Down Expand Up @@ -127,7 +138,7 @@ func TestNewContainer(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := NewContainer(tt.args.name, tt.args.images, tt.args.currentImage, tt.args.tagPattern, tt.args.fields)
got, err := NewContainer(tt.args.name, justSlice(tt.args.images), tt.args.currentImage, tt.args.tagPattern, tt.args.fields)
assert.Equal(t, tt.wantErr, err != nil)
assert.Equal(t, tt.want, got)
})
Expand Down
63 changes: 51 additions & 12 deletions daemon/daemon.go
Expand Up @@ -670,30 +670,69 @@ func containers2containers(cs []resource.Container) []v6.Container {
return res
}

// Much of the time, images will be sorted by timestamp. At marginal
// cost, we cache the result of sorting, so that other uses of the
// image can reuse it (if they are also sorted by timestamp).

type repo struct {
images []image.Info
imagesByTag map[string]image.Info
imagesSortedByCreated update.SortedImageInfos
}

func (r *repo) SortedImages(p policy.Pattern) update.SortedImageInfos {
// RequiresTimestamp means "ordered by timestamp" (it's required
// because no comparison to see which image is newer can be made
// if a timestamp is missing)
if p.RequiresTimestamp() {
if r.imagesSortedByCreated == nil {
r.imagesSortedByCreated = update.SortImages(r.images, p)
}
return r.imagesSortedByCreated
}
return update.SortImages(r.images, p)
}

func (r *repo) Images() []image.Info {
return r.images
}

func (r *repo) ImageByTag(tag string) image.Info {
return r.imagesByTag[tag]
}

func getWorkloadContainers(workload cluster.Workload, imageRepos update.ImageRepos, resource resource.Resource, fields []string) (res []v6.Container, err error) {
repos := map[image.Name]*repo{}

for _, c := range workload.ContainersOrNil() {
imageRepo := c.Image.Name
imageName := c.Image.Name
var policies policy.Set
if resource != nil {
policies = resource.Policies()
}
tagPattern := policy.GetTagPattern(policies, c.Name)

repoMetadata := imageRepos.GetRepositoryMetadata(imageRepo)
var images []image.Info
// Build images, tolerating tags with missing metadata
for _, tag := range repoMetadata.Tags {
info, ok := repoMetadata.Images[tag]
if !ok {
info = image.Info{
ID: image.Ref{Tag: tag},
imageRepo, ok := repos[imageName]
if !ok {
repoMetadata := imageRepos.GetRepositoryMetadata(imageName)
var images []image.Info
// Build images, tolerating tags with missing metadata
for _, tag := range repoMetadata.Tags {
info, ok := repoMetadata.Images[tag]
if !ok {
info = image.Info{
ID: image.Ref{Tag: tag},
}
}
images = append(images, info)
}
images = append(images, info)
imageRepo = &repo{images: images, imagesByTag: repoMetadata.Images}
repos[imageName] = imageRepo
}
currentImage := repoMetadata.FindImageWithRef(c.Image)

container, err := v6.NewContainer(c.Name, images, currentImage, tagPattern, fields)
currentImage := imageRepo.ImageByTag(c.Image.Tag)

container, err := v6.NewContainer(c.Name, imageRepo, currentImage, tagPattern, fields)
if err != nil {
return res, err
}
Expand Down
13 changes: 12 additions & 1 deletion remote/rpc/compat.go
Expand Up @@ -9,6 +9,7 @@ import (
"github.com/weaveworks/flux/api/v11"
"github.com/weaveworks/flux/api/v6"
"github.com/weaveworks/flux/cluster"
"github.com/weaveworks/flux/image"
"github.com/weaveworks/flux/policy"
"github.com/weaveworks/flux/remote"
"github.com/weaveworks/flux/resource"
Expand Down Expand Up @@ -138,6 +139,16 @@ type listImagesWithoutOptionsClient interface {
ListImages(ctx context.Context, spec update.ResourceSpec) ([]v6.ImageStatus, error)
}

type alreadySorted update.SortedImageInfos

func (infos alreadySorted) Images() []image.Info {
return []image.Info(infos)
}

func (infos alreadySorted) SortedImages(_ policy.Pattern) update.SortedImageInfos {
return update.SortedImageInfos(infos)
}

// listImagesWithOptions is called by ListImagesWithOptions so we can use an
// interface to dispatch .ListImages() and .ListServices() to the correct
// API version.
Expand Down Expand Up @@ -177,7 +188,7 @@ func listImagesWithOptions(ctx context.Context, client listImagesWithoutOptionsC
}
tagPattern := policy.GetTagPattern(p, container.Name)
// Create a new container using the same function used in v10
newContainer, err := v6.NewContainer(container.Name, container.Available, container.Current, tagPattern, opts.OverrideContainerFields)
newContainer, err := v6.NewContainer(container.Name, alreadySorted(container.Available), container.Current, tagPattern, opts.OverrideContainerFields)
if err != nil {
return statuses, err
}
Expand Down

0 comments on commit d229dd3

Please sign in to comment.