Commit 2972347390fe213f71343cc4011e2543e22996fb

Authored by Jürgen Knödlseder
1 parent 83ee4f41

Add GammaLib daemon for application statistics management (#3592)

ChangeLog
1   -2022-02-18
  1 +2022-02-19
2 2  
3 3 * Version 2.0.0 released
4 4 ========================
5 5  
  6 + Add GammaLib daemon for application statistics management (#3592)
6 7 By default, do not allow log-likelihood decrease in LM optimiser
7 8 Support Galactic coordinates in spatial models (#3988)
8 9 Implement CGRO clock correction (#3973)
... ...
1 1 New Features and Important Changes in GammaLib 2.0.0
2 2  
3   -18 February 2022
  3 +19 February 2022
4 4  
5 5  
6 6 1. Introduction
... ... @@ -14,6 +14,7 @@ made since the last release of GammaLib.
14 14 The following classes have been added:
15 15 - GFunctions
16 16 - GIntegrals
  17 +- GDaemon
17 18 - GResponseVectorCache
18 19 - GPulsar
19 20 - GPulsarEphemeris
... ... @@ -148,6 +149,7 @@ The following methods have been added:
148 149 - gammalib::xml_check_type()
149 150 - gammalib::host_country()
150 151 - gammalib::http_query()
  152 +- gammalib::gamma_filename()
151 153 - gammalib::check_energy_interval()
152 154 - gammalib::check_prj_x2s_status()
153 155 - gammalib::check_prj_s2x_status()
... ... @@ -412,6 +414,14 @@ were added to compute the total solid angle within specified sky region(s) (#178
412 414  
413 415 14. Support module
414 416 ------------------
  417 +Added GDaemon class that implements the GammaLib daemon. The GammaLib daemon collects
  418 +on a regular basis low-level application statistics and generates from this information
  419 +high-level statistics on a daily basis. This avoids that the low-level statistics
  420 +file becomes too large (#3592).
  421 +
  422 +Added gammalib::gamma_filename() method that returns the full path for a filename in
  423 +the users's .gamma directory.
  424 +
415 425 Added gammalib::host_country() method that returns the host country on which the code
416 426 is executed.
417 427  
... ...
README.md
1 1 GammaLib information
2 2 ====================
3   -* Version: 2.0.0.dev (18 February 2022)
  3 +* Version: 2.0.0.dev (19 February 2022)
4 4  
5 5 [![Build Status](https://cta-jenkins.irap.omp.eu/buildStatus/icon?job=gammalib-integrate-os)](https://cta-jenkins.irap.omp.eu/job/gammalib-integrate-os/)
6 6  
... ...
doc/source/admin/release_history/2.0.rst
1 1 .. _2.0:
2 2  
3   -GammaLib 2.0 release (14 February 2022)
  3 +GammaLib 2.0 release (19 February 2022)
4 4 =======================================
5 5  
6 6 Overview
... ... @@ -10,11 +10,11 @@ GammaLib 2.0 is a major release that adds significant functionality.
10 10  
11 11 In particular, this release provides:
12 12  
13   -* extended support for COMPTEL data analysis
14   -* full support of all spatial models for COMPTEL
  13 +* full support for COMPTEL data analysis
15 14 * support for pulsar analysis
16 15 * vectorised response computation
17 16 * support Galactic coordinates in spatial models
  17 +* provision of carbon footprint information and statistics
18 18  
19 19  
20 20 Bug fixes
... ... @@ -103,19 +103,18 @@ Improvements
103 103 New features
104 104 ------------
105 105  
106   -<<<<<<< HEAD
107   -* [`3924 <https://cta-redmine.irap.omp.eu/issues/3924>`_] -
108   - Add ``GModelSpatialRadialGeneralGauss`` and ``GModelSpatialEllipticalGeneralGauss`` classes
109   -=======
110 106 * [`3973 <https://cta-redmine.irap.omp.eu/issues/3973>`_] -
111 107 Add ``GCOMBvc`` and ``GCOMBvcs`` classes to support COMPTEL pulsar analysis, revise
112 108 ``GCOMSelection`` interface to support both orbital and pulsar phase selection,
113 109 extend ``GCOMObservation`` interface to handle Solar System Barycentre Data
114   ->>>>>>> 3973-add-pulsar-analysis
  110 +* [`3924 <https://cta-redmine.irap.omp.eu/issues/3924>`_] -
  111 + Add ``GModelSpatialRadialGeneralGauss`` and ``GModelSpatialEllipticalGeneralGauss`` classes
115 112 * [`3910 <https://cta-redmine.irap.omp.eu/issues/3910>`_] -
116 113 Add ``GCOMModelDRM`` class
117 114 * [`3606 <https://cta-redmine.irap.omp.eu/issues/3606>`_] -
118 115 Add ``GModelSpectralBins`` class
  116 +* [`3592 <https://cta-redmine.irap.omp.eu/issues/3592>`_] -
  117 + Add GammaLib daemon for application statistics management
119 118 * [`3527 <https://cta-redmine.irap.omp.eu/issues/3527>`_] -
120 119 Add ``GCTABackground2D`` class
121 120 * [`3469 <https://cta-redmine.irap.omp.eu/issues/3469>`_] -
... ...
include/GApplication.hpp
... ... @@ -136,7 +136,7 @@ protected:
136 136 void set_log_chatter(void);
137 137 void set_log_filename(void);
138 138 void write_statistics(void);
139   - std::string strdate(void) const;
  139 + void start_daemon(void) const;
140 140  
141 141 // Protected data members
142 142 std::string m_name; //!< Application name
... ...
include/GDaemon.hpp 0 → 100644
  1 +/***************************************************************************
  2 + * GDaemon.hpp - 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.hpp
  23 + * @brief Daemon class definition
  24 + * @author Juergen Knoedlseder
  25 + */
  26 +
  27 +#ifndef GDAEMON_HPP
  28 +#define GDAEMON_HPP
  29 +
  30 +/* __ Includes ___________________________________________________________ */
  31 +#include <string>
  32 +#include "GBase.hpp"
  33 +#include "GTools.hpp"
  34 +#include "GFilename.hpp"
  35 +#include "GLog.hpp"
  36 +
  37 +/* __ Forward declarations _______________________________________________ */
  38 +class GCsv;
  39 +
  40 +/* __ Constants __________________________________________________________ */
  41 +
  42 +
  43 +/***********************************************************************//**
  44 + * @class GDaemon
  45 + *
  46 + * @brief Daemon class
  47 + *
  48 + * @todo Add class description.
  49 + ***************************************************************************/
  50 +class GDaemon : public GBase {
  51 +
  52 +public:
  53 + // Constructors and destructors
  54 + GDaemon(void);
  55 + GDaemon(const GDaemon& daemon);
  56 + virtual ~GDaemon(void);
  57 +
  58 + // Operators
  59 + GDaemon& operator=(const GDaemon& daemon);
  60 +
  61 + // Implemented pure virtual base class methods
  62 + virtual void clear(void);
  63 + virtual GDaemon* clone(void) const;
  64 + virtual std::string classname(void) const;
  65 + virtual std::string print(const GChatter& chatter = NORMAL) const;
  66 +
  67 + // Other methods
  68 + void start(void);
  69 + bool alive(void) const;
  70 +
  71 +protected:
  72 + // Protected methods
  73 + void init_members(void);
  74 + void copy_members(const GDaemon& daemon);
  75 + void free_members(void);
  76 + void create_lock_file(void);
  77 + void delete_lock_file(void) const;
  78 + GFilename lock_filename(void) const;
  79 + GFilename statistics_filename(void) const;
  80 + pid_t lock_pid(void) const;
  81 + void update_statistics(void);
  82 + void update_high_level_statistics(const GCsv& statistics);
  83 +
  84 + // Protected members
  85 + pid_t m_pid; //!< Process ID
  86 + int m_period; //!< Wake-up period in seconds
  87 + GLog m_log; //!< Logger
  88 + GChatter m_chatter; //!< Chattiness of logger
  89 +};
  90 +
  91 +
  92 +/***********************************************************************//**
  93 + * @brief Return class name
  94 + *
  95 + * @return String containing the class name ("GDaemon").
  96 + ***************************************************************************/
  97 +inline
  98 +std::string GDaemon::classname(void) const
  99 +{
  100 + return ("GDaemon");
  101 +}
  102 +
  103 +
  104 +/***********************************************************************//**
  105 + * @brief Returns daemon lock filename
  106 + *
  107 + * @return Daemon lock filename.
  108 + ***************************************************************************/
  109 +inline
  110 +GFilename GDaemon::lock_filename(void) const
  111 +{
  112 + return (gammalib::gamma_filename("daemon.lock"));
  113 +}
  114 +
  115 +#endif /* GDAEMON_HPP */
... ...
include/GTools.hpp
... ... @@ -165,6 +165,7 @@ namespace gammalib {
165 165 std::string http_query(const std::string& host,
166 166 const std::string& query);
167 167 std::string host_country(void);
  168 + GFilename gamma_filename(const std::string& name);
168 169 }
169 170  
170 171  
... ...
include/GammaLib.hpp
... ... @@ -50,6 +50,7 @@
50 50 #include "GUrlFile.hpp"
51 51 #include "GUrlString.hpp"
52 52 #include "GFilename.hpp"
  53 +#include "GDaemon.hpp"
53 54  
54 55 /* __ Linear algebra module ______________________________________________ */
55 56 #include "GVector.hpp"
... ...
include/Makefile.am
... ... @@ -36,6 +36,7 @@ pkginclude_HEADERS = GammaLib.hpp \
36 36 GUrlFile.hpp \
37 37 GUrlString.hpp \
38 38 GFilename.hpp \
  39 + GDaemon.hpp \
39 40 GVector.hpp \
40 41 GMatrixBase.hpp \
41 42 GMatrix.hpp \
... ...
pyext/GDaemon.i 0 → 100644
  1 +/***************************************************************************
  2 + * GDaemon.i - 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.i
  23 + * @brief Daemon class definition
  24 + * @author Juergen Knoedlseder
  25 + */
  26 +%{
  27 +/* Put headers and other declarations here that are needed for compilation */
  28 +#include "GDaemon.hpp"
  29 +%}
  30 +
  31 +
  32 +/***********************************************************************//**
  33 + * @class GDaemon
  34 + *
  35 + * @brief Daemon class
  36 + ***************************************************************************/
  37 +class GDaemon : public GBase {
  38 +
  39 +public:
  40 + // Constructors and destructors
  41 + GDaemon(void);
  42 + //GDaemon(const GDaemon& daemon);
  43 + virtual ~GDaemon(void);
  44 +
  45 + // Implemented pure virtual base class methods
  46 + virtual void clear(void);
  47 + virtual GDaemon* clone(void) const;
  48 + virtual std::string classname(void) const;
  49 +
  50 + // Other methods
  51 + void start(void);
  52 + bool alive(void) const;
  53 +};
  54 +
  55 +
  56 +/***********************************************************************//**
  57 + * @brief GDaemon class extension
  58 + ***************************************************************************/
  59 +%extend GDaemon {
  60 + GDaemon copy() {
  61 + return (*self);
  62 + }
  63 +};
... ...
pyext/GTools.i
... ... @@ -118,4 +118,5 @@ namespace gammalib {
118 118 std::string http_query(const std::string& host,
119 119 const std::string& query);
120 120 std::string host_country(void);
  121 + GFilename gamma_filename(const std::string& name);
121 122 }
... ...
pyext/gammalib/support.i
1 1 /***************************************************************************
2 2 * support.i - Support module *
3 3 * ----------------------------------------------------------------------- *
4   - * copyright (C) 2011-2020 by Jurgen Knodlseder *
  4 + * copyright (C) 2011-2022 by Jurgen Knodlseder *
5 5 * ----------------------------------------------------------------------- *
6 6 * *
7 7 * This program is free software: you can redistribute it and/or modify *
... ... @@ -82,3 +82,4 @@
82 82 %include "GUrlFile.i"
83 83 %include "GUrlString.i"
84 84 %include "GFilename.i"
  85 +%include "GDaemon.i"
... ...
src/app/GApplication.cpp
... ... @@ -28,7 +28,7 @@
28 28 #ifdef HAVE_CONFIG_H
29 29 #include <config.h>
30 30 #endif
31   -#include <pwd.h> // user/passwd function
  31 +//#include <pwd.h> // user/passwd function
32 32 #include <fcntl.h> // for file locking
33 33 #include <unistd.h> // access() function
34 34 #include <cstdio> // std::fopen(), etc. functions
... ... @@ -37,6 +37,7 @@
37 37 #include "GFits.hpp"
38 38 #include "GFitsHDU.hpp"
39 39 #include "GFilename.hpp"
  40 +#include "GDaemon.hpp"
40 41  
41 42 /* __ OpenMP section _____________________________________________________ */
42 43 #ifdef _OPENMP
... ... @@ -1190,6 +1191,9 @@ void GApplication::init_members(void)
1190 1191 m_cstart = double(clock());
1191 1192 #endif
1192 1193  
  1194 + // Start daemon
  1195 + start_daemon();
  1196 +
1193 1197 // Return
1194 1198 return;
1195 1199 }
... ... @@ -1305,42 +1309,35 @@ void GApplication::set_log_filename(void)
1305 1309 ***************************************************************************/
1306 1310 void GApplication::write_statistics(void)
1307 1311 {
1308   - // Initialise ASCII file name
1309   - std::string filename;
1310   -
1311   - // Get ASCII file name. Make sure that ASCII file exists and that it
1312   - // contains a header line
1313   - uid_t uid = geteuid();
1314   - struct passwd* pw = getpwuid(uid);
1315   - if (pw != NULL) {
1316   - filename = std::string(pw->pw_dir) + "/.gamma/statistics.csv";
1317   - if (access(filename.c_str(), R_OK) != 0) {
1318   -
1319   - // OpenMP critical zone to write header in case that the file
1320   - // does not yet exist
1321   - #pragma omp critical(GApplication_write_statistics)
1322   - {
1323   -
1324   - // Open statistics file, and in case of success, write
1325   - // header line
1326   - FILE* fptr = fopen(filename.c_str(), "w");
1327   - if (fptr != NULL) {
1328   - fprintf(fptr, "Date,"
1329   - "GammaLib version,"
1330   - "Country,"
1331   - "Application,"
1332   - "Version,"
1333   - "Wall clock seconds,"
1334   - "CPU seconds,"
1335   - "g eCO2\n");
1336   - fclose(fptr);
1337   - }
  1312 + // Get statistics ASCII file name
  1313 + GFilename filename = gammalib::gamma_filename("statistics.csv");
  1314 +
  1315 + // Make sure that file exists and that it contains a header line
  1316 + if (access(filename.url().c_str(), R_OK) != 0) {
1338 1317  
1339   - } // end of OMP critial zone
  1318 + // OpenMP critical zone to write header in case that the file does
  1319 + // not yet exist
  1320 + #pragma omp critical(GApplication_write_statistics)
  1321 + {
1340 1322  
1341   - } // endif: no statistics file existed
  1323 + // Open statistics file, and in case of success, write header
  1324 + // line
  1325 + FILE* fptr = fopen(filename.url().c_str(), "w");
  1326 + if (fptr != NULL) {
  1327 + fprintf(fptr, "Date,"
  1328 + "GammaLib version,"
  1329 + "Country,"
  1330 + "Application,"
  1331 + "Version,"
  1332 + "Wall clock seconds,"
  1333 + "CPU seconds,"
  1334 + "g eCO2\n");
  1335 + fclose(fptr);
  1336 + }
1342 1337  
1343   - } // endif: managed to get user ID
  1338 + } // end of OMP critial zone
  1339 +
  1340 + } // endif: no statistics file existed
1344 1341  
1345 1342 // OpenMP critical zone to write statitics
1346 1343 #pragma omp critical(GApplication_write_statistics)
... ... @@ -1353,7 +1350,7 @@ void GApplication::write_statistics(void)
1353 1350 lock.l_start = 0; // No offset, lock entire file ...
1354 1351 lock.l_len = 0; // ... to the end
1355 1352 lock.l_pid = getpid(); // Current process ID
1356   - int fd = open(filename.c_str(), O_WRONLY);
  1353 + int fd = open(filename.url().c_str(), O_WRONLY);
1357 1354 if (fd != -1) {
1358 1355  
1359 1356 // Lock file
... ... @@ -1361,7 +1358,7 @@ void GApplication::write_statistics(void)
1361 1358  
1362 1359 // Open statistics file, and in case of success, write
1363 1360 // statistics
1364   - FILE* fptr = fopen(filename.c_str(), "a");
  1361 + FILE* fptr = fopen(filename.url().c_str(), "a");
1365 1362 if (fptr != NULL) {
1366 1363  
1367 1364 // Get host country
... ... @@ -1392,3 +1389,40 @@ void GApplication::write_statistics(void)
1392 1389 // Return
1393 1390 return;
1394 1391 }
  1392 +
  1393 +
  1394 +/***********************************************************************//**
  1395 + * @brief Start GammaLib daemon
  1396 + *
  1397 + * Starts GammaLib daemon. The GammaLib daemon becomes an independent child
  1398 + * process, hence it will not affect the application that originally launched
  1399 + * the daemon.
  1400 + ***************************************************************************/
  1401 +void GApplication::start_daemon(void) const
  1402 +{
  1403 + // Initialise daemon
  1404 + GDaemon daemon;
  1405 +
  1406 + // If daemon is not alive then create instance
  1407 + if (!daemon.alive()) {
  1408 +
  1409 + // Create child process to start the daemon. Do nothing if child
  1410 + // process creation fails.
  1411 + int pid = fork();
  1412 + if (pid >= 0) {
  1413 +
  1414 + // If we have a PID of 0 we are in the child process. In this
  1415 + // case we create and start the daemon ...
  1416 + if (pid == 0) {
  1417 + GDaemon daemon;
  1418 + daemon.start();
  1419 + exit(0);
  1420 + }
  1421 +
  1422 + } // endif: child proces created
  1423 +
  1424 + } // endif: daemon
  1425 +
  1426 + // Return
  1427 + return;
  1428 +}
... ...
src/support/GDaemon.cpp 0 → 100644
  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> // kill()
  36 +#if defined(__linux__) || defined(__linux) || defined(linux) || defined(__gnu_linux__)
  37 +#include <sys/prctl.h> // prctl(), PR_SET_NAME
  38 +#endif
  39 +#include "GDaemon.hpp"
  40 +#include "GCsv.hpp"
  41 +#include "GTime.hpp"
  42 +#include "GXml.hpp"
  43 +#include "GXmlNode.hpp"
  44 +
  45 +/* __ Method name definitions ____________________________________________ */
  46 +
  47 +/* __ Macros _____________________________________________________________ */
  48 +
  49 +/* __ Coding definitions _________________________________________________ */
  50 +
  51 +/* __ Debug definitions __________________________________________________ */
  52 +
  53 +
  54 +/*==========================================================================
  55 + = =
  56 + = Constructors/destructors =
  57 + = =
  58 + ==========================================================================*/
  59 +
  60 +/***********************************************************************//**
  61 + * @brief Void constructor
  62 + ***************************************************************************/
  63 +GDaemon::GDaemon(void)
  64 +{
  65 + // Initialise class members
  66 + init_members();
  67 +
  68 + // Return
  69 + return;
  70 +}
  71 +
  72 +
  73 +/***********************************************************************//**
  74 + * @brief Copy constructor
  75 + *
  76 + * @param[in] daemon Daemon.
  77 + ***************************************************************************/
  78 +GDaemon::GDaemon(const GDaemon& daemon)
  79 +{
  80 + // Initialise class members
  81 + init_members();
  82 +
  83 + // Copy members
  84 + copy_members(daemon);
  85 +
  86 + // Return
  87 + return;
  88 +}
  89 +
  90 +
  91 +/***********************************************************************//**
  92 + * @brief Destructor
  93 + ***************************************************************************/
  94 +GDaemon::~GDaemon(void)
  95 +{
  96 + // Free members
  97 + free_members();
  98 +
  99 + // Return
  100 + return;
  101 +}
  102 +
  103 +
  104 +/*==========================================================================
  105 + = =
  106 + = Operators =
  107 + = =
  108 + ==========================================================================*/
  109 +
  110 +/***********************************************************************//**
  111 + * @brief Assignment operator
  112 + *
  113 + * @param[in] daemon Daemon.
  114 + * @return Daemon.
  115 + ***************************************************************************/
  116 +GDaemon& GDaemon::operator=(const GDaemon& daemon)
  117 +{
  118 + // Execute only if object is not identical
  119 + if (this != &daemon) {
  120 +
  121 + // Free members
  122 + free_members();
  123 +
  124 + // Initialise private members
  125 + init_members();
  126 +
  127 + // Copy members
  128 + copy_members(daemon);
  129 +
  130 + } // endif: object was not identical
  131 +
  132 + // Return this object
  133 + return *this;
  134 +}
  135 +
  136 +
  137 +/*==========================================================================
  138 + = =
  139 + = Public methods =
  140 + = =
  141 + ==========================================================================*/
  142 +
  143 +/***********************************************************************//**
  144 + * @brief Clear Daemon
  145 + ***************************************************************************/
  146 +void GDaemon::clear(void)
  147 +{
  148 + // Free members
  149 + free_members();
  150 +
  151 + // Initialise private members
  152 + init_members();
  153 +
  154 + // Return
  155 + return;
  156 +}
  157 +
  158 +
  159 +/***********************************************************************//**
  160 + * @brief Clone Daemon
  161 + *
  162 + * @return Pointer to deep copy of Daemon.
  163 + ***************************************************************************/
  164 +GDaemon* GDaemon::clone(void) const
  165 +{
  166 + return new GDaemon(*this);
  167 +}
  168 +
  169 +
  170 +/***********************************************************************//**
  171 + * @brief Starts the daemon
  172 + *
  173 + * Starts up the daemon and entered the never-ending daemon event loop.
  174 + ***************************************************************************/
  175 +void GDaemon::start(void)
  176 +{
  177 + // Open logger
  178 + m_log.open(gammalib::gamma_filename("daemon.log"), true);
  179 + m_log.chatter((GChatter)m_chatter);
  180 + m_log.date(true);
  181 +
  182 + // Log start up of daemon
  183 + m_log << "GammaLib daemon start up" << std::endl;
  184 +
  185 + // Set daemon name
  186 + #if defined(__linux__) || defined(__linux) || defined(linux) || defined(__gnu_linux__)
  187 + prctl(PR_SET_NAME, "gammalibd");
  188 + #endif
  189 +
  190 + // Create lock file
  191 + create_lock_file();
  192 +
  193 + // Main event handling loop
  194 + while (1) {
  195 +
  196 + // Update application statistics
  197 + update_statistics();
  198 +
  199 + // Force logger flushing
  200 + m_log.flush(true);
  201 +
  202 + // Wait for some period
  203 + sleep(m_period);
  204 +
  205 + } // endwhile: main event loop
  206 +
  207 + // Delete lock file
  208 + delete_lock_file();
  209 +
  210 + // Close log file
  211 + m_log.close();
  212 +
  213 + // Return
  214 + return;
  215 +}
  216 +
  217 +
  218 +/***********************************************************************//**
  219 + * @brief Check if daemon is alive
  220 + *
  221 + * @return True if daemon is alive
  222 + *
  223 + * Checks if the daemon is alive. The daemon is alive if a lock file exists
  224 + * and if the process with the PID that is found in the lock file is alive.
  225 + *
  226 + * This method can be used by a client to check whether a daemon is alive.
  227 + ***************************************************************************/
  228 +bool GDaemon::alive(void) const
  229 +{
  230 + // Initialise flag
  231 + bool alive = false;
  232 +
  233 + // Get process ID in lock file
  234 + pid_t pid = lock_pid();
  235 +
  236 + // If process ID is positive then check if process is still alive
  237 + if (pid > 0) {
  238 +
  239 + // Check if process is alive
  240 + if (0 == kill(pid, 0)) {
  241 + alive = true;
  242 + }
  243 +
  244 + } // endif: process ID was positive
  245 +
  246 + // Return flag
  247 + return alive;
  248 +}
  249 +
  250 +
  251 +/***********************************************************************//**
  252 + * @brief Print Daemon
  253 + *
  254 + * @param[in] chatter Chattiness.
  255 + * @return String containing Daemon information.
  256 + ***************************************************************************/
  257 +std::string GDaemon::print(const GChatter& chatter) const
  258 +{
  259 + // Initialise result string
  260 + std::string result;
  261 +
  262 + // Continue only if chatter is not silent
  263 + if (chatter != SILENT) {
  264 +
  265 + // Append header
  266 + result.append("=== GDaemon ===");
  267 +
  268 + // Append information
  269 + result.append("\n"+gammalib::parformat("Process ID"));
  270 + result.append(gammalib::str(m_pid));
  271 + result.append("\n"+gammalib::parformat("Wake up period (s)"));
  272 + result.append(gammalib::str(m_period));
  273 + result.append("\n"+gammalib::parformat("Logger chatter level"));
  274 + result.append(gammalib::str(m_chatter));
  275 +
  276 + } // endif: chatter was not silent
  277 +
  278 + // Return result
  279 + return result;
  280 +}
  281 +
  282 +
  283 +/*==========================================================================
  284 + = =
  285 + = Private methods =
  286 + = =
  287 + ==========================================================================*/
  288 +
  289 +/***********************************************************************//**
  290 + * @brief Initialise class members
  291 + ***************************************************************************/
  292 +void GDaemon::init_members(void)
  293 +{
  294 + // Initialise members
  295 + m_pid = 0; //!< No process ID
  296 + m_period = 3600.0; //!< Wake-up daemon every hour
  297 + m_log.clear();
  298 + m_chatter = NORMAL; //!< NORMAL chatter level
  299 +
  300 + // Return
  301 + return;
  302 +}
  303 +
  304 +
  305 +/***********************************************************************//**
  306 + * @brief Copy class members
  307 + *
  308 + * @param[in] daemon Daemon.
  309 + ***************************************************************************/
  310 +void GDaemon::copy_members(const GDaemon& daemon)
  311 +{
  312 + // Copy members
  313 + m_pid = daemon.m_pid;
  314 + m_period = daemon.m_period;
  315 + m_log = daemon.m_log;
  316 + m_chatter = daemon.m_chatter;
  317 +
  318 + // Return
  319 + return;
  320 +}
  321 +
  322 +
  323 +/***********************************************************************//**
  324 + * @brief Delete class members
  325 + ***************************************************************************/
  326 +void GDaemon::free_members(void)
  327 +{
  328 + // Delete lock file
  329 + delete_lock_file();
  330 +
  331 + // Return
  332 + return;
  333 +}
  334 +
  335 +
  336 +/***********************************************************************//**
  337 + * @brief Create the lock file
  338 + *
  339 + * Creates the daemon lock file and write process ID into lock file.
  340 + ***************************************************************************/
  341 +void GDaemon::create_lock_file(void)
  342 +{
  343 + // Get lock filename
  344 + GFilename lockfile = lock_filename();
  345 +
  346 + // Open lock file. Continue only if opening was successful.
  347 + FILE* fptr = fopen(lockfile.url().c_str(), "w");
  348 +
  349 + // Successful?
  350 + if (fptr != NULL) {
  351 +
  352 + // Get process ID
  353 + m_pid = getpid();
  354 +
  355 + // Write process ID into lockfile
  356 + fprintf(fptr, "%d\n", m_pid);
  357 +
  358 + // Close lockfile
  359 + fclose(fptr);
  360 +
  361 + // Log creation of lock file
  362 + m_log << "Created lock file \"" << lockfile.url();
  363 + m_log << "\" for PID " << m_pid << std::endl;
  364 +
  365 + } // endif: Lock file opened successfully
  366 +
  367 + // Return
  368 + return;
  369 +}
  370 +
  371 +
  372 +/***********************************************************************//**
  373 + * @brief Delete daemon lock file
  374 + *
  375 + * Deletes the daemon lock file on disk if the process ID in the lock file
  376 + * corresponds to the process ID of the instance.
  377 + ***************************************************************************/
  378 +void GDaemon::delete_lock_file(void) const
  379 +{
  380 + // If process ID in lock file is the ID of the current process then
  381 + // delete the lock file
  382 + if (lock_pid() == getpid()) {
  383 +
  384 + // Get lock filename
  385 + GFilename lockfile = lock_filename();
  386 +
  387 + // Delete lock file
  388 + std::remove(lockfile.url().c_str());
  389 +
  390 + }
  391 +
  392 + // Return
  393 + return;
  394 +}
  395 +
  396 +
  397 +/***********************************************************************//**
  398 + * @brief Returns process ID in lock file
  399 + *
  400 + * @return Process ID in lock file.
  401 + *
  402 + * Returns the process ID that is found in the lock file. If no lock file
  403 + * exists the method returns 0.
  404 + ***************************************************************************/
  405 +pid_t GDaemon::lock_pid(void) const
  406 +{
  407 + // Initialis process ID
  408 + pid_t pid = 0;
  409 +
  410 + // Get lock filename
  411 + GFilename lockfile = lock_filename();
  412 +
  413 + // Open lock file. Continue only if opening was successful.
  414 + FILE* fptr = fopen(lockfile.url().c_str(), "r");
  415 + if (fptr != NULL) {
  416 +
  417 + // Allocate line buffer
  418 + const int n = 1000;
  419 + char line[n];
  420 +
  421 + // Read line
  422 + fgets(line, n-1, fptr);
  423 +
  424 + // Close file
  425 + fclose(fptr);
  426 +
  427 + // Extract process ID
  428 + pid = gammalib::toint(std::string(line));
  429 +
  430 + } // endif: lock file opened successfully
  431 +
  432 + // Return process ID
  433 + return pid;
  434 +}
  435 +
  436 +
  437 +/***********************************************************************//**
  438 + * @brief Update application statistics
  439 + *
  440 + * Update application statistics in the @p statistics.xml file by scanning
  441 + * the low-level file @p statistics.csv.
  442 + *
  443 + * @todo Delete low-level file if scan was successful.
  444 + ***************************************************************************/
  445 +void GDaemon::update_statistics(void)
  446 +{
  447 + // Set low-level statistics filename
  448 + GFilename filename = gammalib::gamma_filename("statistics.csv");
  449 +
  450 + // Continue only if file exists
  451 + if (filename.exists()) {
  452 +
  453 + // OpenMP critical zone to write statitics
  454 + #pragma omp critical(GDaemon_update_statistics)
  455 + {
  456 +
  457 + // Get file lock. Continue only in case of success.
  458 + struct flock lock;
  459 + lock.l_type = F_RDLCK; // Want a read lock
  460 + lock.l_whence = SEEK_SET; // Want beginning of file
  461 + lock.l_start = 0; // No offset, lock entire file ...
  462 + lock.l_len = 0; // ... to the end
  463 + lock.l_pid = getpid(); // Current process ID
  464 + int fd = open(filename.url().c_str(), O_RDONLY);
  465 + if (fd != -1) {
  466 +
  467 + // Lock file
  468 + fcntl(fd, F_SETLKW, &lock);
  469 +
  470 + // Load CSV file
  471 + GCsv statistics(filename, ",");
  472 +
  473 + // Update high-level statistics
  474 + update_high_level_statistics(statistics);
  475 +
  476 + // Remove low-level statistics file
  477 + std::remove(filename.url().c_str());
  478 +
  479 + // Unlock file
  480 + lock.l_type = F_UNLCK;
  481 + fcntl(fd, F_SETLK, &lock);
  482 +
  483 + // Close file
  484 + close(fd);
  485 +
  486 + } // endif: file locking successful
  487 +
  488 + } // end of OMP critial zone
  489 +
  490 + } // endif: there was a statistics ASCII file
  491 +
  492 + // Return
  493 + return;
  494 +}
  495 +
  496 +
  497 +/***********************************************************************//**
  498 + * @brief Update high-level statistics
  499 + *
  500 + * Update high-level statistics by scanning the low-level statistics file.
  501 + * The high-level statistics is written out as XML file. If no XML file
  502 + * exists a new one is created, otherwise an existing file is updated.
  503 + ***************************************************************************/
  504 +void GDaemon::update_high_level_statistics(const GCsv& statistics)
  505 +{
  506 + // Log updating of high-level statistics
  507 + m_log << "Update high-level statistics" << std::endl;
  508 +
  509 + // Set current time
  510 + GTime now;
  511 + now.now();
  512 +
  513 + // Set high-level statistics filename
  514 + GFilename filename = gammalib::gamma_filename("statistics.xml");
  515 +
  516 + // Initialise high-level statistics
  517 + GXml xml;
  518 +
  519 + // If a high-level statistics file exists already then load information
  520 + // from the file
  521 + if (filename.exists()) {
  522 + xml.load(filename);
  523 + }
  524 +
  525 + // ... otherwise create empty high-level statistics file
  526 + else {
  527 +
  528 + // Append base
  529 + GXmlNode* base = xml.append(GXmlElement("statistics title=\"High-level statistic\""));
  530 +
  531 + // Append header
  532 + GXmlNode* header = base->append("header");
  533 + GXmlNode* dates = header->append("dates");
  534 + GXmlNode* d1 = dates->append("creation");
  535 + d1->append(GXmlText(now.utc()));
  536 + GXmlNode* d2 = dates->append("modified");
  537 + d2->append(GXmlText(now.utc()));
  538 + GXmlNode* d3 = dates->append("start");
  539 + d3->append(GXmlText(""));
  540 + GXmlNode* d4 = dates->append("stop");
  541 + d4->append(GXmlText(""));
  542 +
  543 + // Append data
  544 + GXmlNode* data = base->append("data");
  545 + GXmlNode* daily = data->append("daily");
  546 +
  547 + // Log creation of high-level statistics file
  548 + m_log << "Created high-level statistics XML file \"";
  549 + m_log << filename.url() << "\"" << std::endl;
  550 +
  551 + } // endelse: created empty high-level statistics file
  552 +
  553 + // Get some useful pointers
  554 + GXmlNode* base = xml.element("statistics",0);
  555 + GXmlNode* header = base->element("header",0);
  556 + GXmlNode* data = base->element("data",0);
  557 + GXmlNode* daily = data->element("daily",0);
  558 +
  559 + // Loop over statistics
  560 + for (int i = 0; i < statistics.nrows(); ++i) {
  561 +
  562 + // Skip header row
  563 + if (i == 0) {
  564 + continue;
  565 + }
  566 +
  567 + // Extract relevant attributes
  568 + std::string date = statistics.string(i,0).substr(0,10);
  569 + std::string tool = statistics.string(i,3);
  570 + std::string version = statistics.string(i,4);
  571 + double wall = statistics.real(i,5);
  572 + double cpu = statistics.real(i,6);
  573 + double gCO2e = statistics.real(i,7);
  574 +
  575 + // Skip tools without name
  576 + if (tool.empty()) {
  577 + continue;
  578 + }
  579 +
  580 + // Get pointer to relevant date element. If no date element exists
  581 + // that corresponds to the date of the statistics then append an
  582 + // element
  583 + GXmlNode* n_date = NULL;
  584 + int ndates = daily->elements("date");
  585 + if (ndates == 0) {
  586 + n_date = daily->append("date");
  587 + n_date->append(GXmlElement("value", date));
  588 + }
  589 + else {
  590 + for (int k = 0; k < ndates; ++k) {
  591 + GXmlElement* n = daily->element("date", k);
  592 + if (daily->element("date", k)->element("value",0)->value() == date) {
  593 + n_date = n;
  594 + break;
  595 + }
  596 + }
  597 + if (n_date == NULL) {
  598 + n_date = daily->append("date");
  599 + n_date->append(GXmlElement("value", date));
  600 + }
  601 + }
  602 +
  603 + // Get pointer to "tools" node. If no such node exists then append
  604 + // one
  605 + GXmlNode* n_tools = NULL;
  606 + if (n_date->elements("tools") == 0) {
  607 + n_tools = n_date->append("tools");
  608 + }
  609 + else {
  610 + n_tools = n_date->element("tools",0);
  611 + }
  612 +
  613 + // Get pointer to relevant tool element. If no tool element exists
  614 + // that corresponds to the relevant tool then append an element
  615 + GXmlElement* n_tool = NULL;
  616 + int ntools = n_tools->elements();
  617 + if (ntools == 0) {
  618 + n_tool = n_tools->append(tool);
  619 + }
  620 + else {
  621 + for (int k = 0; k < ntools; ++k) {
  622 + GXmlElement* n = n_tools->element(k);
  623 + if (n->name() == tool) {
  624 + n_tool = n;
  625 + break;
  626 + }
  627 + }
  628 + if (n_tool == NULL) {
  629 + n_tool = n_tools->append(tool);
  630 + }
  631 + }
  632 +
  633 + // Set or update attributes
  634 + int calls = 1;
  635 + if (!n_tool->has_attribute("version")) {
  636 + n_tool->attribute("version", version);
  637 + }
  638 + if (n_tool->has_attribute("calls")) {
  639 + calls += gammalib::toint(n_tool->attribute("calls"));
  640 + }
  641 + if (n_tool->has_attribute("wall")) {
  642 + wall += gammalib::todouble(n_tool->attribute("wall"));
  643 + }
  644 + if (n_tool->has_attribute("cpu")) {
  645 + cpu += gammalib::todouble(n_tool->attribute("cpu"));
  646 + }
  647 + if (n_tool->has_attribute("gCO2e")) {
  648 + gCO2e += gammalib::todouble(n_tool->attribute("gCO2e"));
  649 + }
  650 + n_tool->attribute("calls", gammalib::str(calls));
  651 + n_tool->attribute("wall", gammalib::str(wall));
  652 + n_tool->attribute("cpu", gammalib::str(cpu));
  653 + n_tool->attribute("gCO2e", gammalib::str(gCO2e));
  654 +
  655 + } // endfor: looped over statistics
  656 +
  657 + // Update modified time in header
  658 + GXmlNode* modified = header->element("dates > modified");
  659 + static_cast<GXmlText*>((*modified)[0])->text(now.utc());
  660 +
  661 + // Update first and last time in header
  662 + std::string start;
  663 + std::string stop;
  664 + if (statistics.nrows() > 1) {
  665 + start = statistics.string(1,0);
  666 + stop = statistics.string(statistics.nrows()-1,0);
  667 + GXmlNode* n_start = header->element("dates > start");
  668 + GXmlNode* n_stop = header->element("dates > stop");
  669 + std::string current_start = static_cast<GXmlText*>((*n_start)[0])->text();
  670 + std::string current_stop = static_cast<GXmlText*>((*n_stop)[0])->text();
  671 + if (current_start.empty() || (start < current_start)) {
  672 + static_cast<GXmlText*>((*n_start)[0])->text(start);
  673 + }
  674 + if (current_stop.empty() || (stop > current_stop)) {
  675 + static_cast<GXmlText*>((*n_stop)[0])->text(stop);
  676 + }
  677 + }
  678 +
  679 + // Save high-level statistics file
  680 + xml.save(filename);
  681 +
  682 + // Return
  683 + return;
  684 +}
... ...
src/support/GTools.cpp
... ... @@ -28,7 +28,7 @@
28 28 #ifdef HAVE_CONFIG_H
29 29 #include <config.h>
30 30 #endif
31   -#include <unistd.h> // access() functions
  31 +#include <unistd.h> // geteuid(), access() functions
32 32 #include <fcntl.h> // fcntl() functions
33 33 #include <sys/stat.h>
34 34 #include <sys/time.h> // timeval
... ... @@ -37,7 +37,7 @@
37 37 #include <sys/socket.h> // recv() function
38 38 #include <netinet/in.h> // struct sockaddr_in, struct sockaddr
39 39 #include <netdb.h> // struct hostent, gethostbyname
40   -#include <pwd.h>
  40 +#include <pwd.h> // getpwuid()
41 41 #include <cmath>
42 42 #include <cfloat>
43 43 #include <cctype>
... ... @@ -2331,3 +2331,43 @@ std::string gammalib::host_country(void)
2331 2331 // Return country
2332 2332 return country;
2333 2333 }
  2334 +
  2335 +
  2336 +/***********************************************************************//**
  2337 + * @brief Returns filename in .gamma directory
  2338 + *
  2339 + * @param[in] name Name of file in .gamma directory.
  2340 + * @return Filename in .gamma directory.
  2341 + *
  2342 + * Returns the filename of the name of a file in the .gamma directory.
  2343 + ***************************************************************************/
  2344 +GFilename gammalib::gamma_filename(const std::string& name)
  2345 +{
  2346 + // Initialise lock filename
  2347 + GFilename filename;
  2348 +
  2349 + // First try accessing the user ID to get the home directory
  2350 + uid_t uid = geteuid();
  2351 + struct passwd* pw = getpwuid(uid);
  2352 + if (pw != NULL) {
  2353 + filename = std::string(pw->pw_dir) + "/.gamma/" + name;
  2354 + }
  2355 +
  2356 + // ... otherwise try using the $HOME environment variable
  2357 + else {
  2358 +
  2359 + // First attempt fetching $HOME environment variable
  2360 + char* home_ptr = std::getenv("HOME");
  2361 + if (home_ptr != NULL) {
  2362 + filename = std::string(home_ptr) + "/.gamma/" + name;
  2363 + }
  2364 +
  2365 + // ... otherwise use ~ as home directory
  2366 + else {
  2367 + filename = "~/.gamma/" + name;
  2368 + }
  2369 + }
  2370 +
  2371 + // Return filename
  2372 + return filename;
  2373 +}
... ...
src/support/Makefile.am
1 1 #############################################################################
2 2 # Makefile.am for support module #
3 3 # ------------------------------------------------------------------------- #
4   -# Copyright (C) 2016-2021 Juergen Knoedlseder #
  4 +# Copyright (C) 2016-2022 Juergen Knoedlseder #
5 5 # ------------------------------------------------------------------------- #
6 6 # #
7 7 # This program is free software: you can redistribute it and/or modify #
... ... @@ -36,7 +36,9 @@ sources = GException.cpp \
36 36 GUrl.cpp \
37 37 GUrlFile.cpp \
38 38 GUrlString.cpp \
39   - GFilename.cpp
  39 + GFilename.cpp \
  40 + GDaemon.cpp
  41 +
40 42  
41 43 # Build libtool library
42 44 noinst_LTLIBRARIES = libsupport.la
... ...