Commit 1bf87804 authored by David Redondo's avatar David Redondo 🏎

Something I am more happy with

parent d391e4fc
add_library(ksysguard_plugin_cpu MODULE cpu.cpp cpuplugin.cpp)
add_library(ksysguard_plugin_cpu MODULE cpu.cpp cpuplugin.cpp usagecomputer.cpp)
if (CMAKE_SYSTEM_NAME STREQUAL "Linux")
target_sources(ksysguard_plugin_cpu PRIVATE linuxcpu.cpp)
target_sources(ksysguard_plugin_cpu PRIVATE linuxcpu.cpp linuxcpuplugin.cpp)
elseif(CMAKE_SYSTEM_NAME STREQUAL "FreeBSD")
target_sources(ksysguard_plugin_cpu PRIVATE freebsdcpu.cpp)
target_sources(ksysguard_plugin_cpu PRIVATE freebsdcpuplugin.cpp)
endif()
target_link_libraries(ksysguard_plugin_cpu Qt5::Core KSysGuard::StatsBackend KF5::CoreAddons KF5::I18n)
......
......@@ -21,14 +21,9 @@
#include <KLocalizedString>
CpuObject::CpuObject(const QString &id, const QString &name, SensorContainer *parent)
BaseCpuObject::BaseCpuObject(const QString &id, const QString &name, SensorContainer *parent)
: SensorObject(id, name, parent)
, m_frequency{nullptr}
, m_temperature{nullptr}
{
auto n = new SensorProperty(QStringLiteral("name"), i18nc("@title", "Name"), name, this);
n->setVariantType(QVariant::String);
m_usage = new SensorProperty(QStringLiteral("usage"), i18nc("@title", "Total Usage"), this);
m_usage->setPrefix(name);
m_usage->setShortName(i18nc("@title, Short for 'Total Usage'", "Usage"));
......@@ -56,19 +51,36 @@ CpuObject::CpuObject(const QString &id, const QString &name, SensorContainer *pa
m_wait->setVariantType(QVariant::Double);
m_wait->setMax(100);
if (id != QStringLiteral("all"))
{
m_frequency = new SensorProperty(QStringLiteral("frequency"), i18nc("@title", "Current Frequency"), this);
m_frequency->setPrefix(name);
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);
auto n = new SensorProperty(QStringLiteral("name"), i18nc("@title", "Name"), name, this);
n->setVariantType(QVariant::String);
}
CpuObject::CpuObject(const QString &id, const QString &name, SensorContainer *parent)
: BaseCpuObject(id, name, parent)
{
m_frequency = new SensorProperty(QStringLiteral("frequency"), i18nc("@title", "Current Frequency"), this);
m_frequency->setPrefix(name);
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);
m_temperature = new SensorProperty(QStringLiteral("temperature"), i18nc("@title", "Current Temperature"), this);
m_temperature->setPrefix(name);
m_temperature->setShortName(i18nc("@title, Short for Current Temperatur", "Temperature"));
m_temperature->setVariantType(QVariant::Double);
m_temperature->setUnit(KSysGuard::Unit::UnitCelsius);
}
AllCpusObject::AllCpusObject(unsigned int cpuCount, unsigned int coreCount, SensorContainer *parent)
: BaseCpuObject(QStringLiteral("all"), i18nc("@title", "All"), parent)
{
auto cpus = new SensorProperty(QStringLiteral("cpuCount"), i18nc("@title", "Number of CPUs"), cpuCount, this);
cpus->setShortName(i18nc("@title, Short fort 'Number of CPUs'", "CPUs"));
cpus->setDescription(i18nc("@info", "Number of physical CPUs installed in the system"));
m_temperature = new SensorProperty(QStringLiteral("temperature"), i18nc("@title", "Current Temperature"), this);
m_temperature->setPrefix(name);
m_temperature->setShortName(i18nc("@title, Short for Current Temperatur", "Temperature"));
m_temperature->setVariantType(QVariant::Double);
m_temperature->setUnit(KSysGuard::Unit::UnitCelsius);
}
auto cores = new SensorProperty(QStringLiteral("coreCount"), i18nc("@title", "Number of Cores"), coreCount, this);
cores->setShortName(i18nc("@title, Short fort 'Number of Cores'", "Cores"));
cores->setDescription(i18nc("@info", "Number of CPU cores across all physical CPUS"));
}
......@@ -22,19 +22,31 @@
#include <SensorObject.h>
class CpuObject : public SensorObject {
public:
CpuObject(const QString &id, const QString &name, SensorContainer *parent);
// const int physicalId; // NOTE The combination of these two ids is not necessarily unique with hyperthreading
// const int coreId;
protected:
class BaseCpuObject : public SensorObject {
protected:
BaseCpuObject(const QString &id, const QString &name, SensorContainer *parent);
SensorProperty *m_usage;
SensorProperty *m_system;
SensorProperty *m_user;
SensorProperty *m_wait;
};
class CpuObject : public BaseCpuObject {
public:
CpuObject(const QString &id, const QString &name, SensorContainer *parent);
protected:
SensorProperty *m_frequency;
SensorProperty *m_temperature;
};
class AllCpusObject : public BaseCpuObject {
public:
AllCpusObject(unsigned int cpuCount, unsigned int coreCount, SensorContainer *parent);
protected:
SensorProperty *m_cpuCount;
SensorProperty *m_coreCount;
};
#endif
......@@ -25,8 +25,8 @@
#include <SensorContainer.h>
#include "freebsdcpu.h"
#include "linuxcpu.h"
#include "freebsdcpuplugin.h"
#include "linuxcpuplugin.h"
CpuPluginPrivate::CpuPluginPrivate(CpuPlugin *q)
: m_container(new SensorContainer(QStringLiteral("cpu"), i18n("CPUs"), q))
......
......@@ -21,6 +21,7 @@
#define CPUPLUGIN_P_H
class CpuPlugin;
class SensorContainer;
class CpuPluginPrivate {
public:
......
#include "freebsdcpu.h"
#include "freebsdcpuplugin.h"
#include <algorithm>
#include <vector>
......@@ -19,59 +19,44 @@ bool readSysctl(const char *name, T *buffer, size_t size = sizeof(T)) {
FreeBsdCpuObject::FreeBsdCpuObject(const QString &id, const QString &name, SensorContainer *parent)
: CpuObject(id, name, parent)
{
if (id != QStringLiteral("all")) {
// For min and max frequency we have to parse the values return by freq_levels because only
// minimum is exposed as a single value
size_t size;
const QByteArray levelsName = QByteArrayLiteral("dev.cpu.") + id.right(1).toLocal8Bit() + QByteArrayLiteral(".freq_levels");
if (sysctlbyname(levelsName, nullptr, &size, nullptr, 0) != -1) {
QByteArray freqLevels(size, Qt::Uninitialized);
if (sysctlbyname(levelsName, freqLevels.data(), &size, nullptr, 0) != -1) {
// The format is a list of pairs "frequency/power", see https://svnweb.freebsd.org/base/head/sys/kern/kern_cpu.c?revision=360464&view=markup#l1019
const QList<QByteArray> levels = freqLevels.split(' ');
int min = INT_MAX;
int max = 0;
for (const auto &level : levels) {
const int frequency = level.left(level.indexOf('/')).toInt();
min = std::min(frequency, min);
max = std::max(frequency, max);
}
// value are already in MHz see cpufreq(4)
m_frequency->setMin(min);
m_frequency->setMax(max);
// For min and max frequency we have to parse the values return by freq_levels because only
// minimum is exposed as a single value
size_t size;
const QByteArray levelsName = QByteArrayLiteral("dev.cpu.") + id.right(1).toLocal8Bit() + QByteArrayLiteral(".freq_levels");
if (sysctlbyname(levelsName, nullptr, &size, nullptr, 0) != -1) {
QByteArray freqLevels(size, Qt::Uninitialized);
if (sysctlbyname(levelsName, freqLevels.data(), &size, nullptr, 0) != -1) {
// The format is a list of pairs "frequency/power", see https://svnweb.freebsd.org/base/head/sys/kern/kern_cpu.c?revision=360464&view=markup#l1019
const QList<QByteArray> levels = freqLevels.split(' ');
int min = INT_MAX;
int max = 0;
for (const auto &level : levels) {
const int frequency = level.left(level.indexOf('/')).toInt();
min = std::min(frequency, min);
max = std::max(frequency, max);
}
// value are already in MHz see cpufreq(4)
m_frequency->setMin(min);
m_frequency->setMax(max);
}
const QByteArray tjmax = QByteArrayLiteral("dev.cpu.") + id.right(1).toLocal8Bit() + QByteArrayLiteral(".coretemp.tjmax");
int maxTemperature;
size_t maxTemperatureSize = sizeof(maxTemperature);
// This is only availabel on Intel (using the coretemp driver)
if (readSysctl(tjmax.constData(), &maxTemperature)) {
m_temperature->setMax(maxTemperature);
}
}
const QByteArray tjmax = QByteArrayLiteral("dev.cpu.") + id.right(1).toLocal8Bit() + QByteArrayLiteral(".coretemp.tjmax");
int maxTemperature;
size_t maxTemperatureSize = sizeof(maxTemperature);
// This is only availabel on Intel (using the coretemp driver)
if (readSysctl(tjmax.constData(), &maxTemperature)) {
m_temperature->setMax(maxTemperature);
}
}
// No wait usage on FreeBSD
void FreeBsdCpuObject::update(long system, long user, long idle)
{
long totalTicks = system + user + idle;
long totalDiff = totalTicks - m_totalTicks;
auto percentage = [totalDiff] (long tickDiff) {
// according to the documentation some counters can go backwards in some circumstances
return tickDiff > 0 ? 100.0 * tickDiff / totalDiff : 0;
};
// No wait usage on FreeBSD
m_usageComputer.setTicks(system, user, 0, idle);
m_system->setValue(percentage(system - m_systemTicks));
m_user->setValue(percentage(user - m_userTicks));
m_usage->setValue(percentage((system + user) - (m_systemTicks + m_userTicks)));
m_systemTicks = system;
m_userTicks = user;
m_totalTicks = totalTicks;
if (id() == QStringLiteral("all")) {
return;
}
m_system->setValue(m_usageComputer.systemUsage);
m_user->setValue(m_usageComputer.userUsage);
m_usage->setValue(m_usageComputer.totalUsage);
int frequency;
const QByteArray prefix = QByteArrayLiteral("dev.cpu.") + id().right(1).toLocal8Bit();
......@@ -87,6 +72,16 @@ void FreeBsdCpuObject::update(long system, long user, long idle)
}
}
void FreeBsdAllCpusObject::update(long system, long user, long idle)
{
// No wait usage on FreeBSD
m_usageComputer.setTicks(system, user, 0, idle);
m_system->setValue(m_usageComputer.systemUsage);
m_user->setValue(m_usageComputer.userUsage);
m_usage->setValue(m_usageComputer.totalUsage);
}
FreeBsdCpuPluginPrivate::FreeBsdCpuPluginPrivate(CpuPlugin* q)
: CpuPluginPrivate(q)
......@@ -97,21 +92,12 @@ FreeBsdCpuPluginPrivate::FreeBsdCpuPluginPrivate(CpuPlugin* q)
for (int i = 0; i < numCpu; ++i) {
new FreeBsdCpuObject(QStringLiteral("cpu%1").arg(i), i18nc("@title", "CPU %1", i + 1), m_container);
}
// Add total usage sensors
auto total = new FreeBsdCpuObject(QStringLiteral("all"), i18nc("@title", "All"), m_container);
auto cpuCount = new SensorProperty(QStringLiteral("cpuCount"), i18nc("@title", "Number of CPUs"), numCpu, 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"), numCpu, total);
coreCount->setShortName(i18nc("@title, Short fort 'Number of Cores'", "Cores"));
coreCount->setDescription(i18nc("@info", "Number of CPU cores across all physical CPUS"));
new FreeBsdAllCpusObject(numCpu, numCpu, m_container);
}
void FreeBsdCpuPluginPrivate::update()
{
auto updateCpu = [] (FreeBsdCpuObject *cpu, long *cp_time){
auto updateCpu = [] (auto *cpu, long *cp_time){
cpu->update(cp_time[CP_SYS + CP_INTR], cp_time[CP_USER] + cp_time[CP_NICE], cp_time[CP_IDLE]);
};
unsigned int numCores = m_container->objects().count() - 1;
......
......@@ -3,15 +3,22 @@
#include "cpu.h"
#include "cpuplugin_p.h"
#include "usagecomputer.h"
class FreeBsdCpuObject : public CpuObject {
public:
FreeBsdCpuObject(const QString &id, const QString &name, SensorContainer *parent);
void update(long system, long user, long idle);
private:
long m_totalTicks;
long m_systemTicks;
long m_userTicks;
UsageComputer m_usageComputer;
};
class FreeBsdAllCpusObject : public AllCpusObject {
public:
using AllCpusObject::AllCpusObject;
void update(long system, long user, long idle);
private:
UsageComputer m_usageComputer;
};
class FreeBsdCpuPluginPrivate : public CpuPluginPrivate {
......
......@@ -2,14 +2,10 @@
#include <QFile>
#include <KLocalizedString>
#ifdef HAVE_SENSORS
#include <sensors/sensors.h>
#endif
#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);
......@@ -26,17 +22,15 @@ LinuxCpuObject::LinuxCpuObject(const QString &id, const QString &name, SensorCon
, m_sensorChipName{nullptr}
, m_temperatureSubfeature{-1}
{
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);
}
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);
}
}
......@@ -63,28 +57,15 @@ void LinuxCpuObject::setTemperatureSensor(const sensors_chip_name * const chipNa
void LinuxCpuObject::update(unsigned long long system, unsigned long long user, unsigned long long wait, unsigned long long idle)
{
// First calculate usages
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)));
// First update usages
m_usageComputer.setTicks(system, user, wait, idle);
m_totalTicks = totalTicks;
m_systemTicks = system;
m_userTicks = user;
m_waitTicks = wait;
m_system->setValue(m_usageComputer.systemUsage);
m_user->setValue(m_usageComputer.userUsage);
m_wait->setValue(m_usageComputer.waitUsage);
m_usage->setValue(m_usageComputer.totalUsage);
// Second update frequencies
if (!m_frequency) {
return;
}
// Second try to get current frequency
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);
......@@ -108,179 +89,13 @@ void LinuxCpuObject::update(unsigned long long system, unsigned long long user,
#endif
}
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()) {
unsigned int processor, physicalId, coreId;
double frequency = 0;
// Processors are divided by empty lines
for (; 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. For mapping temperature both ids are still needed nonetheless.
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 == "core id") {
coreId = value.toInt();
} else if (field == "cpu MHz") {
frequency = value.toDouble();
}
}
const QString name = i18nc("@title", "CPU %1 Core %2", physicalId + 1, ++numCores[physicalId]);
auto cpu = new LinuxCpuObject(QStringLiteral("cpu%1").arg(processor), name, m_container, frequency);
m_cpusBySystemIds.insert({physicalId, coreId}, cpu);
}
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"));
addSensors();
}
void LinuxAllCpusObject::update(unsigned long long system, unsigned long long user, unsigned long long wait, unsigned long long idle) {
m_usageComputer.setTicks(system, user, wait, idle);
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;
}
auto values = line.split(' ');
unsigned long long user = values[1].toULongLong();
unsigned long long nice = values[2].toULongLong();
unsigned long long system = values[3].toULongLong();
unsigned long long idle = values[4].toULongLong();
unsigned long long iowait = values[5].toULongLong();
unsigned long long irq = values[6].toULongLong();
unsigned long long softirq = values[7].toULongLong();
unsigned long long steal = values[8].toULongLong();
cpu->update(system + irq + softirq, user + nice , iowait + steal, idle);
}
m_system->setValue(m_usageComputer.systemUsage);
m_user->setValue(m_usageComputer.userUsage);
m_wait->setValue(m_usageComputer.waitUsage);
m_usage->setValue(m_usageComputer.totalUsage);
}
void LinuxCpuPluginPrivate::addSensors()
{
#ifdef HAVE_SENSORS
sensors_init(nullptr);
int number = 0;
while (const sensors_chip_name * const chipName = sensors_get_detected_chips(nullptr, &number)) {
char name[100];
sensors_snprintf_chip_name(name, 100, chipName);
if (qstrcmp(chipName->prefix, "coretemp") == 0) {
addSensorsIntel(chipName);
} else if (qstrcmp(chipName->prefix, "k10temp") == 0) {
addSensorsAmd(chipName);
}
}
#endif
}
// Documentation: https://www.kernel.org/doc/html/latest/hwmon/coretemp.html
void LinuxCpuPluginPrivate::addSensorsIntel(const sensors_chip_name * const chipName)
{
#ifdef HAVE_SENSORS
int featureNumber = 0;
QHash<unsigned int, sensors_feature const *> coreFeatures;
int physicalId = -1;
while (sensors_feature const * feature = sensors_get_features(chipName, &featureNumber)) {
if (feature->type != SENSORS_FEATURE_TEMP) {
continue;
}
char * sensorLabel = sensors_get_label(chipName, feature);
unsigned int coreId;
// First try to see if it's a core temperature because we should have more of those
if (std::sscanf(sensorLabel, "Core %d", &coreId) != 0) {
coreFeatures.insert(coreId, feature);
} else {
std::sscanf(sensorLabel, "Package id %d", &physicalId);
}
free(sensorLabel);
}
if (physicalId == -1) {
return;
}
for (auto feature = coreFeatures.cbegin(); feature != coreFeatures.cend(); ++feature) {
if (m_cpusBySystemIds.contains({physicalId, feature.key()})) {
// When the cpu has hyperthreading we display multiple cores for each physical core.
// Naturally they share the same temperature sensor and have the same coreId.
auto cpu_range = m_cpusBySystemIds.equal_range({physicalId, feature.key()});
for (auto cpu_it = cpu_range.first; cpu_it != cpu_range.second; ++cpu_it) {
(*cpu_it)->setTemperatureSensor(chipName, feature.value());
}
}
}
#endif
}
void LinuxCpuPluginPrivate::addSensorsAmd(const sensors_chip_name * const chipName)
{
// All Processors should have the Tctl pseudo temperature as temp1. Newer ones have the real die
// temperature Tdie as temp2. Some of those have temperatures for each core complex die (CCD) as
// temp3-6 or temp3-10 depending on the number of CCDS.
// https://www.kernel.org/doc/html/latest/hwmon/k10temp.html
int featureNumber = 0;
sensors_feature const * tctl = nullptr;
sensors_feature const * tdie = nullptr;
sensors_feature const * tccd[8] = {nullptr};
while (sensors_feature const * feature = sensors_get_features(chipName, &featureNumber)) {
const QByteArray name (feature->name);
if (feature->type != SENSORS_FEATURE_TEMP || !name.startsWith("temp")) {
continue;
}
// For temps 1 and 2 we can't just go by the number because in kernels older than 5.7 they
// are the wrong way around, so we have to compare labels.
// https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=b02c6857389da66b09e447103bdb247ccd182456
char * label = sensors_get_label(chipName, feature);
if (qstrcmp(label, "Tctl") == 0) {
tctl = feature;
}
else if (qstrcmp(label, "Tdie") == 0) {
tdie = feature;
} else {
tccd[name.mid(4).toUInt()] = feature;
}
free(label);
}
// TODO How to map CCD temperatures to cores?
auto setSingleSensor = [this, chipName] (const sensors_feature * const feature) {
for (auto &cpu : m_cpusBySystemIds) {
cpu->setTemperatureSensor(chipName, feature);
}
};
if (tdie) {
setSingleSensor(tdie);
} else if (tctl) {
setSingleSensor(tctl);
}
}
/*
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 <QHash>
#include "cpu.h"
#include "cpuplugin_p.h"