[lxc-devel] [lxd/master] Improve progress tracking

stgraber on Github lxc-bot at linuxcontainers.org
Wed Nov 16 17:13:55 UTC 2016


A non-text attachment was scrubbed...
Name: not available
Type: text/x-mailbox
Size: 301 bytes
Desc: not available
URL: <http://lists.linuxcontainers.org/pipermail/lxc-devel/attachments/20161116/158be804/attachment.bin>
-------------- next part --------------
From b0777b2f8944da913837c5f08b4b49165f56190d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Wed, 16 Nov 2016 11:57:30 -0500
Subject: [PATCH 1/2] client: Rework progress handling
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Use one common approach to progress tracking and reporting.

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxc/image.go  | 34 ++++++++++-----------
 lxc/init.go   | 13 ++++----
 lxc/launch.go |  4 ++-
 lxc/main.go   | 38 +++++++++++++++++++++++
 po/lxd.pot    | 96 +++++++++++++++++++++++++++++------------------------------
 5 files changed, 111 insertions(+), 74 deletions(-)

diff --git a/lxc/image.go b/lxc/image.go
index c032d5c..54a4be0 100644
--- a/lxc/image.go
+++ b/lxc/image.go
@@ -276,14 +276,12 @@ func (c *imageCmd) run(config *lxd.Config, args []string) error {
 			return err
 		}
 
-		progressHandler := func(progress string) {
-			fmt.Printf(i18n.G("Copying the image: %s")+"\r", progress)
-		}
-
-		err = d.CopyImage(inName, dest, c.copyAliases, c.addAliases, c.publicImage, c.autoUpdate, progressHandler)
+		progress := ProgressRenderer{Format: i18n.G("Copying the image: %s")}
+		err = d.CopyImage(inName, dest, c.copyAliases, c.addAliases, c.publicImage, c.autoUpdate, progress.Update)
 		if err == nil {
-			fmt.Println(i18n.G("Image copied successfully!"))
+			progress.Done(i18n.G("Image copied successfully!"))
 		}
+
 		return err
 
 	case "delete":
@@ -417,29 +415,29 @@ func (c *imageCmd) run(config *lxd.Config, args []string) error {
 			return err
 		}
 
