File indexing completed on 2024-04-28 16:49:52
0001 /* 0002 SPDX-FileCopyrightText: 2019 David Edmundson <davidedmundson@kde.org> 0003 0004 SPDX-License-Identifier: LGPL-2.0-or-later 0005 */ 0006 0007 #include "cgroup_data_model.h" 0008 0009 #include "Formatter.h" 0010 #include "cgroup.h" 0011 #include "extended_process_list.h" 0012 #include "process_attribute.h" 0013 #include "process_data_model.h" 0014 0015 #include <KLocalizedString> 0016 0017 #include <QDebug> 0018 #include <QDir> 0019 #include <QMetaEnum> 0020 #include <QTimer> 0021 0022 #include <algorithm> 0023 #include <filesystem> 0024 0025 using namespace KSysGuard; 0026 0027 class KSysGuard::CGroupDataModelPrivate 0028 { 0029 public: 0030 QVector<KSysGuard::Process *> processesFor(CGroup *app); 0031 0032 QSharedPointer<ExtendedProcesses> m_processes; 0033 QTimer *m_updateTimer; 0034 ProcessAttributeModel *m_attributeModel = nullptr; 0035 QHash<QString, KSysGuard::ProcessAttribute *> m_availableAttributes; 0036 QVector<KSysGuard::ProcessAttribute *> m_enabledAttributes; 0037 0038 bool m_available = false; 0039 QString m_root; 0040 QScopedPointer<CGroup> m_rootGroup; 0041 0042 QVector<CGroup *> m_cGroups; // an ordered list of unfiltered cgroups from our root 0043 QHash<QString, CGroup *> m_cgroupMap; // all known cgroups from our root 0044 QHash<QString, CGroup *> m_oldGroups; 0045 QHash<CGroup *, QVector<Process *>> m_processMap; // cached mapping of cgroup to list of processes of that group 0046 }; 0047 0048 class GroupNameAttribute : public ProcessAttribute 0049 { 0050 public: 0051 GroupNameAttribute(QObject *parent) 0052 : KSysGuard::ProcessAttribute(QStringLiteral("menuId"), i18nc("@title", "Desktop ID"), parent) 0053 { 0054 } 0055 QVariant cgroupData(CGroup *app, const QVector<KSysGuard::Process *> &processes) const override 0056 { 0057 Q_UNUSED(processes) 0058 return app->service()->menuId(); 0059 } 0060 }; 0061 0062 class AppIconAttribute : public KSysGuard::ProcessAttribute 0063 { 0064 public: 0065 AppIconAttribute(QObject *parent) 0066 : KSysGuard::ProcessAttribute(QStringLiteral("iconName"), i18nc("@title", "Icon"), parent) 0067 { 0068 } 0069 QVariant cgroupData(CGroup *app, const QVector<KSysGuard::Process *> &processes) const override 0070 { 0071 Q_UNUSED(processes) 0072 return app->service()->icon(); 0073 } 0074 }; 0075 0076 class AppNameAttribute : public KSysGuard::ProcessAttribute 0077 { 0078 public: 0079 AppNameAttribute(QObject *parent) 0080 : KSysGuard::ProcessAttribute(QStringLiteral("appName"), i18nc("@title", "Name"), parent) 0081 { 0082 } 0083 QVariant cgroupData(CGroup *app, const QVector<KSysGuard::Process *> &processes) const override 0084 { 0085 Q_UNUSED(processes) 0086 return app->service()->name(); 0087 } 0088 }; 0089 0090 CGroupDataModel::CGroupDataModel(QObject *parent) 0091 : CGroupDataModel(QStringLiteral("/"), parent) 0092 { 0093 } 0094 0095 CGroupDataModel::CGroupDataModel(const QString &root, QObject *parent) 0096 : QAbstractItemModel(parent) 0097 , d(new CGroupDataModelPrivate) 0098 { 0099 d->m_updateTimer = new QTimer(this); 0100 d->m_processes = ExtendedProcesses::instance(); 0101 0102 QVector<ProcessAttribute *> attributes = d->m_processes->attributes(); 0103 attributes.reserve(attributes.count() + 3); 0104 attributes.append(new GroupNameAttribute(this)); 0105 attributes.append(new AppNameAttribute(this)); 0106 attributes.append(new AppIconAttribute(this)); 0107 for (auto attr : std::as_const(attributes)) { 0108 d->m_availableAttributes[attr->id()] = attr; 0109 } 0110 0111 if (CGroup::cgroupSysBasePath().isEmpty()) { 0112 return; 0113 } 0114 0115 connect(d->m_updateTimer, &QTimer::timeout, this, [this]() { 0116 update(); 0117 }); 0118 d->m_updateTimer->setInterval(2000); 0119 d->m_updateTimer->start(); 0120 0121 // updateAllProcesses will delete processes that no longer exist, a method that 0122 // can be called by any user of the shared Processes 0123 // so clear out our cache of cgroup -> process whenever anything gets removed 0124 connect(d->m_processes.data(), &Processes::beginRemoveProcess, this, [this]() { 0125 d->m_processMap.clear(); 0126 }); 0127 0128 setRoot(root); 0129 } 0130 0131 CGroupDataModel::~CGroupDataModel() 0132 { 0133 } 0134 0135 int CGroupDataModel::rowCount(const QModelIndex &parent) const 0136 { 0137 if (parent.isValid()) { 0138 return 0; 0139 } 0140 return d->m_cGroups.count(); 0141 } 0142 0143 QModelIndex CGroupDataModel::index(int row, int column, const QModelIndex &parent) const 0144 { 0145 if (row < 0 || row >= d->m_cGroups.count()) { 0146 return QModelIndex(); 0147 } 0148 if (parent.isValid()) { 0149 return QModelIndex(); 0150 } 0151 return createIndex(row, column, d->m_cGroups.at(row)); 0152 } 0153 0154 QModelIndex CGroupDataModel::parent(const QModelIndex &child) const 0155 { 0156 Q_UNUSED(child) 0157 return QModelIndex(); 0158 } 0159 0160 int CGroupDataModel::columnCount(const QModelIndex &parent) const 0161 { 0162 if (parent.isValid()) { 0163 return 0; 0164 } 0165 0166 return d->m_enabledAttributes.count(); 0167 } 0168 0169 QStringList CGroupDataModel::availableAttributes() const 0170 { 0171 return d->m_availableAttributes.keys(); 0172 } 0173 0174 QStringList CGroupDataModel::enabledAttributes() const 0175 { 0176 QStringList rc; 0177 rc.reserve(d->m_enabledAttributes.size()); 0178 for (auto attr : std::as_const(d->m_enabledAttributes)) { 0179 rc << attr->id(); 0180 } 0181 return rc; 0182 } 0183 0184 void CGroupDataModel::setEnabledAttributes(const QStringList &enabledAttributes) 0185 { 0186 beginResetModel(); 0187 0188 QVector<ProcessAttribute *> unusedAttributes = d->m_enabledAttributes; 0189 d->m_enabledAttributes.clear(); 0190 0191 for (auto attribute : enabledAttributes) { 0192 auto attr = d->m_availableAttributes.value(attribute, nullptr); 0193 if (!attr) { 0194 qWarning() << "Could not find attribute" << attribute; 0195 continue; 0196 } 0197 unusedAttributes.removeOne(attr); 0198 d->m_enabledAttributes << attr; 0199 int columnIndex = d->m_enabledAttributes.count() - 1; 0200 0201 // reconnect as using the attribute in the lambda makes everything super fast 0202 disconnect(attr, &KSysGuard::ProcessAttribute::dataChanged, this, nullptr); 0203 connect(attr, &KSysGuard::ProcessAttribute::dataChanged, this, [this, columnIndex](KSysGuard::Process *process) { 0204 auto cgroup = d->m_cgroupMap.value(process->cGroup()); 0205 if (!cgroup) { 0206 return; 0207 } 0208 const QModelIndex index = getQModelIndex(cgroup, columnIndex); 0209 Q_EMIT dataChanged(index, index); 0210 }); 0211 } 0212 0213 for (auto unusedAttr : std::as_const(unusedAttributes)) { 0214 disconnect(unusedAttr, &KSysGuard::ProcessAttribute::dataChanged, this, nullptr); 0215 } 0216 0217 endResetModel(); 0218 0219 Q_EMIT enabledAttributesChanged(); 0220 } 0221 0222 QModelIndex CGroupDataModel::getQModelIndex(CGroup *cgroup, int column) const 0223 { 0224 Q_ASSERT(cgroup); 0225 int row = d->m_cGroups.indexOf(cgroup); 0226 return index(row, column, QModelIndex()); 0227 } 0228 0229 QHash<int, QByteArray> CGroupDataModel::roleNames() const 0230 { 0231 QHash<int, QByteArray> roles = QAbstractItemModel::roleNames(); 0232 QMetaEnum e = ProcessDataModel::staticMetaObject.enumerator(ProcessDataModel::staticMetaObject.indexOfEnumerator("AdditionalRoles")); 0233 0234 for (int i = 0; i < e.keyCount(); ++i) { 0235 roles.insert(e.value(i), e.key(i)); 0236 } 0237 0238 return roles; 0239 } 0240 0241 QVariant CGroupDataModel::data(const QModelIndex &index, int role) const 0242 { 0243 if (!checkIndex(index, CheckIndexOption::IndexIsValid)) { 0244 return QVariant(); 0245 } 0246 int attr = index.column(); 0247 auto attribute = d->m_enabledAttributes[attr]; 0248 switch (role) { 0249 case Qt::DisplayRole: 0250 case ProcessDataModel::FormattedValue: { 0251 KSysGuard::CGroup *app = reinterpret_cast<KSysGuard::CGroup *>(index.internalPointer()); 0252 const QVariant value = attribute->cgroupData(app, d->processesFor(app)); 0253 return KSysGuard::Formatter::formatValue(value, attribute->unit()); 0254 } 0255 case ProcessDataModel::Value: { 0256 KSysGuard::CGroup *app = reinterpret_cast<KSysGuard::CGroup *>(index.internalPointer()); 0257 const QVariant value = attribute->cgroupData(app, d->processesFor(app)); 0258 return value; 0259 } 0260 case ProcessDataModel::Attribute: { 0261 return attribute->id(); 0262 } 0263 case ProcessDataModel::Minimum: { 0264 return attribute->min(); 0265 } 0266 case ProcessDataModel::Maximum: { 0267 return attribute->max(); 0268 } 0269 case ProcessDataModel::ShortName: { 0270 if (!attribute->shortName().isEmpty()) { 0271 return attribute->shortName(); 0272 } 0273 return attribute->name(); 0274 } 0275 case ProcessDataModel::Name: { 0276 return attribute->name(); 0277 } 0278 case ProcessDataModel::Unit: { 0279 return attribute->unit(); 0280 } 0281 case ProcessDataModel::PIDs: { 0282 const auto pids = static_cast<KSysGuard::CGroup *>(index.internalPointer())->pids(); 0283 QVariantList result; 0284 std::transform(pids.begin(), pids.end(), std::back_inserter(result), [](pid_t pid) { 0285 return int(pid); 0286 }); 0287 return result; 0288 } 0289 } 0290 return QVariant(); 0291 } 0292 0293 QVariant CGroupDataModel::headerData(int section, Qt::Orientation orientation, int role) const 0294 { 0295 if (orientation == Qt::Vertical) { 0296 return QVariant(); 0297 } 0298 0299 if (section < 0 || section >= columnCount()) { 0300 return QVariant(); 0301 } 0302 0303 auto attribute = d->m_enabledAttributes[section]; 0304 0305 switch (role) { 0306 case Qt::DisplayRole: 0307 case ProcessDataModel::ShortName: { 0308 if (!attribute->shortName().isEmpty()) { 0309 return attribute->shortName(); 0310 } 0311 return attribute->name(); 0312 } 0313 case ProcessDataModel::Name: 0314 return attribute->name(); 0315 case ProcessDataModel::Value: 0316 case ProcessDataModel::Attribute: { 0317 return attribute->id(); 0318 } 0319 case ProcessDataModel::Unit: { 0320 auto attribute = d->m_enabledAttributes[section]; 0321 return attribute->unit(); 0322 } 0323 case ProcessDataModel::Minimum: { 0324 return attribute->min(); 0325 } 0326 case ProcessDataModel::Maximum: { 0327 return attribute->max(); 0328 } 0329 default: 0330 break; 0331 } 0332 0333 return QVariant(); 0334 } 0335 0336 ProcessAttributeModel *CGroupDataModel::attributesModel() 0337 { 0338 // lazy load 0339 if (!d->m_attributeModel) { 0340 d->m_attributeModel = new KSysGuard::ProcessAttributeModel(d->m_availableAttributes.values().toVector(), this); 0341 } 0342 return d->m_attributeModel; 0343 } 0344 0345 bool CGroupDataModel::isEnabled() const 0346 { 0347 return d->m_updateTimer->isActive(); 0348 } 0349 0350 void CGroupDataModel::setEnabled(bool enabled) 0351 { 0352 if (enabled) { 0353 d->m_updateTimer->start(); 0354 QMetaObject::invokeMethod( 0355 this, 0356 [this] { 0357 update(); 0358 }, 0359 Qt::QueuedConnection); 0360 } else { 0361 d->m_updateTimer->stop(); 0362 } 0363 } 0364 0365 QString CGroupDataModel::root() const 0366 { 0367 return d->m_root; 0368 } 0369 0370 void CGroupDataModel::setRoot(const QString &root) 0371 { 0372 if (root == d->m_root) { 0373 return; 0374 } 0375 d->m_root = root; 0376 Q_EMIT rootChanged(); 0377 QMetaObject::invokeMethod( 0378 this, 0379 [this] { 0380 update(); 0381 }, 0382 Qt::QueuedConnection); 0383 0384 const QString path = CGroup::cgroupSysBasePath() + root; 0385 bool available = QFile::exists(path); 0386 0387 if (available) { 0388 d->m_rootGroup.reset(new CGroup(root)); 0389 } else { 0390 d->m_rootGroup.reset(); 0391 } 0392 0393 if (available != d->m_available) { 0394 d->m_available = available; 0395 Q_EMIT availableChanged(); 0396 } 0397 } 0398 0399 void CGroupDataModel::update() 0400 { 0401 if (!d->m_rootGroup) { 0402 return; 0403 } 0404 0405 d->m_oldGroups = d->m_cgroupMap; 0406 0407 Processes::UpdateFlags flags; 0408 for (auto attribute : std::as_const(d->m_enabledAttributes)) { 0409 flags |= attribute->requiredUpdateFlags(); 0410 } 0411 0412 // In an ideal world we would only the relevant process 0413 // but Ksysguard::Processes doesn't handle that very well 0414 d->m_processes->updateAllProcesses(d->m_updateTimer->interval(), flags); 0415 0416 update(d->m_rootGroup.data()); 0417 0418 for (auto c : std::as_const(d->m_oldGroups)) { 0419 int row = d->m_cGroups.indexOf(c); 0420 if (row >= 0) { 0421 beginRemoveRows(QModelIndex(), row, row); 0422 d->m_cGroups.removeOne(c); 0423 endRemoveRows(); 0424 } 0425 d->m_cgroupMap.remove(c->id()); 0426 delete c; 0427 } 0428 } 0429 0430 bool CGroupDataModel::filterAcceptsCGroup(const QString &id) 0431 { 0432 return id.endsWith(QLatin1String(".service")) || id.endsWith(QLatin1String(".scope")); 0433 } 0434 0435 void CGroupDataModel::update(CGroup *node) 0436 { 0437 namespace fs = std::filesystem; 0438 const QString path = CGroup::cgroupSysBasePath() + node->id(); 0439 0440 // Update our own stat info 0441 // This may trigger some dataChanged 0442 node->requestPids(this, [this, node](QVector<pid_t> pids) { 0443 auto row = d->m_cGroups.indexOf(node); 0444 if (row >= 0) { 0445 d->m_cGroups[row]->setPids(pids); 0446 d->m_processMap.remove(d->m_cGroups[row]); 0447 Q_EMIT dataChanged(index(row, 0, QModelIndex()), index(row, columnCount() - 1, QModelIndex())); 0448 } 0449 }); 0450 0451 std::error_code error; 0452 const fs::directory_iterator iterator(path.toUtf8().data(), error); 0453 if (error) { 0454 return; 0455 } 0456 for (const auto &entry : iterator) { 0457 if (!entry.is_directory()) { 0458 continue; 0459 } 0460 const QString childId = node->id() % QLatin1Char('/') % QString::fromUtf8(entry.path().filename().c_str()); 0461 CGroup *childNode = d->m_cgroupMap[childId]; 0462 if (!childNode) { 0463 childNode = new CGroup(childId); 0464 d->m_cgroupMap[childNode->id()] = childNode; 0465 0466 if (filterAcceptsCGroup(childId)) { 0467 int row = d->m_cGroups.count(); 0468 beginInsertRows(QModelIndex(), row, row); 0469 d->m_cGroups.append(childNode); 0470 endInsertRows(); 0471 } 0472 } 0473 update(childNode); 0474 d->m_oldGroups.remove(childId); 0475 } 0476 } 0477 0478 bool CGroupDataModel::isAvailable() const 0479 { 0480 return d->m_available; 0481 } 0482 0483 QVector<Process *> CGroupDataModelPrivate::processesFor(CGroup *app) 0484 { 0485 if (m_processMap.contains(app)) { 0486 return m_processMap.value(app); 0487 } 0488 0489 QVector<Process *> result; 0490 const auto pids = app->pids(); 0491 std::for_each(pids.begin(), pids.end(), [this, &result](pid_t pid) { 0492 auto process = m_processes->getProcess(pid); 0493 if (process) { 0494 result.append(process); 0495 } 0496 }); 0497 0498 m_processMap.insert(app, result); 0499 0500 return result; 0501 }