diff --git a/processcore/CMakeLists.txt b/processcore/CMakeLists.txt index e92fd50..0b6d4e0 100644 --- a/processcore/CMakeLists.txt +++ b/processcore/CMakeLists.txt @@ -1,66 +1,75 @@ add_definitions(-DTRANSLATION_DOMAIN=\"processcore\") ########### 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_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) target_link_libraries(processcore PUBLIC Qt5::Core PRIVATE KF5::I18n + KF5::CoreAddons ${ZLIB_LIBRARIES} ) if( ${CMAKE_SYSTEM_NAME} MATCHES "NetBSD" ) message(STATUS "Adding kvm library on NetBSD") target_link_libraries(processcore kvm) endif() target_include_directories(processcore PUBLIC "$" "$" ) set_target_properties(processcore PROPERTIES VERSION ${KSYSGUARD_VERSION_STRING} SOVERSION ${KSYSGUARD_SOVERSION} EXPORT_NAME ProcessCore) install(TARGETS processcore EXPORT libksysguardLibraryTargets ${KDE_INSTALL_TARGETS_DEFAULT_ARGS}) ########### install files ############### install( FILES processes.h process.h + process_attribute.h + process_data_provider.h + formatter.h + unit.h DESTINATION ${KDE_INSTALL_INCLUDEDIR}/ksysguard/processcore COMPONENT Devel ) #------ KAuth stuff # Auth example helper set(ksysguardprocesslist_helper_srcs helper.cpp process.cpp processes_local_p.cpp processes_base_p.cpp) add_executable(ksysguardprocesslist_helper ${ksysguardprocesslist_helper_srcs}) target_link_libraries(ksysguardprocesslist_helper Qt5::Core KF5::AuthCore KF5::I18n) install(TARGETS ksysguardprocesslist_helper DESTINATION ${KAUTH_HELPER_INSTALL_DIR}) kauth_install_helper_files(ksysguardprocesslist_helper org.kde.ksysguard.processlisthelper root) kauth_install_actions(org.kde.ksysguard.processlisthelper actions.actions) set_target_properties(ksysguardprocesslist_helper PROPERTIES COMPILE_FLAGS "-Wall -ggdb") diff --git a/processcore/extended_process_list.cpp b/processcore/extended_process_list.cpp new file mode 100644 index 0000000..4a1d000 --- /dev/null +++ b/processcore/extended_process_list.cpp @@ -0,0 +1,100 @@ +/* + 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 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/extended_process_list.h b/processcore/extended_process_list.h new file mode 100644 index 0000000..c740edc --- /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/formatter.cpp b/processcore/formatter.cpp new file mode 100644 index 0000000..4138498 --- /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/formatter.h b/processcore/formatter.h new file mode 100644 index 0000000..cbecd87 --- /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/process_attribute.cpp b/processcore/process_attribute.cpp new file mode 100644 index 0000000..6aefff3 --- /dev/null +++ b/processcore/process_attribute.cpp @@ -0,0 +1,151 @@ +/* + 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 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_attribute.h b/processcore/process_attribute.h new file mode 100644 index 0000000..af3b2c2 --- /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_data_provider.cpp b/processcore/process_data_provider.cpp new file mode 100644 index 0000000..363e814 --- /dev/null +++ b/processcore/process_data_provider.cpp @@ -0,0 +1,95 @@ +/* + 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 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/process_data_provider.h b/processcore/process_data_provider.h new file mode 100644 index 0000000..5139693 --- /dev/null +++ b/processcore/process_data_provider.h @@ -0,0 +1,96 @@ +/* + 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 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/processes.cpp b/processcore/processes.cpp index e6f67db..2e4da96 100644 --- a/processcore/processes.cpp +++ b/processcore/processes.cpp @@ -1,544 +1,544 @@ /* This file is part of the KDE project Copyright (C) 2007 John Tapsell 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 "processes.h" #include "processes_base_p.h" #include "processes_local_p.h" #include "processes_remote_p.h" #include "processes_atop_p.h" #include "process.h" #include #include #include #include //for sysconf #include /* if porting to an OS without signal.h please #define SIGTERM to something */ #include namespace KSysGuard { class Q_DECL_HIDDEN Processes::Private { public: Private(Processes *q_ptr) { mFakeProcess.setParent(&mFakeProcess); mAbstractProcesses = nullptr; mHistoricProcesses = nullptr; mIsLocalHost = true; mProcesses.insert(-1, &mFakeProcess); mElapsedTimeMilliSeconds = 0; mHavePreviousIoValues = false; mUpdateFlags = nullptr; mUsingHistoricalData = false; q = q_ptr; } ~Private(); void markProcessesAsEnded(long pid); QSet mToBeProcessed; QSet mProcessedLastTime; QSet mEndedProcesses; ///< Processes that have finished QHash mProcesses; ///< This must include mFakeProcess at pid -1 QList mListProcesses; ///< A list of the processes. Does not include mFakeProcesses Process mFakeProcess; ///< A fake process with pid -1 just so that even init points to a parent AbstractProcesses *mAbstractProcesses; ///< The OS specific code to get the process information ProcessesATop *mHistoricProcesses; ///< A way to get historic information about processes bool mIsLocalHost; ///< Whether this is localhost or not QTime mLastUpdated; ///< This is the time we last updated. Used to calculate cpu usage. long mElapsedTimeMilliSeconds; ///< The number of milliseconds (1000ths of a second) that passed since the last update Processes::UpdateFlags mUpdateFlags; bool mHavePreviousIoValues; ///< This is whether we updated the IO value on the last update bool mUsingHistoricalData; ///< Whether to return historical data for updateProcess() etc Processes *q; }; Processes::Private::~Private() { Q_FOREACH(Process *process, mProcesses) { if(process != &mFakeProcess) delete process; } mProcesses.clear(); mListProcesses.clear(); delete mAbstractProcesses; mAbstractProcesses = nullptr; } Processes::Processes(const QString &host, QObject *parent) : QObject(parent), d(new Private(this)) { if(host.isEmpty()) { d->mAbstractProcesses = new ProcessesLocal(); } else { ProcessesRemote *remote = new ProcessesRemote(host); d->mAbstractProcesses = remote; connect(remote, &ProcessesRemote::runCommand, this, &Processes::runCommand); } d->mIsLocalHost = host.isEmpty(); connect( d->mAbstractProcesses, &AbstractProcesses::processesUpdated, this, &Processes::processesUpdated); } Processes::~Processes() { delete d; } Processes::Error Processes::lastError() const { return d->mAbstractProcesses->errorCode; } Process *Processes::getProcess(long pid) const { return d->mProcesses.value(pid); } const QList &Processes::getAllProcesses() const { return d->mListProcesses; } int Processes::processCount() const { return d->mListProcesses.count(); } bool Processes::updateProcess( Process *ps, long ppid) { Process *parent = d->mProcesses.value(ppid, &d->mFakeProcess); Q_ASSERT(parent); //even init has a non-null parent - the mFakeProcess if(ps->parent() != parent) { emit beginMoveProcess(ps, parent/*new parent*/); //Processes has been reparented Process *p = ps; do { p = p->parent(); p->numChildren()--; } while (p->pid()!= -1); Q_ASSERT(ps != parent); ps->parent()->children().removeAll(ps); ps->setParent(parent); //the parent has changed parent->children().append(ps); p = ps; do { p = p->parent(); p->numChildren()++; } while (p->pid()!= -1); emit endMoveProcess(); Q_ASSERT(ps != parent); ps->setParent(parent); } ps->setParentPid(ppid); bool success = updateProcessInfo(ps); emit processChanged(ps, false); return success; } bool Processes::updateProcessInfo(Process *ps) { //Now we can actually get the process info qlonglong oldUserTime = ps->userTime(); qlonglong oldSysTime = ps->sysTime(); qlonglong oldIoCharactersRead = 0; qlonglong oldIoCharactersWritten = 0; qlonglong oldIoReadSyscalls = 0; qlonglong oldIoWriteSyscalls = 0; qlonglong oldIoCharactersActuallyRead = 0; qlonglong oldIoCharactersActuallyWritten = 0; if(d->mUpdateFlags.testFlag(Processes::IOStatistics)) { oldIoCharactersRead = ps->ioCharactersRead(); oldIoCharactersWritten = ps->ioCharactersWritten(); oldIoReadSyscalls = ps->ioReadSyscalls(); oldIoWriteSyscalls = ps->ioWriteSyscalls(); oldIoCharactersActuallyRead = ps->ioCharactersActuallyRead(); oldIoCharactersActuallyWritten = ps->ioCharactersActuallyWritten(); } ps->setChanges(Process::Nothing); bool success; if(d->mUsingHistoricalData) success = d->mHistoricProcesses->updateProcessInfo(ps->pid(), ps); else success = d->mAbstractProcesses->updateProcessInfo(ps->pid(), ps); //Now we have the process info. Calculate the cpu usage and total cpu usage for itself and all its parents if(!d->mUsingHistoricalData && d->mElapsedTimeMilliSeconds != 0) { //Update the user usage and sys usage #ifndef Q_OS_NETBSD /* The elapsed time is the d->mElapsedTimeMilliSeconds * (which is of the order 2 seconds or so) plus a small * correction where we get the amount of time elapsed since * we start processing. This is because the processing itself * can take a non-trivial amount of time. */ int elapsedTime = ps->elapsedTimeMilliSeconds(); ps->setElapsedTimeMilliSeconds(d->mLastUpdated.elapsed()); elapsedTime = ps->elapsedTimeMilliSeconds() - elapsedTime + d->mElapsedTimeMilliSeconds; if(elapsedTime) { ps->setUserUsage((int)(((ps->userTime() - oldUserTime)*1000.0) / elapsedTime)); ps->setSysUsage((int)(((ps->sysTime() - oldSysTime)*1000.0) / elapsedTime)); } #endif if(d->mUpdateFlags.testFlag(Processes::IOStatistics)) { if( d->mHavePreviousIoValues ) { ps->setIoCharactersReadRate((ps->ioCharactersRead() - oldIoCharactersRead) * 1000.0 / elapsedTime); ps->setIoCharactersWrittenRate((ps->ioCharactersWritten() - oldIoCharactersWritten) * 1000.0 / elapsedTime); ps->setIoReadSyscallsRate((ps->ioReadSyscalls() - oldIoReadSyscalls) * 1000.0 / elapsedTime); ps->setIoWriteSyscallsRate((ps->ioWriteSyscalls() - oldIoWriteSyscalls) * 1000.0 / elapsedTime); ps->setIoCharactersActuallyReadRate((ps->ioCharactersActuallyRead() - oldIoCharactersActuallyRead) * 1000.0 / elapsedTime); ps->setIoCharactersActuallyWrittenRate((ps->ioCharactersActuallyWritten() - oldIoCharactersActuallyWritten) * 1000.0 / elapsedTime); } else d->mHavePreviousIoValues = true; } else if(d->mHavePreviousIoValues) { d->mHavePreviousIoValues = false; ps->setIoCharactersReadRate(0); ps->setIoCharactersWrittenRate(0); ps->setIoReadSyscallsRate(0); ps->setIoWriteSyscallsRate(0); ps->setIoCharactersActuallyReadRate(0); ps->setIoCharactersActuallyWrittenRate(0); } } if(d->mUsingHistoricalData || d->mElapsedTimeMilliSeconds != 0) { ps->setTotalUserUsage(ps->userUsage()); ps->setTotalSysUsage(ps->sysUsage()); if(ps->userUsage() != 0 || ps->sysUsage() != 0) { Process *p = ps->parent(); while(p->pid() != -1) { p->totalUserUsage() += ps->userUsage(); p->totalSysUsage() += ps->sysUsage(); emit processChanged(p, true); p = p->parent(); } } } return success; } bool Processes::addProcess(long pid, long ppid) { Process *parent = d->mProcesses.value(ppid); if(!parent) { //Under race conditions, the parent could have already quit //In this case, attach to top leaf parent = &d->mFakeProcess; Q_ASSERT(parent); //even init has a non-null parent - the mFakeProcess } //it's a new process - we need to set it up Process *ps = new Process(pid, ppid, parent); emit beginAddProcess(ps); d->mProcesses.insert(pid, ps); ps->setIndex(d->mListProcesses.count()); d->mListProcesses.append(ps); ps->parent()->children().append(ps); Process *p = ps; do { Q_ASSERT(p); p = p->parent(); p->numChildren()++; } while (p->pid() != -1); ps->setParentPid(ppid); //Now we can actually get the process info bool success = updateProcessInfo(ps); emit endAddProcess(); return success; } bool Processes::updateOrAddProcess( long pid) { long ppid; if(d->mUsingHistoricalData) ppid = d->mHistoricProcesses->getParentPid(pid); else ppid = d->mAbstractProcesses->getParentPid(pid); if (ppid == pid) //Shouldn't ever happen ppid = -1; if(d->mToBeProcessed.contains(ppid)) { //Make sure that we update the parent before we update this one. Just makes things a bit easier. d->mToBeProcessed.remove(ppid); d->mProcessedLastTime.remove(ppid); //It may or may not be here - remove it if it is there updateOrAddProcess(ppid); } Process *ps = d->mProcesses.value(pid); if(!ps) return addProcess(pid, ppid); else return updateProcess(ps, ppid); } void Processes::updateAllProcesses(long updateDurationMS, Processes::UpdateFlags updateFlags) { d->mUpdateFlags = updateFlags; if(d->mUsingHistoricalData || d->mLastUpdated.elapsed() >= updateDurationMS || !d->mLastUpdated.isValid()) { d->mElapsedTimeMilliSeconds = d->mLastUpdated.restart(); if(d->mUsingHistoricalData) d->mHistoricProcesses->updateAllProcesses(d->mUpdateFlags); else d->mAbstractProcesses->updateAllProcesses(d->mUpdateFlags); //For a local machine, this will directly call Processes::processesUpdated() } } void Processes::processesUpdated() { //First really delete any processes that ended last time long pid; { QSetIterator i(d->mEndedProcesses); while( i.hasNext() ) { pid = i.next(); deleteProcess(pid); } } if(d->mUsingHistoricalData) d->mToBeProcessed = d->mHistoricProcesses->getAllPids(); else d->mToBeProcessed = d->mAbstractProcesses->getAllPids(); QSet beingProcessed(d->mToBeProcessed); //keep a copy so that we can replace mProcessedLastTime with this at the end of this function { QMutableSetIterator i(d->mToBeProcessed); while( i.hasNext()) { pid = i.next(); i.remove(); d->mProcessedLastTime.remove(pid); //It may or may not be here - remove it if it is there updateOrAddProcess(pid); //This adds the process or changes an existing one i.toFront(); //we can remove entries from this set elsewhere, so our iterator might be invalid. Reset it back to the start of the set } } { QSetIterator i(d->mProcessedLastTime); while( i.hasNext()) { //We saw these pids last time, but not this time. That means we have to mark them for deletion now pid = i.next(); d->markProcessesAsEnded(pid); } d->mEndedProcesses = d->mProcessedLastTime; } d->mProcessedLastTime = beingProcessed; //update the set for next time this function is called - return; + emit updated(); } void Processes::Private::markProcessesAsEnded(long pid) { Q_ASSERT(pid >= 0); Process *process = mProcesses.value(pid); if(!process) return; process->setStatus(Process::Ended); emit q->processChanged(process, false); } void Processes::deleteProcess(long pid) { Q_ASSERT(pid >= 0); Process *process = d->mProcesses.value(pid); if(!process) return; Q_FOREACH( Process *it, process->children()) { d->mProcessedLastTime.remove(it->pid()); deleteProcess(it->pid()); } emit beginRemoveProcess(process); d->mProcesses.remove(pid); d->mListProcesses.removeAll(process); process->parent()->children().removeAll(process); Process *p = process; do { Q_ASSERT(p); p = p->parent(); p->numChildren()--; } while (p->pid() != -1); #ifndef QT_NO_DEBUG int i = 0; #endif Q_FOREACH( Process *it, d->mListProcesses ) { if(it->index() > process->index()) it->setIndex(it->index() - 1); #ifndef QT_NO_DEBUG Q_ASSERT(it->index() == i++); #endif } delete process; emit endRemoveProcess(); } bool Processes::killProcess(long pid) { return sendSignal(pid, SIGTERM); } bool Processes::sendSignal(long pid, int sig) { d->mAbstractProcesses->errorCode = Unknown; if(d->mUsingHistoricalData) { d->mAbstractProcesses->errorCode = NotSupported; return false; } return d->mAbstractProcesses->sendSignal(pid, sig); } bool Processes::setNiceness(long pid, int priority) { d->mAbstractProcesses->errorCode = Unknown; if(d->mUsingHistoricalData) { d->mAbstractProcesses->errorCode = NotSupported; return false; } return d->mAbstractProcesses->setNiceness(pid, priority); } bool Processes::setScheduler(long pid, KSysGuard::Process::Scheduler priorityClass, int priority) { d->mAbstractProcesses->errorCode = Unknown; if(d->mUsingHistoricalData) { d->mAbstractProcesses->errorCode = NotSupported; return false; } return d->mAbstractProcesses->setScheduler(pid, priorityClass, priority); } bool Processes::setIoNiceness(long pid, KSysGuard::Process::IoPriorityClass priorityClass, int priority) { d->mAbstractProcesses->errorCode = Unknown; if(d->mUsingHistoricalData) { d->mAbstractProcesses->errorCode = NotSupported; return false; } return d->mAbstractProcesses->setIoNiceness(pid, priorityClass, priority); } bool Processes::supportsIoNiceness() { if(d->mUsingHistoricalData) return false; return d->mAbstractProcesses->supportsIoNiceness(); } long long Processes::totalPhysicalMemory() { return d->mAbstractProcesses->totalPhysicalMemory(); } long Processes::numberProcessorCores() { return d->mAbstractProcesses->numberProcessorCores(); } void Processes::answerReceived( int id, const QList& answer ) { KSysGuard::ProcessesRemote *processes = qobject_cast(d->mAbstractProcesses); if(processes) processes->answerReceived(id, answer); } QList< QPair > Processes::historiesAvailable() const { if(!d->mIsLocalHost) return QList< QPair >(); if(!d->mHistoricProcesses) d->mHistoricProcesses = new ProcessesATop(); return d->mHistoricProcesses->historiesAvailable(); } void Processes::useCurrentData() { if(d->mUsingHistoricalData) { delete d->mHistoricProcesses; d->mHistoricProcesses = nullptr; connect( d->mAbstractProcesses, &AbstractProcesses::processesUpdated, this, &Processes::processesUpdated); d->mUsingHistoricalData = false; } } bool Processes::setViewingTime(const QDateTime &when) { d->mAbstractProcesses->errorCode = Unknown; if(!d->mIsLocalHost) { d->mAbstractProcesses->errorCode = NotSupported; return false; } if(!d->mUsingHistoricalData) { if(!d->mHistoricProcesses) d->mHistoricProcesses = new ProcessesATop(); disconnect( d->mAbstractProcesses, &AbstractProcesses::processesUpdated, this, &Processes::processesUpdated); connect( d->mHistoricProcesses, &AbstractProcesses::processesUpdated, this, &Processes::processesUpdated); d->mUsingHistoricalData = true; } return d->mHistoricProcesses->setViewingTime(when); } bool Processes::loadHistoryFile(const QString &filename) { d->mAbstractProcesses->errorCode = Unknown; if(!d->mIsLocalHost) { d->mAbstractProcesses->errorCode = NotSupported; return false; } if(!d->mHistoricProcesses) d->mHistoricProcesses = new ProcessesATop(false); return d->mHistoricProcesses->loadHistoryFile(filename); } QString Processes::historyFileName() const { if(!d->mIsLocalHost || !d->mHistoricProcesses) return QString(); return d->mHistoricProcesses->historyFileName(); } QDateTime Processes::viewingTime() const { if(!d->mIsLocalHost || !d->mHistoricProcesses) return QDateTime(); return d->mHistoricProcesses->viewingTime(); } bool Processes::isHistoryAvailable() const { if(!d->mIsLocalHost) return false; if(!d->mHistoricProcesses) d->mHistoricProcesses = new ProcessesATop(); return d->mHistoricProcesses->isHistoryAvailable(); } } diff --git a/processcore/processes.h b/processcore/processes.h index d2fe0d4..29d489d 100644 --- a/processcore/processes.h +++ b/processcore/processes.h @@ -1,268 +1,270 @@ /* This file is part of the KDE project Copyright (C) 2007 John Tapsell 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. */ #ifndef PROCESSES_H_ #define PROCESSES_H_ #include "process.h" #include #include namespace KSysGuard { /** * This class retrieves the processes currently running in an OS independent way. * * To use, do something like: * * \code * #include "processes.h> * #include "process.h> * * KSysGuard::Processes *processes = new KSysGuard::Processes() * QHash processlist = processes->getProcesses(); * foreach( Process * process, processlist) { * kDebug() << "Process with pid " << process->pid() << " is called " << process->name; * } * delete processes; * processes = NULL; * \endcode * * @author John Tapsell */ #ifdef Q_WS_WIN class Processes : public QObject #else class Q_DECL_EXPORT Processes : public QObject #endif { Q_OBJECT public: Processes(const QString &hostname = QString(), QObject * parent = nullptr); ~Processes() override; enum UpdateFlag { StandardInformation = 1, IOStatistics = 2, XMemory = 4 }; Q_DECLARE_FLAGS(UpdateFlags, UpdateFlag) enum Error { Unknown = 0, InvalidPid, InvalidParameter, InsufficientPermissions, ProcessDoesNotExistOrZombie, NotSupported }; /** * Update all the process information. After calling this, /proc or equivalent is scanned and * the signals processChanged, etc are emitted. * * Set updateDuration to whatever time period that you update, in milliseconds. * For example, if you update every 2000ms, set this to 2000. That way it won't update * more often than needed. */ void updateAllProcesses(long updateDurationMS = 0, Processes::UpdateFlags updateFlags = nullptr); /** * Return information for one specific process. Call getProcess(0) to get the * fake process used as the top most parent for all processes. * This doesn't fetch any new information and so returns almost instantly. * Call updateAllProcesses() to actually fetch the process information. */ Process *getProcess(long pid) const; /** * Get the error code for the last command that failed. */ Error lastError() const; /** * Kill the specified process. You may not have the privilege to kill the process. * The process may also chose to ignore the command. Send the SIGKILL signal to kill * the process immediately. You may lose any unsaved data. * * @returns Successful or not in killing the process */ bool killProcess(long pid); /** * Send the specified named POSIX signal to the process given. * * For example, to indicate for process 324 to STOP do: * \code * #include * ... * * KSysGuard::Processes::sendSignal(23, SIGSTOP); * \endcode * */ bool sendSignal(long pid, int sig); /** * Set the priority for a process. This is from 19 (very nice, lowest priority) to * -20 (highest priority). The default value for a process is 0. * * @return false if you do not have permission to set the priority */ bool setNiceness(long pid, int priority); /** * Set the scheduler for a process. This is defined according to POSIX.1-2001 * See "man sched_setscheduler" for more information. * * @p priorityClass One of SCHED_FIFO, SCHED_RR, SCHED_OTHER, and SCHED_BATCH * @p priority Set to 0 for SCHED_OTHER and SCHED_BATCH. Between 1 and 99 for SCHED_FIFO and SCHED_RR * @return false if you do not have permission to set the priority */ bool setScheduler(long pid, KSysGuard::Process::Scheduler priorityClass, int priority); /** * Set the io priority for a process. This is from 7 (very nice, lowest io priority) to * 0 (highest priority). The default value is determined as: io_nice = (cpu_nice + 20) / 5. * * @return false if you do not have permission to set the priority */ bool setIoNiceness(long pid, KSysGuard::Process::IoPriorityClass priorityClass, int priority); /** * Returns true if ionice is supported on this system */ bool supportsIoNiceness(); /** * Return the internal pointer of all the processes. The order of the processes * is guaranteed to never change. Call updateAllProcesses() first to actually * update the information. */ const QList< Process *> &getAllProcesses() const; /** * Return the number of processes. Call updateAllProcesses() to actually * update the information. * * This is equivalent to getAllProcesses().count() */ int processCount() const; /** * Return the total amount of physical memory in KB. This is fast (just a system call) * Returns 0 on error */ long long totalPhysicalMemory(); /** * Return the number of processor cores enabled. * (A system can disable processors. Disabled processors are not counted here). * This is fast (just a system call) */ long numberProcessorCores(); /** Update/add process for given pid immediately */ bool updateOrAddProcess( long pid); /** Whether we can get historic process and system data */ bool isHistoryAvailable() const; /** Stop using historical data and use the most recent up-to-date data */ void useCurrentData(); /** Return a list of end times and intervals for all the available history */ QList< QPair > historiesAvailable() const; /** Use historical process data closest to the given date-time. * Returns false if it is outside the range available or there is a problem * getting the data. */ bool setViewingTime(const QDateTime &when); QDateTime viewingTime() const; bool loadHistoryFile(const QString &filename); QString historyFileName() const; public Q_SLOTS: /** The abstract processes has updated its list of processes */ void processesUpdated(); Q_SIGNALS: /** The data for a process has changed. * if @p onlyTotalCpu is set, only the total cpu usage has been updated. * process->changes contains a bit field indicating what has changed since the last time this was emitted * for this process */ void processChanged( KSysGuard::Process *process, bool onlyTotalCpu); /** * This indicates we are about to add a process in the model. * The process already has the pid, ppid and tree_parent set up. */ void beginAddProcess( KSysGuard::Process *process); /** * We have finished inserting a process */ void endAddProcess(); /** * This indicates we are about to remove a process in the model. Emit the appropriate signals */ void beginRemoveProcess( KSysGuard::Process *process); /** * We have finished removing a process */ void endRemoveProcess(); /** * This indicates we are about move a process from one parent to another. */ void beginMoveProcess(KSysGuard::Process *process, KSysGuard::Process *new_parent); /** * We have finished moving the process */ void endMoveProcess(); + + void updated(); protected: class Private; Private *d; private: inline void deleteProcess(long pid); bool updateProcess( Process *process, long ppid); bool updateProcessInfo(Process *ps); bool addProcess(long pid, long ppid); Q_SIGNALS: /** For a remote machine, we rely on being able to communicate with ksysguardd. * This must be dealt with by the program including this widget. It must listen to our * 'runCommand' signal, and run the given command, with the given id. */ void runCommand(const QString &command, int id); public: /** For a remote machine, we rely on being able to communicate with ksysguardd. * The programming using this must call this slot when an answer is received from ksysguardd, * in response to a runCommand request. The id identifies the answer */ void answerReceived( int id, const QList& answer ); }; Q_DECLARE_OPERATORS_FOR_FLAGS(Processes::UpdateFlags) } #endif diff --git a/processcore/unit.cpp b/processcore/unit.cpp new file mode 100644 index 0000000..e8a395a --- /dev/null +++ b/processcore/unit.cpp @@ -0,0 +1,3 @@ +#include "unit.h" + +#include "moc_unit.cpp" diff --git a/processcore/unit.h b/processcore/unit.h new file mode 100644 index 0000000..5ad3f57 --- /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/processui/ProcessModel.cpp b/processui/ProcessModel.cpp index b66b47f..9018c54 100644 --- a/processui/ProcessModel.cpp +++ b/processui/ProcessModel.cpp @@ -1,2309 +1,2360 @@ /* KSysGuard, the KDE System Guard Copyright (c) 1999, 2000 Chris Schlaeger Copyright (c) 2006-2007 John Tapsell 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 "ProcessModel.h" #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 #include #include #include #include #include #include #include #include #include #include #include #include #include #define HEADING_X_ICON_SIZE 16 #define MILLISECONDS_TO_SHOW_RED_FOR_KILLED_PROCESS 2000 #define GET_OWN_ID #ifdef GET_OWN_ID /* For getuid*/ #include #include #endif #ifdef HAVE_XRES #include #endif extern QApplication* Qapp; static QString formatByteSize(qlonglong amountInKB, int units) { enum { UnitsAuto, UnitsKB, UnitsMB, UnitsGB, UnitsTB, UnitsPB }; static QString kString = i18n("%1 K", QString::fromLatin1("%1")); static QString mString = i18n("%1 M", QString::fromLatin1("%1")); static QString gString = i18n("%1 G", QString::fromLatin1("%1")); static QString tString = i18n("%1 T", QString::fromLatin1("%1")); static QString pString = i18n("%1 P", QString::fromLatin1("%1")); double amount; if (units == UnitsAuto) { if (amountInKB < 1024.0*0.9) units = UnitsKB; // amount < 0.9 MiB == KiB else if (amountInKB < 1024.0*1024.0*0.9) units = UnitsMB; // amount < 0.9 GiB == MiB else if (amountInKB < 1024.0*1024.0*1024.0*0.9) units = UnitsGB; // amount < 0.9 TiB == GiB else if (amountInKB < 1024.0*1024.0*1024.0*1024.0*0.9) units = UnitsTB; // amount < 0.9 PiB == TiB else units = UnitsPB; } switch(units) { case UnitsKB: return kString.arg(QLocale().toString(amountInKB)); case UnitsMB: amount = amountInKB/1024.0; return mString.arg(QLocale().toString(amount, 'f', 1)); case UnitsGB: amount = amountInKB/(1024.0*1024.0); if(amount < 0.1 && amount > 0.05) amount = 0.1; return gString.arg(QLocale().toString(amount, 'f', 1)); case UnitsTB: amount = amountInKB/(1024.0*1024.0*1024.0); if(amount < 0.1 && amount > 0.05) amount = 0.1; return tString.arg(QLocale().toString(amount, 'f', 1)); case UnitsPB: amount = amountInKB/(1024.0*1024.0*1024.0*1024.0); if(amount < 0.1 && amount > 0.05) amount = 0.1; return pString.arg(QLocale().toString(amount, 'f', 1)); default: return QLatin1String(""); // error } } ProcessModelPrivate::ProcessModelPrivate() : mBlankPixmap(HEADING_X_ICON_SIZE,1) { mBlankPixmap.fill(QColor(0,0,0,0)); mSimple = true; mIsLocalhost = true; mMemTotal = -1; mNumProcessorCores = 1; mProcesses = nullptr; mShowChildTotals = true; mShowCommandLineOptions = false; mShowingTooltips = true; mNormalizeCPUUsage = true; mIoInformation = ProcessModel::ActualBytes; #ifdef HAVE_XRES mHaveXRes = false; #endif mHaveTimer = false, mTimerId = -1, mMovingRow = false; mRemovingRow = false; mInsertingRow = false; #if HAVE_X11 mIsX11 = QX11Info::isPlatformX11(); #else mIsX11 = false; #endif } ProcessModelPrivate::~ProcessModelPrivate() { #if HAVE_X11 qDeleteAll(mPidToWindowInfo); #endif delete mProcesses; mProcesses = nullptr; } ProcessModel::ProcessModel(QObject* parent, const QString &host) : QAbstractItemModel(parent), d(new ProcessModelPrivate) { d->q=this; #ifdef HAVE_XRES if (d->mIsX11) { int event, error, major, minor; d->mHaveXRes = XResQueryExtension(QX11Info::display(), &event, &error) && XResQueryVersion(QX11Info::display(), &major, &minor); } #endif if(host.isEmpty() || host == QLatin1String("localhost")) { d->mHostName = QString(); d->mIsLocalhost = true; } else { d->mHostName = host; d->mIsLocalhost = false; } setupHeader(); d->setupProcesses(); #if HAVE_X11 d->setupWindows(); #endif d->mUnits = UnitsKB; d->mIoUnits = UnitsKB; } bool ProcessModel::lessThan(const QModelIndex &left, const QModelIndex &right) const { //Because we need to sort Descendingly by default for most of the headings, we often return left > right KSysGuard::Process *processLeft = reinterpret_cast< KSysGuard::Process * > (left.internalPointer()); KSysGuard::Process *processRight = reinterpret_cast< KSysGuard::Process * > (right.internalPointer()); Q_ASSERT(processLeft); Q_ASSERT(processRight); Q_ASSERT(left.column() == right.column()); switch(left.column()) { case HeadingUser: { /* Sorting by user will be the default and the most common. We want to sort in the most useful way that we can. We need to return a number though. This code is based on that sorting ascendingly should put the current user at the top First the user we are running as should be at the top. Then any other users in the system. Then at the bottom the 'system' processes. We then sort by cpu usage to sort by that, then finally sort by memory usage */ /* First, place traced processes at the very top, ignoring any other sorting criteria */ if(processLeft->tracerpid() >= 0) return true; if(processRight->tracerpid() >= 0) return false; /* Sort by username. First group into own user, normal users, system users */ if(processLeft->uid() != processRight->uid()) { //We primarily sort by username if(d->mIsLocalhost) { int ownUid = getuid(); if(processLeft->uid() == ownUid) return true; //Left is our user, right is not. So left is above right if(processRight->uid() == ownUid) return false; //Left is not our user, right is. So right is above left } bool isLeftSystemUser = processLeft->uid() < 100 || !canUserLogin(processLeft->uid()); bool isRightSystemUser = processRight->uid() < 100 || !canUserLogin(processRight->uid()); if(isLeftSystemUser && !isRightSystemUser) return false; //System users are less than non-system users if(!isLeftSystemUser && isRightSystemUser) return true; //They are either both system users, or both non-system users. //So now sort by username return d->getUsernameForUser(processLeft->uid(), false) < d->getUsernameForUser(processRight->uid(), false); } /* 2nd sort order - Graphics Windows */ //Both columns have the same user. Place processes with windows at the top if(processLeft->hasManagedGuiWindow() && !processRight->hasManagedGuiWindow()) return true; if(!processLeft->hasManagedGuiWindow() && processRight->hasManagedGuiWindow()) return false; /* 3rd sort order - CPU Usage */ int leftCpu, rightCpu; if(d->mSimple || !d->mShowChildTotals) { leftCpu = processLeft->userUsage() + processLeft->sysUsage(); rightCpu = processRight->userUsage() + processRight->sysUsage(); } else { leftCpu = processLeft->totalUserUsage() + processLeft->totalSysUsage(); rightCpu = processRight->totalUserUsage() + processRight->totalSysUsage(); } if(leftCpu != rightCpu) return leftCpu > rightCpu; /* 4th sort order - Memory Usage */ qlonglong memoryLeft = (processLeft->vmURSS() != -1) ? processLeft->vmURSS() : processLeft->vmRSS(); qlonglong memoryRight = (processRight->vmURSS() != -1) ? processRight->vmURSS() : processRight->vmRSS(); return memoryLeft > memoryRight; } case HeadingCPUUsage: { int leftCpu, rightCpu; if(d->mSimple || !d->mShowChildTotals) { leftCpu = processLeft->userUsage() + processLeft->sysUsage(); rightCpu = processRight->userUsage() + processRight->sysUsage(); } else { leftCpu = processLeft->totalUserUsage() + processLeft->totalSysUsage(); rightCpu = processRight->totalUserUsage() + processRight->totalSysUsage(); } return leftCpu > rightCpu; } case HeadingCPUTime: { return (processLeft->userTime() + processLeft->sysTime()) > (processRight->userTime() + processRight->sysTime()); } case HeadingMemory: { qlonglong memoryLeft = (processLeft->vmURSS() != -1) ? processLeft->vmURSS() : processLeft->vmRSS(); qlonglong memoryRight = (processRight->vmURSS() != -1) ? processRight->vmURSS() : processRight->vmRSS(); return memoryLeft > memoryRight; } case HeadingVmPSS: { return processLeft->vmPSS() > processRight->vmPSS(); } case HeadingStartTime: { return processLeft->startTime() > processRight->startTime(); } case HeadingNoNewPrivileges: return processLeft->noNewPrivileges() > processRight->noNewPrivileges(); case HeadingXMemory: return processLeft->pixmapBytes() > processRight->pixmapBytes(); case HeadingVmSize: return processLeft->vmSize() > processRight->vmSize(); case HeadingSharedMemory: { qlonglong memoryLeft = (processLeft->vmURSS() != -1) ? processLeft->vmRSS() - processLeft->vmURSS() : 0; qlonglong memoryRight = (processRight->vmURSS() != -1) ? processRight->vmRSS() - processRight->vmURSS() : 0; return memoryLeft > memoryRight; } case HeadingPid: return processLeft->pid() > processRight->pid(); case HeadingNiceness: //Sort by scheduler first if(processLeft->scheduler() != processRight->scheduler()) { if(processLeft->scheduler() == KSysGuard::Process::RoundRobin || processLeft->scheduler() == KSysGuard::Process::Fifo) return true; if(processRight->scheduler() == KSysGuard::Process::RoundRobin || processRight->scheduler() == KSysGuard::Process::Fifo) return false; if(processLeft->scheduler() == KSysGuard::Process::Other) return true; if(processRight->scheduler() == KSysGuard::Process::Other) return false; if(processLeft->scheduler() == KSysGuard::Process::Batch) return true; } if(processLeft->niceLevel() == processRight->niceLevel()) return processLeft->pid() < processRight->pid(); //Subsort by pid if the niceLevel is the same return processLeft->niceLevel() < processRight->niceLevel(); case HeadingTty: { if(processLeft->tty() == processRight->tty()) return processLeft->pid() < processRight->pid(); //Both ttys are the same. Sort by pid if(processLeft->tty().isEmpty()) return false; //Only left is empty (since they aren't the same) else if(processRight->tty().isEmpty()) return true; //Only right is empty //Neither left or right is empty. The tty string is like "tty10" so split this into "tty" and "10" //and sort by the string first, then sort by the number QRegExp regexpLeft(QStringLiteral("^(\\D*)(\\d*)$")); QRegExp regexpRight(regexpLeft); if(regexpLeft.indexIn(QString::fromUtf8(processLeft->tty())) == -1 || regexpRight.indexIn(QString::fromUtf8(processRight->tty())) == -1) return processLeft->tty() < processRight->tty(); int nameMatch = regexpLeft.cap(1).compare(regexpRight.cap(1)); if(nameMatch < 0) return true; if(nameMatch > 0) return false; return regexpLeft.cap(2).toInt() < regexpRight.cap(2).toInt(); } case HeadingIoRead: switch(d->mIoInformation) { case ProcessModel::Bytes: return processLeft->ioCharactersRead() > processRight->ioCharactersRead(); case ProcessModel::Syscalls: return processLeft->ioReadSyscalls() > processRight->ioReadSyscalls(); case ProcessModel::ActualBytes: return processLeft->ioCharactersActuallyRead() > processRight->ioCharactersActuallyRead(); case ProcessModel::BytesRate: return processLeft->ioCharactersReadRate() > processRight->ioCharactersReadRate(); case ProcessModel::SyscallsRate: return processLeft->ioReadSyscallsRate() > processRight->ioReadSyscallsRate(); case ProcessModel::ActualBytesRate: return processLeft->ioCharactersActuallyReadRate() > processRight->ioCharactersActuallyReadRate(); } return {}; // It actually never gets here since all cases are handled in the switch, but makes gcc not complain about a possible fall through case HeadingIoWrite: switch(d->mIoInformation) { case ProcessModel::Bytes: return processLeft->ioCharactersWritten() > processRight->ioCharactersWritten(); case ProcessModel::Syscalls: return processLeft->ioWriteSyscalls() > processRight->ioWriteSyscalls(); case ProcessModel::ActualBytes: return processLeft->ioCharactersActuallyWritten() > processRight->ioCharactersActuallyWritten(); case ProcessModel::BytesRate: return processLeft->ioCharactersWrittenRate() > processRight->ioCharactersWrittenRate(); case ProcessModel::SyscallsRate: return processLeft->ioWriteSyscallsRate() > processRight->ioWriteSyscallsRate(); case ProcessModel::ActualBytesRate: return processLeft->ioCharactersActuallyWrittenRate() > processRight->ioCharactersActuallyWrittenRate(); } } //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() { delete d; } KSysGuard::Processes *ProcessModel::processController() const { return d->mProcesses; } #if HAVE_X11 void ProcessModelPrivate::windowRemoved(WId wid) { WindowInfo *window = mWIdToWindowInfo.take(wid); if(!window) return; qlonglong pid = window->pid; QMultiHash::iterator i = mPidToWindowInfo.find(pid); while (i != mPidToWindowInfo.end() && i.key() == pid) { if(i.value()->wid == wid) { i = mPidToWindowInfo.erase(i); break; } else i++; } delete window; //Update the model so that it redraws and resorts KSysGuard::Process *process = mProcesses->getProcess(pid); if(!process) return; int row; if(mSimple) row = process->index(); else row = process->parent()->children().indexOf(process); QModelIndex index2 = q->createIndex(row, ProcessModel::HeadingXTitle, process); emit q->dataChanged(index2, index2); } #endif #if HAVE_X11 void ProcessModelPrivate::setupWindows() { if (!mIsX11) { return; } connect( KWindowSystem::self(), SIGNAL(windowChanged(WId,uint)), this, SLOT(windowChanged(WId,uint))); connect( KWindowSystem::self(), &KWindowSystem::windowAdded, this, &ProcessModelPrivate::windowAdded); connect( KWindowSystem::self(), &KWindowSystem::windowRemoved, this, &ProcessModelPrivate::windowRemoved); //Add all the windows that KWin is managing - i.e. windows that the user can see const QList windows = KWindowSystem::windows(); for (auto it = windows.begin(); it != windows.end(); ++it ) { updateWindowInfo(*it, ~0u, true); } } #endif #ifdef HAVE_XRES bool ProcessModelPrivate::updateXResClientData() { if (!mIsX11) { return false; } XResClient *clients; int count; XResQueryClients(QX11Info::display(), &count, &clients); mXResClientResources.clear(); for (int i=0; i < count; i++) mXResClientResources.insert(-(qlonglong)(clients[i].resource_base), clients[i].resource_mask); if(clients) XFree(clients); return true; } void ProcessModelPrivate::queryForAndUpdateAllXWindows() { if (!mIsX11) { return; } updateXResClientData(); Window *children, dummy; unsigned int count; Status result = XQueryTree(QX11Info::display(), QX11Info::appRootWindow(), &dummy, &dummy, &children, &count); if(!result) return; if(!updateXResClientData()) return; for (uint i=0; i < count; ++i) { WId wid = children[i]; QMap::iterator iter = mXResClientResources.lowerBound(-(qlonglong)(wid)); if(iter == mXResClientResources.end()) continue; //We couldn't find it this time :-/ if(-iter.key() != (qlonglong)(wid & ~iter.value())) continue; //Already added this window //Get the PID for this window if we do not know it NETWinInfo info(QX11Info::connection(), wid, QX11Info::appRootWindow(), NET::WMPid, NET::Properties2()); qlonglong pid = info.pid(); if(!pid) continue; //We found a window with this client mXResClientResources.erase(iter); KSysGuard::Process *process = mProcesses->getProcess(pid); if(!process) return; //shouldn't really happen.. maybe race condition etc unsigned long previousPixmapBytes = process->pixmapBytes(); //Now update the pixmap bytes for this window bool success = XResQueryClientPixmapBytes(QX11Info::display(), wid, &process->pixmapBytes()); if(!success) process->pixmapBytes() = 0; if(previousPixmapBytes != process->pixmapBytes()) { int row; if(mSimple) row = process->index(); else row = process->parent()->children().indexOf(process); QModelIndex index = q->createIndex(row, ProcessModel::HeadingXMemory, process); emit q->dataChanged(index, index); } } if(children) XFree((char*)children); } #endif void ProcessModelPrivate::setupProcesses() { if(mProcesses) { #ifdef Q_WS_X11_DISABLE mWIdToWindowInfo.clear(); mPidToWindowInfo.clear(); #endif delete mProcesses; mProcesses = nullptr; q->beginResetModel(); 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); connect( mProcesses, &KSysGuard::Processes::endAddProcess, this, &ProcessModelPrivate::endInsertRow); connect( mProcesses, &KSysGuard::Processes::beginRemoveProcess, this, &ProcessModelPrivate::beginRemoveRow); connect( mProcesses, &KSysGuard::Processes::endRemoveProcess, this, &ProcessModelPrivate::endRemoveRow); connect( mProcesses, &KSysGuard::Processes::beginMoveProcess, this, &ProcessModelPrivate::beginMoveProcess); 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 void ProcessModelPrivate::windowChanged(WId wid, unsigned int properties) { updateWindowInfo(wid, properties, false); } void ProcessModelPrivate::windowAdded(WId wid) { updateWindowInfo(wid, ~0u, true); } void ProcessModelPrivate::updateWindowInfo(WId wid, unsigned int properties, bool newWindow) { if (!mIsX11) { return; } properties &= (NET::WMPid | NET::WMVisibleName | NET::WMName | NET::WMIcon); if(!properties) return; //Nothing interesting changed WindowInfo *w = mWIdToWindowInfo.value(wid); if(!w && !newWindow) return; //We do not have a record of this window and this is not a new window if(properties == NET::WMIcon) { if(w) w->icon = KWindowSystem::icon(wid, HEADING_X_ICON_SIZE, HEADING_X_ICON_SIZE, true); return; } /* Get PID for window */ NETWinInfo info(QX11Info::connection(), wid, QX11Info::appRootWindow(), NET::Properties(properties) & ~NET::WMIcon, NET::Properties2()); if(!w) { //We know that this must be a newWindow qlonglong pid = info.pid(); if(!(properties & NET::WMPid && pid)) return; //No PID for the window - this happens if the process did not set _NET_WM_PID //If we are to get the PID only, we are only interested in the XRes info for this, //so don't bother if we already have this info if(properties == NET::WMPid && mPidToWindowInfo.contains(pid)) return; w = new WindowInfo(wid, pid); mPidToWindowInfo.insertMulti(pid, w); mWIdToWindowInfo.insert(wid, w); } if(w && (properties & NET::WMIcon)) w->icon = KWindowSystem::icon(wid, HEADING_X_ICON_SIZE, HEADING_X_ICON_SIZE, true); if(properties & NET::WMVisibleName && info.visibleName()) w->name = QString::fromUtf8(info.visibleName()); else if(properties & NET::WMName) w->name = QString::fromUtf8(info.name()); else if(properties & (NET::WMName | NET::WMVisibleName)) w->name.clear(); KSysGuard::Process *process = mProcesses->getProcess(w->pid); if(!process) { return; //This happens when a new window is detected before we've read in the process } int row; if(mSimple) row = process->index(); else row = process->parent()->children().indexOf(process); if(!process->hasManagedGuiWindow()) { process->hasManagedGuiWindow() = true; //Since this is the first window for a process, invalidate HeadingName so that //if we are sorting by name this gets taken into account QModelIndex index1 = q->createIndex(row, ProcessModel::HeadingName, process); emit q->dataChanged(index1, index1); } QModelIndex index2 = q->createIndex(row, ProcessModel::HeadingXTitle, process); emit q->dataChanged(index2, index2); } #endif void ProcessModel::update(long updateDurationMSecs, KSysGuard::Processes::UpdateFlags updateFlags) { if(updateFlags != KSysGuard::Processes::XMemory) { d->mProcesses->updateAllProcesses(updateDurationMSecs, updateFlags); if(d->mMemTotal <= 0) d->mMemTotal = d->mProcesses->totalPhysicalMemory(); } #ifdef HAVE_XRES //Add all the rest of the windows if(d->mHaveXRes && updateFlags.testFlag(KSysGuard::Processes::XMemory)) d->queryForAndUpdateAllXWindows(); #endif } QString ProcessModelPrivate::getStatusDescription(KSysGuard::Process::ProcessStatus status) const { switch( status) { case KSysGuard::Process::Running: return i18n("- Process is doing some work."); case KSysGuard::Process::Sleeping: return i18n("- Process is waiting for something to happen."); case KSysGuard::Process::Stopped: return i18n("- Process has been stopped. It will not respond to user input at the moment."); case KSysGuard::Process::Zombie: return i18n("- Process has finished and is now dead, but the parent process has not cleaned up."); case KSysGuard::Process::Ended: // return i18n("- Process has finished and no longer exists."); default: return QString(); } } KSysGuard::Process *ProcessModel::getProcessAtIndex(int index) const { Q_ASSERT(d->mSimple); return d->mProcesses->getAllProcesses().at(index); } int ProcessModel::rowCount(const QModelIndex &parent) const { if(d->mSimple) { if(parent.isValid()) return 0; //In flat mode, none of the processes have children return d->mProcesses->processCount(); } //Deal with the case that we are showing it as a tree KSysGuard::Process *process; if(parent.isValid()) { if(parent.column() != 0) return 0; //For a treeview we say that only the first column has children process = reinterpret_cast< KSysGuard::Process * > (parent.internalPointer()); //when parent is invalid, it must be the root level which we set as 0 } else { process = d->mProcesses->getProcess(-1); } Q_ASSERT(process); int num_rows = process->children().count(); return num_rows; } 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 { if(d->mSimple) { if(parent.isValid()) return 0; //In flat mode, none of the processes have children return !d->mProcesses->getAllProcesses().isEmpty(); } //Deal with the case that we are showing it as a tree KSysGuard::Process *process; if(parent.isValid()) { if(parent.column() != 0) return false; //For a treeview we say that only the first column has children process = reinterpret_cast< KSysGuard::Process * > (parent.internalPointer()); //when parent is invalid, it must be the root level which we set as 0 } else { process = d->mProcesses->getProcess(-1); } Q_ASSERT(process); bool has_children = !process->children().isEmpty(); Q_ASSERT((rowCount(parent) > 0) == has_children); return has_children; } 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(); if( d->mProcesses->processCount() <= row) return QModelIndex(); return createIndex( row, column, d->mProcesses->getAllProcesses().at(row)); } //Deal with the case that we are showing it as a tree KSysGuard::Process *parent_process = nullptr; if(parent.isValid()) //not valid for init or children without parents, so use our special item with pid of 0 parent_process = reinterpret_cast< KSysGuard::Process * > (parent.internalPointer()); else parent_process = d->mProcesses->getProcess(-1); Q_ASSERT(parent_process); if(parent_process->children().count() > row) return createIndex(row,column, parent_process->children()[row]); else { return QModelIndex(); } } bool ProcessModel::isSimpleMode() const { return d->mSimple; } void ProcessModelPrivate::processChanged(KSysGuard::Process *process, bool onlyTotalCpu) { int row; if(mSimple) row = process->index(); else row = process->parent()->children().indexOf(process); if (!process->timeKillWasSent().isNull()) { int elapsed = process->timeKillWasSent().elapsed(); if (elapsed < MILLISECONDS_TO_SHOW_RED_FOR_KILLED_PROCESS) { if (!mPidsToUpdate.contains(process->pid())) mPidsToUpdate.append(process->pid()); QModelIndex index1 = q->createIndex(row, 0, process); QModelIndex index2 = q->createIndex(row, mHeadings.count()-1, process); emit q->dataChanged(index1, index2); if (!mHaveTimer) { mHaveTimer = true; mTimerId = startTimer(100); } } } int totalUpdated = 0; Q_ASSERT(row != -1); //Something has gone very wrong if(onlyTotalCpu) { if(mShowChildTotals) { //Only the total cpu usage changed, so only update that QModelIndex index = q->createIndex(row, ProcessModel::HeadingCPUUsage, process); emit q->dataChanged(index, index); } return; } else { if(process->changes() == KSysGuard::Process::Nothing) { return; //Nothing changed } if(process->changes() & KSysGuard::Process::Uids) { totalUpdated++; QModelIndex index = q->createIndex(row, ProcessModel::HeadingUser, process); emit q->dataChanged(index, index); } if(process->changes() & KSysGuard::Process::Tty) { totalUpdated++; QModelIndex index = q->createIndex(row, ProcessModel::HeadingTty, process); emit q->dataChanged(index, index); } if(process->changes() & (KSysGuard::Process::Usage | KSysGuard::Process::Status) || (process->changes() & KSysGuard::Process::TotalUsage && mShowChildTotals)) { totalUpdated+=2; QModelIndex index = q->createIndex(row, ProcessModel::HeadingCPUUsage, process); emit q->dataChanged(index, index); index = q->createIndex(row, ProcessModel::HeadingCPUTime, process); emit q->dataChanged(index, index); //Because of our sorting, changing usage needs to also invalidate the User column index = q->createIndex(row, ProcessModel::HeadingUser, process); emit q->dataChanged(index, index); } if(process->changes() & KSysGuard::Process::Status) { totalUpdated+=2; QModelIndex index = q->createIndex(row, ProcessModel::HeadingNoNewPrivileges, process); emit q->dataChanged(index, index); index = q->createIndex(row, ProcessModel::HeadingCGroup, process); emit q->dataChanged(index, index); index = q->createIndex(row, ProcessModel::HeadingMACContext, process); emit q->dataChanged(index, index); } if(process->changes() & KSysGuard::Process::NiceLevels) { totalUpdated++; QModelIndex index = q->createIndex(row, ProcessModel::HeadingNiceness, process); emit q->dataChanged(index, index); } if(process->changes() & KSysGuard::Process::VmSize) { totalUpdated++; QModelIndex index = q->createIndex(row, ProcessModel::HeadingVmSize, process); emit q->dataChanged(index, index); } if(process->changes() & (KSysGuard::Process::VmSize | KSysGuard::Process::VmRSS | KSysGuard::Process::VmURSS)) { totalUpdated+=2; QModelIndex index = q->createIndex(row, ProcessModel::HeadingMemory, process); emit q->dataChanged(index, index); QModelIndex index2 = q->createIndex(row, ProcessModel::HeadingSharedMemory, process); emit q->dataChanged(index2, index2); //Because of our sorting, changing usage needs to also invalidate the User column index = q->createIndex(row, ProcessModel::HeadingUser, process); emit q->dataChanged(index, index); } if (process->changes() & KSysGuard::Process::VmPSS) { totalUpdated++; auto index = q->createIndex(row, ProcessModel::HeadingVmPSS, process); emit q->dataChanged(index, index); } if(process->changes() & KSysGuard::Process::Name) { totalUpdated++; QModelIndex index = q->createIndex(row, ProcessModel::HeadingName, process); emit q->dataChanged(index, index); } if(process->changes() & KSysGuard::Process::Command) { totalUpdated++; QModelIndex index = q->createIndex(row, ProcessModel::HeadingCommand, process); emit q->dataChanged(index, index); } if(process->changes() & KSysGuard::Process::Login) { totalUpdated++; QModelIndex index = q->createIndex(row, ProcessModel::HeadingUser, process); emit q->dataChanged(index, index); } if(process->changes() & KSysGuard::Process::IO) { totalUpdated++; QModelIndex index = q->createIndex(row, ProcessModel::HeadingIoRead, process); emit q->dataChanged(index, index); index = q->createIndex(row, ProcessModel::HeadingIoWrite, process); emit q->dataChanged(index, index); } /* Normally this would only be called if changes() tells * us to. We need to update the timestamp even if the value * didn't change though. */ auto historyMapEntry = mMapProcessCPUHistory.find(process); if(historyMapEntry != mMapProcessCPUHistory.end()) { auto &history = *historyMapEntry; unsigned long timestamp = QDateTime::currentMSecsSinceEpoch(); // Only add an entry if the latest one is older than MIN_HIST_AGE if(history.isEmpty() || timestamp - history.constLast().timestamp > MIN_HIST_AGE) { if(history.size() == MAX_HIST_ENTRIES) { history.removeFirst(); } float usage = (process->totalUserUsage() + process->totalSysUsage()) / (100.0f * mNumProcessorCores); history.push_back({static_cast(QDateTime::currentMSecsSinceEpoch()), usage}); } } } } void ProcessModelPrivate::beginInsertRow( KSysGuard::Process *process) { Q_ASSERT(process); Q_ASSERT(!mRemovingRow); Q_ASSERT(!mInsertingRow); Q_ASSERT(!mMovingRow); mInsertingRow = true; #if HAVE_X11 process->hasManagedGuiWindow() = mPidToWindowInfo.contains(process->pid()); #endif if(mSimple) { int row = mProcesses->processCount(); q->beginInsertRows( QModelIndex(), row, row ); return; } //Deal with the case that we are showing it as a tree int row = process->parent()->children().count(); QModelIndex parentModelIndex = q->getQModelIndex(process->parent(), 0); //Only here can we actually change the model. First notify the view/proxy models then modify q->beginInsertRows(parentModelIndex, row, row); } void ProcessModelPrivate::endInsertRow() { Q_ASSERT(!mRemovingRow); Q_ASSERT(mInsertingRow); Q_ASSERT(!mMovingRow); mInsertingRow = false; q->endInsertRows(); } void ProcessModelPrivate::beginRemoveRow( KSysGuard::Process *process ) { Q_ASSERT(process); Q_ASSERT(process->pid() >= 0); Q_ASSERT(!mRemovingRow); Q_ASSERT(!mInsertingRow); Q_ASSERT(!mMovingRow); mRemovingRow = true; mMapProcessCPUHistory.remove(process); if(mSimple) { return q->beginRemoveRows(QModelIndex(), process->index(), process->index()); } else { int row = process->parent()->children().indexOf(process); if(row == -1) { qCDebug(LIBKSYSGUARD_PROCESSUI) << "A serious problem occurred in remove row."; mRemovingRow = false; return; } return q->beginRemoveRows(q->getQModelIndex(process->parent(), 0), row, row); } } void ProcessModelPrivate::endRemoveRow() { Q_ASSERT(!mInsertingRow); Q_ASSERT(!mMovingRow); if(!mRemovingRow) return; mRemovingRow = false; q->endRemoveRows(); } void ProcessModelPrivate::beginMoveProcess(KSysGuard::Process *process, KSysGuard::Process *new_parent) { Q_ASSERT(!mRemovingRow); Q_ASSERT(!mInsertingRow); Q_ASSERT(!mMovingRow); if(mSimple) return; //We don't need to move processes when in simple mode mMovingRow = true; int current_row = process->parent()->children().indexOf(process); Q_ASSERT(current_row != -1); int new_row = new_parent->children().count(); QModelIndex sourceParent = q->getQModelIndex( process->parent(), 0); QModelIndex destinationParent = q->getQModelIndex( new_parent, 0 ); mMovingRow = q->beginMoveRows(sourceParent, current_row, current_row, destinationParent, new_row); Q_ASSERT(mMovingRow); } void ProcessModelPrivate::endMoveRow() { Q_ASSERT(!mInsertingRow); Q_ASSERT(!mRemovingRow); if(!mMovingRow) return; mMovingRow = false; q->endMoveRows(); } QModelIndex ProcessModel::getQModelIndex( KSysGuard::Process *process, int column) const { Q_ASSERT(process); int pid = process->pid(); if (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 int row = 0; if(d->mSimple) { row = process->index(); } else { row = process->parent()->children().indexOf(process); } Q_ASSERT(row != -1); return createIndex(row, column, process); } QModelIndex ProcessModel::parent ( const QModelIndex & index ) const { if(!index.isValid()) return QModelIndex(); KSysGuard::Process *process = reinterpret_cast< KSysGuard::Process * > (index.internalPointer()); Q_ASSERT(process); if(d->mSimple) return QModelIndex(); else return getQModelIndex(process->parent(), 0); } QVariant ProcessModel::headerData(int section, Qt::Orientation orientation, int role) const { 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: { switch(section) { case HeadingPid: case HeadingTty: case HeadingMemory: case HeadingXMemory: case HeadingSharedMemory: case HeadingStartTime: case HeadingNoNewPrivileges: case HeadingIoRead: case HeadingIoWrite: case HeadingVmSize: case HeadingNiceness: // return QVariant(Qt::AlignRight); case HeadingUser: case HeadingCPUUsage: case HeadingCPUTime: case HeadingVmPSS: return QVariant(Qt::AlignCenter); } return QVariant(); } case Qt::ToolTipRole: { if(!d->mShowingTooltips) return QVariant(); switch(section) { case HeadingName: return i18n("The process name."); case HeadingUser: return i18n("The user who owns this process."); case HeadingTty: return i18n("The controlling terminal on which this process is running."); case HeadingNiceness: return 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)."); case HeadingCPUUsage: if(d->mNumProcessorCores == 1) return i18n("The current CPU usage of the process."); else // i18n: %1 is always greater than 1, so do not worry about // nonsensical verbosity of the singular part. if(d->mNormalizeCPUUsage) return i18np("The current total CPU usage of the process, divided by the %1 processor core in the machine.", "The current total CPU usage of the process, divided by the %1 processor cores in the machine.", d->mNumProcessorCores); else return i18n("The current total CPU usage of the process."); case HeadingCPUTime: return i18n("The total user and system time that this process has been running for, displayed as minutes:seconds."); case HeadingVmSize: return 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."); case HeadingMemory: return i18n("This is the amount of real 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.
This is often the most useful figure to judge the memory use of a program. See What's This for more information.
"); case HeadingSharedMemory: return 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.
"); case HeadingStartTime: return i18n("The elapsed time since the process was started."); case HeadingNoNewPrivileges: return i18n("Linux flag NoNewPrivileges, if set the process can't gain further privileges via setuid etc."); case HeadingCommand: return i18n("The command with which this process was launched."); case HeadingXMemory: return i18n("The amount of pixmap memory that this process is using."); case HeadingXTitle: return i18n("The title of any windows that this process is showing."); case HeadingPid: return i18n("The unique Process ID that identifies this process."); case HeadingIoRead: return i18n("The number of bytes read. See What's This for more information."); case HeadingIoWrite: return i18n("The number of bytes written. See What's This for more information."); case HeadingCGroup: return i18n("The control group (cgroup) where this process belongs."); case HeadingMACContext: return i18n("Mandatory Access Control (SELinux or AppArmor) context for this process."); case HeadingVmPSS: return i18n("The amount of private physical memory used by a process, with the amount of shared memory divided by the amount of processes using that shared memory added."); default: return QVariant(); } } case Qt::WhatsThisRole: { switch(section) { case HeadingName: return i18n("Technical information: The kernel process name is a maximum of 8 characters long, so the full command is examined. If the first word in the full command line starts with the process name, the first word of the command line is shown, otherwise the process name is used."); case HeadingUser: return i18n("The user who owns this process. If the effective, setuid etc user is different, the user who owns the process will be shown, followed by the effective user. The ToolTip contains the full information.

" "" "" "" "" #ifdef Q_OS_LINUX "" #endif "
Login Name/GroupThe username of the Real User/Group who created this process
Effective User/GroupThe process is running with privileges of the Effective User/Group. This is shown if different from the real user.
Setuid User/GroupThe saved username of the binary. The process can escalate its Effective User/Group to the Setuid User/Group.
File System User/GroupAccesses to the filesystem are checked with the File System User/Group. This is a Linux specific call. See setfsuid(2) for more information.
"); case HeadingVmSize: return i18n("This is the size of allocated address space - not memory, but address space. This value in practice means next to nothing. When a process requests a large memory block from the system but uses only a small part of it, the real usage will be low, VIRT will be high.

Technical information: This is VmSize in proc/*/status and VIRT in top."); case HeadingMemory: return i18n("Technical information: This is an approximation of the Private memory usage, calculated as VmRSS - Shared, from /proc/*/statm. This tends to underestimate the true Private memory usage of a process (by not including i/o backed memory pages), but is the best estimation that is fast to determine. This is sometimes known as URSS (Unique Resident Set Size). For an individual process, see \"Detailed Memory Information\" for a more accurate, but slower, calculation of the true Private memory usage."); case HeadingCPUUsage: return i18n("The CPU usage of a process and all of its threads."); case HeadingCPUTime: return i18n("The total system and user time that a process and all of its threads have been running on the CPU for. This can be greater than the wall clock time if the process has been across multiple CPU cores."); case HeadingSharedMemory: return i18n("Technical information: This is an approximation of the Shared memory, called SHR in top. It is the number of pages that are backed by a file (see kernel Documentation/filesystems/proc.txt). For an individual process, see \"Detailed Memory Information\" for a more accurate, but slower, calculation of the true Shared memory usage."); case HeadingStartTime: return i18n("Technical information: The underlying value (clock ticks since system boot) is retrieved from /proc/[pid]/stat"); case HeadingNoNewPrivileges: return i18n("Technical information: The flag is retrieved from /proc/[pid]/status"); case HeadingCommand: return i18n("Technical information: This is from /proc/*/cmdline"); case HeadingXMemory: return i18n("Technical information: This is the amount of memory used by the Xorg process for images for this process. This is memory used in addition to Memory and Shared Memory.
Technical information: This only counts the pixmap memory, and does not include resource memory used by fonts, cursors, glyphsets etc. See the xrestop program for a more detailed breakdown."); case HeadingXTitle: return i18n("Technical information: For each X11 window, the X11 property _NET_WM_PID is used to map the window to a PID. If a process' windows are not shown, then that application incorrectly is not setting _NET_WM_PID."); case HeadingPid: return i18n("Technical information: This is the Process ID. A multi-threaded application is treated a single process, with all threads sharing the same PID. The CPU usage etc will be the total, accumulated, CPU usage of all the threads."); case HeadingIoRead: case HeadingIoWrite: return i18n("This column shows the IO statistics for each process. The tooltip provides the following information:
" "" "" "" "" "" "" "" "
Characters ReadThe number of bytes which this task has caused to be read from storage. This is simply the sum of bytes which this process passed to read() and pread(). It includes things like tty IO and it is unaffected by whether or not actual physical disk IO was required (the read might have been satisfied from pagecache).
Characters WrittenThe number of bytes which this task has caused, or shall cause to be written to disk. Similar caveats apply here as with Characters Read.
Read SyscallsThe number of read I/O operations, i.e. syscalls like read() and pread().
Write SyscallsThe number of write I/O operations, i.e. syscalls like write() and pwrite().
Actual Bytes ReadThe number of bytes which this process really did cause to be fetched from the storage layer. Done at the submit_bio() level, so it is accurate for block-backed filesystems. This may not give sensible values for NFS and CIFS filesystems.
Actual Bytes WrittenAttempt to count the number of bytes which this process caused to be sent to the storage layer. This is done at page-dirtying time.

" "The number in brackets shows the rate at which each value is changing, determined from taking the difference between the previous value and the new value, and dividing by the update interval.

" "Technical information: This data is collected from /proc/*/io and is documented further in Documentation/accounting and Documentation/filesystems/proc.txt in the kernel source."); case HeadingCGroup: return i18n("Technical information: This shows Linux Control Group (cgroup) membership, retrieved from /proc/[pid]/cgroup. Control groups are used by Systemd and containers for limiting process group's usage of resources and to monitor them."); case HeadingMACContext: return i18n("Technical information: This shows Mandatory Access Control (SELinux or AppArmor) context, retrieved from /proc/[pid]/attr/current."); case HeadingVmPSS: return i18n("Technical information: This is often referred to as \"Proportional Set Size\" and is the closest approximation of the real amount of total memory used by a process. Note that the number of applications sharing shared memory is determined per shared memory section and thus can vary per memory section."); default: return QVariant(); } } case Qt::DisplayRole: return d->mHeadings[section]; default: return QVariant(); } } void ProcessModel::setSimpleMode(bool simple) { if(d->mSimple == simple) return; emit layoutAboutToBeChanged (); d->mSimple = simple; int flatrow; int treerow; QList flatIndexes; QList treeIndexes; foreach( KSysGuard::Process *process, d->mProcesses->getAllProcesses()) { flatrow = process->index(); treerow = process->parent()->children().indexOf(process); flatIndexes.clear(); treeIndexes.clear(); for(int i=0; i < columnCount(); i++) { flatIndexes << createIndex(flatrow, i, process); treeIndexes << createIndex(treerow, i, process); } if(d->mSimple) //change from tree mode to flat mode changePersistentIndexList(treeIndexes, flatIndexes); else // change from flat mode to tree mode changePersistentIndexList(flatIndexes, treeIndexes); } emit layoutChanged(); } bool ProcessModel::canUserLogin(long uid ) const { if(uid == 65534) { //nobody user return false; } if(!d->mIsLocalhost) return true; //We only deal with localhost. Just always return true for non localhost int canLogin = d->mUidCanLogin.value(uid, -1); //Returns 0 if we cannot login, 1 if we can, and the default is -1 meaning we don't know if(canLogin != -1) return canLogin; //We know whether they can log in //We got the default, -1, so we don't know. Look it up KUser user(uid); if(!user.isValid()) { //for some reason the user isn't recognised. This might happen under certain security situations. //Just return true to be safe d->mUidCanLogin[uid] = 1; return true; } QString shell = user.shell(); if(shell == QLatin1String("/bin/false") ) //FIXME - add in any other shells it could be for false { d->mUidCanLogin[uid] = 0; return false; } d->mUidCanLogin[uid] = 1; return true; } QString ProcessModelPrivate::getTooltipForUser(const KSysGuard::Process *ps) const { QString userTooltip; if(!mIsLocalhost) { return xi18nc("@info:tooltip", "Login Name: %1", getUsernameForUser(ps->uid(), true)); } else { KUser user(ps->uid()); if(!user.isValid()) userTooltip += xi18nc("@info:tooltip", "This user is not recognized for some reason."); else { if(!user.property(KUser::FullName).isValid()) userTooltip += xi18nc("@info:tooltip", "%1", user.property(KUser::FullName).toString()); userTooltip += xi18nc("@info:tooltip", "Login Name: %1 (uid: %2)", user.loginName(), QString::number(ps->uid())); if(!user.property(KUser::RoomNumber).isValid()) userTooltip += xi18nc("@info:tooltip", " Room Number: %1", user.property(KUser::RoomNumber).toString()); if(!user.property(KUser::WorkPhone).isValid()) userTooltip += xi18nc("@info:tooltip", " Work Phone: %1", user.property(KUser::WorkPhone).toString()); } } if( (ps->uid() != ps->euid() && ps->euid() != -1) || (ps->uid() != ps->suid() && ps->suid() != -1) || (ps->uid() != ps->fsuid() && ps->fsuid() != -1)) { if(ps->euid() != -1) userTooltip += xi18nc("@info:tooltip", "Effective User: %1", getUsernameForUser(ps->euid(), true)); if(ps->suid() != -1) userTooltip += xi18nc("@info:tooltip", "Setuid User: %1", getUsernameForUser(ps->suid(), true)); if(ps->fsuid() != -1) userTooltip += xi18nc("@info:tooltip", "File System User: %1", getUsernameForUser(ps->fsuid(), true)); userTooltip += QLatin1String("
"); } if(ps->gid() != -1) { userTooltip += xi18nc("@info:tooltip", "Group: %1", getGroupnameForGroup(ps->gid())); if( (ps->gid() != ps->egid() && ps->egid() != -1) || (ps->gid() != ps->sgid() && ps->sgid() != -1) || (ps->gid() != ps->fsgid() && ps->fsgid() != -1)) { if(ps->egid() != -1) userTooltip += xi18nc("@info:tooltip", "Effective Group: %1", getGroupnameForGroup(ps->egid())); if(ps->sgid() != -1) userTooltip += xi18nc("@info:tooltip", "Setuid Group: %1", getGroupnameForGroup(ps->sgid())); if(ps->fsgid() != -1) userTooltip += xi18nc("@info:tooltip", "File System Group: %1", getGroupnameForGroup(ps->fsgid())); } } return userTooltip; } QString ProcessModel::getStringForProcess(KSysGuard::Process *process) const { return i18nc("Short description of a process. PID, name, user", "%1: %2, owned by user %3", (long)(process->pid()), process->name(), d->getUsernameForUser(process->uid(), false)); } QString ProcessModelPrivate::getGroupnameForGroup(long gid) const { if(mIsLocalhost) { QString groupname = KUserGroup(gid).name(); if(!groupname.isEmpty()) return i18nc("Group name and group id", "%1 (gid: %2)", groupname, gid); } return QString::number(gid); } QString ProcessModelPrivate::getUsernameForUser(long uid, bool withuid) const { QString &username = mUserUsername[uid]; if(username.isNull()) { if(!mIsLocalhost) { username = QLatin1String(""); //empty, but not null } else { KUser user(uid); if(!user.isValid()) username = QLatin1String(""); else username = user.loginName(); } } if(username.isEmpty()) return QString::number(uid); if(withuid) return i18nc("User name and user id", "%1 (uid: %2)", username, (long int)uid); return username; } 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(); } KFormat format; switch (role){ case Qt::DisplayRole: { KSysGuard::Process *process = reinterpret_cast< KSysGuard::Process * > (index.internalPointer()); switch(index.column()) { case HeadingName: if(d->mShowCommandLineOptions) return process->name(); else return process->name().section(QLatin1Char(' '), 0,0); case HeadingPid: return (qlonglong)process->pid(); case HeadingUser: if(!process->login().isEmpty()) return process->login(); if(process->uid() == process->euid()) return d->getUsernameForUser(process->uid(), false); else return QString(d->getUsernameForUser(process->uid(), false) + QStringLiteral(", ") + d->getUsernameForUser(process->euid(), false)); case HeadingNiceness: switch(process->scheduler()) { case KSysGuard::Process::Other: return process->niceLevel(); case KSysGuard::Process::SchedulerIdle: return i18nc("scheduler", "Idle"); //neither static nor dynamic priority matter case KSysGuard::Process::Batch: return i18nc("scheduler", "(Batch) %1", process->niceLevel()); //only dynamic priority matters case KSysGuard::Process::RoundRobin: return i18nc("Round robin scheduler", "RR %1", process->niceLevel()); case KSysGuard::Process::Fifo: if(process->niceLevel() == 99) return i18nc("Real Time scheduler", "RT"); else return i18nc("First in first out scheduler", "FIFO %1", process->niceLevel()); case KSysGuard::Process::Interactive: return i18nc("scheduler", "(IA) %1", process->niceLevel()); } return {}; // It actually never gets here since all cases are handled in the switch, but makes gcc not complain about a possible fall through case HeadingTty: return process->tty(); case HeadingCPUUsage: { double total; if(d->mShowChildTotals && !d->mSimple) total = process->totalUserUsage() + process->totalSysUsage(); else total = process->userUsage() + process->sysUsage(); if(d->mNormalizeCPUUsage) total = total / d->mNumProcessorCores; if(total < 1 && process->status() != KSysGuard::Process::Sleeping && process->status() != KSysGuard::Process::Running && process->status() != KSysGuard::Process::Ended) return process->translatedStatus(); //tell the user when the process is a zombie or stopped if(total < 0.5) return QString(); return QString(QString::number((int)(total+0.5)) + QLatin1Char('%')); } case HeadingCPUTime: { qlonglong seconds = (process->userTime() + process->sysTime())/100; return QStringLiteral("%1:%2").arg(seconds/60).arg((int)seconds%60, 2, 10, QLatin1Char('0')); } case HeadingMemory: if(process->vmURSS() == -1) { //If we don't have the URSS (the memory used by only the process, not the shared libraries) //then return the RSS (physical memory used by the process + shared library) as the next best thing return formatMemoryInfo(process->vmRSS(), d->mUnits, true); } else { return formatMemoryInfo(process->vmURSS(), d->mUnits, true); } case HeadingVmSize: return formatMemoryInfo(process->vmSize(), d->mUnits, true); case HeadingSharedMemory: if(process->vmRSS() - process->vmURSS() <= 0 || process->vmURSS() == -1) return QVariant(QVariant::String); return formatMemoryInfo(process->vmRSS() - process->vmURSS(), d->mUnits); case HeadingStartTime: { // NOTE: the next 6 lines are the same as in the next occurrence of 'case HeadingStartTime:' => keep in sync or remove duplicate code const auto clockTicksSinceSystemBoot = process->startTime(); const auto clockTicksPerSecond = sysconf(_SC_CLK_TCK); // see man proc or http://superuser.com/questions/101183/what-is-a-cpu-tick const auto secondsSinceSystemBoot = (double)clockTicksSinceSystemBoot / clockTicksPerSecond; const auto systemBootTime = TimeUtil::systemUptimeAbsolute(); const auto absoluteStartTime = systemBootTime.addSecs(secondsSinceSystemBoot); const auto relativeStartTime = absoluteStartTime.secsTo(QDateTime::currentDateTime()); return TimeUtil::secondsToHumanElapsedString(relativeStartTime); } case HeadingNoNewPrivileges: return QString::number(process->noNewPrivileges()); case HeadingCommand: { return process->command().replace(QLatin1Char('\n'),QLatin1Char(' ')); // It would be nice to embolden the process name in command, but this requires that the itemdelegate to support html text // QString command = process->command; // command.replace(process->name, "" + process->name + ""); // return "" + command; } case HeadingIoRead: { switch(d->mIoInformation) { case ProcessModel::Bytes: //divide by 1024 to convert to kB return formatMemoryInfo(process->ioCharactersRead() / 1024, d->mIoUnits, true); case ProcessModel::Syscalls: if( process->ioReadSyscalls()) return QString::number(process->ioReadSyscalls()); break; case ProcessModel::ActualBytes: return formatMemoryInfo(process->ioCharactersActuallyRead() / 1024, d->mIoUnits, true); case ProcessModel::BytesRate: if( process->ioCharactersReadRate() / 1024) return i18n("%1/s", formatMemoryInfo(process->ioCharactersReadRate() / 1024, d->mIoUnits, true)); break; case ProcessModel::SyscallsRate: if( process->ioReadSyscallsRate()) return QString::number(process->ioReadSyscallsRate()); break; case ProcessModel::ActualBytesRate: if( process->ioCharactersActuallyReadRate() / 1024) return i18n("%1/s", formatMemoryInfo(process->ioCharactersActuallyReadRate() / 1024, d->mIoUnits, true)); break; } return QVariant(); } case HeadingIoWrite: { switch(d->mIoInformation) { case ProcessModel::Bytes: return formatMemoryInfo(process->ioCharactersWritten() / 1024, d->mIoUnits, true); case ProcessModel::Syscalls: if( process->ioWriteSyscalls()) return QString::number(process->ioWriteSyscalls()); break; case ProcessModel::ActualBytes: return formatMemoryInfo(process->ioCharactersActuallyWritten() / 1024, d->mIoUnits, true); case ProcessModel::BytesRate: if(process->ioCharactersWrittenRate() / 1024) return i18n("%1/s", formatMemoryInfo(process->ioCharactersWrittenRate() / 1024, d->mIoUnits, true)); break; case ProcessModel::SyscallsRate: if( process->ioWriteSyscallsRate()) return QString::number(process->ioWriteSyscallsRate()); break; case ProcessModel::ActualBytesRate: if(process->ioCharactersActuallyWrittenRate() / 1024) return i18n("%1/s", formatMemoryInfo(process->ioCharactersActuallyWrittenRate() / 1024, d->mIoUnits, true)); break; } return QVariant(); } #if HAVE_X11 case HeadingXMemory: return formatMemoryInfo(process->pixmapBytes() / 1024, d->mUnits, true); case HeadingXTitle: { if(!process->hasManagedGuiWindow()) return QVariant(QVariant::String); WindowInfo *w = d->mPidToWindowInfo.value(process->pid(), NULL); if(!w) return QVariant(QVariant::String); else return w->name; } #endif case HeadingCGroup: return process->cGroup(); case HeadingMACContext: return process->macContext(); case HeadingVmPSS: return process->vmPSS() >= 0 ? formatMemoryInfo(process->vmPSS(), d->mUnits, true) : QVariant{}; default: return QVariant(); } break; } case Qt::ToolTipRole: { if(!d->mShowingTooltips) return QVariant(); KSysGuard::Process *process = reinterpret_cast< KSysGuard::Process * > (index.internalPointer()); QString tracer; if(process->tracerpid() >= 0) { KSysGuard::Process *process_tracer = d->mProcesses->getProcess(process->tracerpid()); if(process_tracer) //it is possible for this to be not the case in certain race conditions tracer = xi18nc("tooltip. name,pid ","This process is being debugged by %1 (%2)", process_tracer->name(), (long int)process->tracerpid()); } switch(index.column()) { case HeadingName: { /* It would be nice to be able to show the icon in the tooltip, but Qt4 won't let us put * a picture in a tooltip :( QIcon icon; if(mPidToWindowInfo.contains(process->pid())) { WId wid; wid = mPidToWindowInfo[process->pid()].wid; icon = KWindowSystem::icon(wid); } if(icon.isValid()) { tooltip = i18n(""); firstrow = false; } for(int i = 0; i < d->mHeadings.size(); i++) { if(firstrow) { QString heading = d->mHeadings[i]; textHtmlHeaders += QStringLiteral(""); if(i) { textCsvHeaders += QLatin1Char(','); textPlainHeaders += QLatin1String(", "); } textPlainHeaders += heading; heading.replace(QLatin1Char('"'), QLatin1String("\"\"")); textCsvHeaders += QLatin1Char('"') + heading + QLatin1Char('"'); } QModelIndex index2 = createIndex(index.row(), i, reinterpret_cast< KSysGuard::Process * > (index.internalPointer())); QString display = data(index2, PlainValueRole).toString(); if(i) { textCsv += QLatin1Char(','); textPlain += QLatin1String(", "); } textHtml += QStringLiteral(""); textPlain += display; display.replace(QLatin1Char('"'),QLatin1String("\"\"")); textCsv += QLatin1Char('"') + display + QLatin1Char('"'); } } } textHtml = QStringLiteral("
%1", icon); } */ QString tooltip; if(process->parentPid() == -1) { //Give a quick explanation of init and kthreadd if(process->name() == QLatin1String("init") || process->name() == QLatin1String("systemd")) { tooltip = xi18nc("@info:tooltip", "%1The parent of all other processes and cannot be killed.Process ID: %2", process->name(), (long int)process->pid()); } else if(process->name() == QLatin1String("kthreadd")) { tooltip = xi18nc("@info:tooltip", "KThreaddManages kernel threads. The children processes run in the kernel, controlling hard disk access, etc."); } else { tooltip = xi18nc("@info:tooltip","%1Process ID: %2",process->name(), (long int)process->pid()); } } else { KSysGuard::Process *parent_process = d->mProcesses->getProcess(process->parentPid()); if(parent_process) { //In race conditions, it's possible for this process to not exist tooltip = xi18nc("@info:tooltip", "%1" "Process ID: %2" "Parent: %3" "Parent's ID: %4", process->name(), (long int)process->pid(), parent_process->name(), (long int)process->parentPid()); } else { tooltip = xi18nc("@info:tooltip", "%1" "Process ID: %2" "Parent's ID: %3", process->name(), (long int)process->pid(), (long int)process->parentPid()); } } if(process->numThreads() >= 1) tooltip += xi18nc("@info:tooltip", "Number of threads: %1", process->numThreads()); if(!process->command().isEmpty()) { tooltip += xi18nc("@info:tooltip", "Command: %1", process->command()); } if(!process->tty().isEmpty()) tooltip += xi18nc("@info:tooltip", "Running on: %1", QString::fromUtf8(process->tty())); if(!tracer.isEmpty()) return QStringLiteral("%1
%2").arg(tooltip).arg(tracer); return tooltip; } case HeadingStartTime: { // NOTE: the next 6 lines are the same as in the previous occurrence of 'case HeadingStartTime:' => keep in sync or remove duplicate code const auto clockTicksSinceSystemBoot = process->startTime(); const auto clockTicksPerSecond = sysconf(_SC_CLK_TCK); const auto secondsSinceSystemBoot = (double)clockTicksSinceSystemBoot / clockTicksPerSecond; const auto systemBootTime = TimeUtil::systemUptimeAbsolute(); const auto absoluteStartTime = systemBootTime.addSecs(secondsSinceSystemBoot); const auto relativeStartTime = absoluteStartTime.secsTo(QDateTime::currentDateTime()); return xi18nc("@info:tooltip", "Clock ticks since system boot: %1" "Seconds since system boot: %2 (System boot time: %3)" "Absolute start time: %4" "Relative start time: %5", clockTicksSinceSystemBoot, secondsSinceSystemBoot, systemBootTime.toString(), absoluteStartTime.toString(), TimeUtil::secondsToHumanElapsedString(relativeStartTime)); } case HeadingCommand: { QString tooltip = xi18nc("@info:tooltip", "This process was run with the following command:" "%1", process->command()); if(!process->tty().isEmpty()) tooltip += xi18nc("@info:tooltip", "Running on: %1", QString::fromUtf8(process->tty())); if (!tracer.isEmpty()) { return QStringLiteral("%1
%2").arg(tooltip).arg(tracer); } return tooltip; } case HeadingUser: { QString tooltip = d->getTooltipForUser(process); if(tracer.isEmpty()) { return tooltip; } return QString(tooltip + QStringLiteral("
") + tracer); } case HeadingNiceness: { QString tooltip; switch(process->scheduler()) { case KSysGuard::Process::Other: case KSysGuard::Process::Batch: case KSysGuard::Process::Interactive: tooltip = xi18nc("@info:tooltip", "Nice level: %1 (%2)", process->niceLevel(), process->niceLevelAsString()); break; case KSysGuard::Process::RoundRobin: case KSysGuard::Process::Fifo: tooltip = xi18nc("@info:tooltip", "This is a real time process." "Scheduler priority: %1", process->niceLevel()); break; case KSysGuard::Process::SchedulerIdle: break; //has neither dynamic (niceness) or static (scheduler priority) priority } if(process->scheduler() != KSysGuard::Process::Other) tooltip += xi18nc("@info:tooltip", "Scheduler: %1", process->schedulerAsString()); if(process->ioPriorityClass() != KSysGuard::Process::None) { if((process->ioPriorityClass() == KSysGuard::Process::RealTime || process->ioPriorityClass() == KSysGuard::Process::BestEffort) && process->ioniceLevel() != -1) tooltip += xi18nc("@info:tooltip", "I/O Nice level: %1 (%2)", process->ioniceLevel(), process->ioniceLevelAsString()); tooltip += xi18nc("@info:tooltip", "I/O Class: %1", process->ioPriorityClassAsString()); } if(tracer.isEmpty()) return tooltip; return QString(tooltip + QStringLiteral("
") + tracer); } case HeadingCPUUsage: case HeadingCPUTime: { int divideby = (d->mNormalizeCPUUsage?d->mNumProcessorCores:1); QString tooltip = xi18nc("@info:tooltip", "Process status: %1 %2" "User CPU usage: %3%" "System CPU usage: %4%", /* Please do not add here - the tooltip is appended to */ process->translatedStatus(), d->getStatusDescription(process->status()), (float)(process->userUsage()) / divideby, (float)(process->sysUsage()) / divideby); if(process->numThreads() >= 1) tooltip += xi18nc("@info:tooltip", "Number of threads: %1", process->numThreads()); if(process->numChildren() > 0) { tooltip += xi18nc("@info:tooltip", "Number of children: %1" "Total User CPU usage: %2%" "Total System CPU usage: %3%" "Total CPU usage: %4%", process->numChildren(), (float)(process->totalUserUsage())/ divideby, (float)(process->totalSysUsage()) / divideby, (float)(process->totalUserUsage() + process->totalSysUsage()) / divideby); } if(process->userTime() > 0) tooltip += xi18nc("@info:tooltip", "CPU time spent running as user: %1 seconds", QString::number(process->userTime() / 100.0, 'f', 1)); if(process->sysTime() > 0) tooltip += xi18nc("@info:tooltip", "CPU time spent running in kernel: %1 seconds", QString::number(process->sysTime() / 100.0, 'f', 1)); if(process->niceLevel() != 0) tooltip += xi18nc("@info:tooltip", "Nice level: %1 (%2)", process->niceLevel(), process->niceLevelAsString() ); if(process->ioPriorityClass() != KSysGuard::Process::None) { if((process->ioPriorityClass() == KSysGuard::Process::RealTime || process->ioPriorityClass() == KSysGuard::Process::BestEffort) && process->ioniceLevel() != -1) tooltip += xi18nc("@info:tooltip", "I/O Nice level: %1 (%2)", process->ioniceLevel(), process->ioniceLevelAsString() ); tooltip += xi18nc("@info:tooltip", "I/O Class: %1", process->ioPriorityClassAsString() ); } if(!tracer.isEmpty()) return QString(tooltip + QStringLiteral("
") + tracer); return tooltip; } case HeadingVmSize: { return QVariant(); } case HeadingMemory: { QString tooltip; if(process->vmURSS() != -1) { //We don't have information about the URSS, so just fallback to RSS if(d->mMemTotal > 0) tooltip += xi18nc("@info:tooltip", "Memory usage: %1 out of %2 (%3 %)", format.formatByteSize(process->vmURSS() * 1024), format.formatByteSize(d->mMemTotal * 1024), process->vmURSS() * 100 / d->mMemTotal); else tooltip += xi18nc("@info:tooltip", "Memory usage: %1
", format.formatByteSize(process->vmURSS() * 1024)); } if(d->mMemTotal > 0) tooltip += xi18nc("@info:tooltip", "RSS Memory usage: %1 out of %2 (%3 %)", format.formatByteSize(process->vmRSS() * 1024), format.formatByteSize(d->mMemTotal * 1024), process->vmRSS() * 100 / d->mMemTotal); else tooltip += xi18nc("@info:tooltip", "RSS Memory usage: %1", format.formatByteSize(process->vmRSS() * 1024)); return tooltip; } case HeadingSharedMemory: { if(process->vmURSS() == -1) { return xi18nc("@info:tooltip", "Your system does not seem to have this information available to be read."); } if(d->mMemTotal >0) return xi18nc("@info:tooltip", "Shared library memory usage: %1 out of %2 (%3 %)", format.formatByteSize((process->vmRSS() - process->vmURSS()) * 1024), format.formatByteSize(d->mMemTotal * 1024), (process->vmRSS() - process->vmURSS()) * 100 / d->mMemTotal); else return xi18nc("@info:tooltip", "Shared library memory usage: %1", format.formatByteSize((process->vmRSS() - process->vmURSS()) * 1024)); } case HeadingIoWrite: case HeadingIoRead: { //FIXME - use the formatByteRate functions when added return kxi18nc("@info:tooltip", "Characters read: %1 (%2 KiB/s)" "Characters written: %3 (%4 KiB/s)" "Read syscalls: %5 (%6 s⁻¹)" "Write syscalls: %7 (%8 s⁻¹)" "Actual bytes read: %9 (%10 KiB/s)" "Actual bytes written: %11 (%12 KiB/s)") .subs(format.formatByteSize(process->ioCharactersRead())) .subs(QString::number(process->ioCharactersReadRate() / 1024)) .subs(format.formatByteSize(process->ioCharactersWritten())) .subs(QString::number(process->ioCharactersWrittenRate() / 1024)) .subs(QString::number(process->ioReadSyscalls())) .subs(QString::number(process->ioReadSyscallsRate())) .subs(QString::number(process->ioWriteSyscalls())) .subs(QString::number(process->ioWriteSyscallsRate())) .subs(format.formatByteSize(process->ioCharactersActuallyRead())) .subs(QString::number(process->ioCharactersActuallyReadRate() / 1024)) .subs(format.formatByteSize(process->ioCharactersActuallyWritten())) .subs(QString::number(process->ioCharactersActuallyWrittenRate() / 1024)) .toString(); } case HeadingXTitle: { #if HAVE_X11 const auto values = d->mPidToWindowInfo.values(process->pid()); if (values.count() == 1) { return values.first()->name; } QString tooltip; for (const auto &value : values) { if (!tooltip.isEmpty()) { tooltip += QLatin1Char('\n'); } tooltip += QStringLiteral("• ") + value->name; } return tooltip; #endif return QVariant(QVariant::String); } case HeadingVmPSS: { if(process->vmPSS() == -1) { return xi18nc("@info:tooltip", "Your system does not seem to have this information available to be read."); } if(d->mMemTotal > 0 ) { return xi18nc( "@info:tooltip", "Total memory usage: %1 out of %2 (%3 %)", format.formatByteSize(process->vmPSS() * 1024), format.formatByteSize(d->mMemTotal * 1024), qRound(process->vmPSS() * 1000.0 / d->mMemTotal) / 10.0 ); } else { return xi18nc( "@info:tooltip", "Shared library memory usage: %1", format.formatByteSize(process->vmPSS() * 1024) ); } } default: return QVariant(QVariant::String); } } case Qt::TextAlignmentRole: switch(index.column() ) { case HeadingUser: case HeadingCPUUsage: return QVariant(Qt::AlignCenter); case HeadingNiceness: case HeadingCPUTime: case HeadingStartTime: case HeadingNoNewPrivileges: case HeadingPid: case HeadingMemory: case HeadingXMemory: case HeadingSharedMemory: case HeadingVmSize: case HeadingIoWrite: case HeadingIoRead: case HeadingVmPSS: return QVariant(Qt::AlignRight | Qt::AlignVCenter); default: return QVariant(Qt::AlignLeft | Qt::AlignVCenter); } case UidRole: { if(index.column() != 0) return QVariant(); //If we query with this role, then we want the raw UID for this. KSysGuard::Process *process = reinterpret_cast< KSysGuard::Process * > (index.internalPointer()); return process->uid(); } case PlainValueRole: //Used to return a plain value. For copying to a clipboard etc { KSysGuard::Process *process = reinterpret_cast< KSysGuard::Process * > (index.internalPointer()); switch(index.column()) { case HeadingName: return process->name(); case HeadingPid: return (qlonglong)process->pid(); case HeadingUser: if(!process->login().isEmpty()) return process->login(); if(process->uid() == process->euid()) return d->getUsernameForUser(process->uid(), false); else return QString(d->getUsernameForUser(process->uid(), false) + QStringLiteral(", ") + d->getUsernameForUser(process->euid(), false)); case HeadingNiceness: return process->niceLevel(); case HeadingTty: return process->tty(); case HeadingCPUUsage: { double total; if(d->mShowChildTotals && !d->mSimple) total = process->totalUserUsage() + process->totalSysUsage(); else total = process->userUsage() + process->sysUsage(); if(d->mNormalizeCPUUsage) return total / d->mNumProcessorCores; else return total; } case HeadingCPUTime: return (qlonglong)(process->userTime() + process->sysTime()); case HeadingMemory: if(process->vmRSS() == 0) return QVariant(QVariant::String); if(process->vmURSS() == -1) { return (qlonglong)process->vmRSS(); } else { return (qlonglong)process->vmURSS(); } case HeadingVmSize: return (qlonglong)process->vmSize(); case HeadingSharedMemory: if(process->vmRSS() - process->vmURSS() < 0 || process->vmURSS() == -1) return QVariant(QVariant::String); return (qlonglong)(process->vmRSS() - process->vmURSS()); case HeadingStartTime: return process->startTime(); // 2015-01-03, gregormi: can maybe be replaced with something better later case HeadingNoNewPrivileges: return process->noNewPrivileges(); case HeadingCommand: return process->command(); case HeadingIoRead: switch(d->mIoInformation) { case ProcessModel::Bytes: return process->ioCharactersRead(); case ProcessModel::Syscalls: return process->ioReadSyscalls(); case ProcessModel::ActualBytes: return process->ioCharactersActuallyRead(); case ProcessModel::BytesRate: return (qlonglong)process->ioCharactersReadRate(); case ProcessModel::SyscallsRate: return (qlonglong)process->ioReadSyscallsRate(); case ProcessModel::ActualBytesRate: return (qlonglong)process->ioCharactersActuallyReadRate(); } return {}; // It actually never gets here since all cases are handled in the switch, but makes gcc not complain about a possible fall through case HeadingIoWrite: switch(d->mIoInformation) { case ProcessModel::Bytes: return process->ioCharactersWritten(); case ProcessModel::Syscalls: return process->ioWriteSyscalls(); case ProcessModel::ActualBytes: return process->ioCharactersActuallyWritten(); case ProcessModel::BytesRate: return (qlonglong)process->ioCharactersWrittenRate(); case ProcessModel::SyscallsRate: return (qlonglong)process->ioWriteSyscallsRate(); case ProcessModel::ActualBytesRate: return (qlonglong)process->ioCharactersActuallyWrittenRate(); } return {}; // It actually never gets here since all cases are handled in the switch, but makes gcc not complain about a possible fall through case HeadingXMemory: return (qulonglong)process->pixmapBytes(); #if HAVE_X11 case HeadingXTitle: { WindowInfo *w = d->mPidToWindowInfo.value(process->pid(), NULL); if(!w) return QString(); return w->name; } #endif case HeadingCGroup: return process->cGroup(); case HeadingMACContext: return process->macContext(); case HeadingVmPSS: return process->vmPSS() >= 0 ? process->vmPSS() : QVariant{}; default: return QVariant(); } break; } #if HAVE_X11 case WindowIdRole: { KSysGuard::Process *process = reinterpret_cast< KSysGuard::Process * > (index.internalPointer()); WindowInfo *w = d->mPidToWindowInfo.value(process->pid(), NULL); if(!w) return QVariant(); else return (int)w->wid; } #endif case PercentageRole: { KSysGuard::Process *process = reinterpret_cast< KSysGuard::Process * > (index.internalPointer()); Q_CHECK_PTR(process); switch(index.column()) { case HeadingCPUUsage: { float cpu; if(d->mSimple || !d->mShowChildTotals) cpu = process->userUsage() + process->sysUsage(); else cpu = process->totalUserUsage() + process->totalSysUsage(); cpu = cpu / 100.0; if(!d->mNormalizeCPUUsage) return cpu; return cpu / d->mNumProcessorCores; } case HeadingMemory: if(d->mMemTotal <= 0) return -1; if(process->vmURSS() != -1) return float(process->vmURSS()) / d->mMemTotal; else return float(process->vmRSS()) / d->mMemTotal; case HeadingSharedMemory: if(process->vmURSS() == -1 || d->mMemTotal <= 0) return -1; return float(process->vmRSS() - process->vmURSS())/d->mMemTotal; case HeadingVmPSS: if (process->vmPSS() == -1 || d->mMemTotal <= 0) { return -1; } return float(process->vmPSS()) / d->mMemTotal; default: return -1; } } case PercentageHistoryRole: { KSysGuard::Process *process = reinterpret_cast< KSysGuard::Process * > (index.internalPointer()); Q_CHECK_PTR(process); switch(index.column()) { case HeadingCPUUsage: { auto it = d->mMapProcessCPUHistory.find(process); if (it == d->mMapProcessCPUHistory.end()) { it = d->mMapProcessCPUHistory.insert(process, {}); it->reserve(ProcessModelPrivate::MAX_HIST_ENTRIES); } return QVariant::fromValue(*it); } default: {} } return QVariant::fromValue(QVector{}); } case Qt::DecorationRole: { if(index.column() == HeadingName) { #if HAVE_X11 KSysGuard::Process *process = reinterpret_cast< KSysGuard::Process * > (index.internalPointer()); if(!process->hasManagedGuiWindow()) { if(d->mSimple) //When not in tree mode, we need to pad the name column where we do not have an icon return QIcon(d->mBlankPixmap); else //When in tree mode, the padding looks bad, so do not pad in this case return QVariant(); } WindowInfo *w = d->mPidToWindowInfo.value(process->pid(), NULL); if(w && !w->icon.isNull()) return w->icon; return QIcon(d->mBlankPixmap); #else return QVariant(); #endif } else if (index.column() == HeadingCPUUsage) { KSysGuard::Process *process = reinterpret_cast< KSysGuard::Process * > (index.internalPointer()); if(process->status() == KSysGuard::Process::Stopped || process->status() == KSysGuard::Process::Zombie) { // QPixmap pix = KIconLoader::global()->loadIcon("button_cancel", KIconLoader::Small, // KIconLoader::SizeSmall, KIconLoader::DefaultState, QStringList(), // 0L, true); } } return QVariant(); } case Qt::BackgroundRole: { if (index.column() != HeadingUser) { if (!d->mHaveTimer) //If there is no timer, then no processes are being killed, so no point looking for one return QVariant(); KSysGuard::Process *process = reinterpret_cast< KSysGuard::Process * > (index.internalPointer()); if (!process->timeKillWasSent().isNull()) { int elapsed = process->timeKillWasSent().elapsed(); if (elapsed < MILLISECONDS_TO_SHOW_RED_FOR_KILLED_PROCESS) {//Only show red for about 7 seconds int transparency = 255 - elapsed*250/MILLISECONDS_TO_SHOW_RED_FOR_KILLED_PROCESS; KColorScheme scheme(QPalette::Active, KColorScheme::Selection); QBrush brush = scheme.background(KColorScheme::NegativeBackground); QColor color = brush.color(); color.setAlpha(transparency); brush.setColor(color); return brush; } } return QVariant(); } KSysGuard::Process *process = reinterpret_cast< KSysGuard::Process * > (index.internalPointer()); if(process->status() == KSysGuard::Process::Ended) { return QColor(Qt::lightGray); } if(process->tracerpid() >= 0) { //It's being debugged, so probably important. Let's mark it as such return QColor(Qt::yellow); } if(d->mIsLocalhost && process->uid() == getuid()) { //own user return QColor(0, 208, 214, 50); } if(process->uid() < 100 || !canUserLogin(process->uid())) return QColor(218, 220,215, 50); //no color for system tasks //other users return QColor(2, 154, 54, 50); } case Qt::FontRole: { if(index.column() == HeadingCPUUsage) { KSysGuard::Process *process = reinterpret_cast< KSysGuard::Process * > (index.internalPointer()); if(process->userUsage() == 0) { QFont font; font.setItalic(true); return font; } } return QVariant(); } default: //This is a very very common case, so the route to this must be very minimal return QVariant(); } return QVariant(); //never get here, but make compiler happy } bool ProcessModel::hasGUIWindow(qlonglong pid) const { #if HAVE_X11 return d->mPidToWindowInfo.contains(pid); #else return false; #endif } bool ProcessModel::isLocalhost() const { return d->mIsLocalhost; } void ProcessModel::setupHeader() { //These must be in the same order that they are in the header file QStringList headings; headings << i18nc("process heading", "Name"); headings << i18nc("process heading", "Username"); headings << i18nc("process heading", "PID"); headings << i18nc("process heading", "TTY"); headings << i18nc("process heading", "Niceness"); // xgettext: no-c-format headings << i18nc("process heading", "CPU %"); headings << i18nc("process heading", "CPU Time"); headings << i18nc("process heading", "IO Read"); headings << i18nc("process heading", "IO Write"); headings << i18nc("process heading", "Virtual Size"); headings << i18nc("process heading", "Memory"); headings << i18nc("process heading", "Shared Mem"); headings << i18nc("process heading", "Relative Start Time"); headings << i18nc("process heading", "NNP"); headings << i18nc("process heading", "Command"); #if HAVE_X11 if (d->mIsX11) { headings << i18nc("process heading", "X11 Memory"); headings << i18nc("process heading", "Window Title"); } #endif headings << i18nc("process heading", "CGroup"); headings << i18nc("process heading", "MAC Context"); 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()); d->mHeadings = headings; headerDataChanged(Qt::Horizontal, 0 , headings.count()-1); } } void ProcessModel::retranslateUi() { setupHeader(); } KSysGuard::Process *ProcessModel::getProcess(qlonglong pid) { return d->mProcesses->getProcess(pid); } bool ProcessModel::showTotals() const { return d->mShowChildTotals; } void ProcessModel::setShowTotals(bool showTotals) //slot { if(showTotals == d->mShowChildTotals) return; d->mShowChildTotals = showTotals; QModelIndex index; foreach( KSysGuard::Process *process, d->mProcesses->getAllProcesses()) { if(process->numChildren() > 0) { int row; if(d->mSimple) row = process->index(); else row = process->parent()->children().indexOf(process); index = createIndex(row, HeadingCPUUsage, process); emit dataChanged(index, index); } } } qlonglong ProcessModel::totalMemory() const { return d->mMemTotal; } void ProcessModel::setUnits(Units units) { if(d->mUnits == units) return; d->mUnits = units; QModelIndex index; foreach( KSysGuard::Process *process, d->mProcesses->getAllProcesses()) { int row; if(d->mSimple) row = process->index(); else row = process->parent()->children().indexOf(process); index = createIndex(row, HeadingMemory, process); emit dataChanged(index, index); index = createIndex(row, HeadingXMemory, process); emit dataChanged(index, index); index = createIndex(row, HeadingSharedMemory, process); emit dataChanged(index, index); index = createIndex(row, HeadingVmSize, process); emit dataChanged(index, index); } } ProcessModel::Units ProcessModel::units() const { return (Units) d->mUnits; } void ProcessModel::setIoUnits(Units units) { if(d->mIoUnits == units) return; d->mIoUnits = units; QModelIndex index; foreach( KSysGuard::Process *process, d->mProcesses->getAllProcesses()) { int row; if(d->mSimple) row = process->index(); else row = process->parent()->children().indexOf(process); index = createIndex(row, HeadingIoRead, process); emit dataChanged(index, index); index = createIndex(row, HeadingIoWrite, process); emit dataChanged(index, index); } } ProcessModel::Units ProcessModel::ioUnits() const { return (Units) d->mIoUnits; } void ProcessModel::setIoInformation( ProcessModel::IoInformation ioInformation ) { d->mIoInformation = ioInformation; } ProcessModel::IoInformation ProcessModel::ioInformation() const { return d->mIoInformation; } QString ProcessModel::formatMemoryInfo(qlonglong amountInKB, Units units, bool returnEmptyIfValueIsZero) const { //We cache the result of i18n for speed reasons. We call this function //hundreds of times, every second or so if(returnEmptyIfValueIsZero && amountInKB == 0) return QString(); static QString percentageString = i18n("%1%", QString::fromLatin1("%1")); if (units == UnitsPercentage) { if(d->mMemTotal == 0) return QLatin1String(""); //memory total not determined yet. Shouldn't happen, but don't crash if it does float percentage = amountInKB*100.0/d->mMemTotal; if(percentage < 0.1) percentage = 0.1; return percentageString.arg(percentage, 0, 'f', 1); } else return formatByteSize(amountInKB, units); } QString ProcessModel::hostName() const { return d->mHostName; } QStringList ProcessModel::mimeTypes() const { QStringList types; types << QStringLiteral("text/plain"); types << QStringLiteral("text/csv"); types << QStringLiteral("text/html"); return types; } QMimeData *ProcessModel::mimeData(const QModelIndexList &indexes) const { QMimeData *mimeData = new QMimeData(); QString textCsv; QString textCsvHeaders; QString textPlain; QString textPlainHeaders; QString textHtml; QString textHtmlHeaders; QString display; int firstColumn = -1; bool firstrow = true; foreach (const QModelIndex &index, indexes) { if (index.isValid()) { if(firstColumn == -1) firstColumn = index.column(); else if(firstColumn != index.column()) continue; else { textCsv += QLatin1Char('\n'); textPlain += QLatin1Char('\n'); textHtml += QLatin1String("
") + heading + QStringLiteral("") + display.toHtmlEscaped() + QStringLiteral("
") + textHtmlHeaders + QStringLiteral("") + textHtml + QStringLiteral("
"); textCsv = textCsvHeaders + QLatin1Char('\n') + textCsv; textPlain = textPlainHeaders + QLatin1Char('\n') + textPlain; mimeData->setText(textPlain); mimeData->setHtml(textHtml); mimeData->setData(QStringLiteral("text/csv"), textCsv.toUtf8()); return mimeData; } Qt::ItemFlags ProcessModel::flags(const QModelIndex &index) const { if (!index.isValid()) return Qt::NoItemFlags; //Would this ever happen? KSysGuard::Process *process = reinterpret_cast< KSysGuard::Process * > (index.internalPointer()); if(process->status() == KSysGuard::Process::Ended) return Qt::ItemIsDragEnabled | Qt::ItemIsSelectable; else return Qt::ItemIsDragEnabled | Qt::ItemIsSelectable | Qt::ItemIsEnabled; } bool ProcessModel::isShowCommandLineOptions() const { return d->mShowCommandLineOptions; } void ProcessModel::setShowCommandLineOptions(bool showCommandLineOptions) { d->mShowCommandLineOptions = showCommandLineOptions; } bool ProcessModel::isShowingTooltips() const { return d->mShowingTooltips; } void ProcessModel::setShowingTooltips(bool showTooltips) { d->mShowingTooltips = showTooltips; } bool ProcessModel::isNormalizedCPUUsage() const { return d->mNormalizeCPUUsage; } void ProcessModel::setNormalizedCPUUsage(bool normalizeCPUUsage) { d->mNormalizeCPUUsage = normalizeCPUUsage; } void ProcessModelPrivate::timerEvent( QTimerEvent * event ) { Q_UNUSED(event); foreach (qlonglong pid, mPidsToUpdate) { KSysGuard::Process *process = mProcesses->getProcess(pid); if (process && !process->timeKillWasSent().isNull() && process->timeKillWasSent().elapsed() < MILLISECONDS_TO_SHOW_RED_FOR_KILLED_PROCESS) { int row; if(mSimple) row = process->index(); else row = process->parent()->children().indexOf(process); QModelIndex index1 = q->createIndex(row, 0, process); QModelIndex index2 = q->createIndex(row, mHeadings.count()-1, process); emit q->dataChanged(index1, index2); } else { mPidsToUpdate.removeAll(pid); } } if (mPidsToUpdate.isEmpty()) { mHaveTimer = false; killTimer(mTimerId); mTimerId = -1; } } diff --git a/processui/ProcessModel_p.h b/processui/ProcessModel_p.h index ecb6a1b..366e14d 100644 --- a/processui/ProcessModel_p.h +++ b/processui/ProcessModel_p.h @@ -1,225 +1,227 @@ /* KSysGuard, the KDE System Guard Copyright (c) 2006-2007 John Tapsell 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. */ #ifndef PROCESSMODEL_P_H_ #define PROCESSMODEL_P_H_ -#include +#include #include "ProcessModel.h" #include #include #include #include #include #include #include #include #include #include "../config-ksysguard.h" #if HAVE_X11 #include #include #include #include #include #include struct WindowInfo { WindowInfo(WId _wid, qlonglong _pid) { wid = _wid; pid = 0; pid = _pid; } qlonglong pid; QPixmap icon; WId wid; QString name; }; #endif namespace KSysGuard { class Processes; } class ProcessModelPrivate : public QObject { Q_OBJECT public: ProcessModelPrivate(); ~ProcessModelPrivate() override; public Q_SLOTS: #if HAVE_X11 /** When an X window is changed, this is called */ void windowChanged(WId wid, unsigned int properties); /** When an X window is created, this is called */ void windowAdded(WId wid); /** When an X window is closed, this is called */ void windowRemoved(WId wid); #endif /** Change the data for a process. This is called from KSysGuard::Processes * if @p onlyCpuOrMem is set, only the total cpu usuage is updated. * process->changes contains a bitfield of what has been changed */ void processChanged(KSysGuard::Process *process, bool onlyCpuOrMem); /** Called from KSysGuard::Processes * This indicates we are about to insert a process in the model. Emit the appropriate signals */ void beginInsertRow( KSysGuard::Process *parent); /** Called from KSysGuard::Processes * We have finished inserting a process */ void endInsertRow(); /** Called from KSysGuard::Processes * This indicates we are about to remove a process in the model. Emit the appropriate signals */ void beginRemoveRow( KSysGuard::Process *process); /** Called from KSysGuard::Processes * We have finished removing a process */ void endRemoveRow(); /** Called from KSysGuard::Processes * This indicates we are about to move a process in the model from one parent process to another. Emit the appropriate signals */ void beginMoveProcess(KSysGuard::Process *process, KSysGuard::Process *new_parent); /** Called from KSysGuard::Processes * We have finished moving a process */ void endMoveRow(); public: /** Connects to the host */ void setupProcesses(); /** A mapping of running,stopped,etc to a friendly description like 'Stopped, either by a job control signal or because it is being traced.'*/ QString getStatusDescription(KSysGuard::Process::ProcessStatus status) const; /** Return a qt markup tooltip string for a local user. It will have their full name etc. * This will be slow the first time, as it practically indirectly reads the whole of /etc/passwd * But the second time will be as fast as hash lookup as we cache the result */ inline QString getTooltipForUser(const KSysGuard::Process *process) const; /** Return a username for a local user if it can, otherwise just their uid. * This may have been given from the result of "ps" (but not necessarily). * If it's not found, then it needs to find out the username from the uid. * This will be slow the first time, as it practically indirectly reads the whole of /etc/passwd * But the second time will be as fast as hash lookup as we cache the result * * If withuid is set, and the username is found, return: "username (Uid: uid)" */ inline QString getUsernameForUser(long uid, bool withuid) const; /** Return the groupname for a given gid. This is in the form of "gid" if not known, or * "groupname (Uid: gid)" if known. */ inline QString getGroupnameForGroup(long gid) const; #if HAVE_X11 /** On X11 system, connects to the signals emitted when windows are created/destroyed */ void setupWindows(); void updateWindowInfo(WId wid, unsigned int properties, bool newWindow); QMultiHash< long long, WindowInfo *> mPidToWindowInfo; ///< Map a process pid to X window info if available QHash< WId, WindowInfo *> mWIdToWindowInfo; ///< Map an X window id to window info #ifdef HAVE_XRES bool updateXResClientData(); void queryForAndUpdateAllXWindows(); #endif #endif void timerEvent ( QTimerEvent * event ) override; ///< Call dataChanged() for all the processes in mPidsToUpdate /** @see setIsLocalhost */ bool mIsLocalhost; /** A caching hash for tooltips for a user. * @see getTooltipForUser */ mutable QHash mUserTooltips; /** A caching hash for username for a user uid, or just their uid if it can't be found (as a long long) * @see getUsernameForUser */ mutable QHash mUserUsername; /** A mapping of a user id to whether this user can log in. We have to guess based on the shell. * All are set to true to non localhost. * It is set to: * 0 if the user cannot login * 1 is the user can login * The reason for using an int and not a bool is so that we can do * \code mUidCanLogin.value(uid,-1) \endcode and thus we get a tristate for whether * they are logged in, not logged in, or not known yet. * */ mutable QHash mUidCanLogin; /** A translated list of headings (column titles) in the order we want to display them. Used in headerData() */ QStringList mHeadings; bool mShowChildTotals; ///< If set to true, a parent will return the CPU usage of all its children recursively bool mSimple; //< In simple mode, the model returns everything as flat, with no icons, etc. This is set by changing cmbFilter QTime mLastUpdated; ///< Time that we last updated the processes. 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 /** Show the process command line options in the process name column */ bool mShowCommandLineOptions; bool mShowingTooltips; bool mNormalizeCPUUsage; /** When displaying memory sizes, this is the units it should be displayed in */ ProcessModel::Units mUnits; ProcessModel::Units mIoUnits; ProcessModel::IoInformation mIoInformation; /** The hostname */ QString mHostName; bool mHaveTimer; int mTimerId; QList mPidsToUpdate; ///< A list of pids that we need to emit dataChanged() for regularly static const int MAX_HIST_ENTRIES = 100; static const int MIN_HIST_AGE = 200; ///< If the latest history entry is at least this ms old, a new one gets added /** 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; #endif bool mMovingRow; bool mRemovingRow; bool mInsertingRow; bool mIsX11; ProcessModel* q; }; #endif