1 // ANNA - Anna is Not Nothingness Anymore //
3 // (c) Copyright 2005-2015 Eduardo Ramos Testillano & Francisco Ruiz Rayo //
5 // See project site at http://redmine.teslayout.com/projects/anna-suite //
6 // See accompanying file LICENSE or copy at http://www.teslayout.com/projects/public/anna.LICENSE //
9 //------------------------------------------------------------------------------
10 //--------------------------------------------------------------------- #include
11 //------------------------------------------------------------------------------
15 #include <anna/statistics/Accumulator.hpp>
16 #include <anna/statistics/Engine.hpp>
18 #include <anna/time/Date.hpp>
25 #include <anna/xml/xml.hpp>
26 #include <anna/core/tracing/Logger.hpp>
27 #include <anna/core/tracing/TraceMethod.hpp>
31 using namespace anna::statistics;
32 using namespace anna::time;
35 //******************************************************************************
36 //------------------------------------------------------------------------------
37 //--------------------------------------------------- Accumulator::Accumulator()
38 //------------------------------------------------------------------------------
40 // Default Constructor
41 Accumulator::Accumulator() {
46 Accumulator::~Accumulator() {
47 // LOGMETHOD (TraceMethod tttm ("anna::statistics::Accumulator", "Destructor", ANNA_FILE_LOCATION));
55 //------------------------------------------------------------------------------
56 //---------------------------------------------------- Accumulator::initialize()
57 //------------------------------------------------------------------------------
58 void Accumulator::initialize(const int & conceptId) throw() {
59 _concept_data_t conceptData;
61 a_concept_data_map[conceptId] = conceptData;
65 //------------------------------------------------------------------------------
66 //---------------------------------------------------- Accumulator::getConcept()
67 //------------------------------------------------------------------------------
68 _concept_data_t * Accumulator::getConcept(const int & conceptId) const throw(anna::RuntimeException) {
69 _concept_data_map_iter it = a_concept_data_map.find(conceptId);
71 if(it == a_concept_data_map.end()) { // not found
72 // Check if concept id is registered:
73 Engine& engine = Engine::instantiate();
74 std::string conceptDescription, conceptUnitDescription;
77 if(!engine.getConcept(conceptId, conceptDescription, conceptUnitDescription, integerNature)) {
78 std::string msg = "Can't find at engine nothing about concept id = ";
79 msg += anna::functions::asString(conceptId);
80 throw anna::RuntimeException(msg, ANNA_FILE_LOCATION);
83 Accumulator * nc_this = const_cast<Accumulator *>(this);
84 nc_this->initialize(conceptId);
85 // (la otra posibilidad era hacer mutable el mapa e inicializar poniendo el contenido de 'initialize()')
86 it = a_concept_data_map.find(conceptId);
89 return ((_concept_data_t *) & ((*it).second));
93 //------------------------------------------------------------------------------
94 //--------------------------------------------------- Accumulator::floatFormat()
95 //------------------------------------------------------------------------------
96 std::string Accumulator::floatFormat(const int & numberOfDecimals) const throw() {
97 std::string result = "%.";
98 result += anna::functions::asString(numberOfDecimals);
107 //------------------------------------------------------------------------------
108 //------------------------------------------ Accumulator::getStandardDeviation()
109 //------------------------------------------------------------------------------
110 double Accumulator::getStandardDeviation(const _concept_data_t * conceptData) const throw(anna::RuntimeException) {
111 // SD = sqrt (1/N SUM (xi^2) - X^2) = sqrt (SquareSum / N - Average^2)
112 if(conceptData->Size == 0)
113 throw anna::RuntimeException("Divide by zero: sample size = 0 for Standard Deviation !!", ANNA_FILE_LOCATION);
115 return (::sqrt((conceptData->SquareSum / conceptData->Size) - (::pow(conceptData->Average, (double)2))));
119 //------------------------------------------------------------------------------
120 //------------------------------------ Accumulator::getBesselStandardDeviation()
121 //------------------------------------------------------------------------------
122 double Accumulator::getBesselStandardDeviation(const _concept_data_t * conceptData) const throw(anna::RuntimeException) {
123 // BSD = sqrt (1/(N-1) SUM (xi^2) - N/(N-1) X^2) = sqrt (SquareSum / (N-1) - N/(N-1) Average^2)
124 if(conceptData->Size == 1)
125 throw anna::RuntimeException("Divide by zero: sample size = 1 for bessel's Standard Deviation !!", ANNA_FILE_LOCATION);
127 double N = conceptData->Size;
128 return (::sqrt((conceptData->SquareSum / (N - 1)) - (N / (N - 1)) * (::pow(conceptData->Average, (double)2))));
136 //------------------------------------------------------------------------------
137 //------------------------------------------------------- Accumulator::process()
138 //------------------------------------------------------------------------------
139 void Accumulator::process(const int & conceptId, const double & value) throw(anna::RuntimeException) {
140 // LOGMETHOD (TraceMethod tttm ("anna::statistics::Accumulator", "process", ANNA_FILE_LOCATION));
141 Engine& engine = Engine::instantiate();
143 if(!engine.enabled()) return;
145 _concept_data_t *ptr_auxConceptData = getConcept(conceptId);
146 anna::Millisecond current = anna::Millisecond::getTime();
147 // Optional sample file dump:
148 Engine::instantiate().logSample(conceptId, current, value);
150 // Statistics calculations
151 if(ptr_auxConceptData->Size == ULLONG_MAX) // Statistics is better during processing until reset
152 ptr_auxConceptData->reset();
154 ptr_auxConceptData->Size ++;
157 if(ptr_auxConceptData->Size == 1) {
158 ptr_auxConceptData->MinimumProcessed = value;
159 ptr_auxConceptData->MaximumProcessed = value;
160 ptr_auxConceptData->MinimumEventTimestampMs = current;
161 ptr_auxConceptData->MaximumEventTimestampMs = current;
163 if(value < ptr_auxConceptData->MinimumProcessed) {
164 ptr_auxConceptData->MinimumProcessed = value;
165 ptr_auxConceptData->MinimumEventTimestampMs = current;
168 if(value > ptr_auxConceptData->MaximumProcessed) {
169 ptr_auxConceptData->MaximumProcessed = value;
170 ptr_auxConceptData->MaximumEventTimestampMs = current;
174 ptr_auxConceptData->Sum += value;
175 ptr_auxConceptData->SquareSum += ::pow(value, (double)2); // (*) standard deviation formula simplification
176 ptr_auxConceptData->Average = ptr_auxConceptData->Sum / ptr_auxConceptData->Size;
177 //ptr_auxConceptData->SquareSumSampleDeviation += ::pow(value - ptr_auxConceptData->Average, (double)2);
178 // Don't use former method to get standard deviation because is not correct: in each processing, average
179 // will be change (is not fixed). There is a simplification for standard deviation, based on square sum (*):
180 // Take X as average for x1, x2, x3, etc., xN
181 // SUM (xi - X)^2 = SUM (xi^2 + X^2 - 2xiX) = SUM (xi^2) + NX^2 - 2X SUM (xi) =
182 // = SUM (xi`2) + NX^2 - 2X(NX) = SUM (xi^2) - NX^2
184 // Then, SD = sqrt (1/N SUM (xi^2) - X^2) = sqrt (SquareSum / N - Average^2)
185 // And corrected SD (Bessel's standard deviation) is:
186 // BSD = sqrt (1/(N-1) SUM (xi^2) - N/(N-1) X^2) = sqrt (SquareSum / (N-1) - N/(N-1) Average^2)
187 // All these values don't need to be calculated now
188 // Worked with pointer
189 //a_concept_data_map[conceptId] = *ptr_auxConceptData;
193 //------------------------------------------------------------------------------
194 //------------------------------------------------------- Accumulator::operator=
195 //------------------------------------------------------------------------------
196 const Accumulator & Accumulator::operator = (const Accumulator & accumulator) {
197 // LOGMETHOD (TraceMethod tttm ("anna::statistics::Accumulator", "operator=", ANNA_FILE_LOCATION));
198 // Avoid self copy: i.e., Accumulator a,b; a=&b; a=b; b=a;
199 if(this == &accumulator) return (*this);
201 a_concept_data_map = accumulator.a_concept_data_map;
207 //------------------------------------------------------------------------------
208 //--------------------------------------------------------- Accumulator::reset()
209 //------------------------------------------------------------------------------
210 void Accumulator::reset(void) throw() {
211 // LOGMETHOD (TraceMethod tttm ("anna::statistics::Accumulator", "reset", ANNA_FILE_LOCATION));
212 _concept_data_map_iter it;
213 _concept_data_map_iter it_min(a_concept_data_map.begin());
214 _concept_data_map_iter it_max(a_concept_data_map.end());
216 for(it = it_min; it != it_max; it++) {
217 //reset ((*it).first);
218 _concept_data_t * ptr_auxConceptData = (_concept_data_t*) & ((*it).second);
219 ptr_auxConceptData->reset();
224 //------------------------------------------------------------------------------
225 //--------------------------------------------------------- Accumulator::reset()
226 //------------------------------------------------------------------------------
227 void Accumulator::reset(const int & conceptId) throw(anna::RuntimeException) {
228 // LOGMETHOD (TraceMethod tttm ("anna::statistics::Accumulator", "reset", ANNA_FILE_LOCATION));
229 _concept_data_t *ptr_auxConceptData = getConcept(conceptId);
230 //if (ptr_auxConceptData == NULL) return; // Not possible: getConcept initializes it if not found.
231 ptr_auxConceptData->reset();
237 //------------------------------------------------------------------------------
238 //---------------------------------------------------- Accumulator::sampleSize()
239 //------------------------------------------------------------------------------
240 unsigned long long int Accumulator::sampleSize(const int & conceptId) const throw(anna::RuntimeException) {
241 // LOGMETHOD (TraceMethod tttm ("anna::statistics::Accumulator", "sampleSize", ANNA_FILE_LOCATION));
242 _concept_data_t *ptr_auxConceptData = getConcept(conceptId);
243 //if (ptr_auxConceptData == NULL) return 0; // Not possible: getConcept initializes it if not found.
244 return (ptr_auxConceptData->Size);
248 //------------------------------------------------------------------------------
249 //------------------------------------------------------ Accumulator::getValue()
250 //------------------------------------------------------------------------------
251 double Accumulator::getValue(const int & conceptId, const Operation::Type & operation) const throw(anna::RuntimeException) {
252 // LOGMETHOD (TraceMethod tttm ("anna::statistics::Accumulator", "getValue", ANNA_FILE_LOCATION));
253 const _concept_data_t *ptr_auxConceptData = getConcept(conceptId);
256 //case Operation::Sum:
257 // return (ptr_auxConceptData->Sum);
258 case Operation::Average:
259 return (ptr_auxConceptData->Average);
260 case Operation::StandardDeviation:
261 return (getStandardDeviation(ptr_auxConceptData));
262 case Operation::BesselStandardDeviation:
263 return (getBesselStandardDeviation(ptr_auxConceptData));
264 case Operation::Minimum:
265 return (ptr_auxConceptData->MinimumProcessed);
266 case Operation::Maximum:
267 return (ptr_auxConceptData->MaximumProcessed);
269 throw anna::RuntimeException("Unrecognized operation", ANNA_FILE_LOCATION);
272 //if (ptr_auxConceptData == NULL) return ((double)0); // Not possible: getConcept initializes it if not found.
276 //------------------------------------------------------------------------------
277 //------------------------------------------------------ Accumulator::asString()
278 //------------------------------------------------------------------------------
279 std::string Accumulator::asString(const int & numberOfDecimals) const throw() {
281 _concept_data_map_iter iter;
282 _concept_data_map_iter iter_min(a_concept_data_map.begin());
283 _concept_data_map_iter iter_max(a_concept_data_map.end());
284 time_t unixTimestamp;
285 time::Date time_now; // set current time on local TZ
286 time::Date time_aux; // for events
287 Engine& engine = Engine::instantiate();
288 std::string fFormat = floatFormat(numberOfDecimals);
289 trace = "\n=====================";
290 trace += "\nStatistic Information";
291 trace += "\n=====================";
292 trace += "\nCurrent Time: ";
293 trace += time_now.asString();
295 if(a_concept_data_map.size() == 0) {
296 trace += "\nNo concept data found (empty map)";
300 trace += "\nNumber of elements (measured concepts) = "; trace += anna::functions::asString(a_concept_data_map.size()); trace += "\n";
302 std::string conceptDescription;
303 std::string conceptUnitDescription;
304 bool integerNatureSample;
306 for(iter = iter_min; iter != iter_max; iter++) {
307 conceptId = (*iter).first;
308 const _concept_data_t * ptrConceptData = &((*iter).second);
309 engine.getConcept(conceptId, conceptDescription, conceptUnitDescription, integerNatureSample); // never launch exception
310 trace += "\n Concept: [";
311 trace += anna::functions::asString(conceptId);
313 trace += conceptDescription; trace += " ("; trace += conceptUnitDescription; trace += ")";
315 if(ptrConceptData->Size != 0) {
316 trace += "\n Sample Size: "; trace += anna::functions::asString((anna::U64)(ptrConceptData->Size));
317 trace += "\n Average: "; trace += anna::functions::asString(fFormat.c_str(), ptrConceptData->Average);
318 trace += "\n Standard Deviation: "; trace += anna::functions::asString(fFormat.c_str(), getStandardDeviation(ptrConceptData));
320 if(ptrConceptData->Size != 1) {
321 trace += "\n Bessel's Standard Deviation: "; trace += anna::functions::asString(fFormat.c_str(), getBesselStandardDeviation(ptrConceptData));
324 trace += "\n Minimum: ";
326 if(integerNatureSample)
327 trace += anna::functions::asString((int)(ptrConceptData->MinimumProcessed));
329 trace += anna::functions::asString(fFormat.c_str(), ptrConceptData->MinimumProcessed);
331 // Minimum Timestamp:
332 unixTimestamp = (ptrConceptData->MinimumEventTimestampMs / 1000); // not neccessary such precision
333 time_aux.storeUnix(unixTimestamp);
334 trace += " ("; trace += time_aux.asString(); trace += ")";
335 trace += "\n Maximum: ";
337 if(integerNatureSample)
338 trace += anna::functions::asString((int)(ptrConceptData->MaximumProcessed));
340 trace += anna::functions::asString(fFormat.c_str(), ptrConceptData->MaximumProcessed);
342 // Maximum Timestamp:
343 unixTimestamp = (ptrConceptData->MaximumEventTimestampMs / 1000); // not neccessary such precision
344 time_aux.storeUnix(unixTimestamp);
345 trace += " ("; trace += time_aux.asString(); trace += ")";
347 unixTimestamp = (ptrConceptData->LastResetEventTimestampMs / 1000); // not neccessary such precision
348 time_aux.storeUnix(unixTimestamp);
349 trace += "\n Last Reset Timestamp: "; trace += time_aux.asString();
351 // Processing Rate: it is not updated at 'process' to avoid 'time_now' use on every call
352 if(time_now != time_aux) {
353 trace += "\n Processing rate: ";
354 int lapse = time_now.getUnixTimestamp() - time_aux.getUnixTimestamp();
355 trace += anna::functions::asString(fFormat.c_str(), ptrConceptData->Size / ((double)lapse));
356 trace += " vps (values per second)";
359 trace += "\n Not enough data to get statistics";
368 //------------------------------------------------------------------------------
369 //--------------------------------------------------------- Accumulator::asXML()
370 //------------------------------------------------------------------------------
371 anna::xml::Node* Accumulator::asXML(anna::xml::Node* parent, const int & numberOfDecimals) const throw() {
372 anna::xml::Node* result = parent->createChild("anna.statistics.Accumulator");
373 _concept_data_map_iter iter;
374 _concept_data_map_iter iter_min(a_concept_data_map.begin());
375 _concept_data_map_iter iter_max(a_concept_data_map.end());
376 time_t unixTimestamp;
377 time::Date time_now; // set current time on local TZ
378 time::Date time_aux; // for events
379 Engine& engine = Engine::instantiate();
380 std::string fFormat = floatFormat(numberOfDecimals);
381 //result->createAttribute("CurrentTime", time_now.asString());
382 anna::xml::Node* currentTime = result->createChild("CurrentTime");
383 time_now.asXML(currentTime);
385 if(a_concept_data_map.size() == 0) {
386 result->createAttribute("ConceptDataMapSize", "0: No concept data found (empty map)");
390 result->createAttribute("ConceptDataMapSize", anna::functions::asString(a_concept_data_map.size()));
392 std::string conceptDescription;
393 std::string conceptUnitDescription;
394 bool integerNatureSample;
397 for(iter = iter_min; iter != iter_max; iter++) {
398 conceptId = (*iter).first;
399 const _concept_data_t * ptrConceptData = &((*iter).second);
400 engine.getConcept(conceptId, conceptDescription, conceptUnitDescription, integerNatureSample); // never launch exception
401 anna::xml::Node* concept = result->createChild("Concept");
402 concept->createAttribute("Id", conceptId);
403 concept->createAttribute("Description", conceptDescription);
404 concept->createAttribute("Unit", conceptUnitDescription);
405 anna::xml::Node* data = concept->createChild("Data");
407 if(ptrConceptData->Size != 0) {
408 data->createAttribute("Size", anna::functions::asString((anna::U64)ptrConceptData->Size));
409 data->createAttribute("Average", anna::functions::asString(fFormat.c_str(), ptrConceptData->Average));
410 data->createAttribute("StandardDeviation", anna::functions::asString(fFormat.c_str(), getStandardDeviation(ptrConceptData)));
412 if(ptrConceptData->Size != 1)
413 data->createAttribute("BesselStandardDeviation", anna::functions::asString(fFormat.c_str(), getBesselStandardDeviation(ptrConceptData)));
416 anna::xml::Node* minimum = data->createChild("Minimum");
418 if(integerNatureSample)
419 minimum->createAttribute("Value", anna::functions::asString((int)(ptrConceptData->MinimumProcessed)));
421 minimum->createAttribute("Value", anna::functions::asString(fFormat.c_str(), ptrConceptData->MinimumProcessed));
423 unixTimestamp = (ptrConceptData->MinimumEventTimestampMs / 1000); // not neccessary such precision
424 time_aux.storeUnix(unixTimestamp);
425 time_aux.asXML(minimum);
427 anna::xml::Node* maximum = data->createChild("Maximum");
429 if(integerNatureSample)
430 maximum->createAttribute("Value", anna::functions::asString((int)(ptrConceptData->MaximumProcessed)));
432 maximum->createAttribute("Value", anna::functions::asString(fFormat.c_str(), ptrConceptData->MaximumProcessed));
434 unixTimestamp = (ptrConceptData->MaximumEventTimestampMs / 1000); // not neccessary such precision
435 time_aux.storeUnix(unixTimestamp);
436 time_aux.asXML(maximum);
438 anna::xml::Node* lastResetEvent = data->createChild("LastResetTimestamp");
439 unixTimestamp = (ptrConceptData->LastResetEventTimestampMs / 1000); // not neccessary such precision
440 time_aux.storeUnix(unixTimestamp);
441 time_aux.asXML(lastResetEvent);
443 // Processing Rate: it is not updated at 'process' to avoid 'time_now' use on every call
444 if(time_now != time_aux) {
445 int lapse = time_now.getUnixTimestamp() - time_aux.getUnixTimestamp();
446 cad_aux = anna::functions::asString(fFormat.c_str(), ptrConceptData->Size / ((double)lapse)); cad_aux += " vps (values per second)";
447 data->createAttribute("ProcessingRate", cad_aux);
450 concept->createAttribute("Data", "Not enough data to get statistics");