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