#pragma once #include "rbx/atomic.h" #include "rbx/boost.hpp" #include "rbx/rbxTime.h" #include "rbx/Debug.h" #include "rbx/MathUtil.h" #include #include #include #include #include namespace RBX { template class RunningAverage { boost::shared_ptr > buffer; public: RunningAverage(double lerp = 0.05, ValueType initialValue = 0, unsigned int bufferSize = 0) :lerp(lerp), lastSampleValue(initialValue), averageValue(initialValue), averageVariance(0), firstTime(true) { if (bufferSize) buffer.reset(new boost::circular_buffer(bufferSize)); } void sample(ValueType value) { if (isFinite(value)) { sampleValue(value); sampleVariance(value); if (buffer) buffer->push_back(value); } } // Get the current value AverageType value() const { return averageValue; } AverageType variance() const { return averageVariance; } AverageType standard_deviation() const { // TODO: Cache this value? return std::sqrt(averageVariance); } AverageType variance_to_mean_ratio() const { return averageVariance / (averageValue * averageValue); } AverageType coefficient_of_variation() const { return standard_deviation() / averageValue; } // Get the last sampled value ValueType lastSample() const { return lastSampleValue; } template void iter(F& f) const { if (buffer) { for(typename boost::circular_buffer::const_iterator it = buffer->begin(); it != buffer->end(); ++it) { f(*it); } } } void reset(ValueType resetValue = 0) { lastSampleValue = resetValue; averageValue = resetValue; averageVariance = 0; firstTime = true; if (buffer) buffer->clear(); } bool hasSampled() {return !firstTime;} const double lerp; private: ValueType lastSampleValue; AverageType averageValue; AverageType averageVariance; bool firstTime; inline void sampleValue(ValueType value) { averageValue = firstTime ? value : (1.0 - lerp) * averageValue + lerp * (AverageType)value; lastSampleValue = value; firstTime = false; } inline void sampleVariance(ValueType value) { const double diff = value - averageValue; const double variance = diff * diff; averageVariance = (1.0 - lerp) * averageVariance + lerp * variance; } }; template class WindowAverage { protected: boost::circular_buffer buffer; public: struct Stats { Stats(size_t samples, const AverageType& average, const AverageType& variance) : samples(samples), average(average), variance(variance) {}; size_t samples; AverageType average; AverageType variance; }; WindowAverage(size_t maxSamples) : buffer(maxSamples) { } void setMaxSamples(size_t maxSamples) { buffer.set_capacity(maxSamples); } size_t getMaxSamples() const { return buffer.capacity(); } void sample(ValueType value) { buffer.push_back(value); } // calls fonbeforedrop if an item is about to be dropped template void sample(ValueType value, F& fonbeforedrop) { if(buffer.full()) { fonbeforedrop(buffer.front()); } sample(value); } Stats getSanitizedStats(Confidence conf = C90) const { Stats regularStats = getStats(); if(regularStats.samples <= 1) return regularStats; Stats result(0, AverageType(), AverageType()); AverageType std = sqrt(regularStats.variance); for(typename boost::circular_buffer::const_iterator it = buffer.begin(); it != buffer.end(); ++it) { double value = *it; if (IsValueOutlier(value, regularStats.samples, regularStats.average, std, conf)) continue; result.samples++; result.average += value; } result.average /= result.samples; for(typename boost::circular_buffer::const_iterator it = buffer.begin(); it != buffer.end(); ++it) { double value = *it; if (IsValueOutlier(value, regularStats.samples, regularStats.average, std, conf)) continue; AverageType diff = (result.average - value); result.variance = result.variance + diff * diff; } result.variance /= (result.samples - 1); return result; } Stats getStats(size_t samples = ~0) const // get n last frames. { samples = std::min(buffer.size(), samples); Stats result(samples, AverageType(), AverageType()); typename boost::circular_buffer::const_reverse_iterator it; size_t ii; for(it = buffer.rbegin(), ii = 0; ii < samples; ++it, ++ii) { result.average = result.average + *it; } if (samples != 0) { result.average /= samples; } for(it = buffer.rbegin(), ii = 0; ii < samples; ++it, ++ii) { AverageType diff = (result.average - *it); result.variance = result.variance + diff * diff; } if(samples > 1) { result.variance /= (samples -1); } return result; } AverageType getLatest() const // get data from the last frame { return buffer.empty() ? 0 : *(buffer.rbegin()); } template void iter(F& f) const { for(typename boost::circular_buffer::const_iterator it = buffer.begin(); it != buffer.end(); ++it) { f(*it); } } void clear() { buffer.clear(); } size_t size() const { return buffer.size(); } }; // A class that follows the pattern of RunningAverage, but keeps track of step frequency. template class RunningAverageTimeInterval { public: RunningAverageTimeInterval(double lerp = 0.05) :firstTime(true),average(lerp) {} void sample() { if (firstTime) { timer.reset(); firstTime = false; } else { average.sample(timer.reset().seconds()); } } // Get the current value Time::Interval value() const { Time::Interval timeSinceLastSample = timer.delta(); double v = average.value(); if (timeSinceLastSample.seconds() > 2.0 * v) return timeSinceLastSample; else return Time::Interval(v); } double rate() const { double v = value().seconds(); return v>0.0 ? 1.0/v : 0.0; } double variance() const { return average.variance(); } double standard_deviation() const { return average.standard_deviation(); } double variance_to_mean_ratio() const { return average.variance_to_mean_ratio(); } double coefficient_of_variation() const { return average.coefficient_of_variation(); } double getLerp() const { return average.lerp;} Time::Interval lastSample() const { return Time::Interval(average.lastSample()); } private: Timer timer; bool firstTime; RunningAverage<> average; }; struct FOnBeforeDrop { WindowAverage<>& average; Time::Interval& currentWindow; Time::Interval& maxWindow; FOnBeforeDrop(WindowAverage<>& average, Time::Interval& currentWindow, Time::Interval& maxWindow) : average(average), currentWindow(currentWindow), maxWindow(maxWindow) {}; void operator()(double sample) { if(currentWindow.seconds() < maxWindow.seconds()) { // prevent dropping. // grow window size. average.setMaxSamples(average.getMaxSamples() * 2); } else { // allow drop. adjust total counter. currentWindow -= Time::Interval(sample); } } } ; // A class that follows the pattern of RunningAverage, but keeps track of step frequency. template class WindowAverageTimeInterval { public: WindowAverageTimeInterval(Time::Interval maxWindow) :maxWindow(maxWindow), average(16) {} void setMaxWindow(Time::Interval maxWindow) { this->maxWindow = maxWindow; if(maxWindow.seconds() == 0.0) { // special case, release memory average.setMaxSamples(16); } }; Time::Interval getMaxWindow() const { return maxWindow; }; size_t getCapacity() const { return average.getMaxSamples(); }; void sample() { if (firstTime) { timer.reset(); firstTime = false; } else { double dt = timer.reset().seconds(); currentWindow += Time::Interval(dt); // this functor will allow our ring buffer to grow geometrically // as long as it doesn't contain maxWindow worth of interval measurments. FOnBeforeDrop fonbeforedrop(average, currentWindow, maxWindow); average.sample(dt, fonbeforedrop); } } struct Stats { Stats( size_t samples, Time::Interval averagedt, Time::Interval variancedt, Time::Interval totalt, double samplespersecond) : samples(samples) , average(averagedt) , variance(variancedt) , sum(totalt) , samplespersecond(samplespersecond) {}; size_t samples; Time::Interval average; Time::Interval variance; Time::Interval sum; double samplespersecond; }; struct FSum { FSum() : vsum(0.0) {}; double vsum; void operator() (double v) { vsum += v; }; }; Stats getStats(size_t samples = ~0) const { WindowAverage<>::Stats basicstats = average.getStats(samples); FSum fsum; average.iter(fsum); return Stats(basicstats.samples, Time::Interval(basicstats.average), Time::Interval(basicstats.variance), Time::Interval(fsum.vsum), samples / fsum.vsum); } template void iter(F& f) const { average.iter(f); } void clear() { firstTime = true; average.clear(); } size_t size() const { return average.size(); } private: Time::Interval currentWindow; Time::Interval maxWindow; WindowAverage<> average; Timer timer; bool firstTime; }; template class TotalCountTimeInterval { Timer timer; double interval; ValueType valueLastInterval; ValueType valueCurrentInterval; public: TotalCountTimeInterval(double interval = 1.0f) : interval(interval), valueCurrentInterval(0), valueLastInterval(0) {} void increment(ValueType count = 1) { if (timer.delta().seconds() >= interval) { valueLastInterval = valueCurrentInterval; valueCurrentInterval = 0; timer.reset(); } valueCurrentInterval += count; } void decrement(ValueType count = 1) { if (timer.delta().seconds() >= interval) { valueLastInterval = valueCurrentInterval; valueCurrentInterval = 0; timer.reset(); } valueCurrentInterval -= count; } ValueType getCount() const { return (timer.delta().seconds() <= interval) ? valueLastInterval : 0; } }; class ThrottlingHelper { int* eventsPerMinute; int* eventsPerObjectPerMinute; int requestCounter; int maxObjectCount; Time lastTimestamp; public: ThrottlingHelper(int* eventsPerMinute, int* eventsPerObjectPerMinute = NULL) : // Designed to pass FInt eventsPerMinute(eventsPerMinute), eventsPerObjectPerMinute(eventsPerObjectPerMinute), requestCounter(0), maxObjectCount(0), lastTimestamp(Time::nowFast()) { } bool checkLimit(int objectCount = 0) { Time now = Time::nowFast(); if((now - lastTimestamp).seconds() > 60) { requestCounter = 0; lastTimestamp = now; maxObjectCount = 0; } requestCounter++; maxObjectCount = std::max(objectCount, maxObjectCount); int totalCount = *eventsPerMinute; if(eventsPerObjectPerMinute) totalCount += maxObjectCount * (*eventsPerObjectPerMinute); if(requestCounter > totalCount) return false; return true; } }; class BudgetedThrottlingHelper { float currentBudget; public: BudgetedThrottlingHelper() : currentBudget(0.0) {} void addBudget(float budget, float maxBudget) { currentBudget = std::min(currentBudget + budget, maxBudget); } bool checkAndReduceBudget() { if (currentBudget < 0) return false; currentBudget--; return true; } float getBudget() { return currentBudget; }; }; // A class that follows the pattern of RunningAverage, but keeps track of time spent in a cyclical task. template class RunningAverageDutyCycle { public: RunningAverageDutyCycle(double lerp) :time(lerp),interval(lerp) {} RunningAverageDutyCycle(double lerp, int timeBufferSize) :time(lerp, 0, timeBufferSize),interval(lerp) {} void sample(Time::Interval elapsedTime) { interval.sample(); time.sample(elapsedTime.seconds()); } RBX::Time startSample() const { return Time::now(); } void stopSample(RBX::Time start) { sample(Time::now() - start); } double dutyCycle() const { double averageInterval = interval.value().seconds(); double averageTime = time.value(); return averageInterval!=0 ? averageTime/averageInterval : (averageTime>0 ? 1 : 0); } double rate() const { return interval.rate(); } const RunningAverageTimeInterval& stepInterval() const { return interval; } Time::Interval lastStepInterval() { return interval.lastSample(); } const RunningAverage& stepTime() const { return time; } double getIntervalLerp() const { return interval.getLerp(); } private: RunningAverage time; RunningAverageTimeInterval interval; }; // A class that follows the pattern of WindowAverage, but keeps track of time spent in a cyclical task. template class WindowAverageDutyCycle { public: WindowAverageDutyCycle(Time::Interval maxWindow) :time(16),interval(maxWindow) {} void setMaxWindow(Time::Interval maxWindow) { interval.setMaxWindow(maxWindow); if(maxWindow.seconds() == 0.0) { // special case, release memory time.setMaxSamples(16); } }; Time::Interval getMaxWindow() const { return interval.getMaxWindow(); }; void sample(Time::Interval elapsedTime) { interval.sample(); // make sure time's buffer size tracks interval's buffer size. if(time.getMaxSamples() != interval.getCapacity()) { time.setMaxSamples(interval.getCapacity()); } time.sample(elapsedTime.seconds()); } struct Stats { Stats( const typename WindowAverageTimeInterval::Stats& interval, const WindowAverage::Stats& time, double dutyfraction ) : interval(interval) , time(time) , dutyfraction(dutyfraction) {}; typename WindowAverageTimeInterval::Stats interval; WindowAverage::Stats time; double dutyfraction; // 1.0: duty time is 100% of interval time }; Stats getStats(size_t samples = ~0) const { typename WindowAverage<>::Stats timestats = time.getStats(samples); typename WindowAverageTimeInterval::Stats intervalstats = interval.getStats(samples); double interval = intervalstats.average.seconds(); return Stats(intervalstats, timestats, interval ? timestats.average / interval : 0); } template void iterTimes(F& f) const { time.iter(f); } template void iterIntervals(F& f) const { interval.iter(f); } struct GTCounter { GTCounter(double gt) : c(0), gtValue(gt) {}; size_t c; double gtValue; void operator()(double dt) { if(dt > gtValue) c++; } }; size_t countTimesGreaterThan(Time::Interval dt) const { GTCounter count(dt.seconds()); iterTimes(count); return count.c; } size_t countIntervalsGreaterThan(Time::Interval dt) const { GTCounter count(dt.seconds()); iterIntervals(count); return count.c; } void clear() { time.clear(); interval.clear(); } size_t timesamples() const { return time.size(); } size_t intervalsamples() const { return interval.size(); } private: WindowAverage time; WindowAverageTimeInterval interval; }; // A thread-safe, lock-free DutyCycle meter template class ActivityMeter { static const int bucketCount = windowSeconds * 1024; boost::array buckets; rbx::atomic currentTime; rbx::atomic currentValue; rbx::atomic totalValue; RBX::Time startTime; RBX::Time lastSampleTime; public: ActivityMeter() :currentTime(-1) ,currentValue(0) ,totalValue(0) ,startTime(RBX::Time::now()) { for (size_t i=0; i(); if (lastSampleTime == now) return; lastSampleTime = now; unsigned long newTime = ((unsigned long)(bucketCount * (lastSampleTime - startTime).seconds())); unsigned long oldTime = currentTime.swap(newTime); if (oldTime < newTime) { long newValue = currentValue; for (unsigned long i = oldTime + 1; i <= newTime; ++i) { int index = i % bucketCount; int oldBucketValue = buckets[index]; for (int j = 0; j < oldBucketValue; ++j) --totalValue; buckets[index] = (char)newValue; for (int j = 0; j < newValue; ++j) ++totalValue; } } } }; template class InvocationMeter { static const int bucketCount = windowSeconds * 1024; boost::array buckets; rbx::atomic currentTime; rbx::atomic totalValue; RBX::Time startTime; RBX::Time lastSampleTime; public: InvocationMeter() :currentTime(-1) ,totalValue(0) ,startTime(RBX::Time::now()) { for (size_t i=0; i(); if (lastSampleTime == now) return; lastSampleTime = now; unsigned long newTime = ((unsigned long)(bucketCount * (lastSampleTime - startTime).seconds())); unsigned long oldTime = currentTime.swap(newTime); // RBXASSERT(oldTime <= newTime); // if (oldTime != newTime) if (oldTime < newTime) // changed per Erik 11/18/09 { for (unsigned long i = oldTime + 1; i <= newTime; ++i) { int index = i % bucketCount; int oldBucketValue = buckets[index]; for (int j = 0; j < oldBucketValue; ++j) --totalValue; buckets[index] = 0; } } if(increment){ int newValue = 1; int newIndex = newTime % bucketCount; buckets[newIndex] = newValue; for (int j = 0; j < newValue; ++j) ++totalValue; } } }; }