diff --git a/processcore/CMakeLists.txt b/processcore/CMakeLists.txt index 506c1a9..f9e6a8b 100644 --- a/processcore/CMakeLists.txt +++ b/processcore/CMakeLists.txt @@ -1,87 +1,91 @@ add_definitions(-DTRANSLATION_DOMAIN=\"processcore\") ########### next target ############### set(ksysguard_LIB_SRCS + application_data_model.cpp + cgroup.cpp + cgroup_data_model.cpp extended_process_list.cpp processes.cpp process.cpp process_attribute.cpp processes_local_p.cpp processes_remote_p.cpp processes_base_p.cpp processes_atop_p.cpp process_controller.cpp process_attribute_model.cpp process_data_model.cpp process_data_provider.cpp ) ecm_qt_declare_logging_category(ksysguard_LIB_SRCS HEADER processcore_debug.h IDENTIFIER LIBKSYSGUARD_PROCESSCORE CATEGORY_NAME org.kde.libksysguard.processcore) add_library(processcore ${ksysguard_LIB_SRCS}) add_library(KSysGuard::ProcessCore ALIAS processcore) target_link_libraries(processcore PUBLIC Qt5::Core KSysGuard::Formatter PRIVATE KF5::I18n KF5::AuthCore KF5::CoreAddons + KF5::Service ${ZLIB_LIBRARIES} ) if( ${CMAKE_SYSTEM_NAME} MATCHES "NetBSD" ) message(STATUS "Adding kvm library on NetBSD") target_link_libraries(processcore kvm) endif() if(NOT HAVE_CLOCK_GETTIME_C) target_link_libraries(processcore PRIVATE rt) endif() target_include_directories(processcore PUBLIC "$" "$" ) set_target_properties(processcore PROPERTIES VERSION ${KSYSGUARD_VERSION_STRING} SOVERSION ${KSYSGUARD_SOVERSION} EXPORT_NAME ProcessCore) install(TARGETS processcore EXPORT libksysguardLibraryTargets ${KDE_INSTALL_TARGETS_DEFAULT_ARGS}) add_subdirectory(declarative) ########### install files ############### install( FILES processes.h process.h process_controller.h process_attribute.h process_data_provider.h process_data_model.h process_attribute_model.h formatter.h unit.h DESTINATION ${KDE_INSTALL_INCLUDEDIR}/ksysguard/processcore COMPONENT Devel ) #------ KAuth stuff # Auth example helper set(ksysguardprocesslist_helper_srcs helper.cpp process.cpp processes_local_p.cpp processes_base_p.cpp) add_executable(ksysguardprocesslist_helper ${ksysguardprocesslist_helper_srcs}) target_link_libraries(ksysguardprocesslist_helper Qt5::Core KF5::AuthCore KF5::I18n) install(TARGETS ksysguardprocesslist_helper DESTINATION ${KAUTH_HELPER_INSTALL_DIR}) kauth_install_helper_files(ksysguardprocesslist_helper org.kde.ksysguard.processlisthelper root) kauth_install_actions(org.kde.ksysguard.processlisthelper actions.actions) set_target_properties(ksysguardprocesslist_helper PROPERTIES COMPILE_FLAGS "-Wall -ggdb") diff --git a/processcore/application_data_model.cpp b/processcore/application_data_model.cpp new file mode 100644 index 0000000..70f9dbf --- /dev/null +++ b/processcore/application_data_model.cpp @@ -0,0 +1,43 @@ +/* + Copyright (c) 2019 David Edmundson + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include "application_data_model.h" +#include + +#include + +using namespace KSysGuard; + +ApplicationDataModel::ApplicationDataModel(QObject *parent) + : CGroupDataModel(parent) +{ + const QString userId = KUserId::currentEffectiveUserId().toString(); + setRoot(QStringLiteral("/user.slice/user-%1.slice/user@%1.service").arg(userId)); +} + +bool ApplicationDataModel::filterAcceptsCGroup(const QString &id) +{ + if (!CGroupDataModel::filterAcceptsCGroup(id)) { + return false; + } + // this is temporary. In the future as per https://systemd.io/DESKTOP_ENVIRONMENTS/ + // all apps will have a drop-in that puts apps in the app.slice + // when this happens adjust the root above and drop this line + return id.contains(QLatin1String("/app")) || id.contains(QLatin1String("/flatpak")); +} diff --git a/processcore/application_data_model.h b/processcore/application_data_model.h new file mode 100644 index 0000000..4e0426f --- /dev/null +++ b/processcore/application_data_model.h @@ -0,0 +1,34 @@ +/* + Copyright (c) 2019 David Edmundson + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#pragma once + +#include "cgroup_data_model.h" + +namespace KSysGuard { + +class Q_DECL_EXPORT ApplicationDataModel : public CGroupDataModel +{ + Q_OBJECT +public: + ApplicationDataModel(QObject *parent = nullptr); + bool filterAcceptsCGroup(const QString &id) override; +}; + +} diff --git a/processcore/cgroup.cpp b/processcore/cgroup.cpp new file mode 100644 index 0000000..f6a8ab3 --- /dev/null +++ b/processcore/cgroup.cpp @@ -0,0 +1,154 @@ +/* + Copyright (c) 2019 David Edmundson + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include "cgroup.h" + +#include +#include +#include +#include +#include + +#include "process.h" + +using namespace KSysGuard; + +class KSysGuard::CGroupPrivate +{ +public: + CGroupPrivate(const QString &_processGroupId) + : processGroupId(_processGroupId) + , service(serviceFromAppId(_processGroupId)) + { + } + const QString processGroupId; + const KService::Ptr service; + QVector processes; + static KService::Ptr serviceFromAppId(const QString &appId); + + static QRegularExpression s_appIdFromProcessGroupPattern; + + QVector procs; +}; + +class CGroupSystemInformation +{ +public: + CGroupSystemInformation(); + QString sysGgroupRoot; +}; + +Q_GLOBAL_STATIC(CGroupSystemInformation, s_cGroupSystemInformation) + +// Flatpak's are currently in a cgroup, but they don't follow the specification +// this has been fixed, but this provides some compatability till that lands +// app vs apps exists because the spec changed. +QRegularExpression CGroupPrivate::s_appIdFromProcessGroupPattern(QStringLiteral("[apps|app|flatpak]-([^-]+)-.*")); + +CGroup::CGroup(const QString &id) + : d(new CGroupPrivate(id)) +{ +} + +CGroup::~CGroup() +{ +} + +QString KSysGuard::CGroup::id() const +{ + return d->processGroupId; +} + +KService::Ptr KSysGuard::CGroup::service() const +{ + return d->service; +} + +QVector CGroup::processes() const +{ + return d->procs; +} + +void CGroup::setProcesses(QVector procs) +{ + d->procs = procs; +} + +QVector KSysGuard::CGroup::getPids() const +{ + const QString pidFilePath = cgroupSysBasePath() + d->processGroupId + QLatin1String("/cgroup.procs"); + QFile pidFile(pidFilePath); + pidFile.open(QFile::ReadOnly | QIODevice::Text); + QTextStream stream(&pidFile); + + QVector procs; + QString line = stream.readLine(); + while (!line.isNull()) { + procs.append(line.toLong()); + line = stream.readLine(); + } + + return procs; +} + +KService::Ptr CGroupPrivate::serviceFromAppId(const QString &processGroup) +{ + const int lastSlash = processGroup.lastIndexOf(QLatin1Char('/')); + + QString serviceName = processGroup; + if (lastSlash != -1) { + serviceName = processGroup.mid(lastSlash + 1); + } + + const QRegularExpressionMatch &appIdMatch = s_appIdFromProcessGroupPattern.match(serviceName); + + if (!appIdMatch.isValid() || !appIdMatch.hasMatch()) { + // create a transient service object just to have a sensible name + return KService::Ptr(new KService(serviceName, QString(), QString())); + } + + const QString appId = appIdMatch.captured(1); + + KService::Ptr service = KService::serviceByMenuId(appId + QStringLiteral(".desktop")); + + if (!service) { + service = new KService(appId, QString(), QString()); + } + + return service; +} + +QString CGroup::cgroupSysBasePath() +{ + return s_cGroupSystemInformation->sysGgroupRoot; +} + +CGroupSystemInformation::CGroupSystemInformation() +{ + QDir base(QStringLiteral("/sys/fs/cgroup")); + if (base.exists(QLatin1String("unified"))) { + sysGgroupRoot = base.absoluteFilePath(QStringLiteral("unified")); + return; + } + if (base.exists()) { + sysGgroupRoot = base.absolutePath(); + } +} + + diff --git a/processcore/cgroup.h b/processcore/cgroup.h new file mode 100644 index 0000000..6969507 --- /dev/null +++ b/processcore/cgroup.h @@ -0,0 +1,94 @@ +/* + Copyright (c) 2019 David Edmundson + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#pragma once + +#include +#include +#include + +#include + +namespace KSysGuard +{ + +class Process; +class CGroupPrivate; + +/** + * @brief The CGroup class represents a cgroup. This could be a + * service, slice or scope + */ +class Q_DECL_EXPORT CGroup +{ +public: + virtual ~CGroup(); + /** + * @brief id + * @return The cgroup ID passed from the constructor + */ + QString id() const; + + /** + * @brief Returns metadata about the given service + * Only applicable for .service entries and really only useful for applications. + * This KService object is always valid, but may not correspond to a real desktop entry + * @return + */ + KService::Ptr service() const; + + /** + * @brief updates and fetches the list of processes associated with the process + * @return A Vector of pids + * @note This reloads the data on every fetch + */ + QVector getPids() const; + + /** + * @brief updates and fetches the list of processes associated with the process + * @return A Vector of pids + * @note This reloads the data on every fetch + */ + QVector processes() const; + + /** + * Returns the base path to exposed cgroup information. Either /sys/fs/cgroup or /sys/fs/cgroup/unified as applicable + * If cgroups are unavailable this will be an empty string + */ + static QString cgroupSysBasePath(); + +private: + /** + * Create a new cgroup object for a given cgroup entry + * The id is the fully formed separated path, such as + * "system.slice/dbus.service" + */ + CGroup(const QString &id); + + /** + * Set the updated processes of this cgroup object. + * Managed by CgroupDataModel exclusively + */ + void setProcesses(QVector procs); + QScopedPointer d; + friend class CGroupDataModel; + friend class CGroupDataModelPrivate; +}; + +} diff --git a/processcore/cgroup_data_model.cpp b/processcore/cgroup_data_model.cpp new file mode 100644 index 0000000..418d875 --- /dev/null +++ b/processcore/cgroup_data_model.cpp @@ -0,0 +1,439 @@ +/* + Copyright (c) 2019 David Edmundson + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include "cgroup_data_model.h" + +#include "cgroups.h" +#include "cgroup.h" +#include "extended_process_list.h" +#include "process_attribute.h" +#include "process_data_model.h" +#include "Formatter.h" + +#include + +#include +#include +#include +#include + +#include + +using namespace KSysGuard; + +class KSysGuard::CGroupDataModelPrivate +{ +public: + ExtendedProcesses *m_processes; + QTimer *m_updateTimer; + ProcessAttributeModel *m_attributeModel = nullptr; + QHash m_availableAttributes; + QVector m_enabledAttributes; + + QString m_root; + QScopedPointer m_rootGroup; + + QVector m_cGroups; // an ordered list of unfiltered cgroups from our root + QHash m_cgroupMap; // all known cgroups from our root + QHash m_oldGroups; +}; + +class GroupNameAttribute : public ProcessAttribute +{ +public: + GroupNameAttribute(QObject *parent) : + KSysGuard::ProcessAttribute(QStringLiteral("menuId"), i18n("Desktop ID"), parent) { + } + QVariant cgroupData(CGroup *app) const override { + return app->service()->menuId(); + } +}; + +class AppIconAttribute : public KSysGuard::ProcessAttribute +{ +public: + AppIconAttribute(QObject *parent) : + KSysGuard::ProcessAttribute(QStringLiteral("iconName"), i18n("Icon"), parent) { + } + QVariant cgroupData(CGroup *app) const override { + return app->service()->icon(); + } +}; + +class AppNameAttribute : public KSysGuard::ProcessAttribute +{ +public: + AppNameAttribute(QObject *parent) : + KSysGuard::ProcessAttribute(QStringLiteral("appName"), i18n("Name"), parent) { + } + QVariant cgroupData(CGroup *app) const override { + return app->service()->name(); + } +}; + +CGroupDataModel::CGroupDataModel(QObject *parent) + : QAbstractItemModel(parent) + , d(new CGroupDataModelPrivate) +{ + d->m_updateTimer = new QTimer(this); + d->m_processes = new ExtendedProcesses(this); + + QVector attributes = d->m_processes->attributes(); + attributes.reserve(attributes.count() + 3); + attributes.append(new GroupNameAttribute(this)); + attributes.append(new AppNameAttribute(this)); + attributes.append(new AppIconAttribute(this)); + for (auto attr: attributes) { + d->m_availableAttributes[attr->id()] = attr; + } + + connect(d->m_updateTimer, &QTimer::timeout, this, [this]() { + update(); + }); + d->m_updateTimer->setInterval(2000); + d->m_updateTimer->start(); + + setRoot(QStringLiteral("/")); +} + +CGroupDataModel::~CGroupDataModel() +{ +} + +int CGroupDataModel::rowCount(const QModelIndex &parent) const +{ + if (parent.isValid()) { + return 0; + } + return d->m_cGroups.count(); +} + +QModelIndex CGroupDataModel::index(int row, int column, const QModelIndex &parent) const +{ + if (row < 0 || row >= d->m_cGroups.count()) { + return QModelIndex(); + } + if (parent.isValid()) { + return QModelIndex(); + } + return createIndex(row, column, d->m_cGroups.at(row)); +} + +QModelIndex CGroupDataModel::parent(const QModelIndex &child) const +{ + Q_UNUSED(child) + return QModelIndex(); +} + +int CGroupDataModel::columnCount(const QModelIndex &parent) const +{ + if (parent.isValid()) { + return 0; + } + + return d->m_enabledAttributes.count(); +} + +QStringList CGroupDataModel::availableAttributes() const +{ + return d->m_availableAttributes.keys(); +} + +QStringList CGroupDataModel::enabledAttributes() const +{ + QStringList rc; + rc.reserve(d->m_enabledAttributes.size()); + for (auto attr: d->m_enabledAttributes) { + rc << attr->id(); + } + return rc; +} + +void CGroupDataModel::setEnabledAttributes(const QStringList &enabledAttributes) +{ + beginResetModel(); + + QVector unusedAttributes = d->m_enabledAttributes; + d->m_enabledAttributes.clear(); + + for (auto attribute: enabledAttributes) { + auto attr = d->m_availableAttributes[attribute]; + if (!attr) { + qWarning() << "Could not find attribute" << attribute; + continue; + } + unusedAttributes.removeOne(attr); + d->m_enabledAttributes << attr; + int columnIndex = d->m_enabledAttributes.count() - 1; + + // reconnect as using the attribute in the lambda makes everything super fast + disconnect(attr, &KSysGuard::ProcessAttribute::dataChanged, this, nullptr); + connect(attr, &KSysGuard::ProcessAttribute::dataChanged, this, [this, columnIndex](KSysGuard::Process *process) { + auto cgroup = d->m_cgroupMap.value(process->cGroup()); + qDebug() << "DATA CHANGED" << process->cGroup(); + if (!cgroup) { + return; + } + qDebug() << "found"; + const QModelIndex index = getQModelIndex(cgroup, columnIndex); + emit dataChanged(index, index); + }); + + attr->setEnabled(true); + } + + for (auto unusedAttr: unusedAttributes) { + disconnect(unusedAttr, &KSysGuard::ProcessAttribute::dataChanged, this, nullptr); + unusedAttr->setEnabled(false); + } + + endResetModel(); + + emit enabledAttributesChanged(); +} + +QModelIndex CGroupDataModel::getQModelIndex(CGroup *cgroup, int column) const +{ + Q_ASSERT(cgroup); + int row = d->m_cGroups.indexOf(cgroup); + return index(row, column, QModelIndex()); +} + +QHash CGroupDataModel::roleNames() const +{ + QHash roles = QAbstractItemModel::roleNames(); + QMetaEnum e = ProcessDataModel::staticMetaObject.enumerator(ProcessDataModel::staticMetaObject.indexOfEnumerator("AdditionalRoles")); + + for (int i = 0; i < e.keyCount(); ++i) { + roles.insert(e.value(i), e.key(i)); + } + + return roles; +} + +QVariant CGroupDataModel::data(const QModelIndex &index, int role) const +{ + if (!index.isValid()) { + return QVariant(); + } + + if (index.column() > columnCount()) { + return QVariant(); + } + + int attr = index.column(); + auto attribute = d->m_enabledAttributes[attr]; + switch(role) { + case Qt::DisplayRole: + case ProcessDataModel::FormattedValue: { + KSysGuard::CGroup *app = reinterpret_cast< KSysGuard::CGroup* > (index.internalPointer()); + const QVariant value = attribute->cgroupData(app); + return KSysGuard::Formatter::formatValue(value, attribute->unit()); + } + case ProcessDataModel::Value: { + KSysGuard::CGroup *app = reinterpret_cast< KSysGuard::CGroup* > (index.internalPointer()); + const QVariant value = attribute->cgroupData(app); + return value; + } + case ProcessDataModel::Attribute: { + return attribute->id(); + } + case ProcessDataModel::Minimum: { + return attribute->min(); + } + case ProcessDataModel::Maximum: { + return attribute->max(); + } + case ProcessDataModel::ShortName: { + if (!attribute->shortName().isEmpty()) { + return attribute->shortName(); + } + return attribute->shortName(); + } + case ProcessDataModel::Name: { + return attribute->name(); + } + case ProcessDataModel::Unit: { + return attribute->unit(); + } + case ProcessDataModel::PIDs: { + KSysGuard::CGroup *app = reinterpret_cast< KSysGuard::CGroup* > (index.internalPointer()); + QVariantList pidList; + std::transform(app->processes().constBegin(), app->processes().constEnd(), std::back_inserter(pidList), [](Process* process) -> QVariant { + return QVariant::fromValue(process->pid()); + }); + return pidList; + } + } + return QVariant(); +} + +QVariant CGroupDataModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + if (orientation == Qt::Vertical) { + return QVariant(); + } + + if (section < 0 || section >= columnCount()) { + return QVariant(); + } + + auto attribute = d->m_enabledAttributes[section]; + + switch (role) { + case Qt::DisplayRole: + case ProcessDataModel::ShortName: { + if (!attribute->shortName().isEmpty()) { + return attribute->shortName(); + } + return attribute->shortName(); + } + case ProcessDataModel::Name: + return attribute->name(); + case ProcessDataModel::Value: + case ProcessDataModel::Attribute: { + return attribute->id(); + } + case ProcessDataModel::Unit: { + auto attribute = d->m_enabledAttributes[section]; + return attribute->unit(); + } + case ProcessDataModel::Minimum: { + return attribute->min(); + } + case ProcessDataModel::Maximum: { + return attribute->max(); + } + default: + break; + } + + return QVariant(); +} + +ProcessAttributeModel *CGroupDataModel::attributesModel() +{ + //lazy load + if (!d->m_attributeModel) { + d->m_attributeModel = new KSysGuard::ProcessAttributeModel(d->m_processes, this); + } + return d->m_attributeModel; +} + +bool CGroupDataModel::isEnabled() const +{ + return d->m_updateTimer->isActive(); +} + +void CGroupDataModel::setEnabled(bool enabled) +{ + if (enabled) { + d->m_updateTimer->start(); + } else { + d->m_updateTimer->stop(); + } +} + +QString CGroupDataModel::root() const +{ + return d->m_root; +} + +void CGroupDataModel::setRoot(const QString &root) +{ + if (root == d->m_root) { + return; + } + d->m_root = root; + d->m_rootGroup.reset(new CGroup(root)); + emit rootChanged(); + update(); +} + +void CGroupDataModel::update() +{ + if (!d->m_rootGroup) { + return; + } + + d->m_oldGroups = d->m_cgroupMap; + + // In an ideal world we would only the relevant process + // but Ksysguard::Processes doesn't handle that very well + d->m_processes->updateAllProcesses(); + + update(d->m_rootGroup.data()); + + for (auto c: d->m_oldGroups) { + int row = d->m_cGroups.indexOf(c); + if (row >= 0) { + beginRemoveRows(QModelIndex(), row, row); + d->m_cGroups.removeOne(c); + endRemoveRows(); + } + d->m_cgroupMap.remove(c->id()); + delete c; + } +} + +bool CGroupDataModel::filterAcceptsCGroup(const QString &id) +{ + return id.endsWith(QLatin1String(".service")) || id.endsWith(QLatin1String(".scope")); +} + +void CGroupDataModel::update(CGroup *node) +{ + const QString path = CGroup::cgroupSysBasePath() + node->id(); + QDir dir(path); + if (!dir.exists()) { + return; + } + + // Update our own stat info + // This may trigger some dataChanged + const QVector pids = node->getPids(); + QVector processes; + for (const pid_t pid: qAsConst(pids)) { + auto proc = d->m_processes->getProcess(pid); + if (proc) { // as potentially this is racey with when kprocess fetched data + processes << proc; + } + } + node->setProcesses(processes); + + for (auto subDir: dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot)) { + const QString childId = node->id() + QLatin1Char('/') + subDir; + if (!d->m_cgroupMap.contains(childId)) { + auto childNode = new CGroup(childId); + qDebug() << childId; + d->m_cgroupMap[childNode->id()] = childNode; + + if (filterAcceptsCGroup(childId)) { + int row = d->m_cGroups.count(); + beginInsertRows(QModelIndex(), row, row); + d->m_cGroups.append(childNode); + endInsertRows(); + } + update(childNode); + } else { + update(d->m_cgroupMap[childId]); + } + d->m_oldGroups.remove(childId); + } +} diff --git a/processcore/cgroup_data_model.h b/processcore/cgroup_data_model.h new file mode 100644 index 0000000..3ab2d05 --- /dev/null +++ b/processcore/cgroup_data_model.h @@ -0,0 +1,90 @@ +/* + Copyright (c) 2019 David Edmundson + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#pragma once + +#include + +#include "process_attribute_model.h" + +namespace KSysGuard { + +class CGroup; + +class CGroupDataModelPrivate; + +/** + * @brief The CGroupDataModel class is a list model of all cgroups from a given root + * Data is exposed as per ProcessDataModel with configurable columns + */ +class Q_DECL_EXPORT CGroupDataModel : public QAbstractItemModel +{ + Q_OBJECT + Q_PROPERTY(QStringList availableAttributes READ availableAttributes CONSTANT) + Q_PROPERTY(QStringList enabledAttributes READ enabledAttributes WRITE setEnabledAttributes NOTIFY enabledAttributesChanged) + Q_PROPERTY(QObject* attributesModel READ attributesModel CONSTANT) + Q_PROPERTY(bool enabled READ isEnabled WRITE setEnabled NOTIFY enabledChanged) + + Q_PROPERTY(QString setRoot READ root WRITE setRoot NOTIFY rootChanged) + +public: + CGroupDataModel(QObject *parent = nullptr); + ~CGroupDataModel(); + + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + int columnCount(const QModelIndex &parent = QModelIndex()) const override; + + QVariant data(const QModelIndex &index, int role) const override; + QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; + + QModelIndex index(int row, int column, const QModelIndex &parent) const override; + QModelIndex parent(const QModelIndex &child) const override; + + QStringList availableAttributes() const; + QStringList enabledAttributes() const; + void setEnabledAttributes(const QStringList &enabledAttributes); + + QModelIndex getQModelIndex(CGroup *cgroup, int column) const; + QHash roleNames() const override; + + ProcessAttributeModel *attributesModel(); + + bool isEnabled() const; + void setEnabled(bool isEnabled); + + QString root() const; + void setRoot(const QString &root); + + void update(); + + +Q_SIGNALS: + void enabledAttributesChanged(); + void enabledChanged(); + void rootChanged(); + +protected: + virtual bool filterAcceptsCGroup(const QString &id); + +private: + QScopedPointer d; + void update(CGroup *node); +}; + +} diff --git a/processcore/cgroups.cpp b/processcore/cgroups.cpp new file mode 100644 index 0000000..12514ec --- /dev/null +++ b/processcore/cgroups.cpp @@ -0,0 +1,68 @@ +/* + Copyright (c) 2019 David Edmundson + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include "cgroups.h" +#include "cgroup.h" + +#include +#include + +#include + +#include "process.h" +#include "processes.h" + +namespace KSysGuard +{ +class CGroupsPrivate +{ +public: + CGroupsPrivate(CGroups *_q); + +}; +} + +using namespace KSysGuard; + +CGroups::CGroups(KSysGuard::Processes *processList, QObject *parent) + : QObject(parent) + , d(new CGroupsPrivate(this)) +{ + +} + +CGroups::~CGroups() +{ + qDeleteAll(d->appMap); +} + +CGroup *KSysGuard::CGroups::getApplication(const QString &id) const +{ + return d->appMap.value(id); +} + +QList CGroups::getAllApplications() const +{ + return d->apps; +} + +CGroupsPrivate::CGroupsPrivate(CGroups *_q) + :q(_q) +{} + diff --git a/processcore/cgroups.h b/processcore/cgroups.h new file mode 100644 index 0000000..fd790d4 --- /dev/null +++ b/processcore/cgroups.h @@ -0,0 +1,65 @@ +/* + Copyright (c) 2019 David Edmundson + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#pragma once + +#include + +namespace KSysGuard +{ +class CGroup; +class Processes; + +class CGroupsPrivate; + +class Q_DECL_EXPORT CGroups : public QObject +{ + Q_OBJECT +public: + CGroups(Processes *processList, QObject *parent = nullptr); + ~CGroups() override; + + CGroup *getApplication(const QString &id) const; + QList getAllApplications() const; + +Q_SIGNALS: + /** + * Emitted when a process has been added or removed from an application + */ + void applicationChanged(CGroup *); + /** + * Emitted when the process data within an application has changed + */ + void applicationDataChanged(CGroup *); + /** + * A new process has been added with a given application ID + */ + void beginApplicationAdded(CGroup *); + + void endApplicationAdded(CGroup *); + /** + * No processes now have the given application ID + */ + void beginApplicationRemoved(CGroup *); + void endApplicationRemoved(); + +private: + QScopedPointer d; +}; +} diff --git a/processcore/declarative/ProcessPlugin.cpp b/processcore/declarative/ProcessPlugin.cpp index bb6a3c8..1e917e5 100644 --- a/processcore/declarative/ProcessPlugin.cpp +++ b/processcore/declarative/ProcessPlugin.cpp @@ -1,47 +1,50 @@ /* Copyright (C) 2020 Arjen Hiemstra This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "ProcessPlugin.h" #include +#include "application_data_model.h" #include "process_controller.h" #include "process_data_model.h" #include "process_attribute_model.h" #include "ProcessEnums.h" using namespace KSysGuard; void ProcessPlugin::registerTypes(const char *uri) { Q_ASSERT(QLatin1String(uri) == QLatin1String("org.kde.ksysguard.process")); qRegisterMetaType(); qRegisterMetaType(); qRegisterMetaType(); qRegisterMetaType(); qRegisterMetaType(); qmlRegisterType(uri, 1, 0, "ProcessController"); qmlRegisterUncreatableMetaObject(KSysGuardProcess::staticMetaObject, uri, 1, 0, "Process", QStringLiteral("Contains process enums")); qmlRegisterType(uri, 1, 0, "ProcessDataModel"); qmlRegisterUncreatableType(uri, 1, 0, "ProcessAttributeModel", QStringLiteral("Available through ProcessDataModel")); + qmlRegisterType(uri, 1, 0, "ApplicationDataModel"); + } diff --git a/processcore/process_attribute.cpp b/processcore/process_attribute.cpp index 519032b..c869982 100644 --- a/processcore/process_attribute.cpp +++ b/processcore/process_attribute.cpp @@ -1,164 +1,173 @@ /* Copyright (c) 2019 David Edmundson This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "process_attribute.h" #include "processes.h" +#include "cgroup.h" using namespace KSysGuard; class Q_DECL_HIDDEN KSysGuard::ProcessAttribute::Private { public: QString m_id; QString m_name; QString m_shortName; QString m_description; qreal m_min = 0; qreal m_max = 0; KSysGuard::Unit m_unit = KSysGuard::UnitInvalid; //Both a format hint and implies data type (i.e double/string) QHash m_data; bool m_enabled = false; bool m_defaultVisible = false; }; ProcessAttribute::ProcessAttribute(const QString &id, QObject *parent) : ProcessAttribute(id, QString(), parent) { } ProcessAttribute::ProcessAttribute(const QString &id, const QString &name, QObject *parent) : QObject(parent) , d(new Private) { d->m_id = id; d->m_name = name; } ProcessAttribute::~ProcessAttribute() { } QString ProcessAttribute::id() const { return d->m_id; } bool ProcessAttribute::enabled() const { return d->m_enabled; } void ProcessAttribute::setEnabled(const bool enabled) { if (d->m_enabled == enabled) { return; } d->m_enabled = enabled; emit enabledChanged(enabled); } QString ProcessAttribute::name() const { return d->m_name; } void ProcessAttribute::setName(const QString &name) { d->m_name = name; } QString ProcessAttribute::shortName() const { return d->m_shortName.isEmpty() ? d->m_name : d->m_shortName; } void ProcessAttribute::setShortName(const QString &name) { d->m_shortName = name; } QString ProcessAttribute::description() const { return d->m_description; } void ProcessAttribute::setDescription(const QString &description) { d->m_description = description; } qreal ProcessAttribute::min() const { return d->m_min; } void ProcessAttribute::setMin(const qreal min) { d->m_min = min; } qreal ProcessAttribute::max() const { return d->m_max; } void ProcessAttribute::setMax(const qreal max) { d->m_max = max; } KSysGuard::Unit ProcessAttribute::unit() const { return d->m_unit; } void ProcessAttribute::setUnit(KSysGuard::Unit unit) { d->m_unit = unit; } bool KSysGuard::ProcessAttribute::isVisibleByDefault() const { return d->m_defaultVisible; } void KSysGuard::ProcessAttribute::setVisibleByDefault(bool visible) { d->m_defaultVisible = visible; } QVariant ProcessAttribute::data(KSysGuard::Process *process) const { return d->m_data.value(process); } void ProcessAttribute::setData(KSysGuard::Process *process, const QVariant &value) { d->m_data[process] = value; emit dataChanged(process); } void ProcessAttribute::clearData(KSysGuard::Process *process) { d->m_data.remove(process); emit dataChanged(process); } + +QVariant ProcessAttribute::cgroupData(KSysGuard::CGroup *cgroup) const +{ + qreal total = std::accumulate(cgroup->processes().constBegin(), cgroup->processes().constEnd(), 0.0, [this](qreal total, KSysGuard::Process *process) { + return total + data(process).toDouble(); + }); + return QVariant(total); +} diff --git a/processcore/process_attribute.h b/processcore/process_attribute.h index 7f63ccf..e04dec6 100644 --- a/processcore/process_attribute.h +++ b/processcore/process_attribute.h @@ -1,120 +1,123 @@ /* Copyright (c) 2019 David Edmundson This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #pragma once #include #include #include "unit.h" namespace KSysGuard { class Process; +class CGroup; class Q_DECL_EXPORT ProcessAttribute : public QObject { Q_OBJECT public: ProcessAttribute(const QString &id, QObject *parent); ProcessAttribute(const QString &id, const QString &name, QObject *parent); ~ProcessAttribute() override; /** * A unique non-translatable ID for this attribute. For saving in config files */ QString id() const; /** * Controls whether we should fetch process attributes */ bool enabled() const; void setEnabled(const bool enable); /** * A translated user facing name for the attribute. * e.g "Download Speed" */ QString name() const; void setName(const QString &name); /** * A translated shorter version of the name * for use in table column headers for example * e.g "D/L" * If unset, name is returned */ QString shortName() const; void setShortName(const QString &name); /** * A translated human readable description of this attribute */ QString description() const; void setDescription(const QString &description); /** * The minimum value possible for this sensor * (i.e to show a CPU is between 0 and 100) * Set min and max to 0 if not relevant */ qreal min() const; void setMin(const qreal min); /** * The maximum value possible for this attribute */ qreal max() const; void setMax(const qreal max); KSysGuard::Unit unit() const; void setUnit(KSysGuard::Unit unit); /** * A hint to UIs that this sensor would like to be visible by default. * * Defaults to false. */ bool isVisibleByDefault() const; void setVisibleByDefault(bool visible); /** * The last stored value for a given process */ virtual QVariant data(KSysGuard::Process *process) const; /** * Updates the stored value for a given process * Note stray processes will be automatically expunged */ void setData(KSysGuard::Process *process, const QVariant &value); /** * Remove an attribute from our local cache */ void clearData(KSysGuard::Process *process); + virtual QVariant cgroupData(KSysGuard::CGroup *cgroup) const; + Q_SIGNALS: void dataChanged(KSysGuard::Process *process); void enabledChanged(bool enabled); private: class Private; QScopedPointer d; }; }