File: NvidiaSmiProcess.cpp

package info (click to toggle)
ksystemstats 6.5.4-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 2,528 kB
  • sloc: cpp: 4,881; makefile: 6; sh: 1
file content (209 lines) | stat: -rw-r--r-- 6,621 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
/*
 * SPDX-FileCopyrightText: 2019 David Edmundson <davidedmundson@kde.org>
 * SPDX-FileCopyrightText: 2020 Arjen Hiemstra <ahiemstra@heimr.nl>
 *
 * SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
 */

#include "NvidiaSmiProcess.h"

#include <QStandardPaths>

using namespace Qt::StringLiterals;

NvidiaSmiProcess::NvidiaSmiProcess()
{
    m_smiPath = QStandardPaths::findExecutable(QStringLiteral("nvidia-smi"));
}

bool NvidiaSmiProcess::isSupported() const
{
    return !m_smiPath.isEmpty();
}

std::vector<NvidiaSmiProcess::GpuQueryResult> NvidiaSmiProcess::query()
{
    if (!isSupported()) {
        return m_queryResult;
    }

    if (!m_queryResult.empty()) {
        return m_queryResult;
    }

    // Read and parse the result of "nvidia-smi query"
    // This seems to be the only way to get certain values like total memory or
    // maximum temperature. Unfortunately the output isn't very easily parseable
    // so we have to do some trickery to parse things.

    QProcess queryProcess;
    queryProcess.setProgram(m_smiPath);
    queryProcess.setArguments({QStringLiteral("--query")});
    queryProcess.start();

    int gpuCounter = 0;
    auto data = m_queryResult.end();

    bool readMemory = false;
    bool readMaxClocks = false;
    bool readMaxPwr = false;

    while (queryProcess.waitForReadyRead()) {
        if (!queryProcess.canReadLine()) {
            continue;
        }

        auto line = queryProcess.readLine();
        if (line.startsWith("GPU ")) {
            // Start of GPU properties block. Ensure we have a new data object
            // to write to.
            data = m_queryResult.emplace(m_queryResult.end());
            // nvidia-smi has to much zeros compared to linux, remove line break
            data->pciPath = line.mid(strlen("GPU 0000")).chopped(1).toLower();
            gpuCounter++;
        }

        if ((readMemory || readMaxClocks) && !line.startsWith("        ")) {
            // Memory/clock information does not have a unique prefix but should
            // be indented more than their "headers". So if the indentation is
            // less, we are no longer in an info block and should treat it as
            // such.
            readMemory = false;
            readMaxClocks = false;
        }

        if (line.startsWith("    Product Name")) {
            data->name = line.mid(line.indexOf(':') + 1).trimmed();
        }

        if (line.startsWith("    FB Memory Usage")) {
            readMemory = true;
        }

        if (line.startsWith("    Max Clocks")) {
            readMaxClocks = true;
        }

        if (line.startsWith("    Power Readings")) {
            readMaxPwr = true;
        }

        if (line.startsWith("        Total") && readMemory) {
            data->totalMemory += std::atoi(line.mid(line.indexOf(':') + 1));
        }

        if (line.startsWith("        GPU Shutdown Temp")) {
            data->maxTemperature = std::atoi(line.mid(line.indexOf(':') + 1));
        }

        if (line.startsWith("        Graphics") && readMaxClocks) {
            data->maxCoreFrequency = std::atoi(line.mid(line.indexOf(':') + 1));
        }

        if (line.startsWith("        Memory") && readMaxClocks) {
            data->maxMemoryFrequency = std::atoi(line.mid(line.indexOf(':') + 1));
        }

        if (line.startsWith("        Power Limit") && readMaxPwr) {
            data->maxPower = std::atoi(line.mid(line.indexOf(':') + 1));
        }
    }

    return m_queryResult;
}

void NvidiaSmiProcess::ref()
{
    if (!isSupported()) {
        return;
    }

    m_references++;

    if (m_process) {
        return;
    }

    m_process = std::make_unique<QProcess>();
    m_process->setProgram(m_smiPath);
    m_process->setArguments({
        QStringLiteral("dmon"), // Monitor
        QStringLiteral("-d"),
        QStringLiteral("2"), // 2 seconds delay, to match daemon update rate
        QStringLiteral("-s"),
        QStringLiteral("pucm") // Include all relevant statistics
    });
    connect(m_process.get(), &QProcess::readyReadStandardOutput, this, [this] {
        while (m_process->canReadLine()) {
            const QString line = m_process->readLine();
            readStatisticsData(line);
        }
    });
    m_process->start();
}

void NvidiaSmiProcess::unref()
{
    if (!isSupported()) {
        return;
    }

    m_references--;

    if (!m_process || m_references > 0) {
        return;
    }

    m_process->terminate();
    m_process->waitForFinished();
    m_process.reset();
}

void NvidiaSmiProcess::readStatisticsData(const QString &line)
{
    QList<QStringView> parts = QStringView(line).trimmed().split(QLatin1Char(' '), Qt::SkipEmptyParts);

    // discover index of fields in the header format is something like
    //# gpu   pwr gtemp mtemp    sm   mem   enc   dec  mclk  pclk    fb  bar1
    // # Idx     W     C     C     %     %     %     %   MHz   MHz    MB    MB
    // 0     25     29      -     1      1      0      0   4006   1506    891     22
    if (line.startsWith(QLatin1Char('#'))) {
        if (m_dmonIndices.gpu == -1) {
            // Remove First part because of leading '# ';
            parts.removeFirst();
            m_dmonIndices.gpu = parts.indexOf("gpu"_L1);
            m_dmonIndices.power = parts.indexOf("pwr"_L1);
            m_dmonIndices.gtemp = parts.indexOf("gtemp"_L1);
            m_dmonIndices.sm = parts.indexOf("sm"_L1);
            m_dmonIndices.enc = parts.indexOf("enc"_L1);
            m_dmonIndices.dec = parts.indexOf("dec"_L1);
            m_dmonIndices.fb = parts.indexOf("fb"_L1);
            m_dmonIndices.bar1 = parts.indexOf("bar1"_L1);
            m_dmonIndices.mclk = parts.indexOf("mclk"_L1);
            m_dmonIndices.pclk = parts.indexOf("pclk"_L1);
        }
        return;
    }

    auto readDataIfFound = [&parts] (int index) {
        return index >= 0 ? parts[index].toUInt() : 0;
    };

    GpuData data;
    data.index = readDataIfFound(m_dmonIndices.gpu);
    data.power = readDataIfFound(m_dmonIndices.power);
    data.temperature = readDataIfFound(m_dmonIndices.gtemp);

    // GPU usage equals "SM" usage + "ENC" usage + "DEC" usage
    data.usage = readDataIfFound(m_dmonIndices.sm) + readDataIfFound(m_dmonIndices.enc) + readDataIfFound(m_dmonIndices.dec);

    // Total memory used equals "FB" usage
    data.memoryUsed = readDataIfFound(m_dmonIndices.fb);

    data.memoryFrequency = readDataIfFound(m_dmonIndices.mclk);
    data.coreFrequency = readDataIfFound(m_dmonIndices.pclk);

    Q_EMIT dataReceived(data);
}

#include "moc_NvidiaSmiProcess.cpp"