diff --git a/declarativeplugin/kdeconnectdeclarativeplugin.cpp b/declarativeplugin/kdeconnectdeclarativeplugin.cpp --- a/declarativeplugin/kdeconnectdeclarativeplugin.cpp +++ b/declarativeplugin/kdeconnectdeclarativeplugin.cpp @@ -101,6 +101,10 @@ return new RemoteSystemVolumeDbusInterface(deviceId.toString()); } +QObject* createSystemStatsInterface(const QVariant& deviceId) +{ + return new SystemStatsDbusInterface(deviceId.toString()); +} void KdeConnectDeclarativePlugin::registerTypes(const char* uri) { @@ -118,6 +122,7 @@ qmlRegisterUncreatableType(uri, 1, 0, "RemoteCommandsDbusInterface", QStringLiteral("You're not supposed to instantiate interfaces")); qmlRegisterUncreatableType(uri, 1, 0, "RemoteSystemVolumeInterface", QStringLiteral("You're not supposed to instantiate interfaces")); qmlRegisterUncreatableType(uri, 1, 0, "ShareDbusInterface", QStringLiteral("You're not supposed to instantiate interfaces")); + qmlRegisterUncreatableType(uri, 1, 0, "SystemStatsInterface", QStringLiteral("You're not supposed to instantiate interfaces")); qmlRegisterSingletonType(uri, 1, 0, "DaemonDbusInterface", [](QQmlEngine*, QJSEngine*) -> QObject* { return new DaemonDbusInterface; @@ -171,4 +176,7 @@ engine->rootContext()->setContextProperty(QStringLiteral("RemoteSystemVolumeDbusInterfaceFactory") , new ObjectFactory(engine, createRemoteSystemVolumeInterface)); + engine->rootContext()->setContextProperty(QStringLiteral("SystemStatsDbusInterfaceFactory") + , new ObjectFactory(engine, createSystemStatsInterface)); + } diff --git a/interfaces/CMakeLists.txt b/interfaces/CMakeLists.txt --- a/interfaces/CMakeLists.txt +++ b/interfaces/CMakeLists.txt @@ -53,6 +53,7 @@ geninterface(${CMAKE_SOURCE_DIR}/plugins/sms/conversationsdbusinterface.h conversationsinterface) geninterface(${CMAKE_SOURCE_DIR}/plugins/share/shareplugin.h shareinterface) geninterface(${CMAKE_SOURCE_DIR}/plugins/remotesystemvolume/remotesystemvolumeplugin.h remotesystemvolumeinterface) +geninterface(${CMAKE_SOURCE_DIR}/plugins/systemstats/systemstatsplugin.h systemstatsinterface) diff --git a/interfaces/dbusinterfaces.h b/interfaces/dbusinterfaces.h --- a/interfaces/dbusinterfaces.h +++ b/interfaces/dbusinterfaces.h @@ -39,6 +39,7 @@ #include "interfaces/conversationsinterface.h" #include "interfaces/shareinterface.h" #include "interfaces/remotesystemvolumeinterface.h" +#include "interfaces/systemstatsinterface.h" /** * Using these "proxy" classes just in case we need to rename the @@ -239,6 +240,15 @@ ~RemoteSystemVolumeDbusInterface() = default; }; +class KDECONNECTINTERFACES_EXPORT SystemStatsDbusInterface + : public OrgKdeKdeconnectDeviceSystemstatsInterface +{ + Q_OBJECT +public: + explicit SystemStatsDbusInterface(const QString& deviceId, QObject* parent = nullptr); + ~SystemStatsDbusInterface() override; +}; + template static void setWhenAvailable(const QDBusPendingReply& pending, W func, QObject* parent) { diff --git a/interfaces/dbusinterfaces.cpp b/interfaces/dbusinterfaces.cpp --- a/interfaces/dbusinterfaces.cpp +++ b/interfaces/dbusinterfaces.cpp @@ -194,3 +194,10 @@ OrgKdeKdeconnectDeviceRemotesystemvolumeInterface(DaemonDbusInterface::activatedService(), "/modules/kdeconnect/devices/" + deviceId + "/remotesystemvolume", QDBusConnection::sessionBus(), parent) { } + +SystemStatsDbusInterface::SystemStatsDbusInterface(const QString& deviceId, QObject* parent): + OrgKdeKdeconnectDeviceSystemstatsInterface(DaemonDbusInterface::activatedService(), "/modules/kdeconnect/devices/" + deviceId + "/systemstats", QDBusConnection::sessionBus(), parent) +{ +} + +SystemStatsDbusInterface::~SystemStatsDbusInterface() = default; diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -38,6 +38,7 @@ add_subdirectory(remotecontrol) add_subdirectory(lockdevice) add_subdirectory(remotesystemvolume) + add_subdirectory(systemstats) endif() if(KF5PulseAudioQt_FOUND OR WIN32) diff --git a/plugins/systemstats/CMakeLists.txt b/plugins/systemstats/CMakeLists.txt new file mode 100644 --- /dev/null +++ b/plugins/systemstats/CMakeLists.txt @@ -0,0 +1,11 @@ +set(kdeconnect_systemstats_SRCS + systemstatsplugin.cpp +) + +kdeconnect_add_plugin(kdeconnect_systemstats JSON kdeconnect_systemstats.json SOURCES ${kdeconnect_systemstats_SRCS}) + +target_link_libraries(kdeconnect_systemstats + kdeconnectcore + Qt5::DBus + KF5::I18n +) diff --git a/plugins/systemstats/kdeconnect_systemstats.json b/plugins/systemstats/kdeconnect_systemstats.json new file mode 100644 --- /dev/null +++ b/plugins/systemstats/kdeconnect_systemstats.json @@ -0,0 +1,29 @@ +{ + "KPlugin": { + "Authors": [ + { + "Email": "blaws05@gmail.com", + "Name": "Billy Laws" + } + ], + "Description": "Report system resource usage", + "EnabledByDefault": true, + "Icon": "dialog-ok", + "Id": "kdeconnect_systemstats", + "License": "GPL", + "Name": "System Stats", + "ServiceTypes": [ + "KdeConnect/Plugin" + ], + "Version": "0.1", + "Website": "https://bylaws.gitlab.io" + }, + "X-KdeConnect-OutgoingPacketType": [ + "kdeconnect.systemstats.request", + "kdeconnect.systemstats" + ], + "X-KdeConnect-SupportedPacketType": [ + "kdeconnect.systemstats", + "kdeconnect.systemstats.request" + ] +} diff --git a/plugins/systemstats/systemstatsplugin.h b/plugins/systemstats/systemstatsplugin.h new file mode 100644 --- /dev/null +++ b/plugins/systemstats/systemstatsplugin.h @@ -0,0 +1,82 @@ +/** + * Copyright 2018 Billy Laws + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef SYSTEMSTATSPLUGIN_H +#define SYSTEMSTATSPLUGIN_H + +#include + +#include + +#define PACKET_TYPE_SYSTEMSTATS QStringLiteral("kdeconnect.systemstats") +#define PACKET_TYPE_SYSTEMSTATS_REQUEST QStringLiteral("kdeconnect.systemstats.request") +#define NUM_CPU_STATES 10 + +enum CPUStates +{ + S_USER = 0, + S_NICE, + S_SYSTEM, + S_IDLE, + S_IOWAIT, + S_IRQ, + S_SOFTIRQ, + S_STEAL, + S_GUEST, + S_GUEST_NICE +}; + +typedef struct CPUData { + QString cpu; + size_t times[NUM_CPU_STATES]; +} CPUData; + +class Q_DECL_EXPORT SystemStatsPlugin + : public KdeConnectPlugin +{ + Q_OBJECT + Q_CLASSINFO("D-Bus Interface", "org.kde.kdeconnect.device.systemstats") + Q_PROPERTY(int CPUUsage READ CPUUsage NOTIFY propertiesChanged) + +public: + explicit SystemStatsPlugin(QObject* parent, const QVariantList& args); + ~SystemStatsPlugin() override; + + int CPUUsage() const; + Q_SCRIPTABLE void requestStatsUpdate(); + + bool receivePacket(const NetworkPacket& np) override; + void connected() override {}; + + QString dbusPath() const override; + +Q_SIGNALS: + Q_SCRIPTABLE void propertiesChanged(); + +private: + int getIdleTime(const CPUData& entry); + int getActiveTime(const CPUData& entry); + int getCPUUsage(int core); + void readStatsCPU(QVector& entries); + + int m_CPUUsage; +}; + +#endif diff --git a/plugins/systemstats/systemstatsplugin.cpp b/plugins/systemstats/systemstatsplugin.cpp new file mode 100644 --- /dev/null +++ b/plugins/systemstats/systemstatsplugin.cpp @@ -0,0 +1,160 @@ +/** + * Copyright 2018 Billy Laws + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "systemstatsplugin.h" + +#include +#include + +#include +#include +#include + +#include +#include + +K_PLUGIN_FACTORY_WITH_JSON( KdeConnectPluginFactory, "kdeconnect_systemstats.json", registerPlugin< SystemStatsPlugin >(); ) + +Q_LOGGING_CATEGORY(KDECONNECT_PLUGIN_SYSTEMSTATS, "kdeconnect.plugin.systemstats") + +SystemStatsPlugin::SystemStatsPlugin(QObject* parent, const QVariantList& args) + : KdeConnectPlugin(parent, args) +{ +// qCDebug(KDECONNECT_PLUGIN_SYSTEMSTATS) << "SystemStats plugin constructor for device" << device()->name(); +} + +SystemStatsPlugin::~SystemStatsPlugin() +{ +// qCDebug(KDECONNECT_PLUGIN_SYSTEMSTATS) << "SystemStats plugin destructor for device" << device()->name(); +} + +bool SystemStatsPlugin::receivePacket(const NetworkPacket& np) +{ + if (np.type() == PACKET_TYPE_SYSTEMSTATS) { + m_CPUUsage = np.get(QStringLiteral("CPUUsage"), 100); + Q_EMIT propertiesChanged(); + } + + if (np.type() == PACKET_TYPE_SYSTEMSTATS_REQUEST) { + NetworkPacket answer(PACKET_TYPE_SYSTEMSTATS); + if (np.get(QStringLiteral("requestCPUUsage"))) + answer.set(QStringLiteral("CPUUsage"), getCPUUsage(0)); + + sendPacket(answer); + } else { + return false; + } + + return true; +} + +int SystemStatsPlugin::getCPUUsage(int core){ + QVector entries1; + QVector entries2; + + readStatsCPU(entries1); + + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + + readStatsCPU(entries2); + + const CPUData& entry1 = entries1[core]; + const CPUData& entry2 = entries2[core]; + + const float ACTIVE_TIME = getActiveTime(entry2) - getActiveTime(entry1); + const float IDLE_TIME = getIdleTime(entry2) - getIdleTime(entry1); + + return (100.f * ACTIVE_TIME / (ACTIVE_TIME + IDLE_TIME)); +} + +void SystemStatsPlugin::readStatsCPU(QVector& entries) +{ + QFile fileStat("/proc/stat"); + fileStat.open(QIODevice::ReadOnly | QIODevice::Text); + + QTextStream statStream(&fileStat); + QString line = statStream.readLine(); + + while (!statStream.atEnd()) { + if (line.startsWith("cpu")) { + QTextStream ss(&line); + + // store entry + CPUData entry; + + // read cpu label + ss >> entry.cpu; + + if (entry.cpu.size() > static_cast(strlen("cpu"))){ + entry.cpu.remove(0, strlen("cpu")); + } else { + entry.cpu = "total"; + } + + // read times + for(int i = 0; i < NUM_CPU_STATES; ++i) { + ss >> entry.times[i]; + } + + entries.append(entry); + } + + line = statStream.readLine(); + } +} + +int SystemStatsPlugin::getIdleTime(const CPUData& entry) +{ + return entry.times[S_IDLE] + + entry.times[S_IOWAIT]; +} + +int SystemStatsPlugin::getActiveTime(const CPUData& entry) +{ + return entry.times[S_USER] + + entry.times[S_NICE] + + entry.times[S_SYSTEM] + + entry.times[S_IRQ] + + entry.times[S_SOFTIRQ] + + entry.times[S_STEAL] + + entry.times[S_GUEST] + + entry.times[S_GUEST_NICE]; +} + +int SystemStatsPlugin::CPUUsage() const { + return m_CPUUsage; +} + +void SystemStatsPlugin::requestStatsUpdate() +{ + NetworkPacket np(PACKET_TYPE_SYSTEMSTATS_REQUEST, { + {"requestCPUUsage", true}} + ); + + sendPacket(np); +} + +QString SystemStatsPlugin::dbusPath() const +{ + return "/modules/kdeconnect/devices/" + device()->id() + "/systemstats"; +} + +#include "systemstatsplugin.moc" +