// Copyright (C) 2024, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a // copy of this software and associated documentation files (the "Software"), // to deal in the Software without restriction, including without limitation // the rights to use, copy, modify, merge, publish, distribute, sublicense, // and/or sell copies of the Software, and to permit persons to whom the // Software is furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. using System; using System.Collections.Generic; using System.Text; using System.IO; //TODO: Use the IPGlobalProperties to dynamically throttle data //http://msdn.microsoft.com/en-us/library/system.net.networkinformation.ipglobalproperties.aspx namespace Duplicati.Library.Utility { /// /// This class throttles the rate data can be read or written to the underlying stream. /// This creates a bandwidth throttle option for any stream, including a network stream. /// public class ThrottledStream : OverrideableStream { /// /// The max number of bytes pr. second to write /// private long m_writespeed; /// /// The max number of bytes pr. second to read /// private long m_readspeed; /// /// 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 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 time the last read was sampled /// private DateTime m_last_limit_update; /// /// The number of ticks to have passed before a sample is taken /// private const long SAMPLE_PERIOD = TimeSpan.TicksPerSecond / 4; /// /// The number of ticks to have passed before the limits updater is called /// private const long UPDATE_PERIOD = TimeSpan.TicksPerSecond; /// /// Callback method used to update limits /// private readonly Action m_updateLimits; /// /// Creates a throttle around a stream. /// /// The stream to throttle public ThrottledStream(Stream basestream, Action updateLimits) : base(basestream) { m_last_read_sample = m_last_write_sample = m_last_limit_update = new DateTime(0); m_updateLimits = updateLimits; } /// /// Creates a throttle around a stream. /// /// The stream to throttle /// The maximum number of bytes pr. second to read. Specify a number less than 1 to allow unlimited speed. /// The maximum number of bytes pr. second to write. Specify a number less than 1 to allow unlimited speed. public ThrottledStream(Stream basestream, long readspeed, long writespeed) : base(basestream) { m_readspeed = readspeed; m_writespeed = writespeed; m_last_read_sample = m_last_write_sample = m_last_limit_update = new DateTime(0); m_updateLimits = null; } /// /// Read from this stream into a buffer. /// /// The buffer to write to. /// The offset into the buffer. /// The number of bytes to read. public override int Read(byte[] buffer, int offset, int count) { var remaining = count; 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 UpdateLimits(); 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); var actual = m_basestream.Read(buffer, offset + count - remaining, chunksize); if (actual <= 0) break; m_current_read_counter += actual; remaining -= actual; } return count - remaining; } /// /// Write from a buffer to this stream. /// /// The buffer to read from. /// The offset into the buffer. /// The number of bytes to write. public override void Write(byte[] buffer, int offset, int count) { int bytesWritten = 0; 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 UpdateLimits(); 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 + bytesWritten, chunksize); m_current_write_counter += chunksize; bytesWritten += chunksize; count -= chunksize; } } /// /// Gets or sets the current read speed in bytes pr. second. /// Set to zero or less to disable throttling. /// public long ReadSpeed { get { return m_readspeed; } set { m_readspeed = value; } } /// /// Gets or sets the current write speed in bytes pr. second. /// Set to zero or less to disable throttling /// public long WriteSpeed { get { return m_writespeed; } set { m_writespeed = value; } } /// /// Gets the actual measured read speed. /// public double MeasuredReadSpeed { get { return m_current_read_speed; } } /// /// Gets the actual measured write speed. /// public double MeasuredWriteSpeed { get { return m_current_write_speed; } } /// /// Helper method called to update the limits /// private void UpdateLimits() { var now = DateTime.Now; if (m_updateLimits != null && now.Ticks - m_last_limit_update.Ticks > UPDATE_PERIOD) { m_updateLimits(this); m_last_limit_update = now; } } /// /// Calculates the speed, and inserts appropriate delays /// private void DelayIfRequired(ref long limit, int count, ref DateTime last_sample, ref long last_count, ref double current_speed) { var now = DateTime.Now; if (count <= 0 || limit <= 0) return; // 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; } // Compute the current duration var duration = now - last_sample; // 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; // 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 responsive when increasing the // throughput, even with large changes var ms = (int)Math.Min(target_delay.TotalMilliseconds, 2 * 1000); System.Threading.Thread.Sleep(ms); // When we compute how fast this sample was, we include the delay now = DateTime.Now; } current_speed = last_count / (now - last_sample).TotalSeconds; last_sample = now; last_count = 0; } } } }