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"