Commit f0c691d4 authored by Arjen Hiemstra's avatar Arjen Hiemstra

Add initial NVidia GPU support to GPU plugin

Ported from the "nvidia" plugin
parent 237c8843
......@@ -3,8 +3,10 @@ set(KSYSGUARD_GPU_PLUGIN_SOURCES
GpuBackend.cpp
GpuDevice.cpp
LinuxAmdGpu.cpp
LinuxNvidiaGpu.cpp
LinuxBackend.cpp
SysFsSensor.cpp
NvidiaSmiProcess.cpp
)
add_library(ksysguard_plugin_gpu MODULE ${KSYSGUARD_GPU_PLUGIN_SOURCES})
......
/*
* 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 "LinuxNvidiaGpu.h"
#include "NvidiaSmiProcess.h"
NvidiaSmiProcess *LinuxNvidiaGpu::s_smiProcess = nullptr;
LinuxNvidiaGpu::LinuxNvidiaGpu(int index, const QString& id, const QString& name)
: GpuDevice(id, name)
, m_index(index)
{
if (!s_smiProcess) {
s_smiProcess = new NvidiaSmiProcess();
}
connect(s_smiProcess, &NvidiaSmiProcess::dataReceived, this, &LinuxNvidiaGpu::onDataReceived);
}
LinuxNvidiaGpu::~LinuxNvidiaGpu()
{
}
void LinuxNvidiaGpu::initialize()
{
GpuDevice::initialize();
for (auto sensor : {m_usageProperty, m_totalVramProperty, m_usedVramProperty, m_temperatureProperty, m_coreFrequencyProperty, m_memoryFrequencyProperty}) {
connect(sensor, &SensorProperty::subscribedChanged, sensor, [sensor]() {
if (sensor->isSubscribed()) {
LinuxNvidiaGpu::s_smiProcess->ref();
} else {
LinuxNvidiaGpu::s_smiProcess->unref();
}
});
}
auto queryResult = s_smiProcess->query();
if (m_index >= queryResult.length()) {
qWarning() << "Could not retrieve information for NVidia GPU" << m_index;
} else {
auto data = queryResult.at(m_index);
m_nameProperty->setValue(data.name);
m_totalVramProperty->setValue(data.totalMemory);
m_usedVramProperty->setMax(data.totalMemory);
m_coreFrequencyProperty->setMax(data.maxCoreFrequency);
m_memoryFrequencyProperty->setMax(data.maxMemoryFrequency);
m_temperatureProperty->setMax(data.maxTemperature);
}
m_usedVramProperty->setUnit(KSysGuard::UnitMegaByte);
m_totalVramProperty->setUnit(KSysGuard::UnitMegaByte);
}
void LinuxNvidiaGpu::onDataReceived(const NvidiaSmiProcess::GpuData& data)
{
if (data.index != m_index) {
return;
}
m_usageProperty->setValue(data.usage);
m_usedVramProperty->setValue(data.memoryUsed);
m_coreFrequencyProperty->setValue(data.coreFrequency);
m_memoryFrequencyProperty->setValue(data.memoryFrequency);
m_temperatureProperty->setValue(data.temperature);
}
/*
* 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"
#include "NvidiaSmiProcess.h"
class LinuxNvidiaGpu : public GpuDevice
{
Q_OBJECT
public:
LinuxNvidiaGpu(int index, const QString& id, const QString& name);
~LinuxNvidiaGpu() override;
void initialize() override;
private:
void onDataReceived(const NvidiaSmiProcess::GpuData &data);
int m_index = 0;
static NvidiaSmiProcess *s_smiProcess;
};
/*
* SPDX-FileCopyrightText: 2019 David Edmundson <davidedmundson@kde.org>
* SPDX-FileCopyrightText: 2020 Arjen Hiemstra <ahiemstra@heimr.nl>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "NvidiaSmiProcess.h"
#include <QStandardPaths>
NvidiaSmiProcess::NvidiaSmiProcess()
{
m_smiPath = QStandardPaths::findExecutable("nvidia-smi");
}
bool NvidiaSmiProcess::isSupported() const
{
return !m_smiPath.isEmpty();
}
QVector<NvidiaSmiProcess::GpuQueryResult> NvidiaSmiProcess::query()
{
QVector<NvidiaSmiProcess::GpuQueryResult> result;
if (!isSupported()) {
return result;
}
// Read and parse the result of "nvidia-smi query"
// This seems to be the only way to get certain values like total memory or
// maximum temperature. Unfortunately the output isn't very easily parseable
// so we have to do some trickery to parse things.
QProcess queryProcess;
queryProcess.setProgram(m_smiPath);
queryProcess.setArguments({QStringLiteral("query")});
queryProcess.start();
queryProcess.waitForReadyRead();
int gpuCounter = 0;
GpuQueryResult &data = result[0];
bool readMemory = false;
bool readMaxClocks = false;
while (queryProcess.canReadLine()) {
auto line = queryProcess.readLine();
if (line.startsWith("GPU ")) {
// Start of GPU properties block.
result.append(GpuQueryResult{});
data = result[gpuCounter];
gpuCounter++;
}
if ((readMemory || readMaxClocks) && !line.startsWith(" ")) {
readMemory = false;
readMaxClocks = false;
}
if (line.startsWith(" Product Name")) {
data.name = line.mid(line.indexOf(':')).trimmed();
}
if (line.startsWith(" FB Memory Usage") || line.startsWith(" BAR1 Memory Usage")) {
readMemory = true;
}
if (line.startsWith(" Max Clocks")) {
readMaxClocks = true;
}
if (line.startsWith(" Total") && readMemory) {
data.totalMemory += std::atoi(line.mid(line.indexOf(':')));
}
if (line.startsWith(" GPU Shutdown Temp")) {
data.maxTemperature = std::atoi(line.mid(line.indexOf(':')));
}
if (line.startsWith(" Graphics") && readMaxClocks) {
data.maxCoreFrequency = std::atoi(line.mid(line.indexOf(':')));
}
if (line.startsWith(" Memory") && readMaxClocks) {
data.maxMemoryFrequency = std::atoi(line.mid(line.indexOf(':')));
}
}
return result;
}
void NvidiaSmiProcess::ref()
{
if (!isSupported()) {
return;
}
m_references++;
if (m_process) {
return;
}
m_process = std::make_unique<QProcess>();
m_process->setProgram(m_smiPath);
m_process->setArguments({
QStringLiteral("dmon"), // Monitor
QStringLiteral("-d"), QStringLiteral("2"), // 2 seconds delay, to match daemon update rate
QStringLiteral("-s"), QStringLiteral("pucm") // Include all relevant statistics
});
connect(m_process.get(), &QProcess::readyReadStandardOutput, this, &NvidiaSmiProcess::readStatisticsData);
m_process->start();
}
void NvidiaSmiProcess::unref()
{
if (!isSupported()) {
return;
}
m_references--;
if (!m_process || m_references > 0) {
return;
}
m_process->terminate();
m_process->waitForFinished();
m_process.reset();
}
void NvidiaSmiProcess::readStatisticsData()
{
while (m_process->canReadLine()) {
const QString line = m_process->readLine();
if (line.startsWith(QLatin1Char('#'))) {
continue;
}
const QVector<QStringRef> parts = line.splitRef(QLatin1Char(' '), Qt::SkipEmptyParts);
// format at time of writing is
// # gpu pwr gtemp mtemp sm mem enc dec mclk pclk fb bar1
if (parts.count() != 12) {
continue;
}
bool ok;
int index = parts[0].toInt(&ok);
if (!ok) {
continue;
}
GpuData data;
data.index = index;
data.power = parts[1].toUInt();
data.temperature = parts[2].toUInt();
// GPU usage equals "SM" usage + "ENC" usage + "DEC" usage
data.usage = parts[4].toUInt() + parts[6].toUInt() + parts[7].toUInt();
// Total memory used equals "FB" usage + "BAR1" usage
data.memoryUsed = parts[10].toUInt() + parts[11].toUInt();
data.memoryFrequency = parts[8].toUInt();
data.coreFrequency = parts[9].toUInt();
Q_EMIT dataReceived(data);
}
}
/*
* SPDX-FileCopyrightText: 2020 Arjen Hiemstra <ahiemstra@heimr.nl>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
#pragma once
#include <memory>
#include <QObject>
#include <QProcess>
class NvidiaSmiProcess : public QObject
{
Q_OBJECT
public:
struct GpuData {
int index = -1;
uint power = 0;
uint temperature = 0;
uint usage = 0;
uint memoryUsed = 0;
uint coreFrequency = 0;
uint memoryFrequency = 0;
};
struct GpuQueryResult {
QString name;
uint totalMemory = 0;
uint maxCoreFrequency = 0;
uint maxMemoryFrequency = 0;
uint maxTemperature = 0;
};
NvidiaSmiProcess();
bool isSupported() const;
QVector<GpuQueryResult> query();
void ref();
void unref();
Q_SIGNAL void dataReceived(const GpuData &data);
private:
void readStatisticsData();
QString m_smiPath;
std::unique_ptr<QProcess> m_process = nullptr;
int m_references = 0;
};
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment