|
| 1 | +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. |
| 2 | +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. |
| 3 | +// All rights not expressly granted are reserved. |
| 4 | +// |
| 5 | +// This software is distributed under the terms of the GNU General Public |
| 6 | +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". |
| 7 | +// |
| 8 | +// In applying this license CERN does not waive the privileges and immunities |
| 9 | +// granted to it by virtue of its status as an Intergovernmental Organization |
| 10 | +// or submit itself to any jurisdiction. |
| 11 | + |
| 12 | +/// \file EPNstderrMonitor.cxx |
| 13 | +/// \author David Rohr |
| 14 | + |
| 15 | +#include <fairmq/Device.h> |
| 16 | +#include <fairmq/runDevice.h> |
| 17 | + |
| 18 | +#include "InfoLogger/InfoLogger.hxx" |
| 19 | + |
| 20 | +#include <string> |
| 21 | +#include <thread> |
| 22 | +#include <vector> |
| 23 | +#include <unordered_map> |
| 24 | +#include <regex> |
| 25 | +#include <filesystem> |
| 26 | +#include <chrono> |
| 27 | +#include <fstream> |
| 28 | + |
| 29 | +#include <unistd.h> |
| 30 | +#include <sys/inotify.h> |
| 31 | +#include <poll.h> |
| 32 | + |
| 33 | +using namespace AliceO2; |
| 34 | + |
| 35 | +static constexpr size_t MAX_LINES_FILE = 30; |
| 36 | +static constexpr size_t MAX_BYTES_FILE = MAX_LINES_FILE * 512; |
| 37 | +static constexpr size_t MAX_LINES_TOTAL = 1000; |
| 38 | +static constexpr size_t MAX_BYTES_TOTAL = MAX_LINES_TOTAL * 256; |
| 39 | + |
| 40 | +struct fileMon { |
| 41 | + std::ifstream file; |
| 42 | + std::string name; |
| 43 | + unsigned int nLines = 0; |
| 44 | + unsigned int nBytes = 0; |
| 45 | + |
| 46 | + fileMon(const std::string& path, const std::string& filename); |
| 47 | +}; |
| 48 | + |
| 49 | +fileMon::fileMon(const std::string& path, const std::string& filename) |
| 50 | +{ |
| 51 | + printf("Monitoring file %s\n", filename.c_str()); |
| 52 | + name = filename; |
| 53 | + file.open(path + "/" + filename, std::ifstream::in); |
| 54 | +} |
| 55 | + |
| 56 | +class EPNMonitor |
| 57 | +{ |
| 58 | + public: |
| 59 | + EPNMonitor(std::string path, bool infoLogger, int runNumber, std::string partition); |
| 60 | + ~EPNMonitor(); |
| 61 | + |
| 62 | + private: |
| 63 | + void thread(); |
| 64 | + void check_add_file(const std::string& filename); |
| 65 | + void sendLog(const std::string& file, const std::string& message); |
| 66 | + |
| 67 | + bool mInfoLoggerActive; |
| 68 | + volatile bool mTerminate = false; |
| 69 | + std::thread mThread; |
| 70 | + std::unordered_map<std::string, fileMon> mFiles; |
| 71 | + std::string mPath; |
| 72 | + std::vector<std::regex> mFilters; |
| 73 | + unsigned int mRunNUmber; |
| 74 | + std::string mPartition; |
| 75 | + unsigned int nLines = 0; |
| 76 | + unsigned int nBytes = 0; |
| 77 | + std::unique_ptr<InfoLogger::InfoLogger> mLogger; |
| 78 | + std::unique_ptr<InfoLogger::InfoLoggerContext> mLoggerContext; |
| 79 | +}; |
| 80 | + |
| 81 | +EPNMonitor::EPNMonitor(std::string path, bool infoLogger, int runNumber, std::string partition) |
| 82 | +{ |
| 83 | + mFilters.emplace_back("^Info in <"); |
| 84 | + mFilters.emplace_back("^[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}.[0-9]{6}"); |
| 85 | + mInfoLoggerActive = infoLogger; |
| 86 | + mPath = path; |
| 87 | + mRunNUmber = runNumber; |
| 88 | + mPartition = partition; |
| 89 | + if (infoLogger) { |
| 90 | + mLogger = std::make_unique<InfoLogger::InfoLogger>(); |
| 91 | + mLoggerContext = std::make_unique<InfoLogger::InfoLoggerContext>(); |
| 92 | + mLoggerContext->setField(InfoLogger::InfoLoggerContext::FieldName::Partition, partition != "" ? partition : "unspecified"); |
| 93 | + mLoggerContext->setField(InfoLogger::InfoLoggerContext::FieldName::Run, runNumber == 0 ? std::to_string(runNumber) : "unspecified"); |
| 94 | + } |
| 95 | + mThread = std::thread(&EPNMonitor::thread, this); |
| 96 | +} |
| 97 | + |
| 98 | +EPNMonitor::~EPNMonitor() |
| 99 | +{ |
| 100 | + mTerminate = true; |
| 101 | + mThread.join(); |
| 102 | +} |
| 103 | + |
| 104 | +void EPNMonitor::check_add_file(const std::string& filename) |
| 105 | +{ |
| 106 | + //printf("Checking '%s'\n", filename.c_str()); |
| 107 | + static const std::regex match_stderr("_err\\.log$"); |
| 108 | + if (std::regex_search(filename, match_stderr)) { |
| 109 | + mFiles.try_emplace(filename, mPath, filename); |
| 110 | + } |
| 111 | +} |
| 112 | + |
| 113 | +void EPNMonitor::sendLog(const std::string& file, const std::string& message) |
| 114 | +{ |
| 115 | + if (mInfoLoggerActive) { |
| 116 | + mLoggerContext->setField(InfoLogger::InfoLoggerContext::FieldName::Facility, "stderr/" + file); |
| 117 | + static const InfoLogger::InfoLogger::InfoLoggerMessageOption opt = {InfoLogger::InfoLogger::Severity::Error, 3, InfoLogger::InfoLogger::undefinedMessageOption.errorCode, InfoLogger::InfoLogger::undefinedMessageOption.sourceFile, InfoLogger::InfoLogger::undefinedMessageOption.sourceLine}; |
| 118 | + mLogger->log(opt, *mLoggerContext, "stderr: %s", message.c_str()); |
| 119 | + } else { |
| 120 | + printf("stderr: %s: %s\n", file.c_str(), message.c_str()); |
| 121 | + } |
| 122 | +} |
| 123 | + |
| 124 | +void EPNMonitor::thread() |
| 125 | +{ |
| 126 | + printf("EPN stderr Monitor active\n"); |
| 127 | + |
| 128 | + int fd; |
| 129 | + int wd; |
| 130 | + static constexpr size_t BUFFER_SIZE = 64 * 1024; |
| 131 | + std::vector<char> evt_buffer(BUFFER_SIZE); |
| 132 | + std::vector<char> text_buffer(8192); |
| 133 | + fd = inotify_init(); |
| 134 | + wd = inotify_add_watch(fd, mPath.c_str(), IN_CREATE); |
| 135 | + if (fd < 0) { |
| 136 | + throw std::runtime_error(std::string("Error initializing inotify ") + std::to_string(fd) + " " + std::to_string(wd)); |
| 137 | + } |
| 138 | + pollfd pfd = {fd, POLLIN, 0}; |
| 139 | + |
| 140 | + for (const auto& entry : std::filesystem::directory_iterator(mPath)) { |
| 141 | + if (entry.is_regular_file()) { |
| 142 | + check_add_file(entry.path().filename()); |
| 143 | + } |
| 144 | + } |
| 145 | + |
| 146 | + auto lastTime = std::chrono::system_clock::now(); |
| 147 | + while (!mTerminate) { |
| 148 | + if (poll(&pfd, 1, 50) > 0) { |
| 149 | + int l = read(fd, evt_buffer.data(), BUFFER_SIZE); |
| 150 | + if (l < 0) { |
| 151 | + throw std::runtime_error(std::string("Error waiting for inotify event ") + std::to_string(l)); |
| 152 | + } |
| 153 | + for (int i = 0; i < l; i += sizeof(inotify_event)) { |
| 154 | + inotify_event* event = (inotify_event*)&evt_buffer[i]; |
| 155 | + if (event->len && (event->mask & IN_CREATE) && !(event->mask & IN_ISDIR)) { |
| 156 | + check_add_file(event->name); |
| 157 | + } |
| 158 | + i += event->len; |
| 159 | + } |
| 160 | + } |
| 161 | + auto curTime = std::chrono::system_clock::now(); |
| 162 | + if (std::chrono::duration_cast<std::chrono::milliseconds>(curTime - lastTime).count() >= 1000) { |
| 163 | + char* ptr = text_buffer.data(); |
| 164 | + std::string line; |
| 165 | + for (auto fit = mFiles.begin(); fit != mFiles.end(); fit++) { |
| 166 | + auto& f = fit->second; |
| 167 | + auto& file = f.file; |
| 168 | + file.clear(); |
| 169 | + do { |
| 170 | + std::getline(file, line); |
| 171 | + if (line.size()) { |
| 172 | + bool filterLine = false; |
| 173 | + for (const auto& filter : mFilters) { |
| 174 | + if (std::regex_search(line, filter)) { |
| 175 | + filterLine = true; |
| 176 | + break; |
| 177 | + } |
| 178 | + } |
| 179 | + if (filterLine) { |
| 180 | + continue; |
| 181 | + } |
| 182 | + f.nLines++; |
| 183 | + f.nBytes += line.size(); |
| 184 | + nLines++; |
| 185 | + nBytes += line.size(); |
| 186 | + if (f.nLines >= MAX_LINES_FILE || f.nBytes >= MAX_BYTES_FILE) { |
| 187 | + sendLog(f.name, "Exceeded log size for process " + f.name + " (" + std::to_string(f.nLines) + " lines, " + std::to_string(f.nBytes) + " bytes), not reporting any more errors from this file..."); |
| 188 | + fit = mFiles.erase(fit); |
| 189 | + break; |
| 190 | + } |
| 191 | + if (nLines >= MAX_LINES_TOTAL || nBytes >= MAX_BYTES_TOTAL) { |
| 192 | + break; |
| 193 | + } |
| 194 | + sendLog(f.name, line); |
| 195 | + } |
| 196 | + } while (!file.eof()); |
| 197 | + } |
| 198 | + lastTime = curTime; |
| 199 | + } |
| 200 | + if (nLines >= MAX_LINES_TOTAL || nBytes >= MAX_BYTES_TOTAL) { |
| 201 | + sendLog("", "Max total stderr log size exceeded (" + std::to_string(nLines) + " lines, " + std::to_string(nBytes) + "), not sending any more stderr logs from this node..."); |
| 202 | + break; |
| 203 | + } |
| 204 | + |
| 205 | + usleep(50000); |
| 206 | + } |
| 207 | + |
| 208 | + inotify_rm_watch(fd, wd); |
| 209 | + close(fd); |
| 210 | + |
| 211 | + printf("EPN stderr Monitor terminating\n"); |
| 212 | +} |
| 213 | + |
| 214 | +static std::unique_ptr<EPNMonitor> gEPNMonitor; |
| 215 | + |
| 216 | +namespace bpo = boost::program_options; |
| 217 | + |
| 218 | +struct EPNstderrMonitor : fair::mq::Device { |
| 219 | + void InitTask() override |
| 220 | + { |
| 221 | + std::string path = "."; |
| 222 | + bool infoLogger = fConfig->GetProperty<int>("infologger"); |
| 223 | + bool dds = false; |
| 224 | + if (fConfig->Count("plugin")) { |
| 225 | + const auto& plugins = fConfig->GetProperty<std::vector<std::string>>("plugin"); |
| 226 | + bool dds = std::find(plugins.begin(), plugins.end(), "ODC") != plugins.end(); |
| 227 | + } |
| 228 | + |
| 229 | + bool runNumber = dds ? fConfig->GetProperty<int>("runNumber") : 0; |
| 230 | + std::string partition = ""; |
| 231 | + gEPNMonitor = std::make_unique<EPNMonitor>(path, infoLogger, runNumber, partition); |
| 232 | + } |
| 233 | + bool ConditionalRun() override |
| 234 | + { |
| 235 | + return true; |
| 236 | + } |
| 237 | +}; |
| 238 | + |
| 239 | +void addCustomOptions(bpo::options_description& options) |
| 240 | +{ |
| 241 | + options.add_options()("infologger", bpo::value<int>()->default_value(0), "Send via infologger"); |
| 242 | +} |
| 243 | + |
| 244 | +std::unique_ptr<fair::mq::Device> getDevice(fair::mq::ProgOptions& config) |
| 245 | +{ |
| 246 | + return std::make_unique<EPNstderrMonitor>(); |
| 247 | +} |
0 commit comments