/* * Phusion Passenger - https://www.phusionpassenger.com/ * Copyright (c) 2014-2017 Phusion Holding B.V. * * "Passenger", "Phusion Passenger" and "Union Station" are registered * trademarks of Phusion Holding B.V. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ #ifndef _PASSENGER_SPEED_METER_H_ #define _PASSENGER_SPEED_METER_H_ #include #include #include #include #include namespace Passenger { /** * Utility class which, when periodically fed with quantities, measures the * rate of change in the most recent time period. It does this by storing * samples of the most recent quantities, calculating a weighted average of * the differences between each sample, and extrapolating that over a time * period. * * For example, * * * When periodically given the number of miles a car has driven so far, * the average speed of the most recent 10 minutes can be calculated. * * When periodically given the number of processes the OS has created * so far, the average fork rate (per minute) of the most recent minute * can be calculated. * * SpeedMeter is designed to use as little memory as possible, and designed * so that samples can be taken at erratic intervals. This is why it limits * the number of samples that can be stored in memory, and why it puts time * limits on the samples. * * * ## Template parameters * * ### `maxSamples` * * `maxSamples` dictates the sample buffer size: the maximum number of samples * to hold in memory. Defaults to 8. * * The more samples you have, the more historical data influences the results. * If you have few samples then recent data have the most impact on the results. * * ### `minAge` * * A new sample is only accepted if at least `minAge` microseconds * have passed since the last sample. This is to ensure that the sample * buffer contains enough historical data so that results are not skewed by * adding samples too quickly. A full sample buffer is guaranteed to cover * the last `minAge * maxSamples` microseconds. * * Defaults to 1 second (1 000 000 usec). * * To see why this is useful, consider when `maxSamples` is 5 and `minAge` is 0. * If you measure the number of processes 5 times with 10 ms in between, * then it's unlikely that the OS has spawned any processes in that 50 ms time * interval. However you've now filled the sample buffer with only data from * those 5 recent measurements, so SpeedMeter thinks the rate of change is 0. * If you set `minAge` to, say, 5 seconds, then the sample buffer will be updated * at most once every 5 seconds, guaranteeing that it can retain historical data * for the last 25 seconds. * * `minAge * maxSamples` should be larger than `window`. * * ### `maxAge` * * When calculating the result, samples older than `maxAge` usec ago are ignored. * This value should be larger than `window`. Defaults to 1 minute (60 000 000 * usec). * * ### `window` * * When calculating the result, this value dictates the time interval over which * the rate of change should be calculated. Defaults to 1 second (1 000 000 usec), * meaning that by default the result describes the speed per second. * * * ## Interpreting the parameters * * Given parameters, the behavior of SpeedMeter can be interpreted as follows: * * All samples that have been collected in the last `minAge * maxSamples` usecs * (or the last `maxAge` usecs, if samples have been collected slowly), are used for * calculating the speed per `window` usecs. * * Thus, given the default parameter values, we can say: * * All samples that have been collected in the last 8 seconds (the last 1 minute, * if samples have been collected slowly), are used for calculating the speed * per second. */ template< typename ValueType = double, unsigned int maxSamples = 8, unsigned int minAge = 1000000, unsigned int maxAge = 60 * 1000000, unsigned int window = 1000000 > class SpeedMeter { private: struct Sample { unsigned long long timestamp; ValueType val; Sample() : timestamp(0), val(0) { } }; unsigned short start, count; Sample samples[maxSamples]; static int mod(int a, int b) { int r = a % b; return r < 0 ? r + b : r; } const Sample &getSample(int index) const { return samples[mod(start + index, maxSamples)]; } const Sample &getLastSample() const { return getSample(count - 1); } unsigned long long getTimeThreshold() const { const unsigned long long currentTime = SystemTime::getUsec(); if (currentTime > maxAge) { return currentTime - maxAge; } else { return 0; } } void resetOnClockSkew(unsigned long long timestamp) { if (getLastSample().timestamp > timestamp) { for (unsigned int i = 0; i < maxSamples; i++) { samples[i] = Sample(); } } } public: SpeedMeter() : start(0), count(0) { } bool addSample(ValueType val, unsigned long long timestamp = 0) { if (timestamp == 0) { timestamp = SystemTime::getUsec(); } const Sample &lastSample = getLastSample(); resetOnClockSkew(timestamp); if (lastSample.timestamp <= timestamp - minAge) { Sample &nextSample = samples[(start + count) % maxSamples]; nextSample.timestamp = timestamp; nextSample.val = val; if (count == maxSamples) { start = (start + 1) % maxSamples; } else { count++; } return true; } else { return false; } } /** Number of items in the sample buffer. */ unsigned int size() const { return count; } /** Current speed over the configured window. Returns unknownSpeed() if less than 2 * samples have been collected so far. */ double currentSpeed() const { const unsigned long long timeThreshold = getTimeThreshold(); int begin = 0, i; unsigned long long interval; double avgWeight = 0, sum = 0; // Ignore samples that are too old. while (begin < (int) count - 1 && getSample(begin).timestamp <= timeThreshold) { begin++; } // Given the deltas of each sample and the next sample, // calculate the weighted average of all those deltas, // with time interval of each delta as the weight, // and calculate the average weight. for (i = begin; i < (int) count - 1; i++) { const Sample &cur = getSample(i); const Sample &next = getSample(i + 1); ValueType delta = next.val - cur.val; unsigned long long weight = next.timestamp - cur.timestamp; sum += (double) delta * weight; avgWeight += weight; } avgWeight /= std::max(1, (int) count - 1 - begin); interval = getSample((int) count - 1).timestamp - getSample(begin).timestamp; if (interval > 0) { // sum / interval is the speed per average delta interval, // so we extrapolate that over the entire window interval. return (sum / interval) * (window / avgWeight); } else { return unknownSpeed(); } } static ValueType unknownSpeed() { return numeric_limits::max(); } #if 0 void debug() const { const unsigned long long timeThreshold = getTimeThreshold(); printf("---- begin debug ----\n"); printf("timeThreshold: %llu\n", timeThreshold); for (int i = 0; i < (int) maxSamples; i++) { const Sample &sample = samples[i]; printf("elem %u, sample %u: timestamp = %llu, val = %.0f%s%s%s\n", i, mod(i - start, maxSamples), sample.timestamp, (double) sample.val, (sample.timestamp <= timeThreshold) ? " X" : "", (i == start) ? " <-- start" : "", (count > 0 && i == mod((int) start + count - 1, maxSamples)) ? " <-- last" : ""); } printf("---- end debug ----\n"); } #endif }; } // namespace Passenger #endif /* _PASSENGER_SPEED_METER_H_ */