diff --git a/pkg/adapter/adapter.go b/pkg/adapter/adapter.go index e06dbd63..efa9ba5e 100644 --- a/pkg/adapter/adapter.go +++ b/pkg/adapter/adapter.go @@ -116,8 +116,8 @@ func (adp *LocalAdapter) Convert(ctx context.Context, source string) error { return errors.Wrap(err, "create lease") } defer done(ctx) - - return adp.cvt.Convert(ctx, source, target) + _, err = adp.cvt.Convert(ctx, source, target) + return err } func (adp *LocalAdapter) Dispatch(ctx context.Context, ref string, sync bool) error { diff --git a/pkg/converter/converter.go b/pkg/converter/converter.go index 5b820b65..48e4c462 100644 --- a/pkg/converter/converter.go +++ b/pkg/converter/converter.go @@ -85,14 +85,15 @@ func (cvt *Converter) pull(ctx context.Context, source string) error { return nil } -func (cvt *Converter) Convert(ctx context.Context, source, target string) error { +func (cvt *Converter) Convert(ctx context.Context, source, target string) (*Metric, error) { + var metric Metric sourceNamed, err := docker.ParseDockerRef(source) if err != nil { - return errors.Wrap(err, "parse source reference") + return nil, errors.Wrap(err, "parse source reference") } targetNamed, err := docker.ParseDockerRef(target) if err != nil { - return errors.Wrap(err, "parse target reference") + return nil, errors.Wrap(err, "parse target reference") } source = sourceNamed.String() target = targetNamed.String() @@ -104,21 +105,29 @@ func (cvt *Converter) Convert(ctx context.Context, source, target string) error logger.Infof("try to pull with plain HTTP for %s", source) cvt.provider.UsePlainHTTP() if err := cvt.pull(ctx, source); err != nil { - return errors.Wrap(err, "try to pull image") + return nil, errors.Wrap(err, "try to pull image") } } else { - return errors.Wrap(err, "pull image") + return nil, errors.Wrap(err, "pull image") } } - logger.Infof("pulled image %s, elapse %s", source, time.Since(start)) + metric.SourcePullElapsed = time.Since(start) + if err := metric.SetSourceImageSize(ctx, cvt, source); err != nil { + return nil, errors.Wrap(err, "get source image size") + } + logger.Infof("pulled image %s, elapse %s", source, metric.SourcePullElapsed) logger.Infof("converting image %s", source) start = time.Now() desc, err := cvt.driver.Convert(ctx, cvt.provider, source) if err != nil { - return errors.Wrap(err, "convert image") + return nil, errors.Wrap(err, "convert image") + } + metric.ConversionElapsed = time.Since(start) + if err := metric.SetTargetImageSize(ctx, cvt.provider.ContentStore(), desc); err != nil { + return nil, errors.Wrap(err, "get target image size") } - logger.Infof("converted image %s, elapse %s", target, time.Since(start)) + logger.Infof("converted image %s, elapse %s", target, metric.ConversionElapsed) start = time.Now() logger.Infof("pushing image %s", target) @@ -127,13 +136,14 @@ func (cvt *Converter) Convert(ctx context.Context, source, target string) error logger.Infof("try to push with plain HTTP for %s", target) cvt.provider.UsePlainHTTP() if err := cvt.provider.Push(ctx, *desc, target); err != nil { - return errors.Wrap(err, "try to push image") + return nil, errors.Wrap(err, "try to push image") } } else { - return errors.Wrap(err, "push image") + return nil, errors.Wrap(err, "push image") } } - logger.Infof("pushed image %s, elapse %s", target, time.Since(start)) + metric.TargetPushElapsed = time.Since(start) + logger.Infof("pushed image %s, elapse %s", target, metric.TargetPushElapsed) - return nil + return &metric, nil } diff --git a/pkg/converter/metric.go b/pkg/converter/metric.go new file mode 100644 index 00000000..1f5291ac --- /dev/null +++ b/pkg/converter/metric.go @@ -0,0 +1,90 @@ +// Copyright Project Harbor Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package converter + +import ( + "context" + "time" + + "github.com/containerd/containerd/content" + "github.com/containerd/containerd/images" + ocispec "github.com/opencontainers/image-spec/specs-go/v1" + "github.com/pkg/errors" +) + +const ( + MediaTypeDockerSchema2Manifest = "application/vnd.docker.distribution.manifest.v2+json" + MediaTypeDockerSchema2ManifestList = "application/vnd.docker.distribution.manifest.list.v2+json" +) + +// Metric supports converter to collect metric +type Metric struct { + // converter source image size + SourceImageSize int64 + // converter target image size + TargetImageSize int64 + // converter pull source image time + SourcePullElapsed time.Duration + // converter convert image time + ConversionElapsed time.Duration + // converter push target image time + TargetPushElapsed time.Duration +} + +func (metric *Metric) SetTargetImageSize(ctx context.Context, cs content.Store, desc *ocispec.Descriptor) error { + var err error + metric.TargetImageSize, err = metric.imageSize(ctx, cs, desc) + return err +} + +func (metric *Metric) SetSourceImageSize(ctx context.Context, cvt *Converter, source string) error { + image, err := cvt.provider.Image(ctx, source) + if err != nil { + return err + } + metric.SourceImageSize, err = metric.imageSize(ctx, cvt.provider.ContentStore(), image) + return err +} + +func (metric *Metric) imageSize(ctx context.Context, cs content.Store, image *ocispec.Descriptor) (int64, error) { + var imageSize int64 + switch image.MediaType { + case ocispec.MediaTypeImageIndex, MediaTypeDockerSchema2ManifestList: + index, err := images.ChildrenHandler(cs)(ctx, *image) + if err != nil { + return imageSize, err + } + for _, manifest := range index { + layers, err := images.ChildrenHandler(cs)(ctx, manifest) + if err != nil { + return imageSize, err + } + for _, layer := range layers { + imageSize += layer.Size + } + } + case ocispec.MediaTypeImageManifest, MediaTypeDockerSchema2Manifest: + layers, err := images.ChildrenHandler(cs)(ctx, *image) + if err != nil { + return imageSize, err + } + for _, layer := range layers { + imageSize += layer.Size + } + default: + return imageSize, errors.New("Unknown descriptor type") + } + return imageSize, nil +}