Skip to content

Commit

Permalink
[core] Allow SpeedMonitor to handle very fast speeds
Browse files Browse the repository at this point in the history
Over a decade ago speeds in excess of 2GB/sec would've been
fairly unthinkable, but nowadays that's not too hard to
imagine.

As the SpeedMonitor is an averager, and since we're using a
fairly naive implementation which sums everything up and then
divides by the averaging period, we can easily overflow 32bits
if the rate gets above 150MB/sec as the default averaging period
is 12.

Rather than trying to squeeze into 32bits, just make all the
internals 64bit as it may become a requirement in the not too
distant future.
  • Loading branch information
alanmcgovern committed Sep 6, 2019
1 parent 8ab63c3 commit 44654cf
Show file tree
Hide file tree
Showing 9 changed files with 84 additions and 63 deletions.
32 changes: 27 additions & 5 deletions src/MonoTorrent.Tests/Common/SpeedMonitorTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,25 @@ namespace MonoTorrent.Common
public class SpeedMonitorTest
{
[Test]
public void ZeroAveragingPeriod ()
public void VeryFastRate ()
{
var monitor = new SpeedMonitor (0);
monitor.AddDelta (1000);
monitor.Tick (1000);
// We're reporting 500MB every 750ms period
var speed = 500 * 1024 * 1024;

Assert.AreEqual (1000, monitor.Rate, "#1");
// The actual speed averaged over a 1 second period would be this
var estimatedActualSpeed = speed * 4 / 3;

var monitor = new SpeedMonitor ();
for (int i = 0; i < 37; i ++) {
monitor.AddDelta (speed);
monitor.Tick (750);

Assert.Greater (monitor.Rate, 0, "#1." + i);
Assert.Less (monitor.Rate, estimatedActualSpeed + (1 * 1024 * 1024), "#2." + i);
}

// Should be somewhere between 499 and 501 MB/sec
Assert.IsTrue((monitor.Rate - estimatedActualSpeed) < (1 * 1024 * 1024), "#3");
}

[Test]
Expand Down Expand Up @@ -65,5 +77,15 @@ public void Tick_AveragingTwo_TickThree ()

Assert.AreEqual (0, monitor.Rate, "#1");
}

[Test]
public void ZeroAveragingPeriod ()
{
var monitor = new SpeedMonitor (0);
monitor.AddDelta (1000);
monitor.Tick (1000);

Assert.AreEqual (1000, monitor.Rate, "#1");
}
}
}
6 changes: 3 additions & 3 deletions src/MonoTorrent/MonoTorrent.Client.Connections/PeerId.cs
Original file line number Diff line number Diff line change
Expand Up @@ -206,11 +206,11 @@ public override string ToString()

private const int MARKET_RATE = 7000; // taken from reference BitTyrant implementation
ValueStopwatch LastRateReductionTime; // last time we reduced rate of this peer
private int lastMeasuredDownloadRate; // last download rate measured
long lastMeasuredDownloadRate; // last download rate measured
ValueStopwatch TyrantStartTime;

// stats
private int maxObservedDownloadSpeed;
long maxObservedDownloadSpeed;

