Commit 237c8843 authored by Arjen Hiemstra's avatar Arjen Hiemstra

Add initial GPU plugin

Only AMD GPUs on Linux supported so far, but those report a nice set of
details.
parent 3a30493b
......@@ -92,6 +92,13 @@ if (libpcap_FOUND)
)
endif()
find_package(UDev)
set_package_properties(
UDev PROPERTIES
TYPE RECOMMENDED
PURPOSE "UDev is used for finding graphics cards."
)
include_directories(${CMAKE_CURRENT_BINARY_DIR})
configure_file(config-workspace.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-workspace.h)
......
......@@ -6,3 +6,7 @@ add_subdirectory(network)
add_subdirectory(power)
add_subdirectory(disks)
add_subdirectory(cpu)
if(UDev_FOUND)
add_subdirectory(gpu)
endif()
set(KSYSGUARD_GPU_PLUGIN_SOURCES
GpuPlugin.cpp
GpuBackend.cpp
GpuDevice.cpp
LinuxAmdGpu.cpp
LinuxBackend.cpp
SysFsSensor.cpp
)
add_library(ksysguard_plugin_gpu MODULE ${KSYSGUARD_GPU_PLUGIN_SOURCES})
target_link_libraries(ksysguard_plugin_gpu Qt5::Core Qt5::DBus KSysGuard::StatsBackend KF5::CoreAddons KF5::I18n UDev::UDev)
install(TARGETS ksysguard_plugin_gpu DESTINATION ${KDE_INSTALL_PLUGINDIR}/ksysguard)
/*
* SPDX-FileCopyrightText: 2020 Arjen Hiemstra <ahiemstra@heimr.nl>
*
* SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
*/
#include "GpuBackend.h"
/*
* SPDX-FileCopyrightText: 2020 Arjen Hiemstra <ahiemstra@heimr.nl>
*
* SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
*/
#pragma once
#include <QObject>
class GpuDevice;
class GpuBackend : public QObject
{
Q_OBJECT
public:
GpuBackend(QObject* parent = nullptr) : QObject(parent) { }
~GpuBackend() override = default;
virtual void start() = 0;
virtual void stop() = 0;
virtual void update() = 0;
Q_SIGNAL void deviceAdded(GpuDevice *device);
Q_SIGNAL void deviceRemoved(GpuDevice *device);
};
/*
* SPDX-FileCopyrightText: 2020 Arjen Hiemstra <ahiemstra@heimr.nl>
*
* SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
*/
#include "GpuDevice.h"
#include <KLocalizedString>
GpuDevice::GpuDevice(const QString& id, const QString& name)
: SensorObject(id, name)
{
}
void GpuDevice::initialize()
{
makeSensors();
m_nameProperty->setName(i18nc("@title", "Name"));
m_nameProperty->setPrefix(name());
m_nameProperty->setValue(name());
m_usageProperty->setName(i18nc("@title", "Usage"));
m_usageProperty->setPrefix(name());
m_usageProperty->setMin(0);
m_usageProperty->setMax(100);
m_usageProperty->setUnit(KSysGuard::UnitPercent);
m_totalVramProperty->setName(i18nc("@title", "Total Video Memory"));
m_totalVramProperty->setPrefix(name());
m_totalVramProperty->setShortName(i18nc("@title Short for Total Video Memory", "Total"));
m_totalVramProperty->setUnit(KSysGuard::UnitByte);
m_usedVramProperty->setName(i18nc("@title", "Video Memory Used"));
m_usedVramProperty->setPrefix(name());
m_usedVramProperty->setShortName(i18nc("@title Short for Video Memory Used", "Used"));
m_usedVramProperty->setMax(m_totalVramProperty);
m_usedVramProperty->setUnit(KSysGuard::UnitByte);
m_coreFrequencyProperty->setName(i18nc("@title", "Frequency"));
m_coreFrequencyProperty->setPrefix(name());
m_coreFrequencyProperty->setUnit(KSysGuard::UnitMegaHertz);
m_memoryFrequencyProperty->setName(i18nc("@title", "Memory Frequency"));
m_memoryFrequencyProperty->setPrefix(name());
m_memoryFrequencyProperty->setUnit(KSysGuard::UnitMegaHertz);
m_coreTemperatureProperty->setName(i18nc("@title", "Temperature"));
m_coreTemperatureProperty->setPrefix(name());
m_coreTemperatureProperty->setUnit(KSysGuard::UnitCelsius);
m_memoryTemperatureProperty->setName(i18nc("@title", "Memory Temperature"));
m_memoryTemperatureProperty->setPrefix(name());
m_memoryTemperatureProperty->setUnit(KSysGuard::UnitCelsius);
}
void GpuDevice::makeSensors()
{
m_nameProperty = new SensorProperty(QStringLiteral("name"), this);
m_usageProperty = new SensorProperty(QStringLiteral("usage"), this);
m_totalVramProperty = new SensorProperty(QStringLiteral("totalVram"), this);
m_usedVramProperty = new SensorProperty(QStringLiteral("usedVram"), this);
m_coreFrequencyProperty = new SensorProperty(QStringLiteral("coreFrequency"), this);
m_memoryFrequencyProperty = new SensorProperty(QStringLiteral("memoryFrequency"), this);
m_coreTemperatureProperty = new SensorProperty(QStringLiteral("coreTemperature"), this);
m_memoryTemperatureProperty = new SensorProperty(QStringLiteral("memoryTemperature"), this);
}
/*
* SPDX-FileCopyrightText: 2020 Arjen Hiemstra <ahiemstra@heimr.nl>
*
* SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
*/
#pragma once
#include <QObject>
#include "SensorObject.h"
class GpuDevice : public SensorObject
{
Q_OBJECT
public:
GpuDevice(const QString& id, const QString& name);
~GpuDevice() override = default;
virtual void initialize();
virtual void update() = 0;
protected:
virtual void makeSensors();
SensorProperty *m_nameProperty;
SensorProperty *m_usageProperty;
SensorProperty *m_totalVramProperty;
SensorProperty *m_usedVramProperty;
SensorProperty *m_coreTemperatureProperty;
SensorProperty *m_memoryTemperatureProperty;
SensorProperty *m_coreFrequencyProperty;
SensorProperty *m_memoryFrequencyProperty;
};
/*
Copyright (c) 2020 Arjen Hiemstra <ahiemstra@heimr.nl>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#include "GpuPlugin.h"
#include <KPluginFactory>
#include <KLocalizedString>
#include <SensorContainer.h>
#include "GpuDevice.h"
#include "LinuxBackend.h"
class GpuPlugin::Private
{
public:
std::unique_ptr<SensorContainer> container;
std::unique_ptr<GpuBackend> backend;
};
GpuPlugin::GpuPlugin(QObject *parent, const QVariantList &args)
: SensorPlugin(parent, args)
, d(std::make_unique<Private>())
{
d->container = std::make_unique<SensorContainer>(QStringLiteral("gpu"), i18nc("@title", "GPU"), this);
#ifdef Q_OS_LINUX
d->backend = std::make_unique<LinuxBackend>();
#endif
if (d->backend) {
connect(d->backend.get(), &GpuBackend::deviceAdded, this, [this](GpuDevice* device) {
d->container->addObject(device);
});
connect(d->backend.get(), &GpuBackend::deviceRemoved, this, [this](GpuDevice* device) {
d->container->removeObject(device);
});
d->backend->start();
}
}
GpuPlugin::~GpuPlugin()
{
d->container.reset();
if (d->backend) {
d->backend->stop();
}
}
void GpuPlugin::update()
{
if (d->backend) {
d->backend->update();
}
}
K_PLUGIN_CLASS_WITH_JSON(GpuPlugin, "metadata.json")
#include "GpuPlugin.moc"
/*
Copyright (c) 2020 Arjen Hiemstra <ahiemstra@heimr.nl>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#pragma once
#include <memory>
#include "SensorPlugin.h"
class GpuPlugin : public SensorPlugin
{
Q_OBJECT
public:
GpuPlugin(QObject *parent, const QVariantList &args);
~GpuPlugin();
QString providerName() const override
{
return QStringLiteral("gpu");
}
void update() override;
private:
class Private;
std::unique_ptr<Private> d;
};
/*
* SPDX-FileCopyrightText: 2020 Arjen Hiemstra <ahiemstra@heimr.nl>
*
* SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
*/
#include "LinuxAmdGpu.h"
#include <libudev.h>
#include <QDir>
#include "SysFsSensor.h"
int ppTableGetMax(const QByteArray &table)
{
const auto lines = table.split('\n');
auto line = lines.last();
return std::atoi(line.mid(line.indexOf(':') + 1).data());
}
int ppTableGetCurrent(const QByteArray &table)
{
const auto lines = table.split('\n');
int current = 0;
for (auto line : lines) {
if (!line.contains('*')) {
continue;
}
current = std::atoi(line.mid(line.indexOf(':') + 1));
}
return current;
}
LinuxAmdGpu::LinuxAmdGpu(const QString& id, const QString& name, udev_device *device)
: GpuDevice(id, name)
, m_device(device)
{
udev_device_ref(m_device);
}
LinuxAmdGpu::~LinuxAmdGpu()
{
udev_device_unref(m_device);
}
void LinuxAmdGpu::initialize()
{
// Find temprature sensor paths.
// Temperature sensors are exposed in the "hwmon" subdirectory of the
// device, but in a subdirectory with a number appended that differs per
// device. So search through the hwmon directory for the files that we want.
QDir hwmonDir(QString::fromLocal8Bit(udev_device_get_syspath(m_device)) % QStringLiteral("/hwmon"));
const auto entries = hwmonDir.entryList({QStringLiteral("hwmon*")});
for (auto entry : entries) {
QString inputPath = entry % QStringLiteral("/temp1_input");
QString critPath = entry % QStringLiteral("/temp1_crit");
if (hwmonDir.exists(inputPath) && hwmonDir.exists(critPath)) {
m_coreTemperatureCurrentPath = QStringLiteral("hwmon/") % inputPath;
m_coreTemperatureMaxPath = QStringLiteral("hwmon/") % critPath;
break;
}
}
GpuDevice::initialize();
m_nameProperty->setValue(QString::fromLocal8Bit(udev_device_get_property_value(m_device, "ID_MODEL_FROM_DATABASE")));
auto result = udev_device_get_sysattr_value(m_device, "mem_info_vram_total");
if (result) {
m_totalVramProperty->setValue(std::atoll(result));
}
m_coreFrequencyProperty->setMax(ppTableGetMax(udev_device_get_sysattr_value(m_device, "pp_dpm_sclk")));
m_memoryFrequencyProperty->setMax(ppTableGetMax(udev_device_get_sysattr_value(m_device, "pp_dpm_mclk")));
result = udev_device_get_sysattr_value(m_device, m_coreTemperatureMaxPath.toLocal8Bit());
if (result) {
m_coreTemperatureProperty->setMax(std::atoi(result) / 1000);
}
}
void LinuxAmdGpu::update()
{
for (auto sensor : m_sysFsSensors) {
sensor->update();
}
}
void LinuxAmdGpu::makeSensors()
{
auto devicePath = QString::fromLocal8Bit(udev_device_get_syspath(m_device));
m_nameProperty = new SensorProperty(QStringLiteral("name"), this);
m_totalVramProperty = new SensorProperty(QStringLiteral("totalVram"), this);
auto sensor = new SysFsSensor(QStringLiteral("usage"), devicePath % QStringLiteral("/gpu_busy_percent"), this);
m_usageProperty = sensor;
m_sysFsSensors << sensor;
sensor = new SysFsSensor(QStringLiteral("usedVram"), devicePath % QStringLiteral("/mem_info_vram_used"), this);
m_usedVramProperty = sensor;
m_sysFsSensors << sensor;
sensor = new SysFsSensor(QStringLiteral("coreFrequency"), devicePath % QStringLiteral("/pp_dpm_sclk"), this);
sensor->setConvertFunction([](const QByteArray &input) {
return ppTableGetCurrent(input);
});
m_coreFrequencyProperty = sensor;
m_sysFsSensors << sensor;
sensor = new SysFsSensor(QStringLiteral("memoryFrequency"), devicePath % QStringLiteral("/pp_dpm_mclk"), this);
sensor->setConvertFunction([](const QByteArray &input) {
return ppTableGetCurrent(input);
});
m_memoryFrequencyProperty = sensor;
m_sysFsSensors << sensor;
sensor = new SysFsSensor(QStringLiteral("coreTemperature"), devicePath % QLatin1Char('/') % m_coreTemperatureCurrentPath, this);
sensor->setConvertFunction([](const QByteArray &input) {
auto result = std::atoi(input);
return result / 1000;
});
m_coreTemperatureProperty = sensor;
m_sysFsSensors << sensor;
m_memoryTemperatureProperty = new SensorProperty(QStringLiteral("memoryTemperature"), this);
}
/*
* SPDX-FileCopyrightText: 2020 Arjen Hiemstra <ahiemstra@heimr.nl>
*
* SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
*/
#pragma once
#include <QObject>
#include "GpuDevice.h"
struct udev_device;
class SysFsSensor;
class LinuxAmdGpu : public GpuDevice
{
Q_OBJECT
public:
LinuxAmdGpu(const QString& id, const QString& name, udev_device *device);
~LinuxAmdGpu() override;
void initialize() override;
void update() override;
protected:
void makeSensors() override;
private:
udev_device *m_device;
QVector<SysFsSensor*> m_sysFsSensors;
QString m_coreTemperatureCurrentPath;
QString m_coreTemperatureMaxPath;
};
/*
* SPDX-FileCopyrightText: 2020 Arjen Hiemstra <ahiemstra@heimr.nl>
*
* SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
*/
#include "LinuxBackend.h"
#include <QDebug>
#include <QDir>
#include <KLocalizedString>
#include <libudev.h>
#include "LinuxAmdGpu.h"
// Vendor ID strings, as used in sysfs
static const char *amdVendor = "0x1002";
static const char *intelVendor = "0x8086";
static const char *nvidiaVendor = "0x10de";
LinuxBackend::LinuxBackend(QObject *parent)
: GpuBackend(parent)
{
}
void LinuxBackend::start()
{
if (!m_udev) {
m_udev = udev_new();
}
auto enumerate = udev_enumerate_new(m_udev);
udev_enumerate_add_match_property(enumerate, "ID_PCI_CLASS_FROM_DATABASE", "Display controller");
udev_enumerate_scan_devices(enumerate);
int gpuCounter = 0;
auto devices = udev_enumerate_get_list_entry(enumerate);
for (auto entry = devices; entry; entry = udev_list_entry_get_next(entry)) {
auto path = udev_list_entry_get_name(entry);
auto device = udev_device_new_from_syspath(m_udev, path);
auto vendor = QByteArray(udev_device_get_sysattr_value(device, "vendor"));
GpuDevice *gpu = nullptr;
if (vendor == amdVendor) {
gpu = new LinuxAmdGpu{QStringLiteral("gpu%1").arg(gpuCounter), i18nc("@title %1 is GPU number", "GPU %1", gpuCounter + 1), device};
} else if (vendor == intelVendor) {
} else if (vendor == nvidiaVendor) {
}
if (gpu) {
gpuCounter++;
gpu->initialize();
m_devices.append(gpu);
Q_EMIT deviceAdded(gpu);
}
udev_device_unref(device);
}
udev_enumerate_unref(enumerate);
}
void LinuxBackend::stop()
{
qDeleteAll(m_devices);
udev_unref(m_udev);
}
void LinuxBackend::update()
{
for (auto device : qAsConst(m_devices)) {
device->update();
}
}
/*
* SPDX-FileCopyrightText: 2020 Arjen Hiemstra <ahiemstra@heimr.nl>
*
* SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
*/
#pragma once
#include "GpuBackend.h"
struct udev;
class GpuDevice;
class LinuxBackend : public GpuBackend
{
Q_OBJECT
public:
LinuxBackend(QObject* parent = nullptr);
void start() override;
void stop() override;
void update() override;
private:
udev *m_udev;
QVector<GpuDevice*> m_devices;
};
/*
* SPDX-FileCopyrightText: 2020 Arjen Hiemstra <ahiemstra@heimr.nl>
*
* SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
*/
#include "SysFsSensor.h"
#include <QFile>
SysFsSensor::SysFsSensor(const QString& id, const QString& path, SensorObject* parent)
: SensorProperty(id, parent)
{
m_path = path;
m_convertFunction = [](const QByteArray &input) {
return std::atoll(input);
};
}
void SysFsSensor::setConvertFunction(const std::function<QVariant (const QByteArray &)>& function)
{
m_convertFunction = function;
}
void SysFsSensor::update()
{
if (!isSubscribed()) {
return;
}
QFile file(m_path);
if (!file.exists()) {