File: freebsdcpuplugin.cpp

package info (click to toggle)
ksystemstats 6.5.4-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 2,528 kB
  • sloc: cpp: 4,881; makefile: 6; sh: 1
file content (183 lines) | stat: -rw-r--r-- 6,332 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
/*
    SPDX-FileCopyrightText: 2020 David Redondo <kde@david-redondo.de>

    SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
*/

#include "freebsdcpuplugin.h"

#include "loadaverages.h"

#include <algorithm>
#include <vector>

#include <sys/types.h>
#include <sys/resource.h>
#include <sys/sysctl.h>

#include <KLocalizedString>

#include <systemstats/SensorContainer.h>
#include <systemstats/SysctlSensor.h>

namespace {

/** Reads a sysctl into a typed buffer, return success-value
 *
 * Returns @c false if the sysctl fails, otherwise @c true (even if
 * the returned size is a mismatch or whatever).
 */
template <typename T>
bool readSysctl(const char *name, T *buffer, size_t size = sizeof(T)) {
    return sysctlbyname(name, buffer, &size, nullptr, 0) != -1;
}

double deciKelvinToCelsius(const int deci_kelvin) {
    return static_cast<double>(deci_kelvin) / 10.0 - 273.15;
}

/** Calls update() with sysctl cp_time data
 *
 * For a CPU object, or an AllCpus object, calls update() with the relevant data.
 */
template<typename cpu_t>
inline void updateCpu(cpu_t *cpu, long *cp_time)
{
    cpu->update(cp_time[CP_SYS] + cp_time[CP_INTR], cp_time[CP_USER] + cp_time[CP_NICE], cp_time[CP_IDLE]);
}

/** Reads cp_times from sysctl and applies to the vector of CPU objects
 *
 * Assumes that the CPU objects are ordered in the vector in the same order
 * that their data show up in the sysctl return value.
 */
inline void read_cp_times(QList<FreeBsdCpuObject *> &cpus)
{
    unsigned int numCores = cpus.count();
    std::vector<long> cp_times(numCores * CPUSTATES);
    size_t cpTimesSize = sizeof(long) *  cp_times.size();
    if (readSysctl("kern.cp_times", cp_times.data(), cpTimesSize)) {//, &cpTimesSize, nullptr, 0) != -1) {
        for (unsigned int  i = 0; i < numCores; ++i) {
            auto cpu = cpus[i];
            updateCpu(cpu, &cp_times[CPUSTATES * i]);
        }
    }
}

}

FreeBsdCpuObject::FreeBsdCpuObject(int cpuNumber, const QString &name, KSysGuard::SensorContainer *parent)
    : CpuObject(QStringLiteral("cpu%1").arg(cpuNumber), name, parent),
    m_sysctlPrefix(QByteArrayLiteral("dev.cpu.") + QByteArray::number(cpuNumber))
{
}

void FreeBsdCpuObject::makeSensors()
{
    BaseCpuObject::makeSensors();

    m_frequency = new KSysGuard::SysctlSensor<int>(QStringLiteral("frequency"), m_sysctlPrefix + QByteArrayLiteral(".freq"), 0, this);

    KSysGuard::SysctlSensor<int> *temp_sensor = new KSysGuard::SysctlSensor<int>(QStringLiteral("temperature"), m_sysctlPrefix + QByteArrayLiteral(".temperature"), 0, this);
    // The temperature value is exported in deci-kelvin in the sysctl. See FreeBSD sbin/sysctl/sysctl.c for details.
    temp_sensor->setConversionFunction([](int raw_value) { return deciKelvinToCelsius(raw_value); });
    m_temperature = temp_sensor;
}

void FreeBsdCpuObject::initialize()
{
    CpuObject::initialize();

    // For min and max frequency we have to parse the values return by freq_levels because only
    // minimum is exposed as a single value
    size_t size;
    const QByteArray levelsName = m_sysctlPrefix + QByteArrayLiteral(".freq_levels");
    // calling sysctl with nullptr writes the needed size to size
    if (sysctlbyname(levelsName, nullptr, &size, nullptr, 0) != -1) {
        QByteArray freqLevels(size, Qt::Uninitialized);
        if (sysctlbyname(levelsName, freqLevels.data(), &size, nullptr, 0) != -1) {
            // 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
            const QList<QByteArray> levels = freqLevels.split(' ');
            int min = INT_MAX;
            int max = 0;
            for (const auto &level : levels) {
                const int frequency = level.left(level.indexOf('/')).toInt();
                min = std::min(frequency, min);
                max = std::max(frequency, max);
            }
            // value are already in MHz  see cpufreq(4)
            m_frequency->setMin(min);
            m_frequency->setMax(max);
        }
    }
    const QByteArray tjmax = m_sysctlPrefix + QByteArrayLiteral(".coretemp.tjmax");
    int maxTemperature;
    // This is only availabel on Intel (using the coretemp driver)
    if (readSysctl(tjmax.constData(), &maxTemperature)) {
        m_temperature->setMax(deciKelvinToCelsius(maxTemperature));
    }
}

void FreeBsdCpuObject::update(long system, long user, long idle)
{
    // No wait usage on FreeBSD
    m_usageComputer.setTicks(system, user, 0, idle);

    m_system->setValue(m_usageComputer.systemUsage);
    m_user->setValue(m_usageComputer.userUsage);
    m_usage->setValue(m_usageComputer.totalUsage);

    // The calculations above are "free" because we already have the data;
    // is we are not subscribed, don't bother updating the data that needs
    // extra work to be done.
    if (!isSubscribed()) {
        return;
    }

    m_temperature->update();
    m_frequency->update();
}

void FreeBsdAllCpusObject::update(long system, long user, long idle)
{
    // No wait usage on FreeBSD
    m_usageComputer.setTicks(system, user, 0, idle);

    m_system->setValue(m_usageComputer.systemUsage);
    m_user->setValue(m_usageComputer.userUsage);
    m_usage->setValue(m_usageComputer.totalUsage);
}

FreeBsdCpuPluginPrivate::FreeBsdCpuPluginPrivate(CpuPlugin* q)
    : CpuPluginPrivate(q)
{
    m_loadAverages = new LoadAverages(m_container);
    // The values used here can be found in smp(4)
    int numCpu;
    readSysctl("hw.ncpu", &numCpu);
    for (int i = 0; i < numCpu; ++i) {
        auto cpu = new FreeBsdCpuObject(i, i18nc("@title", "CPU %1", i + 1), m_container);
        cpu->initialize();
        m_cpus.push_back(cpu);
    }
    m_allCpus = new FreeBsdAllCpusObject(m_container);
    m_allCpus->initialize();

    read_cp_times(m_cpus);
}

void FreeBsdCpuPluginPrivate::update()
{
    m_loadAverages->update();

    auto isSubscribed = [] (const KSysGuard::SensorObject *o) {return o->isSubscribed();};
    if (std::none_of(m_cpus.cbegin(), m_cpus.cend(), isSubscribed) && !m_allCpus->isSubscribed()) {
        return;
    }
    read_cp_times(m_cpus);
    // update total values
    long cp_time[CPUSTATES];
    if (readSysctl("kern.cp_time", &cp_time)) {
        updateCpu(m_allCpus, cp_time);
    }
}