CMSDK 2.0.1
Cross-platform C++ base library and SDK for the Psyclone AIOS platform
Loading...
Searching...
No Matches
MovingAverage.cpp
Go to the documentation of this file.
1
10#include "MovingAverage.h"
11#include "UnitTestFramework.h"
12
13namespace cmlabs {
14
15MovingAverage::MovingAverage(uint32 binTimeMS, uint16 binCount) {
16 mutex.enter(200, __FUNCTION__);
17 this->binTime = binTimeMS * 1000;
18 this->binCount = binCount;
19 entries = (StatEntry*)malloc(binCount*sizeof(StatEntry));
20 memset(entries, 0, binCount*sizeof(StatEntry));
21 currentBin = 0;
23 totalValue = 0;
24 totalCount = 0;
25 mutex.leave();
26}
27
29 mutex.enter(200, __FUNCTION__);
30 free(entries);
31 entries = NULL;
32 mutex.leave();
33}
34
35bool MovingAverage::shiftBins(uint64 now) {
36 // assume mutex is already locked
37 // assume we already checked that data has been added
38
39 int64 binShift = ( now - currentBinStart) / binTime;
40 if (binShift < 0) {
41 // This is a past time, don't change current bin
42 }
43 else if (binShift > binCount) {
44 // We have missed all of our bins, reset everything except total
45 // The computer was probably in sleep mode...
46 currentBinStart = now;
47 memset(entries, 0, binCount*sizeof(StatEntry));
48 currentBin = 0;
49 }
50 else {
51 // Shift the current bin forwards, wrap around as needed
52 while (binShift > 0) {
53 if (++currentBin >= binCount)
54 currentBin = 0;
55 entries[currentBin].count = 0;
56 entries[currentBin].value = 0;
57 binShift--;
59 }
60 }
61 return true;
62}
63
64
65bool MovingAverage::add(double val, uint32 count, uint64 time) {
66 // We assume that time is close enough to 'now' so we don't have to do another GetTimeNow()
67
68 mutex.enter(200, __FUNCTION__);
69
70 if (!currentBinStart)
71 currentBinStart = time;
72 else {
73 int64 binShift = ( time - currentBinStart) / binTime;
74 if (binShift < 0) {
75 // This is a past entry, put it into the old bin entry
76 if (abs(binShift) < binCount) {
77 int64 oldBin = (int64)currentBin + binShift; // negative binShift
78 if (oldBin < 0)
79 oldBin += binCount;
80 entries[(uint64)oldBin].value += val;
81 entries[(uint64)oldBin].count += count;
82 totalValue += val;
83 totalCount += count;
84 mutex.leave();
85 return true;
86 }
87 else {
88 // too old, just add to total
89 totalValue += val;
90 totalCount += count;
91 mutex.leave();
92 return true;
93 }
94 }
95 else if (binShift > binCount) {
96 // We have missed all of our bins, reset everything except total
97 // The computer was probably in sleep mode...
98 currentBinStart = time;
99 memset(entries, 0, binCount*sizeof(StatEntry));
100 currentBin = 0;
101 }
102 else {
103 // Shift the current bin forwards, wrap around as needed
104 while (binShift > 0) {
105 if (++currentBin >= binCount)
106 currentBin = 0;
107 entries[currentBin].count = 0;
108 entries[currentBin].value = 0;
109 binShift--;
111 }
112 }
113 }
114
115 entries[currentBin].value += val;
116 entries[currentBin].count += count;
117 totalValue += val;
118 totalCount += count;
119
120 mutex.leave();
121 return true;
122}
123
124bool MovingAverage::getAverage(uint32 ms, double& val, uint64& count, uint64 now) {
125 if (!getSum(ms, val, count, now))
126 return false;
127
128 if (count)
129 val = val / count;
130 return true;
131}
132
134 uint32 ms1, uint32 ms2, uint32 ms3,
135 double& avg1, uint64& count1,
136 double& avg2, uint64& count2,
137 double& avg3, uint64& count3, uint64 now) {
138
139 double val1 = 0, val2 = 0, val3 = 0;
140 if (!getSumMulti(ms1, ms2, ms3, val1, count1, val2, count2, val3, count3, now))
141 return false;
142
143 avg1 = avg2 = avg3 = 0;
144 if (count1)
145 avg1 = val1 / count1;
146 if (count2)
147 avg2 = val2 / count2;
148 if (count3)
149 avg3 = val3 / count3;
150
151 return true;
152}
153
154bool MovingAverage::getSum(uint32 ms, double& val, uint64& count, uint64 now) {
155 uint64 bins = ((uint64)ms*1000)/binTime;
156 if (bins == 0)
157 bins = 1;
158 else if (bins > binCount-1)
159 bins = binCount-1;
160
161 val = 0;
162 count = 0;
163
164 mutex.enter(200, __FUNCTION__);
165
166 if (!currentBinStart) {
167 mutex.leave();
168 return true;
169 }
170 else if (!shiftBins(now)) {
171 mutex.leave();
172 return false;
173 }
174
175 StatEntry* entry = entries + currentBin;
176 // Never include the current bin as it is still being built
177 if (entry == entries)
178 entry = entries + binCount;
179 entry--;
180 for (uint64 n=0; n<bins; n++) {
181 val += entry->value;
182 count += entry->count;
183 if (entry == entries)
184 entry = entries + binCount;
185 entry--;
186 }
187
188 mutex.leave();
189 return true;
190}
191
193 uint32 ms1, uint32 ms2, uint32 ms3,
194 double& sum1, uint64& count1,
195 double& sum2, uint64& count2,
196 double& sum3, uint64& count3, uint64 now) {
197
198 uint64 bins1 = ((uint64)ms1*1000)/binTime;
199 if (bins1 == 0) bins1 = 1; else if (bins1 > binCount-1) bins1 = binCount-1;
200 uint64 bins2 = ((uint64)ms2*1000)/binTime;
201 if (bins2 == 0) bins2 = 1; else if (bins2 > binCount-1) bins2 = binCount-1;
202 uint64 bins3 = ((uint64)ms3*1000)/binTime;
203 if (bins3 == 0) bins3 = 1; else if (bins3 > binCount-1) bins3 = binCount-1;
204
205 uint64 bins = clmax(clmax(bins1, bins2), bins3);
206 sum1 = sum2 = sum3 = 0;
207 count1 = count2 = count3 = 0;
208
209 mutex.enter(200, __FUNCTION__);
210
211 if (!currentBinStart) {
212 mutex.leave();
213 return true;
214 }
215 else if (!shiftBins(now)) {
216 mutex.leave();
217 return false;
218 }
219
220 StatEntry* entry = entries + currentBin;
221 // Never include the current bin as it is still being built
222 if (entry == entries)
223 entry = entries + binCount;
224 entry--;
225 for (uint64 n=0; n<bins; n++) {
226 if (n<bins1) {
227 sum1 += entry->value;
228 count1 += entry->count;
229 }
230 if (n<bins2) {
231 sum2 += entry->value;
232 count2 += entry->count;
233 }
234 if (n<bins3) {
235 sum3 += entry->value;
236 count3 += entry->count;
237 }
238 if (entry == entries)
239 entry = entries + binCount;
240 entry--;
241 }
242
243 mutex.leave();
244 return true;
245
246}
247
248
249bool MovingAverage::getTotal(double& val, uint64& count) {
250 mutex.enter(200, __FUNCTION__);
251 val = totalValue;
252 count = totalCount;
253 mutex.leave();
254 return true;
255}
256
257bool MovingAverage::getThroughput(uint32 ms, double& valPerSec, double& countPerSec, uint64 now) {
258 double val;
259 uint64 count;
260 if (!getSum(ms, val, count, now))
261 return false;
262
263 double sec = (double)ms/1000.0;
264 if (sec > 0) {
265 valPerSec = val / sec;
266 countPerSec = count / sec;
267 }
268 return true;
269}
270
271bool MovingAverage::getThroughputMulti(uint32 ms1, uint32 ms2, uint32 ms3, double& valPerSec1, double& countPerSec1, double& valPerSec2, double& countPerSec2, double& valPerSec3, double& countPerSec3, uint64 now ) {
272
273 double val1 = 0, val2 = 0, val3 = 0;
274 uint64 count1 = 0, count2 = 0, count3 = 0;
275 if (!getSumMulti(ms1, ms2, ms3, val1, count1, val2, count2, val3, count3, now))
276 return false;
277
278 double sec = (double)ms1/1000.0;
279 if (sec > 0) {
280 valPerSec1 = val1 / sec;
281 countPerSec1 = count1 / sec;
282 }
283
284 sec = (double)ms2/1000.0;
285 if (sec > 0) {
286 valPerSec2 = val2 / sec;
287 countPerSec2 = count2 / sec;
288 }
289
290 sec = (double)ms3/1000.0;
291 if (sec > 0) {
292 valPerSec3 = val3 / sec;
293 countPerSec3 = count3 / sec;
294 }
295
296 return true;
297}
298
299std::string MovingAverage::getPerfXML(uint32 binMS, uint32 binNum) {
300 uint64 now = GetTimeNow();
301 uint64 binSize = ((uint64)binMS * 1000) / binTime;
302 uint64 bins = binNum * binSize;
303 if (bins == 0)
304 bins = 1;
305 else if (bins > binCount - 1)
306 bins = binCount - 1;
307
308 uint32 actualBins = 0;
309
310 mutex.enter(200, __FUNCTION__);
311 std::string xml = "";
312
313 if (currentBinStart && shiftBins(now)) {
314 double val = 0;
315 uint64 count = 0;
316 uint32 binCounter = 0;
317
318 StatEntry* entry = entries + currentBin;
319 // Never include the current bin as it is still being built
320 if (entry == entries)
321 entry = entries + binCount;
322 entry--;
323 for (uint64 n = 0; n < bins; n++) {
324 val += entry->value;
325 count += entry->count;
326 if (entry == entries)
327 entry = entries + binCount;
328 entry--;
329 binCounter++;
330 if (binCounter >= binSize) {
331 if (count)
332 xml += utils::StringFormat("<count=\"%llu\" avg=\"%.6f\" total=\"%.6f\" />\n", count, val / count, val);
333 else
334 xml += utils::StringFormat("<count=\"0\" avg=\"0\" total=\"0\" />\n");
335 val = 0;
336 count = 0;
337 actualBins++;
338 binCounter = 0;
339 }
340 }
341 if (count) {
342 xml += utils::StringFormat("<count=\"%llu\" avg=\"%.6f\" total=\"%.6f\" />\n", count, val / count, val);
343 actualBins++;
344 }
345 }
346
347 for (uint32 c = actualBins; c < binNum; c++)
348 xml += utils::StringFormat("<count=\"0\" avg=\"0\" total=\"0\" />\n");
349
350 mutex.leave();
351 return utils::StringFormat("<performance>%s</performance>", xml.c_str());
352}
353
354std::string MovingAverage::getPerfJSON(uint32 binMS, uint32 binNum) {
355 uint64 now = GetTimeNow();
356 uint64 binSize = ((uint64)binMS * 1000) / binTime;
357 uint64 bins = binNum * binSize;
358 if (bins == 0)
359 bins = 1;
360 else if (bins > binCount - 1)
361 bins = binCount - 1;
362
363 uint32 actualBins = 0;
364
365 mutex.enter(200, __FUNCTION__);
366 std::string json = "";
367
368 if (currentBinStart && shiftBins(now)) {
369 double val = 0;
370 uint64 count = 0;
371 uint32 binCounter = 0;
372
373 StatEntry* entry = entries + currentBin;
374 // Never include the current bin as it is still being built
375 if (entry == entries)
376 entry = entries + binCount;
377 entry--;
378 for (uint64 n = 0; n < bins; n++) {
379 val += entry->value;
380 count += entry->count;
381 if (entry == entries)
382 entry = entries + binCount;
383 entry--;
384 binCounter++;
385 if (binCounter >= binSize) {
386 if (count)
387 json += utils::StringFormat("%s{\"count\":%llu ,\"avg\":%.6f, \"total\":%.6f}", json.length() ? "," : "", count, val / count, val);
388 else
389 json += utils::StringFormat("%s{\"count\":0 ,\"avg\":0, \"total\":0}", json.length() ? "," : "");
390 val = 0;
391 count = 0;
392 actualBins++;
393 binCounter = 0;
394 }
395 }
396 if (count) {
397 json += utils::StringFormat("%s{\"count\":%llu ,\"avg\":%.6f, \"total\":%.6f}", json.length() ? "," : "", count, val / count, val);
398 actualBins++;
399 }
400 }
401
402 for (uint32 c = actualBins; c < binNum; c++)
403 json += utils::StringFormat("%s{\"count\":0 ,\"avg\":0, \"total\":0}", json.length() ? "," : "");
404
405 mutex.leave();
406 return utils::StringFormat("[%s]", json.c_str());
407}
408
409
410//bool MovingAverage::getThroughputMultiAlt(uint32 ms1, uint32 ms2, uint32 ms3, double& valPerSec1, double& countPerSec1, double& valPerSec2, double& countPerSec2, double& valPerSec3, double& countPerSec3 ) {
411// if (!getThroughput(ms1, valPerSec1, countPerSec1))
412// return false;
413// if (!getThroughput(ms2, valPerSec2, countPerSec2))
414// return false;
415// if (!getThroughput(ms3, valPerSec3, countPerSec3))
416// return false;
417// return true;
418//}
419
420
421
422
423
424
426 // binTime 100ms, 600 bins => 60s window
427 MovingAverage stats(100, 60*10);
428
429 uint32 innerCount = 100000;
430 uint32 outerCount = 30;
431
432 uint64 start, now = GetTimeNow();
433 uint32 n, m;
434 double val, v1, v2, v3;
435 uint64 count;
436 double c1, c2, c3;
437
438 // 1. Add values and verify total bookkeeping
439 unittest::progress(10, "timed add throughput");
440 uint64 addStart = GetTimeNow();
441 uint64 totalAdds = 0;
442 for (n=0; n<outerCount; n++) {
443 start = GetTimeNow();
444 for (m=0; m<innerCount; m++) {
445 stats.add(1024, 1, now);
446 now += 10;
447 totalAdds++;
448 }
449 unittest::detail("Time per timed add: %.3fus", ((double)GetTimeAge(start))/innerCount);
450 }
451 double addUs = (double)GetTimeAge(addStart);
452 if (addUs > 0)
453 unittest::metric("add_throughput", (double)totalAdds / addUs * 1e6, "ops/s", true);
454 if (addUs > 0)
455 unittest::metric("avg_add_latency", addUs / totalAdds, "us", false);
456
457 // 2. Total must reflect every add (totals are never expired)
458 unittest::progress(45, "verify totals");
459 double totVal = 0;
460 uint64 totCount = 0;
461 if (!stats.getTotal(totVal, totCount)) {
462 unittest::fail("MovingAverage test: getTotal failed\n");
463 return false;
464 }
465 if (totCount != totalAdds) {
466 unittest::fail("MovingAverage test: getTotal count %llu != expected %llu\n", totCount, totalAdds);
467 return false;
468 }
469 if (totVal < (double)totalAdds * 1024.0 * 0.999 || totVal > (double)totalAdds * 1024.0 * 1.001) {
470 unittest::fail("MovingAverage test: getTotal value %f != expected %f\n", totVal, (double)totalAdds * 1024.0);
471 return false;
472 }
473
474 // NB: keep using the advanced simulated 'now' (the add loop stepped it ~30s
475 // forward). Resetting it to real GetTimeNow() here would put the query
476 // window before every entry, yielding a zero-count average.
477
478 // 3. Average over a window: each entry has value 1024, so average must be ~1024
479 unittest::progress(60, "average query");
480 start = GetTimeNow();
481 for (n=0; n<outerCount; n++) {
482 if (!stats.getAverage(1000, val, count, now)) {
483 unittest::fail("MovingAverage test: getAverage failed\n");
484 return false;
485 }
486 }
487 if (count == 0) {
488 unittest::fail("MovingAverage test: getAverage returned zero count\n");
489 return false;
490 }
491 if (val < 1023.0 || val > 1025.0) {
492 unittest::fail("MovingAverage test: getAverage value %f not ~1024\n", val);
493 return false;
494 }
495 unittest::metric("getaverage_latency", ((double)GetTimeAge(start))/outerCount, "us", false);
496 unittest::detail("Average %f count: %llu", val, count);
497
498 // 4. Sum over a window
499 unittest::progress(75, "sum query");
500 start = GetTimeNow();
501 for (n=0; n<outerCount; n++) {
502 if (!stats.getSum(1000, val, count, now)) {
503 unittest::fail("MovingAverage test: getSum failed\n");
504 return false;
505 }
506 }
507 if (count == 0 || val <= 0) {
508 unittest::fail("MovingAverage test: getSum returned empty window\n");
509 return false;
510 }
511 // value/count must still average to ~1024
512 if (val / (double)count < 1023.0 || val / (double)count > 1025.0) {
513 unittest::fail("MovingAverage test: getSum avg %f not ~1024\n", val / (double)count);
514 return false;
515 }
516 unittest::metric("getsum_latency", ((double)GetTimeAge(start))/outerCount, "us", false);
517 unittest::detail("Sum %f count: %llu", val, count);
518
519 // 5. Single-window throughput
520 unittest::progress(85, "throughput query");
521 start = GetTimeNow();
522 for (n=0; n<outerCount; n++) {
523 if (!stats.getThroughput(1000, v1, c1, now)) {
524 unittest::fail("MovingAverage test: getThroughput failed\n");
525 return false;
526 }
527 }
528 if (v1 <= 0 || c1 <= 0) {
529 unittest::fail("MovingAverage test: getThroughput non-positive rate\n");
530 return false;
531 }
532 unittest::metric("getthroughput_latency", ((double)GetTimeAge(start))/outerCount, "us", false);
533 unittest::detail("1sec %s %.3f msg/s", utils::BytifyRate(v1).c_str(), c1);
534
535 // 6. Multi-window throughput
536 unittest::progress(95, "multi throughput query");
537 start = GetTimeNow();
538 for (n=0; n<outerCount; n++) {
539 if (!stats.getThroughputMulti(1000, 10000, 30000, v1, c1, v2, c2, v3, c3, now)) {
540 unittest::fail("MovingAverage test: getThroughputMulti failed\n");
541 return false;
542 }
543 }
544 if (v1 <= 0 || v2 <= 0 || v3 <= 0) {
545 unittest::fail("MovingAverage test: getThroughputMulti non-positive rate\n");
546 return false;
547 }
548 unittest::metric("getthroughputmulti_latency", ((double)GetTimeAge(start))/outerCount, "us", false);
549 unittest::detail("1sec %s %.3f msg/s 10sec %s %.3f msg/s 30sec %s %.3f msg/s",
550 utils::BytifyRate(v1).c_str(), c1,
551 utils::BytifyRate(v2).c_str(), c2,
552 utils::BytifyRate(v3).c_str(), c3);
553
554 unittest::progress(100, "done");
555 return true;
556}
557
560 "Moving average bin stats: add, sum, average, throughput", "core");
561}
562
563
564
565
566
567} // namespace cmlabs
Time-binned moving average / throughput tracker.
#define clmax(a, b)
Definition Standard.h:114
Small, dependency-free unit test harness used by all CMSDK object tests.
static bool UnitTest()
Self test.
bool shiftBins(uint64 now)
Advance the circular buffer so the bin containing now becomes current, zeroing expired bins.
bool getSum(uint32 ms, double &val, uint64 &count, uint64 now=GetTimeNow())
Value sum over the last ms milliseconds.
MovingAverage(uint32 binTimeMS=100, uint16 binCount=600)
Create a tracker.
bool getTotal(double &val, uint64 &count)
Lifetime totals since construction (not limited to the bin window).
uint32 binTime
Bin duration in µs (constructor's binTimeMS multiplied by 1000).
bool getSumMulti(uint32 ms1, uint32 ms2, uint32 ms3, double &sum1, uint64 &count1, double &sum2, uint64 &count2, double &sum3, uint64 &count3, uint64 now=GetTimeNow())
Sums for three windows in one locked pass; parameters parallel getThroughputMulti().
bool getAverageMulti(uint32 ms1, uint32 ms2, uint32 ms3, double &avg1, uint64 &count1, double &avg2, uint64 &count2, double &avg3, uint64 &count3, uint64 now=GetTimeNow())
Averages for three windows in one locked pass; parameters parallel getThroughputMulti().
std::string getPerfJSON(uint32 binMS=1000, uint32 binNum=60)
Render recent history as JSON performance bins.
bool add(double val, uint32 count=1, uint64 time=GetTimeNow())
Record a sample.
std::string getPerfXML(uint32 binMS=1000, uint32 binNum=60)
Render recent history as XML performance bins.
bool getThroughputMulti(uint32 ms1, uint32 ms2, uint32 ms3, double &valPerSec1, double &countPerSec1, double &valPerSec2, double &countPerSec2, double &valPerSec3, double &countPerSec3, uint64 now=GetTimeNow())
Compute throughput for three windows in one locked pass (cheaper than three getThroughput() calls).
bool getThroughput(uint32 ms, double &valPerSec, double &countPerSec, uint64 now=GetTimeNow())
Compute rates over the last ms milliseconds.
bool getAverage(uint32 ms, double &val, uint64 &count, uint64 now=GetTimeNow())
Average value over the last ms milliseconds.
static UnitTestRunner & instance()
Access the singleton (created on first use).
void registerTest(const char *name, UnitTestFunc func, const char *description="", const char *category="", bool inDefaultRun=true)
Register a test with the runner.
uint64 GetTimeNow()
Return the current absolute time (µs since year 0) according to the TMC.
Definition PsyTime.cpp:69
int64 GetTimeAge(uint64 t)
Age of a timestamp relative to now.
Definition PsyTime.cpp:25
std::string StringFormat(const char *format,...)
printf into a std::string.
Definition Utils.cpp:6626
std::string BytifyRate(double val)
Format a byte rate, e.g.
Definition Utils.cpp:7807
void fail(const char *fmt,...)
Set an explanatory reason shown on the FAIL line.
void metric(const char *name, double value, const char *unit="", bool higherIsBetter=true)
Record a performance metric.
void detail(const char *fmt,...)
Verbose-only indented diagnostic line (shown only when verbose=1).
void progress(int percent, const char *action)
Report progress with a short description of the current action.
void Register_MovingAverage_Tests()
Accumulated samples of one time bin: number of samples and their value sum.
double value
Sum of sample values in this bin.
uint64 count
Number of samples added to this bin.