1 module tagion.logger.Statistic; 2 3 import std.exception : assumeWontThrow; 4 import std.format; 5 import std.meta : AliasSeq; 6 import std.typecons : Flag, No, Tuple, Yes; 7 import tagion.hibon.HiBONRecord; 8 9 @safe @recordType("S") 10 struct Statistic(T, Flag!"histogram" flag = No.histogram) { 11 protected { 12 double sum2 = 0.0; 13 double sum = 0.0; 14 @label("min") T _min = T.max; 15 @label("max") T _max = T.min; 16 uint N; 17 static if (flag) { 18 @label("H") uint[T] _histogram; 19 } 20 } 21 22 void opCall(const T value) pure nothrow { 23 import std.algorithm.comparison : max, min; 24 25 _min = min(_min, value); 26 _max = max(_max, value); 27 immutable double x = value; 28 sum += x; 29 sum2 += x * x; 30 N++; 31 static if (flag) { 32 _histogram.update( 33 value, 34 () => 1, 35 (ref uint a) => a += 1); 36 } 37 } 38 39 alias Result = Tuple!(double, "sigma", double, "mean", uint, "N", T, "min", T, "max"); 40 mixin HiBONRecord; 41 42 const pure nothrow @nogc { 43 const(Result) result() { 44 immutable mx = sum / N; 45 immutable mx2 = mx * mx; 46 immutable M = sum2 + N * mx2 - 2 * mx * sum; 47 import std.math : sqrt; 48 49 return Result(sqrt(M / (N - 1)), mx, N, _min, _max); 50 } 51 52 static if (flag) { 53 bool contains(const T size) { 54 return (size in _histogram) !is null; 55 } 56 57 const(uint[T]) histogram() { 58 return _histogram; 59 } 60 } 61 } 62 string toString() pure const nothrow { 63 return assumeWontThrow(format("N=%d sum2=%s sum=%s min=%s max=%s", N, sum2, sum, _min, _max)); 64 } 65 66 static if (flag == Yes.histogram) { 67 68 string histogramString(const bool logscale = false,const uint axis_scale = 100) pure const nothrow { 69 import std.algorithm; 70 import std.format; 71 import std.range : repeat; 72 import std.math : round, log10; 73 74 double max_value; 75 if (logscale) { 76 max_value = log10(double(_histogram.byValue.maxPos.front)); 77 78 } 79 else { 80 max_value = double(_histogram.byValue.maxPos.front); 81 } 82 uint height(const T size) { 83 if (logscale) { 84 return cast(uint)(round(axis_scale * log10(double(size)) / max_value)); 85 } 86 return cast(uint)(round(axis_scale * double(size) / max_value)); 87 } 88 string[] result; 89 result~=assumeWontThrow(format("%4s|%-(%s%)| %-(%9d|%)|", 90 (logscale)?"log":"lin", ' '.repeat(8), 91 iota(1,axis_scale/10+1).map!(a => 10*a))); 92 foreach (keypair; _histogram.byKeyValue.array.sort!((a, b) => a.key < b.key)) { 93 const number = keypair.key; 94 const size = keypair.value; 95 result ~= assumeWontThrow(format("%4d|%8d| %s", number, size, "#".repeat(height(size)).join)); 96 } 97 98 return result.join("\n"); 99 100 } 101 102 } 103 104 } 105 106 /// 107 @safe 108 unittest { 109 Statistic!uint s; 110 const samples = [10, 15, 17, 6, 8, 12, 18]; 111 samples.each!(a => s(a)); 112 113 auto r = s.result; 114 // Mean 115 assert(approx(r.mean, 12.2857)); 116 // Number of samples 117 assert(r.N == samples.length); 118 // Sigma 119 assert(approx(r.sigma, 4.5721)); 120 121 assert(r.max == samples.maxElement); 122 assert(r.min == samples.minElement); 123 124 } 125 126 /// 127 @safe 128 unittest { 129 /// Use of the Statistic including histogram 130 Statistic!(long, Yes.histogram) s; 131 const samples = [-10, 15, -10, 6, 8, -12, 18, 8, -12, 9, 4, 5, 6]; 132 samples.each!(n => s(n)); 133 134 auto r = s.result; 135 // Mean 136 assert(approx(r.mean, 2.6923)); 137 // Number of samples 138 assert(r.N == samples.length); 139 // Sigma 140 assert(approx(r.sigma, 10.266)); 141 142 assert(r.max == samples.maxElement); 143 assert(r.min == samples.minElement); 144 // samples/histogram does not contain -4 145 assert(!s.contains(-4)); 146 // but conatians -10 147 assert(s.contains(-10)); 148 149 // Get the statiscal histogram 150 const histogram = s.histogram; 151 152 assert(histogram.get(-4, 0) == 0); 153 assert(histogram.get(-10, 0) > 0); 154 155 // verifies the number of samples in the histogram 156 assert(histogram.get(-10, 0) == samples.filter!(a => a == -10).count); 157 } 158 159 version (unittest) { 160 import std.algorithm.iteration : each, filter; 161 import std.algorithm.searching : count, maxElement, minElement; 162 import std.math : isClose; 163 164 alias approx = (a, b) => isClose(a, b, 0.001); 165 }