// SPDX-License-Identifier: GPL-3.0-or-later /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * Copyright (C) 2013 - 2024, nymea GmbH * Copyright (C) 2024 - 2025, chargebyte austria GmbH * * This file is part of nymea-plugins. * * nymea-plugins 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 3 of the License, or * (at your option) any later version. * * nymea-plugins 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 nymea-plugins. If not, see . * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ #include "integrationpluginsystemmonitor.h" #include "plugininfo.h" #include #include #include #include #include #include IntegrationPluginSystemMonitor::IntegrationPluginSystemMonitor() { } IntegrationPluginSystemMonitor::~IntegrationPluginSystemMonitor() { if (m_refreshTimer) { hardwareManager()->pluginTimerManager()->unregisterTimer(m_refreshTimer); } } void IntegrationPluginSystemMonitor::setupThing(ThingSetupInfo *info) { if (!m_refreshTimer) { m_refreshTimer = hardwareManager()->pluginTimerManager()->registerTimer(2); connect(m_refreshTimer, &PluginTimer::timeout, this, [=](){ foreach (Thing *thing, myThings()) { if (thing->thingClassId() == systemMonitorThingClassId) { updateSystemMonitor(thing); } else if (thing->thingClassId() == processMonitorThingClassId) { updateProcessMonitor(thing); } } }); } info->finish(Thing::ThingErrorNoError); } void IntegrationPluginSystemMonitor::thingRemoved(Thing *thing) { Q_UNUSED(thing) if (myThings().isEmpty()) { hardwareManager()->pluginTimerManager()->unregisterTimer(m_refreshTimer); m_refreshTimer = nullptr; } m_oldTotalJiffies.remove(thing); m_oldWorkJiffies.remove(thing); m_oldProcessWorkJiffies.remove(thing); } void IntegrationPluginSystemMonitor::updateSystemMonitor(Thing *thing) { double cpuPercentage = readTotalCpuUsage(thing); if (cpuPercentage >= 0) { thing->setStateValue(systemMonitorCpuUsageStateTypeId, cpuPercentage); } thing->setStateValue(systemMonitorPercentMemoryStateTypeId, readTotalMemoryUsage()); QStorageInfo storageInfo = QStorageInfo::root(); double percentage = 100.0 * (storageInfo.bytesTotal() - storageInfo.bytesFree()) / storageInfo.bytesTotal(); thing->setStateValue(systemMonitorPercentStorageStateTypeId, percentage); } void IntegrationPluginSystemMonitor::updateProcessMonitor(Thing *thing) { QString processName = thing->paramValue(processMonitorThingProcessNameParamTypeId).toString(); // For backwards compatibility we'll use nymead if the new parameter (version 1.3) isn't set at all yet. if (processName.isEmpty()) { processName = "nymead"; } qint32 pid = getPidByName(processName); if (pid == -1) { thing->setStateValue(processMonitorRunningStateTypeId, false); return; } thing->setStateValue(processMonitorRunningStateTypeId, true); quint32 total, rss, shared; double percentage; if (readProcessMemoryUsage(pid, total, rss, shared, percentage)) { thing->setStateValue(processMonitorPercentMemoryStateTypeId, percentage); thing->setStateValue(processMonitorRssMemoryStateTypeId, rss); thing->setStateValue(processMonitorVirtualMemoryStateTypeId, total); thing->setStateValue(processMonitorSharedMemoryStateTypeId, shared); } thing->setStateValue(processMonitorCpuUsageStateTypeId, readProcessCpuUsage(pid, thing)); } double IntegrationPluginSystemMonitor::readTotalCpuUsage(Thing *thing) { QFile f("/proc/stat"); if (!f.open(QFile::ReadOnly)) { qCWarning(dcSystemMonitor()) << "Unable to open /proc/stat. Cannot read CPU usage"; return -1; } QByteArray cpuStat = f.readLine().replace(" ", " "); f.close(); qCDebug(dcSystemMonitor()) << "SystemCPU:" << "stat:" << cpuStat; QList parts = cpuStat.split(' '); if (parts.first() != "cpu" || parts.count() < 8) { qCWarning(dcSystemMonitor()) << "/proc/stat not in expected format"; return -1; } qulonglong systemJiffies = parts.at(1).toULong(); // qulonglong niceJiffies = parts.at(2).toULong(); qulonglong userJiffies = parts.at(3).toULong(); qulonglong idleJiffies = parts.at(4).toULong(); // qulonglong ioWaitJiffies = parts.at(5).toULong(); // qulonglong irqJiffies = parts.at(6).toULong(); // qulonglong softirqJiffies = parts.at(7).toULong(); qulonglong workJiffies = systemJiffies + userJiffies; qulonglong totalJiffies = workJiffies + idleJiffies; double cpuPercentage = 0; if (m_oldTotalJiffies.contains(thing)) { qulonglong oldTotalJiffies = m_oldTotalJiffies.value(thing); qulonglong oldWorkJiffies = m_oldWorkJiffies.value(thing); qCDebug(dcSystemMonitor()) << "SystemCPU:" << "Current work:" << workJiffies << "total:" << totalJiffies << "Old work:" << oldWorkJiffies << "total:" << oldTotalJiffies; if (workJiffies < oldWorkJiffies || totalJiffies < oldTotalJiffies) { // One of the values overflowed. Skipping this cycle m_oldTotalJiffies[thing] = totalJiffies; m_oldWorkJiffies[thing] = workJiffies; return -1; } qulonglong totalJiffDiff = totalJiffies - oldTotalJiffies; qulonglong workJiffDiff = workJiffies - oldWorkJiffies; cpuPercentage = 100.0 * workJiffDiff / totalJiffDiff; } m_oldTotalJiffies[thing] = totalJiffies; m_oldWorkJiffies[thing] = workJiffies; return cpuPercentage; } double IntegrationPluginSystemMonitor::readTotalMemoryUsage() { struct sysinfo memInfo; sysinfo(&memInfo); qulonglong totalPhysicalMem = memInfo.totalram; // Multiply in next statement to avoid int overflow on right hand side... totalPhysicalMem *= memInfo.mem_unit; qulonglong physicalMemUsed = (memInfo.totalram - memInfo.freeram - memInfo.bufferram); // Multiply in next statement to avoid int overflow on right hand side... physicalMemUsed *= memInfo.mem_unit; // qCDebug(dcSystemMonitor()) << "Total RAM" << totalPhysicalMem << "used:" << physicalMemUsed; return 100.0 * physicalMemUsed / totalPhysicalMem; } bool IntegrationPluginSystemMonitor::readProcessMemoryUsage(qint32 pid, quint32 &total, quint32 &rss, quint32 &shared, double &percentage) { QFile f(QString("/proc/%1/statm").arg(pid)); if (!f.open(QFile::ReadOnly)) { qCWarning(dcSystemMonitor()).nospace() << "Unable to open " << f.fileName() << ". Cannot read memory usage."; return false; } QByteArray memStat = f.readLine(); f.close(); QList parts = memStat.split(' '); if (parts.count() < 3) { qCWarning(dcSystemMonitor()) << f.fileName() << "not in expected format"; return false; } int tsize = parts.at(0).toInt(); int resident = parts.at(1).toInt(); int share = parts.at(2).toInt(); long page_size_kb = sysconf(_SC_PAGE_SIZE) / 1024; total = tsize; total *= page_size_kb; rss = resident; rss *= page_size_kb; shared = share; shared *= page_size_kb; struct sysinfo memInfo; sysinfo(&memInfo); qulonglong totalMem = memInfo.totalram; totalMem *= memInfo.mem_unit; // totalMem is in Bytes, rss in kb percentage = 100000.0 * rss / totalMem; // qCDebug(dcSystemMonitor()) << "totalMem:" << totalMem << "total:" << total << "rss" << rss << "shared" << shared << "%" << percentage; return true; } double IntegrationPluginSystemMonitor::readProcessCpuUsage(qint32 pid, Thing *thing) { QFile systemFile("/proc/stat"); if (!systemFile.open(QFile::ReadOnly)) { qCWarning(dcSystemMonitor()) << "Unable to open /proc/stat. Cannot read CPU usage"; return 0; } QByteArray cpuStat = systemFile.readLine().replace(" ", " "); systemFile.close(); qCDebug(dcSystemMonitor()) << "ProcessCPU:" << "System stat:" << cpuStat; QList parts = cpuStat.split(' '); if (parts.first() != "cpu" || parts.count() < 8) { qCWarning(dcSystemMonitor()) << "/proc/stat not in expected format"; return 0; } parts.removeFirst(); qulonglong systemJiffies = parts.at(1).toULong(); qulonglong userJiffies = parts.at(3).toULong(); // qulonglong idleJiffies = parts.at(4).toULong(); qulonglong totalJiffies = systemJiffies + userJiffies;// + idleJiffies; QFile f(QString("/proc/%1/stat").arg(pid)); if (!f.open(QFile::ReadOnly)) { qCWarning(dcSystemMonitor()).nospace() << "Unable to open " << f.fileName() << ". Cannot read CPU usage."; return 0; } QByteArray stat = f.readLine(); f.close(); qCDebug(dcSystemMonitor()) << "ProcessCPU:" << "Process stat:" << stat; parts = stat.split(' '); if (parts.length() < 15) { qCWarning(dcSystemMonitor()) << f.fileName() << "not in expected format"; return 0; } qulonglong processUserJiffies = parts.at(13).toULongLong(); qulonglong processKernelJiffies = parts.at(14).toULongLong(); qulonglong processChildUserJiffies = parts.at(15).toULongLong(); qulonglong processChildKernelJiffies = parts.at(16).toULongLong(); qulonglong processWorkJiffies = processUserJiffies + processKernelJiffies + processChildUserJiffies + processChildKernelJiffies; double percentage = 0; if (m_oldTotalJiffies.contains(thing)) { qulonglong oldTotalJiffies = m_oldTotalJiffies.value(thing); qulonglong oldProcessWorkJiffies = m_oldProcessWorkJiffies.value(thing); qCDebug(dcSystemMonitor()) << "ProcessCPU:" << "Current total:" << totalJiffies << "process:" << processWorkJiffies << "Old total:" << oldTotalJiffies << "process:" << oldProcessWorkJiffies; qulonglong totalJiffDiff = totalJiffies - oldTotalJiffies; qulonglong processWorkJiffDiff = processWorkJiffies - oldProcessWorkJiffies; if (totalJiffDiff > 0) { percentage = 100.0 * processWorkJiffDiff / totalJiffDiff; } } m_oldTotalJiffies[thing] = totalJiffies; m_oldProcessWorkJiffies[thing] = processWorkJiffies; return percentage; } qint32 IntegrationPluginSystemMonitor::getPidByName(const QString &processName) { QDir proc("/proc"); foreach (const QString dirName, proc.entryList(QDir::AllDirs | QDir::NoDotAndDotDot)) { QFile statusFile(proc.absoluteFilePath(dirName + QDir::separator() + "status")); if (!statusFile.open(QFile::ReadOnly)) { continue; } QString line = statusFile.readLine().trimmed(); line.remove(QRegularExpression("Name:(\\s)*")); // qCDebug(dcSystemMonitor()) << "Found process:" << line << "looking for" << processName.left(15); // names in /proc//status are trimmed to 15 characters... if (processName.left(15) == line.left(15)) { return dirName.toInt(); } } return -1; }