diff --git a/OpenSim/Framework/PriorityQueue.cs b/OpenSim/Framework/PriorityQueue.cs index eec2a9268e..3e6fdaa7f9 100644 --- a/OpenSim/Framework/PriorityQueue.cs +++ b/OpenSim/Framework/PriorityQueue.cs @@ -42,22 +42,40 @@ namespace OpenSim.Framework public delegate bool UpdatePriorityHandler(ref uint priority, ISceneEntity entity); - // Heap[0] for self updates - // Heap[1..12] for entity updates - + /// + /// Total number of queues (priorities) available + /// public const uint NumberOfQueues = 12; - public const uint ImmediateQueue = 0; + + /// + /// Number of queuest (priorities) that are processed immediately + /// [] m_heaps = new MinHeap[NumberOfQueues]; private Dictionary m_lookupTable; + + // internal state used to ensure the deqeues are spread across the priority + // queues "fairly". queuecounts is the amount to pull from each queue in + // each pass. weighted towards the higher priority queues private uint m_nextQueue = 0; + private uint m_countFromQueue = 0; + private uint[] m_queueCounts = { 8, 4, 4, 2, 2, 2, 2, 1, 1, 1, 1, 1 }; + + // next request is a counter of the number of updates queued, it provides + // a total ordering on the updates coming through the queue and is more + // lightweight (and more discriminating) than tick count private UInt64 m_nextRequest = 0; + /// + /// Lock for enqueue and dequeue operations on the priority queue + /// private object m_syncRoot = new object(); public object SyncRoot { get { return this.m_syncRoot; } } +#region constructor public PriorityQueue() : this(MinHeap.DEFAULT_CAPACITY) { } public PriorityQueue(int capacity) @@ -66,8 +84,16 @@ namespace OpenSim.Framework for (int i = 0; i < m_heaps.Length; ++i) m_heaps[i] = new MinHeap(capacity); - } + m_nextQueue = NumberOfImmediateQueues; + m_countFromQueue = m_queueCounts[m_nextQueue]; + } +#endregion Constructor + +#region PublicMethods + /// + /// Return the number of items in the queues + /// public int Count { get @@ -75,10 +101,14 @@ namespace OpenSim.Framework int count = 0; for (int i = 0; i < m_heaps.Length; ++i) count += m_heaps[i].Count; + return count; } } + /// + /// Enqueue an item into the specified priority queue + /// public bool Enqueue(uint pqueue, IEntityUpdate value) { LookupItem lookup; @@ -100,32 +130,62 @@ namespace OpenSim.Framework return true; } + /// + /// Remove an item from one of the queues. Specifically, it removes the + /// oldest item from the next queue in order to provide fair access to + /// all of the queues + /// public bool TryDequeue(out IEntityUpdate value, out Int32 timeinqueue) { // If there is anything in priority queue 0, return it first no // matter what else. Breaks fairness. But very useful. - if (m_heaps[ImmediateQueue].Count > 0) + for (int iq = 0; iq < NumberOfImmediateQueues; iq++) { - MinHeapItem item = m_heaps[ImmediateQueue].RemoveMin(); + if (m_heaps[iq].Count > 0) + { + MinHeapItem item = m_heaps[iq].RemoveMin(); + m_lookupTable.Remove(item.Value.Entity.LocalId); + timeinqueue = Util.EnvironmentTickCountSubtract(item.EntryTime); + value = item.Value; + + return true; + } + } + + // To get the fair queing, we cycle through each of the + // queues when finding an element to dequeue. + // We pull (NumberOfQueues - QueueIndex) items from each queue in order + // to give lower numbered queues a higher priority and higher percentage + // of the bandwidth. + + // Check for more items to be pulled from the current queue + if (m_heaps[m_nextQueue].Count > 0 && m_countFromQueue > 0) + { + m_countFromQueue--; + + MinHeapItem item = m_heaps[m_nextQueue].RemoveMin(); m_lookupTable.Remove(item.Value.Entity.LocalId); timeinqueue = Util.EnvironmentTickCountSubtract(item.EntryTime); value = item.Value; - + return true; } - + + // Find the next non-immediate queue with updates in it for (int i = 0; i < NumberOfQueues; ++i) { - // To get the fair queing, we cycle through each of the - // queues when finding an element to dequeue, this code - // assumes that the distribution of updates in the queues - // is polynomial, probably quadractic (eg distance of PI * R^2) - uint h = (uint)((m_nextQueue + i) % NumberOfQueues); - if (m_heaps[h].Count > 0) - { - m_nextQueue = (uint)((h + 1) % NumberOfQueues); + m_nextQueue = (uint)((m_nextQueue + 1) % NumberOfQueues); + m_countFromQueue = m_queueCounts[m_nextQueue]; - MinHeapItem item = m_heaps[h].RemoveMin(); + // if this is one of the immediate queues, just skip it + if (m_nextQueue < NumberOfImmediateQueues) + continue; + + if (m_heaps[m_nextQueue].Count > 0) + { + m_countFromQueue--; + + MinHeapItem item = m_heaps[m_nextQueue].RemoveMin(); m_lookupTable.Remove(item.Value.Entity.LocalId); timeinqueue = Util.EnvironmentTickCountSubtract(item.EntryTime); value = item.Value; @@ -139,6 +199,10 @@ namespace OpenSim.Framework return false; } + /// + /// Reapply the prioritization function to each of the updates currently + /// stored in the priority queues. + /// + /// public override string ToString() { string s = ""; for (int i = 0; i < NumberOfQueues; i++) - { - if (s != "") s += ","; - s += m_heaps[i].Count.ToString(); - } + s += String.Format("{0,7} ",m_heaps[i].Count); return s; } +#endregion PublicMethods + #region MinHeapItem private struct MinHeapItem : IComparable { diff --git a/OpenSim/Region/ClientStack/LindenUDP/LLClientView.cs b/OpenSim/Region/ClientStack/LindenUDP/LLClientView.cs index cd438d67d1..51ec3a82e8 100644 --- a/OpenSim/Region/ClientStack/LindenUDP/LLClientView.cs +++ b/OpenSim/Region/ClientStack/LindenUDP/LLClientView.cs @@ -394,6 +394,11 @@ namespace OpenSim.Region.ClientStack.LindenUDP public ulong ActiveGroupPowers { get { return m_activeGroupPowers; } } public bool IsGroupMember(UUID groupID) { return m_groupPowers.ContainsKey(groupID); } + /// + /// Entity update queues + /// + public PriorityQueue EntityUpdateQueue { get { return m_entityUpdates; } } + /// /// First name of the agent/avatar represented by the client /// @@ -3625,7 +3630,11 @@ namespace OpenSim.Region.ClientStack.LindenUDP // Remove the update packet from the list of packets waiting for acknowledgement // because we are requeuing the list of updates. They will be resent in new packets // with the most recent state and priority. - m_udpClient.NeedAcks.Remove(oPacket.SequenceNumber, 0, true); + m_udpClient.NeedAcks.Remove(oPacket.SequenceNumber); + + // Count this as a resent packet since we are going to requeue all of the updates contained in it + Interlocked.Increment(ref m_udpClient.PacketsResent); + foreach (EntityUpdate update in updates) ResendPrimUpdate(update); } @@ -4092,7 +4101,11 @@ namespace OpenSim.Region.ClientStack.LindenUDP // Remove the update packet from the list of packets waiting for acknowledgement // because we are requeuing the list of updates. They will be resent in new packets // with the most recent state. - m_udpClient.NeedAcks.Remove(oPacket.SequenceNumber, 0, true); + m_udpClient.NeedAcks.Remove(oPacket.SequenceNumber); + + // Count this as a resent packet since we are going to requeue all of the updates contained in it + Interlocked.Increment(ref m_udpClient.PacketsResent); + foreach (ObjectPropertyUpdate update in updates) ResendPropertyUpdate(update); } @@ -11513,7 +11526,7 @@ namespace OpenSim.Region.ClientStack.LindenUDP /// public byte[] GetThrottlesPacked(float multiplier) { - return m_udpClient.GetThrottlesPacked(); + return m_udpClient.GetThrottlesPacked(multiplier); } /// diff --git a/OpenSim/Region/ClientStack/LindenUDP/LLUDPClient.cs b/OpenSim/Region/ClientStack/LindenUDP/LLUDPClient.cs index e54d32629c..80d4e1b62d 100644 --- a/OpenSim/Region/ClientStack/LindenUDP/LLUDPClient.cs +++ b/OpenSim/Region/ClientStack/LindenUDP/LLUDPClient.cs @@ -182,9 +182,9 @@ namespace OpenSim.Region.ClientStack.LindenUDP m_maxRTO = maxRTO; // Create a token bucket throttle for this client that has the scene token bucket as a parent - m_throttleClient = new AdaptiveTokenBucket(parentThrottle, rates.TotalLimit); + m_throttleClient = new AdaptiveTokenBucket(parentThrottle, rates.Total, rates.AdaptiveThrottlesEnabled); // Create a token bucket throttle for the total categary with the client bucket as a throttle - m_throttleCategory = new TokenBucket(m_throttleClient, rates.TotalLimit); + m_throttleCategory = new TokenBucket(m_throttleClient, rates.Total); // Create an array of token buckets for this clients different throttle categories m_throttleCategories = new TokenBucket[THROTTLE_CATEGORY_COUNT]; @@ -195,7 +195,7 @@ namespace OpenSim.Region.ClientStack.LindenUDP // Initialize the packet outboxes, where packets sit while they are waiting for tokens m_packetOutboxes[i] = new OpenSim.Framework.LocklessQueue(); // Initialize the token buckets that control the throttling for each category - m_throttleCategories[i] = new TokenBucket(m_throttleCategory, rates.GetLimit(type)); + m_throttleCategories[i] = new TokenBucket(m_throttleCategory, rates.GetRate(type)); } // Default the retransmission timeout to three seconds @@ -229,26 +229,6 @@ namespace OpenSim.Region.ClientStack.LindenUDP /// Information about the client connection public ClientInfo GetClientInfo() { -/// - TokenBucket tb; - - tb = m_throttleClient.Parent; - m_log.WarnFormat("[TOKENS] {3}: Actual={0},Request={1},TotalRequest={2}",tb.DripRate,tb.RequestedDripRate,tb.TotalDripRequest,"ROOT"); - - tb = m_throttleClient; - m_log.WarnFormat("[TOKENS] {3}: Actual={0},Request={1},TotalRequest={2}",tb.DripRate,tb.RequestedDripRate,tb.TotalDripRequest," CLIENT"); - - tb = m_throttleCategory; - m_log.WarnFormat("[TOKENS] {3}: Actual={0},Request={1},TotalRequest={2}",tb.DripRate,tb.RequestedDripRate,tb.TotalDripRequest," CATEGORY"); - - for (int i = 0; i < THROTTLE_CATEGORY_COUNT; i++) - { - tb = m_throttleCategories[i]; - m_log.WarnFormat("[TOKENS] {4} <{0}:{1}>: Actual={2},Requested={3}",AgentID,i,tb.DripRate,tb.RequestedDripRate," BUCKET"); - } - -/// - // TODO: This data structure is wrong in so many ways. Locking and copying the entire lists // of pending and needed ACKs for every client every time some method wants information about // this connection is a recipe for poor performance @@ -260,12 +240,10 @@ namespace OpenSim.Region.ClientStack.LindenUDP info.landThrottle = (int)m_throttleCategories[(int)ThrottleOutPacketType.Land].DripRate; info.windThrottle = (int)m_throttleCategories[(int)ThrottleOutPacketType.Wind].DripRate; info.cloudThrottle = (int)m_throttleCategories[(int)ThrottleOutPacketType.Cloud].DripRate; - // info.taskThrottle = m_throttleCategories[(int)ThrottleOutPacketType.State].DripRate + m_throttleCategories[(int)ThrottleOutPacketType.Task].DripRate; info.taskThrottle = (int)m_throttleCategories[(int)ThrottleOutPacketType.Task].DripRate; info.assetThrottle = (int)m_throttleCategories[(int)ThrottleOutPacketType.Asset].DripRate; info.textureThrottle = (int)m_throttleCategories[(int)ThrottleOutPacketType.Texture].DripRate; - info.totalThrottle = info.resendThrottle + info.landThrottle + info.windThrottle + info.cloudThrottle + - info.taskThrottle + info.assetThrottle + info.textureThrottle; + info.totalThrottle = (int)m_throttleCategory.DripRate; return info; } @@ -352,8 +330,6 @@ namespace OpenSim.Region.ClientStack.LindenUDP int asset = (int)(BitConverter.ToSingle(adjData, pos) * 0.125f); // State is a subcategory of task that we allocate a percentage to int state = 0; - // int state = (int)((float)task * STATE_TASK_PERCENTAGE); - // task -= state; // Make sure none of the throttles are set below our packet MTU, // otherwise a throttle could become permanently clogged @@ -364,19 +340,14 @@ namespace OpenSim.Region.ClientStack.LindenUDP task = Math.Max(task, LLUDPServer.MTU); texture = Math.Max(texture, LLUDPServer.MTU); asset = Math.Max(asset, LLUDPServer.MTU); - state = Math.Max(state, LLUDPServer.MTU); - int total = resend + land + wind + cloud + task + texture + asset + state; - - //m_log.DebugFormat("[LLUDPCLIENT]: {0} is setting throttles. Resend={1}, Land={2}, Wind={3}, Cloud={4}, Task={5}, Texture={6}, Asset={7}, State={8}, Total={9}", - // AgentID, resend, land, wind, cloud, task, texture, asset, state, total); + //int total = resend + land + wind + cloud + task + texture + asset; + //m_log.DebugFormat("[LLUDPCLIENT]: {0} is setting throttles. Resend={1}, Land={2}, Wind={3}, Cloud={4}, Task={5}, Texture={6}, Asset={7}, Total={8}", + // AgentID, resend, land, wind, cloud, task, texture, asset, total); // Update the token buckets with new throttle values TokenBucket bucket; - bucket = m_throttleCategory; - bucket.RequestedDripRate = total; - bucket = m_throttleCategories[(int)ThrottleOutPacketType.Resend]; bucket.RequestedDripRate = resend; @@ -405,22 +376,38 @@ namespace OpenSim.Region.ClientStack.LindenUDP m_packedThrottles = null; } - public byte[] GetThrottlesPacked() + public byte[] GetThrottlesPacked(float multiplier) { byte[] data = m_packedThrottles; if (data == null) { + float rate; + data = new byte[7 * 4]; int i = 0; - Buffer.BlockCopy(Utils.FloatToBytes((float)m_throttleCategories[(int)ThrottleOutPacketType.Resend].RequestedDripRate), 0, data, i, 4); i += 4; - Buffer.BlockCopy(Utils.FloatToBytes((float)m_throttleCategories[(int)ThrottleOutPacketType.Land].RequestedDripRate), 0, data, i, 4); i += 4; - Buffer.BlockCopy(Utils.FloatToBytes((float)m_throttleCategories[(int)ThrottleOutPacketType.Wind].RequestedDripRate), 0, data, i, 4); i += 4; - Buffer.BlockCopy(Utils.FloatToBytes((float)m_throttleCategories[(int)ThrottleOutPacketType.Cloud].RequestedDripRate), 0, data, i, 4); i += 4; - Buffer.BlockCopy(Utils.FloatToBytes((float)m_throttleCategories[(int)ThrottleOutPacketType.Task].RequestedDripRate), 0, data, i, 4); i += 4; - Buffer.BlockCopy(Utils.FloatToBytes((float)m_throttleCategories[(int)ThrottleOutPacketType.Texture].RequestedDripRate), 0, data, i, 4); i += 4; - Buffer.BlockCopy(Utils.FloatToBytes((float)m_throttleCategories[(int)ThrottleOutPacketType.Asset].RequestedDripRate), 0, data, i, 4); i += 4; + // multiply by 8 to convert bytes back to bits + rate = (float)m_throttleCategories[(int)ThrottleOutPacketType.Resend].RequestedDripRate * 8 * multiplier; + Buffer.BlockCopy(Utils.FloatToBytes(rate), 0, data, i, 4); i += 4; + + rate = (float)m_throttleCategories[(int)ThrottleOutPacketType.Land].RequestedDripRate * 8 * multiplier; + Buffer.BlockCopy(Utils.FloatToBytes(rate), 0, data, i, 4); i += 4; + + rate = (float)m_throttleCategories[(int)ThrottleOutPacketType.Wind].RequestedDripRate * 8 * multiplier; + Buffer.BlockCopy(Utils.FloatToBytes(rate), 0, data, i, 4); i += 4; + + rate = (float)m_throttleCategories[(int)ThrottleOutPacketType.Cloud].RequestedDripRate * 8 * multiplier; + Buffer.BlockCopy(Utils.FloatToBytes(rate), 0, data, i, 4); i += 4; + + rate = (float)m_throttleCategories[(int)ThrottleOutPacketType.Task].RequestedDripRate * 8 * multiplier; + Buffer.BlockCopy(Utils.FloatToBytes(rate), 0, data, i, 4); i += 4; + + rate = (float)m_throttleCategories[(int)ThrottleOutPacketType.Texture].RequestedDripRate * 8 * multiplier; + Buffer.BlockCopy(Utils.FloatToBytes(rate), 0, data, i, 4); i += 4; + + rate = (float)m_throttleCategories[(int)ThrottleOutPacketType.Asset].RequestedDripRate * 8 * multiplier; + Buffer.BlockCopy(Utils.FloatToBytes(rate), 0, data, i, 4); i += 4; m_packedThrottles = data; } diff --git a/OpenSim/Region/ClientStack/LindenUDP/LLUDPServer.cs b/OpenSim/Region/ClientStack/LindenUDP/LLUDPServer.cs index a1a58e5a68..ab6674db27 100644 --- a/OpenSim/Region/ClientStack/LindenUDP/LLUDPServer.cs +++ b/OpenSim/Region/ClientStack/LindenUDP/LLUDPServer.cs @@ -672,7 +672,7 @@ namespace OpenSim.Region.ClientStack.LindenUDP if (packet.Header.AppendedAcks && packet.Header.AckList != null) { for (int i = 0; i < packet.Header.AckList.Length; i++) - udpClient.NeedAcks.Remove(packet.Header.AckList[i], now, packet.Header.Resent); + udpClient.NeedAcks.Acknowledge(packet.Header.AckList[i], now, packet.Header.Resent); } // Handle PacketAck packets @@ -681,7 +681,7 @@ namespace OpenSim.Region.ClientStack.LindenUDP PacketAckPacket ackPacket = (PacketAckPacket)packet; for (int i = 0; i < ackPacket.Packets.Length; i++) - udpClient.NeedAcks.Remove(ackPacket.Packets[i].ID, now, packet.Header.Resent); + udpClient.NeedAcks.Acknowledge(ackPacket.Packets[i].ID, now, packet.Header.Resent); // We don't need to do anything else with PacketAck packets return; diff --git a/OpenSim/Region/ClientStack/LindenUDP/ThrottleRates.cs b/OpenSim/Region/ClientStack/LindenUDP/ThrottleRates.cs index aaf6e26d58..c9aac0ba09 100644 --- a/OpenSim/Region/ClientStack/LindenUDP/ThrottleRates.cs +++ b/OpenSim/Region/ClientStack/LindenUDP/ThrottleRates.cs @@ -52,30 +52,13 @@ namespace OpenSim.Region.ClientStack.LindenUDP public int Texture; /// Drip rate for asset packets public int Asset; - /// Drip rate for state packets - public int State; + /// Drip rate for the parent token bucket public int Total; - /// Maximum burst rate for resent packets - public int ResendLimit; - /// Maximum burst rate for land packets - public int LandLimit; - /// Maximum burst rate for wind packets - public int WindLimit; - /// Maximum burst rate for cloud packets - public int CloudLimit; - /// Maximum burst rate for task (state and transaction) packets - public int TaskLimit; - /// Maximum burst rate for texture packets - public int TextureLimit; - /// Maximum burst rate for asset packets - public int AssetLimit; - /// Maximum burst rate for state packets - public int StateLimit; - /// Burst rate for the parent token bucket - public int TotalLimit; - + /// Flag used to enable adaptive throttles + public bool AdaptiveThrottlesEnabled; + /// /// Default constructor /// @@ -86,26 +69,17 @@ namespace OpenSim.Region.ClientStack.LindenUDP { IConfig throttleConfig = config.Configs["ClientStack.LindenUDP"]; - Resend = throttleConfig.GetInt("resend_default", 12500); - Land = throttleConfig.GetInt("land_default", 1000); - Wind = throttleConfig.GetInt("wind_default", 1000); - Cloud = throttleConfig.GetInt("cloud_default", 1000); - Task = throttleConfig.GetInt("task_default", 1000); - Texture = throttleConfig.GetInt("texture_default", 1000); - Asset = throttleConfig.GetInt("asset_default", 1000); - State = throttleConfig.GetInt("state_default", 1000); - - ResendLimit = throttleConfig.GetInt("resend_limit", 18750); - LandLimit = throttleConfig.GetInt("land_limit", 29750); - WindLimit = throttleConfig.GetInt("wind_limit", 18750); - CloudLimit = throttleConfig.GetInt("cloud_limit", 18750); - TaskLimit = throttleConfig.GetInt("task_limit", 18750); - TextureLimit = throttleConfig.GetInt("texture_limit", 55750); - AssetLimit = throttleConfig.GetInt("asset_limit", 27500); - StateLimit = throttleConfig.GetInt("state_limit", 37000); + Resend = throttleConfig.GetInt("resend_default", 6625); + Land = throttleConfig.GetInt("land_default", 9125); + Wind = throttleConfig.GetInt("wind_default", 1750); + Cloud = throttleConfig.GetInt("cloud_default", 1750); + Task = throttleConfig.GetInt("task_default", 18500); + Texture = throttleConfig.GetInt("texture_default", 18500); + Asset = throttleConfig.GetInt("asset_default", 10500); Total = throttleConfig.GetInt("client_throttle_max_bps", 0); - TotalLimit = Total; + + AdaptiveThrottlesEnabled = throttleConfig.GetBoolean("enable_adaptive_throttles", false); } catch (Exception) { } } @@ -128,34 +102,6 @@ namespace OpenSim.Region.ClientStack.LindenUDP return Texture; case ThrottleOutPacketType.Asset: return Asset; - case ThrottleOutPacketType.State: - return State; - case ThrottleOutPacketType.Unknown: - default: - return 0; - } - } - - public int GetLimit(ThrottleOutPacketType type) - { - switch (type) - { - case ThrottleOutPacketType.Resend: - return ResendLimit; - case ThrottleOutPacketType.Land: - return LandLimit; - case ThrottleOutPacketType.Wind: - return WindLimit; - case ThrottleOutPacketType.Cloud: - return CloudLimit; - case ThrottleOutPacketType.Task: - return TaskLimit; - case ThrottleOutPacketType.Texture: - return TextureLimit; - case ThrottleOutPacketType.Asset: - return AssetLimit; - case ThrottleOutPacketType.State: - return StateLimit; case ThrottleOutPacketType.Unknown: default: return 0; diff --git a/OpenSim/Region/ClientStack/LindenUDP/TokenBucket.cs b/OpenSim/Region/ClientStack/LindenUDP/TokenBucket.cs index 4ee6d3a30a..2ec79abd16 100644 --- a/OpenSim/Region/ClientStack/LindenUDP/TokenBucket.cs +++ b/OpenSim/Region/ClientStack/LindenUDP/TokenBucket.cs @@ -29,6 +29,8 @@ using System; using System.Collections; using System.Collections.Generic; using System.Reflection; +using OpenSim.Framework; + using log4net; namespace OpenSim.Region.ClientStack.LindenUDP @@ -177,7 +179,7 @@ namespace OpenSim.Region.ClientStack.LindenUDP RequestedDripRate = dripRate; // TotalDripRequest = dripRate; // this will be overwritten when a child node registers // MaxBurst = (Int64)((double)dripRate * m_quantumsPerBurst); - m_lastDrip = Environment.TickCount & Int32.MaxValue; + m_lastDrip = Util.EnvironmentTickCount(); } #endregion Constructor @@ -211,12 +213,15 @@ namespace OpenSim.Region.ClientStack.LindenUDP /// public void RegisterRequest(TokenBucket child, Int64 request) { - m_children[child] = request; - // m_totalDripRequest = m_children.Values.Sum(); + lock (m_children) + { + m_children[child] = request; + // m_totalDripRequest = m_children.Values.Sum(); - m_totalDripRequest = 0; - foreach (KeyValuePair cref in m_children) - m_totalDripRequest += cref.Value; + 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) @@ -229,12 +234,16 @@ namespace OpenSim.Region.ClientStack.LindenUDP /// public void UnregisterRequest(TokenBucket child) { - m_children.Remove(child); - // m_totalDripRequest = m_children.Values.Sum(); + lock (m_children) + { + m_children.Remove(child); + // m_totalDripRequest = m_children.Values.Sum(); - m_totalDripRequest = 0; - foreach (KeyValuePair cref in m_children) - m_totalDripRequest += cref.Value; + 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) @@ -297,10 +306,8 @@ namespace OpenSim.Region.ClientStack.LindenUDP // Determine the interval over which we are adding tokens, never add // more than a single quantum of tokens - Int32 now = Environment.TickCount & Int32.MaxValue; - Int32 deltaMS = Math.Min(now - m_lastDrip, m_ticksPerQuantum); - - m_lastDrip = now; + Int32 deltaMS = Math.Min(Util.EnvironmentTickCountSubtract(m_lastDrip), m_ticksPerQuantum); + m_lastDrip = Util.EnvironmentTickCount(); // This can be 0 in the very unusual case that the timer wrapped // It can be 0 if we try add tokens at a sub-tick rate @@ -315,10 +322,12 @@ namespace OpenSim.Region.ClientStack.LindenUDP { private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); - // - // The minimum rate for flow control. - // - protected const Int64 m_minimumFlow = m_minimumDripRate * 10; + /// + /// The minimum rate for flow control. Minimum drip rate is one + /// packet per second. Open the throttle to 15 packets per second + /// or about 160kbps. + /// + protected const Int64 m_minimumFlow = m_minimumDripRate * 15; // // The maximum rate for flow control. Drip rate can never be @@ -331,6 +340,8 @@ namespace OpenSim.Region.ClientStack.LindenUDP set { m_maxDripRate = (value == 0 ? 0 : Math.Max(value,m_minimumFlow)); } } + private bool m_enabled = false; + // // // @@ -348,9 +359,16 @@ namespace OpenSim.Region.ClientStack.LindenUDP // // // - public AdaptiveTokenBucket(TokenBucket parent, Int64 maxDripRate) : base(parent,m_minimumFlow) + public AdaptiveTokenBucket(TokenBucket parent, Int64 maxDripRate, bool enabled) : base(parent,maxDripRate) { - MaxDripRate = maxDripRate; + m_enabled = enabled; + + if (m_enabled) + { + m_log.WarnFormat("[TOKENBUCKET] Adaptive throttle enabled"); + MaxDripRate = maxDripRate; + AdjustedDripRate = m_minimumFlow; + } } // @@ -359,7 +377,8 @@ namespace OpenSim.Region.ClientStack.LindenUDP public void ExpirePackets(Int32 count) { // m_log.WarnFormat("[ADAPTIVEBUCKET] drop {0} by {1} expired packets",AdjustedDripRate,count); - AdjustedDripRate = (Int64) (AdjustedDripRate / Math.Pow(2,count)); + if (m_enabled) + AdjustedDripRate = (Int64) (AdjustedDripRate / Math.Pow(2,count)); } // @@ -367,7 +386,8 @@ namespace OpenSim.Region.ClientStack.LindenUDP // public void AcknowledgePackets(Int32 count) { - AdjustedDripRate = AdjustedDripRate + count; + if (m_enabled) + AdjustedDripRate = AdjustedDripRate + count; } } } diff --git a/OpenSim/Region/ClientStack/LindenUDP/UnackedPacketCollection.cs b/OpenSim/Region/ClientStack/LindenUDP/UnackedPacketCollection.cs index b1709649f5..793aefe6e5 100644 --- a/OpenSim/Region/ClientStack/LindenUDP/UnackedPacketCollection.cs +++ b/OpenSim/Region/ClientStack/LindenUDP/UnackedPacketCollection.cs @@ -65,7 +65,9 @@ namespace OpenSim.Region.ClientStack.LindenUDP /// Holds packets that need to be added to the unacknowledged list private LocklessQueue m_pendingAdds = new LocklessQueue(); /// Holds information about pending acknowledgements - private LocklessQueue m_pendingRemoves = new LocklessQueue(); + private LocklessQueue m_pendingAcknowledgements = new LocklessQueue(); + /// Holds information about pending removals + private LocklessQueue m_pendingRemoves = new LocklessQueue(); /// /// Add an unacked packet to the collection @@ -83,15 +85,33 @@ namespace OpenSim.Region.ClientStack.LindenUDP /// /// Marks a packet as acknowledged + /// This method is used when an acknowledgement is received from the network for a previously + /// sent packet. Effects of removal this way are to update unacked byte count, adjust RTT + /// and increase throttle to the coresponding client. /// /// Sequence number of the packet to /// acknowledge /// Current value of Environment.TickCount /// This does not immediately acknowledge the packet, it only /// queues the ack so it can be handled in a thread-safe way later - public void Remove(uint sequenceNumber, int currentTime, bool fromResend) + public void Acknowledge(uint sequenceNumber, int currentTime, bool fromResend) { - m_pendingRemoves.Enqueue(new PendingAck(sequenceNumber, currentTime, fromResend)); + m_pendingAcknowledgements.Enqueue(new PendingAck(sequenceNumber, currentTime, fromResend)); + } + + /// + /// Marks a packet as no longer needing acknowledgement without a received acknowledgement. + /// This method is called when a packet expires and we no longer need an acknowledgement. + /// When some reliable packet types expire, they are handled in a way other than simply + /// resending them. The only effect of removal this way is to update unacked byte count. + /// + /// Sequence number of the packet to + /// acknowledge + /// The does not immediately remove the packet, it only queues the removal + /// so it can be handled in a thread safe way later + public void Remove(uint sequenceNumber) + { + m_pendingRemoves.Enqueue(sequenceNumber); } /// @@ -151,15 +171,15 @@ namespace OpenSim.Region.ClientStack.LindenUDP m_packets[pendingAdd.SequenceNumber] = pendingAdd; // Process all the pending removes, including updating statistics and round-trip times - PendingAck pendingRemove; - OutgoingPacket ackedPacket; - while (m_pendingRemoves.TryDequeue(out pendingRemove)) + PendingAck pendingAcknowledgement; + while (m_pendingAcknowledgements.TryDequeue(out pendingAcknowledgement)) { - if (m_packets.TryGetValue(pendingRemove.SequenceNumber, out ackedPacket)) + OutgoingPacket ackedPacket; + if (m_packets.TryGetValue(pendingAcknowledgement.SequenceNumber, out ackedPacket)) { if (ackedPacket != null) { - m_packets.Remove(pendingRemove.SequenceNumber); + m_packets.Remove(pendingAcknowledgement.SequenceNumber); // As with other network applications, assume that an acknowledged packet is an // indication that the network can handle a little more load, speed up the transmission @@ -168,16 +188,32 @@ namespace OpenSim.Region.ClientStack.LindenUDP // Update stats Interlocked.Add(ref ackedPacket.Client.UnackedBytes, -ackedPacket.Buffer.DataLength); - if (!pendingRemove.FromResend) + if (!pendingAcknowledgement.FromResend) { // Calculate the round-trip time for this packet and its ACK - int rtt = pendingRemove.RemoveTime - ackedPacket.TickCount; + int rtt = pendingAcknowledgement.RemoveTime - ackedPacket.TickCount; if (rtt > 0) ackedPacket.Client.UpdateRoundTrip(rtt); } } } } + + uint pendingRemove; + while(m_pendingRemoves.TryDequeue(out pendingRemove)) + { + OutgoingPacket removedPacket; + if (m_packets.TryGetValue(pendingRemove, out removedPacket)) + { + if (removedPacket != null) + { + m_packets.Remove(pendingRemove); + + // Update stats + Interlocked.Add(ref removedPacket.Client.UnackedBytes, -removedPacket.Buffer.DataLength); + } + } + } } } -} \ No newline at end of file +} diff --git a/OpenSim/Region/Framework/Scenes/Prioritizer.cs b/OpenSim/Region/Framework/Scenes/Prioritizer.cs index 17b2da13dd..ef78f0fdc9 100644 --- a/OpenSim/Region/Framework/Scenes/Prioritizer.cs +++ b/OpenSim/Region/Framework/Scenes/Prioritizer.cs @@ -88,7 +88,7 @@ namespace OpenSim.Region.Framework.Scenes // If this is an update for our own avatar give it the highest priority if (client.AgentId == entity.UUID) - return PriorityQueue.ImmediateQueue; + return 0; uint priority; @@ -119,16 +119,40 @@ namespace OpenSim.Region.Framework.Scenes private uint GetPriorityByTime(IClientAPI client, ISceneEntity entity) { - return 1; + // And anything attached to this avatar gets top priority as well + if (entity is SceneObjectPart) + { + SceneObjectPart sop = (SceneObjectPart)entity; + if (sop.ParentGroup.RootPart.IsAttachment && client.AgentId == sop.ParentGroup.RootPart.AttachedAvatar) + return 1; + } + + return PriorityQueue.NumberOfImmediateQueues; // first queue past the immediate queues } private uint GetPriorityByDistance(IClientAPI client, ISceneEntity entity) { + // And anything attached to this avatar gets top priority as well + if (entity is SceneObjectPart) + { + SceneObjectPart sop = (SceneObjectPart)entity; + if (sop.ParentGroup.RootPart.IsAttachment && client.AgentId == sop.ParentGroup.RootPart.AttachedAvatar) + return 1; + } + return ComputeDistancePriority(client,entity,false); } private uint GetPriorityByFrontBack(IClientAPI client, ISceneEntity entity) { + // And anything attached to this avatar gets top priority as well + if (entity is SceneObjectPart) + { + SceneObjectPart sop = (SceneObjectPart)entity; + if (sop.ParentGroup.RootPart.IsAttachment && client.AgentId == sop.ParentGroup.RootPart.AttachedAvatar) + return 1; + } + return ComputeDistancePriority(client,entity,true); } @@ -205,8 +229,10 @@ namespace OpenSim.Region.Framework.Scenes // And convert the distance to a priority queue, this computation gives queues // at 10, 20, 40, 80, 160, 320, 640, and 1280m - uint pqueue = 1; - for (int i = 0; i < 8; i++) + uint pqueue = PriorityQueue.NumberOfImmediateQueues; + uint queues = PriorityQueue.NumberOfQueues - PriorityQueue.NumberOfImmediateQueues; + + for (int i = 0; i < queues - 1; i++) { if (distance < 10 * Math.Pow(2.0,i)) break; diff --git a/OpenSim/Region/Framework/Scenes/ScenePresence.cs b/OpenSim/Region/Framework/Scenes/ScenePresence.cs index 3a5b05de8e..a6bdc593c8 100644 --- a/OpenSim/Region/Framework/Scenes/ScenePresence.cs +++ b/OpenSim/Region/Framework/Scenes/ScenePresence.cs @@ -3049,18 +3049,17 @@ namespace OpenSim.Region.Framework.Scenes cadu.GroupAccess = 0; cadu.Position = AbsolutePosition; cadu.regionHandle = m_rootRegionHandle; - float multiplier = 1; - int innacurateNeighbors = m_scene.GetInaccurateNeighborCount(); - if (innacurateNeighbors != 0) - { - multiplier = 1f / (float)innacurateNeighbors; - } - if (multiplier <= 0f) - { - multiplier = 0.25f; - } - //m_log.Info("[NeighborThrottle]: " + m_scene.GetInaccurateNeighborCount().ToString() + " - m: " + multiplier.ToString()); + // Throttles + float multiplier = 1; + int childRegions = m_knownChildRegions.Count; + if (childRegions != 0) + multiplier = 1f / childRegions; + + // Minimum throttle for a child region is 1/4 of the root region throttle + if (multiplier <= 0.25f) + multiplier = 0.25f; + cadu.throttles = ControllingClient.GetThrottlesPacked(multiplier); cadu.Velocity = Velocity; @@ -3456,16 +3455,14 @@ namespace OpenSim.Region.Framework.Scenes // Throttles float multiplier = 1; - int innacurateNeighbors = m_scene.GetInaccurateNeighborCount(); - if (innacurateNeighbors != 0) - { - multiplier = 1f / innacurateNeighbors; - } - if (multiplier <= 0f) - { + int childRegions = m_knownChildRegions.Count; + if (childRegions != 0) + multiplier = 1f / childRegions; + + // Minimum throttle for a child region is 1/4 of the root region throttle + if (multiplier <= 0.25f) multiplier = 0.25f; - } - //m_log.Info("[NeighborThrottle]: " + m_scene.GetInaccurateNeighborCount().ToString() + " - m: " + multiplier.ToString()); + cAgent.Throttles = ControllingClient.GetThrottlesPacked(multiplier); cAgent.HeadRotation = m_headrotation; diff --git a/OpenSim/Region/OptionalModules/Agent/UDP/Linden/LindenUDPInfoModule.cs b/OpenSim/Region/OptionalModules/Agent/UDP/Linden/LindenUDPInfoModule.cs index 6a24cc1092..db17d8faf7 100644 --- a/OpenSim/Region/OptionalModules/Agent/UDP/Linden/LindenUDPInfoModule.cs +++ b/OpenSim/Region/OptionalModules/Agent/UDP/Linden/LindenUDPInfoModule.cs @@ -81,6 +81,14 @@ namespace OpenSim.Region.CoreModules.UDP.Linden lock (m_scenes) m_scenes[scene.RegionInfo.RegionID] = scene; + scene.AddCommand( + this, "show pqueues", + "show pqueues [full]", + "Show priority queue data for each client", + "Without the 'full' option, only root agents are shown." + + " With the 'full' option child agents are also shown.", + ShowPQueuesReport); + scene.AddCommand( this, "show queues", "show queues [full]", @@ -119,6 +127,11 @@ namespace OpenSim.Region.CoreModules.UDP.Linden // m_log.DebugFormat("[LINDEN UDP INFO MODULE]: REGION {0} LOADED", scene.RegionInfo.RegionName); } + protected void ShowPQueuesReport(string module, string[] cmd) + { + MainConsole.Instance.Output(GetPQueuesReport(cmd)); + } + protected void ShowQueuesReport(string module, string[] cmd) { MainConsole.Instance.Output(GetQueuesReport(cmd)); @@ -155,6 +168,80 @@ namespace OpenSim.Region.CoreModules.UDP.Linden ""); } + + /// + /// Generate UDP Queue data report for each client + /// + /// + /// + protected string GetPQueuesReport(string[] showParams) + { + bool showChildren = false; + string pname = ""; + + if (showParams.Length > 2 && showParams[2] == "full") + showChildren = true; + else if (showParams.Length > 3) + pname = showParams[2] + " " + showParams[3]; + + StringBuilder report = new StringBuilder(); + + int columnPadding = 2; + int maxNameLength = 18; + int maxRegionNameLength = 14; + int maxTypeLength = 4; + int totalInfoFieldsLength = maxNameLength + columnPadding + maxRegionNameLength + columnPadding + maxTypeLength + columnPadding; + + report.Append(GetColumnEntry("User", maxNameLength, columnPadding)); + report.Append(GetColumnEntry("Region", maxRegionNameLength, columnPadding)); + report.Append(GetColumnEntry("Type", maxTypeLength, columnPadding)); + + report.AppendFormat( + "{0,7} {1,7} {2,7} {3,7} {4,7} {5,7} {6,7} {7,7} {8,7} {9,7} {10,7} {11,7}\n", + "Pri 0", + "Pri 1", + "Pri 2", + "Pri 3", + "Pri 4", + "Pri 5", + "Pri 6", + "Pri 7", + "Pri 8", + "Pri 9", + "Pri 10", + "Pri 11"); + + lock (m_scenes) + { + foreach (Scene scene in m_scenes.Values) + { + scene.ForEachClient( + delegate(IClientAPI client) + { + if (client is LLClientView) + { + bool isChild = scene.PresenceChildStatus(client.AgentId); + if (isChild && !showChildren) + return; + + string name = client.Name; + if (pname != "" && name != pname) + return; + + string regionName = scene.RegionInfo.RegionName; + + report.Append(GetColumnEntry(name, maxNameLength, columnPadding)); + report.Append(GetColumnEntry(regionName, maxRegionNameLength, columnPadding)); + report.Append(GetColumnEntry(isChild ? "Cd" : "Rt", maxTypeLength, columnPadding)); + report.AppendLine(((LLClientView)client).EntityUpdateQueue.ToString()); + } + }); + } + } + + return report.ToString(); + } + /// /// Generate UDP Queue data report for each client /// @@ -163,10 +250,13 @@ namespace OpenSim.Region.CoreModules.UDP.Linden protected string GetQueuesReport(string[] showParams) { bool showChildren = false; + string pname = ""; if (showParams.Length > 2 && showParams[2] == "full") showChildren = true; - + else if (showParams.Length > 3) + pname = showParams[2] + " " + showParams[3]; + StringBuilder report = new StringBuilder(); int columnPadding = 2; @@ -224,6 +314,9 @@ namespace OpenSim.Region.CoreModules.UDP.Linden return; string name = client.Name; + if (pname != "" && name != pname) + return; + string regionName = scene.RegionInfo.RegionName; report.Append(GetColumnEntry(name, maxNameLength, columnPadding)); @@ -249,10 +342,13 @@ namespace OpenSim.Region.CoreModules.UDP.Linden protected string GetThrottlesReport(string[] showParams) { bool showChildren = false; + string pname = ""; if (showParams.Length > 2 && showParams[2] == "full") showChildren = true; - + else if (showParams.Length > 3) + pname = showParams[2] + " " + showParams[3]; + StringBuilder report = new StringBuilder(); int columnPadding = 2; @@ -302,7 +398,7 @@ namespace OpenSim.Region.CoreModules.UDP.Linden if (client is LLClientView) { LLClientView llClient = client as LLClientView; - + if (firstClient) { report.AppendLine(GetServerThrottlesReport(llClient.UDPServer)); @@ -314,6 +410,9 @@ namespace OpenSim.Region.CoreModules.UDP.Linden return; string name = client.Name; + if (pname != "" && name != pname) + return; + string regionName = scene.RegionInfo.RegionName; LLUDPClient llUdpClient = llClient.UDPClient; @@ -352,7 +451,7 @@ namespace OpenSim.Region.CoreModules.UDP.Linden int maxRegionNameLength = 14; int maxTypeLength = 4; - string name = "SERVER AGENT LIMITS"; + string name = "SERVER AGENT RATES"; report.Append(GetColumnEntry(name, maxNameLength, columnPadding)); report.Append(GetColumnEntry("-", maxRegionNameLength, columnPadding)); @@ -362,13 +461,13 @@ namespace OpenSim.Region.CoreModules.UDP.Linden report.AppendFormat( "{0,7} {1,8} {2,7} {3,7} {4,7} {5,7} {6,9} {7,7}", (throttleRates.Total * 8) / 1000, - (throttleRates.ResendLimit * 8) / 1000, - (throttleRates.LandLimit * 8) / 1000, - (throttleRates.WindLimit * 8) / 1000, - (throttleRates.CloudLimit * 8) / 1000, - (throttleRates.TaskLimit * 8) / 1000, - (throttleRates.TextureLimit * 8) / 1000, - (throttleRates.AssetLimit * 8) / 1000); + (throttleRates.Resend * 8) / 1000, + (throttleRates.Land * 8) / 1000, + (throttleRates.Wind * 8) / 1000, + (throttleRates.Cloud * 8) / 1000, + (throttleRates.Task * 8) / 1000, + (throttleRates.Texture * 8) / 1000, + (throttleRates.Asset * 8) / 1000); return report.ToString(); } diff --git a/bin/OpenSimDefaults.ini b/bin/OpenSimDefaults.ini index 4c0c79470d..43551bdcb1 100644 --- a/bin/OpenSimDefaults.ini +++ b/bin/OpenSimDefaults.ini @@ -362,30 +362,24 @@ ; ;client_throttle_max_bps = 196608 - ; Per-client bytes per second rates for the various throttle categories. - ; These are default values that will be overriden by clients + ; Adaptive throttling attempts to limit network overload when multiple + ; clients login by starting each connection more slowly. Disabled by + ; default ; - ;resend_default = 12500 - ;land_default = 1000 - ;wind_default = 1000 - ;cloud_default = 1000 - ;task_default = 1000 - ;texture_default = 1000 - ;asset_default = 1000 - ;state_default = 1000 + ;enable_adaptive_throttles = true - ; Per-client maximum burst rates in bytes per second for the various - ; throttle categories. These are default values that will be overriden by - ; clients - ; - ;resend_limit = 18750 - ;land_limit = 29750 - ;wind_limit = 18750 - ;cloud_limit = 18750 - ;task_limit = 18750 - ;texture_limit = 55750 - ;asset_limit = 27500 - ;state_limit = 37000 + ; Per-client bytes per second rates for the various throttle categories. + ; These are default values that will be overriden by clients. These + ; defaults are approximately equivalent to the throttles set by the Imprudence + ; viewer when maximum bandwidth is set to 350kbps + + ;resend_default = 6625 + ;land_default = 9125 + ;wind_default = 1750 + ;cloud_default = 1750 + ;task_default = 18500 + ;texture_default = 18500 + ;asset_default = 10500 ; Configures how ObjectUpdates are aggregated. These numbers ; do not literally mean how many updates will be put in each