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