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