MetricsCollector.cs 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223
  1. using System;
  2. using System.Diagnostics;
  3. namespace OpenSim.Framework
  4. {
  5. /// <summary>
  6. /// A MetricsCollector for 'long' values.
  7. /// </summary>
  8. public class MetricsCollectorLong : MetricsCollector<long>
  9. {
  10. public MetricsCollectorLong(int windowSize, int numBuckets)
  11. : base(windowSize, numBuckets)
  12. {
  13. }
  14. protected override long GetZero() { return 0; }
  15. protected override long Add(long a, long b) { return a + b; }
  16. }
  17. /// <summary>
  18. /// A MetricsCollector for time spans.
  19. /// </summary>
  20. public class MetricsCollectorTime : MetricsCollectorLong
  21. {
  22. public MetricsCollectorTime(int windowSize, int numBuckets)
  23. : base(windowSize, numBuckets)
  24. {
  25. }
  26. public void AddSample(Stopwatch timer)
  27. {
  28. long ticks = timer.ElapsedTicks;
  29. if (ticks > 0)
  30. AddSample(ticks);
  31. }
  32. public TimeSpan GetSumTime()
  33. {
  34. return TimeSpan.FromMilliseconds((GetSum() * 1000) / Stopwatch.Frequency);
  35. }
  36. }
  37. struct MetricsBucket<T>
  38. {
  39. public T value;
  40. public int count;
  41. }
  42. /// <summary>
  43. /// Collects metrics in a sliding window.
  44. /// </summary>
  45. /// <remarks>
  46. /// MetricsCollector provides the current Sum of the metrics that it collects. It can easily be extended
  47. /// to provide the Average, too. It uses a sliding window to keep these values current.
  48. ///
  49. /// This class is not thread-safe.
  50. ///
  51. /// Subclass MetricsCollector to have it use a concrete value type. Override the abstract methods.
  52. /// </remarks>
  53. public abstract class MetricsCollector<T>
  54. {
  55. private int bucketSize; // e.g. 3,000 ms
  56. private MetricsBucket<T>[] buckets;
  57. private int NumBuckets { get { return buckets.Length; } }
  58. // The number of the current bucket, if we had an infinite number of buckets and didn't have to wrap around
  59. long curBucketGlobal;
  60. // The total of all the buckets
  61. T totalSum;
  62. int totalCount;
  63. /// <summary>
  64. /// Returns the default (zero) value.
  65. /// </summary>
  66. /// <returns></returns>
  67. protected abstract T GetZero();
  68. /// <summary>
  69. /// Adds two values.
  70. /// </summary>
  71. protected abstract T Add(T a, T b);
  72. /// <summary>
  73. /// Creates a MetricsCollector.
  74. /// </summary>
  75. /// <param name="windowSize">The period of time over which to collect the metrics, in ms. E.g.: 30,000.</param>
  76. /// <param name="numBuckets">The number of buckets to divide the samples into. E.g.: 10. Using more buckets
  77. /// smooths the jarring that occurs whenever we drop an old bucket, but uses more memory.</param>
  78. public MetricsCollector(int windowSize, int numBuckets)
  79. {
  80. bucketSize = windowSize / numBuckets;
  81. buckets = new MetricsBucket<T>[numBuckets];
  82. Reset();
  83. }
  84. public void Reset()
  85. {
  86. ZeroBuckets(0, NumBuckets);
  87. curBucketGlobal = GetNow() / bucketSize;
  88. totalSum = GetZero();
  89. totalCount = 0;
  90. }
  91. public void AddSample(T sample)
  92. {
  93. MoveWindow();
  94. int curBucket = (int)(curBucketGlobal % NumBuckets);
  95. buckets[curBucket].value = Add(buckets[curBucket].value, sample);
  96. buckets[curBucket].count++;
  97. totalSum = Add(totalSum, sample);
  98. totalCount++;
  99. }
  100. /// <summary>
  101. /// Returns the total values in the collection window.
  102. /// </summary>
  103. public T GetSum()
  104. {
  105. // It might have been a while since we last added a sample, so we may need to adjust the window
  106. MoveWindow();
  107. return totalSum;
  108. }
  109. /// <summary>
  110. /// Returns the current time in ms.
  111. /// </summary>
  112. private long GetNow()
  113. {
  114. return DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond;
  115. }
  116. /// <summary>
  117. /// Clears the values in buckets [offset, offset+num)
  118. /// </summary>
  119. private void ZeroBuckets(int offset, int num)
  120. {
  121. for (int i = 0; i < num; i++)
  122. {
  123. buckets[offset + i].value = GetZero();
  124. buckets[offset + i].count = 0;
  125. }
  126. }
  127. /// <summary>
  128. /// Adjusts the buckets so that the "current bucket" corresponds to the current time.
  129. /// This may require dropping old buckets.
  130. /// </summary>
  131. /// <remarks>
  132. /// This method allows for the possibility that we don't get new samples for each bucket, so the
  133. /// new bucket may be some distance away from the last used bucket.
  134. /// </remarks>
  135. private void MoveWindow()
  136. {
  137. long newBucketGlobal = GetNow() / bucketSize;
  138. long bucketsDistance = newBucketGlobal - curBucketGlobal;
  139. if (bucketsDistance == 0)
  140. {
  141. // We're still on the same bucket as before
  142. return;
  143. }
  144. if (bucketsDistance >= NumBuckets)
  145. {
  146. // Discard everything
  147. Reset();
  148. return;
  149. }
  150. int curBucket = (int)(curBucketGlobal % NumBuckets);
  151. int newBucket = (int)(newBucketGlobal % NumBuckets);
  152. // Clear all the buckets in this range: (cur, new]
  153. int numToClear = (int)bucketsDistance;
  154. if (curBucket < NumBuckets - 1)
  155. {
  156. // Clear buckets at the end of the window
  157. int num = Math.Min((int)bucketsDistance, NumBuckets - (curBucket + 1));
  158. ZeroBuckets(curBucket + 1, num);
  159. numToClear -= num;
  160. }
  161. if (numToClear > 0)
  162. {
  163. // Clear buckets at the beginning of the window
  164. ZeroBuckets(0, numToClear);
  165. }
  166. // Move the "current bucket" pointer
  167. curBucketGlobal = newBucketGlobal;
  168. RecalcTotal();
  169. }
  170. private void RecalcTotal()
  171. {
  172. totalSum = GetZero();
  173. totalCount = 0;
  174. for (int i = 0; i < NumBuckets; i++)
  175. {
  176. totalSum = Add(totalSum, buckets[i].value);
  177. totalCount += buckets[i].count;
  178. }
  179. }
  180. }
  181. }