/Aurora/ClientStack/TokenBucket.cs

https://bitbucket.org/VirtualReality/software-testing · C# · 250 lines · 94 code · 24 blank · 132 comment · 11 complexity · 47af40ebdb290becee39ebc6a8e6b543 MD5 · raw file

  1. /*
  2. * Copyright (c) Contributors, http://aurora-sim.org/, http://opensimulator.org/
  3. * See CONTRIBUTORS.TXT for a full list of copyright holders.
  4. *
  5. * Redistribution and use in source and binary forms, with or without
  6. * modification, are permitted provided that the following conditions are met:
  7. * * Redistributions of source code must retain the above copyright
  8. * notice, this list of conditions and the following disclaimer.
  9. * * Redistributions in binary form must reproduce the above copyright
  10. * notice, this list of conditions and the following disclaimer in the
  11. * documentation and/or other materials provided with the distribution.
  12. * * Neither the name of the Aurora-Sim Project nor the
  13. * names of its contributors may be used to endorse or promote products
  14. * derived from this software without specific prior written permission.
  15. *
  16. * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
  17. * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
  18. * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  19. * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
  20. * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
  21. * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  22. * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
  23. * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  24. * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  25. * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  26. */
  27. using System;
  28. namespace Aurora.ClientStack
  29. {
  30. /// <summary>
  31. /// A hierarchical token bucket for bandwidth throttling. See
  32. /// http://en.wikipedia.org/wiki/Token_bucket for more information
  33. /// </summary>
  34. public class TokenBucket
  35. {
  36. /// <summary>
  37. /// Parent bucket to this bucket, or null if this is a root
  38. /// bucket
  39. /// </summary>
  40. private readonly TokenBucket parent;
  41. /// <summary>
  42. /// Number of tokens currently in the bucket
  43. /// </summary>
  44. private int content;
  45. /// <summary>
  46. /// Time of the last drip, in system ticks
  47. /// </summary>
  48. private int lastDrip;
  49. /// <summary>
  50. /// Size of the bucket in bytes. If zero, the bucket has
  51. /// infinite capacity
  52. /// </summary>
  53. private int maxBurst;
  54. /// <summary>
  55. /// Rate that the bucket fills, in bytes per millisecond. If
  56. /// zero, the bucket always remains full
  57. /// </summary>
  58. private float tokensPerMS;
  59. #region Properties
  60. /// <summary>
  61. /// The parent bucket of this bucket, or null if this bucket has no
  62. /// parent. The parent bucket will limit the aggregate bandwidth of all
  63. /// of its children buckets
  64. /// </summary>
  65. public TokenBucket Parent
  66. {
  67. get { return parent; }
  68. }
  69. /// <summary>
  70. /// Maximum burst rate in bytes per second. This is the maximum number
  71. /// of tokens that can accumulate in the bucket at any one time
  72. /// </summary>
  73. public int MaxBurst
  74. {
  75. get { return maxBurst; }
  76. set { maxBurst = (value >= 0 ? value : 0); }
  77. }
  78. /// <summary>
  79. /// The speed limit of this bucket in bytes per second. This is the
  80. /// number of tokens that are added to the bucket per second
  81. /// </summary>
  82. /// <remarks>
  83. /// Tokens are added to the bucket any time
  84. /// <seealso>
  85. /// <cref>RemoveTokens</cref>
  86. /// </seealso>
  87. /// is called, at the granularity of
  88. /// the system tick interval (typically around 15-22ms)
  89. /// </remarks>
  90. public float DripRate
  91. {
  92. get { return tokensPerMS*1000; }
  93. set
  94. {
  95. if (value == 0)
  96. tokensPerMS = 0;
  97. else
  98. {
  99. float bpms = (int) (value/1000.0f);
  100. tokensPerMS = bpms <= 0.5f ? .5f : bpms;
  101. }
  102. }
  103. }
  104. /// <summary>
  105. /// The speed limit of this bucket in bytes per millisecond
  106. /// </summary>
  107. public float DripPerMS
  108. {
  109. get { return tokensPerMS; }
  110. }
  111. /// <summary>
  112. /// The number of bytes that can be sent at this moment. This is the
  113. /// current number of tokens in the bucket
  114. /// <remarks>
  115. /// If this bucket has a parent bucket that does not have
  116. /// enough tokens for a request,
  117. /// <seealso>
  118. /// <cref>RemoveTokens</cref>
  119. /// </seealso>
  120. /// will
  121. /// return false regardless of the content of this bucket
  122. /// </remarks>
  123. /// </summary>
  124. public int Content
  125. {
  126. get { return content; }
  127. }
  128. #endregion Properties
  129. /// <summary>
  130. /// Default constructor
  131. /// </summary>
  132. /// <param name="parent">
  133. /// Parent bucket if this is a child bucket, or
  134. /// null if this is a root bucket
  135. /// </param>
  136. /// <param name="maxBurst">
  137. /// Maximum size of the bucket in bytes, or
  138. /// zero if this bucket has no maximum capacity
  139. /// </param>
  140. /// <param name="dripRate">
  141. /// Rate that the bucket fills, in bytes per
  142. /// second. If zero, the bucket always remains full
  143. /// </param>
  144. public TokenBucket(TokenBucket parent, int maxBurst, int dripRate)
  145. {
  146. this.parent = parent;
  147. MaxBurst = maxBurst;
  148. DripRate = dripRate;
  149. lastDrip = Environment.TickCount & Int32.MaxValue;
  150. }
  151. /// <summary>
  152. /// Remove a given number of tokens from the bucket
  153. /// </summary>
  154. /// <param name="amount">Number of tokens to remove from the bucket</param>
  155. /// <returns>
  156. /// True if the requested number of tokens were removed from
  157. /// the bucket, otherwise false
  158. /// </returns>
  159. public bool RemoveTokens(int amount)
  160. {
  161. bool dummy;
  162. return RemoveTokens(amount, out dummy);
  163. }
  164. /// <summary>
  165. /// Remove a given number of tokens from the bucket
  166. /// </summary>
  167. /// <param name="amount">Number of tokens to remove from the bucket</param>
  168. /// <param name="dripSucceeded">
  169. /// True if tokens were added to the bucket
  170. /// during this call, otherwise false
  171. /// </param>
  172. /// <returns>
  173. /// True if the requested number of tokens were removed from
  174. /// the bucket, otherwise false
  175. /// </returns>
  176. public bool RemoveTokens(int amount, out bool dripSucceeded)
  177. {
  178. if (maxBurst == 0)
  179. {
  180. dripSucceeded = true;
  181. return true;
  182. }
  183. dripSucceeded = Drip();
  184. if (content - amount >= 0)
  185. {
  186. if (parent != null && !parent.RemoveTokens(amount))
  187. return false;
  188. content -= amount;
  189. return true;
  190. }
  191. return false;
  192. }
  193. /// <summary>
  194. /// Add tokens to the bucket over time. The number of tokens added each
  195. /// call depends on the length of time that has passed since the last
  196. /// call to Drip
  197. /// </summary>
  198. /// <returns>True if tokens were added to the bucket, otherwise false</returns>
  199. public bool Drip()
  200. {
  201. if (tokensPerMS <= 0)
  202. {
  203. content = maxBurst;
  204. return true;
  205. }
  206. int now = Environment.TickCount & Int32.MaxValue;
  207. int deltaMS = now - lastDrip;
  208. if (deltaMS <= 0)
  209. {
  210. if (deltaMS < 0)
  211. lastDrip = now;
  212. return false;
  213. }
  214. int dripAmount = (int) (deltaMS*tokensPerMS);
  215. content = Math.Min(content + dripAmount, maxBurst);
  216. lastDrip = now;
  217. /*
  218. if (dripAmount < 0 || content < 0)
  219. // sim has been idle for too long, integer has overflown
  220. // previous calculation is meaningless, let's put it at correct max
  221. content = maxBurst;
  222. */
  223. return true;
  224. }
  225. }
  226. }