fix(gpu): use epsilon comparison instead of == for floating-point guards

This commit is contained in:
2026-06-30 01:36:40 +02:00
parent 396825d785
commit b2cddea636
+118 -77
View File
@@ -17,26 +17,31 @@ namespace ZShell::services {
namespace { namespace {
constexpr const char* kTypeDetectScript = constexpr const char* kTypeDetectScript =
"if command -v nvidia-smi >/dev/null 2>&1 && nvidia-smi -L >/dev/null 2>&1; then echo NVIDIA;" "if command -v nvidia-smi >/dev/null 2>&1 && nvidia-smi -L >/dev/null "
" elif ls /sys/class/drm/card*/device/gpu_busy_percent 2>/dev/null | grep -q .; then echo GENERIC;" "2>&1; then echo NVIDIA;"
" elif ls /sys/class/drm/card*/device/gpu_busy_percent 2>/dev/null | grep "
"-q .; then echo GENERIC;"
" else echo NONE; fi"; " else echo NONE; fi";
constexpr const char* kNameDetectScript = "nvidia-smi --query-gpu=name --format=csv,noheader 2>/dev/null" constexpr const char* kNameDetectScript =
" || glxinfo -B 2>/dev/null | grep 'Device:' | cut -d':' -f2 | cut -d'(' -f1" "nvidia-smi --query-gpu=name --format=csv,noheader 2>/dev/null"
" || lspci 2>/dev/null | grep -i 'vga\\|3d controller\\|display' | head -1"; " || glxinfo -B 2>/dev/null | grep 'Device:' | cut -d':' -f2 | cut -d'(' "
"-f1"
" || lspci 2>/dev/null | grep -i 'vga\\|3d controller\\|display' | head -1";
} // namespace } // namespace
Gpu::Gpu(QObject* parent) Gpu::Gpu(QObject* parent) : TickingService(parent) {
: TickingService(parent) { QString configPath =
QString configPath = QDir::homePath() + QStringLiteral("/.config/zshell/config.json"); QDir::homePath() + QStringLiteral("/.config/zshell/config.json");
auto reloadConfig = [this, configPath]() { auto reloadConfig = [this, configPath]() {
QFile file(configPath); QFile file(configPath);
if (file.open(QIODevice::ReadOnly)) { if (file.open(QIODevice::ReadOnly)) {
QJsonDocument doc = QJsonDocument::fromJson(file.readAll()); QJsonDocument doc = QJsonDocument::fromJson(file.readAll());
if (!doc.isNull()) { if (!doc.isNull()) {
QJsonObject services = doc.object().value("services").toObject(); QJsonObject services =
doc.object().value("services").toObject();
setUserType(parseType(services.value("gpuType").toString())); setUserType(parseType(services.value("gpuType").toString()));
} }
} }
@@ -46,18 +51,25 @@ Gpu::Gpu(QObject* parent)
static QFileSystemWatcher* watcher = new QFileSystemWatcher(); static QFileSystemWatcher* watcher = new QFileSystemWatcher();
if (!watcher->files().contains(configPath)) { if (!watcher->files().contains(configPath)) {
QObject::connect(watcher, &QFileSystemWatcher::fileChanged, this, [this, configPath]() { QObject::connect(
QTimer::singleShot(100, this, [this, configPath]() { watcher,
QFile file(configPath); &QFileSystemWatcher::fileChanged,
if (file.exists()) { this,
QJsonDocument doc = QJsonDocument::fromJson(file.readAll()); [this, configPath]() {
if (!doc.isNull()) { QTimer::singleShot(100, this, [this, configPath]() {
QJsonObject services = doc.object().value("services").toObject(); QFile file(configPath);
setUserType(parseType(services.value("gpuType").toString())); if (file.exists()) {
QJsonDocument doc =
QJsonDocument::fromJson(file.readAll());
if (!doc.isNull()) {
QJsonObject services =
doc.object().value("services").toObject();
setUserType(parseType(
services.value("gpuType").toString()));
}
} }
} });
}); });
});
watcher->addPath(configPath); watcher->addPath(configPath);
} }
@@ -134,13 +146,13 @@ void Gpu::setName(QString value) {
} }
void Gpu::setMemoryUsed(qreal value) { void Gpu::setMemoryUsed(qreal value) {
if (m_memoryUsed == value) return; if (std::abs(m_memoryUsed - value) < 0.001) return;
m_memoryUsed = value; m_memoryUsed = value;
Q_EMIT memoryUsedChanged(); Q_EMIT memoryUsedChanged();
} }
void Gpu::setMemoryTotal(qreal value) { void Gpu::setMemoryTotal(qreal value) {
if (m_memoryTotal == value) return; if (std::abs(m_memoryTotal - value) < 0.001) return;
m_memoryTotal = value; m_memoryTotal = value;
Q_EMIT memoryTotalChanged(); Q_EMIT memoryTotalChanged();
} }
@@ -169,15 +181,22 @@ void Gpu::detectTypeOnce() {
return; return;
} }
m_typeProc = new QProcess(this); m_typeProc = new QProcess(this);
QObject::connect(m_typeProc, &QProcess::finished, this, [this](int, QProcess::ExitStatus) { QObject::connect(
const QByteArray out = m_typeProc->readAllStandardOutput().trimmed(); m_typeProc,
&QProcess::finished,
this,
[this](int, QProcess::ExitStatus) {
const QByteArray out =
m_typeProc->readAllStandardOutput().trimmed();
if (!out.isEmpty()) { if (!out.isEmpty()) {
setAutoType(parseType(QString::fromLatin1(out))); setAutoType(parseType(QString::fromLatin1(out)));
} }
m_typeProc->deleteLater(); m_typeProc->deleteLater();
m_typeProc = nullptr; m_typeProc = nullptr;
}); });
m_typeProc->start(QStringLiteral("sh"), { QStringLiteral("-c"), QString::fromLatin1(kTypeDetectScript) }); m_typeProc->start(
QStringLiteral("sh"),
{QStringLiteral("-c"), QString::fromLatin1(kTypeDetectScript)});
} }
void Gpu::detectNameOnce() { void Gpu::detectNameOnce() {
@@ -185,21 +204,30 @@ void Gpu::detectNameOnce() {
return; return;
} }
m_nameProc = new QProcess(this); m_nameProc = new QProcess(this);
QObject::connect(m_nameProc, &QProcess::finished, this, [this](int, QProcess::ExitStatus) { QObject::connect(
const QString output = QString::fromUtf8(m_nameProc->readAllStandardOutput()).trimmed(); m_nameProc,
&QProcess::finished,
this,
[this](int, QProcess::ExitStatus) {
const QString output =
QString::fromUtf8(m_nameProc->readAllStandardOutput()).trimmed();
if (!output.isEmpty()) { if (!output.isEmpty()) {
const QString lower = output.toLower(); const QString lower = output.toLower();
if (lower.contains(QStringLiteral("nvidia")) || lower.contains(QStringLiteral("geforce")) || if (lower.contains(QStringLiteral("nvidia")) ||
lower.contains(QStringLiteral("rtx")) || lower.contains(QStringLiteral("gtx")) || lower.contains(QStringLiteral("geforce")) ||
lower.contains(QStringLiteral("rx"))) { lower.contains(QStringLiteral("rtx")) ||
lower.contains(QStringLiteral("gtx")) ||
lower.contains(QStringLiteral("rx"))) {
setName(cleanName(output)); setName(cleanName(output));
} else { } else {
static const QRegularExpression bracketRe(QStringLiteral("\\[([^\\]]+)\\][^\\[]*$")); static const QRegularExpression bracketRe(
QStringLiteral("\\[([^\\]]+)\\][^\\[]*$"));
const auto bracket = bracketRe.match(output); const auto bracket = bracketRe.match(output);
if (bracket.hasMatch()) { if (bracket.hasMatch()) {
setName(cleanName(bracket.captured(1))); setName(cleanName(bracket.captured(1)));
} else { } else {
static const QRegularExpression colonRe(QStringLiteral(":\\s*(.+)")); static const QRegularExpression colonRe(
QStringLiteral(":\\s*(.+)"));
const auto colon = colonRe.match(output); const auto colon = colonRe.match(output);
if (colon.hasMatch()) { if (colon.hasMatch()) {
setName(cleanName(colon.captured(1))); setName(cleanName(colon.captured(1)));
@@ -210,17 +238,21 @@ void Gpu::detectNameOnce() {
m_nameProc->deleteLater(); m_nameProc->deleteLater();
m_nameProc = nullptr; m_nameProc = nullptr;
}); });
m_nameProc->start(QStringLiteral("sh"), { QStringLiteral("-c"), QString::fromLatin1(kNameDetectScript) }); m_nameProc->start(
QStringLiteral("sh"),
{QStringLiteral("-c"), QString::fromLatin1(kNameDetectScript)});
} }
void Gpu::readGenericUsage() { void Gpu::readGenericUsage() {
const QStringList paths = const QStringList paths = QDir(QStringLiteral("/sys/class/drm"))
QDir(QStringLiteral("/sys/class/drm")) .entryList(
.entryList(QStringList() << QStringLiteral("card*"), QDir::Dirs | QDir::NoDotAndDotDot); QStringList() << QStringLiteral("card*"),
QDir::Dirs | QDir::NoDotAndDotDot);
qreal sum = 0.0; qreal sum = 0.0;
int count = 0; int count = 0;
for (const QString& card : paths) { for (const QString& card : paths) {
QFile f(QStringLiteral("/sys/class/drm/%1/device/gpu_busy_percent").arg(card)); QFile f(QStringLiteral("/sys/class/drm/%1/device/gpu_busy_percent")
.arg(card));
if (!f.open(QIODevice::ReadOnly | QIODevice::Text)) { if (!f.open(QIODevice::ReadOnly | QIODevice::Text)) {
continue; continue;
} }
@@ -244,57 +276,65 @@ void Gpu::startNvidiaUsage() {
return; return;
} }
m_nvidiaProc = new QProcess(this); m_nvidiaProc = new QProcess(this);
QObject::connect(m_nvidiaProc, &QProcess::readyReadStandardOutput, this, [this]() { QObject::connect(
while (m_nvidiaProc->canReadLine()) { m_nvidiaProc, &QProcess::readyReadStandardOutput, this, [this]() {
const QByteArray out = m_nvidiaProc->readLine(); while (m_nvidiaProc->canReadLine()) {
const QString output = QString::fromUtf8(out).trimmed(); const QByteArray out = m_nvidiaProc->readLine();
if (output.isEmpty()) const QString output = QString::fromUtf8(out).trimmed();
continue; if (output.isEmpty()) continue;
const QList<QString> parts = output.split(','); const QList<QString> parts = output.split(',');
if (parts.size() < 4) if (parts.size() < 4) return;
return;
bool ok1 = false; bool ok1 = false;
bool ok2 = false; bool ok2 = false;
bool ok3 = false; bool ok3 = false;
bool ok4 = false; bool ok4 = false;
const qreal usage = parts.at(0).trimmed().toDouble(&ok1) / 100.0; const qreal usage =
const qreal temp = parts.at(1).trimmed().toDouble(&ok2); parts.at(0).trimmed().toDouble(&ok1) / 100.0;
const qreal memUsed = parts.at(2).trimmed().toDouble(&ok3); const qreal temp = parts.at(1).trimmed().toDouble(&ok2);
const qreal memTotal = parts.at(3).trimmed().toDouble(&ok4); const qreal memUsed = parts.at(2).trimmed().toDouble(&ok3);
const qreal memTotal = parts.at(3).trimmed().toDouble(&ok4);
if (ok1 && std::abs(usage - m_percentage) > 0.0001) { if (ok1 && std::abs(usage - m_percentage) > 0.0001) {
m_percentage = usage; m_percentage = usage;
Q_EMIT percentageChanged(); Q_EMIT percentageChanged();
}
if (ok2 && std::abs(temp - m_temperature) > 0.05) {
m_temperature = temp;
Q_EMIT temperatureChanged();
}
if (ok3) {
setMemoryUsed(memUsed);
}
if (ok4) {
setMemoryTotal(memTotal);
}
} }
if (ok2 && std::abs(temp - m_temperature) > 0.05) { });
m_temperature = temp;
Q_EMIT temperatureChanged();
}
if (ok3) {
setMemoryUsed(memUsed);
}
if (ok4) {
setMemoryTotal(memTotal);
}
}
});
QObject::connect(m_nvidiaProc, &QProcess::finished, this, [this]() { QObject::connect(m_nvidiaProc, &QProcess::finished, this, [this]() {
m_nvidiaProc->deleteLater(); m_nvidiaProc->deleteLater();
m_nvidiaProc = nullptr; m_nvidiaProc = nullptr;
}); });
QObject::connect(m_nvidiaProc, &QProcess::errorOccurred, this, [this](QProcess::ProcessError) { QObject::connect(
m_nvidiaProc->deleteLater(); m_nvidiaProc,
m_nvidiaProc = nullptr; &QProcess::errorOccurred,
}); this,
[this](QProcess::ProcessError) {
m_nvidiaProc->deleteLater();
m_nvidiaProc = nullptr;
});
m_nvidiaProc->start(QStringLiteral("nvidia-smi"), { QStringLiteral("--query-gpu=utilization.gpu,temperature.gpu,memory.used,memory.total"), m_nvidiaProc->start(
QStringLiteral("--format=csv,noheader,nounits"), QStringLiteral("nvidia-smi"),
QStringLiteral("-lms"), {QStringLiteral(
QString::number(updateInterval()) }); "--query-gpu=utilization.gpu,temperature.gpu,"
"memory.used,memory.total"),
QStringLiteral("--format=csv,noheader,nounits"),
QStringLiteral("-lms"),
QString::number(updateInterval())});
} }
void Gpu::readGpuTemperature() { void Gpu::readGpuTemperature() {
@@ -322,7 +362,8 @@ Gpu::Type Gpu::parseType(const QString& s) {
QString Gpu::cleanName(QString s) { QString Gpu::cleanName(QString s) {
static const QRegularExpression noise( static const QRegularExpression noise(
QStringLiteral("\\(R\\)|\\(TM\\)|Graphics"), QRegularExpression::CaseInsensitiveOption); QStringLiteral("\\(R\\)|\\(TM\\)|Graphics"),
QRegularExpression::CaseInsensitiveOption);
static const QRegularExpression spaces(QStringLiteral("\\s+")); static const QRegularExpression spaces(QStringLiteral("\\s+"));
s.replace(noise, QString()); s.replace(noise, QString());
s.replace(spaces, QStringLiteral(" ")); s.replace(spaces, QStringLiteral(" "));