/***************************************************************************
* GDaemon.cpp - Daemon class *
* ----------------------------------------------------------------------- *
* copyright (C) 2022 by Juergen Knoedlseder *
* ----------------------------------------------------------------------- *
* *
* This program is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation, either version 3 of the License, or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program. If not, see . *
* *
***************************************************************************/
/**
* @file GDaemon.cpp
* @brief Daemon class implementation
* @author Juergen Knoedlseder
*/
/* __ Includes ___________________________________________________________ */
#ifdef HAVE_CONFIG_H
#include
#endif
#include // std::getenv()
#include // std::fopen()
#include // sleep()
#include // for file locking
#include // kill()
#if defined(__linux__) || defined(__linux) || defined(linux) || defined(__gnu_linux__)
#include // prctl(), PR_SET_NAME
#endif
#include "GDaemon.hpp"
#include "GCsv.hpp"
#include "GTime.hpp"
#include "GXml.hpp"
#include "GXmlNode.hpp"
/* __ Method name definitions ____________________________________________ */
/* __ Macros _____________________________________________________________ */
/* __ Coding definitions _________________________________________________ */
/* __ Debug definitions __________________________________________________ */
/*==========================================================================
= =
= Constructors/destructors =
= =
==========================================================================*/
/***********************************************************************//**
* @brief Void constructor
***************************************************************************/
GDaemon::GDaemon(void)
{
// Initialise class members
init_members();
// Return
return;
}
/***********************************************************************//**
* @brief Copy constructor
*
* @param[in] daemon Daemon.
***************************************************************************/
GDaemon::GDaemon(const GDaemon& daemon)
{
// Initialise class members
init_members();
// Copy members
copy_members(daemon);
// Return
return;
}
/***********************************************************************//**
* @brief Destructor
***************************************************************************/
GDaemon::~GDaemon(void)
{
// Free members
free_members();
// Return
return;
}
/*==========================================================================
= =
= Operators =
= =
==========================================================================*/
/***********************************************************************//**
* @brief Assignment operator
*
* @param[in] daemon Daemon.
* @return Daemon.
***************************************************************************/
GDaemon& GDaemon::operator=(const GDaemon& daemon)
{
// Execute only if object is not identical
if (this != &daemon) {
// Free members
free_members();
// Initialise private members
init_members();
// Copy members
copy_members(daemon);
} // endif: object was not identical
// Return this object
return *this;
}
/*==========================================================================
= =
= Public methods =
= =
==========================================================================*/
/***********************************************************************//**
* @brief Clear Daemon
***************************************************************************/
void GDaemon::clear(void)
{
// Free members
free_members();
// Initialise private members
init_members();
// Return
return;
}
/***********************************************************************//**
* @brief Clone Daemon
*
* @return Pointer to deep copy of Daemon.
***************************************************************************/
GDaemon* GDaemon::clone(void) const
{
return new GDaemon(*this);
}
/***********************************************************************//**
* @brief Starts the daemon
*
* Starts up the daemon and entered the never-ending daemon event loop.
***************************************************************************/
void GDaemon::start(void)
{
// Open logger
m_log.open(gammalib::gamma_filename("daemon.log"), true);
m_log.chatter((GChatter)m_chatter);
m_log.date(true);
// Log start up of daemon
m_log << "GammaLib daemon start up" << std::endl;
// Set daemon name
#if defined(__linux__) || defined(__linux) || defined(linux) || defined(__gnu_linux__)
prctl(PR_SET_NAME, "gammalibd");
#endif
// Create lock file
create_lock_file();
// Main event handling loop
while (1) {
// Update application statistics
update_statistics();
// Force logger flushing
m_log.flush(true);
// Wait for some period
sleep(m_period);
} // endwhile: main event loop
// Delete lock file
delete_lock_file();
// Close log file
m_log.close();
// Return
return;
}
/***********************************************************************//**
* @brief Check if daemon is alive
*
* @return True if daemon is alive
*
* Checks if the daemon is alive. The daemon is alive if a lock file exists
* and if the process with the PID that is found in the lock file is alive.
*
* This method can be used by a client to check whether a daemon is alive.
***************************************************************************/
bool GDaemon::alive(void) const
{
// Initialise flag
bool alive = false;
// Get process ID in lock file
pid_t pid = lock_pid();
// If process ID is positive then check if process is still alive
if (pid > 0) {
// Check if process is alive
if (0 == kill(pid, 0)) {
alive = true;
}
} // endif: process ID was positive
// Return flag
return alive;
}
/***********************************************************************//**
* @brief Print Daemon
*
* @param[in] chatter Chattiness.
* @return String containing Daemon information.
***************************************************************************/
std::string GDaemon::print(const GChatter& chatter) const
{
// Initialise result string
std::string result;
// Continue only if chatter is not silent
if (chatter != SILENT) {
// Append header
result.append("=== GDaemon ===");
// Append information
result.append("\n"+gammalib::parformat("Process ID"));
result.append(gammalib::str(m_pid));
result.append("\n"+gammalib::parformat("Wake up period (s)"));
result.append(gammalib::str(m_period));
result.append("\n"+gammalib::parformat("Logger chatter level"));
result.append(gammalib::str(m_chatter));
} // endif: chatter was not silent
// Return result
return result;
}
/*==========================================================================
= =
= Private methods =
= =
==========================================================================*/
/***********************************************************************//**
* @brief Initialise class members
***************************************************************************/
void GDaemon::init_members(void)
{
// Initialise members
m_pid = 0; //!< No process ID
m_period = 3600.0; //!< Wake-up daemon every hour
m_log.clear();
m_chatter = NORMAL; //!< NORMAL chatter level
// Return
return;
}
/***********************************************************************//**
* @brief Copy class members
*
* @param[in] daemon Daemon.
***************************************************************************/
void GDaemon::copy_members(const GDaemon& daemon)
{
// Copy members
m_pid = daemon.m_pid;
m_period = daemon.m_period;
m_log = daemon.m_log;
m_chatter = daemon.m_chatter;
// Return
return;
}
/***********************************************************************//**
* @brief Delete class members
***************************************************************************/
void GDaemon::free_members(void)
{
// Delete lock file
delete_lock_file();
// Return
return;
}
/***********************************************************************//**
* @brief Create the lock file
*
* Creates the daemon lock file and write process ID into lock file.
***************************************************************************/
void GDaemon::create_lock_file(void)
{
// Get lock filename
GFilename lockfile = lock_filename();
// Open lock file. Continue only if opening was successful.
FILE* fptr = fopen(lockfile.url().c_str(), "w");
// Successful?
if (fptr != NULL) {
// Get process ID
m_pid = getpid();
// Write process ID into lockfile
fprintf(fptr, "%d\n", m_pid);
// Close lockfile
fclose(fptr);
// Log creation of lock file
m_log << "Created lock file \"" << lockfile.url();
m_log << "\" for PID " << m_pid << std::endl;
} // endif: Lock file opened successfully
// Return
return;
}
/***********************************************************************//**
* @brief Delete daemon lock file
*
* Deletes the daemon lock file on disk if the process ID in the lock file
* corresponds to the process ID of the instance.
***************************************************************************/
void GDaemon::delete_lock_file(void) const
{
// If process ID in lock file is the ID of the current process then
// delete the lock file
if (lock_pid() == getpid()) {
// Get lock filename
GFilename lockfile = lock_filename();
// Delete lock file
std::remove(lockfile.url().c_str());
}
// Return
return;
}
/***********************************************************************//**
* @brief Returns process ID in lock file
*
* @return Process ID in lock file.
*
* Returns the process ID that is found in the lock file. If no lock file
* exists the method returns 0.
***************************************************************************/
pid_t GDaemon::lock_pid(void) const
{
// Initialis process ID
pid_t pid = 0;
// Get lock filename
GFilename lockfile = lock_filename();
// Open lock file. Continue only if opening was successful.
FILE* fptr = fopen(lockfile.url().c_str(), "r");
if (fptr != NULL) {
// Allocate line buffer
const int n = 1000;
char line[n];
// Read line
fgets(line, n-1, fptr);
// Close file
fclose(fptr);
// Extract process ID
pid = gammalib::toint(std::string(line));
} // endif: lock file opened successfully
// Return process ID
return pid;
}
/***********************************************************************//**
* @brief Update application statistics
*
* Update application statistics in the @p statistics.xml file by scanning
* the low-level file @p statistics.csv.
*
* @todo Delete low-level file if scan was successful.
***************************************************************************/
void GDaemon::update_statistics(void)
{
// Set low-level statistics filename
GFilename filename = gammalib::gamma_filename("statistics.csv");
// Continue only if file exists
if (filename.exists()) {
// OpenMP critical zone to write statitics
#pragma omp critical(GDaemon_update_statistics)
{
// Get file lock. Continue only in case of success.
struct flock lock;
lock.l_type = F_RDLCK; // Want a read lock
lock.l_whence = SEEK_SET; // Want beginning of file
lock.l_start = 0; // No offset, lock entire file ...
lock.l_len = 0; // ... to the end
lock.l_pid = getpid(); // Current process ID
int fd = open(filename.url().c_str(), O_RDONLY);
if (fd != -1) {
// Lock file
fcntl(fd, F_SETLKW, &lock);
// Load CSV file
GCsv statistics(filename, ",");
// Update high-level statistics
update_high_level_statistics(statistics);
// Remove low-level statistics file
std::remove(filename.url().c_str());
// Unlock file
lock.l_type = F_UNLCK;
fcntl(fd, F_SETLK, &lock);
// Close file
close(fd);
} // endif: file locking successful
} // end of OMP critial zone
} // endif: there was a statistics ASCII file
// Return
return;
}
/***********************************************************************//**
* @brief Update high-level statistics
*
* Update high-level statistics by scanning the low-level statistics file.
* The high-level statistics is written out as XML file. If no XML file
* exists a new one is created, otherwise an existing file is updated.
***************************************************************************/
void GDaemon::update_high_level_statistics(const GCsv& statistics)
{
// Log updating of high-level statistics
m_log << "Update high-level statistics" << std::endl;
// Set current time
GTime now;
now.now();
// Set high-level statistics filename
GFilename filename = gammalib::gamma_filename("statistics.xml");
// Initialise high-level statistics
GXml xml;
// If a high-level statistics file exists already then load information
// from the file
if (filename.exists()) {
xml.load(filename);
}
// ... otherwise create empty high-level statistics file
else {
// Append base
GXmlNode* base = xml.append(GXmlElement("statistics title=\"High-level statistic\""));
// Append header
GXmlNode* header = base->append("header");
GXmlNode* dates = header->append("dates");
GXmlNode* d1 = dates->append("creation");
d1->append(GXmlText(now.utc()));
GXmlNode* d2 = dates->append("modified");
d2->append(GXmlText(now.utc()));
GXmlNode* d3 = dates->append("start");
d3->append(GXmlText(""));
GXmlNode* d4 = dates->append("stop");
d4->append(GXmlText(""));
// Append data
GXmlNode* data = base->append("data");
GXmlNode* daily = data->append("daily");
// Log creation of high-level statistics file
m_log << "Created high-level statistics XML file \"";
m_log << filename.url() << "\"" << std::endl;
} // endelse: created empty high-level statistics file
// Get some useful pointers
GXmlNode* base = xml.element("statistics",0);
GXmlNode* header = base->element("header",0);
GXmlNode* data = base->element("data",0);
GXmlNode* daily = data->element("daily",0);
// Loop over statistics
for (int i = 0; i < statistics.nrows(); ++i) {
// Skip header row
if (i == 0) {
continue;
}
// Extract relevant attributes
std::string date = statistics.string(i,0).substr(0,10);
std::string tool = statistics.string(i,3);
std::string version = statistics.string(i,4);
double wall = statistics.real(i,5);
double cpu = statistics.real(i,6);
double gCO2e = statistics.real(i,7);
// Skip tools without name
if (tool.empty()) {
continue;
}
// Get pointer to relevant date element. If no date element exists
// that corresponds to the date of the statistics then append an
// element
GXmlNode* n_date = NULL;
int ndates = daily->elements("date");
if (ndates == 0) {
n_date = daily->append("date");
n_date->append(GXmlElement("value", date));
}
else {
for (int k = 0; k < ndates; ++k) {
GXmlElement* n = daily->element("date", k);
if (daily->element("date", k)->element("value",0)->value() == date) {
n_date = n;
break;
}
}
if (n_date == NULL) {
n_date = daily->append("date");
n_date->append(GXmlElement("value", date));
}
}
// Get pointer to "tools" node. If no such node exists then append
// one
GXmlNode* n_tools = NULL;
if (n_date->elements("tools") == 0) {
n_tools = n_date->append("tools");
}
else {
n_tools = n_date->element("tools",0);
}
// Get pointer to relevant tool element. If no tool element exists
// that corresponds to the relevant tool then append an element
GXmlElement* n_tool = NULL;
int ntools = n_tools->elements();
if (ntools == 0) {
n_tool = n_tools->append(tool);
}
else {
for (int k = 0; k < ntools; ++k) {
GXmlElement* n = n_tools->element(k);
if (n->name() == tool) {
n_tool = n;
break;
}
}
if (n_tool == NULL) {
n_tool = n_tools->append(tool);
}
}
// Set or update attributes
int calls = 1;
if (!n_tool->has_attribute("version")) {
n_tool->attribute("version", version);
}
if (n_tool->has_attribute("calls")) {
calls += gammalib::toint(n_tool->attribute("calls"));
}
if (n_tool->has_attribute("wall")) {
wall += gammalib::todouble(n_tool->attribute("wall"));
}
if (n_tool->has_attribute("cpu")) {
cpu += gammalib::todouble(n_tool->attribute("cpu"));
}
if (n_tool->has_attribute("gCO2e")) {
gCO2e += gammalib::todouble(n_tool->attribute("gCO2e"));
}
n_tool->attribute("calls", gammalib::str(calls));
n_tool->attribute("wall", gammalib::str(wall));
n_tool->attribute("cpu", gammalib::str(cpu));
n_tool->attribute("gCO2e", gammalib::str(gCO2e));
} // endfor: looped over statistics
// Update modified time in header
GXmlNode* modified = header->element("dates > modified");
static_cast((*modified)[0])->text(now.utc());
// Update first and last time in header
std::string start;
std::string stop;
if (statistics.nrows() > 1) {
start = statistics.string(1,0);
stop = statistics.string(statistics.nrows()-1,0);
GXmlNode* n_start = header->element("dates > start");
GXmlNode* n_stop = header->element("dates > stop");
std::string current_start = static_cast((*n_start)[0])->text();
std::string current_stop = static_cast((*n_stop)[0])->text();
if (current_start.empty() || (start < current_start)) {
static_cast((*n_start)[0])->text(start);
}
if (current_stop.empty() || (stop > current_stop)) {
static_cast((*n_stop)[0])->text(stop);
}
}
// Save high-level statistics file
xml.save(filename);
// Return
return;
}