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 }