|
|
|
@@ -2,97 +2,187 @@
|
|
|
|
|
|
|
|
|
|
#include "sensorslib.hpp"
|
|
|
|
|
|
|
|
|
|
#include <cmath>
|
|
|
|
|
#include <qcontainerfwd.h>
|
|
|
|
|
#include "config/rootnodes.hpp"
|
|
|
|
|
#include "config/services.hpp"
|
|
|
|
|
|
|
|
|
|
#include <qdir.h>
|
|
|
|
|
#include <qfile.h>
|
|
|
|
|
#include <qobject.h>
|
|
|
|
|
#include <qdiriterator.h>
|
|
|
|
|
#include <qregularexpression.h>
|
|
|
|
|
#include <QJsonDocument>
|
|
|
|
|
#include <QJsonObject>
|
|
|
|
|
#include <QJsonValue>
|
|
|
|
|
#include <QFileSystemWatcher>
|
|
|
|
|
#include <QTimer>
|
|
|
|
|
|
|
|
|
|
namespace ZShell::services {
|
|
|
|
|
|
|
|
|
|
using Qt::StringLiterals::operator""_s;
|
|
|
|
|
|
|
|
|
|
namespace {
|
|
|
|
|
|
|
|
|
|
constexpr const char* kTypeDetectScript =
|
|
|
|
|
"if command -v nvidia-smi >/dev/null 2>&1 && nvidia-smi -L >/dev/null "
|
|
|
|
|
"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";
|
|
|
|
|
QStringList cardFiles(const QString& relative) {
|
|
|
|
|
static const QRegularExpression k_cardRe(u"^card\\d+$"_s);
|
|
|
|
|
|
|
|
|
|
constexpr const char* kNameDetectScript =
|
|
|
|
|
"nvidia-smi --query-gpu=name --format=csv,noheader 2>/dev/null"
|
|
|
|
|
" || 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";
|
|
|
|
|
QStringList files;
|
|
|
|
|
QDirIterator it(u"/sys/class/drm"_s, QDir::Dirs | QDir::NoDotAndDotDot);
|
|
|
|
|
while (it.hasNext()) {
|
|
|
|
|
const QString path = it.next();
|
|
|
|
|
if (!k_cardRe.match(it.fileName()).hasMatch()) continue;
|
|
|
|
|
const QString file = path + relative;
|
|
|
|
|
if (QFile::exists(file)) files << file;
|
|
|
|
|
}
|
|
|
|
|
return files;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::optional<qreal> readNumber(const QString& path) {
|
|
|
|
|
QFile f(path);
|
|
|
|
|
if (!f.open(QIODevice::ReadOnly | QIODevice::Text)) return std::nullopt;
|
|
|
|
|
bool ok = false;
|
|
|
|
|
const qreal v = f.readAll().trimmed().toDouble(&ok);
|
|
|
|
|
if (!ok) return std::nullopt;
|
|
|
|
|
return v;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QString cleanName(QString s) {
|
|
|
|
|
static const QRegularExpression k_noise(
|
|
|
|
|
u"\\(R\\)|\\(TM\\)|Graphics"_s,
|
|
|
|
|
QRegularExpression::CaseInsensitiveOption);
|
|
|
|
|
static const QRegularExpression k_spaces(u"\\s+"_s);
|
|
|
|
|
s.replace(k_noise, {});
|
|
|
|
|
s.replace(k_spaces, u" "_s);
|
|
|
|
|
return s.trimmed();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QString parseNvidiaName(const QByteArray& out) {
|
|
|
|
|
const QString first =
|
|
|
|
|
QString::fromUtf8(out).split(u'\n').value(0).trimmed();
|
|
|
|
|
return first.isEmpty() ? QString() : cleanName(first);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QString parseGlxinfoName(const QByteArray& out) {
|
|
|
|
|
const QStringList lines = QString::fromUtf8(out).split(u'\n');
|
|
|
|
|
for (const QString& line : lines) {
|
|
|
|
|
const qsizetype idx = line.indexOf(u"Device:"_s);
|
|
|
|
|
if (idx < 0) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QString rest = line.mid(idx + 7);
|
|
|
|
|
const qsizetype paren = rest.indexOf(u'(');
|
|
|
|
|
if (paren >= 0) {
|
|
|
|
|
rest = rest.left(paren);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const QString cleaned = cleanName(rest);
|
|
|
|
|
if (!cleaned.isEmpty()) {
|
|
|
|
|
return cleaned;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return {};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QString parseLspciName(const QByteArray& out) {
|
|
|
|
|
static const QRegularExpression k_lineRe(
|
|
|
|
|
u"vga|3d controller|display"_s,
|
|
|
|
|
QRegularExpression::CaseInsensitiveOption);
|
|
|
|
|
|
|
|
|
|
const QStringList lines = QString::fromUtf8(out).split(u'\n');
|
|
|
|
|
QString match;
|
|
|
|
|
for (const QString& line : lines) {
|
|
|
|
|
if (k_lineRe.match(line).hasMatch()) {
|
|
|
|
|
match = line;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (match.isEmpty()) {
|
|
|
|
|
return {};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static const QRegularExpression k_bracketRe(u"\\[([^\\]]+)\\][^\\[]*$"_s);
|
|
|
|
|
const auto bracket = k_bracketRe.match(match);
|
|
|
|
|
if (bracket.hasMatch()) {
|
|
|
|
|
return cleanName(bracket.captured(1));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static const QRegularExpression k_colonRe(u":\\s+(.+)"_s);
|
|
|
|
|
const auto colon = k_colonRe.match(match);
|
|
|
|
|
if (colon.hasMatch()) {
|
|
|
|
|
return cleanName(colon.captured(1));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return {};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
struct NameSource {
|
|
|
|
|
QString program;
|
|
|
|
|
QStringList args;
|
|
|
|
|
QString (*parse)(const QByteArray&);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const std::array<NameSource, 3>& nameSources() {
|
|
|
|
|
static const std::array<NameSource, 3> k_sources = {{
|
|
|
|
|
{
|
|
|
|
|
.program = u"nvidia-smi"_s,
|
|
|
|
|
.args = {u"--query-gpu=name"_s, u"--format=csv,noheader"_s},
|
|
|
|
|
.parse = &parseNvidiaName,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
.program = u"glxinfo"_s,
|
|
|
|
|
.args = {u"-B"_s},
|
|
|
|
|
.parse = &parseGlxinfoName,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
.program = u"lspci"_s,
|
|
|
|
|
.args = {},
|
|
|
|
|
.parse = &parseLspciName,
|
|
|
|
|
},
|
|
|
|
|
}};
|
|
|
|
|
return k_sources;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
constexpr int k_nvidiaSource = 0;
|
|
|
|
|
constexpr int k_firstGenericSource = 1;
|
|
|
|
|
|
|
|
|
|
} // namespace
|
|
|
|
|
|
|
|
|
|
Gpu::Gpu(QObject* parent) : TickingService(parent) {
|
|
|
|
|
QString configPath =
|
|
|
|
|
QDir::homePath() + QStringLiteral("/.config/zshell/config.json");
|
|
|
|
|
auto* svc = config::ConfigSingleton::instance()->services();
|
|
|
|
|
m_userType = svc->gpuType();
|
|
|
|
|
QObject::connect(svc, &config::Services::gpuTypeChanged, this, [this, svc] {
|
|
|
|
|
const GpuType value = svc->gpuType();
|
|
|
|
|
if (value == m_userType) return;
|
|
|
|
|
m_userType = value;
|
|
|
|
|
resolveGpu();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
auto reloadConfig = [this, configPath]() {
|
|
|
|
|
QFile file(configPath);
|
|
|
|
|
if (file.open(QIODevice::ReadOnly)) {
|
|
|
|
|
QJsonDocument doc = QJsonDocument::fromJson(file.readAll());
|
|
|
|
|
if (!doc.isNull()) {
|
|
|
|
|
QJsonObject services =
|
|
|
|
|
doc.object().value("services").toObject();
|
|
|
|
|
setUserType(parseType(services.value("gpuType").toString()));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
resolveGpu();
|
|
|
|
|
|
|
|
|
|
reloadConfig();
|
|
|
|
|
m_restartTimer = new QTimer(this);
|
|
|
|
|
m_restartTimer->setSingleShot(true);
|
|
|
|
|
m_restartTimer->setInterval(500);
|
|
|
|
|
QObject::connect(m_restartTimer, &QTimer::timeout, this, [this] {
|
|
|
|
|
if (!m_nvidiaProc || m_type != GpuType::Nvidia) return;
|
|
|
|
|
|
|
|
|
|
static QFileSystemWatcher* watcher = new QFileSystemWatcher();
|
|
|
|
|
if (!watcher->files().contains(configPath)) {
|
|
|
|
|
QObject::connect(
|
|
|
|
|
watcher,
|
|
|
|
|
&QFileSystemWatcher::fileChanged,
|
|
|
|
|
this,
|
|
|
|
|
[this, configPath]() {
|
|
|
|
|
QTimer::singleShot(100, this, [this, configPath]() {
|
|
|
|
|
QFile file(configPath);
|
|
|
|
|
if (file.open(QIODevice::ReadOnly)) {
|
|
|
|
|
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);
|
|
|
|
|
stopNvidiaUsage();
|
|
|
|
|
startNvidiaUsage();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
QObject::connect(this, &TickingService::updateIntervalChanged, this, [this] {
|
|
|
|
|
if (!m_nvidiaProc) return;
|
|
|
|
|
m_restartTimer->start();
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Gpu::~Gpu() {
|
|
|
|
|
if (!m_nvidiaProc) return;
|
|
|
|
|
m_nvidiaProc->disconnect(this);
|
|
|
|
|
m_nvidiaProc->terminate();
|
|
|
|
|
if (!m_nvidiaProc->waitForFinished(500)) {
|
|
|
|
|
m_nvidiaProc->kill();
|
|
|
|
|
m_nvidiaProc->waitForFinished(200);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Detection must run before any ServiceRef appears: callers may gate the ref on
|
|
|
|
|
// `type !== Gpu.None`, which would otherwise deadlock the detection.
|
|
|
|
|
if (m_userType == Auto) {
|
|
|
|
|
detectTypeOnce();
|
|
|
|
|
}
|
|
|
|
|
detectNameOnce();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Gpu::Type Gpu::type() const {
|
|
|
|
|
return m_userType == Auto ? m_autoType : m_userType;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Gpu::Type Gpu::userType() const {
|
|
|
|
|
return m_userType;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Gpu::Type Gpu::autoType() const {
|
|
|
|
|
return m_autoType;
|
|
|
|
|
GpuType Gpu::type() const {
|
|
|
|
|
return m_type;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QString Gpu::name() const {
|
|
|
|
@@ -115,28 +205,13 @@ qreal Gpu::memoryTotal() const {
|
|
|
|
|
return m_memoryTotal;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Gpu::setUserType(Type value) {
|
|
|
|
|
if (value == m_userType) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
const Type prevDerived = type();
|
|
|
|
|
m_userType = value;
|
|
|
|
|
Q_EMIT userTypeChanged();
|
|
|
|
|
if (type() != prevDerived) {
|
|
|
|
|
Q_EMIT typeChanged();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
void Gpu::setType(GpuType value) {
|
|
|
|
|
if (value == m_type) return;
|
|
|
|
|
|
|
|
|
|
void Gpu::setAutoType(Type value) {
|
|
|
|
|
if (value == m_userType) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
const Type prevDerived = type();
|
|
|
|
|
m_autoType = value;
|
|
|
|
|
Q_EMIT autoTypeChanged();
|
|
|
|
|
if (type() != prevDerived) {
|
|
|
|
|
Q_EMIT typeChanged();
|
|
|
|
|
}
|
|
|
|
|
m_type = value;
|
|
|
|
|
if (m_type != GpuType::Nvidia) stopNvidiaUsage();
|
|
|
|
|
if (m_type == GpuType::None) resetUsage();
|
|
|
|
|
emit typeChanged();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Gpu::setName(QString value) {
|
|
|
|
@@ -147,6 +222,12 @@ void Gpu::setName(QString value) {
|
|
|
|
|
Q_EMIT nameChanged();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Gpu::setDetecting(bool value) {
|
|
|
|
|
if (value == m_detecting) return;
|
|
|
|
|
m_detecting = value;
|
|
|
|
|
emit detectingChanged();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Gpu::setMemoryUsed(qreal value) {
|
|
|
|
|
if (std::abs(m_memoryUsed - value) < 0.001) return;
|
|
|
|
|
m_memoryUsed = value;
|
|
|
|
@@ -160,168 +241,192 @@ void Gpu::setMemoryTotal(qreal value) {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Gpu::tick() {
|
|
|
|
|
const Type t = type();
|
|
|
|
|
if (t == Generic) {
|
|
|
|
|
if (m_type == GpuType::Generic) {
|
|
|
|
|
readGenericUsage();
|
|
|
|
|
readGenericMemory();
|
|
|
|
|
readGpuTemperature();
|
|
|
|
|
} else if (t == Nvidia) {
|
|
|
|
|
} else if (m_type == GpuType::Nvidia) {
|
|
|
|
|
startNvidiaUsage();
|
|
|
|
|
} else {
|
|
|
|
|
if (std::abs(m_percentage) > 0.0001) {
|
|
|
|
|
m_percentage = 0.0;
|
|
|
|
|
Q_EMIT percentageChanged();
|
|
|
|
|
}
|
|
|
|
|
if (std::abs(m_temperature) > 0.05) {
|
|
|
|
|
m_temperature = 0.0;
|
|
|
|
|
Q_EMIT temperatureChanged();
|
|
|
|
|
}
|
|
|
|
|
resetUsage();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Gpu::detectTypeOnce() {
|
|
|
|
|
if (m_typeProc) {
|
|
|
|
|
return;
|
|
|
|
|
void Gpu::refreshSysfsCache() {
|
|
|
|
|
m_busyFiles = cardFiles(u"/device/gpu_busy_percent"_s);
|
|
|
|
|
|
|
|
|
|
m_vramFiles.clear();
|
|
|
|
|
const QStringList totals = cardFiles(u"/device/mem_info_vram_total"_s);
|
|
|
|
|
for (const QString& total : totals) {
|
|
|
|
|
QString used = total;
|
|
|
|
|
used.replace(u"mem_info_vram_total"_s, u"mem_info_vram_used"_s);
|
|
|
|
|
if (QFile::exists(used)) m_vramFiles.append({used, total});
|
|
|
|
|
}
|
|
|
|
|
m_typeProc = new QProcess(this);
|
|
|
|
|
QObject::connect(
|
|
|
|
|
m_typeProc,
|
|
|
|
|
&QProcess::finished,
|
|
|
|
|
this,
|
|
|
|
|
[this](int, QProcess::ExitStatus) {
|
|
|
|
|
const QByteArray out =
|
|
|
|
|
m_typeProc->readAllStandardOutput().trimmed();
|
|
|
|
|
if (!out.isEmpty()) {
|
|
|
|
|
setAutoType(parseType(QString::fromLatin1(out)));
|
|
|
|
|
}
|
|
|
|
|
m_typeProc->deleteLater();
|
|
|
|
|
m_typeProc = nullptr;
|
|
|
|
|
});
|
|
|
|
|
m_typeProc->start(
|
|
|
|
|
QStringLiteral("sh"),
|
|
|
|
|
{QStringLiteral("-c"), QString::fromLatin1(kTypeDetectScript)});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Gpu::detectNameOnce() {
|
|
|
|
|
if (m_nameProc) {
|
|
|
|
|
void Gpu::resolveGpu() {
|
|
|
|
|
refreshSysfsCache();
|
|
|
|
|
const int generation = ++m_generation;
|
|
|
|
|
|
|
|
|
|
if (m_userType != GpuType::Auto) setType(m_userType);
|
|
|
|
|
|
|
|
|
|
if (m_userType == GpuType::None) {
|
|
|
|
|
setName({});
|
|
|
|
|
setDetecting(false);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
m_nameProc = new QProcess(this);
|
|
|
|
|
|
|
|
|
|
setName({});
|
|
|
|
|
setDetecting(true);
|
|
|
|
|
tryNameSource(
|
|
|
|
|
m_userType == GpuType::Generic ? k_firstGenericSource : k_nvidiaSource,
|
|
|
|
|
generation);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int Gpu::probeEnd() const {
|
|
|
|
|
return m_userType == GpuType::Nvidia
|
|
|
|
|
? k_firstGenericSource
|
|
|
|
|
: static_cast<int>(nameSources().size());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Gpu::tryNameSource(int index, int generation) {
|
|
|
|
|
const NameSource& src = nameSources().at(static_cast<std::size_t>(index));
|
|
|
|
|
runProcess(
|
|
|
|
|
src.program,
|
|
|
|
|
src.args,
|
|
|
|
|
[this, index, generation, parse = src.parse](const QByteArray& out) {
|
|
|
|
|
finishNameSource(index, generation, parse(out));
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Gpu::finishNameSource(int index, int generation, QString name) {
|
|
|
|
|
if (generation != m_generation) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (m_userType == GpuType::Auto && index == k_nvidiaSource) {
|
|
|
|
|
if (!name.isEmpty())
|
|
|
|
|
setType(GpuType::Nvidia);
|
|
|
|
|
else
|
|
|
|
|
setType(m_busyFiles.isEmpty() ? GpuType::None : GpuType::Generic);
|
|
|
|
|
|
|
|
|
|
if (m_type == GpuType::None) {
|
|
|
|
|
setName({});
|
|
|
|
|
setDetecting(false);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!name.isEmpty()) {
|
|
|
|
|
setName(std::move(name));
|
|
|
|
|
setDetecting(false);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const int next = index + 1;
|
|
|
|
|
if (next < probeEnd()) {
|
|
|
|
|
tryNameSource(next, generation);
|
|
|
|
|
} else {
|
|
|
|
|
setName({});
|
|
|
|
|
setDetecting(false);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Gpu::runProcess(
|
|
|
|
|
const QString& program,
|
|
|
|
|
const QStringList& args,
|
|
|
|
|
std::function<void(const QByteArray&)> callback) {
|
|
|
|
|
auto* proc = new QProcess(this);
|
|
|
|
|
proc->setStandardErrorFile(QProcess::nullDevice());
|
|
|
|
|
|
|
|
|
|
const auto finish =
|
|
|
|
|
[proc, callback = std::move(callback)](const QByteArray& out) {
|
|
|
|
|
callback(out);
|
|
|
|
|
proc->deleteLater();
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
QObject::connect(
|
|
|
|
|
m_nameProc,
|
|
|
|
|
proc,
|
|
|
|
|
&QProcess::finished,
|
|
|
|
|
this,
|
|
|
|
|
[this](int, QProcess::ExitStatus) {
|
|
|
|
|
const QString output =
|
|
|
|
|
QString::fromUtf8(m_nameProc->readAllStandardOutput()).trimmed();
|
|
|
|
|
if (!output.isEmpty()) {
|
|
|
|
|
const QString lower = output.toLower();
|
|
|
|
|
if (lower.contains(QStringLiteral("nvidia")) ||
|
|
|
|
|
lower.contains(QStringLiteral("geforce")) ||
|
|
|
|
|
lower.contains(QStringLiteral("rtx")) ||
|
|
|
|
|
lower.contains(QStringLiteral("gtx")) ||
|
|
|
|
|
lower.contains(QStringLiteral("rx"))) {
|
|
|
|
|
setName(cleanName(output));
|
|
|
|
|
} else {
|
|
|
|
|
static const QRegularExpression bracketRe(
|
|
|
|
|
QStringLiteral("\\[([^\\]]+)\\][^\\[]*$"));
|
|
|
|
|
const auto bracket = bracketRe.match(output);
|
|
|
|
|
if (bracket.hasMatch()) {
|
|
|
|
|
setName(cleanName(bracket.captured(1)));
|
|
|
|
|
} else {
|
|
|
|
|
static const QRegularExpression colonRe(
|
|
|
|
|
QStringLiteral(":\\s*(.+)"));
|
|
|
|
|
const auto colon = colonRe.match(output);
|
|
|
|
|
if (colon.hasMatch()) {
|
|
|
|
|
setName(cleanName(colon.captured(1)));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
m_nameProc->deleteLater();
|
|
|
|
|
m_nameProc = nullptr;
|
|
|
|
|
[finish, proc](int code, QProcess::ExitStatus status) {
|
|
|
|
|
const bool ok = status == QProcess::NormalExit && code == 0;
|
|
|
|
|
finish(ok ? proc->readAllStandardOutput() : QByteArray());
|
|
|
|
|
});
|
|
|
|
|
m_nameProc->start(
|
|
|
|
|
QStringLiteral("sh"),
|
|
|
|
|
{QStringLiteral("-c"), QString::fromLatin1(kNameDetectScript)});
|
|
|
|
|
QObject::connect(
|
|
|
|
|
proc,
|
|
|
|
|
&QProcess::errorOccurred,
|
|
|
|
|
this,
|
|
|
|
|
[finish](QProcess::ProcessError err) {
|
|
|
|
|
if (err == QProcess::FailedToStart) {
|
|
|
|
|
finish(QByteArray());
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
proc->start(program, args);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Gpu::onStopped() {
|
|
|
|
|
stopNvidiaUsage();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Gpu::readGenericMemory() {
|
|
|
|
|
const QStringList paths = QDir(QStringLiteral("/sys/class/drm"))
|
|
|
|
|
.entryList(
|
|
|
|
|
QStringList() << QStringLiteral("card*"),
|
|
|
|
|
QDir::Dirs | QDir::NoDotAndDotDot);
|
|
|
|
|
|
|
|
|
|
qreal totalMem = 0.0;
|
|
|
|
|
qreal usedMem = 0.0;
|
|
|
|
|
for (const QString& card : paths) {
|
|
|
|
|
QFile total(
|
|
|
|
|
QStringLiteral("/sys/class/drm/%1/device/mem_info_vram_total")
|
|
|
|
|
.arg(card));
|
|
|
|
|
if (!total.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
bool ok = false;
|
|
|
|
|
const qreal v = total.readAll().trimmed().toDouble(&ok);
|
|
|
|
|
total.close();
|
|
|
|
|
if (ok) {
|
|
|
|
|
totalMem += v;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QFile used(QStringLiteral("/sys/class/drm/%1/device/mem_info_vram_used")
|
|
|
|
|
.arg(card));
|
|
|
|
|
if (!used.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
bool ok1 = false;
|
|
|
|
|
const qreal v1 = used.readAll().trimmed().toDouble(&ok1);
|
|
|
|
|
used.close();
|
|
|
|
|
if (ok1) {
|
|
|
|
|
usedMem += v1;
|
|
|
|
|
}
|
|
|
|
|
qreal total = 0.0;
|
|
|
|
|
qreal used = 0.0;
|
|
|
|
|
for (const VramFiles& v : std::as_const(m_vramFiles)) {
|
|
|
|
|
const auto t = readNumber(v.total);
|
|
|
|
|
const auto u = readNumber(v.used);
|
|
|
|
|
if (!t || !u) continue;
|
|
|
|
|
total += *t;
|
|
|
|
|
used += *u;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
setMemoryTotal(totalMem);
|
|
|
|
|
setMemoryUsed(usedMem);
|
|
|
|
|
setMemoryTotal(total);
|
|
|
|
|
setMemoryUsed(used);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Gpu::readGenericUsage() {
|
|
|
|
|
const QStringList paths = QDir(QStringLiteral("/sys/class/drm"))
|
|
|
|
|
.entryList(
|
|
|
|
|
QStringList() << QStringLiteral("card*"),
|
|
|
|
|
QDir::Dirs | QDir::NoDotAndDotDot);
|
|
|
|
|
qreal sum = 0.0;
|
|
|
|
|
int count = 0;
|
|
|
|
|
for (const QString& card : paths) {
|
|
|
|
|
QFile f(QStringLiteral("/sys/class/drm/%1/device/gpu_busy_percent")
|
|
|
|
|
.arg(card));
|
|
|
|
|
if (!f.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
bool ok = false;
|
|
|
|
|
const qreal v = f.readAll().trimmed().toDouble(&ok);
|
|
|
|
|
f.close();
|
|
|
|
|
if (ok) {
|
|
|
|
|
sum += v;
|
|
|
|
|
for (const QString& path : std::as_const(m_busyFiles)) {
|
|
|
|
|
if (const auto v = readNumber(path)) {
|
|
|
|
|
sum += *v;
|
|
|
|
|
++count;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
const qreal newPerc = count > 0 ? sum / count / 100.0 : 0.0;
|
|
|
|
|
if (std::abs(newPerc - m_percentage) > 0.0001) {
|
|
|
|
|
m_percentage = newPerc;
|
|
|
|
|
Q_EMIT percentageChanged();
|
|
|
|
|
emit percentageChanged();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Gpu::startNvidiaUsage() {
|
|
|
|
|
if (m_nvidiaProc) {
|
|
|
|
|
void Gpu::stopNvidiaUsage() {
|
|
|
|
|
m_restartTimer->stop();
|
|
|
|
|
|
|
|
|
|
if (!m_nvidiaProc) return;
|
|
|
|
|
|
|
|
|
|
QProcess* proc = m_nvidiaProc;
|
|
|
|
|
m_nvidiaProc = nullptr;
|
|
|
|
|
|
|
|
|
|
proc->disconnect(this);
|
|
|
|
|
if (proc->state() == QProcess::NotRunning) {
|
|
|
|
|
proc->deleteLater();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
m_nvidiaProc = new QProcess(this);
|
|
|
|
|
QObject::connect(proc, &QProcess::finished, proc, &QObject::deleteLater);
|
|
|
|
|
proc->terminate();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Gpu::startNvidiaUsage() {
|
|
|
|
|
if (m_nvidiaProc) return;
|
|
|
|
|
|
|
|
|
|
auto* proc = new QProcess(this);
|
|
|
|
|
m_nvidiaProc = proc;
|
|
|
|
|
|
|
|
|
|
QObject::connect(
|
|
|
|
|
m_nvidiaProc, &QProcess::readyReadStandardOutput, this, [this]() {
|
|
|
|
|
while (m_nvidiaProc->canReadLine()) {
|
|
|
|
|
const QByteArray out = m_nvidiaProc->readLine();
|
|
|
|
|
proc, &QProcess::readyReadStandardOutput, this, [this, proc]() {
|
|
|
|
|
while (proc->canReadLine()) {
|
|
|
|
|
const QByteArray out = proc->readLine();
|
|
|
|
|
const QString output = QString::fromUtf8(out).trimmed();
|
|
|
|
|
if (output.isEmpty()) continue;
|
|
|
|
|
|
|
|
|
@@ -357,21 +462,21 @@ void Gpu::startNvidiaUsage() {
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
QObject::connect(m_nvidiaProc, &QProcess::finished, this, [this]() {
|
|
|
|
|
m_nvidiaProc->deleteLater();
|
|
|
|
|
m_nvidiaProc = nullptr;
|
|
|
|
|
});
|
|
|
|
|
auto cleanup = [this, proc]() {
|
|
|
|
|
if (m_nvidiaProc == proc) m_nvidiaProc = nullptr;
|
|
|
|
|
|
|
|
|
|
proc->deleteLater();
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
QObject::connect(proc, &QProcess::finished, this, cleanup);
|
|
|
|
|
|
|
|
|
|
QObject::connect(
|
|
|
|
|
m_nvidiaProc,
|
|
|
|
|
proc,
|
|
|
|
|
&QProcess::errorOccurred,
|
|
|
|
|
this,
|
|
|
|
|
[this](QProcess::ProcessError) {
|
|
|
|
|
m_nvidiaProc->deleteLater();
|
|
|
|
|
m_nvidiaProc = nullptr;
|
|
|
|
|
});
|
|
|
|
|
[cleanup](QProcess::ProcessError) { cleanup(); });
|
|
|
|
|
|
|
|
|
|
m_nvidiaProc->start(
|
|
|
|
|
proc->start(
|
|
|
|
|
QStringLiteral("nvidia-smi"),
|
|
|
|
|
{QStringLiteral(
|
|
|
|
|
"--query-gpu=utilization.gpu,temperature.gpu,"
|
|
|
|
@@ -386,32 +491,21 @@ void Gpu::readGpuTemperature() {
|
|
|
|
|
const qreal newTemp = t.value_or(0.0);
|
|
|
|
|
if (std::abs(newTemp - m_temperature) > 0.05) {
|
|
|
|
|
m_temperature = newTemp;
|
|
|
|
|
Q_EMIT temperatureChanged();
|
|
|
|
|
emit temperatureChanged();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Gpu::Type Gpu::parseType(const QString& s) {
|
|
|
|
|
const QString u = s.trimmed().toUpper();
|
|
|
|
|
if (u.isEmpty()) {
|
|
|
|
|
return Auto;
|
|
|
|
|
void Gpu::resetUsage() {
|
|
|
|
|
if (std::abs(m_percentage) > 0.0001) {
|
|
|
|
|
m_percentage = 0.0;
|
|
|
|
|
emit percentageChanged();
|
|
|
|
|
}
|
|
|
|
|
if (u == QStringLiteral("NVIDIA")) {
|
|
|
|
|
return Nvidia;
|
|
|
|
|
if (std::abs(m_temperature) > 0.05) {
|
|
|
|
|
m_temperature = 0.0;
|
|
|
|
|
emit temperatureChanged();
|
|
|
|
|
}
|
|
|
|
|
if (u == QStringLiteral("GENERIC")) {
|
|
|
|
|
return Generic;
|
|
|
|
|
}
|
|
|
|
|
return None;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QString Gpu::cleanName(QString s) {
|
|
|
|
|
static const QRegularExpression noise(
|
|
|
|
|
QStringLiteral("\\(R\\)|\\(TM\\)|Graphics"),
|
|
|
|
|
QRegularExpression::CaseInsensitiveOption);
|
|
|
|
|
static const QRegularExpression spaces(QStringLiteral("\\s+"));
|
|
|
|
|
s.replace(noise, QString());
|
|
|
|
|
s.replace(spaces, QStringLiteral(" "));
|
|
|
|
|
return s.trimmed();
|
|
|
|
|
setMemoryUsed(0.0);
|
|
|
|
|
setMemoryTotal(0.0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
} // namespace ZShell::services
|
|
|
|
|