summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorRobert Griebl <robert.griebl@pelagicore.com>2018-03-20 18:06:37 +0100
committerDominik Holland <dominik.holland@pelagicore.com>2018-03-27 15:27:46 +0000
commit1b892e8653965d13cc7b30101b414585dc70143e (patch)
tree4afdc1fbcd80fd4217ffa779c7222da680775807
parentd47e6e66df5b7b457319572334c981d38b6e32a6 (diff)
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.qml2
-rw-r--r--src/manager-lib/systemreader.cpp192
-rw-r--r--src/manager-lib/systemreader.h18
-rw-r--r--src/monitor-lib/systemmonitor.cpp104
-rw-r--r--src/monitor-lib/systemmonitor.h8
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: