/* * Copyright (c) Contributors, http://opensimulator.org/ * See CONTRIBUTORS.TXT for a full list of copyright holders. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * Neither the name of the OpenSimulator Project nor the * names of its contributors may be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ using System; using System.Collections; using System.Collections.Generic; using System.Reflection; using OpenSim.Framework; using log4net; namespace OpenSim.Region.ClientStack.LindenUDP { /// /// A hierarchical token bucket for bandwidth throttling. See /// http://en.wikipedia.org/wiki/Token_bucket for more information /// public class TokenBucket { private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); private static Int32 m_counter = 0; // private Int32 m_identifier; protected const float m_timeScale = 1e-3f; /// /// This is the number of m_minimumDripRate bytes /// allowed in a burst /// roughtly, with this settings, the maximum time system will take /// to recheck a bucket in ms /// /// protected const float m_quantumsPerBurst = 5; /// /// protected const float m_minimumDripRate = 1500; /// Time of the last drip protected double m_lastDrip; /// /// The number of bytes that can be sent at this moment. This is the /// current number of tokens in the bucket /// protected float m_tokenCount; /// /// Map of children buckets and their requested maximum burst rate /// protected Dictionary m_children = new Dictionary(); #region Properties /// /// The parent bucket of this bucket, or null if this bucket has no /// parent. The parent bucket will limit the aggregate bandwidth of all /// of its children buckets /// protected TokenBucket m_parent; public TokenBucket Parent { get { return m_parent; } set { m_parent = value; } } /// /// This is the maximum number /// of tokens that can accumulate in the bucket at any one time. This /// also sets the total request for leaf nodes /// protected float m_burst; public virtual float MaxDripRate { get; set; } public float RequestedBurst { get { return m_burst; } set { float rate = (value < 0 ? 0 : value); if (rate < 1.5f * m_minimumDripRate) rate = 1.5f * m_minimumDripRate; else if (rate > m_minimumDripRate * m_quantumsPerBurst) rate = m_minimumDripRate * m_quantumsPerBurst; m_burst = rate; } } public float Burst { get { float rate = RequestedBurst * BurstModifier(); if (rate < m_minimumDripRate) rate = m_minimumDripRate; return (float)rate; } } /// /// The requested drip rate for this particular bucket. /// /// /// 0 then TotalDripRequest is used instead. /// Can never be above MaxDripRate. /// Tokens are added to the bucket at any time /// is called, at the granularity of /// the system tick interval (typically around 15-22ms) protected float m_dripRate; public virtual float RequestedDripRate { get { return (m_dripRate == 0 ? m_totalDripRequest : m_dripRate); } set { m_dripRate = (value < 0 ? 0 : value); m_totalDripRequest = m_dripRate; if (m_parent != null) m_parent.RegisterRequest(this,m_dripRate); } } public virtual float DripRate { get { float rate = Math.Min(RequestedDripRate,TotalDripRequest); if (m_parent == null) return rate; rate *= m_parent.DripRateModifier(); if (rate < m_minimumDripRate) rate = m_minimumDripRate; return (float)rate; } } /// /// The current total of the requested maximum burst rates of children buckets. /// protected float m_totalDripRequest; public float TotalDripRequest { get { return m_totalDripRequest; } set { m_totalDripRequest = value; } } #endregion Properties #region Constructor /// /// Default constructor /// /// Identifier for this token bucket /// Parent bucket if this is a child bucket, or /// null if this is a root bucket /// Maximum size of the bucket in bytes, or /// zero if this bucket has no maximum capacity /// Rate that the bucket fills, in bytes per /// second. If zero, the bucket always remains full public TokenBucket(TokenBucket parent, float dripRate, float MaxBurst) { m_counter++; Parent = parent; RequestedDripRate = dripRate; RequestedBurst = MaxBurst; m_lastDrip = Util.GetTimeStampMS() + 50.0; } #endregion Constructor /// /// Compute a modifier for the MaxBurst rate. This is 1.0, meaning /// no modification if the requested bandwidth is less than the /// max burst bandwidth all the way to the root of the throttle /// hierarchy. However, if any of the parents is over-booked, then /// the modifier will be less than 1. /// protected float DripRateModifier() { float driprate = DripRate; return driprate >= TotalDripRequest ? 1.0f : (driprate / TotalDripRequest); } /// /// protected float BurstModifier() { // for now... burst rate is always m_quantumsPerBurst (constant) // larger than drip rate so the ratio of burst requests is the // same as the drip ratio return DripRateModifier(); } /// /// Register drip rate requested by a child of this throttle. Pass the /// changes up the hierarchy. /// public void RegisterRequest(TokenBucket child, float request) { lock (m_children) { m_children[child] = request; m_totalDripRequest = 0; foreach (KeyValuePair cref in m_children) m_totalDripRequest += cref.Value; } // Pass the new values up to the parent if (m_parent != null) m_parent.RegisterRequest(this, Math.Min(RequestedDripRate, TotalDripRequest)); } /// /// Remove the rate requested by a child of this throttle. Pass the /// changes up the hierarchy. /// public void UnregisterRequest(TokenBucket child) { lock (m_children) { m_children.Remove(child); m_totalDripRequest = 0; foreach (KeyValuePair cref in m_children) m_totalDripRequest += cref.Value; } // Pass the new values up to the parent if (Parent != null) Parent.RegisterRequest(this,Math.Min(RequestedDripRate, TotalDripRequest)); } /// /// Remove a given number of tokens from the bucket /// /// Number of tokens to remove from the bucket /// True if the requested number of tokens were removed from /// the bucket, otherwise false public bool RemoveTokens(int amount) { // Deposit tokens for this interval Drip(); // If we have enough tokens then remove them and return if (m_tokenCount - amount >= 0) { // we don't have to remove from the parent, the drip rate is already // reflective of the drip rate limits in the parent m_tokenCount -= amount; return true; } return false; } public bool CheckTokens(int amount) { return (m_tokenCount - amount >= 0); } public int GetCatBytesCanSend(int timeMS) { // return (int)(m_tokenCount + timeMS * m_dripRate * 1e-3); return (int)(timeMS * DripRate * 1e-3); } /// /// Add tokens to the bucket over time. The number of tokens added each /// call depends on the length of time that has passed since the last /// call to Drip /// /// True if tokens were added to the bucket, otherwise false protected void Drip() { // This should never happen... means we are a leaf node and were created // with no drip rate... if (DripRate == 0) { m_log.WarnFormat("[TOKENBUCKET] something odd is happening and drip rate is 0 for {0}", m_counter); return; } double now = Util.GetTimeStampMS(); double deltaMS = now - m_lastDrip; m_lastDrip = now; if (deltaMS <= 0) return; m_tokenCount += (float)deltaMS * DripRate * m_timeScale; float burst = Burst; if (m_tokenCount > burst) m_tokenCount = burst; } } public class AdaptiveTokenBucket : TokenBucket { private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); public bool AdaptiveEnabled { get; set; } /// /// The minimum rate for flow control. Minimum drip rate is one /// packet per second. /// protected const float m_minimumFlow = 50000; // // The maximum rate for flow control. Drip rate can never be // greater than this. // protected float m_maxDripRate = 0; public override float MaxDripRate { get { return (m_maxDripRate == 0 ? m_totalDripRequest : m_maxDripRate); } set { m_maxDripRate = (value == 0 ? m_totalDripRequest : Math.Max(value, m_minimumFlow)); } } private bool m_enabled = false; // // Adjust drip rate in response to network conditions. // public virtual float AdjustedDripRate { get { return m_dripRate; } set { m_dripRate = OpenSim.Framework.Util.Clamp(value, m_minimumFlow, MaxDripRate); if (m_parent != null) m_parent.RegisterRequest(this, m_dripRate); } } // // // public AdaptiveTokenBucket(TokenBucket parent, float maxDripRate, float maxBurst, bool enabled) : base(parent, maxDripRate, maxBurst) { m_enabled = enabled; MaxDripRate = maxDripRate; if (enabled) AdjustedDripRate = m_maxDripRate * .5f; else AdjustedDripRate = m_maxDripRate; } /// /// Reliable packets sent to the client for which we never received an ack adjust the drip rate down. /// Number of packets that expired without successful delivery /// public void ExpirePackets(Int32 count) { // m_log.WarnFormat("[ADAPTIVEBUCKET] drop {0} by {1} expired packets",AdjustedDripRate,count); if (m_enabled) AdjustedDripRate = (Int64)(AdjustedDripRate / Math.Pow(2, count)); } // // // public void AcknowledgePackets(Int32 count) { if (m_enabled) AdjustedDripRate = AdjustedDripRate + count; } } }