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;
38 //------------------------------------------------------------------------------
39 //---------------------------------------------------- Accumulator::initialize()
40 //------------------------------------------------------------------------------
41 void Accumulator::initialize(const int & conceptId) throw() {
42 _concept_data_t conceptData;
44 a_concept_data_map[conceptId] = conceptData;
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);
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);
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;
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);
75 Accumulator * nc_this = const_cast<Accumulator *>(this);
76 nc_this->initialize(conceptId);
77 it = a_concept_data_map.find(conceptId);
80 return ((_concept_data_t *) & ((*it).second));
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);
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);
103 return (::sqrt((conceptData->SquareSum / conceptData->Size) - (::pow(conceptData->Average, (double)2))));
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);
115 double N = conceptData->Size;
116 return (::sqrt((conceptData->SquareSum / (N - 1)) - (N / (N - 1)) * (::pow(conceptData->Average, (double)2))));
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();
131 if(!engine.enabled()) return;
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
138 // Statistics calculations
139 if(ptr_auxConceptData->Size == ULLONG_MAX) // Statistics is better during processing until reset
140 ptr_auxConceptData->reset();
142 ptr_auxConceptData->Size ++;
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;
151 if(value < ptr_auxConceptData->MinimumProcessed) {
152 ptr_auxConceptData->MinimumProcessed = value;
153 ptr_auxConceptData->MinimumEventTimestampMs = current;
156 if(value > ptr_auxConceptData->MaximumProcessed) {
157 ptr_auxConceptData->MaximumProcessed = value;
158 ptr_auxConceptData->MaximumEventTimestampMs = current;
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
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;
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);
189 a_concept_data_map = accumulator.a_concept_data_map;
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());
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();
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();
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);
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);
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);
259 throw anna::RuntimeException("Unrecognized operation", ANNA_FILE_LOCATION);
262 //if (ptr_auxConceptData == NULL) return ((double)0); // Not possible: getConcept initializes it if not found.
266 //------------------------------------------------------------------------------
267 //------------------------------------------------------ Accumulator::asString()
268 //------------------------------------------------------------------------------
269 std::string Accumulator::asString(const int & numberOfDecimals) const throw() {
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: ";
284 trace += "\nCurrent Time: ";
285 trace += time_now.asString();
287 if(a_concept_data_map.size() == 0) {
288 trace += "\nNo concept data found (empty map)";
292 trace += "\nNumber of elements (measured concepts) = "; trace += anna::functions::asString(a_concept_data_map.size()); trace += "\n";
294 std::string conceptDescription;
295 std::string conceptUnitDescription;
296 bool integerNatureSample;
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);
305 trace += conceptDescription; trace += " ("; trace += conceptUnitDescription; trace += ")";
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));
312 if(ptrConceptData->Size != 1) {
313 trace += "\n Bessel's Standard Deviation: "; trace += anna::functions::asString(fFormat.c_str(), getBesselStandardDeviation(ptrConceptData));
316 trace += "\n Minimum: ";
318 if(integerNatureSample)
319 trace += anna::functions::asString((int)(ptrConceptData->MinimumProcessed));
321 trace += anna::functions::asString(fFormat.c_str(), ptrConceptData->MinimumProcessed);
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: ";
329 if(integerNatureSample)
330 trace += anna::functions::asString((int)(ptrConceptData->MaximumProcessed));
332 trace += anna::functions::asString(fFormat.c_str(), ptrConceptData->MaximumProcessed);
334 // Maximum Timestamp:
335 unixTimestamp = (ptrConceptData->MaximumEventTimestampMs / 1000); // not neccessary such precision
336 time_aux.storeUnix(unixTimestamp);
337 trace += " ("; trace += time_aux.asString(); trace += ")";
339 unixTimestamp = (ptrConceptData->LastResetEventTimestampMs / 1000); // not neccessary such precision
340 time_aux.storeUnix(unixTimestamp);
341 trace += "\n Last Reset Timestamp: "; trace += time_aux.asString();
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)";
351 trace += "\n Not enough data to get statistics";
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);
378 if(a_concept_data_map.size() == 0) {
379 result->createAttribute("ConceptDataMapSize", "0: No concept data found (empty map)");
383 result->createAttribute("ConceptDataMapSize", anna::functions::asString(a_concept_data_map.size()));
385 std::string conceptDescription;
386 std::string conceptUnitDescription;
387 bool integerNatureSample;
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");
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)));
405 if(ptrConceptData->Size != 1)
406 data->createAttribute("BesselStandardDeviation", anna::functions::asString(fFormat.c_str(), getBesselStandardDeviation(ptrConceptData)));
409 anna::xml::Node* minimum = data->createChild("Minimum");
411 if(integerNatureSample)
412 minimum->createAttribute("Value", anna::functions::asString((int)(ptrConceptData->MinimumProcessed)));
414 minimum->createAttribute("Value", anna::functions::asString(fFormat.c_str(), ptrConceptData->MinimumProcessed));
416 unixTimestamp = (ptrConceptData->MinimumEventTimestampMs / 1000); // not neccessary such precision
417 time_aux.storeUnix(unixTimestamp);
418 time_aux.asXML(minimum);
420 anna::xml::Node* maximum = data->createChild("Maximum");
422 if(integerNatureSample)
423 maximum->createAttribute("Value", anna::functions::asString((int)(ptrConceptData->MaximumProcessed)));
425 maximum->createAttribute("Value", anna::functions::asString(fFormat.c_str(), ptrConceptData->MaximumProcessed));
427 unixTimestamp = (ptrConceptData->MaximumEventTimestampMs / 1000); // not neccessary such precision
428 time_aux.storeUnix(unixTimestamp);
429 time_aux.asXML(maximum);
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);
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);
443 concept->createAttribute("Sample", "Not enough data to get statistics");