File indexing completed on 2024-05-19 05:30:18
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 QList<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 QList<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 : std::as_const(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 long long user = values[1].toLongLong(); 0143 long long nice = values[2].toLongLong(); 0144 long long system = values[3].toLongLong(); 0145 long long idle = values[4].toLongLong(); 0146 long long iowait = values[5].toLongLong(); 0147 long long irq = values[6].toLongLong(); 0148 long long softirq = values[7].toLongLong(); 0149 long long steal = values[8].toLongLong(); 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<std::pair<unsigned int, unsigned int>, sensors_feature const *> coreFeatures; 0181 int physicalId = 0; 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(std::make_pair(physicalId, coreId), feature); 0191 } else { 0192 std::sscanf(sensorLabel, "Package id %d", &physicalId); 0193 } 0194 free(sensorLabel); 0195 } 0196 0197 for (auto feature = coreFeatures.cbegin(); feature != coreFeatures.cend(); ++feature) { 0198 if (m_cpusBySystemIds.contains(feature.key())) { 0199 // When the cpu has hyperthreading we display multiple cores for each physical core. 0200 // Naturally they share the same temperature sensor and have the same coreId. 0201 auto cpu_range = m_cpusBySystemIds.equal_range(feature.key()); 0202 for (auto cpu_it = cpu_range.first; cpu_it != cpu_range.second; ++cpu_it) { 0203 (*cpu_it)->makeTemperatureSensor(chipName, feature.value()); 0204 } 0205 } 0206 } 0207 } 0208 0209 void LinuxCpuPluginPrivate::addSensorsAmd(const sensors_chip_name * const chipName) 0210 { 0211 // All Processors should have the Tctl pseudo temperature as temp1. Newer ones have the real die 0212 // temperature Tdie as temp2. Some of those have temperatures for each core complex die (CCD) as 0213 // temp3-6 or temp3-10 depending on the number of CCDS. 0214 // https://www.kernel.org/doc/html/latest/hwmon/k10temp.html 0215 int featureNumber = 0; 0216 sensors_feature const * tctl = nullptr; 0217 sensors_feature const * tdie = nullptr; 0218 sensors_feature const * tccd[8] = {nullptr}; 0219 while (sensors_feature const * feature = sensors_get_features(chipName, &featureNumber)) { 0220 const QByteArray name (feature->name); 0221 if (feature->type != SENSORS_FEATURE_TEMP || !name.startsWith("temp")) { 0222 continue; 0223 } 0224 // For temps 1 and 2 we can't just go by the number because in kernels older than 5.7 they 0225 // are the wrong way around, so we have to compare labels. 0226 // https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=b02c6857389da66b09e447103bdb247ccd182456 0227 char * label = sensors_get_label(chipName, feature); 0228 if (qstrcmp(label, "Tctl") == 0 || qstrcmp(label, "temp1") == 0) { 0229 tctl = feature; 0230 } else if (qstrcmp(label, "Tdie") == 0 || qstrcmp(label, "temp2") == 0) { 0231 tdie = feature; 0232 } else if (qstrncmp(label, "Tccd", 4) == 0) { 0233 tccd[name.mid(4).toUInt()] = feature; 0234 } else { 0235 qWarning() << "Unrecognised temmperature sensor: " << label; 0236 } 0237 free(label); 0238 } 0239 // TODO How to map CCD temperatures to cores? 0240 0241 auto setSingleSensor = [this, chipName] (const sensors_feature * const feature) { 0242 for (auto &cpu : std::as_const(m_cpusBySystemIds)) { 0243 cpu->makeTemperatureSensor(chipName, feature); 0244 } 0245 }; 0246 if (tdie) { 0247 setSingleSensor(tdie); 0248 } else if (tctl) { 0249 setSingleSensor(tctl); 0250 } 0251 }