SystemStateMonitorWidget.cpp
Go to the documentation of this file.
1/*
2 * This file is part of ArmarX.
3 *
4 * Copyright (C) 2011-2016, High Performance Humanoid Technologies (H2T), Karlsruhe Institute of Technology (KIT), all rights reserved.
5 *
6 * ArmarX is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License version 2 as
8 * published by the Free Software Foundation.
9 *
10 * ArmarX is distributed in the hope that it will be useful, but
11 * WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 *
18 * @package ArmarX::Gui
19 * @author Jan Issac ( jan.issac at gmail dot com)
20 * @date 2012
21 * @copyright http://www.gnu.org/licenses/gpl-2.0.txt
22 * GNU General Public License
23 */
24
26
28
29#include <string>
30#include <vector>
31
32#include <QApplication>
33#include <QLineEdit>
34#include <QStyledItemDelegate>
35#include <QVector>
36
40#include <ArmarXCore/interface/core/ArmarXManagerInterface.h>
41
42#include <ArmarXGui/gui-plugins/SystemStateMonitorPlugin/ui_ArmarXManagerRepositoryDialog.h>
45
46namespace armarx
47{
48 class PropertyEditingDelegate : public QStyledItemDelegate
49 {
50 public:
52 std::mutex* managerPrxMapMutex,
53 QObject* parent = nullptr) :
54 QStyledItemDelegate(parent),
55 managerPrxMap(managerPrxMap),
56 managerPrxMapMutex(managerPrxMapMutex)
57 {
58 }
59
60 // QAbstractItemDelegate interface
61 QWidget*
62 createEditor(QWidget* parent,
63 const QStyleOptionViewItem& option,
64 const QModelIndex& index) const override
65 {
66 return new QLineEdit(parent);
67 }
68
69 void
70 setEditorData(QWidget* editor, const QModelIndex& index) const override
71 {
72 QLineEdit* lineEdit = qobject_cast<QLineEdit*>(editor);
73 if (!lineEdit)
74 {
75 return;
76 }
77
78 const QString cellText = index.data().toString();
79 const int colonIndex = cellText.indexOf(":");
80 if (colonIndex < 0)
81 {
82 // Not a "name : value" property row - editing it would truncate(-2)
83 // and later push a corrupted property. Leave propertyName unset so
84 // setModelData() below bails out.
85 lineEdit->setText(cellText);
86 return;
87 }
88
89 QString propValue = cellText;
90 propValue.remove(0, colonIndex + 2);
91 QString propName = cellText;
92 propName.truncate(colonIndex - 1);
93 lineEdit->setText(propValue);
94 lineEdit->setProperty("propertyName", propName);
95 }
96
97 void
98 setModelData(QWidget* editor,
99 QAbstractItemModel* model,
100 const QModelIndex& index) const override
101 {
102 QLineEdit* lineEdit = qobject_cast<QLineEdit*>(editor);
103 if (!lineEdit)
104 {
105 return;
106 }
107
108 const QString propName = lineEdit->property("propertyName").toString();
109 if (propName.isEmpty())
110 {
111 // setEditorData() found no "name : value" row; do not push anything.
112 return;
113 }
114
115 model->setData(index, propName + " : " + lineEdit->text());
116
117 armarx::ArmarXManagerInterfacePrx prx;
118 {
119 // Read the shared proxy map under its mutex (it is written from the
120 // component-connect thread).
121 std::unique_lock lock(*managerPrxMapMutex);
122 auto it =
123 managerPrxMap->find(index.parent().parent().parent().data().toString());
124 if (it != managerPrxMap->end())
125 {
126 prx = it->second;
127 }
128 }
129
130 if (!prx)
131 {
132 return;
133 }
134
135 try
136 {
137 prx->getPropertiesAdmin()
138 ->ice_collocationOptimized(false)
139 ->ice_timeout(1000)
140 ->setProperties({{propName.toStdString(), lineEdit->text().toStdString()}});
141 }
142 catch (...)
143 {
144 // Never let an Ice exception escape into the Qt event loop (std::terminate).
145 ARMARX_WARNING << "Failed to set property " << propName.toStdString() << ": "
147 }
148 }
149
150 private:
151 armarx::ManagerPrxMap* managerPrxMap;
152 std::mutex* managerPrxMapMutex;
153 };
154
156 {
157 setupModel();
158 setupView();
159 qRegisterMetaType<StateUpdateMap>("StateUpdateMap");
160 qRegisterMetaType<ArmarXManagerItem::ManagerDataMap>("ArmarXManagerItem::ManagerDataMap");
161 qRegisterMetaType<ArmarXManagerItem::ManagerData>("ArmarXManagerItem::ManagerData");
162 qRegisterMetaType<ManagerPrxMap>("ManagerPrxMap");
163
164
165 filterExpansionTimer.setSingleShot(true);
166 connect(&filterExpansionTimer, SIGNAL(timeout()), this, SLOT(delayedFilterExpansion()));
167 connect(ui.btnProblematicOnly,
168 SIGNAL(toggled(bool)),
169 this,
170 SLOT(on_btnProblematicOnly_toggled(bool)),
171 Qt::UniqueConnection);
172
173 fillLegendLayout(ui.colorLegendLayout, *monitoredManagerModel);
174 }
175
177 {
178 // stateUpdateTimer->stop();
179 if (stateUpdateTask)
180 {
181 stateUpdateTask->stop();
182 }
183
184 delete managerRepositoryModel;
185 delete monitoredManagerModel;
186 // delete stateUpdateTimer;
187 }
188
189 void
191 {
192 managerRepositoryModel = new ArmarXManagerModel();
193 monitoredManagerModel = new ArmarXManagerModel();
194 }
195
196 void
198 {
199 ui.setupUi(getWidget());
200
201 managerRepositoryDialog = new ArmarXManagerRepositoryDialog(
202 managerRepositoryModel, monitoredManagerModel, getWidget());
203 managerRepositoryDialog->setModal(true);
204 connect(managerRepositoryDialog, SIGNAL(accepted()), this, SLOT(acceptConfig()));
205 connect(managerRepositoryDialog,
206 SIGNAL(requestedManagerScan()),
207 this,
208 SLOT(retrieveOnlineManagers()));
209 // Parent the delegate to the tree view: setItemDelegate() does not take
210 // ownership, so an unparented delegate would leak.
211 ui.monitoredManagersTree->setItemDelegate(new PropertyEditingDelegate(
212 &currentManagerPrxMap, &managerPrxMapMutex, ui.monitoredManagersTree));
213 filterModel = new InfixFilterModel(this);
214 filterModel->addCustomFilter(
215 [this](QAbstractItemModel* model, int source_row, const QModelIndex& source_parent)
216 {
217 QModelIndex index0 = model->index(source_row, 0, source_parent);
218 if (this->ui.btnProblematicOnly->isChecked())
219 {
220 auto variant =
222 auto variantDependency =
224 if (variant.isValid())
225 {
226 // ARMARX_INFO << "Checking entry: " << index0.data().toString() << " val: " << variant.toBool() << " param: " << hideResolvedComponents;
227 if (variant.toBool())
228 {
229 // ARMARX_INFO << "Resolve: Hiding " << index0.data().toString();
230 return false;
231 }
232 }
233 if (variantDependency.isValid())
234 {
235 if (variantDependency.toBool())
236 {
237 return false;
238 }
239 }
240 if (!variant.isValid() && !variantDependency.isValid())
241 {
242 return false;
243 }
244 // ARMARX_INFO << "Showing entry: " << index0.data().toString() << " state: " <<
245 // (variantDependency.isValid() ? std::to_string(variantDependency.toBool()) : "none")
246 // << " - " << (variant.isValid() ? std::to_string(variant.toBool()) : "none");
247 }
248
249 return true;
250 });
251 filterModel->setSourceModel(monitoredManagerModel);
252 filterModel->setFilterCaseSensitivity(Qt::CaseInsensitive);
253 ui.monitoredManagersTree->setModel(filterModel);
254
255 connect(ui.lineEditFilter,
256 SIGNAL(textChanged(QString)),
257 filterModel,
258 SLOT(setFilterFixedString(QString)));
259 connect(ui.lineEditFilter,
260 SIGNAL(textChanged(QString)),
261 this,
262 SLOT(expandFilterSelection(QString)),
263 Qt::QueuedConnection);
264
265 connect(ui.configureButton, SIGNAL(clicked()), this, SLOT(openManagerRepositoryDialog()));
266
267 connect(this,
269 this,
271
272 // stateUpdateTimer->start();
273 }
274
275 void
277 {
278 managerRepositoryModel->populate(settings->value("ManagerRepository").toStringList());
279
280 monitoredManagerModel->populate(settings->value("MonitoredManagers").toStringList());
281 ui.monitoredManagersTree->expandToDepth(1);
282 }
283
284 void
286 {
287 settings->setValue("ManagerRepository", managerRepositoryModel->toStringList());
288
289 settings->setValue("MonitoredManagers", monitoredManagerModel->toStringList());
290 }
291
292 void
294 {
295 monitoredManagerModel->setIceManager(getIceManager());
296 managerRepositoryModel->setIceManager(getIceManager());
297 }
298
299 void
301 {
302 stateUpdateTask = new PeriodicTask<SystemStateMonitorWidget>(
303 this,
305 500,
306 false,
307 "SystemStateMonitorUpdate");
308 stateUpdateTask->setDelayWarningTolerance(5000);
309 stateUpdateTask->start();
310 if (monitoredManagerModel->empty()) // only if fresh connect without preconfig
311 {
312 prefillView();
313 }
314 else
315 {
316 // Restore proxies from a saved configuration. getManagerProxyMap() traverses
317 // (and writes back into) the QStandardItemModel, so it must run on the GUI
318 // thread; this slot runs on the component-connect thread. Marshal it there
319 // (issue #64.8). Target `this` (the object the lambda dereferences), not
320 // getWidget(), so Qt's queued-event lifetime guard protects it.
322 this,
323 [this]()
324 {
325 std::unique_lock lock(managerPrxMapMutex);
326 currentManagerPrxMap = monitoredManagerModel->getManagerProxyMap();
327 });
328 }
329 }
330
331 void
333 {
334 if (stateUpdateTask)
335 {
336 stateUpdateTask->stop();
337 }
338 }
339
340 void
342 {
343 if (stateUpdateTask)
344 {
345 stateUpdateTask->stop();
346 }
347 }
348
349 void
351 const ArmarXManagerItem::ManagerDataMap& managerData)
352 {
354
355 std::unique_lock lock(monitoredManagerModel->getMutex());
356
357 monitoredManagerModel->updateManagerDetails(managerData);
358 filterModel->invalidate();
359 }
360
361 bool
364 {
366 // Start true so the &= below actually reflects retrieveManagerObjectsState().
367 // (Previously initialised to false, which made this function always return false
368 // and reported managers as online even when their ice_ping failed - issue #64.2.)
369 bool result = true;
370 try
371 {
372 managerData.proxy = prx;
373 prx = prx->ice_timeout(1000);
374 managerData.name = QString::fromStdString(prx->ice_getIdentity().name);
375 managerData.appProperties = prx->getApplicationPropertyInfos();
376 result &= retrieveManagerObjectsState(prx, managerData.objects);
377 try
378 {
379 managerData.connection = prx->ice_collocationOptimized(false)->ice_getConnection();
380 managerData.endpointStr =
381 managerData.connection
382 ? QString::fromStdString(managerData.connection->getEndpoint()->toString())
383 : "Endpoint: no connection data";
384 }
385
386 catch (const IceUtil::Exception& ex)
387 {
388 // ARMARX_INFO << deactivateSpam(5, managerData.name.toStdString()) << "Failed to get connection info for " << managerData.name.toStdString();
389 managerData.endpointStr =
390 QString::fromStdString("? (" + std::string(ex.ice_id()) + ")");
391 }
392 // Reflect the ice_ping result: a manager whose ping failed is not online,
393 // even if getApplicationPropertyInfos() happened to succeed (issue #64.2).
394 managerData.online = result;
395 }
396 catch (...)
397 {
398 managerData.online = false;
399 result = false;
400 }
401 return result;
402 }
403
404 bool
406 ArmarXManagerInterfacePrx prx,
407 ArmarXManagerItem::ObjectMap& objectStates)
408 {
410 // Bound every call with a timeout, not just the ping below: a component that
411 // accepts connections but then hangs in dispatch would otherwise stall the whole
412 // update task forever (issue #64.6).
413 prx = prx->ice_timeout(1000);
414
415 // update existence state
416 try
417 {
418 prx->ice_ping();
419 }
420 catch (...)
421 {
422 return false;
423 }
424
425 // actual retrieval
426 Ice::StringSeq objectNames = prx->getManagedObjectNames();
427
428 for (auto& objectName : objectNames)
429 {
430 ManagedIceObjectItem objEntry;
431 objEntry.name = objectName.c_str();
432 objEntry.state = prx->getObjectState(objectName);
433 objEntry.connectivity = prx->getObjectConnectivity(objectName);
434 objEntry.properties = prx->getObjectPropertyInfos(objectName);
435 try
436 {
437 objEntry.metaInfoMap = prx->getMetaInfo(objectName);
438 }
439 catch (...)
440 {
441 ARMARX_INFO << deactivateSpam(1000, objectName) << "Failed to get meta info for "
442 << objectName;
443 }
444 objectStates[objEntry.name] = (objEntry);
445 }
446
447 return true;
448 }
449
450 void
452 {
453
454 // std::unique_lock lock(monitoredManagerModel->getMutex());
455 // StateUpdateMap stateMap;
457 {
458 decltype(currentManagerPrxMap) proxies;
459 {
460 std::unique_lock lock(managerPrxMapMutex);
461 proxies = currentManagerPrxMap;
462 }
463 IceUtil::Time start = IceUtil::Time::now();
464 for (auto it = proxies.begin(); it != proxies.end(); it++)
465 {
466 // retrieveManagerData() already retrieves the object states, the app
467 // properties, the connection info and the online flag. The previous code
468 // fetched the object states and app properties here as well and then
469 // discarded them by calling retrieveManagerData() - doing every Ice call
470 // twice per manager per tick (issue #64.6).
471 managerDataMap[it->first].name = it->first;
472 retrieveManagerData(it->second, managerDataMap[it->first]);
473 }
474 ARMARX_DEBUG << "update duration: "
475 << (IceUtil::Time::now() - start).toMilliSecondsDouble();
476 }
477
478 emit updateManagerStatesSignal(managerDataMap);
479 }
480
481 void
483 {
484 this->managerRepositoryModel->copyFrom(
485 managerRepositoryDialog->getManagerRepositoryModel());
486
487 this->monitoredManagerModel->copyFrom(managerRepositoryDialog->getMonitoredManagersModel());
488 ui.monitoredManagersTree->expandToDepth(1);
489 std::unique_lock lock(managerPrxMapMutex);
490 currentManagerPrxMap = monitoredManagerModel->getManagerProxyMap();
491 }
492
493 QStringList
494 SystemStateMonitorWidget::fetchOnlineManagers()
495 {
496 // Liveness-ping timeout in milliseconds. This is a read-only monitor: a manager
497 // that does not answer within this budget is treated as "unknown" and simply not
498 // listed. It is never removed from the registry (see issue #64) - a monitor must
499 // not mutate what it monitors. Kept modest because the Scan/Configure callers run
500 // this synchronously on the GUI thread, where each unreachable manager blocks the
501 // UI for this long.
502 const int pingTimeoutMs = 1000;
503
506 {
508 };
509
510 auto admin = getIceManager()->getIceGridSession()->getAdmin();
511 ARMARX_INFO << "Getting managers";
512 IceGrid::ObjectInfoSeq objects = admin->getAllObjectInfos("*Manager");
513 ARMARX_INFO << "Got new managers";
514
515 IceGrid::ObjectInfoSeq result;
516
517 for(const auto& objectInfo : objects)
518 {
519 Ice::ObjectPrx current = objectInfo.proxy;
520
521 ArmarXManagerInterfacePrx object;
522
523 // if objects are hanging we might get connection refused
524 try
525 {
526 const std::string proxyName = current->ice_getIdentity().name;
527 ARMARX_INFO << "Trying to ping proxy " << VAROUT(proxyName);
528 object = ArmarXManagerInterfacePrx::checkedCast(current->ice_timeout(pingTimeoutMs));
529 ARMARX_INFO << "Ping received from " << VAROUT(proxyName);
530 }
531 catch (...)
532 {
533 // Timeout / connection refused: the manager is busy, slow, or gone. Treat
534 // it as unknown and skip it. Do not remove it from the registry.
536 continue;
537 }
538
539 // checkedCast returns null (without throwing) for objects that match the
540 // "*Manager" pattern but are not ArmarXManagers, e.g. IceStorm/TopicManager.
541 if (object)
542 {
543 result.push_back(objectInfo);
544 }
545 }
546 ARMARX_INFO << "Iterated through online managers";
547
548 QStringList managers;
549 for (const IceGrid::ObjectInfo& info : result)
550 {
551 managers.append(info.proxy->ice_getIdentity().name.c_str());
552 }
553
554 return managers;
555 }
556
557 void
559 {
560 managerRepositoryDialog->addOnlineManagers(fetchOnlineManagers());
561 }
562
563 void
565 {
566 managerRepositoryDialog->getManagerRepositoryModel()->copyFrom(managerRepositoryModel);
567
568 managerRepositoryDialog->getMonitoredManagersModel()->copyFrom(monitoredManagerModel);
569
571
572 managerRepositoryDialog->show();
573 }
574
575 void
576 SystemStateMonitorWidget::prefillView()
577 {
578 managerRepositoryModel->clear();
579 monitoredManagerModel->clear();
580
581 QStringList managers = fetchOnlineManagers();
582 // enableMainWidgetAsync(false);
583 // ARMARX_INFO << "Got managers";
584 // QMetaObject::invokeMethod(this, "addArmarXManagers", Q_ARG(QStringList, managers));
585 addArmarXManagers(managers);
586 QMetaObject::invokeMethod(ui.monitoredManagersTree, "expandToDepth", Q_ARG(int, 0));
587 std::unique_lock lock(managerPrxMapMutex);
588 currentManagerPrxMap = monitoredManagerModel->getManagerProxyMap(managers);
589 }
590
591
592 void
594 {
595 // ARMARX_IMPORTANT << this->rowCount();
596 for (auto& name : managerNames)
597 {
598 try
599 {
601 data.name = name;
602 ARMARX_DEBUG << name.toStdString();
603 auto proxy =
604 getIceManager()->getProxy<ArmarXManagerInterfacePrx>(name.toStdString());
606 QMetaObject::invokeMethod(monitoredManagerModel,
607 "upsertManagerDetails",
609 ARMARX_DEBUG << name.toStdString() << " done";
610 }
611 catch (...)
612 {
614 }
615 }
616 }
617
618 void
619 SystemStateMonitorWidget::expandFilterSelection(QString filterStr)
620 {
621 ARMARX_DEBUG_S << VAROUT(filterStr);
622 if (filterStr.length() == 0)
623 {
624 ui.monitoredManagersTree->collapseAll();
625 // ui.monitoredManagersTree->expandToDepth(1);
626 }
627 else
628 {
629 filterExpansionTimer.start(500);
630 }
631 }
632
633 void
634 SystemStateMonitorWidget::delayedFilterExpansion()
635 {
637
638 InfixFilterModel::ExpandFilterResults(ui.monitoredManagersTree);
639 }
640
641 void
642 armarx::SystemStateMonitorWidget::on_btnProblematicOnly_toggled(bool checked)
643 {
644 CHECK_QT_THREAD(getWidget());
645
646 // filterModel->setHideResolvedComponents(checked);
647 filterModel->invalidate();
648 InfixFilterModel::ExpandFilterResults(ui.monitoredManagersTree);
649 }
650
651 void
652 SystemStateMonitorWidget::fillLegendLayout(QHBoxLayout* layout, ArmarXManagerModel& model) const
653 {
654 auto addLegendEntry = [layout, &model](ManagedIceObjectState state, const QString& text)
655 {
656 QLabel* label = new QLabel(text);
657 {
658 QBrush brush = model.getBrush(state);
659 QPalette p = label->palette();
660 p.setColor(label->backgroundRole(), brush.color());
661 label->setPalette(p);
662 label->setAutoFillBackground(true);
663 }
664 {
665 QFont font = label->font();
666 font.setPointSize(10);
667 label->setFont(font);
668 }
669 layout->addWidget(label);
670 };
671
672 addLegendEntry(armarx::eManagedIceObjectCreated, "Created");
673 addLegendEntry(armarx::eManagedIceObjectInitializing, "Initializing");
674 addLegendEntry(armarx::eManagedIceObjectInitialized, "Initialized");
675 addLegendEntry(armarx::eManagedIceObjectInitializationFailed, "Initialization Failed");
676 addLegendEntry(armarx::eManagedIceObjectStarting, "Connecting");
677 addLegendEntry(armarx::eManagedIceObjectStarted, "Connected");
678 addLegendEntry(armarx::eManagedIceObjectStartingFailed, "Connecting Failed");
679 addLegendEntry(armarx::eManagedIceObjectExiting, "Exiting");
680 addLegendEntry(armarx::eManagedIceObjectExited, "Exited");
681 }
682
683} // namespace armarx
uint8_t index
#define option(type, fn)
SpamFilterDataPtr deactivateSpam(SpamFilterDataPtr const &spamFilter, float deactivationDurationSec, const std::string &identifier, bool deactivate)
Definition Logging.cpp:75
#define CHECK_NOT_QT_THREAD(qtObject)
Definition QtUtil.h:36
#define CHECK_QT_THREAD(qtObject)
Macro to check whether the current function is executed in the thread of the given Qt object.
Definition QtUtil.h:30
#define VAROUT(x)
std::map< QString, ManagerData > ManagerDataMap
QMap< QString, ManagedIceObjectItem > ObjectMap
virtual QPointer< QWidget > getWidget()
getWidget returns a pointer to the a widget of this controller.
void enableMainWidgetAsync(bool enable)
This function enables/disables the main widget asynchronously (if called from a non qt thread).
This proxy model reimplements the filterAcceptsRow function with a new behavior: All elements that fi...
static void ExpandFilterResults(QTreeView *treeView)
Expands the treeview that all items that match the filterstring are expanded and directly visible.
ManagedIceObjectConnectivity connectivity
IceManagerPtr getIceManager() const
Returns the IceManager.
The periodic task executes one thread method repeatedly using the time period specified in the constr...
void setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const override
QWidget * createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const override
PropertyEditingDelegate(armarx::ManagerPrxMap *managerPrxMap, std::mutex *managerPrxMapMutex, QObject *parent=nullptr)
void setEditorData(QWidget *editor, const QModelIndex &index) const override
void updateManagerStatesSignal(const ArmarXManagerItem::ManagerDataMap &)
bool retrieveManagerData(ArmarXManagerInterfacePrx prx, ArmarXManagerItem::ManagerData &managerData)
void onDisconnectComponent() override
Hook for subclass.
void loadSettings(QSettings *settings) override
Load stored manager models.
void updateManagerStates(const ArmarXManagerItem::ManagerDataMap &managerData)
Updates the states of the managers stated in the monitored list.
void saveSettings(QSettings *settings) override
Saves the manager models.
void openManagerRepositoryDialog()
Opens the config dialog.
void retrieveManagerObjectsState(ArmarXManagerItem *item)
void addArmarXManagers(QStringList managerNames)
void onExitComponent() override
Hook for subclass.
void acceptConfig()
Accept config changes.
void retrieveOnlineManagers()
Retrieves the online managers.
#define ARMARX_DEBUG_S
The logging level for output that is only interesting while debugging.
Definition Logging.h:203
#define ARMARX_INFO
The normal logging level.
Definition Logging.h:179
#define ARMARX_DEBUG
The logging level for output that is only interesting while debugging.
Definition Logging.h:182
#define ARMARX_WARNING
The logging level for unexpected behaviour, but not a serious problem.
Definition Logging.h:191
#define ARMARX_ON_SCOPE_EXIT
Executes given code when the enclosing scope is left.
This file offers overloads of toIce() and fromIce() functions for STL container types.
std::string GetHandledExceptionString()
void handleExceptions()
std::map< QString, ArmarXManagerInterfacePrx > ManagerPrxMap
void postToObject(QObject *context, Function &&function)
Run function on the GUI (main) thread's event loop.