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 //------------------------------------------------------------------------------
39 Accumulator::~Accumulator() {
40 // LOGMETHOD (TraceMethod tttm ("anna::statistics::Accumulator", "Destructor", ANNA_FILE_LOCATION));
41 // TODO: anna::statistics::Engine::instantiate().releaseAccumulator(a_name);
47 //------------------------------------------------------------------------------
48 //---------------------------------------------------- Accumulator::initialize()
49 //------------------------------------------------------------------------------
50 void Accumulator::initialize(const int & conceptId) throw() {
51 _concept_data_t conceptData;
53 a_concept_data_map[conceptId] = conceptData;
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;
63 conceptName += a_name;
64 return anna::statistics::Engine::instantiate().addConcept(conceptName, unit, integerNatureSample);
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);
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;
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);
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);
92 return ((_concept_data_t *) & ((*it).second));
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);
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);
118 return (::sqrt((conceptData->SquareSum / conceptData->Size) - (::pow(conceptData->Average, (double)2))));
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);
130 double N = conceptData->Size;
131 return (::sqrt((conceptData->SquareSum / (N - 1)) - (N / (N - 1)) * (::pow(conceptData->Average, (double)2))));
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();
146 if(!engine.enabled()) return;
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
153 // Statistics calculations
154 if(ptr_auxConceptData->Size == ULLONG_MAX) // Statistics is better during processing until reset
155 ptr_auxConceptData->reset();
157 ptr_auxConceptData->Size ++;
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;
166 if(value < ptr_auxConceptData->MinimumProcessed) {
167 ptr_auxConceptData->MinimumProcessed = value;
168 ptr_auxConceptData->MinimumEventTimestampMs = current;
171 if(value > ptr_auxConceptData->MaximumProcessed) {
172 ptr_auxConceptData->MaximumProcessed = value;
173 ptr_auxConceptData->MaximumEventTimestampMs = current;
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
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;
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);
204 a_concept_data_map = accumulator.a_concept_data_map;
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());
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();
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();
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);
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);
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);
272 throw anna::RuntimeException("Unrecognized operation", ANNA_FILE_LOCATION);
275 //if (ptr_auxConceptData == NULL) return ((double)0); // Not possible: getConcept initializes it if not found.
279 //------------------------------------------------------------------------------
280 //------------------------------------------------------ Accumulator::asString()
281 //------------------------------------------------------------------------------
282 std::string Accumulator::asString(const int & numberOfDecimals) const throw() {
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: ";
297 trace += "\nCurrent Time: ";
298 trace += time_now.asString();
300 if(a_concept_data_map.size() == 0) {
301 trace += "\nNo concept data found (empty map)";
305 trace += "\nNumber of elements (measured concepts) = "; trace += anna::functions::asString(a_concept_data_map.size()); trace += "\n";
307 std::string conceptDescription;
308 std::string conceptUnitDescription;
309 bool integerNatureSample;
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);
318 trace += conceptDescription; trace += " ("; trace += conceptUnitDescription; trace += ")";
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));
325 if(ptrConceptData->Size != 1) {
326 trace += "\n Bessel's Standard Deviation: "; trace += anna::functions::asString(fFormat.c_str(), getBesselStandardDeviation(ptrConceptData));
329 trace += "\n Minimum: ";
331 if(integerNatureSample)
332 trace += anna::functions::asString((int)(ptrConceptData->MinimumProcessed));
334 trace += anna::functions::asString(fFormat.c_str(), ptrConceptData->MinimumProcessed);
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: ";
342 if(integerNatureSample)
343 trace += anna::functions::asString((int)(ptrConceptData->MaximumProcessed));
345 trace += anna::functions::asString(fFormat.c_str(), ptrConceptData->MaximumProcessed);
347 // Maximum Timestamp:
348 unixTimestamp = (ptrConceptData->MaximumEventTimestampMs / 1000); // not neccessary such precision
349 time_aux.storeUnix(unixTimestamp);
350 trace += " ("; trace += time_aux.asString(); trace += ")";
352 unixTimestamp = (ptrConceptData->LastResetEventTimestampMs / 1000); // not neccessary such precision
353 time_aux.storeUnix(unixTimestamp);
354 trace += "\n Last Reset Timestamp: "; trace += time_aux.asString();
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)";
364 trace += "\n Not enough data to get statistics";
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);
391 if(a_concept_data_map.size() == 0) {
392 result->createAttribute("ConceptDataMapSize", "0: No concept data found (empty map)");
396 result->createAttribute("ConceptDataMapSize", anna::functions::asString(a_concept_data_map.size()));
398 std::string conceptDescription;
399 std::string conceptUnitDescription;
400 bool integerNatureSample;
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");
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)));
418 if(ptrConceptData->Size != 1)
419 data->createAttribute("BesselStandardDeviation", anna::functions::asString(fFormat.c_str(), getBesselStandardDeviation(ptrConceptData)));
422 anna::xml::Node* minimum = data->createChild("Minimum");
424 if(integerNatureSample)
425 minimum->createAttribute("Value", anna::functions::asString((int)(ptrConceptData->MinimumProcessed)));
427 minimum->createAttribute("Value", anna::functions::asString(fFormat.c_str(), ptrConceptData->MinimumProcessed));
429 unixTimestamp = (ptrConceptData->MinimumEventTimestampMs / 1000); // not neccessary such precision
430 time_aux.storeUnix(unixTimestamp);
431 time_aux.asXML(minimum);
433 anna::xml::Node* maximum = data->createChild("Maximum");
435 if(integerNatureSample)
436 maximum->createAttribute("Value", anna::functions::asString((int)(ptrConceptData->MaximumProcessed)));
438 maximum->createAttribute("Value", anna::functions::asString(fFormat.c_str(), ptrConceptData->MaximumProcessed));
440 unixTimestamp = (ptrConceptData->MaximumEventTimestampMs / 1000); // not neccessary such precision
441 time_aux.storeUnix(unixTimestamp);
442 time_aux.asXML(maximum);
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);
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);
456 concept->createAttribute("Data", "Not enough data to get statistics");