From a3bed1fbcbcbe19681aa57733f260ef647a33d8e Mon Sep 17 00:00:00 2001 From: Oren Hurvitz Date: Mon, 27 Jul 2015 12:16:21 +0300 Subject: Report "Script Execution Time" as the time spent executing the script in the last 30 seconds. Use a sliding window to calculate this. Notes: - This metric provides a better indication of which scripts are taking up a lot of CPU (and therefore should be optimized). - Previously the execution time was reset to 0 in every new measurement period, causing the reported time to fluctuate for no reason. This has been fixed by using a sliding window. --- OpenSim/Framework/MetricsCollector.cs | 223 ++++++++++++++++++++++++++++++++++ 1 file changed, 223 insertions(+) create mode 100644 OpenSim/Framework/MetricsCollector.cs (limited to 'OpenSim/Framework/MetricsCollector.cs') diff --git a/OpenSim/Framework/MetricsCollector.cs b/OpenSim/Framework/MetricsCollector.cs new file mode 100644 index 0000000..c8f4a33 --- /dev/null +++ b/OpenSim/Framework/MetricsCollector.cs @@ -0,0 +1,223 @@ +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; + } + } + + } +} -- cgit v1.1