GammaLib  2.1.0.dev
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Pages
GDaemon.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  * GDaemon.cpp - Daemon class *
3  * ----------------------------------------------------------------------- *
4  * copyright (C) 2022 by Juergen Knoedlseder *
5  * ----------------------------------------------------------------------- *
6  * *
7  * This program is free software: you can redistribute it and/or modify *
8  * it under the terms of the GNU General Public License as published by *
9  * the Free Software Foundation, either version 3 of the License, or *
10  * (at your option) any later version. *
11  * *
12  * This program is distributed in the hope that it will be useful, *
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of *
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
15  * GNU General Public License for more details. *
16  * *
17  * You should have received a copy of the GNU General Public License *
18  * along with this program. If not, see <http://www.gnu.org/licenses/>. *
19  * *
20  ***************************************************************************/
21 /**
22  * @file GDaemon.cpp
23  * @brief Daemon class implementation
24  * @author Juergen Knoedlseder
25  */
26 
27 /* __ Includes ___________________________________________________________ */
28 #ifdef HAVE_CONFIG_H
29 #include <config.h>
30 #endif
31 #include <cstdlib> // std::getenv()
32 #include <cstdio> // std::fopen()
33 #include <unistd.h> // sleep()
34 #include <fcntl.h> // for file locking
35 #include <signal.h> // sigaction
36 #include <string.h> // memset
37 #if defined(__linux__) || defined(__linux) || defined(linux) || defined(__gnu_linux__)
38 #include <sys/prctl.h> // prctl(), PR_SET_NAME
39 #endif
40 #include "GDaemon.hpp"
41 #include "GCsv.hpp"
42 #include "GTime.hpp"
43 #include "GXml.hpp"
44 #include "GXmlNode.hpp"
45 
46 /* __ Method name definitions ____________________________________________ */
47 
48 /* __ Macros _____________________________________________________________ */
49 
50 /* __ Coding definitions _________________________________________________ */
51 #define G_APPEND_LOG //!< Append output to any existing log file
52 
53 /* __ Debug definitions __________________________________________________ */
54 
55 
56 /*==========================================================================
57  = =
58  = Constructors/destructors =
59  = =
60  ==========================================================================*/
61 
62 /***********************************************************************//**
63  * @brief Void constructor
64  ***************************************************************************/
66 {
67  // Initialise class members
68  init_members();
69 
70  // Return
71  return;
72 }
73 
74 
75 /***********************************************************************//**
76  * @brief Copy constructor
77  *
78  * @param[in] daemon Daemon.
79  ***************************************************************************/
80 GDaemon::GDaemon(const GDaemon& daemon)
81 {
82  // Initialise class members
83  init_members();
84 
85  // Copy members
86  copy_members(daemon);
87 
88  // Return
89  return;
90 }
91 
92 
93 /***********************************************************************//**
94  * @brief Destructor
95  ***************************************************************************/
97 {
98  // Free members
99  free_members();
100 
101  // Return
102  return;
103 }
104 
105 
106 /*==========================================================================
107  = =
108  = Operators =
109  = =
110  ==========================================================================*/
111 
112 /***********************************************************************//**
113  * @brief Assignment operator
114  *
115  * @param[in] daemon Daemon.
116  * @return Daemon.
117  ***************************************************************************/
119 {
120  // Execute only if object is not identical
121  if (this != &daemon) {
122 
123  // Free members
124  free_members();
125 
126  // Initialise private members
127  init_members();
128 
129  // Copy members
130  copy_members(daemon);
131 
132  } // endif: object was not identical
133 
134  // Return this object
135  return *this;
136 }
137 
138 
139 /*==========================================================================
140  = =
141  = Public methods =
142  = =
143  ==========================================================================*/
144 
145 /***********************************************************************//**
146  * @brief Clear Daemon
147  ***************************************************************************/
148 void GDaemon::clear(void)
149 {
150  // Free members
151  free_members();
152 
153  // Initialise private members
154  init_members();
155 
156  // Return
157  return;
158 }
159 
160 
161 /***********************************************************************//**
162  * @brief Clone Daemon
163  *
164  * @return Pointer to deep copy of Daemon.
165  ***************************************************************************/
167 {
168  return new GDaemon(*this);
169 }
170 
171 
172 /***********************************************************************//**
173  * @brief Starts the daemon
174  *
175  * Starts up the daemon and entered the never-ending daemon event loop.
176  ***************************************************************************/
177 void GDaemon::start(void)
178 {
179  // Get process ID
180  m_pid = getpid();
181 
182  // Open logger
183  #if defined(G_APPEND_LOG)
184  m_log.open(gammalib::gamma_filename("daemon.log"), false);
185  #else
186  m_log.open(gammalib::gamma_filename("daemon.log"), true);
187  #endif
189  m_log.date(true);
190 
191  // Log start up of daemon
192  m_log << "[" << (int)(m_pid) << "] ";
193  m_log << "GammaLib daemon start up" << std::endl;
194 
195  // Set daemon name
196  #if defined(__linux__) || defined(__linux) || defined(linux) || defined(__gnu_linux__)
197  prctl(PR_SET_NAME, "gammalibd");
198  #endif
199 
200  // Create lock file
202 
203  // Create heartbeat file
204  write_heartbeat();
205 
206  // Main event handling loop
207  while (true) {
208 
209  // If another daemon is running then exit this daemon
210  pid_t pid = lock_pid();
211  if ((pid != 0) && (pid != m_pid)) {
212  m_log << "[" << (int)(m_pid) << "] ";
213  m_log << "Another daemon with PID " << (int)(pid) << " is running, ";
214  m_log << "exit this daemon" << std::endl;
215  break;
216  }
217 
218  // Put all activities into try-catch block
219  try {
220 
221  // Update host-country
223 
224  // Update application statistics
226 
227  }
228  catch (const std::exception &except) {
229  m_log << "[" << (int)(m_pid) << "] ";
230  m_log << "Exception catched by daemon" << std::endl;
231  m_log << except.what() << std::endl;
232  }
233 
234  // Force logger flushing
235  m_log.flush(true);
236 
237  // Determine number of heartbeat cycles before wakeup
238  int ncycle = int(double(m_period) / double(m_heartbeat) + 0.5);
239  if (ncycle < 1) {
240  ncycle = 1;
241  }
242 
243  // Loop over heartbeat cycles and wait for heartbeats
244  for (int i = 0; i < ncycle; ++i) {
245  write_heartbeat();
246  sleep(m_heartbeat);
247  }
248 
249  } // endwhile: main event loop
250 
251  // Log termination of event loop
252  m_log << "[" << (int)(m_pid) << "] ";
253  m_log << "Terminated event loop" << std::endl;
254 
255  // Delete lock file
257 
258  // Force logger flushing
259  m_log.flush(true);
260 
261  // Close log file
262  m_log.close();
263 
264  // Return
265  return;
266 }
267 
268 
269 /***********************************************************************//**
270  * @brief Check if daemon is alive
271  *
272  * @return True if daemon is alive
273  *
274  * Checks if the daemon is alive by comparing the time found in the heartbeat
275  * file to the current time. If the heartbeat file is not older than twice
276  * the heartbeat period the daemon is considered alive.
277  *
278  * This method can be used by a client to check whether a daemon is alive.
279  * The method does not rely on any process ID and works even over multiple
280  * machines that share the same disk space.
281  ***************************************************************************/
282 bool GDaemon::alive(void) const
283 {
284  // Initialise flag
285  bool alive = false;
286 
287  // Set current time
288  GTime now;
289  now.now();
290 
291  // Get heartbeat filename
292  GFilename filename = heartbeat_filename();
293 
294  // OpenMP critical zone to listen for heartbeats
295  #pragma omp critical(GDaemon_alive)
296  {
297 
298  // Get file lock. Continue only in case of success.
299  struct flock lock;
300  lock.l_type = F_RDLCK; // Want a read lock
301  lock.l_whence = SEEK_SET; // Want beginning of file
302  lock.l_start = 0; // No offset, lock entire file ...
303  lock.l_len = 0; // ... to the end
304  lock.l_pid = getpid(); // Current process ID
305  int fd = open(filename.url().c_str(), O_RDONLY);
306  if (fd != -1) {
307 
308  // Lock file
309  fcntl(fd, F_SETLKW, &lock);
310 
311  // Open heartbeat file. Continue only if opening was successful.
312  FILE* fptr = fopen(filename.url().c_str(), "r");
313  if (fptr != NULL) {
314 
315  // Allocate line buffer
316  const int n = 1000;
317  char line[n];
318 
319  // Read line
320  fgets(line, n-1, fptr);
321 
322  // Close file
323  fclose(fptr);
324 
325  // Extract time. Put this in a try-catch block to catch
326  // any ill-conditioned time string
327  try {
328  GTime heartbeat;
329  heartbeat.utc(std::string(line));
330  if ((now - heartbeat) < 2.0 * double(m_heartbeat)) {
331  alive = true;
332  }
333  }
334  catch (const std::exception &except) {
335  ;
336  }
337 
338  } // endif: lock file opened successfully
339 
340  // Unlock file
341  lock.l_type = F_UNLCK;
342  fcntl(fd, F_SETLK, &lock);
343 
344  // Close file
345  close(fd);
346 
347  } // endif: file locking successful
348 
349  } // end of OMP critial zone
350 
351  // Return flag
352  return alive;
353 }
354 
355 
356 /***********************************************************************//**
357  * @brief Print Daemon
358  *
359  * @param[in] chatter Chattiness.
360  * @return String containing Daemon information.
361  ***************************************************************************/
362 std::string GDaemon::print(const GChatter& chatter) const
363 {
364  // Initialise result string
365  std::string result;
366 
367  // Continue only if chatter is not silent
368  if (chatter != SILENT) {
369 
370  // Append header
371  result.append("=== GDaemon ===");
372 
373  // Append information
374  result.append("\n"+gammalib::parformat("Process ID"));
375  result.append(gammalib::str((int)(m_pid)));
376  result.append("\n"+gammalib::parformat("Wake up period (s)"));
377  result.append(gammalib::str(m_period));
378  result.append("\n"+gammalib::parformat("Heartbeat period (s)"));
379  result.append(gammalib::str(m_heartbeat));
380  result.append("\n"+gammalib::parformat("Logger chatter level"));
381  result.append(gammalib::str(m_chatter));
382 
383  } // endif: chatter was not silent
384 
385  // Return result
386  return result;
387 }
388 
389 
390 /*==========================================================================
391  = =
392  = Private methods =
393  = =
394  ==========================================================================*/
395 
396 /***********************************************************************//**
397  * @brief Initialise class members
398  ***************************************************************************/
400 {
401  // Initialise members
402  m_pid = 0; //!< No process ID
403  m_period = 3600; //!< Wake-up daemon every hour
404  m_heartbeat = 60; //!< One heartbeat per minute
405  m_log.clear();
406  m_chatter = NORMAL; //!< NORMAL chatter level
407 
408  // Return
409  return;
410 }
411 
412 
413 /***********************************************************************//**
414  * @brief Copy class members
415  *
416  * @param[in] daemon Daemon.
417  ***************************************************************************/
418 void GDaemon::copy_members(const GDaemon& daemon)
419 {
420  // Copy members
421  m_pid = daemon.m_pid;
422  m_period = daemon.m_period;
423  m_heartbeat = daemon.m_heartbeat;
424  m_log = daemon.m_log;
425  m_chatter = daemon.m_chatter;
426 
427  // Return
428  return;
429 }
430 
431 
432 /***********************************************************************//**
433  * @brief Delete class members
434  ***************************************************************************/
436 {
437  // Delete lock file
439 
440  // Return
441  return;
442 }
443 
444 
445 /***********************************************************************//**
446  * @brief Create the lock file
447  *
448  * Creates the daemon lock file and write process ID into lock file.
449  ***************************************************************************/
451 {
452  // Get lock filename
453  GFilename lockfile = lock_filename();
454 
455  // Open lock file. Continue only if opening was successful.
456  FILE* fptr = fopen(lockfile.url().c_str(), "w");
457  if (fptr != NULL) {
458 
459  // Write process ID into lockfile
460  fprintf(fptr, "%d\n", (int)(m_pid));
461 
462  // Close lockfile
463  fclose(fptr);
464 
465  // Log creation of lock file
466  m_log << "[" << (int)(m_pid) << "] ";
467  m_log << "Created lock file \"" << lockfile.url();
468  m_log << "\"" << std::endl;
469 
470  } // endif: Lock file opened successfully
471 
472  // Return
473  return;
474 }
475 
476 
477 /***********************************************************************//**
478  * @brief Delete daemon lock file
479  *
480  * Deletes the daemon lock file on disk if the process ID in the lock file
481  * corresponds to the process ID of the instance.
482  ***************************************************************************/
484 {
485  // If process ID in lock file is the ID of the current process then
486  // delete the lock file
487  if (lock_pid() == getpid()) {
488 
489  // Get lock filename
490  GFilename lockfile = lock_filename();
491 
492  // Delete lock file
493  std::remove(lockfile.url().c_str());
494 
495  // Log removal of lock file
496  m_log << "[" << (int)(m_pid) << "] ";
497  m_log << "Removed lock file \"" << lockfile.url();
498  m_log << "\"" << std::endl;
499 
500  }
501 
502  // Return
503  return;
504 }
505 
506 
507 /***********************************************************************//**
508  * @brief Write heartbeat file
509  *
510  * Creates and updates the heartbeat file that contains the current UTC time.
511  ***************************************************************************/
513 {
514  // Set current time
515  GTime now;
516  now.now();
517 
518  // Get heartbeat filename
519  GFilename filename = heartbeat_filename();
520 
521  // Open heartbeat file. Continue only if opening was successful.
522  FILE* fptr = fopen(filename.url().c_str(), "w");
523  if (fptr != NULL) {
524 
525  // Write current time into heartbeat file
526  fprintf(fptr, "%s\n", now.utc().c_str());
527 
528  // Close lockfile
529  fclose(fptr);
530 
531  } // endif: Heartbeat file opened successfully
532 
533  // Return
534  return;
535 }
536 
537 
538 /***********************************************************************//**
539  * @brief Returns process ID in lock file
540  *
541  * @return Process ID in lock file.
542  *
543  * Returns the process ID that is found in the lock file. If no lock file
544  * exists the method returns 0.
545  ***************************************************************************/
546 pid_t GDaemon::lock_pid(void) const
547 {
548  // Initialis process ID
549  pid_t pid = 0;
550 
551  // Get lock filename
552  GFilename lockfile = lock_filename();
553 
554  // Open lock file. Continue only if opening was successful.
555  FILE* fptr = fopen(lockfile.url().c_str(), "r");
556  if (fptr != NULL) {
557 
558  // Allocate line buffer
559  const int n = 1000;
560  char line[n];
561 
562  // Read line
563  fgets(line, n-1, fptr);
564 
565  // Close file
566  fclose(fptr);
567 
568  // Extract process ID
569  pid = gammalib::toint(std::string(line));
570 
571  } // endif: lock file opened successfully
572 
573  // Return process ID
574  return pid;
575 }
576 
577 
578 /***********************************************************************//**
579  * @brief Update application statistics
580  *
581  * Update application statistics in the @p statistics.xml file by scanning
582  * the low-level file @p statistics.csv.
583  ***************************************************************************/
585 {
586  // Set low-level statistics filename
587  GFilename filename = gammalib::gamma_filename("statistics.csv");
588 
589  // Continue only if file exists
590  if (filename.exists()) {
591 
592  // OpenMP critical zone to read statitics
593  #pragma omp critical(GDaemon_update_statistics)
594  {
595 
596  // Get file lock. Continue only in case of success.
597  struct flock lock;
598  lock.l_type = F_RDLCK; // Want a read lock
599  lock.l_whence = SEEK_SET; // Want beginning of file
600  lock.l_start = 0; // No offset, lock entire file ...
601  lock.l_len = 0; // ... to the end
602  lock.l_pid = getpid(); // Current process ID
603  int fd = open(filename.url().c_str(), O_RDONLY);
604  if (fd != -1) {
605 
606  // Log updating of high-level statistics
607  m_log << "[" << (int)(m_pid) << "] ";
608  m_log << "Update high-level statistics" << std::endl;
609 
610  // Lock file
611  fcntl(fd, F_SETLKW, &lock);
612 
613  // Put all activities into try-catch block
614  try {
615 
616  // Load CSV file
617  GCsv statistics(filename, ",");
618 
619  // Recover valid "statistics.xml" file
621 
622  // Set high-level statistics filename
623  GFilename filename_work = gammalib::gamma_filename("statistics.xml");
624  GFilename filename_copy = gammalib::gamma_filename("statistics.xml~");
625 
626  // Load high-level statistics file
627  GXml xml;
628  xml.load(filename_work);
629 
630  // Update file only if XML document is not empty
631  if (!xml.is_empty()) {
632 
633  // Save XML file into a copy
634  xml.save(filename_copy);
635 
636  // Update dates in header
637  update_dates(xml, statistics);
638 
639  // Update countries in header and data
640  update_countries_header(xml, statistics);
641  update_countries_data(xml, statistics);
642 
643  // Update versions in data
644  update_versions_data(xml, statistics);
645 
646  // Update daily information
647  update_daily(xml, statistics);
648 
649  // Save updated high-level statistics XML file
650  xml.save(filename_work);
651 
652  // Now get rid of the copy
653  std::remove(filename_copy.url().c_str());
654 
655  // And finally get rid of the low-level statistics file
656  std::remove(filename.url().c_str());
657 
658  } // endif: XML document was not empty
659 
660  }
661  catch (const std::exception &except) {
662  m_log << "[" << (int)(m_pid) << "] ";
663  m_log << "Exception catched by daemon" << std::endl;
664  m_log << except.what() << std::endl;
665  }
666 
667  // Unlock file
668  lock.l_type = F_UNLCK;
669  fcntl(fd, F_SETLK, &lock);
670 
671  // Close file
672  close(fd);
673 
674  } // endif: file locking successful
675 
676  } // end of OMP critial zone
677 
678  } // endif: there was a statistics ASCII file
679 
680  // Return
681  return;
682 }
683 
684 
685 /***********************************************************************//**
686  * @brief Update host country
687  *
688  * Update host country code in $HOME/.gamma/host-country file.
689  ***************************************************************************/
691 {
692  // Get host country code by forcing a query
693  std::string country = gammalib::host_country(true);
694 
695  // Continue only if host country is a two-digit code
696  if (country.length() == 2) {
697 
698  // Set host country filename
699  GFilename filename = gammalib::gamma_filename("host-country");
700 
701  // OpenMP critical zone to write host country code
702  #pragma omp critical(GDaemon_update_host_country)
703  {
704 
705  // Open host-country file, and in case of success, write
706  // country
707  FILE* fptr = fopen(filename.url().c_str(), "w");
708  if (fptr != NULL) {
709  fprintf(fptr, "%s\n", country.c_str());
710  fclose(fptr);
711  }
712 
713  } // end of OMP critial zone
714 
715  } // endif: we had a two-digit country code
716 
717  // Return
718  return;
719 }
720 
721 
722 /***********************************************************************//**
723  * @brief Recovers a valid XML file
724  *
725  * This method recovers a valid XML file in @p statistics.xml. Several cases
726  * are covered.
727  *
728  * If none of the files @p statistics.xml and @p statistics.xml~ exists
729  * the method will create a new XML file using the create_xml() method.
730  *
731  * If only the copy @p statistics.xml~ exists there was a problem during
732  * writing the working file, hence the @p statistics.xml~ file is copied
733  * into @p statistics.xml. In case that @p statistics.xml~ is corrupt a
734  * new XML file is created.
735  *
736  * If both files exist, the integrity of both files is checked. If only
737  * @p statistics.xml is corrupted, @p statistics.xml~ will be copied
738  * into @p statistics.xml. If only @p statistics.xml~ is corrupted it
739  * is ignored. If both files are corrupted they are secured and a new XML
740  * file will be created using the create_xml() method. If both files are okay,
741  * the file @p statistics.xml is secured and @p statistics.xml~ is copied
742  * into @p statistics.xml.
743  *
744  * Before existing, any @p statistics.xml.copy file is removed.
745  ***************************************************************************/
747 {
748  // Set filenames
749  GFilename filename_work = gammalib::gamma_filename("statistics.xml");
750  GFilename filename_copy = gammalib::gamma_filename("statistics.xml~");
751 
752  // If none of the files exist then create a new working file
753  if (!filename_work.exists() && !filename_copy.exists()) {
754  create_xml(filename_work);
755  }
756 
757  // ... else if only the copy exists and if the copy is okay, then use it.
758  // Otherwise create a new XML file
759  else if (!filename_work.exists() && filename_copy.exists()) {
760  GXml xml_copy;
761  try {
762  xml_copy.load(filename_copy);
763  xml_copy.save(filename_work);
764  m_log << "[" << (int)(m_pid) << "] ";
765  m_log << "No \"statistics.xml\" file found, use copy ";
766  m_log << "\"statistics.xml~\"" << std::endl;
767  }
768  catch (const std::exception &except) {
769  m_log << "[" << (int)(m_pid) << "] ";
770  m_log << "No \"statistics.xml\" file found and corrupt ";
771  m_log << "\"statistics.xml~\" file encountered, secure ";
772  m_log << "it and create new XML file" << std::endl;
773  GTime now;
774  now.now();
775  std::string newcopy = filename_work.url() + "." + now.utc() + "~";
776  std::rename(filename_copy.url().c_str(), newcopy.c_str());
777  create_xml(filename_work);
778  }
779  }
780 
781  // ... else if both of the files exist then an issue occured in writing
782  // the working file (otherwise the copy would have been removed)
783  else if (filename_work.exists() && filename_copy.exists()) {
784 
785  // Check integrity of working file and copy
786  bool integrity_work = true;
787  bool integrity_copy = true;
788  GXml xml_work;
789  GXml xml_copy;
790  try {
791  xml_work.load(filename_work);
792  integrity_work = true;
793  }
794  catch (const std::exception &except) {
795  integrity_work = false;
796  }
797  try {
798  xml_copy.load(filename_copy);
799  integrity_copy = true;
800  }
801  catch (const std::exception &except) {
802  integrity_copy = false;
803  }
804 
805  // If only the copy is okay but the working file is corrupt the
806  // situation is clear: the working file got corrupted. In that
807  // case save copy as working file.
808  if (integrity_copy && !integrity_work) {
809  m_log << "[" << (int)(m_pid) << "] ";
810  m_log << "Corrupt \"statistics.xml\" file encountered, ";
811  m_log << "use copy \"statistics.xml~\"" << std::endl;
812  xml_copy.save(filename_work);
813  }
814 
815  // ... otherwise if both files are corrupted then rename the
816  // working file and create a new fresh high-level statistics.
817  // This case should actually never happen!
818  else if (!integrity_copy && !integrity_work) {
819  m_log << "[" << (int)(m_pid) << "] ";
820  m_log << "Corrupt \"statistics.xml\" and \"statistics.xml~\" ";
821  m_log << "files encountered, secure them and create new ";
822  m_log << "file" << std::endl;
823  GTime now;
824  now.now();
825  std::string newwork = filename_work.url() + "." + now.utc();
826  std::string newcopy = filename_work.url() + "." + now.utc() + "~";
827  std::rename(filename_work.url().c_str(), newwork.c_str());
828  std::rename(filename_copy.url().c_str(), newcopy.c_str());
829  create_xml(filename_work);
830  }
831 
832  // ... otherwise if both files are okay, then the working file is
833  // secured and the copy is used, as in normal workings it is not
834  // expected that a copy exists, so some issue happend during the
835  // last update. This case should actually never happen!
836  else if (integrity_copy && integrity_work) {
837  m_log << "[" << (int)(m_pid) << "] ";
838  m_log << "Unexpected \"statistics.xml\" file encountered, ";
839  m_log << "secure file and use copy \"statistics.xml~\"";
840  m_log << std::endl;
841  GTime now;
842  now.now();
843  std::string newwork = filename_work.url() + "." + now.utc();
844  std::rename(filename_work.url().c_str(), newwork.c_str());
845  xml_copy.save(filename_work);
846  }
847 
848  } // endelse: both files existed
849 
850  // Make sure we get rid of any copy
851  std::remove(filename_copy.url().c_str());
852 
853  // Return
854  return;
855 }
856 
857 
858 /***********************************************************************//**
859  * @brief Create high-level statistics XML file
860  *
861  * @param[in] filename File name.
862  *
863  * Creates an empty high-level statistics XML file with the specified
864  * @p filename. This method overwrites any existing XML file.
865  ***************************************************************************/
866 void GDaemon::create_xml(const GFilename& filename)
867 {
868  // Set current time
869  GTime now;
870  now.now();
871 
872  // Initialise high-level statistics XML object
873  GXml xml;
874 
875  // Append base node
876  GXmlNode* base = xml.append(GXmlElement("statistics title=\"High-level statistics\""));
877 
878  // Append header
879  GXmlNode* header = base->append("header");
880  GXmlNode* dates = header->append("dates");
881  GXmlNode* countries = header->append("countries");
882  GXmlNode* d1 = dates->append("creation");
883  d1->append(GXmlText(GXmlText(now.utc())));
884  GXmlNode* d2 = dates->append("modified");
885  d2->append(GXmlText(""));
886  GXmlNode* d3 = dates->append("start");
887  d3->append(GXmlText(""));
888  GXmlNode* d4 = dates->append("stop");
889  d4->append(GXmlText(""));
890 
891  // Append data
892  GXmlNode* data = base->append("data");
893  data->append("countries");
894  data->append("versions");
895  data->append("daily");
896 
897  // Log creation of high-level statistics file
898  m_log << "[" << (int)(m_pid) << "] ";
899  m_log << "Created high-level statistics XML file \"";
900  m_log << filename.url() << "\"" << std::endl;
901 
902  // Save high-level statistics XML file
903  xml.save(filename);
904 
905  // Return
906  return;
907 }
908 
909 
910 /***********************************************************************//**
911  * @brief Update dates in high-level statistics header.
912  *
913  * @param[in,out] xml High-level statistics XML object.
914  * @param[in] statistics Low-level statistics CSV object.
915  *
916  * Updates dates in the high-level statistics header.
917  ***************************************************************************/
918 void GDaemon::update_dates(GXml& xml, const GCsv& statistics)
919 {
920  // Set current time
921  GTime now;
922  now.now();
923 
924  // Get pointer to "header" node
925  GXmlNode* base = xml.element("statistics",0);
926  GXmlNode* header = base->element("header",0);
927 
928  // Update modified time in header
929  GXmlNode* modified = header->element("dates > modified");
930  if (modified->is_empty()) {
931  modified->append(GXmlText(now.utc()));
932  }
933  else {
934  static_cast<GXmlText*>((*modified)[0])->text(now.utc());
935  }
936 
937  // Update first and last time in header
938  std::string start;
939  std::string stop;
940  if (statistics.nrows() > 1) {
941  start = statistics.string(1,0);
942  stop = statistics.string(statistics.nrows()-1,0);
943  GXmlNode* start_node = header->element("dates > start");
944  GXmlNode* stop_node = header->element("dates > stop");
945  if (start_node->is_empty()) {
946  start_node->append(GXmlText(start));
947  }
948  else {
949  std::string current_start = static_cast<GXmlText*>((*start_node)[0])->text();
950  if (current_start.empty() || (start < current_start)) {
951  static_cast<GXmlText*>((*start_node)[0])->text(start);
952  }
953  }
954  if (stop_node->is_empty()) {
955  stop_node->append(GXmlText(stop));
956  }
957  else {
958  std::string current_stop = static_cast<GXmlText*>((*stop_node)[0])->text();
959  if (current_stop.empty() || (stop > current_stop)) {
960  static_cast<GXmlText*>((*stop_node)[0])->text(stop);
961  }
962  }
963  }
964 
965  // Return
966  return;
967 }
968 
969 
970 /***********************************************************************//**
971  * @brief Update countries in high-level statistics header.
972  *
973  * @param[in,out] xml High-level statistics XML object.
974  * @param[in] statistics Low-level statistics CSV object.
975  *
976  * Update country statistics in header block.
977  ***************************************************************************/
978 void GDaemon::update_countries_header(GXml& xml, const GCsv& statistics)
979 {
980  // Get pointer to "countries" node in "header" node. In case that no
981  // "countries" node exists in "header" node then append one.
982  GXmlNode* base = xml.element("statistics",0);
983  GXmlNode* header = base->element("header",0);
984  if (header->elements("countries") == 0) {
985  header->append("countries");
986  }
987  GXmlNode* countries = header->element("countries",0);
988 
989  // Loop over statistics
990  for (int i = 1; i < statistics.nrows(); ++i) {
991 
992  // Extract relevant attributes
993  std::string country = statistics.string(i,2);
994 
995  // If country is empty then set country to "unknown"
996  if (country.empty()) {
997  country = "unknown";
998  }
999 
1000  // Update list of countries in header
1001  int ncountries = countries->elements("country");
1002  if (ncountries == 0) {
1003  GXmlNode* n = countries->append("country");
1004  n->append(GXmlText(country));
1005  }
1006  else {
1007  bool found = false;
1008  for (int k = 0; k < ncountries; ++k) {
1009  if (countries->element("country", k)->value() == country) {
1010  found = true;
1011  break;
1012  }
1013  }
1014  if (!found) {
1015  GXmlNode* n = countries->append("country");
1016  n->append(GXmlText(country));
1017  }
1018  }
1019 
1020  } // endfor: looped over statistics
1021 
1022  // Return
1023  return;
1024 }
1025 
1026 
1027 /***********************************************************************//**
1028  * @brief Update countries in high-level statistics data.
1029  *
1030  * @param[in,out] xml High-level statistics XML object.
1031  * @param[in] statistics Low-level statistics CSV object.
1032  *
1033  * Update country statistics in data block.
1034  ***************************************************************************/
1035 void GDaemon::update_countries_data(GXml& xml, const GCsv& statistics)
1036 {
1037  // Get pointer to "countries" node. In case that no "countries" node
1038  // exists in "data" node then append one.
1039  GXmlNode* base = xml.element("statistics",0);
1040  GXmlNode* data = base->element("data",0);
1041  if (data->elements("countries") == 0) {
1042  data->append("countries");
1043  }
1044  GXmlNode* countries = data->element("countries",0);
1045 
1046  // Loop over statistics
1047  for (int i = 1; i < statistics.nrows(); ++i) {
1048 
1049  // Extract relevant attributes
1050  std::string country = statistics.string(i,2);
1051  double wall = statistics.real(i,5);
1052  double cpu = statistics.real(i,6);
1053  double gCO2e = statistics.real(i,7);
1054 
1055  // If country is empty then set country to "unknown"
1056  if (country.empty()) {
1057  country = "unknown";
1058  }
1059 
1060  // Get pointer to relevant "country" element. If no "country"
1061  // element exists that corresponds to the country of the tool then
1062  // append a new element
1063  GXmlElement* country_element = NULL;
1064  int ncountries = countries->elements();
1065  if (ncountries == 0) {
1066  country_element = countries->append(country);
1067  }
1068  else {
1069  for (int k = 0; k < ncountries; ++k) {
1070  GXmlElement* element = countries->element(k);
1071  if (element->name() == country) {
1072  country_element = element;
1073  break;
1074  }
1075  }
1076  if (country_element == NULL) {
1077  country_element = countries->append(country);
1078  }
1079  }
1080 
1081  // Now we have the relevant "country" element and we can update the
1082  // statistics
1083  int calls = 1;
1084  if (country_element->has_attribute("calls")) {
1085  calls += gammalib::toint(country_element->attribute("calls"));
1086  }
1087  if (country_element->has_attribute("wall")) {
1088  wall += gammalib::todouble(country_element->attribute("wall"));
1089  }
1090  if (country_element->has_attribute("cpu")) {
1091  cpu += gammalib::todouble(country_element->attribute("cpu"));
1092  }
1093  if (country_element->has_attribute("gCO2e")) {
1094  gCO2e += gammalib::todouble(country_element->attribute("gCO2e"));
1095  }
1096  country_element->attribute("calls", gammalib::str(calls));
1097  country_element->attribute("wall", gammalib::str(wall));
1098  country_element->attribute("cpu", gammalib::str(cpu));
1099  country_element->attribute("gCO2e", gammalib::str(gCO2e));
1100 
1101  } // endfor: looped over statistics
1102 
1103  // Return
1104  return;
1105 }
1106 
1107 
1108 /***********************************************************************//**
1109  * @brief Update versions in high-level statistics data.
1110  *
1111  * @param[in,out] xml High-level statistics XML object.
1112  * @param[in] statistics Low-level statistics CSV object.
1113  *
1114  * Update version statistics in data block.
1115  ***************************************************************************/
1116 void GDaemon::update_versions_data(GXml& xml, const GCsv& statistics)
1117 {
1118  // Get pointer to "versions" node. In case that no "versions" node exists
1119  // in "data" node then append one.
1120  GXmlNode* base = xml.element("statistics",0);
1121  GXmlNode* data = base->element("data",0);
1122  if (data->elements("versions") == 0) {
1123  data->append("versions");
1124  }
1125  GXmlNode* versions = data->element("versions",0);
1126 
1127  // Loop over statistics
1128  for (int i = 1; i < statistics.nrows(); ++i) {
1129 
1130  // Extract relevant attributes
1131  std::string version = statistics.string(i,4);
1132  double wall = statistics.real(i,5);
1133  double cpu = statistics.real(i,6);
1134  double gCO2e = statistics.real(i,7);
1135 
1136  // If version is empty then set version to "unknown"
1137  if (version.empty()) {
1138  version = "unknown";
1139  }
1140 
1141  // Get pointer to relevant "version" element. If no "version"
1142  // element exists that corresponds to the version of the tool then
1143  // append a new element
1144  GXmlElement* version_element = NULL;
1145  int nversion = versions->elements();
1146  if (nversion == 0) {
1147  version_element = versions->append(version);
1148  }
1149  else {
1150  for (int k = 0; k < nversion; ++k) {
1151  GXmlElement* element = versions->element(k);
1152  if (element->name() == version) {
1153  version_element = element;
1154  break;
1155  }
1156  }
1157  if (version_element == NULL) {
1158  version_element = versions->append(version);
1159  }
1160  }
1161 
1162  // Now we have the relevant "version" element and we can update the
1163  // statistics
1164  int calls = 1;
1165  if (version_element->has_attribute("calls")) {
1166  calls += gammalib::toint(version_element->attribute("calls"));
1167  }
1168  if (version_element->has_attribute("wall")) {
1169  wall += gammalib::todouble(version_element->attribute("wall"));
1170  }
1171  if (version_element->has_attribute("cpu")) {
1172  cpu += gammalib::todouble(version_element->attribute("cpu"));
1173  }
1174  if (version_element->has_attribute("gCO2e")) {
1175  gCO2e += gammalib::todouble(version_element->attribute("gCO2e"));
1176  }
1177  version_element->attribute("calls", gammalib::str(calls));
1178  version_element->attribute("wall", gammalib::str(wall));
1179  version_element->attribute("cpu", gammalib::str(cpu));
1180  version_element->attribute("gCO2e", gammalib::str(gCO2e));
1181 
1182  } // endfor: looped over statistics
1183 
1184  // Return
1185  return;
1186 }
1187 
1188 
1189 /***********************************************************************//**
1190  * @brief Update daily statistics
1191  *
1192  * @param[in,out] xml High-level statistics XML object.
1193  * @param[in] statistics Low-level statistics CSV object.
1194  *
1195  * Update daily statistics in data block.
1196  ***************************************************************************/
1197 void GDaemon::update_daily(GXml& xml, const GCsv& statistics)
1198 {
1199  // Get pointer to "daily" node. In case that no "daily" node exists
1200  // in "data" node then append one.
1201  GXmlNode* base = xml.element("statistics",0);
1202  GXmlNode* data = base->element("data",0);
1203  if (data->elements("daily") == 0) {
1204  data->append("daily");
1205  }
1206  GXmlNode* daily = data->element("daily",0);
1207 
1208  // Loop over statistics
1209  for (int i = 1; i < statistics.nrows(); ++i) {
1210 
1211  // Extract relevant attributes
1212  std::string date = statistics.string(i,0).substr(0,10);
1213  std::string country = statistics.string(i,2);
1214  std::string tool = statistics.string(i,3);
1215  std::string version = statistics.string(i,4);
1216  double wall = statistics.real(i,5);
1217  double cpu = statistics.real(i,6);
1218  double gCO2e = statistics.real(i,7);
1219 
1220  // If tool name is empty then set tool name to "unknown"
1221  if (tool.empty()) {
1222  tool = "unknown";
1223  }
1224 
1225  // Get pointer to relevant "date" node. If no "date" node exists
1226  // that corresponds to the date of the statistics then append an
1227  // "date" node for the required date.
1228  GXmlNode* date_node = NULL;
1229  int ndates = daily->elements("date");
1230  if (ndates == 0) {
1231  date_node = daily->append("date");
1232  date_node->append(GXmlElement("value", date));
1233  }
1234  else {
1235  for (int k = 0; k < ndates; ++k) {
1236  GXmlElement* element = daily->element("date", k);
1237  if (element->element("value",0)->value() == date) {
1238  date_node = element;
1239  break;
1240  }
1241  }
1242  if (date_node == NULL) {
1243  date_node = daily->append("date");
1244  date_node->append(GXmlElement("value", date));
1245  }
1246  }
1247 
1248  // Get pointer to "tools" node. If no such node exists then append
1249  // one
1250  GXmlNode* tools = NULL;
1251  if (date_node->elements("tools") == 0) {
1252  tools = date_node->append("tools");
1253  }
1254  else {
1255  tools = date_node->element("tools",0);
1256  }
1257 
1258  // Get pointer to relevant "tool" element. If no "tool" element
1259  // exists that corresponds to the name of the tool then append
1260  // a new element
1261  GXmlElement* tool_element = NULL;
1262  int ntools = tools->elements();
1263  if (ntools == 0) {
1264  tool_element = tools->append(tool);
1265  }
1266  else {
1267  for (int k = 0; k < ntools; ++k) {
1268  GXmlElement* element = tools->element(k);
1269  if (element->name() == tool) {
1270  tool_element = element;
1271  break;
1272  }
1273  }
1274  if (tool_element == NULL) {
1275  tool_element = tools->append(tool);
1276  }
1277  }
1278 
1279  // Now we have the relevant "tool" element and we can update the
1280  // statistics
1281  int calls = 1;
1282  if (!tool_element->has_attribute("version")) {
1283  tool_element->attribute("version", version);
1284  }
1285  if (!tool_element->has_attribute("country")) {
1286  tool_element->attribute("country", country);
1287  }
1288  if (tool_element->has_attribute("calls")) {
1289  calls += gammalib::toint(tool_element->attribute("calls"));
1290  }
1291  if (tool_element->has_attribute("wall")) {
1292  wall += gammalib::todouble(tool_element->attribute("wall"));
1293  }
1294  if (tool_element->has_attribute("cpu")) {
1295  cpu += gammalib::todouble(tool_element->attribute("cpu"));
1296  }
1297  if (tool_element->has_attribute("gCO2e")) {
1298  gCO2e += gammalib::todouble(tool_element->attribute("gCO2e"));
1299  }
1300  tool_element->attribute("calls", gammalib::str(calls));
1301  tool_element->attribute("wall", gammalib::str(wall));
1302  tool_element->attribute("cpu", gammalib::str(cpu));
1303  tool_element->attribute("gCO2e", gammalib::str(gCO2e));
1304 
1305  } // endfor: looped over statistics
1306 
1307  // Return
1308  return;
1309 }
Abstract XML node base class.
Definition: GXmlNode.hpp:57
void update_countries_data(GXml &xml, const GCsv &statistics)
Update countries in high-level statistics data.
Definition: GDaemon.cpp:1035
void update_host_country(void)
Update host country.
Definition: GDaemon.cpp:690
void open(const GFilename &filename, const bool &clobber=false)
Open log file.
Definition: GLog.cpp:505
void now(void)
Set time to current time.
Definition: GTime.cpp:1126
void save(const GFilename &filename) const
Save XML document into file.
Definition: GXml.cpp:581
void update_dates(GXml &xml, const GCsv &statistics)
Update dates in high-level statistics header.
Definition: GDaemon.cpp:918
void write_heartbeat(void)
Write heartbeat file.
Definition: GDaemon.cpp:512
void create_lock_file(void)
Create the lock file.
Definition: GDaemon.cpp:450
XML class interface definition.
std::string value(void) const
Return string value.
void init_members(void)
Initialise class members.
Definition: GDaemon.cpp:399
void chatter(const GChatter &chatter)
Set chattiness.
Definition: GLog.hpp:366
XML element node class.
Definition: GXmlElement.hpp:48
Comma-separated values table class.
Definition: GCsv.hpp:57
virtual int elements(void) const
Return number of GXMLElement children of node.
Definition: GXmlNode.cpp:586
Time class.
Definition: GTime.hpp:55
virtual GDaemon * clone(void) const
Clone Daemon.
Definition: GDaemon.cpp:166
int m_period
Wake-up period in seconds.
Definition: GDaemon.hpp:96
void load(const GFilename &filename)
Load XML document from file.
Definition: GXml.cpp:540
void update_daily(GXml &xml, const GCsv &statistics)
Update daily statistics.
Definition: GDaemon.cpp:1197
void update_countries_header(GXml &xml, const GCsv &statistics)
Update countries in high-level statistics header.
Definition: GDaemon.cpp:978
const std::string & name(void) const
Return XML element name.
pid_t lock_pid(void) const
Returns process ID in lock file.
Definition: GDaemon.cpp:546
GLog m_log
Logger.
Definition: GDaemon.hpp:98
GFilename lock_filename(void) const
Returns name of daemon lock file.
Definition: GDaemon.hpp:121
void update_statistics(void)
Update application statistics.
Definition: GDaemon.cpp:584
std::string string(const int &row, const int &col) const
Get string value.
Definition: GCsv.cpp:309
void create_xml(const GFilename &filename)
Create high-level statistics XML file.
Definition: GDaemon.cpp:866
void update_versions_data(GXml &xml, const GCsv &statistics)
Update versions in high-level statistics data.
Definition: GDaemon.cpp:1116
void delete_lock_file(void)
Delete daemon lock file.
Definition: GDaemon.cpp:483
bool is_empty(void) const
Signals if document has no child nodes.
Definition: GXml.hpp:279
GXmlElement * element(const int &index)
Return pointer to child element.
Definition: GXml.cpp:419
bool alive(void) const
Check if daemon is alive.
Definition: GDaemon.cpp:282
const GXmlAttribute * attribute(const int &index) const
Return attribute.
Daemon class definition.
XML class.
Definition: GXml.hpp:172
Filename class.
Definition: GFilename.hpp:62
bool has_attribute(const std::string &name) const
Check if element has a given attribute.
void date(const bool &flag)
Set date flag that controls date prefixing.
Definition: GLog.hpp:259
int m_heartbeat
Heartbeat period in seconds.
Definition: GDaemon.hpp:97
bool exists(void) const
Checks whether file exists.
Definition: GFilename.cpp:223
virtual std::string print(const GChatter &chatter=NORMAL) const
Print Daemon.
Definition: GDaemon.cpp:362
XML text node class.
Definition: GXmlText.hpp:43
void close(void)
Close log file.
Definition: GLog.cpp:538
void flush(const bool &force=false)
Flush string buffer into log file.
Definition: GLog.cpp:665
void free_members(void)
Delete class members.
Definition: GDaemon.cpp:435
GChatter
Definition: GTypemaps.hpp:33
const int & nrows(void) const
Return number of rows.
Definition: GCsv.hpp:149
GFilename gamma_filename(const std::string &name)
Returns filename in .gamma directory.
Definition: GTools.cpp:2484
Daemon class.
Definition: GDaemon.hpp:51
void copy_members(const GDaemon &daemon)
Copy class members.
Definition: GDaemon.cpp:418
GDaemon(void)
Void constructor.
Definition: GDaemon.cpp:65
virtual ~GDaemon(void)
Destructor.
Definition: GDaemon.cpp:96
double real(const int &row, const int &col) const
Get double precision value.
Definition: GCsv.cpp:324
std::string url(void) const
Return Uniform Resource Locator (URL)
Definition: GFilename.hpp:189
GChatter m_chatter
Chattiness of logger.
Definition: GDaemon.hpp:99
virtual GXmlElement * element(const int &index)
Return pointer to GXMLElement child.
Definition: GXmlNode.cpp:640
std::string host_country(const bool &force_query=false)
Return two-digit host country code.
Definition: GTools.cpp:2326
virtual bool is_empty(void) const
Signals if node has no child nodes.
Definition: GXmlNode.hpp:145
virtual void clear(void)
Clear Daemon.
Definition: GDaemon.cpp:148
int toint(const std::string &arg)
Convert string into integer value.
Definition: GTools.cpp:821
pid_t m_pid
Process ID.
Definition: GDaemon.hpp:95
GDaemon & operator=(const GDaemon &daemon)
Assignment operator.
Definition: GDaemon.cpp:118
virtual GXmlNode * append(const GXmlNode &node)
Append XML child node.
Definition: GXmlNode.cpp:287
void recover_valid_xml(void)
Recovers a valid XML file.
Definition: GDaemon.cpp:746
void start(void)
Starts the daemon.
Definition: GDaemon.cpp:177
std::string parformat(const std::string &s, const int &indent=0)
Convert string in parameter format.
Definition: GTools.cpp:1143
GFilename heartbeat_filename(void) const
Returns name of daemon heartbeat file.
Definition: GDaemon.hpp:133
Abstract XML node base class interface definition.
std::string utc(const int &precision=0) const
Return time as string in UTC time system.
Definition: GTime.cpp:465
Time class interface definition.
GXmlNode * append(const GXmlNode &node)
Append child node to XML document root.
Definition: GXml.cpp:279
Comma-separated values table class definition.
double todouble(const std::string &arg)
Convert string into double precision value.
Definition: GTools.cpp:926
void clear(void)
Clear object.
Definition: GLog.cpp:456
std::string str(const unsigned short int &value)
Convert unsigned short integer value into string.
Definition: GTools.cpp:489