358c87ccb94181fe934fae6dc6869d47b60e828e
[anna.git] / source / statistics / Accumulator.cpp
1 // ANNA - Anna is Not Nothingness Anymore                                                         //
2 //                                                                                                //
3 // (c) Copyright 2005-2015 Eduardo Ramos Testillano & Francisco Ruiz Rayo                         //
4 //                                                                                                //
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 //
7
8
9 //------------------------------------------------------------------------------
10 //--------------------------------------------------------------------- #include
11 //------------------------------------------------------------------------------
12
13
14 // Local
15 #include <anna/statistics/Accumulator.hpp>
16 #include <anna/statistics/Engine.hpp>
17
18 #include <anna/time/Date.hpp>
19
20 // Standard
21 #include <math.h>
22 #include <sys/time.h>
23 #include <limits.h>
24
25 #include <anna/xml/xml.hpp>
26 #include <anna/core/tracing/Logger.hpp>
27 #include <anna/core/tracing/TraceMethod.hpp>
28
29
30
31 using namespace anna::statistics;
32 using namespace anna::time;
33
34
35 //******************************************************************************
36 //------------------------------------------------------------------------------
37 //-------------------------------------------------- Accumulator::~Accumulator()
38 //------------------------------------------------------------------------------
39 Accumulator::~Accumulator() {
40 // LOGMETHOD (TraceMethod tttm ("anna::statistics::Accumulator", "Destructor", ANNA_FILE_LOCATION));
41   // TODO: anna::statistics::Engine::instantiate().releaseAccumulator(a_name);
42 }
43
44 // Private functions:
45
46
47 //------------------------------------------------------------------------------
48 //---------------------------------------------------- Accumulator::initialize()
49 //------------------------------------------------------------------------------
50 void Accumulator::initialize(const int & conceptId) throw() {
51   _concept_data_t conceptData;
52   conceptData.reset();
53   a_concept_data_map[conceptId] = conceptData;
54 }
55
56
57 //------------------------------------------------------------------------------
58 //---------------------------------------------------- Accumulator::addConcept()
59 //------------------------------------------------------------------------------
60 int Accumulator::addConcept(const std::string & description, const std::string & unit, const bool & integerNatureSample) throw() {
61   std::string conceptName = description;
62   conceptName += ": ";
63   conceptName += a_name;
64   return anna::statistics::Engine::instantiate().addConcept(conceptName, unit, integerNatureSample);
65 }
66
67
68 //------------------------------------------------------------------------------
69 //---------------------------------------------------- Accumulator::getConcept()
70 //------------------------------------------------------------------------------
71 _concept_data_t * Accumulator::getConcept(const int & conceptId) const throw(anna::RuntimeException) {
72   _concept_data_map_iter it = a_concept_data_map.find(conceptId);
73
74   if(it == a_concept_data_map.end()) {  // not found
75     // Check if concept id is registered:
76     Engine& engine = Engine::instantiate();
77     std::string conceptDescription, conceptUnitDescription;
78     bool integerNature;
79
80     if(!engine.getConcept(conceptId, conceptDescription, conceptUnitDescription, integerNature)) {
81       std::string msg = "Can't find at engine nothing about concept id = ";
82       msg += anna::functions::asString(conceptId);
83       throw anna::RuntimeException(msg, ANNA_FILE_LOCATION);
84     }
85
86     Accumulator * nc_this = const_cast<Accumulator *>(this);
87     nc_this->initialize(conceptId);
88     // (la otra posibilidad era hacer mutable el mapa e inicializar poniendo el contenido de 'initialize()')
89     it = a_concept_data_map.find(conceptId);
90   }
91
92   return ((_concept_data_t *) & ((*it).second));
93 }
94
95
96 //------------------------------------------------------------------------------
97 //--------------------------------------------------- Accumulator::floatFormat()
98 //------------------------------------------------------------------------------
99 std::string Accumulator::floatFormat(const int & numberOfDecimals) const throw() {
100   std::string result = "%.";
101   result += anna::functions::asString(numberOfDecimals);
102   result += "f";
103   return (result);
104 }
105
106
107
108
109
110 //------------------------------------------------------------------------------
111 //------------------------------------------ Accumulator::getStandardDeviation()
112 //------------------------------------------------------------------------------
113 double Accumulator::getStandardDeviation(const _concept_data_t * conceptData) const throw(anna::RuntimeException) {
114   // SD = sqrt (1/N SUM (xi^2) - X^2) = sqrt (SquareSum / N - Average^2)
115   if(conceptData->Size == 0)
116     throw anna::RuntimeException("Divide by zero: sample size = 0 for Standard Deviation !!", ANNA_FILE_LOCATION);
117
118   return (::sqrt((conceptData->SquareSum / conceptData->Size) - (::pow(conceptData->Average, (double)2))));
119 }
120
121
122 //------------------------------------------------------------------------------
123 //------------------------------------ Accumulator::getBesselStandardDeviation()
124 //------------------------------------------------------------------------------
125 double Accumulator::getBesselStandardDeviation(const _concept_data_t * conceptData) const throw(anna::RuntimeException) {
126   // BSD = sqrt (1/(N-1) SUM (xi^2) - N/(N-1) X^2) = sqrt (SquareSum / (N-1) - N/(N-1) Average^2)
127   if(conceptData->Size == 1)
128     throw anna::RuntimeException("Divide by zero: sample size = 1 for bessel's Standard Deviation !!", ANNA_FILE_LOCATION);
129
130   double N = conceptData->Size;
131   return (::sqrt((conceptData->SquareSum / (N - 1)) - (N / (N - 1)) * (::pow(conceptData->Average, (double)2))));
132 }
133
134
135
136 // Public functions:
137
138
139 //------------------------------------------------------------------------------
140 //------------------------------------------------------- Accumulator::process()
141 //------------------------------------------------------------------------------
142 void Accumulator::process(const int & conceptId, const double & value) throw(anna::RuntimeException) {
143 // LOGMETHOD (TraceMethod tttm ("anna::statistics::Accumulator", "process", ANNA_FILE_LOCATION));
144   Engine& engine = Engine::instantiate();
145
146   if(!engine.enabled()) return;
147
148   _concept_data_t *ptr_auxConceptData = getConcept(conceptId);
149   anna::Millisecond current = anna::Millisecond::getTime();
150   // Optional sample file dump:
151   Engine::instantiate().logSample(conceptId, current, value); // Accumulator is friend of Engine
152
153   // Statistics calculations
154   if(ptr_auxConceptData->Size == ULLONG_MAX)  // Statistics is better during processing until reset
155     ptr_auxConceptData->reset();
156
157   ptr_auxConceptData->Size ++;
158
159   // If first:
160   if(ptr_auxConceptData->Size == 1) {
161     ptr_auxConceptData->MinimumProcessed = value;
162     ptr_auxConceptData->MaximumProcessed = value;
163     ptr_auxConceptData->MinimumEventTimestampMs = current;
164     ptr_auxConceptData->MaximumEventTimestampMs = current;
165   } else {
166     if(value < ptr_auxConceptData->MinimumProcessed) {
167       ptr_auxConceptData->MinimumProcessed = value;
168       ptr_auxConceptData->MinimumEventTimestampMs = current;
169     }
170
171     if(value > ptr_auxConceptData->MaximumProcessed) {
172       ptr_auxConceptData->MaximumProcessed = value;
173       ptr_auxConceptData->MaximumEventTimestampMs = current;
174     }
175   }
176
177   ptr_auxConceptData->Sum += value;
178   ptr_auxConceptData->SquareSum += ::pow(value, (double)2); // (*) standard deviation formula simplification
179   ptr_auxConceptData->Average = ptr_auxConceptData->Sum / ptr_auxConceptData->Size;
180   //ptr_auxConceptData->SquareSumSampleDeviation += ::pow(value - ptr_auxConceptData->Average, (double)2);
181   // Don't use former method to get standard deviation because is not correct: in each processing, average
182   //  will be change (is not fixed). There is a simplification for standard deviation, based on square sum (*):
183   // Take X as average for x1, x2, x3, etc., xN
184   // SUM (xi - X)^2    = SUM (xi^2 + X^2 - 2xiX) = SUM (xi^2) + NX^2 - 2X SUM (xi) =
185   //                   = SUM (xi`2) + NX^2 - 2X(NX) = SUM (xi^2) - NX^2
186   //
187   // Then, SD = sqrt (1/N SUM (xi^2) - X^2) = sqrt (SquareSum / N - Average^2)
188   // And corrected SD (Bessel's standard deviation) is:
189   // BSD = sqrt (1/(N-1) SUM (xi^2) - N/(N-1) X^2) = sqrt (SquareSum / (N-1) - N/(N-1) Average^2)
190   // All these values don't need to be calculated now
191   // Worked with pointer
192   //a_concept_data_map[conceptId] = *ptr_auxConceptData;
193 }
194
195
196 //------------------------------------------------------------------------------
197 //------------------------------------------------------- Accumulator::operator=
198 //------------------------------------------------------------------------------
199 const Accumulator & Accumulator::operator = (const Accumulator & accumulator) {
200 // LOGMETHOD (TraceMethod tttm ("anna::statistics::Accumulator", "operator=", ANNA_FILE_LOCATION));
201   // Avoid self copy: i.e., Accumulator a,b; a=&b; a=b; b=a;
202   if(this == &accumulator) return (*this);
203
204   a_concept_data_map = accumulator.a_concept_data_map;
205   return (*this);
206 }
207
208
209
210 //------------------------------------------------------------------------------
211 //--------------------------------------------------------- Accumulator::reset()
212 //------------------------------------------------------------------------------
213 void Accumulator::reset(void) throw() {
214 // LOGMETHOD (TraceMethod tttm ("anna::statistics::Accumulator", "reset", ANNA_FILE_LOCATION));
215   _concept_data_map_iter it;
216   _concept_data_map_iter it_min(a_concept_data_map.begin());
217   _concept_data_map_iter it_max(a_concept_data_map.end());
218
219   for(it = it_min; it != it_max; it++) {
220     //reset ((*it).first);
221     _concept_data_t * ptr_auxConceptData = (_concept_data_t*) & ((*it).second);
222     ptr_auxConceptData->reset();
223   }
224 }
225
226
227 //------------------------------------------------------------------------------
228 //--------------------------------------------------------- Accumulator::reset()
229 //------------------------------------------------------------------------------
230 void Accumulator::reset(const int & conceptId) throw(anna::RuntimeException) {
231 // LOGMETHOD (TraceMethod tttm ("anna::statistics::Accumulator", "reset", ANNA_FILE_LOCATION));
232   _concept_data_t *ptr_auxConceptData = getConcept(conceptId);
233   //if (ptr_auxConceptData == NULL) return; // Not possible: getConcept initializes it if not found.
234   ptr_auxConceptData->reset();
235 }
236
237
238 // Gets:
239
240 //------------------------------------------------------------------------------
241 //---------------------------------------------------- Accumulator::sampleSize()
242 //------------------------------------------------------------------------------
243 unsigned long long int Accumulator::sampleSize(const int & conceptId) const throw(anna::RuntimeException) {
244 // LOGMETHOD (TraceMethod tttm ("anna::statistics::Accumulator", "sampleSize", ANNA_FILE_LOCATION));
245   _concept_data_t *ptr_auxConceptData = getConcept(conceptId);
246   //if (ptr_auxConceptData == NULL) return 0; // Not possible: getConcept initializes it if not found.
247   return (ptr_auxConceptData->Size);
248 }
249
250
251 //------------------------------------------------------------------------------
252 //------------------------------------------------------ Accumulator::getValue()
253 //------------------------------------------------------------------------------
254 double Accumulator::getValue(const int & conceptId, const Operation::Type & operation) const throw(anna::RuntimeException) {
255 // LOGMETHOD (TraceMethod tttm ("anna::statistics::Accumulator", "getValue", ANNA_FILE_LOCATION));
256   const _concept_data_t *ptr_auxConceptData = getConcept(conceptId);
257
258   switch(operation) {
259     //case Operation::Sum:
260     // return (ptr_auxConceptData->Sum);
261   case Operation::Average:
262     return (ptr_auxConceptData->Average);
263   case Operation::StandardDeviation:
264     return (getStandardDeviation(ptr_auxConceptData));
265   case Operation::BesselStandardDeviation:
266     return (getBesselStandardDeviation(ptr_auxConceptData));
267   case Operation::Minimum:
268     return (ptr_auxConceptData->MinimumProcessed);
269   case Operation::Maximum:
270     return (ptr_auxConceptData->MaximumProcessed);
271   default:
272     throw anna::RuntimeException("Unrecognized operation", ANNA_FILE_LOCATION);
273   }
274
275   //if (ptr_auxConceptData == NULL) return ((double)0); // Not possible: getConcept initializes it if not found.
276 }
277
278
279 //------------------------------------------------------------------------------
280 //------------------------------------------------------ Accumulator::asString()
281 //------------------------------------------------------------------------------
282 std::string Accumulator::asString(const int & numberOfDecimals) const throw() {
283   std::string trace;
284   _concept_data_map_iter iter;
285   _concept_data_map_iter iter_min(a_concept_data_map.begin());
286   _concept_data_map_iter iter_max(a_concept_data_map.end());
287   time_t unixTimestamp;
288   time::Date time_now; // set current time on local TZ
289   time::Date time_aux; // for events
290   Engine& engine = Engine::instantiate();
291   std::string fFormat = floatFormat(numberOfDecimals);
292   trace =  "\n=====================";
293   trace +=  "\nStatistic Information";
294   trace +=  "\n=====================";
295   trace += "\nAccumulator name: ";
296   trace += a_name;
297   trace += "\nCurrent Time: ";
298   trace += time_now.asString();
299
300   if(a_concept_data_map.size() == 0) {
301     trace += "\nNo concept data found (empty map)";
302     return trace;
303   }
304
305   trace += "\nNumber of elements (measured concepts) = "; trace += anna::functions::asString(a_concept_data_map.size()); trace += "\n";
306   int conceptId;
307   std::string conceptDescription;
308   std::string conceptUnitDescription;
309   bool integerNatureSample;
310
311   for(iter = iter_min; iter != iter_max; iter++) {
312     conceptId = (*iter).first;
313     const _concept_data_t * ptrConceptData = &((*iter).second);
314     engine.getConcept(conceptId, conceptDescription, conceptUnitDescription, integerNatureSample);  // never launch exception
315     trace += "\n   Concept:              [";
316     trace += anna::functions::asString(conceptId);
317     trace += "] ";
318     trace += conceptDescription; trace += " ("; trace += conceptUnitDescription; trace += ")";
319
320     if(ptrConceptData->Size != 0) {
321       trace += "\n   Sample Size:                 "; trace += anna::functions::asString((anna::U64)(ptrConceptData->Size));
322       trace += "\n   Average:                     "; trace += anna::functions::asString(fFormat.c_str(), ptrConceptData->Average);
323       trace += "\n   Standard Deviation:          "; trace += anna::functions::asString(fFormat.c_str(), getStandardDeviation(ptrConceptData));
324
325       if(ptrConceptData->Size != 1) {
326         trace += "\n   Bessel's Standard Deviation: "; trace += anna::functions::asString(fFormat.c_str(), getBesselStandardDeviation(ptrConceptData));
327       }
328
329       trace += "\n   Minimum:                     ";
330
331       if(integerNatureSample)
332         trace += anna::functions::asString((int)(ptrConceptData->MinimumProcessed));
333       else
334         trace += anna::functions::asString(fFormat.c_str(), ptrConceptData->MinimumProcessed);
335
336       // Minimum Timestamp:
337       unixTimestamp = (ptrConceptData->MinimumEventTimestampMs / 1000); // not neccessary such precision
338       time_aux.storeUnix(unixTimestamp);
339       trace += " ("; trace += time_aux.asString(); trace += ")";
340       trace += "\n   Maximum:                     ";
341
342       if(integerNatureSample)
343         trace += anna::functions::asString((int)(ptrConceptData->MaximumProcessed));
344       else
345         trace += anna::functions::asString(fFormat.c_str(), ptrConceptData->MaximumProcessed);
346
347       // Maximum Timestamp:
348       unixTimestamp = (ptrConceptData->MaximumEventTimestampMs / 1000); // not neccessary such precision
349       time_aux.storeUnix(unixTimestamp);
350       trace += " ("; trace += time_aux.asString(); trace += ")";
351       // Last Reset Event:
352       unixTimestamp = (ptrConceptData->LastResetEventTimestampMs / 1000); // not neccessary such precision
353       time_aux.storeUnix(unixTimestamp);
354       trace += "\n   Last Reset Timestamp:        "; trace += time_aux.asString();
355
356       // Processing Rate: it is not updated at 'process' to avoid 'time_now' use on every call
357       if(time_now != time_aux) {
358         trace += "\n   Processing rate:             ";
359         int lapse = time_now.getUnixTimestamp() - time_aux.getUnixTimestamp();
360         trace += anna::functions::asString(fFormat.c_str(), ptrConceptData->Size / ((double)lapse));
361         trace += " vps (values per second)";
362       }
363     } else {
364       trace += "\n   Not enough data to get statistics";
365     }
366   }
367
368   trace += "\n";
369   return (trace);
370 }
371
372
373 //------------------------------------------------------------------------------
374 //--------------------------------------------------------- Accumulator::asXML()
375 //------------------------------------------------------------------------------
376 anna::xml::Node* Accumulator::asXML(anna::xml::Node* parent, const int & numberOfDecimals) const throw() {
377   anna::xml::Node* result = parent->createChild("anna.statistics.Accumulator");
378   result->createAttribute("Name", a_name);
379   _concept_data_map_iter iter;
380   _concept_data_map_iter iter_min(a_concept_data_map.begin());
381   _concept_data_map_iter iter_max(a_concept_data_map.end());
382   time_t unixTimestamp;
383   time::Date time_now; // set current time on local TZ
384   time::Date time_aux; // for events
385   Engine& engine = Engine::instantiate();
386   std::string fFormat = floatFormat(numberOfDecimals);
387   //result->createAttribute("CurrentTime", time_now.asString());
388   anna::xml::Node* currentTime = result->createChild("CurrentTime");
389   time_now.asXML(currentTime);
390
391   if(a_concept_data_map.size() == 0) {
392     result->createAttribute("ConceptDataMapSize", "0: No concept data found (empty map)");
393     return result;
394   }
395
396   result->createAttribute("ConceptDataMapSize", anna::functions::asString(a_concept_data_map.size()));
397   int conceptId;
398   std::string conceptDescription;
399   std::string conceptUnitDescription;
400   bool integerNatureSample;
401   std::string cad_aux;
402
403   for(iter = iter_min; iter != iter_max; iter++) {
404     conceptId = (*iter).first;
405     const _concept_data_t * ptrConceptData = &((*iter).second);
406     engine.getConcept(conceptId, conceptDescription, conceptUnitDescription, integerNatureSample);  // never launch exception
407     anna::xml::Node* concept = result->createChild("Concept");
408     concept->createAttribute("Id", conceptId);
409     concept->createAttribute("Description", conceptDescription);
410     concept->createAttribute("Unit", conceptUnitDescription);
411     anna::xml::Node* data = concept->createChild("Data");
412
413     if(ptrConceptData->Size != 0) {
414       data->createAttribute("Size", anna::functions::asString((anna::U64)ptrConceptData->Size));
415       data->createAttribute("Average", anna::functions::asString(fFormat.c_str(), ptrConceptData->Average));
416       data->createAttribute("StandardDeviation", anna::functions::asString(fFormat.c_str(), getStandardDeviation(ptrConceptData)));
417
418       if(ptrConceptData->Size != 1)
419         data->createAttribute("BesselStandardDeviation", anna::functions::asString(fFormat.c_str(), getBesselStandardDeviation(ptrConceptData)));
420
421       // Minimum:
422       anna::xml::Node* minimum = data->createChild("Minimum");
423
424       if(integerNatureSample)
425         minimum->createAttribute("Value", anna::functions::asString((int)(ptrConceptData->MinimumProcessed)));
426       else
427         minimum->createAttribute("Value", anna::functions::asString(fFormat.c_str(), ptrConceptData->MinimumProcessed));
428
429       unixTimestamp = (ptrConceptData->MinimumEventTimestampMs / 1000); // not neccessary such precision
430       time_aux.storeUnix(unixTimestamp);
431       time_aux.asXML(minimum);
432       // Maximum:
433       anna::xml::Node* maximum = data->createChild("Maximum");
434
435       if(integerNatureSample)
436         maximum->createAttribute("Value", anna::functions::asString((int)(ptrConceptData->MaximumProcessed)));
437       else
438         maximum->createAttribute("Value", anna::functions::asString(fFormat.c_str(), ptrConceptData->MaximumProcessed));
439
440       unixTimestamp = (ptrConceptData->MaximumEventTimestampMs / 1000); // not neccessary such precision
441       time_aux.storeUnix(unixTimestamp);
442       time_aux.asXML(maximum);
443       // Last Reset Event:
444       anna::xml::Node* lastResetEvent = data->createChild("LastResetTimestamp");
445       unixTimestamp = (ptrConceptData->LastResetEventTimestampMs / 1000); // not neccessary such precision
446       time_aux.storeUnix(unixTimestamp);
447       time_aux.asXML(lastResetEvent);
448
449       // Processing Rate: it is not updated at 'process' to avoid 'time_now' use on every call
450       if(time_now != time_aux) {
451         int lapse = time_now.getUnixTimestamp() - time_aux.getUnixTimestamp();
452         cad_aux = anna::functions::asString(fFormat.c_str(), ptrConceptData->Size / ((double)lapse)); cad_aux += " vps (values per second)";
453         data->createAttribute("ProcessingRate", cad_aux);
454       }
455     } else {
456       concept->createAttribute("Data", "Not enough data to get statistics");
457     }
458   }
459
460   return result;
461 }