File indexing completed on 2024-05-19 05:30:19

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