The following pull request was submitted through Github.
It can be accessed and reviewed at: https://github.com/lxc/lxd/pull/5471

This e-mail was sent by the LXC bot, direct replies will not reach the author
unless they happen to be subscribed to this list.

=== Description (from pull-request) ===
By doing tar and compress in a combined stream, we require
1 less copy of the tarred image, and save time for IO for
writing and reading that file.

In my tests with an image of about 1.4GB when compressed this
saves 2.6GB of space used by the tar file, and reduces total
export time from approx 4m40s to 4m10s (30s quicker).

Signed-off-by: Joel Hockey <joelhoc...@chromium.org>
From d9dbcfe50f254dc8ac8362d03bda4ee8fd957a50 Mon Sep 17 00:00:00 2001
From: Joel Hockey <joelhoc...@chromium.org>
Date: Wed, 6 Feb 2019 20:26:04 -0800
Subject: [PATCH] images: Tar and compress in a combined stream when packing an
 image

By doing tar and compress in a combined stream, we require
1 less copy of the tarred image, and save time for IO for
writing and reading that file.

In my tests with an image of about 1.4GB when compressed this
saves 2.6GB of space used by the tar file, and reduces total
export time from approx 4m40s to 4m10s (30s quicker).

Signed-off-by: Joel Hockey <joelhoc...@chromium.org>
---
 lxd/images.go | 90 ++++++++++++++++++---------------------------------
 1 file changed, 31 insertions(+), 59 deletions(-)

diff --git a/lxd/images.go b/lxd/images.go
index 3a5f19239e..062c04ca59 100644
--- a/lxd/images.go
+++ b/lxd/images.go
@@ -186,13 +186,13 @@ func imgPostContInfo(d *Daemon, r *http.Request, req 
api.ImagesPost, op *operati
        }
 
        // Build the actual image file
-       tarfile, err := ioutil.TempFile(builddir, "lxd_build_tar_")
+       imageFile, err := ioutil.TempFile(builddir, "lxd_build_image_")
        if err != nil {
                return nil, err
        }
-       defer os.Remove(tarfile.Name())
+       defer os.Remove(imageFile.Name())
 
-       // Calculate (close estimate of) total size of tarfile
+       // Calculate (close estimate of) total size of input to image
        totalSize := int64(0)
        sumSize := func(path string, fi os.FileInfo, err error) error {
                if err == nil {
@@ -206,13 +206,12 @@ func imgPostContInfo(d *Daemon, r *http.Request, req 
api.ImagesPost, op *operati
                return nil, err
        }
 
-       // Track progress writing tarfile
+       // Track progress creating image.
        metadata := make(map[string]interface{})
-       tarfileProgressWriter := &ioprogress.ProgressWriter{
-               WriteCloser: tarfile,
+       imageProgressWriter := &ioprogress.ProgressWriter{
                Tracker: &ioprogress.ProgressTracker{
                        Handler: func(percent, speed int64) {
-                               shared.SetProgressMetadata(metadata, 
"create_image_from_container_tar", "Image tar", percent, speed)
+                               shared.SetProgressMetadata(metadata, 
"create_image_from_container_pack", "Image pack", percent, speed)
                                op.UpdateMetadata(metadata)
                        },
                        Length: totalSize,
@@ -220,7 +219,6 @@ func imgPostContInfo(d *Daemon, r *http.Request, req 
api.ImagesPost, op *operati
        }
 
        sha256 := sha256.New()
-       var compressedPath string
        var compress string
        var writer io.Writer
 
@@ -232,66 +230,40 @@ func imgPostContInfo(d *Daemon, r *http.Request, req 
api.ImagesPost, op *operati
                        return nil, err
                }
        }
-       usingCompression := compress != "none"
 
-       // If there is no compression, then calculate sha256 on tarfile
-       if usingCompression {
-               writer = tarfileProgressWriter
+       // Setup tar, optional compress and sha256 to happen in one pass.
+       wg := sync.WaitGroup{}
+       var compressErr error
+       if compress != "none" {
+               wg.Add(1)
+               tarReader, tarWriter := io.Pipe()
+               imageProgressWriter.WriteCloser = tarWriter
+               writer = imageProgressWriter
+               compressWriter := io.MultiWriter(imageFile, sha256)
+               go func() {
+                       defer wg.Done()
+                       compressErr = compressFile(compress, tarReader, 
compressWriter)
+               }()
        } else {
-               writer = io.MultiWriter(tarfileProgressWriter, sha256)
-               compressedPath = tarfile.Name()
+               imageProgressWriter.WriteCloser = imageFile
+               writer = io.MultiWriter(imageProgressWriter, sha256)
        }
 
        err = c.Export(writer, req.Properties)
+       // When compression is used, Close on imageProgressWriter/tarWriter
+       // is required for compressFile/gzip to know it is finished.
+       // Otherwise It is equivalent to imageFile.Close.
+       imageProgressWriter.Close()
+       wg.Wait()
        if err != nil {
-               tarfile.Close()
                return nil, err
        }
-       tarfile.Close()
-
-       if usingCompression {
-               tarfile, err = os.Open(tarfile.Name())
-               if err != nil {
-                       return nil, err
-               }
-               defer tarfile.Close()
-
-               fi, err := tarfile.Stat()
-               if err != nil {
-                       return nil, err
-               }
-
-               // Track progress writing gzipped file
-               metadata = make(map[string]interface{})
-               tarfileProgressReader := &ioprogress.ProgressReader{
-                       ReadCloser: tarfile,
-                       Tracker: &ioprogress.ProgressTracker{
-                               Length: fi.Size(),
-                               Handler: func(percent, speed int64) {
-                                       shared.SetProgressMetadata(metadata, 
"create_image_from_container_compress", "Image compress", percent, speed)
-                                       op.UpdateMetadata(metadata)
-                               },
-                       },
-               }
-               compressedPath = tarfile.Name() + ".compressed"
-
-               compressed, err := os.Create(compressedPath)
-               if err != nil {
-                       return nil, err
-               }
-               defer compressed.Close()
-               defer os.Remove(compressed.Name())
-
-               // Calculate sha256 as we compress
-               writer := io.MultiWriter(compressed, sha256)
-
-               err = compressFile(compress, tarfileProgressReader, writer)
-               if err != nil {
-                       return nil, err
-               }
+       if compressErr != nil {
+               return nil, err
        }
+       imageFile.Close()
 
-       fi, err := os.Stat(compressedPath)
+       fi, err := os.Stat(imageFile.Name())
        if err != nil {
                return nil, err
        }
@@ -309,7 +281,7 @@ func imgPostContInfo(d *Daemon, r *http.Request, req 
api.ImagesPost, op *operati
 
        /* rename the the file to the expected name so our caller can use it */
        finalName := shared.VarPath("images", info.Fingerprint)
-       err = shared.FileMove(compressedPath, finalName)
+       err = shared.FileMove(imageFile.Name(), finalName)
        if err != nil {
                return nil, err
        }
_______________________________________________
lxc-devel mailing list
lxc-devel@lists.linuxcontainers.org
http://lists.linuxcontainers.org/listinfo/lxc-devel

Reply via email to