Commit 834de9a5 authored by David Edmundson's avatar David Edmundson

Introduce ProcessDataModel

Summary:
Last release a plugin system was introduced that allowed plugins to
provide columns of process data in a way that included enough metadata
to allow displaying of said data appropriately without the client
needing to be aware of the semantics of what that column represents.

This patch provides all process information in that new format. This is
then exposed as new, much simler, model.

This new model is designed to be consumable from a QML API for any
potential process data viewer.

Existing models are unchanged for maximum compatibility.

Test Plan: Used in another project

Reviewers: #plasma, broulik

Reviewed By: #plasma, broulik

Subscribers: ahiemstra, broulik, plasma-devel

Tags: #plasma

Differential Revision: https://phabricator.kde.org/D27509
parent 7a13a678
......@@ -64,7 +64,7 @@ ecm_setup_version(${PROJECT_VERSION}
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)
......
......@@ -12,6 +12,8 @@ set(ksysguard_LIB_SRCS
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 @@ install( FILES
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
......
This diff is collapsed.
......@@ -33,6 +33,7 @@ public:
~ExtendedProcesses() override;
QVector<ProcessAttribute *> attributes() const;
QVector<ProcessAttribute *> extendedAttributes() const;
private:
class Private;
......
......@@ -146,7 +146,7 @@ void KSysGuard::ProcessAttribute::setVisibleByDefault(bool visible)
d->m_defaultVisible = visible;
}
QVariant ProcessAttribute::data(KSysGuard::Process *process)
QVariant ProcessAttribute::data(KSysGuard::Process *process) const
{
return d->m_data.value(process);
}
......
......@@ -96,7 +96,7 @@ public:
/**
* 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
......
/*
Copyright (c) 2020 David Edmundson <davidedmundson@kde.org>
Copyright (c) 2020 Arjen Hiemstra <ahiemstra@heimr.nl>
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<ProcessAttribute *> 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>(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<int, QByteArray> ProcessAttributeModel::roleNames() const
{
return QAbstractListModel::roleNames().unite({
{ static_cast<int>(Role::Id), "id" },
{ static_cast<int>(Role::Name), "name" },
{ static_cast<int>(Role::ShortName), "shortName" },
{ static_cast<int>(Role::Description), "description" },
{ static_cast<int>(Role::Unit), "unit" },
});
}
/*
Copyright (c) 2020 David Edmundson <davidedmundson@kde.org>
Copyright (c) 2020 Arjen Hiemstra <ahiemstra@heimr.nl>
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 <QAbstractListModel>
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<int, QByteArray> roleNames() const override;
private:
class Private;
QScopedPointer<Private> d;
};
}
/*
Copyright (c) 2020 David Edmundson <davidedmundson@kde.org>
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 <QMetaEnum>
#include <QTimer>
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<QString, KSysGuard::ProcessAttribute *> m_availableAttributes;
QVector<KSysGuard::ProcessAttribute *> 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<KSysGuard::Process *>(index.internalPointer());
const QVariant value = attribute->data(process);
return KSysGuard::Formatter::formatValue(value, attribute->unit());
}
case Value: {
KSysGuard::Process *process = reinterpret_cast<KSysGuard::Process *>(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<ProcessAttribute *> 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<int, QByteArray> ProcessDataModel::roleNames() const
{
QHash<int, QByteArray> roles = QAbstractItemModel::roleNames();
const QMetaEnum e = QMetaEnum::fromType<AdditionalRoles>();
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"
/*
Copyright (c) 2020 David Edmundson <davidedmundson@kde.org>
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 <QAbstractItemModel>
#include <processcore/processes.h>
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<int, QByteArray> 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<Private> d;
};
}
......@@ -519,7 +519,7 @@ void ProcessModelPrivate::setupProcesses() {
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);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment