File indexing completed on 2024-05-12 17:00:14

0001 /*
0002     SPDX-FileCopyrightText: 2020 David Redondo <kde@david-redondo.de>
0003 
0004     SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
0005 */
0006 
0007 #include "linuxcpuplugin.h"
0008 
0009 #include <QFile>
0010 
0011 #include <KLocalizedString>
0012 
0013 #include <systemstats/SensorContainer.h>
0014 
0015 #include <sensors/sensors.h>
0016 
0017 #include "linuxcpu.h"
0018 #include "loadaverages.h"
0019 
0020 struct CpuInfo
0021 {
0022     int id = -1;
0023     int cpu = -1;
0024     int core = -1;
0025     int siblings = -1;
0026     qreal frequency = 0.0;
0027 };
0028 
0029 // Determine sensor names for all the found processors. Because processors can
0030 // be offline, we need to account for processor IDs skipping and report the
0031 // proper names.
0032 static QHash<int, QString> makeCpuNames(const QVector<CpuInfo> &cpus, int cpuCount)
0033 {
0034     QHash<int, QString> result;
0035 
0036     if (cpuCount == 1) {
0037         // Simple case: Only one CPU, just report CPU number + 1 as core number.
0038         for (const auto &info : cpus) {
0039             result.insert(info.id, i18nc("@title", "Core %1", info.id + 1));
0040         }
0041         return result;
0042     }
0043 
0044     int currentCpu = 0;
0045     int previousCpuSiblings = 0;
0046 
0047     for (const auto &info : cpus) {
0048         if (info.cpu != currentCpu) {
0049             previousCpuSiblings = previousCpuSiblings + info.siblings;
0050             currentCpu = info.cpu;
0051         }
0052 
0053         int coreNumber = info.id - previousCpuSiblings;
0054         result.insert(info.id, i18nc("@title", "CPU %1 Core %2", currentCpu + 1, coreNumber));
0055     }
0056 
0057     return result;
0058 }
0059 
0060 LinuxCpuPluginPrivate::LinuxCpuPluginPrivate(CpuPlugin *q)
0061     : CpuPluginPrivate(q)
0062 {
0063     m_loadAverages = new LoadAverages(m_container);
0064 
0065     // Parse /proc/cpuinfo for information about cpus
0066     QFile cpuinfo(QStringLiteral("/proc/cpuinfo"));
0067     cpuinfo.open(QIODevice::ReadOnly);
0068 
0069     int cpuCount = 0;
0070     QVector<CpuInfo> cpus;
0071 
0072     for (QByteArray line = cpuinfo.readLine(); !line.isEmpty(); line = cpuinfo.readLine()) {
0073         CpuInfo info;
0074         // Processors are divided by empty lines
0075         for (; line != "\n";  line = cpuinfo.readLine()) {
0076             // we are interested in processor number as identifier for /proc/stat, physical id (the
0077             // cpu the core belongs to) and the number of the core. However with hyperthreading
0078             // multiple entries will have the same combination of physical id and core id. So we just
0079             // count up the core number. For mapping temperature both ids are still needed nonetheless.
0080             const int delim = line.indexOf(":");
0081             const QByteArray field = line.left(delim).trimmed();
0082             const QByteArray value = line.mid(delim + 1).trimmed();
0083             if (field == "processor") {
0084                 info.id = value.toInt();
0085             } else if (field == "physical id") {
0086                 info.cpu = value.toInt();
0087             } else if (field == "core id") {
0088                 info.core = value.toInt();
0089             } else if (field == "cpu MHz") {
0090                 info.frequency = value.toDouble();
0091             } else if (field == "siblings") {
0092                 info.siblings = value.toInt();
0093             }
0094         }
0095 
0096         cpus.push_back(info);
0097         cpuCount = std::max(cpuCount, info.cpu);
0098     }
0099 
0100     // cpuCount is based on the indices of the cpus, which means it is off by
0101     // one compared to the actual number of CPUs. Correct that here.
0102     cpuCount += 1;
0103 
0104     auto names = makeCpuNames(cpus, cpuCount);
0105 
0106     QHash<int, int> numCores;
0107     for (const auto &entry : qAsConst(cpus)) {
0108         auto cpu = new LinuxCpuObject(QStringLiteral("cpu%1").arg(entry.id), names.value(entry.id), entry.frequency, m_container);
0109         m_cpus.insert(entry.id, cpu);
0110         m_cpusBySystemIds.insert({entry.cpu, entry.core}, cpu);
0111     }
0112 
0113     addSensors();
0114     for (const auto cpu : std::as_const(m_cpus)) {
0115         cpu->initialize();
0116     }
0117     m_allCpus = new LinuxAllCpusObject(m_container);
0118     m_allCpus->initialize();
0119     m_allCpus->setCounts(cpuCount, m_cpus.size());
0120 
0121 }
0122 
0123 void LinuxCpuPluginPrivate::update()
0124 {
0125     m_loadAverages->update();
0126 
0127     auto isSubscribed = [] (const KSysGuard::SensorObject *o) {return o->isSubscribed();};
0128     if (std::none_of(m_cpus.cbegin(), m_cpus.cend(), isSubscribed) && !m_allCpus->isSubscribed()) {
0129         return;
0130     }
0131 
0132     // Parse /proc/stat to get usage values. The format is described at
0133     // https://www.kernel.org/doc/html/latest/filesystems/proc.html#miscellaneous-kernel-statistics-in-proc-stat
0134     QFile stat(QStringLiteral("/proc/stat"));
0135     stat.open(QIODevice::ReadOnly);
0136     for (QByteArray line = stat.readLine(); !line.isNull(); line = stat.readLine()) {
0137         auto values = line.simplified().split(' ');
0138         if (!line.startsWith("cpu")) {
0139             continue;
0140         }
0141 
0142         unsigned long long user = values[1].toULongLong();
0143         unsigned long long nice = values[2].toULongLong();
0144         unsigned long long system = values[3].toULongLong();
0145         unsigned long long idle = values[4].toULongLong();
0146         unsigned long long iowait = values[5].toULongLong();
0147         unsigned long long irq = values[6].toULongLong();
0148         unsigned long long softirq = values[7].toULongLong();
0149         unsigned long long steal = values[8].toULongLong();
0150 
0151         // Total values just start with "cpu", single cpus are numbered cpu0, cpu1, ...
0152         if (line.startsWith("cpu ")) {
0153             m_allCpus->update(system + irq + softirq, user + nice , iowait + steal, idle);
0154         } else if (line.startsWith("cpu")) {
0155             auto cpu = m_cpus.value(std::atoi(line.mid(strlen("cpu"))));
0156             cpu->update(system + irq + softirq, user + nice , iowait + steal, idle);
0157         }
0158     }
0159 }
0160 
0161 
0162 void LinuxCpuPluginPrivate::addSensors()
0163 {
0164     int number = 0;
0165     while (const sensors_chip_name * const chipName = sensors_get_detected_chips(nullptr, &number)) {
0166         char name[100];
0167         sensors_snprintf_chip_name(name, 100, chipName);
0168         if (qstrcmp(chipName->prefix, "coretemp") == 0) {
0169             addSensorsIntel(chipName);
0170         } else if (qstrcmp(chipName->prefix, "k10temp") == 0) {
0171             addSensorsAmd(chipName);
0172         }
0173     }
0174 }
0175 
0176 // Documentation: https://www.kernel.org/doc/html/latest/hwmon/coretemp.html
0177 void LinuxCpuPluginPrivate::addSensorsIntel(const sensors_chip_name * const chipName)
0178 {
0179     int featureNumber = 0;
0180     QHash<unsigned int,  sensors_feature const *> coreFeatures;
0181     int physicalId = -1;
0182     while (sensors_feature const * feature = sensors_get_features(chipName, &featureNumber)) {
0183         if (feature->type != SENSORS_FEATURE_TEMP) {
0184             continue;
0185         }
0186         char * sensorLabel = sensors_get_label(chipName, feature);
0187         unsigned int coreId;
0188         // First try to see if it's a core temperature because we should have more of those
0189         if (std::sscanf(sensorLabel, "Core %d", &coreId) != 0) {
0190             coreFeatures.insert(coreId, feature);
0191         } else {
0192             std::sscanf(sensorLabel, "Package id %d", &physicalId);
0193         }
0194         free(sensorLabel);
0195     }
0196     if (physicalId == -1) {
0197         return;
0198     }
0199     for (auto feature = coreFeatures.cbegin(); feature != coreFeatures.cend(); ++feature) {
0200         if (m_cpusBySystemIds.contains({physicalId, int(feature.key())})) {
0201             // When the cpu has hyperthreading we display multiple cores for each physical core.
0202             // Naturally they share the same temperature sensor and have the same coreId.
0203             auto cpu_range = m_cpusBySystemIds.equal_range({physicalId, int(feature.key())});
0204             for (auto cpu_it = cpu_range.first; cpu_it != cpu_range.second; ++cpu_it) {
0205                 (*cpu_it)->makeTemperatureSensor(chipName, feature.value());
0206             }
0207         }
0208     }
0209 }
0210 
0211 void LinuxCpuPluginPrivate::addSensorsAmd(const sensors_chip_name * const chipName)
0212 {
0213     // All Processors should have the Tctl pseudo temperature as temp1. Newer ones have the real die
0214     // temperature Tdie as temp2. Some of those have temperatures for each core complex die (CCD) as
0215     // temp3-6 or temp3-10 depending on the number of CCDS.
0216     // https://www.kernel.org/doc/html/latest/hwmon/k10temp.html
0217     int featureNumber = 0;
0218     sensors_feature const * tctl = nullptr;
0219     sensors_feature const * tdie = nullptr;
0220     sensors_feature const * tccd[8] = {nullptr};
0221     while (sensors_feature const * feature = sensors_get_features(chipName, &featureNumber)) {
0222         const QByteArray name (feature->name);
0223         if (feature->type != SENSORS_FEATURE_TEMP || !name.startsWith("temp")) {
0224             continue;
0225         }
0226         // For temps 1 and 2 we can't just go by the number because in  kernels older than 5.7 they
0227         // are the wrong way around, so we have to compare labels.
0228         // https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=b02c6857389da66b09e447103bdb247ccd182456
0229         char * label = sensors_get_label(chipName, feature);
0230         if (qstrcmp(label, "Tctl") == 0) {
0231             tctl = feature;
0232         }
0233         else if (qstrcmp(label, "Tdie") == 0) {
0234             tdie = feature;
0235         } else {
0236             tccd[name.mid(4).toUInt()] = feature;
0237         }
0238         free(label);
0239     }
0240     // TODO How to map CCD temperatures to cores?
0241 
0242     auto setSingleSensor = [this, chipName] (const sensors_feature * const feature) {
0243         for (auto &cpu : qAsConst(m_cpusBySystemIds)) {
0244             cpu->makeTemperatureSensor(chipName, feature);
0245         }
0246     };
0247     if (tdie) {
0248         setSingleSensor(tdie);
0249     } else if (tctl) {
0250         setSingleSensor(tctl);
0251     }
0252 }