Line data Source code
1 : #include <QMenu>
2 : #include <QMessageBox>
3 : #include <QProgressDialog>
4 : #include <QScrollBar>
5 : #include <QtGui>
6 :
7 : #include "cetlib/filepath_maker.h"
8 : #include "fhiclcpp/ParameterSet.h"
9 :
10 : #include "mfextensions/Binaries/mvdlg.hh"
11 :
12 : #if GCC_VERSION >= 701000 || defined(__clang__)
13 : #pragma GCC diagnostic push
14 : #pragma GCC diagnostic ignored "-Wimplicit-fallthrough"
15 : #endif
16 :
17 : #define TRACE_NAME "MessageViewer"
18 : #include "TRACE/trace.h"
19 :
20 : #if GCC_VERSION >= 701000 || defined(__clang__)
21 : #pragma GCC diagnostic pop
22 : #endif
23 :
24 : #include "mvdlg.hh"
25 :
26 : // replace the ${..} part in the filename with env variable
27 : // throw if the env does not exist
28 0 : static void process_fname(std::string& fname)
29 : {
30 0 : size_t sub_start = fname.find("${");
31 0 : size_t sub_end = fname.find("}");
32 :
33 0 : const size_t npos = std::string::npos;
34 :
35 0 : if ((sub_start == npos && sub_end != npos) || (sub_start != npos && sub_end == npos) || (sub_start > sub_end))
36 : {
37 0 : throw std::runtime_error("Unrecognized configuration file. Use default configuration instead.");
38 : }
39 :
40 0 : if (sub_start == npos) return;
41 :
42 0 : std::string env = std::string(getenv(fname.substr(sub_start + 2, sub_end - sub_start - 2).c_str()));
43 0 : fname.replace(sub_start, sub_end - sub_start + 1, env);
44 :
45 : // printf("%s\n", fname.c_str());
46 0 : }
47 :
48 0 : static fhicl::ParameterSet readConf(std::string const& fname)
49 : {
50 0 : if (fname.empty()) return fhicl::ParameterSet();
51 :
52 0 : std::string filename = fname;
53 0 : process_fname(filename);
54 :
55 0 : std::string env("FHICL_FILE_PATH=");
56 :
57 0 : if (filename[0] == '/')
58 : {
59 0 : env.append("/");
60 : }
61 : else
62 : {
63 0 : env.append(".");
64 : }
65 :
66 0 : char* mfe_path = getenv("MFEXTENSIONS_DIR");
67 0 : if (mfe_path) env.append(":").append(mfe_path).append("/config");
68 :
69 0 : env.append("\0"); // So that putenv gets a valid C string
70 :
71 0 : putenv(&env[0]);
72 :
73 : // printf("%s\n", env.c_str());
74 :
75 0 : cet::filepath_lookup policy("FHICL_FILE_PATH");
76 :
77 : // it throws when the file is not parsable
78 0 : fhicl::ParameterSet pset = fhicl::ParameterSet::make(filename, policy);
79 :
80 0 : return pset;
81 0 : }
82 :
83 0 : msgViewerDlg::msgViewerDlg(std::string const& conf, QDialog* parent)
84 0 : : QDialog(parent), paused(false), shortMode_(false), nMsgs(0), nSupMsgs(0), nThrMsgs(0), nFilters(0), nDeleted(0), simpleRender(true), searchStr(""), msg_pool_(), host_msgs_(), cat_msgs_(), app_msgs_(), sup_menu(new QMenu(this)), thr_menu(new QMenu(this)), receivers_(readConf(conf).get<fhicl::ParameterSet>("receivers", fhicl::ParameterSet()))
85 : {
86 0 : setupUi(this);
87 :
88 : // window geo settings
89 0 : readSettings();
90 :
91 : // read configuration file
92 0 : fhicl::ParameterSet pset = readConf(conf);
93 :
94 : // parse configuration file
95 0 : parseConf(pset);
96 :
97 : // associate menu with push buttons
98 0 : btnSuppression->setMenu(sup_menu);
99 0 : btnThrottling->setMenu(thr_menu);
100 :
101 : // slots
102 0 : connect(btnPause, SIGNAL(clicked()), this, SLOT(pause()));
103 0 : connect(btnScrollToBottom, SIGNAL(clicked()), this, SLOT(scrollToBottom()));
104 0 : connect(btnExit, SIGNAL(clicked()), this, SLOT(exit()));
105 0 : connect(btnClear, SIGNAL(clicked()), this, SLOT(clear()));
106 :
107 0 : connect(btnRMode, SIGNAL(clicked()), this, SLOT(renderMode()));
108 0 : connect(btnDisplayMode, SIGNAL(clicked()), this, SLOT(shortMode()));
109 :
110 0 : connect(btnSearch, SIGNAL(clicked()), this, SLOT(searchMsg()));
111 0 : connect(btnSearchClear, SIGNAL(clicked()), this, SLOT(searchClear()));
112 :
113 0 : connect(btnFilter, SIGNAL(clicked()), this, SLOT(setFilter()));
114 :
115 0 : connect(btnError, SIGNAL(clicked()), this, SLOT(setSevError()));
116 0 : connect(btnWarning, SIGNAL(clicked()), this, SLOT(setSevWarning()));
117 0 : connect(btnInfo, SIGNAL(clicked()), this, SLOT(setSevInfo()));
118 0 : connect(btnDebug, SIGNAL(clicked()), this, SLOT(setSevDebug()));
119 :
120 0 : connect(sup_menu, SIGNAL(triggered(QAction*)), this, SLOT(setSuppression(QAction*)));
121 :
122 0 : connect(thr_menu, SIGNAL(triggered(QAction*)), this, SLOT(setThrottling(QAction*)));
123 :
124 0 : connect(vsSeverity, SIGNAL(valueChanged(int)), this, SLOT(changeSeverity(int)));
125 :
126 0 : connect(&receivers_, SIGNAL(newMessage(msg_ptr_t)), this, SLOT(onNewMsg(msg_ptr_t)));
127 :
128 0 : connect(tabWidget, SIGNAL(currentChanged(int)), this, SLOT(tabWidgetCurrentChanged(int)));
129 0 : connect(tabWidget, SIGNAL(tabCloseRequested(int)), this, SLOT(tabCloseRequested(int)));
130 0 : MsgFilterDisplay allMessages;
131 0 : allMessages.txtDisplay = txtMessages;
132 0 : allMessages.nDisplayMsgs = 0;
133 0 : allMessages.filterExpression = "";
134 0 : allMessages.nDisplayedDeletedMsgs = 0;
135 0 : allMessages.sevThresh = SINFO;
136 0 : msgFilters_.push_back(allMessages);
137 :
138 : // https://stackoverflow.com/questions/2616483/close-button-only-for-some-tabs-in-qt
139 0 : auto tabBar = tabWidget->findChild<QTabBar*>();
140 0 : tabBar->setTabButton(0, QTabBar::RightSide, nullptr);
141 0 : tabBar->setTabButton(0, QTabBar::LeftSide, nullptr);
142 :
143 0 : if (simpleRender)
144 0 : btnRMode->setChecked(true);
145 : else
146 0 : btnRMode->setChecked(false);
147 :
148 0 : btnRMode->setEnabled(false);
149 :
150 0 : changeSeverity(SINFO);
151 :
152 0 : auto doc = new QTextDocument(txtMessages);
153 0 : txtMessages->setDocument(doc);
154 :
155 0 : receivers_.start();
156 0 : }
157 :
158 0 : msgViewerDlg::~msgViewerDlg()
159 : {
160 0 : receivers_.stop();
161 0 : writeSettings();
162 0 : }
163 :
164 0 : static void str_to_suppress(std::vector<std::string> const& vs, std::vector<suppress>& s, QMenu* menu)
165 : {
166 : QAction* act;
167 :
168 0 : if (vs.empty())
169 : {
170 0 : act = menu->addAction("None");
171 0 : act->setEnabled(false);
172 0 : return;
173 : }
174 :
175 0 : s.reserve(vs.size());
176 :
177 0 : for (size_t i = 0; i < vs.size(); ++i)
178 : {
179 0 : s.emplace_back(vs[i]);
180 0 : act = menu->addAction(QString(vs[i].c_str()));
181 0 : act->setCheckable(true);
182 0 : act->setChecked(true);
183 0 : QVariant v = QVariant::fromValue(static_cast<void*>(&s[i]));
184 0 : act->setData(v);
185 0 : }
186 : }
187 :
188 0 : static void pset_to_throttle(std::vector<fhicl::ParameterSet> const& ps, std::vector<throttle>& t, QMenu* menu)
189 : {
190 : QAction* act;
191 :
192 0 : if (ps.empty())
193 : {
194 0 : act = menu->addAction("None");
195 0 : act->setEnabled(false);
196 0 : return;
197 : }
198 :
199 0 : t.reserve(ps.size());
200 :
201 0 : for (size_t i = 0; i < ps.size(); ++i)
202 : {
203 0 : auto name = ps[i].get<std::string>("name");
204 0 : t.emplace_back(name, ps[i].get<int>("limit", -1), ps[i].get<int64_t>("timespan", -1));
205 0 : act = menu->addAction(QString(name.c_str()));
206 0 : act->setCheckable(true);
207 0 : act->setChecked(true);
208 0 : QVariant v = QVariant::fromValue(static_cast<void*>(&t[i]));
209 0 : act->setData(v);
210 0 : }
211 : }
212 :
213 0 : void msgViewerDlg::parseConf(fhicl::ParameterSet const& conf)
214 : {
215 0 : fhicl::ParameterSet nulp;
216 : // QAction * act;
217 :
218 : // suppression list
219 0 : auto sup = conf.get<fhicl::ParameterSet>("suppress", nulp);
220 :
221 0 : auto sup_host = sup.get<std::vector<std::string>>("hosts", std::vector<std::string>());
222 0 : auto sup_app = sup.get<std::vector<std::string>>("applications", std::vector<std::string>());
223 0 : auto sup_cat = sup.get<std::vector<std::string>>("categories", std::vector<std::string>());
224 :
225 0 : str_to_suppress(sup_host, e_sup_host, sup_menu);
226 0 : sup_menu->addSeparator();
227 :
228 0 : str_to_suppress(sup_app, e_sup_app, sup_menu);
229 0 : sup_menu->addSeparator();
230 :
231 0 : str_to_suppress(sup_cat, e_sup_cat, sup_menu);
232 :
233 : // throttling list
234 0 : auto thr = conf.get<fhicl::ParameterSet>("throttle", nulp);
235 :
236 0 : auto thr_host = thr.get<std::vector<fhicl::ParameterSet>>("hosts", std::vector<fhicl::ParameterSet>());
237 0 : auto thr_app = thr.get<std::vector<fhicl::ParameterSet>>("applications", std::vector<fhicl::ParameterSet>());
238 0 : auto thr_cat = thr.get<std::vector<fhicl::ParameterSet>>("categories", std::vector<fhicl::ParameterSet>());
239 :
240 0 : pset_to_throttle(thr_host, e_thr_host, thr_menu);
241 0 : thr_menu->addSeparator();
242 :
243 0 : pset_to_throttle(thr_app, e_thr_app, thr_menu);
244 0 : thr_menu->addSeparator();
245 :
246 0 : pset_to_throttle(thr_cat, e_thr_cat, thr_menu);
247 :
248 0 : maxMsgs = conf.get<size_t>("max_message_buffer_size", 100000);
249 0 : maxDeletedMsgs = conf.get<size_t>("max_displayed_deleted_messages", 100000);
250 0 : }
251 :
252 0 : bool msgViewerDlg::msg_throttled(msg_ptr_t const& msg)
253 : {
254 : // suppression list
255 :
256 0 : ++nSupMsgs;
257 :
258 0 : for (size_t i = 0; i < e_sup_host.size(); ++i)
259 0 : if (e_sup_host[i].match(msg->host().toStdString())) return true;
260 :
261 0 : for (size_t i = 0; i < e_sup_app.size(); ++i)
262 0 : if (e_sup_app[i].match(msg->app().toStdString())) return true;
263 :
264 0 : for (size_t i = 0; i < e_sup_cat.size(); ++i)
265 0 : if (e_sup_cat[i].match(msg->cat().toStdString())) return true;
266 :
267 0 : --nSupMsgs;
268 :
269 : // throttling
270 :
271 0 : ++nThrMsgs;
272 :
273 0 : for (size_t i = 0; i < e_thr_host.size(); ++i)
274 0 : if (e_thr_host[i].reach_limit(msg->host().toStdString(), msg->time())) return true;
275 :
276 0 : for (size_t i = 0; i < e_thr_app.size(); ++i)
277 0 : if (e_thr_app[i].reach_limit(msg->app().toStdString(), msg->time())) return true;
278 :
279 0 : for (size_t i = 0; i < e_thr_cat.size(); ++i)
280 0 : if (e_thr_cat[i].reach_limit(msg->cat().toStdString(), msg->time())) return true;
281 :
282 0 : --nThrMsgs;
283 :
284 0 : return false;
285 : }
286 :
287 0 : void msgViewerDlg::writeSettings()
288 : {
289 0 : QSettings settings("ARTDAQ", "MsgViewer");
290 :
291 0 : settings.beginGroup("MainWindow");
292 0 : settings.setValue("size", size());
293 0 : settings.setValue("pos", pos());
294 0 : settings.endGroup();
295 0 : }
296 :
297 0 : void msgViewerDlg::readSettings()
298 : {
299 0 : QSettings settings("ARTDAQ", "MsgViewer");
300 :
301 0 : settings.beginGroup("MainWindow");
302 0 : QPoint pos = settings.value("pos", QPoint(100, 100)).toPoint();
303 0 : QSize size = settings.value("size", QSize(660, 760)).toSize();
304 0 : resize(size);
305 0 : move(pos);
306 0 : settings.endGroup();
307 0 : }
308 :
309 0 : void msgViewerDlg::onNewMsg(msg_ptr_t const& msg)
310 : {
311 : // 21-Aug-2015, KAB: copying the incrementing (and displaying) of the number
312 : // of messages to here. I'm also not sure if we want to
313 : // count all messages or just non-suppressed ones or what. But, at least this
314 : // change gets the counter incrementing on the display.
315 0 : ++nMsgs;
316 0 : lcdMsgs->display(nMsgs);
317 :
318 : // test if the message is suppressed or throttled
319 0 : if (msg_throttled(msg))
320 : {
321 0 : lcdSuppressionCount->display(nSupMsgs);
322 0 : lcdThrottlingCount->display(nThrMsgs);
323 0 : return;
324 : }
325 :
326 : // push the message to the message pool
327 : {
328 : // std::lock_guard<std::mutex> lk(msg_pool_mutex_);
329 0 : msg_pool_.emplace_back(msg);
330 : }
331 0 : trim_msg_pool();
332 :
333 : // update corresponding lists of index
334 0 : update_index(msg);
335 :
336 : // Update filtered displays
337 0 : for (size_t d = 0; d < msgFilters_.size(); ++d)
338 : {
339 : bool hostMatch =
340 0 : msgFilters_[d].hostFilter.contains(msg->host(), Qt::CaseInsensitive) || msgFilters_[d].hostFilter.empty();
341 : bool appMatch =
342 0 : msgFilters_[d].appFilter.contains(msg->app(), Qt::CaseInsensitive) || msgFilters_[d].appFilter.empty();
343 : bool catMatch =
344 0 : msgFilters_[d].catFilter.contains(msg->cat(), Qt::CaseInsensitive) || msgFilters_[d].catFilter.empty();
345 :
346 : // Check to display the message
347 0 : if (hostMatch && appMatch && catMatch)
348 : {
349 : {
350 : // std::lock_guard<std::mutex> lk(filter_mutex_);
351 0 : msgFilters_[d].msgs.push_back(msg);
352 : }
353 0 : if ((int)d == tabWidget->currentIndex())
354 0 : displayMsg(msg, d);
355 : }
356 : }
357 : }
358 :
359 0 : void msgViewerDlg::trim_msg_pool()
360 : {
361 0 : bool host_list_update = false;
362 0 : bool app_list_update = false;
363 0 : bool cat_list_update = false;
364 : {
365 0 : std::lock_guard<std::mutex> lk(msg_pool_mutex_);
366 0 : while (maxMsgs > 0 && msg_pool_.size() > maxMsgs)
367 : {
368 0 : QString const& app = msg_pool_.front()->app();
369 0 : QString const& cat = msg_pool_.front()->cat();
370 0 : QString const& host = msg_pool_.front()->host();
371 :
372 : // Check if we can remove an app/host/category
373 : {
374 0 : auto catIter = std::find(cat_msgs_[cat].begin(), cat_msgs_[cat].end(), msg_pool_.front());
375 0 : auto hostIter = std::find(host_msgs_[host].begin(), host_msgs_[host].end(), msg_pool_.front());
376 0 : auto appIter = std::find(app_msgs_[app].begin(), app_msgs_[app].end(), msg_pool_.front());
377 0 : if (catIter != cat_msgs_[cat].end()) cat_msgs_[cat].erase(catIter);
378 0 : if (hostIter != host_msgs_[host].end()) host_msgs_[host].erase(hostIter);
379 0 : if (appIter != app_msgs_[app].end()) app_msgs_[app].erase(appIter);
380 :
381 0 : if (app_msgs_[app].empty())
382 : {
383 0 : app_msgs_.erase(app);
384 0 : app_list_update = true;
385 : }
386 0 : if (cat_msgs_[cat].empty())
387 : {
388 0 : cat_msgs_.erase(cat);
389 0 : cat_list_update = true;
390 : }
391 0 : if (host_msgs_[host].empty())
392 : {
393 0 : host_msgs_.erase(host);
394 0 : host_list_update = true;
395 : }
396 : }
397 :
398 : // Finally, remove the message from the pool so it doesn't appear in new filters
399 0 : msg_pool_.erase(msg_pool_.begin());
400 0 : ++nDeleted;
401 : }
402 0 : }
403 : {
404 0 : std::lock_guard<std::mutex> lk(msg_classification_mutex_);
405 0 : if (host_list_update)
406 0 : updateList(lwHost, host_msgs_);
407 0 : if (app_list_update)
408 0 : updateList(lwApplication, app_msgs_);
409 0 : if (cat_list_update)
410 0 : updateList(lwCategory, cat_msgs_);
411 0 : }
412 :
413 0 : for (size_t d = 0; d < msgFilters_.size(); ++d)
414 : {
415 : {
416 0 : std::lock_guard<std::mutex> lk(filter_mutex_);
417 0 : while (msgFilters_[d].msgs.size() > maxMsgs)
418 : {
419 0 : if ((*msgFilters_[d].msgs.begin())->sev() >= msgFilters_[d].sevThresh)
420 0 : msgFilters_[d].nDisplayedDeletedMsgs++;
421 0 : msgFilters_[d].msgs.erase(msgFilters_[d].msgs.begin());
422 : }
423 0 : }
424 :
425 0 : if ((int)d == tabWidget->currentIndex())
426 : {
427 0 : if (maxDeletedMsgs > 0 && msgFilters_[d].nDisplayedDeletedMsgs > static_cast<int>(maxDeletedMsgs))
428 : {
429 0 : displayMsgs(d);
430 : }
431 0 : lcdDisplayedDeleted->display(msgFilters_[d].nDisplayedDeletedMsgs);
432 : }
433 : }
434 :
435 0 : lcdDeletedCount->display(nDeleted);
436 0 : }
437 :
438 0 : void msgViewerDlg::update_index(msg_ptr_t const& it)
439 : {
440 0 : std::lock_guard<std::mutex> lk(msg_classification_mutex_);
441 0 : QString const& app = it->app();
442 0 : QString const& cat = it->cat();
443 0 : QString const& host = it->host();
444 :
445 0 : if (cat_msgs_.find(cat) == cat_msgs_.end())
446 : {
447 0 : cat_msgs_[cat].push_back(it);
448 0 : updateList(lwCategory, cat_msgs_);
449 : }
450 : else
451 : {
452 0 : cat_msgs_[cat].push_back(it);
453 : }
454 :
455 0 : if (host_msgs_.find(host) == host_msgs_.end())
456 : {
457 0 : host_msgs_[host].push_back(it);
458 0 : updateList(lwHost, host_msgs_);
459 : }
460 : else
461 : {
462 0 : host_msgs_[host].push_back(it);
463 : }
464 :
465 0 : if (app_msgs_.find(app) == app_msgs_.end())
466 : {
467 0 : app_msgs_[app].push_back(it);
468 0 : updateList(lwApplication, app_msgs_);
469 : }
470 : else
471 : {
472 0 : app_msgs_[app].push_back(it);
473 : }
474 0 : }
475 :
476 0 : void msgViewerDlg::displayMsg(msg_ptr_t const& it, int display)
477 : {
478 0 : if (it->sev() < msgFilters_[display].sevThresh) return;
479 :
480 0 : msgFilters_[display].nDisplayMsgs++;
481 0 : if (display == tabWidget->currentIndex())
482 : {
483 0 : lcdDisplayedMsgs->display(msgFilters_[display].nDisplayMsgs);
484 : }
485 :
486 0 : auto txt = it->text(shortMode_);
487 0 : QStringList txts;
488 0 : txts.push_back(txt);
489 0 : UpdateTextAreaDisplay(txts, msgFilters_[display].txtDisplay);
490 0 : }
491 :
492 0 : void msgViewerDlg::displayMsgs(int display)
493 : {
494 0 : msgFilters_[display].txtDisplay->clear();
495 0 : msgFilters_[display].nDisplayMsgs = 0;
496 0 : msgFilters_[display].nDisplayedDeletedMsgs = 0;
497 :
498 0 : QStringList txts;
499 : {
500 0 : std::lock_guard<std::mutex> lk(filter_mutex_);
501 0 : for (auto it = msgFilters_[display].msgs.begin(); it != msgFilters_[display].msgs.end(); ++it)
502 : {
503 0 : if ((*it)->sev() >= msgFilters_[display].sevThresh)
504 : {
505 0 : txts.push_back((*it)->text(shortMode_));
506 0 : ++msgFilters_[display].nDisplayMsgs;
507 : }
508 : }
509 0 : }
510 0 : if (display == tabWidget->currentIndex())
511 : {
512 0 : lcdDisplayedMsgs->display(msgFilters_[display].nDisplayMsgs);
513 : }
514 0 : UpdateTextAreaDisplay(txts, msgFilters_[display].txtDisplay);
515 0 : }
516 :
517 : // https://stackoverflow.com/questions/13559990/how-to-append-text-to-qplaintextedit-without-adding-newline-and-keep-scroll-at
518 0 : void msgViewerDlg::UpdateTextAreaDisplay(QStringList const& texts, QPlainTextEdit* widget)
519 : {
520 0 : const QTextCursor old_cursor = widget->textCursor();
521 0 : const int old_scrollbar_value = widget->verticalScrollBar()->value();
522 : const bool is_scrolled_down =
523 0 : old_scrollbar_value >= widget->verticalScrollBar()->maximum() * 0.95; // At least 95% scrolled down
524 :
525 0 : if (!paused && !is_scrolled_down)
526 : {
527 0 : pause();
528 : }
529 :
530 0 : QTextCursor new_cursor = QTextCursor(widget->document());
531 :
532 0 : new_cursor.beginEditBlock();
533 0 : new_cursor.movePosition(QTextCursor::End);
534 :
535 0 : for (int i = 0; i < texts.size(); i++)
536 : {
537 0 : new_cursor.insertBlock();
538 0 : new_cursor.insertHtml(texts.at(i));
539 0 : if (!shortMode_) new_cursor.insertBlock();
540 : }
541 0 : new_cursor.endEditBlock();
542 :
543 0 : if (old_cursor.hasSelection() || paused)
544 : {
545 : // The user has selected text or scrolled away from the bottom: maintain position.
546 0 : widget->setTextCursor(old_cursor);
547 0 : widget->verticalScrollBar()->setValue(old_scrollbar_value);
548 : }
549 : else
550 : {
551 : // The user hasn't selected any text and the scrollbar is at the bottom: scroll to the bottom.
552 0 : widget->moveCursor(QTextCursor::End);
553 0 : widget->verticalScrollBar()->setValue(widget->verticalScrollBar()->maximum());
554 0 : widget->horizontalScrollBar()->setValue(0);
555 : }
556 0 : }
557 :
558 0 : void msgViewerDlg::scrollToBottom()
559 : {
560 0 : int display = tabWidget->currentIndex();
561 0 : msgFilters_[display].txtDisplay->moveCursor(QTextCursor::End);
562 0 : msgFilters_[display].txtDisplay->verticalScrollBar()->setValue(
563 0 : msgFilters_[display].txtDisplay->verticalScrollBar()->maximum());
564 0 : msgFilters_[display].txtDisplay->horizontalScrollBar()->setValue(0);
565 0 : }
566 :
567 0 : void msgViewerDlg::updateDisplays()
568 : {
569 0 : for (size_t ii = 0; ii < msgFilters_.size(); ++ii)
570 : {
571 0 : displayMsgs(ii);
572 : }
573 0 : }
574 :
575 0 : bool msgViewerDlg::updateList(QListWidget* lw, msgs_map_t const& map)
576 : {
577 0 : bool nonSelectedBefore = (lw->currentRow() == -1);
578 0 : bool nonSelectedAfter = true;
579 :
580 0 : QString item = nonSelectedBefore ? "" : lw->currentItem()->text();
581 :
582 0 : lw->clear();
583 0 : int row = 0;
584 0 : auto it = map.begin();
585 :
586 0 : while (it != map.end())
587 : {
588 0 : lw->addItem(it->first);
589 0 : if (!nonSelectedBefore && nonSelectedAfter)
590 : {
591 0 : if (item == it->first)
592 : {
593 0 : lw->setCurrentRow(row);
594 0 : nonSelectedAfter = false;
595 : }
596 : }
597 0 : ++row;
598 0 : ++it;
599 : }
600 :
601 0 : if (!nonSelectedBefore && nonSelectedAfter) return true;
602 :
603 0 : return false;
604 0 : }
605 :
606 0 : msgs_t msgViewerDlg::list_intersect(msgs_t const& l1, msgs_t const& l2)
607 : {
608 0 : msgs_t output;
609 0 : auto it1 = l1.begin();
610 0 : auto it2 = l2.begin();
611 :
612 0 : while (it1 != l1.end() && it2 != l2.end())
613 : {
614 0 : if (*it1 < *it2)
615 : {
616 0 : ++it1;
617 : }
618 0 : else if (*it2 < *it1)
619 : {
620 0 : ++it2;
621 : }
622 : else
623 : {
624 0 : output.push_back(*it1);
625 0 : ++it1;
626 0 : ++it2;
627 : }
628 : }
629 :
630 0 : TLOG(TLVL_DEBUG + 35) << "list_intersect: output list has " << output.size() << " entries";
631 0 : return output;
632 0 : }
633 :
634 0 : std::string sev_to_string(sev_code_t s)
635 : {
636 0 : switch (s)
637 : {
638 0 : case SDEBUG:
639 0 : return "DEBUG";
640 0 : case SINFO:
641 0 : return "INFO";
642 0 : case SWARNING:
643 0 : return "WARNING";
644 0 : case SERROR:
645 0 : return "ERROR";
646 : }
647 0 : return "UNKNOWN";
648 : }
649 :
650 0 : void msgViewerDlg::setFilter()
651 : {
652 0 : auto hostFilter = toQStringList(lwHost->selectedItems());
653 0 : auto appFilter = toQStringList(lwApplication->selectedItems());
654 0 : auto catFilter = toQStringList(lwCategory->selectedItems());
655 :
656 0 : lwHost->setCurrentRow(-1, QItemSelectionModel::Clear);
657 0 : lwApplication->setCurrentRow(-1, QItemSelectionModel::Clear);
658 0 : lwCategory->setCurrentRow(-1, QItemSelectionModel::Clear);
659 :
660 0 : if (hostFilter.isEmpty() && appFilter.isEmpty() && catFilter.isEmpty())
661 : {
662 0 : return;
663 : }
664 :
665 0 : msgs_t result;
666 0 : QString catFilterExpression = "";
667 0 : QString hostFilterExpression = "";
668 0 : QString appFilterExpression = "";
669 0 : bool first = true;
670 : {
671 0 : for (auto app = 0; app < appFilter.size(); ++app)
672 : { // app-sev index
673 0 : appFilterExpression += QString(first ? "" : " || ") + appFilter[app];
674 0 : first = false;
675 : }
676 0 : TLOG(TLVL_DEBUG + 35) << "setFilter: result contains " << result.size() << " messages";
677 :
678 0 : first = true;
679 0 : if (!hostFilter.isEmpty())
680 : {
681 0 : msgs_t hostResult;
682 0 : for (auto host = 0; host < hostFilter.size(); ++host)
683 : { // host index
684 0 : hostFilterExpression += QString(first ? "" : " || ") + hostFilter[host];
685 0 : first = false;
686 : }
687 0 : }
688 :
689 0 : first = true;
690 0 : if (!catFilter.isEmpty())
691 : {
692 0 : msgs_t catResult;
693 0 : for (auto cat = 0; cat < catFilter.size(); ++cat)
694 : { // cat index
695 0 : catFilterExpression += QString(first ? "" : " || ") + catFilter[cat];
696 0 : first = false;
697 : }
698 0 : }
699 : }
700 : // Create the filter expression
701 : auto nFilterExpressions =
702 0 : (appFilterExpression != "" ? 1 : 0) + (hostFilterExpression != "" ? 1 : 0) + (catFilterExpression != "" ? 1 : 0);
703 0 : QString filterExpression = "";
704 0 : if (nFilterExpressions == 1)
705 : {
706 0 : filterExpression = catFilterExpression + hostFilterExpression + appFilterExpression;
707 : }
708 : else
709 : {
710 0 : filterExpression = "(" + (catFilterExpression != "" ? catFilterExpression + ") && (" : "") + hostFilterExpression +
711 0 : (hostFilterExpression != "" && appFilterExpression != "" ? ") && (" : "") + appFilterExpression +
712 0 : ")";
713 : }
714 :
715 0 : for (size_t d = 0; d < msgFilters_.size(); ++d)
716 : {
717 0 : if (msgFilters_[d].filterExpression == filterExpression)
718 : {
719 0 : tabWidget->setCurrentIndex(d);
720 0 : return;
721 : }
722 : }
723 :
724 : {
725 0 : std::lock_guard<std::mutex> lk(msg_classification_mutex_);
726 0 : for (auto app = 0; app < appFilter.size(); ++app)
727 : { // app-sev index
728 0 : auto it = app_msgs_.find(appFilter[app]);
729 0 : if (it != app_msgs_.end())
730 : {
731 0 : msgs_t temp(it->second);
732 0 : TLOG(TLVL_DEBUG + 35) << "setFilter: app " << appFilter[app].toStdString() << " has " << temp.size() << " messages";
733 0 : result.merge(temp);
734 0 : }
735 : }
736 0 : TLOG(TLVL_DEBUG + 35) << "setFilter: result contains " << result.size() << " messages";
737 :
738 0 : if (!hostFilter.isEmpty())
739 : {
740 0 : msgs_t hostResult;
741 0 : for (auto host = 0; host < hostFilter.size(); ++host)
742 : { // host index
743 0 : auto it = host_msgs_.find(hostFilter[host]);
744 0 : if (it != host_msgs_.end())
745 : {
746 0 : msgs_t temp(it->second);
747 0 : TLOG(TLVL_DEBUG + 35) << "setFilter: host " << hostFilter[host].toStdString() << " has " << temp.size() << " messages";
748 0 : hostResult.merge(temp);
749 0 : }
750 : }
751 0 : if (result.empty())
752 : {
753 0 : result = hostResult;
754 : }
755 : else
756 : {
757 0 : result = list_intersect(result, hostResult);
758 : }
759 0 : TLOG(TLVL_DEBUG + 35) << "setFilter: result contains " << result.size() << " messages";
760 0 : }
761 :
762 0 : if (!catFilter.isEmpty())
763 : {
764 0 : msgs_t catResult;
765 0 : for (auto cat = 0; cat < catFilter.size(); ++cat)
766 : { // cat index
767 0 : auto it = cat_msgs_.find(catFilter[cat]);
768 0 : if (it != cat_msgs_.end())
769 : {
770 0 : msgs_t temp(it->second);
771 0 : TLOG(TLVL_DEBUG + 35) << "setFilter: cat " << catFilter[cat].toStdString() << " has " << temp.size() << " messages";
772 0 : catResult.merge(temp);
773 0 : }
774 : }
775 0 : if (result.empty())
776 : {
777 0 : result = catResult;
778 : }
779 : else
780 : {
781 0 : result = list_intersect(result, catResult);
782 : }
783 0 : TLOG(TLVL_DEBUG + 35) << "setFilter: result contains " << result.size() << " messages";
784 0 : }
785 0 : }
786 :
787 : // Add the tab and populate it
788 :
789 0 : auto newTabTitle = QString("Filter ") + QString::number(++nFilters);
790 0 : auto newTab = new QWidget();
791 :
792 0 : auto txtDisplay = new QPlainTextEdit(newTab);
793 0 : auto doc = new QTextDocument(txtDisplay);
794 0 : txtDisplay->setDocument(doc);
795 :
796 0 : auto layout = new QVBoxLayout();
797 0 : layout->addWidget(txtDisplay);
798 0 : layout->setContentsMargins(0, 0, 0, 0);
799 0 : newTab->setLayout(layout);
800 :
801 0 : MsgFilterDisplay filteredMessages;
802 0 : filteredMessages.msgs = result;
803 0 : filteredMessages.hostFilter = hostFilter;
804 0 : filteredMessages.appFilter = appFilter;
805 0 : filteredMessages.catFilter = catFilter;
806 0 : filteredMessages.filterExpression = filterExpression;
807 0 : filteredMessages.txtDisplay = txtDisplay;
808 0 : filteredMessages.nDisplayMsgs = result.size();
809 0 : filteredMessages.nDisplayedDeletedMsgs = 0;
810 0 : filteredMessages.sevThresh = SINFO;
811 : {
812 0 : std::lock_guard<std::mutex> lk(filter_mutex_);
813 0 : msgFilters_.push_back(filteredMessages);
814 0 : }
815 0 : tabWidget->addTab(newTab, newTabTitle);
816 0 : tabWidget->setTabToolTip(tabWidget->count() - 1, filterExpression);
817 0 : tabWidget->setCurrentIndex(tabWidget->count() - 1);
818 :
819 0 : displayMsgs(msgFilters_.size() - 1);
820 0 : }
821 :
822 0 : void msgViewerDlg::pause()
823 : {
824 0 : if (!paused)
825 : {
826 0 : paused = true;
827 0 : btnPause->setText("Resume Scrolling");
828 : // QMessageBox::about(this, "About MsgViewer", "Message receiving paused ...");
829 : }
830 : else
831 : {
832 0 : paused = false;
833 0 : btnPause->setText("Pause Scrolling");
834 0 : scrollToBottom();
835 : }
836 0 : }
837 :
838 0 : void msgViewerDlg::exit() { close(); }
839 :
840 0 : void msgViewerDlg::clear()
841 : {
842 : int ret =
843 0 : QMessageBox::question(this, tr("Message Viewer"), tr("Are you sure you want to clear all received messages?"),
844 : QMessageBox::Yes | QMessageBox::No, QMessageBox::No);
845 0 : switch (ret)
846 : {
847 0 : case QMessageBox::Yes:
848 0 : nMsgs = 0;
849 0 : nSupMsgs = 0;
850 0 : nThrMsgs = 0;
851 0 : nDeleted = 0;
852 : {
853 0 : std::lock_guard<std::mutex> lk(msg_pool_mutex_);
854 0 : msg_pool_.clear();
855 0 : }
856 : {
857 0 : std::lock_guard<std::mutex> lk(msg_classification_mutex_);
858 0 : host_msgs_.clear();
859 0 : cat_msgs_.clear();
860 0 : app_msgs_.clear();
861 0 : updateList(lwApplication, app_msgs_);
862 0 : updateList(lwCategory, cat_msgs_);
863 0 : updateList(lwHost, host_msgs_);
864 0 : }
865 0 : for (auto& display : msgFilters_)
866 : {
867 0 : std::lock_guard<std::mutex> lk(filter_mutex_);
868 0 : display.txtDisplay->clear();
869 0 : display.msgs.clear();
870 0 : display.nDisplayMsgs = 0;
871 0 : display.nDisplayedDeletedMsgs = 0;
872 0 : }
873 :
874 0 : lcdMsgs->display(nMsgs);
875 0 : lcdDisplayedMsgs->display(0);
876 0 : break;
877 0 : case QMessageBox::No:
878 : default:
879 0 : break;
880 : }
881 0 : }
882 :
883 0 : void msgViewerDlg::shortMode()
884 : {
885 0 : if (!shortMode_)
886 : {
887 0 : shortMode_ = true;
888 0 : btnDisplayMode->setText("Long View");
889 : }
890 : else
891 : {
892 0 : shortMode_ = false;
893 0 : btnDisplayMode->setText("Compact View");
894 : }
895 0 : updateDisplays();
896 0 : }
897 :
898 0 : void msgViewerDlg::changeSeverity(int sev)
899 : {
900 0 : auto display = tabWidget->currentIndex();
901 0 : switch (sev)
902 : {
903 0 : case SERROR:
904 0 : setSevError();
905 0 : break;
906 :
907 0 : case SWARNING:
908 0 : setSevWarning();
909 0 : break;
910 :
911 0 : case SINFO:
912 0 : setSevInfo();
913 0 : break;
914 :
915 0 : default:
916 0 : setSevDebug();
917 : }
918 :
919 0 : displayMsgs(display);
920 0 : }
921 :
922 0 : void msgViewerDlg::setSevError()
923 : {
924 0 : auto display = tabWidget->currentIndex();
925 0 : msgFilters_[display].sevThresh = SERROR;
926 0 : btnError->setChecked(true);
927 0 : btnWarning->setChecked(false);
928 0 : btnInfo->setChecked(false);
929 0 : btnDebug->setChecked(false);
930 0 : vsSeverity->setValue(SERROR);
931 0 : }
932 :
933 0 : void msgViewerDlg::setSevWarning()
934 : {
935 0 : auto display = tabWidget->currentIndex();
936 0 : msgFilters_[display].sevThresh = SWARNING;
937 0 : btnError->setChecked(false);
938 0 : btnWarning->setChecked(true);
939 0 : btnInfo->setChecked(false);
940 0 : btnDebug->setChecked(false);
941 0 : vsSeverity->setValue(SWARNING);
942 0 : }
943 :
944 0 : void msgViewerDlg::setSevInfo()
945 : {
946 0 : auto display = tabWidget->currentIndex();
947 0 : msgFilters_[display].sevThresh = SINFO;
948 0 : btnError->setChecked(false);
949 0 : btnWarning->setChecked(false);
950 0 : btnInfo->setChecked(true);
951 0 : btnDebug->setChecked(false);
952 0 : vsSeverity->setValue(SINFO);
953 0 : }
954 :
955 0 : void msgViewerDlg::setSevDebug()
956 : {
957 0 : auto display = tabWidget->currentIndex();
958 0 : msgFilters_[display].sevThresh = SDEBUG;
959 0 : btnError->setChecked(false);
960 0 : btnWarning->setChecked(false);
961 0 : btnInfo->setChecked(false);
962 0 : btnDebug->setChecked(true);
963 0 : vsSeverity->setValue(SDEBUG);
964 0 : }
965 :
966 0 : void msgViewerDlg::renderMode()
967 : {
968 0 : simpleRender = !simpleRender;
969 :
970 0 : if (simpleRender)
971 : {
972 0 : btnRMode->setChecked(true);
973 0 : for (auto display : msgFilters_)
974 : {
975 0 : display.txtDisplay->setPlainText(display.txtDisplay->toPlainText());
976 0 : }
977 : }
978 : else
979 : {
980 0 : btnRMode->setChecked(false);
981 0 : updateDisplays();
982 : }
983 0 : }
984 :
985 0 : void msgViewerDlg::searchMsg()
986 : {
987 0 : QString search = editSearch->text();
988 :
989 0 : if (search.isEmpty()) return;
990 :
991 0 : auto display = tabWidget->currentIndex();
992 0 : if (search != searchStr)
993 : {
994 0 : msgFilters_[display].txtDisplay->moveCursor(QTextCursor::Start);
995 0 : if (!msgFilters_[display].txtDisplay->find(search))
996 : {
997 0 : msgFilters_[display].txtDisplay->moveCursor(QTextCursor::End);
998 0 : searchStr = "";
999 : }
1000 : else
1001 0 : searchStr = search;
1002 : }
1003 : else
1004 : {
1005 0 : if (!msgFilters_[display].txtDisplay->find(search))
1006 : {
1007 0 : msgFilters_[display].txtDisplay->moveCursor(QTextCursor::Start);
1008 0 : if (!msgFilters_[display].txtDisplay->find(search))
1009 : {
1010 0 : msgFilters_[display].txtDisplay->moveCursor(QTextCursor::End);
1011 0 : searchStr = "";
1012 : }
1013 : }
1014 : }
1015 0 : }
1016 :
1017 0 : void msgViewerDlg::searchClear()
1018 : {
1019 0 : auto display = tabWidget->currentIndex();
1020 0 : editSearch->setText("");
1021 0 : searchStr = "";
1022 0 : msgFilters_[display].txtDisplay->find("");
1023 0 : msgFilters_[display].txtDisplay->moveCursor(QTextCursor::End);
1024 0 : }
1025 :
1026 0 : void msgViewerDlg::setSuppression(QAction* act)
1027 : {
1028 0 : bool status = act->isChecked();
1029 0 : auto sup = static_cast<suppress*>(act->data().value<void*>());
1030 0 : sup->use(status);
1031 0 : }
1032 :
1033 0 : void msgViewerDlg::setThrottling(QAction* act)
1034 : {
1035 0 : bool status = act->isChecked();
1036 0 : auto thr = static_cast<throttle*>(act->data().value<void*>());
1037 0 : thr->use(status);
1038 0 : }
1039 :
1040 0 : void msgViewerDlg::tabWidgetCurrentChanged(int newTab)
1041 : {
1042 0 : displayMsgs(newTab);
1043 0 : lcdDisplayedMsgs->display(msgFilters_[newTab].nDisplayMsgs);
1044 :
1045 0 : lwHost->setCurrentRow(-1, QItemSelectionModel::Clear);
1046 0 : lwApplication->setCurrentRow(-1, QItemSelectionModel::Clear);
1047 0 : lwCategory->setCurrentRow(-1, QItemSelectionModel::Clear);
1048 :
1049 0 : for (auto const& host : msgFilters_[newTab].hostFilter)
1050 : {
1051 0 : auto items = lwHost->findItems(host, Qt::MatchExactly);
1052 0 : if (!items.empty())
1053 : {
1054 0 : items[0]->setSelected(true);
1055 : }
1056 0 : }
1057 0 : for (auto const& app : msgFilters_[newTab].appFilter)
1058 : {
1059 0 : auto items = lwApplication->findItems(app, Qt::MatchExactly);
1060 0 : if (!items.empty())
1061 : {
1062 0 : items[0]->setSelected(true);
1063 : }
1064 0 : }
1065 0 : for (auto const& cat : msgFilters_[newTab].catFilter)
1066 : {
1067 0 : auto items = lwCategory->findItems(cat, Qt::MatchExactly);
1068 0 : if (!items.empty())
1069 : {
1070 0 : items[0]->setSelected(true);
1071 : }
1072 0 : }
1073 :
1074 0 : switch (msgFilters_[newTab].sevThresh)
1075 : {
1076 0 : case SDEBUG:
1077 0 : setSevDebug();
1078 0 : break;
1079 0 : case SINFO:
1080 0 : setSevInfo();
1081 0 : break;
1082 0 : case SWARNING:
1083 0 : setSevWarning();
1084 0 : break;
1085 0 : default:
1086 0 : setSevError();
1087 0 : break;
1088 : }
1089 0 : }
1090 :
1091 0 : void msgViewerDlg::tabCloseRequested(int tabIndex)
1092 : {
1093 0 : if (tabIndex == 0 || static_cast<size_t>(tabIndex) >= msgFilters_.size()) return;
1094 :
1095 0 : auto widget = tabWidget->widget(tabIndex);
1096 0 : tabWidget->removeTab(tabIndex);
1097 0 : delete widget;
1098 :
1099 0 : msgFilters_.erase(msgFilters_.begin() + tabIndex);
1100 : }
1101 :
1102 0 : void msgViewerDlg::closeEvent(QCloseEvent* event) { event->accept(); }
1103 :
1104 0 : QStringList msgViewerDlg::toQStringList(QList<QListWidgetItem*> in)
1105 : {
1106 0 : QStringList out;
1107 :
1108 0 : for (auto i = 0; i < in.size(); ++i)
1109 : {
1110 0 : out << in[i]->text();
1111 : }
1112 :
1113 0 : return out;
1114 0 : }
|