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 }