private void InitializeTyrant()
{
Expand Down Expand Up @@ -279,7 +279,7 @@ internal float Ratio
/// - divide this upload rate by the standard implementation's active set size for that rate
/// </summary>
/// <returns></returns>
internal int GetDownloadRate()
internal long GetDownloadRate()
{
if (this.lastMeasuredDownloadRate > 0)
{
Expand Down
8 changes: 4 additions & 4 deletions src/MonoTorrent/MonoTorrent.Client/ClientEngine.cs
Original file line number Diff line number Diff line change
Expand Up @@ -105,22 +105,22 @@ public class ClientEngine : IDisposable

public IList<TorrentManager> Torrents { get; }

public int TotalDownloadSpeed
public long TotalDownloadSpeed
{
get
{
int total = 0;
long total = 0;
for (int i = 0; i < torrents.Count; i++)
total += torrents[i].Monitor.DownloadSpeed;
return total;
}
}

public int TotalUploadSpeed
public long TotalUploadSpeed
{
get
{
int total = 0;
long total = 0;
for (int i = 0; i < torrents.Count; i++)
total += torrents[i].Monitor.UploadSpeed;
return total;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ public long DataBytesUploaded
get { return DataUp.Total; }
}

public int DownloadSpeed
public long DownloadSpeed
{
get { return DataDown.Rate + ProtocolDown.Rate; }
}
Expand All @@ -71,7 +71,7 @@ public long ProtocolBytesUploaded
get { return ProtocolUp.Total; }
}

public int UploadSpeed
public long UploadSpeed
{
get { return DataUp.Rate + ProtocolUp.Rate; }
}
Expand Down
4 changes: 2 additions & 2 deletions src/MonoTorrent/MonoTorrent.Client/Managers/DiskManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ public BufferedIO (ITorrentData manager, long offset, byte [] buffer, int count,
/// <summary>
/// The amount of data, in bytes, being read per second.
/// </summary>
public int ReadRate => ReadMonitor.Rate;
public long ReadRate => ReadMonitor.Rate;

/// <summary>
/// The settings object passed to the ClientEngine, used to get the current read/write limits.
Expand All @@ -146,7 +146,7 @@ public BufferedIO (ITorrentData manager, long offset, byte [] buffer, int count,
/// <summary>
/// The amount of data, in bytes, being written per second.
/// </summary>
public int WriteRate => WriteMonitor.Rate;
public long WriteRate => WriteMonitor.Rate;

/// <summary>
/// The total number of bytes which have been read.
Expand Down
4 changes: 2 additions & 2 deletions src/MonoTorrent/MonoTorrent.Tracker/RequestMonitor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,12 +37,12 @@ public class RequestMonitor
/// <summary>
/// This is the number of announce requests handled per second.
/// </summary>
public int AnnounceRate => Announces.Rate;
public int AnnounceRate => (int) Announces.Rate;

/// <summary>
/// This is the number of scrape requests handled per second.
/// </summary>
public int ScrapeRate => Scrapes.Rate;
public int ScrapeRate => (int) Scrapes.Rate;

/// <summary>
/// The total number of announces handled.
Expand Down
85 changes: 42 additions & 43 deletions src/MonoTorrent/MonoTorrent/SpeedMonitor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@


using System;
using System.Diagnostics;
using System.Threading;

namespace MonoTorrent
{
Expand All @@ -37,13 +37,15 @@ public class SpeedMonitor
internal const int DefaultAveragePeriod = 12;

ValueStopwatch lastUpdated;
readonly int[] speeds;
long rate;
readonly long[] speeds;
int speedsIndex;
long tempRecvCount;
long total;

public int Rate { get; private set; }
public long Rate => Interlocked.Read (ref rate);

public long Total { get; private set; }
public long Total => Interlocked.Read (ref total);

public SpeedMonitor()
: this(DefaultAveragePeriod)
Expand All @@ -54,39 +56,50 @@ public SpeedMonitor()
public SpeedMonitor(int averagingPeriod)
{
if (averagingPeriod < 0)
throw new ArgumentOutOfRangeException ("averagingPeriod");
throw new ArgumentOutOfRangeException (nameof (averagingPeriod));

this.lastUpdated = ValueStopwatch.StartNew ();
this.speeds = new int [Math.Max (1, averagingPeriod)];
this.speedsIndex = -speeds.Length;
lastUpdated = ValueStopwatch.StartNew ();
speeds = new long [Math.Max (1, averagingPeriod)];
speedsIndex = -speeds.Length;
}


/// <summary>
/// This method is threadsafe and can be called at any point.
/// </summary>
/// <param name="speed"></param>
public void AddDelta(int speed)
{
lock (speeds)
{
this.Total += speed;
this.tempRecvCount += speed;
}
Interlocked.Add (ref total, speed);
Interlocked.Add (ref tempRecvCount, speed);
}

public void Reset()
{
lock (speeds)
{
Total = 0;
Rate = 0;
tempRecvCount = 0;
lastUpdated.Restart ();
speedsIndex = -speeds.Length;
}
Interlocked.Exchange (ref total, 0);
Interlocked.Exchange (ref tempRecvCount, 0);

rate = 0;
lastUpdated.Restart ();
speedsIndex = -speeds.Length;
}

public void Tick()
{
int difference = (int) lastUpdated.Elapsed.TotalMilliseconds;
if (difference > 800)
Tick (difference);
}

internal void Tick (int difference)
{
lastUpdated.Restart ();
TimePeriodPassed(difference);
}

private void TimePeriodPassed(int difference)
void TimePeriodPassed(int difference)
{
int currSpeed = (int)(tempRecvCount * 1000 / difference);
tempRecvCount = 0;
long currSpeed = Interlocked.Exchange (ref tempRecvCount, 0);
currSpeed = (currSpeed * 1000) / difference;

int speedsCount;
if( speedsIndex < 0 )
Expand All @@ -108,27 +121,13 @@ private void TimePeriodPassed(int difference)

speedsIndex = (speedsIndex + 1) % speeds.Length;
}
int total = speeds[0];

var sumTotal = speeds[0];
for( int i = 1; i < speedsCount; i++ )
total += speeds[i];
sumTotal += speeds[i];

Rate = total / speedsCount;
Interlocked.Exchange (ref rate, sumTotal / speedsCount);
}

public void Tick()
{
int difference = (int) lastUpdated.Elapsed.TotalMilliseconds;
if (difference > 800)
Tick (difference);
}

internal void Tick (int difference)
{
lock (speeds) {
lastUpdated.Restart ();
TimePeriodPassed(difference);
}
}
}
}
2 changes: 1 addition & 1 deletion src/MonoTorrent/MonoTorrent/ValueStopwatch.cs
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ public TimeSpan Elapsed {
}
}

public long ElapsedMilliseconds => Elapsed.Milliseconds;
public long ElapsedMilliseconds => (long)Elapsed.TotalMilliseconds;

public long ElapsedTicks => Elapsed.Ticks;

Expand Down
2 changes: 1 addition & 1 deletion src/TrackerApp/StressTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ public class StressTest

public int RequestRate
{
get { return requests.Rate; }
get { return (int) requests.Rate; }
}

public long TotalTrackerRequests
Expand Down

0 comments on commit 44654cf

Please sign in to comment.