using System; using System.Diagnostics; namespace OpenSim.Framework { /// /// A MetricsCollector for 'long' values. /// public class MetricsCollectorLong : MetricsCollector { public MetricsCollectorLong(int windowSize, int numBuckets) : base(windowSize, numBuckets) { } protected override long GetZero() { return 0; } protected override long Add(long a, long b) { return a + b; } } /// /// A MetricsCollector for time spans. /// public class MetricsCollectorTime : MetricsCollectorLong { public MetricsCollectorTime(int windowSize, int numBuckets) : base(windowSize, numBuckets) { } public void AddSample(Stopwatch timer) { long ticks = timer.ElapsedTicks; if (ticks > 0) AddSample(ticks); } public TimeSpan GetSumTime() { return TimeSpan.FromMilliseconds((GetSum() * 1000) / Stopwatch.Frequency); } } struct MetricsBucket { public T value; public int count; } /// /// Collects metrics in a sliding window. /// /// /// MetricsCollector provides the current Sum of the metrics that it collects. It can easily be extended /// to provide the Average, too. It uses a sliding window to keep these values current. /// /// This class is not thread-safe. /// /// Subclass MetricsCollector to have it use a concrete value type. Override the abstract methods. /// public abstract class MetricsCollector { private int bucketSize; // e.g. 3,000 ms private MetricsBucket[] buckets; private int NumBuckets { get { return buckets.Length; } } // The number of the current bucket, if we had an infinite number of buckets and didn't have to wrap around long curBucketGlobal; // The total of all the buckets T totalSum; int totalCount; /// /// Returns the default (zero) value. /// /// protected abstract T GetZero(); /// /// Adds two values. /// protected abstract T Add(T a, T b); /// /// Creates a MetricsCollector. /// /// The period of time over which to collect the metrics, in ms. E.g.: 30,000. /// The number of buckets to divide the samples into. E.g.: 10. Using more buckets /// smooths the jarring that occurs whenever we drop an old bucket, but uses more memory. public MetricsCollector(int windowSize, int numBuckets) { bucketSize = windowSize / numBuckets; buckets = new MetricsBucket[numBuckets]; Reset(); } public void Reset() { ZeroBuckets(0, NumBuckets); curBucketGlobal = GetNow() / bucketSize; totalSum = GetZero(); totalCount = 0; } public void AddSample(T sample) { MoveWindow(); int curBucket = (int)(curBucketGlobal % NumBuckets); buckets[curBucket].value = Add(buckets[curBucket].value, sample); buckets[curBucket].count++; totalSum = Add(totalSum, sample); totalCount++; } /// /// Returns the total values in the collection window. /// public T GetSum() { // It might have been a while since we last added a sample, so we may need to adjust the window MoveWindow(); return totalSum; } /// /// Returns the current time in ms. /// private long GetNow() { return DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond; } /// /// Clears the values in buckets [offset, offset+num) /// private void ZeroBuckets(int offset, int num) { for (int i = 0; i < num; i++) { buckets[offset + i].value = GetZero(); buckets[offset + i].count = 0; } } /// /// Adjusts the buckets so that the "current bucket" corresponds to the current time. /// This may require dropping old buckets. /// /// /// This method allows for the possibility that we don't get new samples for each bucket, so the /// new bucket may be some distance away from the last used bucket. /// private void MoveWindow() { long newBucketGlobal = GetNow() / bucketSize; long bucketsDistance = newBucketGlobal - curBucketGlobal; if (bucketsDistance == 0) { // We're still on the same bucket as before return; } if (bucketsDistance >= NumBuckets) { // Discard everything Reset(); return; } int curBucket = (int)(curBucketGlobal % NumBuckets); int newBucket = (int)(newBucketGlobal % NumBuckets); // Clear all the buckets in this range: (cur, new] int numToClear = (int)bucketsDistance; if (curBucket < NumBuckets - 1) { // Clear buckets at the end of the window int num = Math.Min((int)bucketsDistance, NumBuckets - (curBucket + 1)); ZeroBuckets(curBucket + 1, num); numToClear -= num; } if (numToClear > 0) { // Clear buckets at the beginning of the window ZeroBuckets(0, numToClear); } // Move the "current bucket" pointer curBucketGlobal = newBucketGlobal; RecalcTotal(); } private void RecalcTotal() { totalSum = GetZero(); totalCount = 0; for (int i = 0; i < NumBuckets; i++) { totalSum = Add(totalSum, buckets[i].value); totalCount += buckets[i].count; } } } }