1 /*
2 SPDX-FileCopyrightText: 2020 David Redondo <kde@david-redondo.de>
3
4 SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
5 */
6
7 #include "freebsdcpuplugin.h"
8
9 #include "loadaverages.h"
10
11 #include <algorithm>
12 #include <vector>
13
14 #include <sys/types.h>
15 #include <sys/resource.h>
16 #include <sys/sysctl.h>
17
18 #include <KLocalizedString>
19
20 #include <systemstats/SensorContainer.h>
21 #include <systemstats/SysctlSensor.h>
22
23 namespace {
24
25 /** Reads a sysctl into a typed buffer, return success-value
26 *
27 * Returns @c false if the sysctl fails, otherwise @c true (even if
28 * the returned size is a mismatch or whatever).
29 */
30 template <typename T>
readSysctl(const char * name,T * buffer,size_t size=sizeof (T))31 bool readSysctl(const char *name, T *buffer, size_t size = sizeof(T)) {
32 return sysctlbyname(name, buffer, &size, nullptr, 0) != -1;
33 }
34
35 /** Calls update() with sysctl cp_time data
36 *
37 * For a CPU object, or an AllCpus object, calls update() with the relevant data.
38 */
39 template<typename cpu_t>
updateCpu(cpu_t * cpu,long * cp_time)40 inline void updateCpu(cpu_t *cpu, long *cp_time)
41 {
42 cpu->update(cp_time[CP_SYS] + cp_time[CP_INTR], cp_time[CP_USER] + cp_time[CP_NICE], cp_time[CP_IDLE]);
43 }
44
45 /** Reads cp_times from sysctl and applies to the vector of CPU objects
46 *
47 * Assumes that the CPU objects are ordered in the vector in the same order
48 * that their data show up in the sysctl return value.
49 */
read_cp_times(QVector<FreeBsdCpuObject * > & cpus)50 inline void read_cp_times(QVector<FreeBsdCpuObject*> &cpus)
51 {
52 unsigned int numCores = cpus.count();
53 std::vector<long> cp_times(numCores * CPUSTATES);
54 size_t cpTimesSize = sizeof(long) * cp_times.size();
55 if (readSysctl("kern.cp_times", cp_times.data(), cpTimesSize)) {//, &cpTimesSize, nullptr, 0) != -1) {
56 for (unsigned int i = 0; i < numCores; ++i) {
57 auto cpu = cpus[i];
58 updateCpu(cpu, &cp_times[CPUSTATES * i]);
59 }
60 }
61 }
62
63 }
64
FreeBsdCpuObject(int cpuNumber,const QString & name,KSysGuard::SensorContainer * parent)65 FreeBsdCpuObject::FreeBsdCpuObject(int cpuNumber, const QString &name, KSysGuard::SensorContainer *parent)
66 : CpuObject(QStringLiteral("cpu%1").arg(cpuNumber), name, parent),
67 m_sysctlPrefix(QByteArrayLiteral("dev.cpu.") + QByteArray::number(cpuNumber))
68 {
69 }
70
makeSensors()71 void FreeBsdCpuObject::makeSensors()
72 {
73 BaseCpuObject::makeSensors();
74
75 m_frequency = new KSysGuard::SysctlSensor<int>(QStringLiteral("frequency"), m_sysctlPrefix + QByteArrayLiteral(".freq"), this);
76 m_temperature = new KSysGuard::SysctlSensor<int>(QStringLiteral("temperature"), m_sysctlPrefix + QByteArrayLiteral(".temperature"), this);
77 }
78
initialize()79 void FreeBsdCpuObject::initialize()
80 {
81 CpuObject::initialize();
82
83 // For min and max frequency we have to parse the values return by freq_levels because only
84 // minimum is exposed as a single value
85 size_t size;
86 const QByteArray levelsName = m_sysctlPrefix + QByteArrayLiteral(".freq_levels");
87 // calling sysctl with nullptr writes the needed size to size
88 if (sysctlbyname(levelsName, nullptr, &size, nullptr, 0) != -1) {
89 QByteArray freqLevels(size, Qt::Uninitialized);
90 if (sysctlbyname(levelsName, freqLevels.data(), &size, nullptr, 0) != -1) {
91 // 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
92 const QList<QByteArray> levels = freqLevels.split(' ');
93 int min = INT_MAX;
94 int max = 0;
95 for (const auto &level : levels) {
96 const int frequency = level.left(level.indexOf('/')).toInt();
97 min = std::min(frequency, min);
98 max = std::max(frequency, max);
99 }
100 // value are already in MHz see cpufreq(4)
101 m_frequency->setMin(min);
102 m_frequency->setMax(max);
103 }
104 }
105 const QByteArray tjmax = m_sysctlPrefix + QByteArrayLiteral(".coretemp.tjmax");
106 int maxTemperature;
107 // This is only availabel on Intel (using the coretemp driver)
108 if (readSysctl(tjmax.constData(), &maxTemperature)) {
109 m_temperature->setMax(maxTemperature);
110 }
111 }
112
update(long system,long user,long idle)113 void FreeBsdCpuObject::update(long system, long user, long idle)
114 {
115 // No wait usage on FreeBSD
116 m_usageComputer.setTicks(system, user, 0, idle);
117
118 m_system->setValue(m_usageComputer.systemUsage);
119 m_user->setValue(m_usageComputer.userUsage);
120 m_usage->setValue(m_usageComputer.totalUsage);
121
122 // The calculations above are "free" because we already have the data;
123 // is we are not subscribed, don't bother updating the data that needs
124 // extra work to be done.
125 if (!isSubscribed()) {
126 return;
127 }
128
129 m_temperature->update();
130 m_frequency->update();
131 }
132
update(long system,long user,long idle)133 void FreeBsdAllCpusObject::update(long system, long user, long idle)
134 {
135 // No wait usage on FreeBSD
136 m_usageComputer.setTicks(system, user, 0, idle);
137
138 m_system->setValue(m_usageComputer.systemUsage);
139 m_user->setValue(m_usageComputer.userUsage);
140 m_usage->setValue(m_usageComputer.totalUsage);
141 }
142
FreeBsdCpuPluginPrivate(CpuPlugin * q)143 FreeBsdCpuPluginPrivate::FreeBsdCpuPluginPrivate(CpuPlugin* q)
144 : CpuPluginPrivate(q)
145 {
146 m_loadAverages = new LoadAverages(m_container);
147 // The values used here can be found in smp(4)
148 int numCpu;
149 readSysctl("hw.ncpu", &numCpu);
150 for (int i = 0; i < numCpu; ++i) {
151 auto cpu = new FreeBsdCpuObject(i, i18nc("@title", "CPU %1", i + 1), m_container);
152 cpu->initialize();
153 m_cpus.push_back(cpu);
154 }
155 m_allCpus = new FreeBsdAllCpusObject(m_container);
156 m_allCpus->initialize();
157
158 read_cp_times(m_cpus);
159 }
160
update()161 void FreeBsdCpuPluginPrivate::update()
162 {
163 m_loadAverages->update();
164
165 auto isSubscribed = [] (const KSysGuard::SensorObject *o) {return o->isSubscribed();};
166 if (std::none_of(m_cpus.cbegin(), m_cpus.cend(), isSubscribed) && !m_allCpus->isSubscribed()) {
167 return;
168 }
169 read_cp_times(m_cpus);
170 // update total values
171 long cp_time[CPUSTATES];
172 if (readSysctl("kern.cp_time", &cp_time)) {
173 updateCpu(m_allCpus, cp_time);
174 }
175 }
176