diff --git a/processcore/CMakeLists.txt b/processcore/CMakeLists.txt --- a/processcore/CMakeLists.txt +++ b/processcore/CMakeLists.txt @@ -3,17 +3,21 @@ ########### next target ############### set(ksysguard_LIB_SRCS + extended_process_list.cpp + formatter.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_data_provider.cpp + unit.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(KF5::ProcessCore ALIAS processcore) @@ -23,6 +27,7 @@ PRIVATE KF5::I18n KF5::AuthCore + KF5::CoreAddons ${ZLIB_LIBRARIES} ) @@ -45,6 +50,10 @@ processes.h process.h process_controller.h + process_attribute.h + process_data_provider.h + formatter.h + unit.h DESTINATION ${KDE_INSTALL_INCLUDEDIR}/ksysguard/processcore COMPONENT Devel ) diff --git a/processcore/extended_process_list.h b/processcore/extended_process_list.h new file mode 100644 --- /dev/null +++ b/processcore/extended_process_list.h @@ -0,0 +1,41 @@ +/* + 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 + +namespace KSysGuard { +class ProcessAttribute; + +class Q_DECL_EXPORT ExtendedProcesses : public KSysGuard::Processes +{ + Q_OBJECT +public: + ExtendedProcesses(QObject *parent = nullptr); + ~ExtendedProcesses() override; + + QVector attributes() const; + +private: + class Private; + QScopedPointer d; +}; +} diff --git a/processcore/extended_process_list.cpp b/processcore/extended_process_list.cpp new file mode 100644 --- /dev/null +++ b/processcore/extended_process_list.cpp @@ -0,0 +1,99 @@ +/* + 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 "extended_process_list.h" + +#include +#include +#include + +#include "process_data_provider.h" +#include "process_attribute.h" +#include "processcore_debug.h" + +using namespace KSysGuard; + +class Q_DECL_HIDDEN ExtendedProcesses::Private +{ +public: + Private(ExtendedProcesses *q); + void loadPlugins(); + + ExtendedProcesses *q; + QVector m_providers; +}; + +ExtendedProcesses::Private::Private(ExtendedProcesses *_q) + : q(_q) +{ +} + +ExtendedProcesses::ExtendedProcesses(QObject *parent) + : Processes(QString(), parent) + , d(new Private(this)) +{ + d->loadPlugins(); + + connect(this, &KSysGuard::Processes::beginRemoveProcess, this, [this](KSysGuard::Process *process) { + const auto attrs = attributes(); + for (auto a : attrs) { + a->clearData(process); + } + }); + + connect(this, &KSysGuard::Processes::updated, this, [this]() { + for (auto p : qAsConst(d->m_providers)) { + if (p->enabled()) { + p->update(); + } + } + }); +} + +ExtendedProcesses::~ExtendedProcesses() +{ +} + +QVector ExtendedProcesses::attributes() const +{ + QVector rc; + for (auto p : qAsConst(d->m_providers)) { + rc << p->attributes(); + } + return rc; +} + +void ExtendedProcesses::Private::loadPlugins() +{ + //instantiate all plugins + const QVector listMetaData = KPluginLoader::findPlugins(QStringLiteral("ksysguard/process")); + for (const auto &pluginMetaData : listMetaData) { + qCDebug(LIBKSYSGUARD_PROCESSCORE) << "loading plugin" << pluginMetaData.name(); + auto factory = qobject_cast(pluginMetaData.instantiate()); + if (!factory) { + qCCritical(LIBKSYSGUARD_PROCESSCORE) << "failed to load plugin factory" << pluginMetaData.name(); + continue; + } + ProcessDataProvider *provider = factory->create(q); + if (!provider) { + qCCritical(LIBKSYSGUARD_PROCESSCORE) << "failed to instantiate ProcessDataProvider" << pluginMetaData.name(); + continue; + } + m_providers << provider; + } +} diff --git a/processcore/formatter.h b/processcore/formatter.h new file mode 100644 --- /dev/null +++ b/processcore/formatter.h @@ -0,0 +1,91 @@ +/* + Copyright (C) 2019 Vlad Zagorodniy + + 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 + +// Own +#include "unit.h" + +// Qt +#include +#include + +class KLocalizedString; + +namespace KSysGuard +{ + +/** + * This enum type is used to specify format options. + */ +enum FormatOption { + FormatOptionNone = 0, + FormatOptionAgo = 1 << 0, + FormatOptionShowNull = 1 << 1, +}; +Q_DECLARE_FLAGS(FormatOptions, FormatOption) + +class Q_DECL_EXPORT Formatter +{ +public: + /** + * Returns the scale factor suitable for display. + * + * @param value The maximium output value. + * @param unit The unit of the value. + * @param targetPrefix Preferred metric prefix. + */ + static qreal scaleDownFactor(const QVariant &value, Unit unit, + MetricPrefix targetPrefix = MetricPrefixAutoAdjust); + + /** + * Returns localized string that is suitable for display. + * + * @param value The maximum output value. + * @param unit The unit of the value. + * @param targetPrefix Preferred metric prefix. + */ + static KLocalizedString localizedString(const QVariant &value, Unit unit, + MetricPrefix targetPrefix = MetricPrefixAutoAdjust); + + /** + * Converts @p value to the appropriate displayable string. + * + * The returned string is localized. + * + * @param value The value to be converted. + * @param unit The unit of the value. + * @param targetPrefix Preferred metric prefix. + * @param options + */ + static QString formatValue(const QVariant &value, Unit unit, + MetricPrefix targetPrefix = MetricPrefixAutoAdjust, + FormatOptions options = FormatOptionNone); + + /** + * Returns a symbol that corresponds to the given @p unit. + * + * The returned unit symbol is localized. + */ + static QString symbol(Unit unit); +}; + +} // namespace KSysGuard + +Q_DECLARE_OPERATORS_FOR_FLAGS(KSysGuard::FormatOptions) diff --git a/processcore/formatter.cpp b/processcore/formatter.cpp new file mode 100644 --- /dev/null +++ b/processcore/formatter.cpp @@ -0,0 +1,435 @@ +/* + Copyright (C) 2019 Vlad Zagorodniy + + formatBootTimestamp is based on TimeUtil class: + Copyright (C) 2014 Gregor Mi + + 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 "formatter.h" + +#include + +#include +#include + +#include + +#ifdef Q_OS_OSX +#include +#include +#else +#include +#endif + +#include + +namespace KSysGuard +{ + +// TODO: Is there a bit nicer way to handle formatting? + +static KLocalizedString unitFormat(Unit unit) +{ + const static KLocalizedString B = ki18nc("Bytes unit symbol", "%1 B"); + const static KLocalizedString KiB = ki18nc("Kilobytes unit symbol", "%1 KiB"); + const static KLocalizedString MiB = ki18nc("Megabytes unit symbol", "%1 MiB"); + const static KLocalizedString GiB = ki18nc("Gigabytes unit symbol", "%1 GiB"); + const static KLocalizedString TiB = ki18nc("Terabytes unit symbol", "%1 TiB"); + const static KLocalizedString PiB = ki18nc("Petabytes unit symbol", "%1 PiB"); + + const static KLocalizedString bps = ki18nc("Bytes per second unit symbol", "%1 B/s"); + const static KLocalizedString Kbps = ki18nc("Kilobytes per second unit symbol", "%1 KiB/s"); + const static KLocalizedString Mbps = ki18nc("Megabytes per second unit symbol", "%1 MiB/s"); + const static KLocalizedString Gbps = ki18nc("Gigabytes per second unit symbol", "%1 GiB/s"); + const static KLocalizedString Tbps = ki18nc("Gigabytes per second unit symbol", "%1 TiB/s"); + const static KLocalizedString Pbps = ki18nc("Gigabytes per second unit symbol", "%1 PiB/s"); + + const static KLocalizedString Hz = ki18nc("Hertz unit symbol", "%1 Hz"); + const static KLocalizedString kHz = ki18nc("Kilohertz unit symbol", "%1 kHz"); + const static KLocalizedString MHz = ki18nc("Megahertz unit symbol", "%1 MHz"); + const static KLocalizedString GHz = ki18nc("Gigahertz unit symbol", "%1 GHz"); + const static KLocalizedString THz = ki18nc("Terahertz unit symbol", "%1 THz"); + const static KLocalizedString PHz = ki18nc("Petahertz unit symbol", "%1 PHz"); + + const static KLocalizedString percent = ki18nc("Percent unit", "%1%"); + const static KLocalizedString RPM = ki18nc("Revolutions per minute unit symbol", "%1 RPM"); + const static KLocalizedString C = ki18nc("Celsius unit symbol", "%1°C"); + const static KLocalizedString dBm = ki18nc("Decibels unit symbol", "%1 dBm"); + const static KLocalizedString s = ki18nc("Seconds unit symbol", "%1s"); + const static KLocalizedString V = ki18nc("Volts unit symbol", "%1 V"); + const static KLocalizedString W = ki18nc("Watts unit symbol", "%1 W"); + const static KLocalizedString rate = ki18nc("Rate unit symbol", "%1 s⁻¹"); + const static KLocalizedString unitless = ki18nc("Unitless", "%1"); + + switch (unit) { + case UnitByte: + return B; + case UnitKiloByte: + return KiB; + case UnitMegaByte: + return MiB; + case UnitGigaByte: + return GiB; + case UnitTeraByte: + return TiB; + case UnitPetaByte: + return PiB; + + case UnitByteRate: + return bps; + case UnitKiloByteRate: + return Kbps; + case UnitMegaByteRate: + return Mbps; + case UnitGigaByteRate: + return Gbps; + case UnitTeraByteRate: + return Tbps; + case UnitPetaByteRate: + return Pbps; + + case UnitHertz: + return Hz; + case UnitKiloHertz: + return kHz; + case UnitMegaHertz: + return MHz; + case UnitGigaHertz: + return GHz; + case UnitTeraHertz: + return THz; + case UnitPetaHertz: + return PHz; + + case UnitCelsius: + return C; + case UnitDecibelMilliWatts: + return dBm; + case UnitPercent: + return percent; + case UnitRate: + return rate; + case UnitRpm: + return RPM; + case UnitSecond: + return s; + case UnitVolt: + return V; + case UnitWatt: + return W; + + default: + return unitless; + } +} + +static int unitOrder(Unit unit) +{ + switch (unit) { + case UnitByte: + case UnitKiloByte: + case UnitMegaByte: + case UnitGigaByte: + case UnitTeraByte: + case UnitPetaByte: + case UnitByteRate: + case UnitKiloByteRate: + case UnitMegaByteRate: + case UnitGigaByteRate: + case UnitTeraByteRate: + case UnitPetaByteRate: + return 1024; + + case UnitHertz: + case UnitKiloHertz: + case UnitMegaHertz: + case UnitGigaHertz: + case UnitTeraHertz: + case UnitPetaHertz: + return 1000; + + default: + return 0; + } +} + +static Unit unitBase(Unit unit) +{ + switch (unit) { + case UnitByte: + case UnitKiloByte: + case UnitMegaByte: + case UnitGigaByte: + case UnitTeraByte: + case UnitPetaByte: + return UnitByte; + + case UnitByteRate: + case UnitKiloByteRate: + case UnitMegaByteRate: + case UnitGigaByteRate: + case UnitTeraByteRate: + case UnitPetaByteRate: + return UnitByteRate; + + case UnitHertz: + case UnitKiloHertz: + case UnitMegaHertz: + case UnitGigaHertz: + case UnitTeraHertz: + case UnitPetaHertz: + return UnitHertz; + + default: + return unit; + } +} + +static Unit adjustedUnit(qreal value, Unit unit, MetricPrefix prefix) +{ + const int order = unitOrder(unit); + if (!order) { + return unit; + } + + const Unit baseUnit = unitBase(unit); + const MetricPrefix basePrefix = MetricPrefix(unit - baseUnit); + + if (prefix == MetricPrefixAutoAdjust) { + const qreal absoluteValue = value * std::pow(order, int(basePrefix)); + if (absoluteValue > 0) { + const int targetPrefix = std::log2(absoluteValue) / std::log2(order); + if (targetPrefix <= MetricPrefixLast) { + prefix = MetricPrefix(targetPrefix); + } + } + if (prefix == MetricPrefixAutoAdjust) { + prefix = basePrefix; + } + } + + return Unit(prefix + baseUnit); +} + +static QString formatNumber(const QVariant &value, Unit unit, MetricPrefix prefix, FormatOptions options) +{ + qreal amount = value.toDouble(); + + if (!options.testFlag(FormatOptionShowNull) && qFuzzyIsNull(amount)) { + return QString(); + } + + const Unit adjusted = adjustedUnit(amount, unit, prefix); + if (adjusted != unit) { + amount /= std::pow(unitOrder(unit), adjusted - unit); + } + + const int precision = (value.type() != QVariant::Double && adjusted <= unit) ? 0 : 1; + const QString text = QLocale().toString(amount, 'f', precision); + + return unitFormat(adjusted).subs(text).toString(); +} + +static QString formatTime(const QVariant &value) +{ + const qlonglong seconds = value.toLongLong(); + + const QString minutesString = QString::number(seconds / 60); + const QString secondsScring = QStringLiteral("%1").arg(seconds % 60, 2, 10, QLatin1Char('0')); + + return minutesString + QLatin1Char(':') + secondsScring; +} + +static QString formatBootTimestamp(const QVariant &value, FormatOptions options) +{ + const qlonglong clockTicksSinceSystemBoot = value.toLongLong(); + const QDateTime now = QDateTime::currentDateTime(); + +#ifdef Q_OS_OSX + clock_serv_t cclock; + mach_timespec_t tp; + + host_get_clock_service(mach_host_self(), SYSTEM_CLOCK, &cclock); + clock_get_time(cclock, &tp); + mach_port_deallocate(mach_task_self(), cclock); +#else + timespec tp; + + clock_gettime(CLOCK_MONOTONIC, &tp); +#endif + const QDateTime systemBootTime = now.addSecs(-tp.tv_sec); + + const long clockTicksPerSecond = sysconf(_SC_CLK_TCK); + const qreal secondsSinceSystemBoot = qreal(clockTicksSinceSystemBoot) / clockTicksPerSecond; + const QDateTime absoluteStartTime = systemBootTime.addSecs(secondsSinceSystemBoot); + + if (!options.testFlag(FormatOptionAgo)) { + return QLocale().toString(absoluteStartTime); + } + + const qint64 totalSeconds = absoluteStartTime.secsTo(now); + const qint64 totalMinutes = totalSeconds / 60.0; + const qint64 totalHours = totalSeconds / 60.0 / 60.0; + const qint64 totalDays = totalSeconds / 60.0 / 60.0 / 24.0; + + if (!totalMinutes) { + return i18nc("contains a abbreviated time unit: (s)econds", "%1s ago", totalSeconds); + } + + if (!totalHours) { + const int seconds = totalSeconds - totalMinutes * 60; + return i18nc("contains abbreviated time units: (m)inutes and (s)econds", "%1m %2s ago", + totalMinutes, seconds); + } + + if (!totalDays) { + const int seconds = totalSeconds - totalMinutes * 60; + const int minutes = totalMinutes - totalHours * 60; + return i18nc("contains abbreviated time units: (h)ours, (m)inutes and (s)econds)", + "%1h %2m %3s ago", totalHours, minutes, seconds); + } + + const int minutes = totalMinutes - totalHours * 60; + const int hours = totalHours - totalDays * 24; + return i18ncp("contains also abbreviated time units: (h)ours and (m)inutes", + "%1 day %2h %3m ago", "%1 days %2h %3m ago", totalDays, hours, minutes); +} + +qreal Formatter::scaleDownFactor(const QVariant &value, Unit unit, MetricPrefix targetPrefix) +{ + const Unit adjusted = adjustedUnit(value.toDouble(), unit, targetPrefix); + if (adjusted == unit) { + return 1; + } + + return std::pow(unitOrder(unit), adjusted - unit); +} + +KLocalizedString Formatter::localizedString(const QVariant &value, Unit unit, MetricPrefix targetPrefix) +{ + const Unit adjusted = adjustedUnit(value.toDouble(), unit, targetPrefix); + return unitFormat(adjusted); +} + +QString Formatter::formatValue(const QVariant &value, Unit unit, MetricPrefix targetPrefix, FormatOptions options) +{ + switch (unit) { + case UnitByte: + case UnitKiloByte: + case UnitMegaByte: + case UnitGigaByte: + case UnitTeraByte: + case UnitPetaByte: + case UnitByteRate: + case UnitKiloByteRate: + case UnitMegaByteRate: + case UnitGigaByteRate: + case UnitTeraByteRate: + case UnitPetaByteRate: + case UnitHertz: + case UnitKiloHertz: + case UnitMegaHertz: + case UnitGigaHertz: + case UnitTeraHertz: + case UnitPetaHertz: + case UnitPercent: + case UnitRate: + case UnitRpm: + case UnitCelsius: + case UnitDecibelMilliWatts: + case UnitVolt: + case UnitWatt: + case UnitSecond: + return formatNumber(value, unit, targetPrefix, options); + + case UnitBootTimestamp: + return formatBootTimestamp(value, options); + case UnitTime: + return formatTime(value); + + default: + return value.toString(); + } +} + +QString Formatter::symbol(Unit unit) +{ + // TODO: Is it possible to avoid duplication of these symbols? + switch (unit) { + case UnitByte: + return i18nc("Bytes unit symbol", "B"); + case UnitKiloByte: + return i18nc("Kilobytes unit symbol", "KiB"); + case UnitMegaByte: + return i18nc("Megabytes unit symbol", "MiB"); + case UnitGigaByte: + return i18nc("Gigabytes unit symbol", "GiB"); + case UnitTeraByte: + return i18nc("Terabytes unit symbol", "TiB"); + case UnitPetaByte: + return i18nc("Petabytes unit symbol", "PiB"); + + case UnitByteRate: + return i18nc("Bytes per second unit symbol", "B/s"); + case UnitKiloByteRate: + return i18nc("Kilobytes per second unit symbol", "KiB/s"); + case UnitMegaByteRate: + return i18nc("Megabytes per second unit symbol", "MiB/s"); + case UnitGigaByteRate: + return i18nc("Gigabytes per second unit symbol", "GiB/s"); + case UnitTeraByteRate: + return i18nc("Gigabytes per second unit symbol", "TiB/s"); + case UnitPetaByteRate: + return i18nc("Gigabytes per second unit symbol", "PiB/s"); + + case UnitHertz: + return i18nc("Hertz unit symbol", "Hz"); + case UnitKiloHertz: + return i18nc("Kilohertz unit symbol", "kHz"); + case UnitMegaHertz: + return i18nc("Megahertz unit symbol", "MHz"); + case UnitGigaHertz: + return i18nc("Gigahertz unit symbol", "GHz"); + case UnitTeraHertz: + return i18nc("Terahertz unit symbol", "THz"); + case UnitPetaHertz: + return i18nc("Petahertz unit symbol", "PHz"); + + case UnitPercent: + return i18nc("Percent unit", "%"); + case UnitRpm: + return i18nc("Revolutions per minute unit symbol", "RPM"); + case UnitCelsius: + return i18nc("Celsius unit symbol", "°C"); + case UnitDecibelMilliWatts: + return i18nc("Decibels unit symbol", "dBm"); + case UnitSecond: + return i18nc("Seconds unit symbol", "s"); + case UnitVolt: + return i18nc("Volts unit symbol", "V"); + case UnitWatt: + return i18nc("Watts unit symbol", "W"); + case UnitRate: + return i18nc("Rate unit symbol", "s⁻¹"); + + default: + return QString(); + } +} + +} // namespace KSysGuard diff --git a/processcore/process_attribute.h b/processcore/process_attribute.h new file mode 100644 --- /dev/null +++ b/processcore/process_attribute.h @@ -0,0 +1,112 @@ +/* + 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 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); + + /** + * The last stored value for a given process + */ + QVariant data(KSysGuard::Process *process); + + /** + * 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); + +Q_SIGNALS: + void dataChanged(KSysGuard::Process *process); + void enabledChanged(bool enabled); + +private: + class Private; + QScopedPointer d; +}; + +} diff --git a/processcore/process_attribute.cpp b/processcore/process_attribute.cpp new file mode 100644 --- /dev/null +++ b/processcore/process_attribute.cpp @@ -0,0 +1,152 @@ +/* + 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" + +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; +}; + +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; +} + +QVariant ProcessAttribute::data(KSysGuard::Process *process) +{ + 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); +} diff --git a/processcore/process_data_provider.h b/processcore/process_data_provider.h new file mode 100644 --- /dev/null +++ b/processcore/process_data_provider.h @@ -0,0 +1,98 @@ +/* + 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 + +namespace KSysGuard { + +class Processes; +class Process; +class ProcessAttribute; + +/** + * Base class for a process plugin data + * Plugins provide a list of additional attributes, which in turn have data about a given process + */ +class Q_DECL_EXPORT ProcessDataProvider : public QObject +{ + Q_OBJECT + +public: + ProcessDataProvider(QObject *parent, const QVariantList &args); + ~ProcessDataProvider() override; + + /** + * Accessors for process information matching + */ + KSysGuard::Processes *processes() const; + + /** + * Returns a new process object for a given PID + * This will update the process list if this PID does not exist yet + * This may return a null pointer + */ + KSysGuard::Process *getProcess(long pid); + + /** + * A list of all process attributes provided by this plugin + * It is expected to remain constant through the lifespan of this class + */ + QVector attributes() const; + + /** + * Called when processes should be updated if manually polled + * Plugins can however update at any time if enabled + */ + virtual void update() + { + } + + /** + * True when at least one attribute from this plugin is subscribed + */ + bool enabled() const; + + virtual void handleEnabledChanged(bool enabled) + { + Q_UNUSED(enabled) + } + + // for any future compatibility + virtual void virtual_hook(int id, void *data) + { + Q_UNUSED(id) + Q_UNUSED(data) + } + +protected: + /** + * Register a new process attribute + * Process attributes should be created in the plugin constuctor and must live for the duration the plugin + */ + void addProcessAttribute(ProcessAttribute *attribute); + +private: + class Private; + QScopedPointer d; +}; + +} diff --git a/processcore/process_data_provider.cpp b/processcore/process_data_provider.cpp new file mode 100644 --- /dev/null +++ b/processcore/process_data_provider.cpp @@ -0,0 +1,92 @@ +/* + 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_data_provider.h" +#include "processes.h" +#include "process_attribute.h" + +using namespace KSysGuard; + +class Q_DECL_HIDDEN KSysGuard::ProcessDataProvider::Private +{ +public: + KSysGuard::Processes *m_processes; + QVector m_attributes; + bool m_enabled = false; +}; + +ProcessDataProvider::ProcessDataProvider(QObject *parent, const QVariantList &args) + : QObject(parent) + , d(new Private) +{ + //cast is needed to allow us to use KPluginFactory, but not have null pointers during subclass construction + auto procList = qobject_cast(parent); + Q_ASSERT(procList); + d->m_processes = procList; + + Q_UNUSED(args) +} + +ProcessDataProvider::~ProcessDataProvider() +{ +} + +KSysGuard::Processes *ProcessDataProvider::processes() const +{ + return d->m_processes; +} + +KSysGuard::Process *ProcessDataProvider::getProcess(long pid) +{ + auto process = d->m_processes->getProcess(pid); + if (!process) { + processes()->updateOrAddProcess(pid); + } + process = processes()->getProcess(pid); + return process; +} + +QVector ProcessDataProvider::attributes() const +{ + return d->m_attributes; +} + +bool ProcessDataProvider::enabled() const +{ + return d->m_enabled; +} + +void ProcessDataProvider::addProcessAttribute(ProcessAttribute *attribute) +{ + d->m_attributes << attribute; + connect(attribute, &ProcessAttribute::enabledChanged, this, [this](bool enabled) { + if (enabled == d->m_enabled) { + return; + } + bool wasEnabled = d->m_enabled; + + d->m_enabled = std::any_of(d->m_attributes.constBegin(), d->m_attributes.constEnd(), [](ProcessAttribute *p) { + return p->enabled(); + }); + + if (d->m_enabled != wasEnabled) { + handleEnabledChanged(d->m_enabled); + } + }); +} diff --git a/processcore/processes.h b/processcore/processes.h --- a/processcore/processes.h +++ b/processcore/processes.h @@ -242,6 +242,8 @@ * We have finished moving the process */ void endMoveProcess(); + + void updated(); protected: class Private; Private *d; diff --git a/processcore/processes.cpp b/processcore/processes.cpp --- a/processcore/processes.cpp +++ b/processcore/processes.cpp @@ -354,7 +354,7 @@ } d->mProcessedLastTime = beingProcessed; //update the set for next time this function is called - return; + emit updated(); } void Processes::Private::markProcessesAsEnded(long pid) diff --git a/processcore/unit.h b/processcore/unit.h new file mode 100644 --- /dev/null +++ b/processcore/unit.h @@ -0,0 +1,91 @@ +/* + Copyright (C) 2019 Vlad Zagorodniy + + 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 + +// Qt +#include + +namespace KSysGuard +{ +Q_DECL_EXPORT Q_NAMESPACE + +/** + * This enum type is used to specify metric prefixes. + */ +enum MetricPrefix { + MetricPrefixAutoAdjust = -1, + MetricPrefixUnity = 0, + MetricPrefixKilo, + MetricPrefixMega, + MetricPrefixGiga, + MetricPrefixTera, + MetricPrefixPeta, + MetricPrefixLast = MetricPrefixPeta +}; +Q_ENUM_NS(MetricPrefix) + +/** + * This enum types is used to specify units. + */ +enum Unit { + UnitInvalid = -1, + UnitNone = 0, + + // Byte size units. + UnitByte = 100, + UnitKiloByte = MetricPrefixKilo + UnitByte, + UnitMegaByte = MetricPrefixMega + UnitByte, + UnitGigaByte = MetricPrefixGiga + UnitByte, + UnitTeraByte = MetricPrefixTera + UnitByte, + UnitPetaByte = MetricPrefixPeta + UnitByte, + + // Data rate units. + UnitByteRate = 200, + UnitKiloByteRate = MetricPrefixKilo + UnitByteRate, + UnitMegaByteRate = MetricPrefixMega + UnitByteRate, + UnitGigaByteRate = MetricPrefixGiga + UnitByteRate, + UnitTeraByteRate = MetricPrefixTera + UnitByteRate, + UnitPetaByteRate = MetricPrefixPeta + UnitByteRate, + + // Frequency. + UnitHertz = 300, + UnitKiloHertz = MetricPrefixKilo + UnitHertz, + UnitMegaHertz = MetricPrefixMega + UnitHertz, + UnitGigaHertz = MetricPrefixGiga + UnitHertz, + UnitTeraHertz = MetricPrefixTera + UnitHertz, + UnitPetaHertz = MetricPrefixPeta + UnitHertz, + + // Time units. + UnitBootTimestamp = 400, + UnitSecond, + UnitTime, + + // Misc units. + UnitCelsius = 500, + UnitDecibelMilliWatts, + UnitPercent, + UnitRate, + UnitRpm, + UnitVolt, + UnitWatt, +}; +Q_ENUM_NS(Unit) + +} // namespace KSysGuard diff --git a/processcore/unit.cpp b/processcore/unit.cpp new file mode 100644 --- /dev/null +++ b/processcore/unit.cpp @@ -0,0 +1,3 @@ +#include "unit.h" + +#include "moc_unit.cpp" diff --git a/processui/ProcessModel.cpp b/processui/ProcessModel.cpp --- a/processui/ProcessModel.cpp +++ b/processui/ProcessModel.cpp @@ -26,8 +26,12 @@ #include "ProcessModel_p.h" #include "timeutil.h" -#include "processcore/processes.h" +#include "processcore/extended_process_list.h" +#include "processcore/formatter.h" #include "processcore/process.h" +#include "processcore/process_attribute.h" +#include "processcore/process_data_provider.h" + #include "processui_debug.h" #include @@ -350,7 +354,11 @@ } } //Sort by the display string if we do not have an explicit sorting here - return data(left, Qt::DisplayRole).toString() < data(right, Qt::DisplayRole).toString(); + + if (data(left, ProcessModel::PlainValueRole).toInt() == data(right, ProcessModel::PlainValueRole).toInt()) { + return data(left, Qt::DisplayRole).toString() < data(right, Qt::DisplayRole).toString(); + } + return data(left, ProcessModel::PlainValueRole).toInt() < data(right, ProcessModel::PlainValueRole).toInt(); } ProcessModel::~ProcessModel() @@ -493,7 +501,7 @@ q->endResetModel(); } - mProcesses = new KSysGuard::Processes(mHostName); + mProcesses = new KSysGuard::ExtendedProcesses(); connect( mProcesses, &KSysGuard::Processes::processChanged, this, &ProcessModelPrivate::processChanged); connect( mProcesses, &KSysGuard::Processes::beginAddProcess, this, &ProcessModelPrivate::beginInsertRow); @@ -505,6 +513,16 @@ connect( mProcesses, &KSysGuard::Processes::endMoveProcess, this, &ProcessModelPrivate::endMoveRow); mNumProcessorCores = mProcesses->numberProcessorCores(); if(mNumProcessorCores < 1) mNumProcessorCores=1; //Default to 1 if there was an error getting the number + + mExtraAttributes = mProcesses->attributes(); + for (int i = 0 ; i < mExtraAttributes.count(); i ++) { + mExtraAttributes[i]->setEnabled(true); // In future we will toggle this based on column visibility + + connect(mExtraAttributes[i], &KSysGuard::ProcessAttribute::dataChanged, this, [this, i](KSysGuard::Process *process) { + const QModelIndex index = q->getQModelIndex(process, mHeadings.count() + i); + emit q->dataChanged(index, index); + }); + } } #if HAVE_X11 @@ -653,7 +671,7 @@ int ProcessModel::columnCount ( const QModelIndex & ) const { - return d->mHeadings.count(); + return d->mHeadings.count() + d->mExtraAttributes.count(); } bool ProcessModel::hasChildren ( const QModelIndex & parent = QModelIndex() ) const @@ -682,7 +700,7 @@ QModelIndex ProcessModel::index ( int row, int column, const QModelIndex & parent ) const { if(row<0) return QModelIndex(); - if(column<0 || column >= d->mHeadings.count() ) return QModelIndex(); + if(column<0 || column >= columnCount() ) return QModelIndex(); if(d->mSimple) { if( parent.isValid()) return QModelIndex(); @@ -974,8 +992,18 @@ { if(orientation != Qt::Horizontal) return QVariant(); - if(section < 0 || section >= d->mHeadings.count()) + if(section < 0) return QVariant(); //is this needed? + + if (section >= d->mHeadings.count() && section < columnCount()) { + int attr = section - d->mHeadings.count(); + switch (role) { + case Qt::DisplayRole: + return d->mExtraAttributes[attr]->shortName(); + } + return QVariant(); + } + switch( role ) { case Qt::TextAlignmentRole: { @@ -1269,12 +1297,39 @@ QVariant ProcessModel::data(const QModelIndex &index, int role) const { //This function must be super duper ultra fast because it's called thousands of times every few second :( - //I think it should be optomised for role first, hence the switch statement (fastest possible case) + //I think it should be optimised for role first, hence the switch statement (fastest possible case) if (!index.isValid()) { return QVariant(); } + + if (index.column() > columnCount()) { + return QVariant(); + } + //plugin stuff first if (index.column() >= d->mHeadings.count()) { + int attr = index.column() - d->mHeadings.count(); + switch (role) { + case ProcessModel::PlainValueRole: { + KSysGuard::Process *process = reinterpret_cast< KSysGuard::Process * > (index.internalPointer()); + const QVariant value = d->mExtraAttributes[attr]->data(process); + return value; + } + case Qt::DisplayRole: { + KSysGuard::Process *process = reinterpret_cast< KSysGuard::Process * > (index.internalPointer()); + const QVariant value = d->mExtraAttributes[attr]->data(process); + return KSysGuard::Formatter::formatValue(value, d->mExtraAttributes[attr]->unit()); + } + case Qt::TextAlignmentRole: { + KSysGuard::Process *process = reinterpret_cast< KSysGuard::Process * > (index.internalPointer()); + const QVariant value = d->mExtraAttributes[attr]->data(process); + if (value.canConvert(QMetaType::LongLong) + && static_cast(value.type()) != QMetaType::QString) { + return Qt::AlignRight + Qt::AlignVCenter; + } + return Qt::AlignLeft + Qt::AlignVCenter; + } + } return QVariant(); } @@ -2039,11 +2094,7 @@ headings << i18nc("process heading", "Total Memory"); if(d->mHeadings.isEmpty()) { // If it's empty, this is the first time this has been called, so insert the headings - beginInsertColumns(QModelIndex(), 0, headings.count()-1); - { - d->mHeadings = headings; - } - endInsertColumns(); + d->mHeadings = headings; } else { // This was called to retranslate the headings. Just use the new translations and call headerDataChanged Q_ASSERT(d->mHeadings.count() == headings.count()); diff --git a/processui/ProcessModel_p.h b/processui/ProcessModel_p.h --- a/processui/ProcessModel_p.h +++ b/processui/ProcessModel_p.h @@ -22,7 +22,7 @@ #ifndef PROCESSMODEL_P_H_ #define PROCESSMODEL_P_H_ -#include +#include #include "ProcessModel.h" #include @@ -182,7 +182,7 @@ long long mMemTotal; ///< the total amount of physical memory in kb in the machine. We can used this to determine the percentage of memory an app is using int mNumProcessorCores; ///< The number of (enabled) processor cores in the this machine - KSysGuard::Processes *mProcesses; ///< The processes instance + KSysGuard::ExtendedProcesses *mProcesses; ///< The processes instance QPixmap mBlankPixmap; ///< Used to pad out process names which don't have an icon @@ -208,6 +208,8 @@ /** Storage for the history entries. We need one per percentage column. */ QHash> mMapProcessCPUHistory; + QVector mExtraAttributes; + #ifdef HAVE_XRES bool mHaveXRes; ///< True if the XRes extension is available at run time QMap mXResClientResources;