Tuesday, November 28, 2006 4:39 AM bart

[UPDATE] PowerShell - A cmdlet that reports progress - A simple file downloader cmdlet

Only a couple of days ago I posted about creating a file downloader cmdlet in Windows PowerShell which contained the following little sentence:

One could make this method more complex in order to provide a seconds remaining estimate based on the download speed observed.

One of my readers (dotnetjunkie) was so kind to leave a piece of feedback:

I think you should add transfer speed and estimated remaining time, that would make it even more useful and cooler ! ;)

Well, I couldn't agree more. So I revisited my piece of code I wrote some weeks ago (12th of November to be precise) and added download speed tracking and a seconds remaining indicator.

There are undoubtly different ways to implement such an estimate. My approach is to measure the number of bytes transferred during intervals of approximately 5 seconds (kind of "instant download speed") and to derive the estimated time remaining from this. I'll discuss the code changes in a few steps.

Step 1 - Add a few private members

We need 4 additional members to produce statistics:

  • The first one is a Stopwatch to measure elapsed time. We'll poll this counter every time the DownloadProgressChanged event is fired. In case it's above 5 seconds, we update the statistics and restart the counter.
  • Secondly, we'll cache an indicator that keeps the current transfer speed as a string of the format n [bytes|KB|MB|GB|...]/sec. This value will be updated every 5 seconds.
  • Next, the seconds remaining are kept. It's updated every 5 seconds and during these intervals it just counts down every one second.
  • Finally, a transferred bytes indicator is used for calculation of the bytes transferred the last 5 seconds.

Here's the piece of code:

/// <summary> /// Stopwatch used to measure download speed. /// </summary> private Stopwatch sw = new Stopwatch(); /// <summary> /// Bytes per second indicator (bytes/sec, KB/sec, MB/sec, ...). /// </summary> private string bps = null; /// <summary> /// Seconds remaining indicator. /// </summary> private int secondsRemaining = -1; /// <summary> /// Number of bytes already transferred. /// </summary> private long transferred = 0;

Step 2 - Let the count begin

In the ProcessRecord method we start our Stopwatch; just that:

// // Check validity for download. Will throw an exception in case of transport protocol errors. // using (clnt.OpenRead(_url)) { } // // Start download speed stopwatch. // sw.Start(); // // Download the file asynchronously. Reporting will happen through events on background threads. // clnt.DownloadFileAsync(_url, _file);

Step 3 - Calculate stats and report progress

Time for the real stuff. On to the DownloadProgressChanged event handler. When we observe that the Stopwatch has an elapsed time of 5 or more seconds, we'll stop it, update stats and restart it. The code is shown below:

1 /// <summary> 2 /// Reports download progress. 3 /// </summary> 4 private void webClient_DownloadProgressChanged(object sender, DownloadProgressChangedEventArgs e) 5 { 6 // 7 // Update statistics every 5 seconds (approx). 8 // 9 if (sw.Elapsed >= TimeSpan.FromSeconds(5)) 10 { 11 sw.Stop(); 12 13 // 14 // Calculcate transfer speed. 15 // 16 long bytes = e.BytesReceived - transferred; 17 double bps = bytes * 1000.0 / sw.Elapsed.TotalMilliseconds; 18 this.bps = BpsToString(bps); 19 20 // 21 // Estimated seconds remaining based on the current transfer speed. 22 // 23 secondsRemaining = (int)((e.TotalBytesToReceive - e.BytesReceived) / bps); 24 25 // 26 // Restart stopwatch for next 5 seconds. 27 // 28 transferred = e.BytesReceived; 29 sw.Reset(); 30 sw.Start(); 31 } 32 33 // 34 // Construct a ProgressRecord with download state information but no completion time estimate (SecondsRemaining < 0). 35 // 36 ProgressRecord pr = new ProgressRecord(0, String.Format("Downloading {0}", _url.ToString(), _file), String.Format("{0} of {1} bytes transferred{2}.", e.BytesReceived, e.TotalBytesToReceive, this.bps != null ? String.Format(" (@ {0})", this.bps) : "")); 37 pr.CurrentOperation = String.Format("Destination file: {0}", _file); 38 pr.SecondsRemaining = secondsRemaining - (int)sw.Elapsed.Seconds; 39 pr.PercentComplete = e.ProgressPercentage; 40 41 // 42 // Report availability of a ProgressRecord item. Will cause the while-loop's body in ProgressRecord to execute. 43 // 44 lock (pr_sync) 45 { 46 this.pr = pr; 47 prog.Set(); 48 } 49 }

So, what's going on here. Basically we want to provide a seconds remaining estimate on line 38 and a download speed estimate on line 36. This should be pretty self-explanatory. The real work happens in lines 11 to 30 where the number of bytes transferred in the last 5 seconds are obtained and divided by the expired milliseconds during the last 5 seconds (which should be around 5000 obviously). The rest is maths, except for the BpsToString call as shown below.

Step 4 - A download speed indicator

BpsToString is the method to convert the bytes per second rate to a friendly string representation:

/// <summary> /// Constructs a download speed indicator string. /// </summary> /// <param name="bps">Bytes per second transfer rate.</param> /// <returns>String represenation of the transfer rate in bytes/sec, KB/sec, MB/sec, etc.</returns> private string BpsToString(double bps) { string[] m = new string[] { "bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB" }; //dreaming of YB/sec int i = 0; while (bps >= 0.9 * 1024) { bps /= 1024; i++; } return String.Format("{0:0.00} {1}/sec", bps, m[i]); }

I think the code fragment above is pretty optimistic for what transfer speeds is concerned, but with the expected life time of PowerShell in mind this should be no luxury :-).

Step 5 - The result & code download

This is the result (needless to say the figures are indicative only, it are estimates after all):

And here's the code download link.

Del.icio.us | Digg It | Technorati | Blinklist | Furl | reddit | DotNetKicks

Filed under:

Comments

# Pierres Service &raquo; Blog Archive &raquo; [UPDATE] PowerShell - A cmdlet that reports progress - A simple &#8230;

# Pierres Service &raquo; Blog Archive &raquo; [UPDATE] PowerShell - A cmdlet that reports progress - A simple &#8230;

# http://bartdesmet.net/blogs/bart/archive/2006/11/28/_5B00_UPDATE_5D00_-PowerShell-_2D00_-A-cmdlet-that-reports-progress-_2D00_-A-simple-file-downloader-cmdlet.aspx

Pingback from http://bartdesmet.net/blogs/bart/archive/2006/11/28/_5B00_UPDATE_5D00_-PowerShell-_2D00_-A-cmdlet-that-reports-progress-_2D00_-A-simple-file-downloader-cmdlet.aspx

# Windows PowerShell 2.0 Feature Focus - Script cmdlets

Saturday, March 22, 2008 9:09 AM by B# .NET Blog

Two weeks ago I did a little tour through Europe spreading the word on a couple of our technologies including