Commit a04a9934 authored by David Redondo's avatar David Redondo 🏎
Browse files

Split off into linuxcpu, add frequencies

parent 69d3bb4a
add_library(ksysguard_plugin_cpu MODULE cpu.cpp cpuplugin.cpp linuxcpu.cpp)
add_library(ksysguard_plugin_cpu MODULE cpu.cpp)
target_link_libraries(ksysguard_plugin_cpu Qt5::Core KSysGuard::StatsBackend KF5::CoreAddons KF5::I18n)
install(TARGETS ksysguard_plugin_cpu DESTINATION ${KDE_INSTALL_PLUGINDIR}/ksysguard)
......@@ -19,36 +19,7 @@
#include "cpu.h"
#include <cstdio>
#include <unistd.h>
#include <QFile>
#include <QElapsedTimer>
#include <QUrl>
#include <KLocalizedString>
#include <KPluginFactory>
#include <AggregateSensor.h>
#include <SensorContainer.h>
#include <SensorObject.h>
class CpuObject : public SensorObject {
public:
CpuObject(const QString &id, const QString &name, SensorContainer *parent);
void setTicks(unsigned long long system, unsigned long long user, unsigned long long wait, unsigned long long idle);
private:
SensorProperty *m_usage;
SensorProperty *m_system;
SensorProperty *m_user;
SensorProperty *m_wait;
// TODO temperature frequency
unsigned long long m_totalTicks;
unsigned long long m_systemTicks;
unsigned long long m_userTicks;
unsigned long long m_waitTicks;
};
CpuObject::CpuObject(const QString &id, const QString &name, SensorContainer *parent)
: SensorObject(id, name, parent)
......@@ -78,112 +49,13 @@ CpuObject::CpuObject(const QString &id, const QString &name, SensorContainer *pa
m_wait->setUnit(KSysGuard::UnitPercent);
m_wait->setVariantType(QVariant::Double);
m_wait->setMax(100);
}
void CpuObject::setTicks(unsigned long long system, unsigned long long user, unsigned long long wait, unsigned long long idle)
{
unsigned long long totalTicks = system + user + wait + idle;
unsigned long long totalDiff = totalTicks - m_totalTicks;
auto percentage = [totalDiff] (unsigned long long tickDiff) {
// according to the documentation some counters can go backwards in some circumstances
return tickDiff > 0 ? 100.0 * tickDiff / totalDiff : 0;
};
m_system->setValue(percentage(system - m_systemTicks));
m_user->setValue(percentage(user - m_userTicks));
m_wait->setValue(percentage(wait - m_waitTicks));
m_usage->setValue(percentage((system + user + wait) - (m_systemTicks + m_userTicks + m_waitTicks)));
m_totalTicks = totalTicks;
m_systemTicks = system;
m_userTicks = user;
m_waitTicks = wait;
}
CpuPlugin::CpuPlugin(QObject *parent, const QVariantList &args)
: SensorPlugin(parent, args)
#ifdef Q_OS_LINUX
, m_container(new SensorContainer(QStringLiteral("cpus"), i18n("CPUs"), this))
{
parseCpuInfo();
}
#else
{
}
#endif
void CpuPlugin::parseCpuInfo()
{
QFile cpuinfo("/proc/cpuinfo");
cpuinfo.open(QIODevice::ReadOnly);
QHash<int, int> numCores;
for (QByteArray line = cpuinfo.readLine(); !line.isEmpty(); line = cpuinfo.readLine()) {
int physicalId = -1;
int processor = -1;
// Processors are divided by empty lines
for (; (physicalId == -1 && processor == -1) || line != "\n"; line = cpuinfo.readLine()) {
// we are interested in processor number as identifier for /proc/stat, physical id (the
// cpu the core belongs to) and the number of the core. However with hyperthreading
// multiple entries will have the same combination of physical id and core id. So we just
// count up the core number.
const int delim = line.indexOf(":");
const QByteArray field = line.left(delim).trimmed();
const QByteArray value = line.mid(delim + 1).trimmed();
if (field == "processor") {
processor = value.toInt();
} else if (field == "physical id") {
physicalId = value.toInt();
}
}
const QString name = i18nc("@title", "CPU %1 Core %2").arg(physicalId + 1).arg(++numCores[physicalId]);
new CpuObject(QStringLiteral("cpu%1").arg(processor), name, m_container);
}
const int cores = m_container->objects().size();
// Add total usage sensors
auto total = new CpuObject(QStringLiteral("all"), i18nc("@title", "All"), m_container);
auto cpuCount = new SensorProperty(QStringLiteral("cpuCount"), i18nc("@title", "Number of CPUs"), numCores.size(), total);
cpuCount->setShortName(i18nc("@title, Short fort 'Number of CPUs'", "CPUs"));
cpuCount->setDescription(i18nc("@info", "Number of physical CPUs installed in the system"));
auto coreCount = new SensorProperty(QStringLiteral("coreCount"), i18nc("@title", "Number of Cores"), cores, total);
coreCount->setShortName(i18nc("@title, Short fort 'Number of Cores'", "Cores"));
coreCount->setDescription(i18nc("@info", "Number of CPU cores across all physical CPUS"));
}
void CpuPlugin::update()
{
// Parse /proc/stat to get usage values. The format is described at
// https://www.kernel.org/doc/html/latest/filesystems/proc.html#miscellaneous-kernel-statistics-in-proc-stat
QFile stat("/proc/stat");
stat.open(QIODevice::ReadOnly);
qint64 elapsed = 0;
if (m_elapsedTimer.isValid()) {
elapsed = m_elapsedTimer.restart();
} else {
m_elapsedTimer.start();
}
QByteArray line;
for (QByteArray line = stat.readLine(); !line.isNull(); line = stat.readLine()) {
CpuObject *cpu;
// Total values
if (line.startsWith("cpu ")) {
cpu = static_cast<CpuObject*>(m_container->object(QStringLiteral("all")));
} else if (line.startsWith("cpu")) {
cpu = static_cast<CpuObject*>(m_container->object(line.left(line.indexOf(' '))));
} else {
continue;
}
unsigned long long user, nice, system, idle, iowait, irq, softirq, steal, guest, guest_nice;
std::sscanf(line.data(), "%*s %llu %llu %llu %llu %llu %llu %llu %llu%llu %llu",
&user, &nice, &system, &idle, &iowait, &irq, &softirq, &steal, &guest, &guest_nice);
cpu->setTicks(system + irq + softirq, user + nice , iowait + steal, idle);
if (id != QStringLiteral("all"))
{
m_frequency = new SensorProperty(QStringLiteral("frequency"), i18nc("@title", "Current Frequency"), this);
m_frequency->setShortName(i18nc("@title, Short for 'Current Frequency'", "Frequency"));
m_frequency->setDescription(i18nc("@info", "Current frequency of the CPU"));
m_frequency->setVariantType(QVariant::Double);
m_frequency->setUnit(KSysGuard::Unit::UnitMegaHertz);
}
}
K_PLUGIN_CLASS_WITH_JSON(CpuPlugin, "metadata.json")
#include "cpu.moc"
......@@ -20,30 +20,23 @@
#ifndef CPU_H
#define CPU_H
#include <QObject>
#include <QElapsedTimer>
#include <SensorObject.h>
#include <SensorPlugin.h>
class SensorContainer;
class CpuPlugin : public SensorPlugin
{
Q_OBJECT
class CpuObject : public SensorObject {
public:
CpuPlugin(QObject *parent, const QVariantList &args);
QString providerName() const override
{
return QStringLiteral("cpu");
}
private:
void parseCpuInfo();
void update() override;
SensorContainer *m_container;
QElapsedTimer m_elapsedTimer;
CpuObject(const QString &id, const QString &name, SensorContainer *parent);
virtual void update() = 0;
// const int physicalId; // NOTE The combination of these two ids is not necessarily unique with hyperthreading
// const int coreId;
protected:
SensorProperty *m_usage;
SensorProperty *m_system;
SensorProperty *m_user;
SensorProperty *m_wait;
SensorProperty *m_frequency;
// TODO temperature
};
#endif
/*
Copyright (c) 2020 David Redondo <kde@david-redondo.de>
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 "cpuplugin.h"
#include "cpuplugin_p.h"
#include <KLocalizedString>
#include <KPluginFactory>
#include <SensorContainer.h>
#include "linuxcpu.h"
CpuPluginPrivate::CpuPluginPrivate(CpuPlugin *q)
: m_container(new SensorContainer(QStringLiteral("cpu"), i18n("CPUs"), q))
{
}
CpuPlugin::CpuPlugin(QObject *parent, const QVariantList &args)
: SensorPlugin(parent, args)
#ifdef Q_OS_LINUX
, d(new LinuxCpuPluginPrivate(this))
#else
, d(new CpuPluginPrivate(this))
#endif
{
}
CpuPlugin::~CpuPlugin() = default;
void CpuPlugin::update()
{
d->update();
}
K_PLUGIN_CLASS_WITH_JSON(CpuPlugin, "metadata.json")
#include "cpuplugin.moc"
/*
Copyright (c) 2020 David Redondo <kde@david-redondo.de>
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 CPUPLUGIN_H
#define CPUPLUGIN_H
#include <SensorPlugin.h>
class CpuPluginPrivate;
class CpuPlugin : public SensorPlugin
{
Q_OBJECT
public:
CpuPlugin(QObject *parent, const QVariantList &args);
~CpuPlugin() override;
QString providerName() const override
{
return QStringLiteral("cpu");
}
void update() override;
private:
std::unique_ptr<CpuPluginPrivate> d;
};
#endif
/*
Copyright (c) 2020 David Redondo <kde@david-redondo.de>
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 CPUPLUGIN_P_H
#define CPUPLUGIN_P_H
class CpuPlugin;
class CpuPluginPrivate {
public:
CpuPluginPrivate(CpuPlugin *q);
virtual ~CpuPluginPrivate() = default;
virtual void update() {};
SensorContainer *m_container;
};
#endif
#include "linuxcpu.h"
#include <QFile>
#include <KLocalizedString>
#include <sensors/sensors.h>
#include <SensorContainer.h>
static double readCpuFreq(const QString &cpuId, const QString &attribute, bool &ok)
{
QFile file(QStringLiteral("/sys/devices/system/cpu/%1/cpufreq/").arg(cpuId) + attribute);
if (file.exists()) {
file.open(QIODevice::ReadOnly);
// Remove trailing new line
return file.readAll().chopped(1).toUInt(&ok) / 1000.0; // CPUFreq reports values in kHZ
}
return 0;
}
LinuxCpuObject::LinuxCpuObject(const QString &id, const QString &name, SensorContainer *parent, double frequency)
: CpuObject(id, name, parent)
{
if (m_frequency) {
m_frequency->setValue(frequency);
bool ok;
const double max = readCpuFreq(id, "cpuinfo_max_freq", ok);
if (ok) {
m_frequency->setMax(max);
}
const double min = readCpuFreq(id, "cpuinfo_min_freq", ok);
if (ok) {
m_frequency->setMin(min);
}
qDebug() << id << frequency << min << max;
}
}
void LinuxCpuObject::setTicks(unsigned long long system, unsigned long long user, unsigned long long wait, unsigned long long idle)
{
unsigned long long totalTicks = system + user + wait + idle;
unsigned long long totalDiff = totalTicks - m_totalTicks;
auto percentage = [totalDiff] (unsigned long long tickDiff) {
// according to the documentation some counters can go backwards in some circumstances
return tickDiff > 0 ? 100.0 * tickDiff / totalDiff : 0;
};
m_system->setValue(percentage(system - m_systemTicks));
m_user->setValue(percentage(user - m_userTicks));
m_wait->setValue(percentage(wait - m_waitTicks));
m_usage->setValue(percentage((system + user + wait) - (m_systemTicks + m_userTicks + m_waitTicks)));
m_totalTicks = totalTicks;
m_systemTicks = system;
m_userTicks = user;
m_waitTicks = wait;
}
void LinuxCpuObject::update()
{
if (!m_frequency) {
return;
}
bool ok = false;
// First try cpuinfo_cur_freq, it is the frequency the hardware runs at (https://www.kernel.org/doc/html/latest/admin-guide/pm/cpufreq.html)
int frequency = readCpuFreq(id(), "cpuinfo_cur_freq", ok);
if (!ok) {
frequency = readCpuFreq(id(), "scaling_cur_freq", ok);
}
if (ok) {
m_frequency->setValue(frequency);
}
// FIXME Should we fall back to reading /proc/cpuinfo again when the above fails? Could have the
// frequency value changed even if the cpu apparently doesn't use CPUFreq?
}
LinuxCpuPluginPrivate::LinuxCpuPluginPrivate(CpuPlugin *q)
: CpuPluginPrivate(q)
{
// Parse /proc/cpuinfo
QFile cpuinfo("/proc/cpuinfo");
cpuinfo.open(QIODevice::ReadOnly);
QHash<int, int> numCores;
for (QByteArray line = cpuinfo.readLine(); !line.isEmpty(); line = cpuinfo.readLine()) {
int physicalId = -1;
int processor = -1;
double frequency = -1;
// Processors are divided by empty lines
for (; (physicalId == -1 && processor == -1 && frequency == -1) || line != "\n"; line = cpuinfo.readLine()) {
// we are interested in processor number as identifier for /proc/stat, physical id (the
// cpu the core belongs to) and the number of the core. However with hyperthreading
// multiple entries will have the same combination of physical id and core id. So we just
// count up the core number.
const int delim = line.indexOf(":");
const QByteArray field = line.left(delim).trimmed();
const QByteArray value = line.mid(delim + 1).trimmed();
if (field == "processor") {
processor = value.toInt();
} else if (field == "physical id") {
physicalId = value.toInt();
} else if (field == "cpu MHz") {
frequency = value.toDouble();
}
}
const QString name = i18nc("@title", "CPU %1 Core %2").arg(physicalId + 1).arg(++numCores[physicalId]);
new LinuxCpuObject(QStringLiteral("cpu%1").arg(processor), name, m_container, frequency);
}
const int cores = m_container->objects().size();
// Add total usage sensors
auto total = new LinuxCpuObject(QStringLiteral("all"), i18nc("@title", "All"), m_container, 0);
auto cpuCount = new SensorProperty(QStringLiteral("cpuCount"), i18nc("@title", "Number of CPUs"), numCores.size(), total);
cpuCount->setShortName(i18nc("@title, Short fort 'Number of CPUs'", "CPUs"));
cpuCount->setDescription(i18nc("@info", "Number of physical CPUs installed in the system"));
auto coreCount = new SensorProperty(QStringLiteral("coreCount"), i18nc("@title", "Number of Cores"), cores, total);
coreCount->setShortName(i18nc("@title, Short fort 'Number of Cores'", "Cores"));
coreCount->setDescription(i18nc("@info", "Number of CPU cores across all physical CPUS"));
}
void LinuxCpuPluginPrivate::update()
{
// Parse /proc/stat to get usage values. The format is described at
// https://www.kernel.org/doc/html/latest/filesystems/proc.html#miscellaneous-kernel-statistics-in-proc-stat
QFile stat("/proc/stat");
stat.open(QIODevice::ReadOnly);
QByteArray line;
for (QByteArray line = stat.readLine(); !line.isNull(); line = stat.readLine()) {
LinuxCpuObject *cpu;
// Total values
if (line.startsWith("cpu ")) {
cpu = static_cast<LinuxCpuObject*>(m_container->object(QStringLiteral("all")));
} else if (line.startsWith("cpu")) {
cpu = static_cast<LinuxCpuObject*>(m_container->object(line.left(line.indexOf(' '))));
} else {
continue;
}
unsigned long long user, nice, system, idle, iowait, irq, softirq, steal, guest, guest_nice;
std::sscanf(line.data(), "%*s %llu %llu %llu %llu %llu %llu %llu %llu%llu %llu",
&user, &nice, &system, &idle, &iowait, &irq, &softirq, &steal, &guest, &guest_nice);
cpu->setTicks(system + irq + softirq, user + nice , iowait + steal, idle);
}
for (SensorObject *cpu : m_container->objects()) {
static_cast<LinuxCpuObject*>(cpu)->update();
}
}
/*
Copyright (c) 2020 David Redondo <kde@david-redondo.de>
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 LINUXCPU_H
#define LINUXCPU_H
#include "cpu.h"
#include "cpuplugin_p.h"
class LinuxCpuObject : public CpuObject
{
public:
LinuxCpuObject(const QString &id, const QString &name, SensorContainer *parent, double frequency);
void setTicks(unsigned long long system, unsigned long long user, unsigned long long wait, unsigned long long idle);
void update() override;
private:
unsigned long long m_totalTicks;
unsigned long long m_systemTicks;
unsigned long long m_userTicks;
unsigned long long m_waitTicks;
};
class LinuxCpuPluginPrivate : public CpuPluginPrivate {
public:
LinuxCpuPluginPrivate(CpuPlugin *q);
void update() override;
};
#endif
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment