GammaLib 2.0.0
Loading...
Searching...
No Matches
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
69
70 // Return
71 return;
72}
73
74
75/***********************************************************************//**
76 * @brief Copy constructor
77 *
78 * @param[in] daemon Daemon.
79 ***************************************************************************/
81{
82 // Initialise class 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
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 ***************************************************************************/
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 ***************************************************************************/
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
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) {
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 ***************************************************************************/
282bool 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 ***************************************************************************/
362std::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 ***************************************************************************/
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 ***************************************************************************/
546pid_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 ***************************************************************************/
866void 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 ***************************************************************************/
918void 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 ***************************************************************************/
978void 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 ***************************************************************************/
1035void 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 ***************************************************************************/
1116void 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 ***************************************************************************/
1197void 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}
Comma-separated values table class definition.
Daemon class definition.
Time class interface definition.
GChatter
Definition GTypemaps.hpp:33
@ NORMAL
Definition GTypemaps.hpp:36
@ SILENT
Definition GTypemaps.hpp:34
Abstract XML node base class interface definition.
XML class interface definition.
Comma-separated values table class.
Definition GCsv.hpp:57
std::string string(const int &row, const int &col) const
Get string value.
Definition GCsv.cpp:309
const int & nrows(void) const
Return number of rows.
Definition GCsv.hpp:149
double real(const int &row, const int &col) const
Get double precision value.
Definition GCsv.cpp:324
Daemon class.
Definition GDaemon.hpp:51
void create_xml(const GFilename &filename)
Create high-level statistics XML file.
Definition GDaemon.cpp:866
pid_t m_pid
Process ID.
Definition GDaemon.hpp:95
void update_statistics(void)
Update application statistics.
Definition GDaemon.cpp:584
void copy_members(const GDaemon &daemon)
Copy class members.
Definition GDaemon.cpp:418
virtual ~GDaemon(void)
Destructor.
Definition GDaemon.cpp:96
GFilename lock_filename(void) const
Returns name of daemon lock file.
Definition GDaemon.hpp:121
void update_host_country(void)
Update host country.
Definition GDaemon.cpp:690
GChatter m_chatter
Chattiness of logger.
Definition GDaemon.hpp:99
void update_daily(GXml &xml, const GCsv &statistics)
Update daily statistics.
Definition GDaemon.cpp:1197
void start(void)
Starts the daemon.
Definition GDaemon.cpp:177
pid_t lock_pid(void) const
Returns process ID in lock file.
Definition GDaemon.cpp:546
void write_heartbeat(void)
Write heartbeat file.
Definition GDaemon.cpp:512
GDaemon & operator=(const GDaemon &daemon)
Assignment operator.
Definition GDaemon.cpp:118
void update_versions_data(GXml &xml, const GCsv &statistics)
Update versions in high-level statistics data.
Definition GDaemon.cpp:1116
int m_heartbeat
Heartbeat period in seconds.
Definition GDaemon.hpp:97
bool alive(void) const
Check if daemon is alive.
Definition GDaemon.cpp:282
void update_countries_data(GXml &xml, const GCsv &statistics)
Update countries in high-level statistics data.
Definition GDaemon.cpp:1035
void create_lock_file(void)
Create the lock file.
Definition GDaemon.cpp:450
int m_period
Wake-up period in seconds.
Definition GDaemon.hpp:96
virtual std::string print(const GChatter &chatter=NORMAL) const
Print Daemon.
Definition GDaemon.cpp:362
void update_countries_header(GXml &xml, const GCsv &statistics)
Update countries in high-level statistics header.
Definition GDaemon.cpp:978
void recover_valid_xml(void)
Recovers a valid XML file.
Definition GDaemon.cpp:746
virtual void clear(void)
Clear Daemon.
Definition GDaemon.cpp:148
void update_dates(GXml &xml, const GCsv &statistics)
Update dates in high-level statistics header.
Definition GDaemon.cpp:918
void free_members(void)
Delete class members.
Definition GDaemon.cpp:435
GFilename heartbeat_filename(void) const
Returns name of daemon heartbeat file.
Definition GDaemon.hpp:133
GDaemon(void)
Void constructor.
Definition GDaemon.cpp:65
GLog m_log
Logger.
Definition GDaemon.hpp:98
void init_members(void)
Initialise class members.
Definition GDaemon.cpp:399
virtual GDaemon * clone(void) const
Clone Daemon.
Definition GDaemon.cpp:166
void delete_lock_file(void)
Delete daemon lock file.
Definition GDaemon.cpp:483
Filename class.
Definition GFilename.hpp:62
std::string url(void) const
Return Uniform Resource Locator (URL)
bool exists(void) const
Checks whether file exists.
void flush(const bool &force=false)
Flush string buffer into log file.
Definition GLog.cpp:665
void open(const GFilename &filename, const bool &clobber=false)
Open log file.
Definition GLog.cpp:505
void close(void)
Close log file.
Definition GLog.cpp:538
void date(const bool &flag)
Set date flag that controls date prefixing.
Definition GLog.hpp:259
void chatter(const GChatter &chatter)
Set chattiness.
Definition GLog.hpp:366
void clear(void)
Clear object.
Definition GLog.cpp:456
Time class.
Definition GTime.hpp:55
std::string utc(const int &precision=0) const
Return time as string in UTC time system.
Definition GTime.cpp:465
void now(void)
Set time to current time.
Definition GTime.cpp:1126
XML element node class.
const GXmlAttribute * attribute(const int &index) const
Return attribute.
bool has_attribute(const std::string &name) const
Check if element has a given attribute.
std::string value(void) const
Return string value.
const std::string & name(void) const
Return XML element name.
Abstract XML node base class.
Definition GXmlNode.hpp:57
virtual GXmlNode * append(const GXmlNode &node)
Append XML child node.
Definition GXmlNode.cpp:287
virtual GXmlElement * element(const int &index)
Return pointer to GXMLElement child.
Definition GXmlNode.cpp:640
virtual bool is_empty(void) const
Signals if node has no child nodes.
Definition GXmlNode.hpp:145
virtual int elements(void) const
Return number of GXMLElement children of node.
Definition GXmlNode.cpp:586
XML text node class.
Definition GXmlText.hpp:43
XML class.
Definition GXml.hpp:172
void save(const GFilename &filename) const
Save XML document into file.
Definition GXml.cpp:581
GXmlElement * element(const int &index)
Return pointer to child element.
Definition GXml.cpp:419
GXmlNode * append(const GXmlNode &node)
Append child node to XML document root.
Definition GXml.cpp:279
bool is_empty(void) const
Signals if document has no child nodes.
Definition GXml.hpp:279
void load(const GFilename &filename)
Load XML document from file.
Definition GXml.cpp:540
std::string parformat(const std::string &s, const int &indent=0)
Convert string in parameter format.
Definition GTools.cpp:1143
GFilename gamma_filename(const std::string &name)
Returns filename in .gamma directory.
Definition GTools.cpp:2484
std::string str(const unsigned short int &value)
Convert unsigned short integer value into string.
Definition GTools.cpp:489
double todouble(const std::string &arg)
Convert string into double precision value.
Definition GTools.cpp:926
int toint(const std::string &arg)
Convert string into integer value.
Definition GTools.cpp:821
std::string host_country(const bool &force_query=false)
Return two-digit host country code.
Definition GTools.cpp:2326