-		handler := func(percent int) {
-			fmt.Printf(i18n.G("Transferring image: %d%%")+"\r", percent)
-			if percent == 100 {
-				fmt.Printf("\n")
-			}
-		}
-
 		if strings.HasPrefix(imageFile, "https://") {
-			progressHandler := func(progress string) {
-				fmt.Printf(i18n.G("Importing the image: %s")+"\r", progress)
+			progress := ProgressRenderer{Format: i18n.G("Importing the image: %s")}
+			fingerprint, err = d.PostImageURL(imageFile, properties, c.publicImage, c.addAliases, progress.Update)
+			if err == nil {
+				progress.Done(fmt.Sprintf(i18n.G("Image imported with fingerprint: %s"), fingerprint))
 			}
-
-			fingerprint, err = d.PostImageURL(imageFile, properties, c.publicImage, c.addAliases, progressHandler)
 		} else if strings.HasPrefix(imageFile, "http://") {
 			return fmt.Errorf(i18n.G("Only https:// is supported for remote image import."))
 		} else {
+			progress := ProgressRenderer{Format: i18n.G("Transferring image: %s")}
+			handler := func(percent int) {
+				progress.Update(fmt.Sprintf("%d%%", percent))
+			}
+
 			fingerprint, err = d.PostImage(imageFile, rootfsFile, properties, c.publicImage, c.addAliases, handler)
+			if err == nil {
+				progress.Done(fmt.Sprintf(i18n.G("Image imported with fingerprint: %s"), fingerprint))
+			}
 		}
 
 		if err != nil {
 			return err
 		}
-		fmt.Printf(i18n.G("Image imported with fingerprint: %s")+"\n", fingerprint)
 
 		return nil
 
diff --git a/lxc/init.go b/lxc/init.go
index 454d301..306c932 100644
--- a/lxc/init.go
+++ b/lxc/init.go
@@ -205,9 +205,11 @@ func (c *initCmd) run(config *lxd.Config, args []string) error {
 		return err
 	}
 
-	c.initProgressTracker(d, resp.Operation)
+	progress := ProgressRenderer{}
+	c.initProgressTracker(d, &progress, resp.Operation)
 
 	err = d.WaitForSuccess(resp.Operation)
+	progress.Done("")
 
 	if err != nil {
 		return err
@@ -233,7 +235,8 @@ func (c *initCmd) run(config *lxd.Config, args []string) error {
 	return nil
 }
 
-func (c *initCmd) initProgressTracker(d *lxd.Client, operation string) {
+func (c *initCmd) initProgressTracker(d *lxd.Client, progress *ProgressRenderer, operation string) {
+	progress.Format = i18n.G("Retrieving image: %s")
 	handler := func(msg interface{}) {
 		if msg == nil {
 			return
@@ -264,11 +267,7 @@ func (c *initCmd) initProgressTracker(d *lxd.Client, operation string) {
 		opMd := md["metadata"].(map[string]interface{})
 		_, ok := opMd["download_progress"]
 		if ok {
-			fmt.Printf(i18n.G("Retrieving image: %s")+"\r", opMd["download_progress"].(string))
-		}
-
-		if opMd["download_progress"].(string) == "100%" {
-			fmt.Printf("\n")
+			progress.Update(opMd["download_progress"].(string))
 		}
 	}
 	go d.Monitor([]string{"operation"}, handler)
diff --git a/lxc/launch.go b/lxc/launch.go
index 439302c..7c7cf7b 100644
--- a/lxc/launch.go
+++ b/lxc/launch.go
@@ -103,7 +103,8 @@ func (c *launchCmd) run(config *lxd.Config, args []string) error {
 		return err
 	}
 
-	c.init.initProgressTracker(d, resp.Operation)
+	progress := ProgressRenderer{}
+	c.init.initProgressTracker(d, &progress, resp.Operation)
 
 	if name == "" {
 		op, err := resp.MetadataAsOperation()
@@ -136,6 +137,7 @@ func (c *launchCmd) run(config *lxd.Config, args []string) error {
 	if err = d.WaitForSuccess(resp.Operation); err != nil {
 		return err
 	}
+	progress.Done("")
 
 	c.init.checkNetwork(d, name)
 
diff --git a/lxc/main.go b/lxc/main.go
index 3dc7378..b50904e 100644
--- a/lxc/main.go
+++ b/lxc/main.go
@@ -308,3 +308,41 @@ func execIfAliases(config *lxd.Config, origArgs []string) {
 	fmt.Fprintf(os.Stderr, i18n.G("processing aliases failed %s\n"), ret)
 	os.Exit(5)
 }
+
+type ProgressRenderer struct {
+	Format string
+
+	maxLength int
+}
+
+func (p *ProgressRenderer) Done(msg string) {
+	if msg != "" {
+		msg += "\n"
+	}
+
+	if len(msg) > p.maxLength {
+		p.maxLength = len(msg)
+	} else {
+		fmt.Printf("%s\r", strings.Repeat(" ", p.maxLength))
+	}
+
+	fmt.Print(msg)
+}
+
+func (p *ProgressRenderer) Update(status string) {
+	msg := "%s"
+	if p.Format != "" {
+		msg = p.Format
+	}
+
+	msg = fmt.Sprintf(msg, status)
+	msg += "\r"
+
+	if len(msg) > p.maxLength {
+		p.maxLength = len(msg)
+	} else {
+		fmt.Printf("%s\r", strings.Repeat(" ", p.maxLength))
+	}
+
+	fmt.Print(msg)
+}
diff --git a/po/lxd.pot b/po/lxd.pot
index 61280f6..581dce7 100644
--- a/po/lxd.pot
+++ b/po/lxd.pot
@@ -7,7 +7,7 @@
 msgid   ""
 msgstr  "Project-Id-Version: lxd\n"
         "Report-Msgid-Bugs-To: lxc-devel at lists.linuxcontainers.org\n"
-        "POT-Creation-Date: 2016-11-04 21:31-0400\n"
+        "POT-Creation-Date: 2016-11-16 12:13-0500\n"
         "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
         "Last-Translator: FULL NAME <EMAIL at ADDRESS>\n"
         "Language-Team: LANGUAGE <LL at li.org>\n"
@@ -100,7 +100,7 @@ msgid   "### This is a yaml representation of the profile.\n"
         "### Note that the name is shown but cannot be changed"
 msgstr  ""
 
-#: lxc/image.go:617
+#: lxc/image.go:615
 #, c-format
 msgid   "%s (%d more)"
 msgstr  ""
@@ -113,11 +113,11 @@ msgstr  ""
 msgid   "(none)"
 msgstr  ""
 
-#: lxc/image.go:638 lxc/image.go:680
+#: lxc/image.go:636 lxc/image.go:678
 msgid   "ALIAS"
 msgstr  ""
 
-#: lxc/image.go:642
+#: lxc/image.go:640
 msgid   "ARCH"
 msgstr  ""
 
@@ -134,7 +134,7 @@ msgstr  ""
 msgid   "Admin password for %s: "
 msgstr  ""
 
-#: lxc/image.go:365
+#: lxc/image.go:363
 msgid   "Aliases:"
 msgstr  ""
 
@@ -142,12 +142,12 @@ msgstr  ""
 msgid   "An environment variable of the form HOME=/home/foo"
 msgstr  ""
 
-#: lxc/image.go:348 lxc/info.go:93
+#: lxc/image.go:346 lxc/info.go:93
 #, c-format
 msgid   "Architecture: %s"
 msgstr  ""
 
-#: lxc/image.go:369
+#: lxc/image.go:367
 #, c-format
 msgid   "Auto update: %s"
 msgstr  ""
@@ -214,7 +214,7 @@ msgstr  ""
 msgid   "Config key/value to apply to the new container"
 msgstr  ""
 
-#: lxc/config.go:530 lxc/config.go:595 lxc/image.go:734 lxc/network.go:342 lxc/profile.go:218
+#: lxc/config.go:530 lxc/config.go:595 lxc/image.go:732 lxc/network.go:342 lxc/profile.go:218
 #, c-format
 msgid   "Config parsing error: %s"
 msgstr  ""
@@ -227,7 +227,7 @@ msgstr  ""
 msgid   "Container name is mandatory"
 msgstr  ""
 
-#: lxc/copy.go:140 lxc/copy.go:241 lxc/init.go:227
+#: lxc/copy.go:140 lxc/copy.go:241 lxc/init.go:229
 #, c-format
 msgid   "Container name is: %s"
 msgstr  ""
@@ -247,7 +247,7 @@ msgid   "Copy containers within or in between lxd instances.\n"
         "lxc copy [remote:]<source container> [[remote:]<destination container>] [--ephemeral|e] [--profile|-p <profile>...] [--config|-c <key=value>...]"
 msgstr  ""
 
-#: lxc/image.go:280
+#: lxc/image.go:279
 #, c-format
 msgid   "Copying the image: %s"
 msgstr  ""
@@ -276,12 +276,12 @@ msgstr  ""
 msgid   "Create any directories necessary"
 msgstr  ""
 
-#: lxc/image.go:353 lxc/info.go:95
+#: lxc/image.go:351 lxc/info.go:95
 #, c-format
 msgid   "Created: %s"
 msgstr  ""
 
-#: lxc/init.go:180 lxc/launch.go:134
+#: lxc/init.go:180 lxc/launch.go:135
 #, c-format
 msgid   "Creating %s"
 msgstr  ""
@@ -290,7 +290,7 @@ msgstr  ""
 msgid   "Creating the container"
 msgstr  ""
 
-#: lxc/image.go:641 lxc/image.go:682
+#: lxc/image.go:639 lxc/image.go:680
 msgid   "DESCRIPTION"
 msgstr  ""
 
@@ -352,16 +352,16 @@ msgid   "Execute the specified command in a container.\n"
         "Mode defaults to non-interactive, interactive mode is selected if both stdin AND stdout are terminals (stderr is ignored)."
 msgstr  ""
 
-#: lxc/image.go:357
+#: lxc/image.go:355
 #, c-format
 msgid   "Expires: %s"
 msgstr  ""
 
-#: lxc/image.go:359
+#: lxc/image.go:357
 msgid   "Expires: never"
 msgstr  ""
 
-#: lxc/config.go:272 lxc/image.go:639 lxc/image.go:681
+#: lxc/config.go:272 lxc/image.go:637 lxc/image.go:679
 msgid   "FINGERPRINT"
 msgstr  ""
 
@@ -369,7 +369,7 @@ msgstr  ""
 msgid   "Fast mode (same as --columns=nsacPt"
 msgstr  ""
 
-#: lxc/image.go:346
+#: lxc/image.go:344
 #, c-format
 msgid   "Fingerprint: %s"
 msgstr  ""
@@ -424,16 +424,16 @@ msgstr  ""
 msgid   "Ignore the container state (only for start)."
 msgstr  ""
 
-#: lxc/image.go:285
+#: lxc/image.go:282
 msgid   "Image copied successfully!"
 msgstr  ""
 
-#: lxc/image.go:442
+#: lxc/image.go:422 lxc/image.go:434
 #, c-format
 msgid   "Image imported with fingerprint: %s"
 msgstr  ""
 
-#: lxc/image.go:429
+#: lxc/image.go:419
 #, c-format
 msgid   "Importing the image: %s"
 msgstr  ""
@@ -877,7 +877,7 @@ msgstr  ""
 msgid   "Only https URLs are supported for simplestreams"
 msgstr  ""
 
-#: lxc/image.go:434
+#: lxc/image.go:425
 msgid   "Only https:// is supported for remote image import."
 msgstr  ""
 
@@ -885,7 +885,7 @@ msgstr  ""
 msgid   "Options:"
 msgstr  ""
 
-#: lxc/image.go:538
+#: lxc/image.go:536
 #, c-format
 msgid   "Output is in %s"
 msgstr  ""
@@ -910,7 +910,7 @@ msgstr  ""
 msgid   "PROTOCOL"
 msgstr  ""
 
-#: lxc/image.go:640 lxc/remote.go:384
+#: lxc/image.go:638 lxc/remote.go:384
 msgid   "PUBLIC"
 msgstr  ""
 
@@ -949,7 +949,7 @@ msgstr  ""
 msgid   "Press enter to open the editor again"
 msgstr  ""
 
-#: lxc/config.go:531 lxc/config.go:596 lxc/image.go:735
+#: lxc/config.go:531 lxc/config.go:596 lxc/image.go:733
 msgid   "Press enter to start the editor again"
 msgstr  ""
 
@@ -1014,7 +1014,7 @@ msgstr  ""
 msgid   "Profiles: %s"
 msgstr  ""
 
-#: lxc/image.go:361
+#: lxc/image.go:359
 msgid   "Properties:"
 msgstr  ""
 
@@ -1022,7 +1022,7 @@ msgstr  ""
 msgid   "Public image server"
 msgstr  ""
 
-#: lxc/image.go:349
+#: lxc/image.go:347
 #, c-format
 msgid   "Public: %s"
 msgstr  ""
@@ -1059,12 +1059,12 @@ msgstr  ""
 msgid   "Resources:"
 msgstr  ""
 
-#: lxc/init.go:267
+#: lxc/init.go:239
 #, c-format
 msgid   "Retrieving image: %s"
 msgstr  ""
 
-#: lxc/image.go:643
+#: lxc/image.go:641
 msgid   "SIZE"
 msgstr  ""
 
@@ -1129,7 +1129,7 @@ msgstr  ""
 msgid   "Show the container's last 100 log lines?"
 msgstr  ""
 
-#: lxc/image.go:347
+#: lxc/image.go:345
 #, c-format
 msgid   "Size: %.2fMB"
 msgstr  ""
@@ -1138,11 +1138,11 @@ msgstr  ""
 msgid   "Snapshots:"
 msgstr  ""
 
-#: lxc/image.go:371
+#: lxc/image.go:369
 msgid   "Source:"
 msgstr  ""
 
-#: lxc/launch.go:142
+#: lxc/launch.go:144
 #, c-format
 msgid   "Starting %s"
 msgstr  ""
@@ -1184,7 +1184,7 @@ msgstr  ""
 msgid   "The container is currently running. Use --force to have it stopped and restarted."
 msgstr  ""
 
-#: lxc/init.go:313
+#: lxc/init.go:312
 msgid   "The container you are starting doesn’t have any network attached to it."
 msgstr  ""
 
@@ -1192,7 +1192,7 @@ msgstr  ""
 msgid   "The device doesn't exist"
 msgstr  ""
 
-#: lxc/init.go:297
+#: lxc/init.go:296
 #, c-format
 msgid   "The local image '%s' couldn't be found, trying '%s:' instead."
 msgstr  ""
@@ -1217,15 +1217,15 @@ msgstr  ""
 msgid   "Time to wait for the container before killing it."
 msgstr  ""
 
-#: lxc/image.go:350
+#: lxc/image.go:348
 msgid   "Timestamps:"
 msgstr  ""
 
-#: lxc/init.go:315
+#: lxc/init.go:314
 msgid   "To attach a network to a container, use: lxc network attach"
 msgstr  ""
 
-#: lxc/init.go:314
+#: lxc/init.go:313
 msgid   "To create a new network, use: lxc network create"
 msgstr  ""
 
@@ -1233,12 +1233,12 @@ msgstr  ""
 msgid   "To start your first container, try: lxc launch ubuntu:16.04"
 msgstr  ""
 
-#: lxc/image.go:421
+#: lxc/image.go:427
 #, c-format
-msgid   "Transferring image: %d%%"
+msgid   "Transferring image: %s"
 msgstr  ""
 
-#: lxc/action.go:99 lxc/launch.go:155
+#: lxc/action.go:99 lxc/launch.go:157
 #, c-format
 msgid   "Try `lxc info --show-log %s` for more info"
 msgstr  ""
@@ -1251,7 +1251,7 @@ msgstr  ""
 msgid   "Type: persistent"
 msgstr  ""
 
-#: lxc/image.go:644
+#: lxc/image.go:642
 msgid   "UPLOAD DATE"
 msgstr  ""
 
@@ -1267,7 +1267,7 @@ msgstr  ""
 msgid   "Unable to read remote TLS certificate"
 msgstr  ""
 
-#: lxc/image.go:355
+#: lxc/image.go:353
 #, c-format
 msgid   "Uploaded: %s"
 msgstr  ""
@@ -1305,7 +1305,7 @@ msgstr  ""
 msgid   "`lxc config profile` is deprecated, please use `lxc profile`"
 msgstr  ""
 
-#: lxc/launch.go:127
+#: lxc/launch.go:128
 msgid   "bad number of things scanned from image, container or snapshot"
 msgstr  ""
 
@@ -1333,15 +1333,15 @@ msgstr  ""
 msgid   "default"
 msgstr  ""
 
-#: lxc/copy.go:131 lxc/copy.go:136 lxc/copy.go:232 lxc/copy.go:237 lxc/init.go:217 lxc/init.go:222 lxc/launch.go:111 lxc/launch.go:116
+#: lxc/copy.go:131 lxc/copy.go:136 lxc/copy.go:232 lxc/copy.go:237 lxc/init.go:219 lxc/init.go:224 lxc/launch.go:112 lxc/launch.go:117
 msgid   "didn't get any affected image, container or snapshot from server"
 msgstr  ""
 
-#: lxc/image.go:341
+#: lxc/image.go:339
 msgid   "disabled"
 msgstr  ""
 
-#: lxc/image.go:343
+#: lxc/image.go:341
 msgid   "enabled"
 msgstr  ""
 
@@ -1355,11 +1355,11 @@ msgstr  ""
 msgid   "error: unknown command: %s"
 msgstr  ""
 
-#: lxc/launch.go:131
+#: lxc/launch.go:132
 msgid   "got bad version"
 msgstr  ""
 
-#: lxc/image.go:336 lxc/image.go:620
+#: lxc/image.go:334 lxc/image.go:618
 msgid   "no"
 msgstr  ""
 
@@ -1421,7 +1421,7 @@ msgstr  ""
 msgid   "wrong number of subcommand arguments"
 msgstr  ""
 
-#: lxc/delete.go:45 lxc/image.go:338 lxc/image.go:624
+#: lxc/delete.go:45 lxc/image.go:336 lxc/image.go:622
 msgid   "yes"
 msgstr  ""
 

From ee10788dc3c79065aa1d276f7767c72b8e410eb2 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Tue, 15 Nov 2016 20:39:41 -0500
Subject: [PATCH 2/2] Track speed during network transfers
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 client.go               |  2 +-
 lxc/image.go            |  4 ++--
 lxd/daemon_images.go    |  4 ++--
 shared/simplestreams.go |  4 ++--
 shared/util.go          | 22 +++++++++++++++++++---
 5 files changed, 26 insertions(+), 10 deletions(-)

diff --git a/client.go b/client.go
index 49fa8e3..a32709b 100644
--- a/client.go
+++ b/client.go
@@ -988,7 +988,7 @@ func (c *Client) PostImageURL(imageFile string, properties []string, public bool
 	return fingerprint, nil
 }
 
-func (c *Client) PostImage(imageFile string, rootfsFile string, properties []string, public bool, aliases []string, progressHandler func(percent int)) (string, error) {
+func (c *Client) PostImage(imageFile string, rootfsFile string, properties []string, public bool, aliases []string, progressHandler func(int, int)) (string, error) {
 	if c.Remote.Public {
 		return "", fmt.Errorf("This function isn't supported by public remotes.")
 	}
diff --git a/lxc/image.go b/lxc/image.go
index 54a4be0..2241d7f 100644
--- a/lxc/image.go
+++ b/lxc/image.go
@@ -425,8 +425,8 @@ func (c *imageCmd) run(config *lxd.Config, args []string) error {
 			return fmt.Errorf(i18n.G("Only https:// is supported for remote image import."))
 		} else {
 			progress := ProgressRenderer{Format: i18n.G("Transferring image: %s")}
-			handler := func(percent int) {
-				progress.Update(fmt.Sprintf("%d%%", percent))
+			handler := func(percent int, speed int) {
+				progress.Update(fmt.Sprintf("%d%% (%s/s)", percent, shared.GetByteSizeString(int64(speed))))
 			}
 
 			fingerprint, err = d.PostImage(imageFile, rootfsFile, properties, c.publicImage, c.addAliases, handler)
diff --git a/lxd/daemon_images.go b/lxd/daemon_images.go
index 876bd7a..5007d13 100644
--- a/lxd/daemon_images.go
+++ b/lxd/daemon_images.go
@@ -247,7 +247,7 @@ func (d *Daemon) ImageDownload(op *operation, server string, protocol string, ce
 		d.Storage.ImageDelete(fp)
 	}
 
-	progress := func(progressInt int) {
+	progress := func(progressInt int, speedInt int) {
 		if op == nil {
 			return
 		}
@@ -257,7 +257,7 @@ func (d *Daemon) ImageDownload(op *operation, server string, protocol string, ce
 			meta = make(map[string]interface{})
 		}
 
-		progress := fmt.Sprintf("%d%%", progressInt)
+		progress := fmt.Sprintf("%d%% (%s/s)", progressInt, shared.GetByteSizeString(int64(speedInt)))
 
 		if meta["download_progress"] != progress {
 			meta["download_progress"] = progress
diff --git a/shared/simplestreams.go b/shared/simplestreams.go
index e0e358c..8fced30 100644
--- a/shared/simplestreams.go
+++ b/shared/simplestreams.go
@@ -494,7 +494,7 @@ func (s *SimpleStreams) getPaths(fingerprint string) ([][]string, error) {
 	return nil, fmt.Errorf("Couldn't find the requested image")
 }
 
-func (s *SimpleStreams) downloadFile(path string, hash string, target string, progress func(int)) error {
+func (s *SimpleStreams) downloadFile(path string, hash string, target string, progress func(int, int)) error {
 	download := func(url string, hash string, target string) error {
 		out, err := os.Create(target)
 		if err != nil {
@@ -623,7 +623,7 @@ func (s *SimpleStreams) ExportImage(image string, target string) (string, error)
 	return target, nil
 }
 
-func (s *SimpleStreams) Download(image string, file string, target string, progress func(int)) error {
+func (s *SimpleStreams) Download(image string, file string, target string, progress func(int, int)) error {
 	paths, err := s.getPaths(image)
 	if err != nil {
 		return err
diff --git a/shared/util.go b/shared/util.go
index 2cf0371..2b5b70e 100644
--- a/shared/util.go
+++ b/shared/util.go
@@ -19,6 +19,7 @@ import (
 	"regexp"
 	"strconv"
 	"strings"
+	"time"
 )
 
 const SnapshotDelimiter = "/"
@@ -742,8 +743,10 @@ type TransferProgress struct {
 	percentage float64
 	total      int64
 
+	start *time.Time
+
 	Length  int64
-	Handler func(int)
+	Handler func(int, int)
 }
 
 func (pt *TransferProgress) Read(p []byte) (int, error) {
@@ -753,19 +756,32 @@ func (pt *TransferProgress) Read(p []byte) (int, error) {
 		return n, err
 	}
 
+	if pt.start == nil {
+		cur := time.Now()
+		pt.start = &cur
+	}
+
 	if n > 0 {
 		pt.total += int64(n)
 		percentage := float64(pt.total) / float64(pt.Length) * float64(100)
 
 		if percentage-pt.percentage > 0.9 {
+			// Determine percentage
 			pt.percentage = percentage
-
 			progressInt := 1 - (int(percentage) % 1) + int(percentage)
 			if progressInt > 100 {
 				progressInt = 100
 			}
 
-			pt.Handler(progressInt)
+			// Determine speed
+			speedInt := int(0)
+			duration := time.Since(*pt.start).Seconds()
+			if duration > 0 {
+				speed := float64(pt.total) / duration
+				speedInt = int(speed)
+			}
+
+			pt.Handler(progressInt, speedInt)
 		}
 	}
 


More information about the lxc-devel mailing list