Skip to content

Commit d10ff60

Browse files
committed
Added class SpeedCalculator.cs.
Updated ProgressStreamContent.cs and StreamExtensions.cs to use SpeedCalculator to compute the bytesPerSecond value to be passed into the CopyProgress constructor. Deleted the singleTime Stopwatch from each loop.
1 parent f927ee6 commit d10ff60

File tree

3 files changed

+96
-10
lines changed

3 files changed

+96
-10
lines changed

src/Client/Client/ProgressStreamContent.cs

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
using System.Threading.Tasks;
66

77
using Bytewizer.Backblaze.Models;
8+
using Bytewizer.Backblaze.Utility;
89

910
namespace Bytewizer.Backblaze.Client
1011
{
@@ -67,15 +68,17 @@ protected override Task SerializeToStreamAsync(Stream stream, TransportContext c
6768
return Task.Run(() =>
6869
{
6970
var totalTime = new System.Diagnostics.Stopwatch();
70-
var singleTime = new System.Diagnostics.Stopwatch();
7171
totalTime.Start();
72-
singleTime.Start();
7372

7473
var buffer = new byte[_bufferSize];
7574
long streamLength = _content.CanSeek ? _content.Length : 0;
7675
long size = _expectedContentLength > 0 ? _expectedContentLength : streamLength;
7776
long uploaded = 0;
7877

78+
var speedCalculator = new SpeedCalculator();
79+
80+
speedCalculator.AddSample(0);
81+
7982
while (true)
8083
{
8184
var length = _content.Read(buffer, 0, buffer.Length);
@@ -84,10 +87,9 @@ protected override Task SerializeToStreamAsync(Stream stream, TransportContext c
8487

8588
stream.Write(buffer, 0, length);
8689

87-
long singleElapsed = Math.Max(1, singleTime.ElapsedTicks);
88-
singleTime.Restart();
90+
speedCalculator.AddSample(uploaded);
8991

90-
_progressReport?.Report(new CopyProgress(totalTime.Elapsed, length * TimeSpan.TicksPerSecond / singleElapsed, uploaded, size));
92+
_progressReport?.Report(new CopyProgress(totalTime.Elapsed, speedCalculator.CalculateBytesPerSecond(), uploaded, size));
9193
}
9294
});
9395
}
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
using System;
2+
using System.Collections.Generic;
3+
4+
namespace Bytewizer.Backblaze.Utility
5+
{
6+
/// <summary>
7+
/// Calculates transfer speed as a rolling average. Every time the position changes,
8+
/// the consumer notifies us and a timestamped sample is logged.
9+
/// </summary>
10+
public class SpeedCalculator
11+
{
12+
List<Sample> _samples = new List<Sample>();
13+
14+
struct Sample
15+
{
16+
public long Position;
17+
public DateTime DateTimeUTC;
18+
}
19+
20+
/// <summary>
21+
/// The length in seconds of the window across which speed is averaged.
22+
/// </summary>
23+
public const int WindowSeconds = 10;
24+
25+
/// <summary>
26+
/// Adds a position sample to the set. It is automatically timestamped. Samples
27+
/// should be monotonically increasing. If, for whatever reason, they are not,
28+
/// previously-added samples that are later in the file are discarded so that
29+
/// the set remains strictly increasing.
30+
/// </summary>
31+
/// <param name="position">The updated position of the operation.</param>
32+
public void AddSample(long position)
33+
{
34+
var sample = new Sample();
35+
36+
sample.Position = position;
37+
sample.DateTimeUTC = DateTime.UtcNow;
38+
39+
// If we have walked backward for whatever reason, discard any samples past this
40+
// point so that we maintain the invariant of the sample set increasing position
41+
// monotonically.
42+
while ((_samples.Count > 0) && (_samples[_samples.Count - 1].Position > position))
43+
_samples.RemoveAt(_samples.Count - 1);
44+
45+
_samples.Add(sample);
46+
}
47+
48+
/// <summary>
49+
/// Calculates the current speed based on samples previously added by calls to
50+
/// <see cref="AddSample" />. The value of this function will change over time,
51+
/// even with no changes to the state of the <see cref="SpeedCalculator" />
52+
/// instance, because the value is relative to the current date/time, and the
53+
/// samples with which the calculation is being made are timestamped.
54+
/// </summary>
55+
/// <returns>The average number of bytes per second being processed.</returns>
56+
public long CalculateBytesPerSecond()
57+
{
58+
var cutoff = DateTime.UtcNow.AddSeconds(-WindowSeconds);
59+
60+
// Discard any samples that are outside of the averaging window. We will never
61+
// need them again.
62+
while ((_samples.Count > 0) && (_samples[0].DateTimeUTC < cutoff))
63+
_samples.RemoveAt(0);
64+
65+
if (_samples.Count < 2)
66+
return 0;
67+
68+
var firstSample = _samples[0];
69+
var lastSample = _samples[_samples.Count - 1];
70+
71+
long bytes = lastSample.Position - firstSample.Position;
72+
double seconds = (lastSample.DateTimeUTC - firstSample.DateTimeUTC).TotalSeconds;
73+
74+
// If we don't have a meaningful span of time, clamp it. The number wouldn't
75+
// be terribly meaningful anyway.
76+
if (seconds < 0.01)
77+
seconds = 0.01;
78+
79+
return (long)Math.Round(bytes / seconds);
80+
}
81+
}
82+
}

src/Client/Extensions/StreamExtensions.cs

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
using System.Threading.Tasks;
66

77
using Bytewizer.Backblaze.Models;
8+
using Bytewizer.Backblaze.Utility;
89

910
namespace Bytewizer.Backblaze.Extensions
1011
{
@@ -37,17 +38,18 @@ public static async Task CopyToAsync(this Stream source, Stream destination, int
3738
int bytesRead;
3839

3940
var totalTime = new System.Diagnostics.Stopwatch();
40-
var singleTime = new System.Diagnostics.Stopwatch();
4141
totalTime.Start();
42-
singleTime.Start();
42+
43+
var speedCalculator = new SpeedCalculator();
44+
45+
speedCalculator.AddSample(0);
4346

4447
while ((bytesRead = await source.ReadAsync(buffer, 0, buffer.Length, cancel).ConfigureAwait(false)) != 0)
4548
{
4649
await destination.WriteAsync(buffer, 0, bytesRead, cancel).ConfigureAwait(false);
4750
totalBytesRead += bytesRead;
48-
long singleTicks = Math.Max(1, singleTime.ElapsedTicks);
49-
progressReport?.Report(new CopyProgress(totalTime.Elapsed, bytesRead * TimeSpan.TicksPerSecond / singleTicks, totalBytesRead, expectedTotalBytes));
50-
singleTime.Restart();
51+
speedCalculator.AddSample(totalBytesRead);
52+
progressReport?.Report(new CopyProgress(totalTime.Elapsed, speedCalculator.CalculateBytesPerSecond(), totalBytesRead, expectedTotalBytes));
5153

5254
if (cancel.IsCancellationRequested) { break; }
5355
}

0 commit comments

Comments
 (0)