Skip to content

Commit 157011e

Browse files
committed
EPN: stderr monitoring tool, forwards relevant log messages to InfoLogger
1 parent 4ad0523 commit 157011e

3 files changed

Lines changed: 265 additions & 0 deletions

File tree

Utilities/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,4 @@ add_subdirectory(Mergers)
1515
add_subdirectory(PCG)
1616
add_subdirectory(rANS)
1717
add_subdirectory(Tools)
18+
add_subdirectory(EPNMonitoring)
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
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+
if (NOT CMAKE_SYSTEM_NAME STREQUAL "Darwin")
13+
o2_add_executable(epn-stderr-monitor
14+
COMPONENT_NAME epn
15+
SOURCES src/EPNstderrMonitor.cxx
16+
PUBLIC_LINK_LIBRARIES FairMQ::FairMQ AliceO2::InfoLogger)
17+
endif()
Lines changed: 247 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,247 @@
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

Comments
 (0)