diff --git a/Duplicati/Library/UsageReporter/OSInfoHelper.cs b/Duplicati/Library/UsageReporter/OSInfoHelper.cs
index 3eee098d0..ae112b63a 100644
--- a/Duplicati/Library/UsageReporter/OSInfoHelper.cs
+++ b/Duplicati/Library/UsageReporter/OSInfoHelper.cs
@@ -19,17 +19,28 @@ using System.Linq;
namespace Duplicati.Library.UsageReporter
{
+ ///
+ /// Helper class for extracting operating system info
+ ///
internal static class OSInfoHelper
{
+ ///
+ /// Runs a commandline program and returns stdout as a string.
+ ///
+ /// The stdout value.
+ /// The command to run.
+ /// The commandline arguments.
private static string RunProgramAndReadOutput(string cmd, string args)
{
+ System.Diagnostics.Process pi = null;
try
{
var psi = new System.Diagnostics.ProcessStartInfo(cmd, args);
psi.RedirectStandardOutput = true;
+ psi.RedirectStandardError = true; // Suppress error messages
psi.UseShellExecute = false;
- var pi = System.Diagnostics.Process.Start(psi);
+ pi = System.Diagnostics.Process.Start(psi);
pi.WaitForExit(5000);
if (pi.HasExited)
return pi.StandardOutput.ReadToEnd().Trim();
@@ -37,6 +48,12 @@ namespace Duplicati.Library.UsageReporter
catch
{
}
+ finally
+ {
+ if (pi != null && !pi.HasExited)
+ try { pi.Kill(); }
+ catch { }
+ }
return null;
}
diff --git a/Duplicati/Library/Utility/ThrottledStream.cs b/Duplicati/Library/Utility/ThrottledStream.cs
index c0a7afe14..3a1c27638 100644
--- a/Duplicati/Library/Utility/ThrottledStream.cs
+++ b/Duplicati/Library/Utility/ThrottledStream.cs
@@ -33,16 +33,6 @@ namespace Duplicati.Library.Utility
///
public class ThrottledStream : OverrideableStream
{
- ///
- /// The delegate type for the callback
- ///
- public delegate void ThrottledStreamCallback(ThrottledStream sender);
-
- ///
- /// An event that is raised while the stream is active
- ///
- public event ThrottledStreamCallback Callback;
-
///
/// The max number of bytes pr. second to write
///
@@ -52,41 +42,37 @@ namespace Duplicati.Library.Utility
///
private long m_readspeed;
- ///
- /// This is a list of the most recent reads. The key is the tick at the time, and the value is the number of bytes.
- ///
- List> m_dataread;
- ///
- /// This is a list of the most recent writes. The key is the tick at the time, and the value is the number of bytes.
- ///
- List> m_datawritten;
- ///
- /// This is the sum of all bytes in the m_dataread table, summed for fast access.
- ///
- private long m_bytesread;
- ///
- /// This is the sum of all bytes in the m_datawritten table, summed for fast access.
- ///
- private long m_byteswritten;
+ ///
+ /// The time the last read was sampled
+ ///
+ private DateTime m_last_read_sample;
+ ///
+ /// The bytes-read counter for this period
+ ///
+ private long m_current_read_counter;
+ ///
+ /// The current measured read speed
+ ///
+ private double m_current_read_speed;
- ///
- /// The number of bytes transfered without raising an event
- ///
- private long m_progresscounter = 0;
+ ///
+ /// The time the last read was sampled
+ ///
+ private DateTime m_last_write_sample;
+ ///
+ /// The bytes-written counter for this period
+ ///
+ private long m_current_write_counter;
+ ///
+ /// The current measured read speed
+ ///
+ private double m_current_write_speed;
- ///
- /// The number of reads or writes to keep track of
+ ///
+ /// The number of ticks to have passed before a sample is taken
///
- private const long STATISTICS_SIZE = 500;
- ///
- /// The number of ticks to have passed before the throttle begins
- ///
- private const long MIN_DURATION = TimeSpan.TicksPerSecond / 4;
- ///
- /// The number of sub chunks to perform when throttling
- ///
- private const int DELAY_CHUNK_SIZE = 1024;
-
+ private const long SAMPLE_PERIOD = TimeSpan.TicksPerSecond / 4;
+
///
/// The number of bytes to process without raising an event
///
@@ -104,44 +90,59 @@ namespace Duplicati.Library.Utility
m_readspeed = readspeed;
m_writespeed = writespeed;
- if (m_basestream.CanRead && m_readspeed > 0)
- m_dataread = new List>();
- if (m_basestream.CanWrite && m_writespeed > 0)
- m_datawritten = new List>();
+ m_last_read_sample = m_last_write_sample = new DateTime(0);
}
+ ///
+ /// Read the specified buffer, offset and count.
+ ///
+ /// The buffer to read from.
+ /// The offset into the buffer.
+ /// The number of bytes to read.
public override int Read(byte[] buffer, int offset, int count)
{
- int bytesRead = DelayIfRequired(true, buffer, ref offset, ref count);
- if (count != 0)
- bytesRead += m_basestream.Read(buffer, offset, count);
+ var remaining = count;
- m_progresscounter += bytesRead;
+ while (remaining > 0)
+ {
+ // To avoid excessive waiting, the delay will wait at most 2 seconds,
+ // so we split the blocks to limit the number of seconds we can wait
+ var chunksize = (int)Math.Min(remaining, m_readspeed <= 0 ? remaining : m_readspeed * 2);
+ DelayIfRequired(ref m_readspeed, chunksize, ref m_last_read_sample, ref m_current_read_counter, ref m_current_read_speed);
- if (m_progresscounter > REPORT_DISTANCE_SIZE)
- {
- m_progresscounter %= REPORT_DISTANCE_SIZE;
- if (Callback != null)
- Callback(this);
- }
+ var actual = m_basestream.Read(buffer, offset, chunksize);
- return bytesRead;
+ if (actual <= 0)
+ break;
+
+ m_current_read_counter += actual;
+
+ remaining -= actual;
+ }
+
+ return count - remaining;
}
+ ///
+ /// Write the specified buffer, offset and count.
+ ///
+ /// The buffer to write to.
+ /// The offset into the buffer.
+ /// The number of bytes to write.
public override void Write(byte[] buffer, int offset, int count)
{
- m_progresscounter += count;
+ while (count > 0)
+ {
+ // To avoid excessive waiting, the delay will wait at most 2 seconds,
+ // so we split the blocks to limit the number of seconds we can wait
+ var chunksize = (int)Math.Min(count, m_writespeed <= 0 ? count : m_writespeed * 2);
+ DelayIfRequired(ref m_writespeed, chunksize, ref m_last_write_sample, ref m_current_write_counter, ref m_current_write_speed);
+ m_basestream.Write(buffer, offset, chunksize);
- DelayIfRequired(false, buffer, ref offset, ref count);
- if (count > 0)
- m_basestream.Write(buffer, offset, count);
+ m_current_write_counter += chunksize;
- if (m_progresscounter > REPORT_DISTANCE_SIZE)
- {
- m_progresscounter %= REPORT_DISTANCE_SIZE;
- if (Callback != null)
- Callback(this);
- }
+ count -= chunksize;
+ }
}
///
@@ -164,100 +165,66 @@ namespace Duplicati.Library.Utility
set { m_writespeed = value; }
}
- ///
- /// Calculates the speed, and inserts appropriate delays
- ///
- /// True if the operation is read, false otherwise
- /// The data buffer
- /// The offset into the buffer
- /// The number of bytes to read or write
- /// The number of bytes processed while delaying
- private int DelayIfRequired(bool read, byte[] buffer, ref int offset, ref int count)
- {
- if (count <= 0)
- return 0;
+ ///
+ /// Gets the actual measured read speed.
+ ///
+ public double MeasuredReadSpeed { get { return m_current_read_speed; } }
- List> table = read ? m_dataread : m_datawritten;
- int bytesprocessed = 0;
+ ///
+ /// Gets the actual measured write speed.
+ ///
+ public double MeasuredWriteSpeed { get { return m_current_write_speed; } }
- if (table != null)
- {
- long maxspeed = read ? m_readspeed : m_writespeed;
- Stream stream = m_basestream;
- long bytecount = read ? m_bytesread : m_byteswritten;
+ ///
+ /// Calculates the speed, and inserts appropriate delays
+ ///
+ /// True if the operation is read, false otherwise
+ /// The data buffer
+ /// The offset into the buffer
+ /// The number of bytes to read or write
+ /// The number of bytes processed while delaying
+ private void DelayIfRequired(ref long limit, int count, ref DateTime last_sample, ref long last_count, ref double current_speed)
+ {
+ var now = DateTime.Now;
- //Add this access
- table.Add(new KeyValuePair(DateTime.Now.Ticks, count));
- bytecount += count;
+ if (count <= 0 || limit <= 0)
+ return;
- //Prevent too large tables
- while (table.Count > STATISTICS_SIZE)
- {
- bytecount -= table[0].Value;
- table.RemoveAt(0);
- }
+ // If we are just starting, set the timer and counter
+ if (last_sample.Ticks == 0)
+ {
+ last_count = 0;
+ last_sample = now;
+ current_speed = limit;
+ return;
+ }
- if (table.Count != 0 && bytecount != 0)
- {
- TimeSpan duration = new TimeSpan(table[table.Count - 1].Key - table[0].Key);
+ // Compute the current duration
+ var duration = now - last_sample;
- //Bail if we are too slow
- if (duration.Ticks < MIN_DURATION || bytecount <= 0)
- return 0;
+ // Update speed in intervals
+ if (duration.Ticks > SAMPLE_PERIOD || last_count > limit)
+ {
+ // After a sample period, measure how far ahead we are
+ var target_delay = TimeSpan.FromSeconds(last_count / (double)limit) - duration;
- //TODO: The resolution is too low in "TotalSeconds", so the speed is a little higher
- double speed = bytecount / duration.TotalSeconds;
- if (speed > maxspeed)
- {
- //We are too fast, delay the access. Calculating how much wait we need.
- double secondsNeeded = (bytecount / (double)maxspeed) - duration.TotalSeconds;
- long delayTicks = (long)(secondsNeeded * TimeSpan.TicksPerSecond);
+ // If we are actually ahead, delay for a little while
+ if (target_delay.Ticks > 1000)
+ {
+ // With large changes, we avoid sleeping for several minutes
+ // This makes the throttling more resposive when increasing the
+ // throughput, even with large changes
+ var ms = (int)Math.Min(target_delay.TotalMilliseconds, 2 * 1000);
+ System.Threading.Thread.Sleep(ms);
- //Calculate the time we should finish, to obey the limit
- long targetTime = DateTime.Now.Ticks + delayTicks;
+ // When we compute how fast this sample was, we include the delay
+ now = DateTime.Now;
+ }
- if (delayTicks > 0)
- {
- while (count > DELAY_CHUNK_SIZE && delayTicks > 0)
- {
- int bytes = read ? stream.Read(buffer, offset, DELAY_CHUNK_SIZE) : DELAY_CHUNK_SIZE;
- if (!read)
- stream.Write(buffer, offset, DELAY_CHUNK_SIZE);
-
- delayTicks = targetTime - DateTime.Now.Ticks;
- long ticksToDelay = (delayTicks / count) * bytes;
-
- if (ticksToDelay > 0)
- System.Threading.Thread.Sleep(new TimeSpan(ticksToDelay));
-
- //Reset to include the waited time
- delayTicks = targetTime - DateTime.Now.Ticks;
-
- offset += bytes;
- count -= bytes;
- bytesprocessed += bytes;
-
- if (bytes == 0)
- break;
- }
-
- if (delayTicks > 0)
- System.Threading.Thread.Sleep(new TimeSpan(delayTicks));
-
- //Add a marker, indicating that we already slowed down
- table.Add(new KeyValuePair(DateTime.Now.Ticks, 0));
- }
-
- }
- }
-
- if (read)
- m_bytesread = bytecount;
- else
- m_byteswritten = bytecount;
- }
-
- return bytesprocessed;
+ current_speed = last_count / (now - last_sample).TotalSeconds;
+ last_sample = now;
+ last_count = 0;
+ }
}
}
}