Skip to content

Commit 410397d

Browse files
committed
2 parents 34d9537 + 13f1730 commit 410397d

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)