diff options
author | Robert Griebl <robert.griebl@pelagicore.com> | 2018-03-20 18:06:37 +0100 |
---|---|---|
committer | Dominik Holland <dominik.holland@pelagicore.com> | 2018-03-27 15:27:46 +0000 |
commit | 1b892e8653965d13cc7b30101b414585dc70143e (patch) | |
tree | 4afdc1fbcd80fd4217ffa779c7222da680775807 | |
parent | d47e6e66df5b7b457319572334c981d38b6e32a6 (diff) | |
download | qtapplicationmanager-1b892e8653965d13cc7b30101b414585dc70143e.tar.gz |
Add GPU utilization to the system-monitor
Currently, this only works on Linux with Intel or Nvidia chipsets, plus the
tools from the respective vendors have to be installed:
* nvidia-smi for Nvidia
* intel_gpu_top for Intel. In addition, the binary has to be made SUID root,
e.g. via sudo chmod +s $(which intel_gpu_top)
Change-Id: Ic82888eba26d740074822a9c4bdea9a35772648c
Reviewed-by: Dominik Holland <dominik.holland@pelagicore.com>
-rw-r--r-- | examples/monitor/system-ui/main.qml | 2 | ||||
-rw-r--r-- | src/manager-lib/systemreader.cpp | 192 | ||||
-rw-r--r-- | src/manager-lib/systemreader.h | 18 | ||||
-rw-r--r-- | src/monitor-lib/systemmonitor.cpp | 104 | ||||
-rw-r--r-- | src/monitor-lib/systemmonitor.h | 8 |
5 files changed, 323 insertions, 1 deletions
diff --git a/examples/monitor/system-ui/main.qml b/examples/monitor/system-ui/main.qml index a3ddb658..5343a55d 100644 --- a/examples/monitor/system-ui/main.qml +++ b/examples/monitor/system-ui/main.qml @@ -80,6 +80,7 @@ Window { MonitorText { id: systemMem; text: "Used Memory:" } MonitorText { text: "Idle Threshold: " + SystemMonitor.idleLoadThreshold * 100 + " %" } MonitorText { text: "Idle: " + SystemMonitor.idle } + MonitorText { text: "GPU Load: " + SystemMonitor.gpuLoad * 100 + " %" } } } @@ -258,6 +259,7 @@ Window { SystemMonitor.idleLoadThreshold = 0.05; SystemMonitor.cpuLoadReportingEnabled = true; + SystemMonitor.gpuLoadReportingEnabled = true; SystemMonitor.memoryReportingEnabled = true; ApplicationManager.startApplication(ApplicationManager.application(0).id); diff --git a/src/manager-lib/systemreader.cpp b/src/manager-lib/systemreader.cpp index dc6b756d..708a5362 100644 --- a/src/manager-lib/systemreader.cpp +++ b/src/manager-lib/systemreader.cpp @@ -63,6 +63,14 @@ QT_END_NAMESPACE_AM # include <qplatformdefs.h> # include <QElapsedTimer> # include <QSocketNotifier> +# include <QProcess> +# include <QCoreApplication> +# if !defined(AM_HEADLESS) +# include <QOffscreenSurface> +# include <QOpenGLContext> +# include <QOpenGLFunctions> +# include <QAtomicInteger> +# endif # include <sys/eventfd.h> # include <fcntl.h> @@ -140,6 +148,174 @@ qreal CpuReader::readLoadValue() return m_load; } +class GpuTool : protected QProcess +{ +public: + GpuTool() + : QProcess(qApp) + { + QByteArray vendor; +# if !defined(AM_HEADLESS) + auto readVendor = [&vendor](QOpenGLContext *c) { + const GLubyte *p = c->functions()->glGetString(GL_VENDOR); + if (p) + vendor = QByteArray(reinterpret_cast<const char *>(p)).toLower(); + }; + + if (QOpenGLContext::currentContext()) { + readVendor(QOpenGLContext::currentContext()); + } else { + QOpenGLContext c; + c.create(); + QOffscreenSurface s; + s.create(); + c.makeCurrent(&s); + + readVendor(&c); + } +# endif + if (vendor.contains("intel")) { + setProgram(qSL("intel_gpu_top")); + setArguments({ qSL("-o-"), qSL("-s 1000") }); + m_vendor = Intel; + } else if (vendor.contains("nvidia")) { + setProgram(qSL("nvidia-smi")); + setArguments({ qSL("dmon"), qSL("--select"), qSL("u") }); + m_vendor = Nvidia; + } + + QObject::connect(this, static_cast<void(QProcess::*)(int)>(&QProcess::finished), this, [this](int exitCode) { + if (m_refCount) { + qCWarning(LogSystem) << "Failed to run GPU monitoring tool:" + << program() << arguments().join(qSL(" ")) << " - " + << "exited with code:" << exitCode; + } + }); + + QObject::connect(this, &QProcess::readyReadStandardOutput, this, [this]() { + while (canReadLine()) { + const QByteArray str = readLine(); + if (str.isEmpty() || (str.at(0) == '#')) + continue; + + int pos = 0; + QVector<qreal> values; + + while (pos < str.size() && values.size() < 2) { + if (isspace(str.at(pos))) { + ++pos; + continue; + } + char *endPtr = nullptr; +#if defined(Q_OS_ANDROID) + qreal val = strtod(str.constData() + pos, &endPtr); // check missing for over-/underflow +#else + static locale_t cLocale = newlocale(LC_ALL_MASK, "C", NULL); + qreal val = strtod_l(str.constData() + pos, &endPtr, cLocale); // check missing for over-/underflow +#endif + values << val; + pos = endPtr - str.constData() + 1; + } + + switch (m_vendor) { + case Intel: + if (values.size() >= 2) + m_lastValue = values.at(1) / 100; + break; + case Nvidia: + if (values.size() >= 2) { + if (values.at(0) == 0) // hardcoded to first gfx card + m_lastValue = values.at(1) / 100; + } + break; + default: + m_lastValue = -1; + break; + } + } + }); + } + + void ref() + { + if (m_refCount.ref()) { + if (!isRunning()) { + start(QIODevice::ReadOnly); + if (!waitForStarted(2000)) { + qCWarning(LogSystem) << "Could not start GPU monitoring tool:" + << program() << arguments().join(qSL(" ")) << " - " + << errorString(); + } + } + } + } + + void deref() + { + if (!m_refCount.deref()) { + if (isRunning()) { + kill(); + waitForFinished(); + } + } + } + + bool isRunning() const + { + return (state() == QProcess::Running); + } + + bool isSupported() const + { + return m_vendor != Unsupported; + } + + qreal loadValue() const + { + return m_lastValue; + } + +private: + QAtomicInteger<int> m_refCount; + qreal m_lastValue = 0; + + enum Vendor { + Unsupported = 0, + Intel, + Nvidia + }; + Vendor m_vendor = Unsupported; +}; + +GpuTool *GpuReader::s_gpuToolProcess = nullptr; + +GpuReader::GpuReader() +{ } + +void GpuReader::setActive(bool enabled) +{ + if (!s_gpuToolProcess) + s_gpuToolProcess = new GpuTool(); + + if (!s_gpuToolProcess->isSupported()) { + qCWarning(LogSystem) << "GPU monitoring is not supported on this platform."; + } else { + if (enabled) + s_gpuToolProcess->ref(); + else + s_gpuToolProcess->deref(); + } +} + +bool GpuReader::isActive() const +{ + return s_gpuToolProcess ? s_gpuToolProcess->isRunning() : false; +} + +qreal GpuReader::readLoadValue() +{ + return s_gpuToolProcess ? s_gpuToolProcess->loadValue() : -1; +} // TODO: can we always expect cgroup FS to be mounted on /sys/fs/cgroup? static const QString cGroupsMemoryBaseDir = qSL("/sys/fs/cgroup/memory/"); @@ -556,6 +732,22 @@ QT_END_NAMESPACE_AM QT_BEGIN_NAMESPACE_AM +GpuReader::GpuReader() +{ } + +void GpuReader::setActive(bool /*enable*/) +{ } + +bool GpuReader::isActive() const +{ + return false; +} + +qreal GpuReader::readLoadValue() +{ + return 0; +} + IoReader::IoReader(const char *device) { Q_UNUSED(device) diff --git a/src/manager-lib/systemreader.h b/src/manager-lib/systemreader.h index 669857b5..21a0caf9 100644 --- a/src/manager-lib/systemreader.h +++ b/src/manager-lib/systemreader.h @@ -71,6 +71,24 @@ private: Q_DISABLE_COPY(CpuReader) }; +class GpuTool; + +class GpuReader +{ +public: + GpuReader(); + void setActive(bool enabled); + bool isActive() const; + qreal readLoadValue(); + +private: +#if defined(Q_OS_LINUX) + static GpuTool *s_gpuToolProcess; +#endif + Q_DISABLE_COPY(GpuReader) +}; + + class MemoryReader { public: diff --git a/src/monitor-lib/systemmonitor.cpp b/src/monitor-lib/systemmonitor.cpp index 3080c458..06fc0650 100644 --- a/src/monitor-lib/systemmonitor.cpp +++ b/src/monitor-lib/systemmonitor.cpp @@ -86,6 +86,12 @@ \li real \li The current CPU utilization in the range 0 (completely idle) to 1 (fully busy). \row + \li \c gpuLoad + \li real + \li The current GPU utilization in the range 0 (completely idle) to 1 (fully busy). + This is dependent on tools from the graphics hardware vendor and might not work on + every system. + \row \li \c memoryUsed \li int \li The amount of physical system memory used in bytes. @@ -198,6 +204,37 @@ */ /*! + \qmlproperty int SystemMonitor::gpuLoad + \readonly + + This property holds the current GPU utilization as a value ranging from 0 (inclusive, completely + idle) to 1 (inclusive, fully busy). + + \note This is dependent on tools from the graphics hardware vendor and might not work on + every system. + + Currently, this only works on \e Linux with either \e Intel or \e NVIDIA chipsets, plus the + tools from the respective vendors have to be installed: + + \table + \header + \li Hardware + \li Tool + \li Notes + \row + \li NVIDIA + \li \c nvidia-smi + \li The utilization will only be shown for the first GPU of the system, in case multiple GPUs + are installed. + \row + \li Intel + \li \c intel_gpu_top + \li The binary has to be made set-UID root, e.g. via \c{sudo chmod +s $(which intel_gpu_top)}, + or the application-manager has to be run as the \c root user. + \endtable +*/ + +/*! \qmlproperty bool SystemMonitor::memoryReportingEnabled A boolean value that determines whether periodic memory reporting is enabled. @@ -210,6 +247,15 @@ */ /*! + \qmlproperty bool SystemMonitor::gpuLoadReportingEnabled + + A boolean value that determines whether periodic GPU load reporting is enabled. + + GPU load reporting is only supported on selected hardware: please see gpuLoad for more + information. +*/ + +/*! \qmlproperty bool SystemMonitor::fpsReportingEnabled A boolean value that determines whether periodic frame rate reporting is enabled. @@ -250,6 +296,17 @@ */ /*! + \qmlsignal SystemMonitor::gpuLoadReportingChanged(real load) + + This signal is emitted periodically when GPU load reporting is enabled. The frequency is + defined by \l reportingInterval. The \a load parameter indicates the GPU utilization in the + range 0 (completely idle) to 1 (fully busy). + + \sa gpuLoadReportingEnabled + \sa reportingInterval +*/ + +/*! \qmlsignal SystemMonitor::ioLoadReportingChanged(string device, real load); This signal is emitted periodically for each I/O device that has been registered with @@ -281,6 +338,7 @@ enum Roles CpuLoad = Qt::UserRole + 5000, MemoryUsed, IoLoad, + GpuLoad, AverageFps = Qt::UserRole + 6000, MinimumFps, @@ -316,6 +374,7 @@ public: // reporting MemoryReader *memory = nullptr; CpuReader *cpu = nullptr; + GpuReader *gpu = nullptr; QHash<QString, IoReader *> ioHash; int reportingInterval = -1; int count = 10; @@ -323,9 +382,11 @@ public: bool reportingRangeSet = false; int reportingTimerId = 0; bool reportCpu = false; + bool reportGpu = false; bool reportMem = false; bool reportFps = false; int cpuTail = 0; + int gpuTail = 0; int memTail = 0; int fpsTail = 0; QMap<QString, int> ioTails; @@ -334,6 +395,7 @@ public: struct Report { qreal cpuLoad = 0; + qreal gpuLoad = 0; qreal fpsAvg = 0; qreal fpsMin = 0; qreal fpsMax = 0; @@ -385,7 +447,7 @@ public: void setupTimer(int newInterval = -1) { bool useNewInterval = (newInterval != -1) && (newInterval != reportingInterval); - bool shouldBeOn = reportCpu || reportMem || reportFps || !ioHash.isEmpty() + bool shouldBeOn = reportCpu || reportGpu || reportMem || reportFps || !ioHash.isEmpty() || cpuTail > 0 || memTail > 0 || fpsTail > 0 || !ioTails.isEmpty(); if (useNewInterval) @@ -424,6 +486,14 @@ public: roles.append(CpuLoad); } + if (reportGpu) { + r.gpuLoad = gpu->readLoadValue(); + roles.append(GpuLoad); + } else if (gpuTail > 0) { + --gpuTail; + roles.append(GpuLoad); + } + if (reportMem) { r.memoryUsed = memory->readUsedValue(); roles.append(MemoryUsed); @@ -485,6 +555,8 @@ public: emit q->memoryReportingChanged(r.memoryUsed); if (reportCpu) emit q->cpuLoadReportingChanged(r.cpuLoad); + if (reportGpu) + emit q->gpuLoadReportingChanged(r.gpuLoad); setupTimer(); // we might be able to stop this timer, when end of tail reached @@ -591,6 +663,7 @@ SystemMonitor::SystemMonitor() d->idleCpu = new CpuReader; d->cpu = new CpuReader; + d->gpu = new GpuReader; d->memory = new MemoryReader; d->idleTimerId = d->startTimer(1000); @@ -613,6 +686,7 @@ SystemMonitor::~SystemMonitor() delete d->idleCpu; delete d->memory; delete d->cpu; + delete d->gpu; qDeleteAll(d->ioHash); delete d; } @@ -707,6 +781,12 @@ qreal SystemMonitor::cpuLoad() const return d->reports[d->latestReportPos()].cpuLoad; } +qreal SystemMonitor::gpuLoad() const +{ + Q_D(const SystemMonitor); + return d->reports[d->latestReportPos()].gpuLoad; +} + /*! \qmlmethod bool SystemMonitor::setMemoryWarningThresholds(real lowWarning, real criticalWarning); @@ -841,6 +921,28 @@ bool SystemMonitor::isCpuLoadReportingEnabled() const return d->reportCpu; } +void SystemMonitor::setGpuLoadReportingEnabled(bool enabled) +{ + Q_D(SystemMonitor); + + if (enabled != d->reportGpu) { + d->reportGpu = enabled; + if (!enabled) + d->gpuTail = d->count; + else + d->setupTimer(); + d->gpu->setActive(enabled); + emit gpuLoadReportingEnabledChanged(); + } +} + +bool SystemMonitor::isGpuLoadReportingEnabled() const +{ + Q_D(const SystemMonitor); + + return d->reportGpu; +} + /*! \qmlmethod bool SystemMonitor::addIoLoadReporting(string deviceName); diff --git a/src/monitor-lib/systemmonitor.h b/src/monitor-lib/systemmonitor.h index 2d002ff3..a043a469 100644 --- a/src/monitor-lib/systemmonitor.h +++ b/src/monitor-lib/systemmonitor.h @@ -63,8 +63,10 @@ class SystemMonitor : public QAbstractListModel Q_PROPERTY(quint64 memoryUsed READ memoryUsed NOTIFY memoryReportingChanged) Q_PROPERTY(int cpuCores READ cpuCores CONSTANT) Q_PROPERTY(qreal cpuLoad READ cpuLoad NOTIFY cpuLoadReportingChanged) + Q_PROPERTY(qreal gpuLoad READ gpuLoad NOTIFY gpuLoadReportingChanged) Q_PROPERTY(bool memoryReportingEnabled READ isMemoryReportingEnabled WRITE setMemoryReportingEnabled NOTIFY memoryReportingEnabledChanged) Q_PROPERTY(bool cpuLoadReportingEnabled READ isCpuLoadReportingEnabled WRITE setCpuLoadReportingEnabled NOTIFY cpuLoadReportingEnabledChanged) + Q_PROPERTY(bool gpuLoadReportingEnabled READ isGpuLoadReportingEnabled WRITE setGpuLoadReportingEnabled NOTIFY gpuLoadReportingEnabledChanged) Q_PROPERTY(bool fpsReportingEnabled READ isFpsReportingEnabled WRITE setFpsReportingEnabled NOTIFY fpsReportingEnabledChanged) Q_PROPERTY(bool idle READ isIdle NOTIFY idleChanged) @@ -88,6 +90,7 @@ public: quint64 memoryUsed() const; int cpuCores() const; qreal cpuLoad() const; + qreal gpuLoad() const; void setIdleLoadThreshold(qreal loadThreshold); qreal idleLoadThreshold() const; @@ -104,6 +107,9 @@ public: void setCpuLoadReportingEnabled(bool enabled); bool isCpuLoadReportingEnabled() const; + void setGpuLoadReportingEnabled(bool enabled); + bool isGpuLoadReportingEnabled() const; + Q_INVOKABLE bool addIoLoadReporting(const QString &deviceName); Q_INVOKABLE void removeIoLoadReporting(const QString &deviceName); Q_INVOKABLE QStringList ioLoadReportingDevices() const; @@ -129,11 +135,13 @@ signals: void memoryReportingChanged(quint64 used); void cpuLoadReportingChanged(qreal load); + void gpuLoadReportingChanged(qreal load); void ioLoadReportingChanged(const QString &device, qreal load); void fpsReportingChanged(qreal average, qreal minimum, qreal maximum, qreal jitter); void memoryReportingEnabledChanged(); void cpuLoadReportingEnabledChanged(); + void gpuLoadReportingEnabledChanged(); void fpsReportingEnabledChanged(); private: |