File indexing completed on 2024-05-12 17:00:15

0001 /*
0002  * SPDX-FileCopyrightText: 2019 David Edmundson <davidedmundson@kde.org>
0003  * SPDX-FileCopyrightText: 2020 Arjen Hiemstra <ahiemstra@heimr.nl>
0004  *
0005  * SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
0006  */
0007 
0008 #include "NvidiaSmiProcess.h"
0009 
0010 #include <QStandardPaths>
0011 
0012 NvidiaSmiProcess::NvidiaSmiProcess()
0013 {
0014     m_smiPath = QStandardPaths::findExecutable(QStringLiteral("nvidia-smi"));
0015 }
0016 
0017 bool NvidiaSmiProcess::isSupported() const
0018 {
0019     return !m_smiPath.isEmpty();
0020 }
0021 
0022 std::vector<NvidiaSmiProcess::GpuQueryResult> NvidiaSmiProcess::query()
0023 {
0024     if (!isSupported()) {
0025         return m_queryResult;
0026     }
0027 
0028     if (!m_queryResult.empty()) {
0029         return m_queryResult;
0030     }
0031 
0032     // Read and parse the result of "nvidia-smi query"
0033     // This seems to be the only way to get certain values like total memory or
0034     // maximum temperature. Unfortunately the output isn't very easily parseable
0035     // so we have to do some trickery to parse things.
0036 
0037     QProcess queryProcess;
0038     queryProcess.setProgram(m_smiPath);
0039     queryProcess.setArguments({QStringLiteral("--query")});
0040     queryProcess.start();
0041 
0042     int gpuCounter = 0;
0043     auto data = m_queryResult.end();
0044 
0045     bool readMemory = false;
0046     bool readMaxClocks = false;
0047     bool readMaxPwr = false;
0048 
0049     while (queryProcess.waitForReadyRead()) {
0050         if (!queryProcess.canReadLine()) {
0051             continue;
0052         }
0053 
0054         auto line = queryProcess.readLine();
0055         if (line.startsWith("GPU ")) {
0056             // Start of GPU properties block. Ensure we have a new data object
0057             // to write to.
0058             data = m_queryResult.emplace(m_queryResult.end());
0059             // nvidia-smi has to much zeros compared to linux, remove line break
0060             data->pciPath = line.mid(strlen("GPU 0000")).chopped(1).toLower();
0061             gpuCounter++;
0062         }
0063 
0064         if ((readMemory || readMaxClocks) && !line.startsWith("        ")) {
0065             // Memory/clock information does not have a unique prefix but should
0066             // be indented more than their "headers". So if the indentation is
0067             // less, we are no longer in an info block and should treat it as
0068             // such.
0069             readMemory = false;
0070             readMaxClocks = false;
0071         }
0072 
0073         if (line.startsWith("    Product Name")) {
0074             data->name = line.mid(line.indexOf(':') + 1).trimmed();
0075         }
0076 
0077         if (line.startsWith("    FB Memory Usage") || line.startsWith("    BAR1 Memory Usage")) {
0078             readMemory = true;
0079         }
0080 
0081         if (line.startsWith("    Max Clocks")) {
0082             readMaxClocks = true;
0083         }
0084 
0085         if (line.startsWith("    Power Readings")) {
0086             readMaxPwr = true;
0087         }
0088 
0089         if (line.startsWith("        Total") && readMemory) {
0090             data->totalMemory += std::atoi(line.mid(line.indexOf(':') + 1));
0091         }
0092 
0093         if (line.startsWith("        GPU Shutdown Temp")) {
0094             data->maxTemperature = std::atoi(line.mid(line.indexOf(':') + 1));
0095         }
0096 
0097         if (line.startsWith("        Graphics") && readMaxClocks) {
0098             data->maxCoreFrequency = std::atoi(line.mid(line.indexOf(':') + 1));
0099         }
0100 
0101         if (line.startsWith("        Memory") && readMaxClocks) {
0102             data->maxMemoryFrequency = std::atoi(line.mid(line.indexOf(':') + 1));
0103         }
0104 
0105         if (line.startsWith("        Power Limit") && readMaxPwr) {
0106             data->maxPower = std::atoi(line.mid(line.indexOf(':') + 1));
0107         }
0108     }
0109 
0110     return m_queryResult;
0111 }
0112 
0113 void NvidiaSmiProcess::ref()
0114 {
0115     if (!isSupported()) {
0116         return;
0117     }
0118 
0119     m_references++;
0120 
0121     if (m_process) {
0122         return;
0123     }
0124 
0125     m_process = std::make_unique<QProcess>();
0126     m_process->setProgram(m_smiPath);
0127     m_process->setArguments({
0128         QStringLiteral("dmon"), // Monitor
0129         QStringLiteral("-d"),
0130         QStringLiteral("2"), // 2 seconds delay, to match daemon update rate
0131         QStringLiteral("-s"),
0132         QStringLiteral("pucm") // Include all relevant statistics
0133     });
0134     connect(m_process.get(), &QProcess::readyReadStandardOutput, this, &NvidiaSmiProcess::readStatisticsData);
0135     m_process->start();
0136 }
0137 
0138 void NvidiaSmiProcess::unref()
0139 {
0140     if (!isSupported()) {
0141         return;
0142     }
0143 
0144     m_references--;
0145 
0146     if (!m_process || m_references > 0) {
0147         return;
0148     }
0149 
0150     m_process->terminate();
0151     m_process->waitForFinished();
0152     m_process.reset();
0153 }
0154 
0155 void NvidiaSmiProcess::readStatisticsData()
0156 {
0157     while (m_process->canReadLine()) {
0158         QString line = m_process->readLine();
0159 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
0160         QVector<QStringRef> parts = QStringRef(&line).trimmed().split(QLatin1Char(' '), Qt::SkipEmptyParts);
0161 #else
0162         QVector<QStringView> parts = QStringView(line).trimmed().split(QLatin1Char(' '), Qt::SkipEmptyParts);
0163 #endif
0164         // Because in Qt5 indexOf of QVector<T> only takes T's, write our own indexOf taking arbitrary types
0165         auto indexOf = [](const auto &stack, const auto& needle) {
0166             auto it = std::find(stack.cbegin(), stack.cend(), needle);
0167             return it != stack.cend() ? std::distance(stack.cbegin(), it) : -1;
0168         };
0169 
0170         // discover index of fields in the header format is something like
0171         //# gpu   pwr gtemp mtemp    sm   mem   enc   dec  mclk  pclk    fb  bar1
0172         // # Idx     W     C     C     %     %     %     %   MHz   MHz    MB    MB
0173         // 0     25     29      -     1      1      0      0   4006   1506    891     22
0174         if (line.startsWith(QLatin1Char('#'))) {
0175             if (m_dmonIndices.gpu == -1) {
0176                 // Remove First part because of leading '# ';
0177                 parts.removeFirst();
0178                 m_dmonIndices.gpu = indexOf(parts, QLatin1String("gpu"));
0179                 m_dmonIndices.power = indexOf(parts, QLatin1String("pwr"));
0180                 m_dmonIndices.gtemp = indexOf(parts, QLatin1String("gtemp"));
0181                 m_dmonIndices.sm = indexOf(parts, QLatin1String("sm"));
0182                 m_dmonIndices.enc = indexOf(parts, QLatin1String("enc"));
0183                 m_dmonIndices.dec = indexOf(parts, QLatin1String("dec"));
0184                 m_dmonIndices.fb = indexOf(parts, QLatin1String("fb"));
0185                 m_dmonIndices.bar1 = indexOf(parts, QLatin1String("bar1"));
0186                 m_dmonIndices.mclk = indexOf(parts, QLatin1String("mclk"));
0187                 m_dmonIndices.pclk = indexOf(parts, QLatin1String("pclk"));
0188             }
0189             continue;
0190         }
0191 
0192         bool ok;
0193         int index = parts[0].toInt(&ok);
0194         if (!ok) {
0195             continue;
0196         }
0197 
0198         auto readDataIfFound =  [&parts, this] (int index) {
0199             return index >= 0 ? parts[index].toUInt() : 0;
0200         };
0201 
0202         GpuData data;
0203         data.index = readDataIfFound(m_dmonIndices.gpu);
0204         data.power = readDataIfFound(m_dmonIndices.power);
0205         data.temperature = readDataIfFound(m_dmonIndices.gtemp);
0206 
0207         // GPU usage equals "SM" usage + "ENC" usage + "DEC" usage
0208         data.usage = readDataIfFound(m_dmonIndices.sm) + readDataIfFound(m_dmonIndices.enc) + readDataIfFound(m_dmonIndices.dec);
0209 
0210         // Total memory used equals "FB" usage + "BAR1" usage
0211         data.memoryUsed = readDataIfFound(m_dmonIndices.fb) + readDataIfFound(m_dmonIndices.bar1);
0212 
0213         data.memoryFrequency = readDataIfFound(m_dmonIndices.mclk);
0214         data.coreFrequency = readDataIfFound(m_dmonIndices.pclk);
0215 
0216         Q_EMIT dataReceived(data);
0217     }
0218 }