diff --git a/CMakeLists.txt b/CMakeLists.txt --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -64,7 +64,7 @@ VARIABLE_PREFIX KSYSGUARD VERSION_HEADER "${CMAKE_CURRENT_BINARY_DIR}/ksysguard_version.h" PACKAGE_VERSION_FILE "${CMAKE_CURRENT_BINARY_DIR}/KF5SysGuardConfigVersion.cmake" - SOVERSION 8 + SOVERSION 9 ) find_package(X11) diff --git a/processcore/CMakeLists.txt b/processcore/CMakeLists.txt --- a/processcore/CMakeLists.txt +++ b/processcore/CMakeLists.txt @@ -12,6 +12,8 @@ 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) @@ -55,6 +57,8 @@ 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 diff --git a/processcore/extended_process_list.h b/processcore/extended_process_list.h --- a/processcore/extended_process_list.h +++ b/processcore/extended_process_list.h @@ -33,6 +33,7 @@ ~ExtendedProcesses() override; QVector attributes() const; + QVector extendedAttributes() const; private: class Private; diff --git a/processcore/extended_process_list.cpp b/processcore/extended_process_list.cpp --- a/processcore/extended_process_list.cpp +++ b/processcore/extended_process_list.cpp @@ -18,12 +18,15 @@ */ #include "extended_process_list.h" -#include +#include #include +#include #include +#include -#include "process_data_provider.h" +#include "process.h" #include "process_attribute.h" +#include "process_data_provider.h" #include "processcore_debug.h" using namespace KSysGuard; @@ -35,7 +38,46 @@ void loadPlugins(); ExtendedProcesses *q; + QVector m_coreAttributes; QVector m_providers; + QHash m_userCache; +}; + +enum GroupPolicy { + Accumulate, + Average, + ForwardFirstEntry +}; + +template +class ProcessSensor : public KSysGuard::ProcessAttribute +{ +public: + ProcessSensor(ExtendedProcesses *parent, const QString &id, const QString &name, std::function extractFunc, KSysGuard::Process::Change changeFlag = KSysGuard::Process::Nothing, GroupPolicy groupPolicy = Accumulate) + : KSysGuard::ProcessAttribute(id, name, parent) + , m_extractFunc(extractFunc) + , m_changeFlag(changeFlag) + , m_groupPolicy(groupPolicy) + { + if (m_changeFlag != 0) { + connect(parent, &ExtendedProcesses::processChanged, this, [this](KSysGuard::Process *process) { + if (!process->changes().testFlag(m_changeFlag)) { + return; + } + emit dataChanged(process); + }); + } + } + + QVariant data(KSysGuard::Process *process) const override + { + return QVariant::fromValue(m_extractFunc(process)); + } + +private: + std::function m_extractFunc; + KSysGuard::Process::Change m_changeFlag; + GroupPolicy m_groupPolicy = Accumulate; }; ExtendedProcesses::Private::Private(ExtendedProcesses *_q) @@ -49,6 +91,283 @@ { d->loadPlugins(); + auto pidSensor = new ProcessSensor(this, QStringLiteral("pid"), i18n("PID"), &KSysGuard::Process::pid, KSysGuard::Process::Status, ForwardFirstEntry); + pidSensor->setDescription(i18n("The unique Process ID that identifies this process.")); + d->m_coreAttributes << pidSensor; + + auto parentPidSensor = new ProcessSensor(this, QStringLiteral("parentPid"), i18n("Parent PID"), &KSysGuard::Process::parentPid, Process::Nothing, ForwardFirstEntry); + d->m_coreAttributes << parentPidSensor; + + auto loginSensor = new ProcessSensor(this, QStringLiteral("login"), i18n("Login"), &KSysGuard::Process::login, KSysGuard::Process::Login, ForwardFirstEntry); + loginSensor->setDescription(i18n("The user who owns this process.")); + d->m_coreAttributes << loginSensor; + + auto uidSensor = new ProcessSensor(this, QStringLiteral("uid"), i18n("UID"), &KSysGuard::Process::uid, KSysGuard::Process::Uids, ForwardFirstEntry); + d->m_coreAttributes << uidSensor; + + auto userNameSensor = new ProcessSensor( + this, QStringLiteral("username"), i18n("Username"), [this](KSysGuard::Process *p) { + const K_UID uid = p->uid(); + auto userIt = d->m_userCache.constFind(uid); + if (userIt == d->m_userCache.constEnd()) { + userIt = d->m_userCache.insert(uid, KUser(uid)); + } + return userIt->loginName(); + }, + KSysGuard::Process::Uids, ForwardFirstEntry); + d->m_coreAttributes << userNameSensor; + + auto canUserLoginSensor = new ProcessSensor( + this, QStringLiteral("canUserLogin"), i18n("Can Login"), [this](KSysGuard::Process *p) { + const K_UID uid = p->uid(); + if (uid == 65534) { // special value meaning nobody + return false; + } + auto userIt = d->m_userCache.constFind(uid); + if (userIt == d->m_userCache.constEnd()) { + userIt = d->m_userCache.insert(uid, KUser(uid)); + } + + if (!userIt->isValid()) { + // For some reason the user isn't recognised. This might happen under certain security situations. + // Just return true to be safe + return true; + } + const QString shell = userIt->shell(); + if (shell == QLatin1String("/bin/false")) { //FIXME - add in any other shells it could be for false + return false; + } + return true; + }, + KSysGuard::Process::Uids, ForwardFirstEntry); + d->m_coreAttributes << canUserLoginSensor; + + auto euidSensor = new ProcessSensor(this, QStringLiteral("euid"), i18n("EUID"), &KSysGuard::Process::euid, KSysGuard::Process::Uids, ForwardFirstEntry); + d->m_coreAttributes << euidSensor; + + auto suidSensor = new ProcessSensor(this, QStringLiteral("suid"), + i18n("suid"), &KSysGuard::Process::suid, KSysGuard::Process::Uids, ForwardFirstEntry); + d->m_coreAttributes << suidSensor; + + auto fsuidSensor = new ProcessSensor(this, QStringLiteral("fsuid"), i18n("fsuid"), &KSysGuard::Process::fsuid, KSysGuard::Process::Uids, ForwardFirstEntry); + d->m_coreAttributes << fsuidSensor; + + auto gidSensor = new ProcessSensor(this, QStringLiteral("gid"), i18n("gid"), &KSysGuard::Process::gid, KSysGuard::Process::Gids, ForwardFirstEntry); + d->m_coreAttributes << gidSensor; + + auto egidSensor = new ProcessSensor(this, QStringLiteral("egid"), i18n("egid"), &KSysGuard::Process::egid, KSysGuard::Process::Gids, ForwardFirstEntry); + d->m_coreAttributes << egidSensor; + + auto sgidSensor = new ProcessSensor(this, QStringLiteral("sgid"), i18n("sgid"), &KSysGuard::Process::sgid, KSysGuard::Process::Gids, ForwardFirstEntry); + d->m_coreAttributes << sgidSensor; + + auto fsgidSensor = new ProcessSensor(this, QStringLiteral("fsgid"), i18n("fsgid"), &KSysGuard::Process::fsgid, KSysGuard::Process::Gids, ForwardFirstEntry); + d->m_coreAttributes << fsgidSensor; + + auto tracerpidSensor = new ProcessSensor(this, QStringLiteral("tracerpid"), i18n("Tracer Pid"), &KSysGuard::Process::tracerpid, Process::Nothing, ForwardFirstEntry); + d->m_coreAttributes << tracerpidSensor; + + auto ttySensor = new ProcessSensor(this, QStringLiteral("tty"), i18n("tty"), &KSysGuard::Process::tty, KSysGuard::Process::Tty, ForwardFirstEntry); + ttySensor->setDescription(i18n("The controlling terminal on which this process is running.")); + d->m_coreAttributes << ttySensor; + + auto userTimeSensor = new ProcessSensor(this, QStringLiteral("userTime"), i18n("User Time"), &KSysGuard::Process::userTime); + d->m_coreAttributes << userTimeSensor; + + auto sysTimeSensor = new ProcessSensor(this, QStringLiteral("sysTime"), i18n("System Time"), &KSysGuard::Process::sysTime); + sysTimeSensor->setUnit(KSysGuard::UnitSecond); + d->m_coreAttributes << sysTimeSensor; + + auto timeSensor = new ProcessSensor( + this, QStringLiteral("totalUsage"), i18n("Total Time"), [](KSysGuard::Process *p) { + return p->userTime() + p->sysTime(); + }, + KSysGuard::Process::Usage); + timeSensor->setShortName(i18n("Time")); + timeSensor->setUnit(KSysGuard::UnitSecond); + timeSensor->setDescription(i18n("The total user and system time that this process has been running for")); + d->m_coreAttributes << timeSensor; + + auto startTimeSensor = new ProcessSensor(this, QStringLiteral("startTime"), i18n("Start Time"), &KSysGuard::Process::startTime, Process::Nothing, ForwardFirstEntry); //Is this correct for apps? + startTimeSensor->setDescription(i18n("The elapsed time since the process was started.")); + startTimeSensor->setUnit(KSysGuard::UnitTime); + d->m_coreAttributes << startTimeSensor; + + auto userUsageSensor = new ProcessSensor(this, QStringLiteral("userUsage"), i18n("User CPU Usage"), &KSysGuard::Process::userUsage, KSysGuard::Process::Usage); + userUsageSensor->setShortName(i18n("User CPU")); + userUsageSensor->setMin(0); + userUsageSensor->setMax(100); + userUsageSensor->setUnit(KSysGuard::UnitPercent); + d->m_coreAttributes << userUsageSensor; + + auto sysUsageSensor = new ProcessSensor(this, QStringLiteral("sysUsage"), i18n("System CPU Usage"), &KSysGuard::Process::sysUsage, KSysGuard::Process::Usage); + sysUsageSensor->setShortName(i18n("System CPU")); + sysUsageSensor->setMin(0); + sysUsageSensor->setMax(100); + sysUsageSensor->setUnit(KSysGuard::UnitPercent); + d->m_coreAttributes << sysUsageSensor; + + auto usageSensor = new ProcessSensor( + this, QStringLiteral("usage"), i18n("Total CPU Usage"), [](KSysGuard::Process *p) { + return p->userUsage() + p->sysUsage(); + }, + KSysGuard::Process::Usage, Accumulate); + usageSensor->setShortName(i18n("CPU")); + usageSensor->setMin(0); + usageSensor->setMax(100); + usageSensor->setUnit(KSysGuard::UnitPercent); + usageSensor->setDescription(i18n("The current total CPU usage of the process.")); + d->m_coreAttributes << usageSensor; + + auto totalUserUsageSensor = new ProcessSensor(this, QStringLiteral("totalUserUsage"), i18n("Group User CPU Usage"), &KSysGuard::Process::totalUserUsage, KSysGuard::Process::TotalUsage, Average); + totalUserUsageSensor->setDescription(i18n("The amount of userspace CPU used by this process and all its children.")); + totalUserUsageSensor->setMin(0); + totalUserUsageSensor->setMax(100); + totalUserUsageSensor->setUnit(KSysGuard::UnitPercent); + d->m_coreAttributes << totalUserUsageSensor; + + auto totalSysUsageSensor = new ProcessSensor(this, QStringLiteral("totalSysUsage"), i18n("Group System CPU Usage"), &KSysGuard::Process::totalSysUsage, KSysGuard::Process::TotalUsage, Average); + totalUserUsageSensor->setDescription(i18n("The amount of system CPU used by this process and all its children.")); + totalSysUsageSensor->setMin(0); + totalSysUsageSensor->setMax(100); + totalSysUsageSensor->setUnit(KSysGuard::UnitPercent); + d->m_coreAttributes << totalSysUsageSensor; + + auto totalUsageSensor = new ProcessSensor( + this, QStringLiteral("totalUsage"), i18n("Group Total CPU Usage"), [](KSysGuard::Process *p) { + return p->totalUserUsage() + p->totalSysUsage(); + }, + KSysGuard::Process::TotalUsage, Average); + totalUsageSensor->setShortName(i18n("Group CPU")); + totalUserUsageSensor->setDescription(i18n("The total amount of CPU used by this process and all its children.")); + totalUsageSensor->setMin(0); + totalUsageSensor->setMax(100); + totalUsageSensor->setUnit(KSysGuard::UnitPercent); + d->m_coreAttributes << totalUsageSensor; + + auto niceLevelSensor = new ProcessSensor(this, QStringLiteral("niceLevel"), i18n("Nice Level"), &KSysGuard::Process::niceLevel, KSysGuard::Process::NiceLevels); + niceLevelSensor->setDescription(i18n("The priority with which this process is being run. For the normal scheduler, this ranges from 19 (very nice, least priority) to -19 (top priority).")); + d->m_coreAttributes << niceLevelSensor; + + auto schedulerSensor = new ProcessSensor(this, QStringLiteral("scheduler"), i18n("Scheduler"), &KSysGuard::Process::scheduler, KSysGuard::Process::NiceLevels); + d->m_coreAttributes << schedulerSensor; + + auto ioPriorityClassSensor = new ProcessSensor(this, QStringLiteral("ioPriorityClass"), i18n("IO Priority Class"), + &KSysGuard::Process::ioPriorityClass, KSysGuard::Process::NiceLevels); + d->m_coreAttributes << ioPriorityClassSensor; + + auto ioniceLevelSensor = new ProcessSensor(this, QStringLiteral("ioniceLevel"), i18n("IO Nice Level"), &KSysGuard::Process::ioniceLevel, KSysGuard::Process::NiceLevels); + ioniceLevelSensor->setUnit(KSysGuard::UnitNone); + d->m_coreAttributes << ioniceLevelSensor; + + auto vmSizeSensor = new ProcessSensor(this, QStringLiteral("vmSize"), i18n("VM Size"), &KSysGuard::Process::vmSize, KSysGuard::Process::VmSize); + vmSizeSensor->setUnit(KSysGuard::UnitKiloByte); + vmSizeSensor->setMin(0); + vmSizeSensor->setMax(totalPhysicalMemory()); + vmSizeSensor->setDescription(i18n("This is the amount of virtual memory space that the process is using, included shared libraries, graphics memory, files on disk, and so on. This number is almost meaningless.")); + d->m_coreAttributes << vmSizeSensor; + + auto vmRSSSensor = new ProcessSensor(this, QStringLiteral("vmRSS"), i18n("RSS Memory Usage"), &KSysGuard::Process::vmRSS, KSysGuard::Process::VmRSS); + vmRSSSensor->setUnit(KSysGuard::UnitKiloByte); + vmRSSSensor->setMin(0); + vmRSSSensor->setMax(totalPhysicalMemory()); + vmRSSSensor->setDescription(i18n("This is the amount of physical memory that this process is using and includes the amount of memory used by shared libraries.")); + + auto vmURSSSensor = new ProcessSensor(this, QStringLiteral("vmURSS"), i18n("Private Memory Usage"), &KSysGuard::Process::vmURSS, KSysGuard::Process::VmURSS); + vmURSSSensor->setUnit(KSysGuard::UnitKiloByte); + vmURSSSensor->setShortName(i18n("Private")); + vmURSSSensor->setMin(0); + vmURSSSensor->setMax(totalPhysicalMemory()); + vmURSSSensor->setDescription(i18n("This is the amount of physical memory that this process is using by itself, and approximates the Private memory usage of the process.
It does not include any swapped out memory, nor the code size of its shared libraries.")); + d->m_coreAttributes << vmURSSSensor; + + auto sharedMemorySensor = new ProcessSensor( + this, QStringLiteral("vmShared"), i18n("Shared Memory Usage"), [](KSysGuard::Process *p) -> qlonglong { + if (p->vmRSS() - p->vmURSS() < 0 || p->vmURSS() == -1) { + return 0; + } + return (qlonglong)(p->vmRSS() - p->vmURSS()); + }, + KSysGuard::Process::VmRSS); + d->m_coreAttributes << sharedMemorySensor; + sharedMemorySensor->setShortName(i18n("Shared")); + sharedMemorySensor->setDescription(i18n("This is approximately the amount of real physical memory that this process's shared libraries are using.
This memory is shared among all processes that use this library.")); + sharedMemorySensor->setUnit(KSysGuard::UnitKiloByte); + sharedMemorySensor->setMin(0); + sharedMemorySensor->setMax(totalPhysicalMemory()); + + auto vmPSSSensor = new ProcessSensor(this, QStringLiteral("vmPSS"), i18n("Memory Usage"), &KSysGuard::Process::vmPSS, KSysGuard::Process::VmPSS); + vmPSSSensor->setShortName(i18n("Memory")); + vmPSSSensor->setUnit(KSysGuard::UnitKiloByte); + vmPSSSensor->setMin(0); + vmPSSSensor->setMax(totalPhysicalMemory()); + vmPSSSensor->setDescription(i18n("This is an approximation of the real amount of physical memory that this process is using. It is calculated by dividing the process' shared memory usage by the amount of processes sharing that memory, then adding the process' private memory.")); + d->m_coreAttributes << vmPSSSensor; + + auto nameSensor = new ProcessSensor(this, QStringLiteral("name"), i18n("Name"), &KSysGuard::Process::name, KSysGuard::Process::Name, ForwardFirstEntry); + nameSensor->setDescription(i18n("The process name.")); + d->m_coreAttributes << nameSensor; + + auto commandSensor = new ProcessSensor(this, QStringLiteral("command"), i18n("Command"), &KSysGuard::Process::command, KSysGuard::Process::Command, ForwardFirstEntry); + commandSensor->setDescription(i18n("The command with which this process was launched.")); + d->m_coreAttributes << commandSensor; + + auto statusSensor = new ProcessSensor(this, QStringLiteral("status"), i18n("Status"), &KSysGuard::Process::status, KSysGuard::Process::Status); + d->m_coreAttributes << statusSensor; + + auto ioCharactersReadSensor = new ProcessSensor(this, QStringLiteral("ioCharactersRead"), i18n("IO Characters Read"), &KSysGuard::Process::ioCharactersRead, KSysGuard::Process::IO); + ioCharactersReadSensor->setUnit(KSysGuard::UnitByte); + d->m_coreAttributes << ioCharactersReadSensor; + + auto ioCharactersWrittenSensor = new ProcessSensor(this, QStringLiteral("ioCharactersWritten"), i18n("IO Characters Written"), &KSysGuard::Process::ioCharactersWritten, KSysGuard::Process::IO); + ioCharactersWrittenSensor->setUnit(KSysGuard::UnitByte); + d->m_coreAttributes << ioCharactersWrittenSensor; + + auto ioReadSyscallsSensor = new ProcessSensor(this, QStringLiteral("ioReadSyscalls"), i18n("IO Read Syscalls"), &KSysGuard::Process::ioReadSyscalls, KSysGuard::Process::IO); + ioReadSyscallsSensor->setUnit(KSysGuard::UnitRate); + d->m_coreAttributes << ioReadSyscallsSensor; + + auto ioReadSyscallsRateSensor = new ProcessSensor(this, QStringLiteral("ioReadSyscallsRate"), i18n("IO Read Syscalls Rate"), &KSysGuard::Process::ioReadSyscallsRate, KSysGuard::Process::IO); + ioReadSyscallsRateSensor->setUnit(KSysGuard::UnitRate); + d->m_coreAttributes << ioReadSyscallsSensor; + + auto ioWriteSyscallsSensor = new ProcessSensor(this, QStringLiteral("ioWriteSyscalls"), i18n("IO Write Syscalls"), &KSysGuard::Process::ioWriteSyscalls, KSysGuard::Process::IO); + ioWriteSyscallsSensor->setUnit(KSysGuard::UnitRate); + d->m_coreAttributes << ioWriteSyscallsSensor; + + auto ioWriteSyscallsRateSensor = new ProcessSensor(this, QStringLiteral("ioReadSyscallsRate"), i18n("IO Write Syscalls Rate"), &KSysGuard::Process::ioWriteSyscallsRate, KSysGuard::Process::IO); + ioWriteSyscallsRateSensor->setUnit(KSysGuard::UnitRate); + d->m_coreAttributes << ioWriteSyscallsRateSensor; + + auto ioCharactersActuallyReadSensor = new ProcessSensor(this, QStringLiteral("ioCharactersActuallyRead"), i18n("IO Characters Actually Read"), &KSysGuard::Process::ioCharactersActuallyRead, KSysGuard::Process::IO); + ioCharactersActuallyReadSensor->setUnit(KSysGuard::UnitByte); + d->m_coreAttributes << ioCharactersActuallyReadSensor; + + auto ioCharactersReadRateSensor = new ProcessSensor(this, QStringLiteral("ioCharactersReadRate"), i18n("IO Characters Read Rate"), &KSysGuard::Process::ioCharactersReadRate, KSysGuard::Process::IO); + ioCharactersReadRateSensor->setDescription(i18n("The read rate for all of a process' IO, including disk cache and other non-physical IO.")); + ioCharactersReadRateSensor->setUnit(KSysGuard::UnitByteRate); + d->m_coreAttributes << ioCharactersReadRateSensor; + + auto ioCharactersWrittenRateSensor = new ProcessSensor(this, QStringLiteral("ioCharactersWrittenRate"), i18n("IO Characters Written Rate"), &KSysGuard::Process::ioCharactersWrittenRate, KSysGuard::Process::IO); + ioCharactersWrittenRateSensor->setDescription(i18n("The write rate for all of a process' IO, including disk cache and other nonphysical IO.")); + ioCharactersWrittenRateSensor->setUnit(KSysGuard::UnitByteRate); + d->m_coreAttributes << ioCharactersWrittenRateSensor; + + auto ioCharactersActuallyReadRateSensor = new ProcessSensor(this, QStringLiteral("ioCharactersActuallyReadRate"), i18n("Disk Read Rate"), &KSysGuard::Process::ioCharactersActuallyReadRate, KSysGuard::Process::IO); + ioCharactersActuallyReadRateSensor->setUnit(KSysGuard::UnitByteRate); + ioCharactersActuallyReadRateSensor->setShortName(i18n("Read")); + ioCharactersActuallyReadRateSensor->setDescription(i18n("The rate of data being read from disk.")); + d->m_coreAttributes << ioCharactersActuallyReadRateSensor; + + auto ioCharactersActuallyWrittenRateSensor = new ProcessSensor(this, QStringLiteral("ioCharactersActuallyWrittenRate"), i18n("Disk Write Rate"), &KSysGuard::Process::ioCharactersActuallyWrittenRate, KSysGuard::Process::IO); + ioCharactersActuallyWrittenRateSensor->setUnit(KSysGuard::UnitByteRate); + ioCharactersActuallyWrittenRateSensor->setShortName(i18n("Write")); + ioCharactersActuallyWrittenRateSensor->setDescription(i18n("The rate of data being written to the disk.")); + d->m_coreAttributes << ioCharactersActuallyWrittenRateSensor; + + auto numThreadsSensor = new ProcessSensor(this, QStringLiteral("numThreads"), i18n("Threads"), &KSysGuard::Process::numThreads, KSysGuard::Process::NumThreads, ForwardFirstEntry); + d->m_coreAttributes << numThreadsSensor; + connect(this, &KSysGuard::Processes::beginRemoveProcess, this, [this](KSysGuard::Process *process) { const auto attrs = attributes(); for (auto a : attrs) { @@ -70,6 +389,11 @@ } QVector ExtendedProcesses::attributes() const +{ + return d->m_coreAttributes + extendedAttributes(); +} + +QVector ExtendedProcesses::extendedAttributes() const { QVector rc; for (auto p : qAsConst(d->m_providers)) { diff --git a/processcore/process_attribute.h b/processcore/process_attribute.h --- a/processcore/process_attribute.h +++ b/processcore/process_attribute.h @@ -96,7 +96,7 @@ /** * The last stored value for a given process */ - QVariant data(KSysGuard::Process *process); + virtual QVariant data(KSysGuard::Process *process) const; /** * Updates the stored value for a given process diff --git a/processcore/process_attribute.cpp b/processcore/process_attribute.cpp --- a/processcore/process_attribute.cpp +++ b/processcore/process_attribute.cpp @@ -146,7 +146,7 @@ d->m_defaultVisible = visible; } -QVariant ProcessAttribute::data(KSysGuard::Process *process) +QVariant ProcessAttribute::data(KSysGuard::Process *process) const { return d->m_data.value(process); } diff --git a/processcore/process_attribute_model.h b/processcore/process_attribute_model.h new file mode 100644 --- /dev/null +++ b/processcore/process_attribute_model.h @@ -0,0 +1,59 @@ +/* + Copyright (c) 2020 David Edmundson + 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. +*/ + +#pragma once + +#include + +namespace KSysGuard +{ + +class ExtendedProcesses; + +/** + * Presents a list of available attributes that can be + * enabled on a ProceessDataModel + */ +class Q_DECL_EXPORT ProcessAttributeModel : public QAbstractListModel +{ + Q_OBJECT +public: + enum class Role { + Name = Qt::DisplayRole, /// Human readable translated name of the attribute + Id = Qt::UserRole, /// Computer readable ID of the attribute + ShortName = Qt::UserRole + 1, /// A shorter human readable translated name of the attribute + Description, /// A longer, sentence-based description of the attribute + Unit, /// The unit, of type KSysGuard::Unit + }; + Q_ENUM(Role); + + ProcessAttributeModel(ExtendedProcesses *processes, QObject *parent = nullptr); + ~ProcessAttributeModel() override; + + int rowCount(const QModelIndex &parent) const override; + QVariant data(const QModelIndex &index, int role) const override; + QHash roleNames() const override; + +private: + class Private; + QScopedPointer d; +}; + +} diff --git a/processcore/process_attribute_model.cpp b/processcore/process_attribute_model.cpp new file mode 100644 --- /dev/null +++ b/processcore/process_attribute_model.cpp @@ -0,0 +1,87 @@ +/* + Copyright (c) 2020 David Edmundson + 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 "process_attribute_model.h" + +#include "extended_process_list.h" +#include "process_attribute.h" + +using namespace KSysGuard; + +class Q_DECL_HIDDEN ProcessAttributeModel::Private +{ +public: + QVector m_attributes; +}; + +ProcessAttributeModel::ProcessAttributeModel(ExtendedProcesses *processes, QObject *parent) + : QAbstractListModel(parent) + , d(new Private) +{ + d->m_attributes = processes->attributes(); +} + +ProcessAttributeModel::~ProcessAttributeModel() +{ +} + +int ProcessAttributeModel::rowCount(const QModelIndex &parent) const +{ + if (parent.isValid()) { + return 0; // flat list + } + return d->m_attributes.count(); +} + +QVariant ProcessAttributeModel::data(const QModelIndex &index, int role) const +{ + if (!checkIndex(index, CheckIndexOption::IndexIsValid | CheckIndexOption::ParentIsInvalid)) { + return QVariant(); + } + + auto attribute = d->m_attributes[index.row()]; + switch (static_cast(role)) { + case Role::Name: + return attribute->name(); + case Role::ShortName: + if (attribute->shortName().isEmpty()) { + return attribute->name(); + } + return attribute->shortName(); + case Role::Id: + return attribute->id(); + case Role::Description: + return attribute->description(); + case Role::Unit: + return attribute->unit(); + } + return QVariant(); +} + +QHash ProcessAttributeModel::roleNames() const +{ + return QAbstractListModel::roleNames().unite({ + { static_cast(Role::Id), "id" }, + { static_cast(Role::Name), "name" }, + { static_cast(Role::ShortName), "shortName" }, + { static_cast(Role::Description), "description" }, + { static_cast(Role::Unit), "unit" }, + }); +} diff --git a/processcore/process_data_model.h b/processcore/process_data_model.h new file mode 100644 --- /dev/null +++ b/processcore/process_data_model.h @@ -0,0 +1,107 @@ +/* + Copyright (c) 2020 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 + +namespace KSysGuard +{ + +class Process; +class ProcessAttributeModel; + +/** + * This class contains a model of all running processes + * Rows represent processes + * Columns represent a specific attribute, such as CPU usage + * Attributes can be enabled or disabled + * + * This class abstracts the process data so that it can be presented without the client + * needing to understand the semantics of each column + * It is designed to be consumable by a QML API + */ +class Q_DECL_EXPORT ProcessDataModel : public QAbstractItemModel +{ + Q_OBJECT + + Q_PROPERTY(QStringList availableAttributes READ availableAttributes CONSTANT) + Q_PROPERTY(QStringList enabledAttributes READ enabledAttributes WRITE setEnabledAttributes NOTIFY enabledAttributesChanged) + Q_PROPERTY(QAbstractItemModel *attributesModel READ attributesModel CONSTANT) + +public: + enum AdditionalRoles { + Value = Qt::UserRole, /// The raw value of the attribute. This is unformatted and could represent an int, real or string + FormattedValue, /// A string containing the value in a locale friendly way with appropriate suffix "eg. 5Mb" "20%" + + PIDs, /// The PIDs associated with this row + Minimum, /// Smallest value this reading can be in normal situations. A hint for graphing utilities + Maximum, /// Largest value this reading can be in normal situations. A hint for graphing utilities + + Attribute, /// The attribute id associated with this column + Name, /// The full name of this attribute + ShortName, /// A shorter name of this attribute, compressed for viewing + Unit, /// The unit associated with this attribute. Returned value is of the type KSysGuard::Unit + }; + Q_ENUM(AdditionalRoles) + + explicit ProcessDataModel(QObject *parent = nullptr); + ~ProcessDataModel() override; + + /** + * A list of attribute IDs that can be enabled + */ + QStringList availableAttributes() const; + + /** + * The list of available attributes that can be enabled, presented as a model + * See @availableAttributes + */ + ProcessAttributeModel *attributesModel(); + + /** + * The currently enabled attributes + */ + QStringList enabledAttributes() const; + /** + * Select which process attributes should be enabled + * The order determines the column order + * + * The default value is empty + */ + void setEnabledAttributes(const QStringList &enabledAttributes); + + QHash roleNames() const override; + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + int columnCount(const QModelIndex &parent = QModelIndex()) const override; + QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override; + QModelIndex parent(const QModelIndex &index) const override; + +Q_SIGNALS: + void enabledAttributesChanged(); + +private: + class Private; + QScopedPointer d; +}; + +} diff --git a/processcore/process_data_model.cpp b/processcore/process_data_model.cpp new file mode 100644 --- /dev/null +++ b/processcore/process_data_model.cpp @@ -0,0 +1,337 @@ +/* + Copyright (c) 2020 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_data_model.h" +#include "formatter.h" + +#include "processcore/extended_process_list.h" +#include "processcore/process.h" +#include "processcore/process_attribute.h" +#include "processcore/process_attribute_model.h" +#include "processcore/process_data_provider.h" + +#include +#include + +using namespace KSysGuard; + +class Q_DECL_HIDDEN KSysGuard::ProcessDataModel::Private +{ +public: + Private(ProcessDataModel *q); + void beginInsertRow(KSysGuard::Process *parent); + void endInsertRow(); + void beginRemoveRow(KSysGuard::Process *process); + void endRemoveRow(); + + void update(); + QModelIndex getQModelIndex(Process *process, int column) const; + + ProcessDataModel *q; + KSysGuard::ExtendedProcesses *m_processes; + QTimer *m_timer; + ProcessAttributeModel *m_attributeModel = nullptr; + const int m_updateInterval = 2000; + + QHash m_availableAttributes; + QVector m_enabledAttributes; + +}; + +ProcessDataModel::ProcessDataModel(QObject *parent) + : QAbstractItemModel(parent) + , d(new ProcessDataModel::Private(this)) +{ +} + +ProcessDataModel::~ProcessDataModel() +{ +} + +ProcessDataModel::Private::Private(ProcessDataModel *_q) + : q(_q) + , m_processes(new KSysGuard::ExtendedProcesses(_q)) + , m_timer(new QTimer(_q)) +{ + connect(m_processes, &KSysGuard::Processes::beginAddProcess, q, [this](KSysGuard::Process *process) { + beginInsertRow(process); + }); + connect(m_processes, &KSysGuard::Processes::endAddProcess, q, [this]() { + endRemoveRow(); + }); + connect(m_processes, &KSysGuard::Processes::beginRemoveProcess, q, [this](KSysGuard::Process *process) { + beginRemoveRow(process); + }); + connect(m_processes, &KSysGuard::Processes::endRemoveProcess, q, [this]() { + endRemoveRow(); + }); + + const auto attributes = m_processes->attributes(); + m_availableAttributes.reserve(attributes.count()); + for (auto attr : attributes) { + m_availableAttributes[attr->id()] = attr; + } + + connect(m_timer, &QTimer::timeout, q, [this]() { + update(); + }); + m_timer->setInterval(m_updateInterval); + m_timer->start(); +} + +QVariant ProcessDataModel::data(const QModelIndex &index, int role) const +{ + if (!checkIndex(index, QAbstractItemModel::CheckIndexOption::IndexIsValid)) { + return QVariant(); + } + + const int attr = index.column(); + auto attribute = d->m_enabledAttributes[attr]; + switch (role) { + case Qt::DisplayRole: + case FormattedValue: { + KSysGuard::Process *process = reinterpret_cast(index.internalPointer()); + const QVariant value = attribute->data(process); + return KSysGuard::Formatter::formatValue(value, attribute->unit()); + } + case Value: { + KSysGuard::Process *process = reinterpret_cast(index.internalPointer()); + const QVariant value = attribute->data(process); + return value; + } + case Attribute: { + return attribute->id(); + } + case Minimum: { + return attribute->min(); + } + case Maximum: { + return attribute->max(); + } + case ShortName: { + if (!attribute->shortName().isEmpty()) { + return attribute->shortName(); + } + return attribute->name(); + } + case Name: { + return attribute->name(); + } + case Unit: { + return attribute->unit(); + } + } + return QVariant(); +} + +int ProcessDataModel::rowCount(const QModelIndex &parent) const +{ + if (parent.isValid()) + return 0; //In flat mode, none of the processes have children + return d->m_processes->processCount(); +} + +QModelIndex ProcessDataModel::parent(const QModelIndex &index) const +{ + Q_UNUSED(index) + return QModelIndex(); +} + +QStringList ProcessDataModel::availableAttributes() const +{ + return d->m_availableAttributes.keys(); +} + +QStringList ProcessDataModel::enabledAttributes() const +{ + QStringList rc; + rc.reserve(d->m_enabledAttributes.size()); + for (auto attr : d->m_enabledAttributes) { + rc << attr->id(); + } + return rc; +} + +void ProcessDataModel::setEnabledAttributes(const QStringList &enabledAttributes) +{ + beginResetModel(); + + QVector unusedAttributes = d->m_enabledAttributes; + d->m_enabledAttributes.clear(); + + for (auto attributeId : enabledAttributes) { + auto attribute = d->m_availableAttributes[attributeId]; + if (!attribute) { + continue; + } + unusedAttributes.removeOne(attribute); + d->m_enabledAttributes << attribute; + int columnIndex = d->m_enabledAttributes.count() - 1; + + // reconnect as using the columnIndex in the lambda makes everything super fast + disconnect(attribute, &KSysGuard::ProcessAttribute::dataChanged, this, nullptr); + connect(attribute, &KSysGuard::ProcessAttribute::dataChanged, this, [this, columnIndex](KSysGuard::Process *process) { + const QModelIndex index = d->getQModelIndex(process, columnIndex); + emit dataChanged(index, index); + }); + + attribute->setEnabled(true); + } + + for (auto *unusedAttribute : qAsConst(unusedAttributes)) { + disconnect(unusedAttribute, &KSysGuard::ProcessAttribute::dataChanged, this, nullptr); + unusedAttribute->setEnabled(false); + } + + d->update(); + endResetModel(); + + emit enabledAttributesChanged(); +} + +QModelIndex ProcessDataModel::index(int row, int column, const QModelIndex &parent) const +{ + if (row < 0) { + return QModelIndex(); + } + if (column < 0 || column >= columnCount()) { + return QModelIndex(); + } + + if (parent.isValid()) { + return QModelIndex(); + } + if (row >= d->m_processes->processCount()) { + return QModelIndex(); + } + return createIndex(row, column, d->m_processes->getAllProcesses().at(row)); +} + +void ProcessDataModel::Private::beginInsertRow(KSysGuard::Process *process) +{ + Q_ASSERT(process); + const int row = m_processes->processCount(); + q->beginInsertRows(QModelIndex(), row, row); +} + +void ProcessDataModel::Private::endInsertRow() +{ + q->endInsertRows(); +} + +void ProcessDataModel::Private::beginRemoveRow(KSysGuard::Process *process) +{ + q->beginRemoveRows(QModelIndex(), process->index(), process->index()); +} + +void ProcessDataModel::Private::endRemoveRow() +{ + q->endRemoveRows(); +} + +void ProcessDataModel::Private::update() +{ + m_processes->updateAllProcesses(m_updateInterval, KSysGuard::Processes::StandardInformation | KSysGuard::Processes::IOStatistics); +} + +QModelIndex ProcessDataModel::Private::getQModelIndex(KSysGuard::Process *process, int column) const +{ + Q_ASSERT(process); + if (process->pid() == -1) + return QModelIndex(); // pid -1 is our fake process meaning the very root (never drawn). To represent that, we return QModelIndex() which also means the top element + const int row = process->index(); + Q_ASSERT(row != -1); + return q->createIndex(row, column, process); +} + +ProcessAttributeModel *ProcessDataModel::attributesModel() +{ + // lazy load + if (!d->m_attributeModel) { + d->m_attributeModel = new KSysGuard::ProcessAttributeModel(d->m_processes, this); + } + return d->m_attributeModel; +} + +int ProcessDataModel::columnCount(const QModelIndex &parent) const +{ + if (parent.isValid()) { + return 0; + } + + return d->m_enabledAttributes.count(); +} + +QHash ProcessDataModel::roleNames() const +{ + QHash roles = QAbstractItemModel::roleNames(); + + const QMetaEnum e = QMetaEnum::fromType(); + + for (int i = 0; i < e.keyCount(); ++i) { + roles.insert(e.value(i), e.key(i)); + } + + return roles; +} + +QVariant ProcessDataModel::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 ShortName: { + if (!attribute->shortName().isEmpty()) { + return attribute->shortName(); + } + return attribute->name(); + } + case Name: + return attribute->name(); + case Value: + case Attribute: { + return attribute->id(); + } + case Unit: { + auto attribute = d->m_enabledAttributes[section]; + return attribute->unit(); + } + case Minimum: { + return attribute->min(); + } + case Maximum: { + return attribute->max(); + } + default: + break; + } + + return QVariant(); +} + +#include "process_data_model.moc" diff --git a/processui/ProcessModel.cpp b/processui/ProcessModel.cpp --- a/processui/ProcessModel.cpp +++ b/processui/ProcessModel.cpp @@ -519,7 +519,7 @@ mNumProcessorCores = mProcesses->numberProcessorCores(); if(mNumProcessorCores < 1) mNumProcessorCores=1; //Default to 1 if there was an error getting the number - mExtraAttributes = mProcesses->attributes(); + mExtraAttributes = mProcesses->extendedAttributes(); for (int i = 0 ; i < mExtraAttributes.count(); i ++) { connect(mExtraAttributes[i], &KSysGuard::ProcessAttribute::dataChanged, this, [this, i](KSysGuard::Process *process) { const QModelIndex index = q->getQModelIndex(process, mHeadings.count() + i);