diff --git a/cmake/version.cmake b/cmake/version.cmake index 20ebfed..f614e9c 100644 --- a/cmake/version.cmake +++ b/cmake/version.cmake @@ -3,7 +3,7 @@ # set (TK_UTIL_MAJOR_VERSION "6") -set (TK_UTIL_MINOR_VERSION "11") +set (TK_UTIL_MINOR_VERSION "12") set (TK_UTIL_RELEASE_VERSION "0") set (TK_UTIL_VERSION ${TK_UTIL_MAJOR_VERSION}.${TK_UTIL_MINOR_VERSION}.${TK_UTIL_RELEASE_VERSION}) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 81119f5..b18cf7d 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -3,6 +3,7 @@ enable_testing ( ) add_subdirectory (TkUtil) add_subdirectory (TkUtilScripting) add_subdirectory (TkutilLauncher) +add_subdirectory (appstats) add_subdirectory (tests) add_subdirectory (encoding) add_subdirectory (socket_proxy) diff --git a/src/TkUtil/ApplicationStats.cpp b/src/TkUtil/ApplicationStats.cpp index d57990d..97abb96 100644 --- a/src/TkUtil/ApplicationStats.cpp +++ b/src/TkUtil/ApplicationStats.cpp @@ -2,6 +2,7 @@ #include "TkUtil/ApplicationStats.h" #include "TkUtil/Exception.h" #include "TkUtil/File.h" +#include "TkUtil/NumericConversions.h" #include "TkUtil/UTF8String.h" #include @@ -16,6 +17,7 @@ #include // flock #include // fopen, fseek, fscanf, fprintf #include // strerror +#include // fchmod #include #include // fork, setsid @@ -49,6 +51,15 @@ ApplicationStats::~ApplicationStats ( ) } // ApplicationStats::~ApplicationStats +string ApplicationStats::getFileName (const string& appName, const string& logDir, size_t month, size_t year) +{ + UTF8String fileName (Charset::UTF_8); + fileName << logDir << "/" << appName << "_" << NumericConversions::toStr (year, 4) << NumericConversions::toStr (month, 2) << ".logs"; + + return fileName.utf8 ( ); +} // ApplicationStats::getFileName + + void ApplicationStats::logUsage (const string& appName, const string& logDir) { // En vue de ne pas altérer le comportement de l'application tout est effectuée dans un processus fils. @@ -81,11 +92,11 @@ void ApplicationStats::logUsage (const string& appName, const string& logDir) // Le nom du fichier : const Date date; const string user (UserData (true).getName ( )); - UTF8String fileName; - fileName << logDir << "/" << appName << "_" << IN_UTIL setw (4) << date.getYear ( ) << setw (2) << (unsigned long)date.getMonth ( ) << ".logs"; + UTF8String fileName (getFileName (appName, logDir, (unsigned long)date.getMonth ( ), date.getYear ( )), Charset::UTF_8); // On ouvre le fichier en lecture/écriture : - FILE* file = fopen (fileName.utf8 ( ).c_str ( ), "r+"); // Ne créé pas le fichier => on le créé ci-dessous si nécessaire : + FILE* file = fopen (fileName.utf8 ( ).c_str ( ), "r+"); // Ne créé pas le fichier => on le créé ci-dessous si nécessaire : + const bool created = NULL == file ? true : false; file = NULL == file ? fopen (fileName.utf8 ( ).c_str ( ), "a+") : file; if (NULL == file) { @@ -134,6 +145,18 @@ void ApplicationStats::logUsage (const string& appName, const string& logDir) fclose (file); return; } // if (0 != flock (fd, LOCK_EX)) + + // Conférer aufichier les droits en écriture pour tous le monde si il vient d'être créé : + if (true == created) + { + if (0 != fchmod (fd, S_IRWXU | S_IRWXG | S_IRWXO)) + { + ConsoleOutput::cerr ( ) << "Erreur lors du confèrement à autrui des droits en écriture sur le fichier de logs " << fileName << " : " << strerror (errno) << co_endl; + fclose (file); + return; + + } // if (0 != fchmod (fd, S_IRWXU | S_IRWXG | S_IRWXO)) + } // if (true == created) // Lecture et actualisation des logs existants : map logs; @@ -200,8 +223,111 @@ void ApplicationStats::logUsage (const string& appName, const string& logDir) { ConsoleOutput::cerr ( ) << "Erreur lors du déverrouillage du fichier de logs " << fileName << " : " << strerror (errno) << co_endl; fclose (file); - } // if (0 != flock (fd, LOCK_UN)) + } // if (0 != flock (fd, LOCK_UN)) + + fclose (file); + file = NULL; + fd = -1; } // ApplicationStats::logUsage +void ApplicationStats::logStats (std::ostream& output, const std::string& appName, const string& from, const string& to, const std::string& logDir) +{ + map logs; + const size_t fromMonth = NumericConversions::strToULong (UTF8String (from).substring (0, 1)); + const size_t fromYear = NumericConversions::strToULong (UTF8String (from).substring (2)); + const size_t toMonth = NumericConversions::strToULong (UTF8String (to).substring (0, 1)); + const size_t toYear = NumericConversions::strToULong (UTF8String (to).substring (2)); + + if (fromYear == toYear) + { + for (size_t m = fromMonth; m <= toMonth; m++) + { + cout << "Performing " << m << "/" << fromYear << endl; + UTF8String fileName (getFileName (appName, logDir, m, fromYear), Charset::UTF_8); + + if (0 != readLogs (fileName, logs)) + ConsoleOutput::cerr ( ) << "Erreur lors de la lecture du fichier de logs " << fileName << " : " << strerror (errno) << co_endl; + } // for (size_t m = fromMonth; m <= toMonth; m++) + } // if (fromYear == toYear) + else + { + for (size_t m = fromMonth; m <= 12; m++) + { + cout << "Performing " << m << "/" << fromYear << endl; + UTF8String fileName (getFileName (appName, logDir, m, fromYear), Charset::UTF_8); + + if (0 != readLogs (fileName, logs)) + ConsoleOutput::cerr ( ) << "Erreur lors de la lecture du fichier de logs " << fileName << " : " << strerror (errno) << co_endl; + } + for (size_t y = fromYear + 1; y < toYear; y++) + for (size_t m = 1; m <= 12; m++) + { + cout << "Performing " << m << "/" << y << endl; + UTF8String fileName (getFileName (appName, logDir, m, y), Charset::UTF_8); + + if (0 != readLogs (fileName, logs)) + ConsoleOutput::cerr ( ) << "Erreur lors de la lecture du fichier de logs " << fileName << " : " << strerror (errno) << co_endl; + } + for (size_t m = 1; m <= toMonth; m++) + { + cout << "Performing " << m << "/" << toYear << endl; + UTF8String fileName (getFileName (appName, logDir, m, toYear), Charset::UTF_8); + + if (0 != readLogs (fileName, logs)) + ConsoleOutput::cerr ( ) << "Erreur lors de la lecture du fichier de logs " << fileName << " : " << strerror (errno) << co_endl; + } + } // else if (fromYear == toYear) + + // On imprime les résultats dans le flux : + for (map::const_iterator itl = logs.begin ( ); logs.end ( ) != itl; itl++) + output << (*itl).first << "\t\t" << (*itl).second << endl; +} // ApplicationStats::logStats + + +int ApplicationStats::readLogs (const string& fileName, map& logs) +{ + File logFile (fileName); + if (false == logFile.isReadable ( )) + return 0; + + int retval = 0; + errno = 0; + FILE* file = fopen (fileName.c_str ( ), "r"); + if (NULL != file) + { + retval = readLogs (*file, fileName, logs); + fclose (file); + } // if (NULL != file) + + return retval; +} // ApplicationStats::readLogs + + +int ApplicationStats::readLogs (FILE& file, const string& fileName, map& logs) +{ + errno = 0; + size_t line = 1, count = 0; + int flag = 0; + char name [256]; + while (2 == (flag = fscanf (&file, "%s\t%u", name, &count))) + { + line++; + map::iterator itl = logs.find (name); + if (logs.end ( ) == itl) + logs.insert (pair (name, count)); + else + (*itl).second += count; + count = 0; + } // while (2 == fscanf (file, "%s\t%u", name, &count)) + if ((flag < 2) && (EOF != flag)) + { + ConsoleOutput::cerr ( ) << "Erreur lors de la lecture du fichier de logs " << fileName << " en ligne " << (unsigned long)line << " : fichier probablement corrompu." << co_endl; + return -1; + } // if (flag < 2) + + return errno; +} // ApplicationStats::readLogs + + END_NAMESPACE_UTIL diff --git a/src/TkUtil/public/TkUtil/ApplicationStats.h b/src/TkUtil/public/TkUtil/ApplicationStats.h index 2de01d4..c4a1c81 100644 --- a/src/TkUtil/public/TkUtil/ApplicationStats.h +++ b/src/TkUtil/public/TkUtil/ApplicationStats.h @@ -3,8 +3,11 @@ #include +#include #include +#include + BEGIN_NAMESPACE_UTIL @@ -27,7 +30,15 @@ class ApplicationStats * utilisateur nbutilisations */ //@{ Fichier simplifié d'utilisation d'applications - + + /** + * @param appName est le nom de l'application + * @param logDir est le répertoire où sont stockés les fichiers de logs + * @param Mois et année concernés + * @return Un nom de fichier simplifié de logs. + */ + static std::string getFileName (const std::string& appName, const std::string& logDir, size_t month, size_t year); + /** * Ajoute une utilisation de cette application à l'utilisateur courrant. L'opération se fait dans un processus détaché. * Toute erreur rencontrée est affichée dans la console de lancement de l'application. @@ -35,12 +46,29 @@ class ApplicationStats * @param logDir est le répertoire où sont stockés les fichiers de logs */ static void logUsage (const std::string& appName, const std::string& logDir); - + + /** + * Rassemble les utilisations d'une application sur la période demandée et en affiche la synthèse dans le flux transmis en argument. + * Toute erreur rencontrée est affichée dans la console de lancement de l'application. + * @param flux où est écrit la synthèse + * @param appName est le nom de l'application + * @param est la date de début au format MMYYYY + * @param est la date de fin au format MMYYYY + * @param logDir est le répertoire où sont stockés les fichiers de logs + */ + static void logStats (std::ostream& output, const std::string& appName, const std::string& logDir, const std::string& from, const std::string& to); //@} Fichier simplifié d'utilisation d'applications private : - + + /** + * Charge les logs du fichiers ouvert transmis en argument dans la map transmise en second argument. + * @return 0 en cas de succès. En cas d'erreur errno est positionné. + */ + static int readLogs (const std::string& fileName, std::map& logs); + static int readLogs (FILE& file, const std::string& fileName, std::map& logs); + /** * Constructeurs/Destructeur : interdits. */ diff --git a/src/appstats/CMakeLists.txt b/src/appstats/CMakeLists.txt new file mode 100644 index 0000000..8e336c2 --- /dev/null +++ b/src/appstats/CMakeLists.txt @@ -0,0 +1,41 @@ +set (CURRENT_PACKAGE_NAME "Appstats") + +include (${CMAKE_SOURCE_DIR}/cmake/version.cmake) +include (${GUIToolkitsVariables_CMAKE_DIR}/common.cmake) +include (${GUIToolkitsVariables_CMAKE_DIR}/workarounds.cmake) + +add_executable (appstats appstats.cpp) + +target_link_libraries (appstats PUBLIC TkUtil) +# INSTALL_RPATH modifie le rpath pour les libs internes au projet : +set_target_properties (appstats PROPERTIES INSTALL_RPATH_USE_LINK_PATH 1 INSTALL_RPATH ${CMAKE_PACKAGE_RPATH_DIR}) + +# INSTALLATION : +include(CMakePackageConfigHelpers) +# ConfigPackageLocation : c'est plus ou moins standardisé, le défaut étant lib/cmake. +# On nos recommande très vivement d'utiliser ce répertoire afin de limiter les soucis par la suite, +# notamment au niveau des vues. +set (ConfigPackageLocation ${CMAKE_CMAKE_DIR}) + +install(TARGETS appstats EXPORT Appstats DESTINATION ${CMAKE_INSTALL_BINDIR} PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE GROUP_READ GROUP_WRITE GROUP_EXECUTE WORLD_READ WORLD_EXECUTE) + +# Utilisation par d'autres modules de appstats : +set (RUNTIME_INSTALL_DIR bin/) # appstats_RUNTIME_DIR avec AppstatsConfig.cmake.in +configure_package_config_file(cmake/${CURRENT_PACKAGE_NAME}Config.cmake.in + ${CMAKE_CURRENT_BINARY_DIR}/${CURRENT_PACKAGE_NAME}/${CURRENT_PACKAGE_NAME}Config.cmake + INSTALL_DESTINATION ${CMAKE_CMAKE_DIR} + PATH_VARS RUNTIME_INSTALL_DIR + ) +write_basic_package_version_file( + ${CMAKE_CURRENT_BINARY_DIR}/${CURRENT_PACKAGE_NAME}/${CURRENT_PACKAGE_NAME}ConfigVersion.cmake + VERSION ${TK_UTIL_VERSION} + COMPATIBILITY SameMinorVersion + ) +install ( + FILES + ${CMAKE_CURRENT_BINARY_DIR}/${CURRENT_PACKAGE_NAME}/${CURRENT_PACKAGE_NAME}Config.cmake + ${CMAKE_CURRENT_BINARY_DIR}/${CURRENT_PACKAGE_NAME}/${CURRENT_PACKAGE_NAME}ConfigVersion.cmake + DESTINATION ${ConfigPackageLocation} PERMISSIONS OWNER_READ OWNER_WRITE GROUP_READ GROUP_WRITE WORLD_READ + ) + + diff --git a/src/appstats/appstats.cpp b/src/appstats/appstats.cpp new file mode 100644 index 0000000..993f5fb --- /dev/null +++ b/src/appstats/appstats.cpp @@ -0,0 +1,103 @@ +#include +#include +#include +#include + +#include + +using namespace TkUtil; +using namespace std; + + +static bool parseArgs (int argc, char* argv []); +static int syntax (int argc, char* argv []); + +static string appName, logDir, from, to; + + +int main (int argc, char* argv []) +{ + try + { + if (false == parseArgs (argc, argv)) + return syntax (argc, argv); + +cout << "APP=" << appName << " DIR=" << logDir << " FROM=" << from << " TO=" << to << endl; + ApplicationStats::logStats (cout, appName, from, to, logDir); + } + catch (const Exception& exc) + { + cerr << "Erreur : " << exc.getFullMessage ( ) << endl; + return syntax (argc, argv); + } + catch (...) + { + cerr << "Erreur non documentée." << endl; + return syntax (argc, argv); + } + + return 0; +} // main + + +static bool parseArgs (int argc, char* argv []) +{ + for (int i = 1; i < argc; i++) + { + bool kept = false; + + if (i < argc) + { + if (string ("-name") == argv [i]) + { + appName = argv [++i]; + kept = true; + } // if (string ("-name") == argv [i]) + else if (string ("-dir") == argv [i]) + { + logDir = argv [++i]; + kept = true; + } // if (string ("-dir") == argv [i]) + else if (string ("-from") == argv [i]) + { + from = argv [++i]; + kept = true; + } // if (string ("-from") == argv [i]) + else if (string ("-to") == argv [i]) + { + to = argv [++i]; + kept = true; + } // if (string ("-to") == argv [i]) + + if (false == kept) + { + UTF8String message (Charset::UTF_8); + message << "Argument non traité : " << argv [i]; + throw Exception (message); + } // if (false == kept) + } // if (i < argc) + } // for (int i = 0; i < argc; i++) + if ((true == appName.empty ( )) || (true == logDir.empty ( ))) + return false; + + Date now; + if (true == from.empty ( )) + from = NumericConversions::toStr ((size_t)now.getMonth ( ), 2) + NumericConversions::toStr ((size_t)(now.getYear ( )), 4); // Hum hum + if (true == to.empty ( )) + to = NumericConversions::toStr ((size_t)now.getMonth ( ), 2) + NumericConversions::toStr ((size_t)(now.getYear ( )), 4); // Hum hum + + return true; +} // parseArgs + + +static int syntax (int argc, char* argv []) +{ + cerr << argv [0] << "-name appName -dir logDir [-from date][-to date]" << endl + << "où :" << endl + << "appName est le nom de l'application," << endl + << "logDir est le répertoire où sont stockés les fichiers de logs de l'application" << endl + << "date est une date au format MMYYYY, sindiquant les premiers logs à prendre en compte après -from," << endl + << "et les derniers logs à prendre en compte après -to." << endl; + + return -1; +} // syntax diff --git a/src/appstats/cmake/AppstatsConfig.cmake.in b/src/appstats/cmake/AppstatsConfig.cmake.in new file mode 100644 index 0000000..285e430 --- /dev/null +++ b/src/appstats/cmake/AppstatsConfig.cmake.in @@ -0,0 +1,5 @@ +@PACKAGE_INIT@ +set (TkUtil_VERSION @TK_UTIL_VERSION@) +set_and_check (appstats_RUNTIME_DIR "@PACKAGE_RUNTIME_INSTALL_DIR@") +check_required_components (TkUtil) + diff --git a/src/tests/CMakeLists.txt b/src/tests/CMakeLists.txt index 824b2bb..fbbad54 100644 --- a/src/tests/CMakeLists.txt +++ b/src/tests/CMakeLists.txt @@ -3,7 +3,7 @@ include (${GUIToolkitsVariables_CMAKE_DIR}/common.cmake) include (${GUIToolkitsVariables_CMAKE_DIR}/workarounds.cmake) set (ALL_EXECUTABLES - ansi_esc_codes app_usage canceled_threads conversions date dir_list exceptions + ansi_esc_codes canceled_threads conversions date dir_list exceptions fileExtractor fileinfos fileopts hostinfos joinable locale logs memory modify_script process remoteProcess removefile script_tags scripting_logs thread_manager thread_pool timer tmpfile diff --git a/versions.txt b/versions.txt index 8a01e49..b0c823d 100644 --- a/versions.txt +++ b/versions.txt @@ -1,3 +1,10 @@ +Version 6.12.0 : 11/02/25 +================= + +Correctif ApplicationStats::logUsage : le fichier n'était pas fermé en fin de traitement et pouvait laisser des traces dans le système de fichiers. +Application appstats pour voir les statistiques d'utilisation d'une application sur une période donnée. Première version. + + Version 6.11.0 : 07/02/25 =================