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