Tracking Rates in C++
Recently I have wanted to figure out how often a certain even happens in my research project, Alaska. For example, I want to know how many allocations have been made, and the rate (allocations per second) they are made at. Importantly, though, I want this tracking to occur in constant time and constant space (and ideally very quickly).
As an example of what I wanted to have, imagine we want to track how fast a memory allocator can operate:
RateCounter counter; // a counter for events
for (...) {
malloc(...);
counter++; // track the event
}
Then, you should be able to ask the counter for stats:
printf("%d allocations at %f per second\n",
counter.read(),
counter.digest());
read
returns the number of events which have occurred, and digest
returns the rate of those events.
I also only care about the most recent rate, so digest
resets the counting.
The code
Here's the code for that RateCounter.
Notice there is the concept of a TimeCache
, which allows you to re-use the time measurement because asking the kernel or whatever for the current time is pretty slow on some systems.
class TimeCache {
uint64_t m_now;
public:
inline TimeCache(void) {
// get the current time in nanoseconds
struct timespec ts;
clock_gettime(CLOCK_REALTIME, &ts);
m_now = (uint64_t)ts.tv_sec * 1000000000ULL + (uint64_t)ts.tv_nsec;
}
inline auto now(void) const { return m_now; }
};
class RateCounter {
uint64_t last_nanoseconds = 0;
uint64_t last_value = 0;
uint64_t value = 0;
float last_rate = 0.0;
public:
inline RateCounter() {
value = 0;
digest();
}
// Read the value, and return the rate (per second) since the last digest.
// This function can take a time cache, which can be used to
// record the exact same time for many RateCounter readings. This
// is useful because reading time from the kernel could be expensive
inline float digest(const TimeCache &tc) {
auto now = tc.now();
auto ns_passed = now - last_nanoseconds;
float seconds_passed = ns_passed / 1000.0 / 1000.0 / 1000.0;
uint64_t change = value - last_value;
// Only update the rate every 100ms
if (seconds_passed < 0.1) return last_rate;
last_nanoseconds = now;
last_value = value;
last_rate = change / seconds_passed;
return last_rate;
}
inline float digest(void) {
alaska::TimeCache tc;
return digest(tc);
}
// Track that an event happened
inline void track(int incr = 1) { value += incr; }
// Return the total count tracked by the counter
inline uint64_t read(void) const { return value; }
auto &operator++(int) {
track(1);
return *this;
}
};
It probably has some problems w/ overflowing nanoseconds, but it suits my purpose :). Hope you find it useful!