Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qbenchmarkvalgrind.cpp
Go to the documentation of this file.
1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#include <QtTest/private/qbenchmark_p.h>
5
6#include <QtTest/private/qbenchmarkvalgrind_p.h>
7#include <QtCore/qstringlist.h>
8#include <QtCore/qcoreapplication.h>
9#include <QtCore/qprocess.h>
10#include <QtCore/qdir.h>
11#include <QtCore/qregularexpression.h>
12#include <QtCore/qset.h>
13#include "3rdparty/valgrind/callgrind_p.h"
14
15#include <charconv>
16#include <optional>
17
18QT_BEGIN_NAMESPACE
19
20using namespace Qt::StringLiterals;
21
22// Returns \c true if valgrind is available.
23bool QBenchmarkValgrindUtils::haveValgrind()
24{
25#ifdef NVALGRIND
26 return false;
27#else
28 QProcess process;
29 process.start(u"valgrind"_s, QStringList(u"--version"_s));
30 return process.waitForStarted() && process.waitForFinished(-1);
31#endif
32}
33
34// Reruns this program through callgrind.
35// Returns \c true upon success, otherwise false.
36bool QBenchmarkValgrindUtils::rerunThroughCallgrind(const QStringList &origAppArgs, int &exitCode)
37{
38 if (!QBenchmarkValgrindUtils::runCallgrindSubProcess(origAppArgs, exitCode)) {
39 qWarning("failed to run callgrind subprocess");
40 return false;
41 }
42 return true;
43}
44
45static void dumpOutput(const QByteArray &data, FILE *fh)
46{
47 QFile file;
48 if (!file.open(fh, QIODevice::WriteOnly)) {
49 qFatal("Could not open filehandle for dumping output: %s",
50 qPrintable(file.errorString()));
51 }
52 file.write(data);
53}
54
55qint64 QBenchmarkValgrindUtils::extractResult(const QString &fileName)
56{
57 QFile file(fileName);
58 const bool openOk = file.open(QIODevice::ReadOnly | QIODevice::Text);
59 Q_ASSERT(openOk);
60 Q_UNUSED(openOk);
61
62 std::optional<qint64> val = std::nullopt;
63 QByteArray line;
64 while (file.readLineInto(&line)) {
65 constexpr QByteArrayView tag = "summary: ";
66 if (line.startsWith(tag)) {
67 const auto maybeNumber = line.data() + tag.size();
68 const auto end = line.data() + line.size();
69 qint64 v;
70 const auto r = std::from_chars(maybeNumber, end, v);
71 if (r.ec == std::errc{}) {
72 val = v;
73 break;
74 }
75 }
76 }
77 if (Q_UNLIKELY(!val))
78 qFatal("Failed to extract result");
79 return *val;
80}
81
82// Gets the newest file name (i.e. the one with the highest integer suffix).
83QString QBenchmarkValgrindUtils::getNewestFileName()
84{
85 QStringList nameFilters;
86 QString base = QBenchmarkGlobalData::current->callgrindOutFileBase;
87 Q_ASSERT(!base.isEmpty());
88
89 nameFilters << QString::fromLatin1("%1.*").arg(base);
90 const QFileInfoList fiList = QDir().entryInfoList(nameFilters, QDir::Files | QDir::Readable);
91 Q_ASSERT(!fiList.empty());
92 int hiSuffix = -1;
93 QFileInfo lastFileInfo;
94 const QString pattern = QString::fromLatin1("%1.(\\d+)").arg(base);
95 QRegularExpression rx(pattern);
96 for (const QFileInfo &fileInfo : fiList) {
97 QRegularExpressionMatch match = rx.match(fileInfo.fileName());
98 Q_ASSERT(match.hasMatch());
99 bool ok;
100 const int suffix = match.captured(1).toInt(&ok);
101 Q_ASSERT(ok);
102 Q_ASSERT(suffix >= 0);
103 if (suffix > hiSuffix) {
104 lastFileInfo = fileInfo;
105 hiSuffix = suffix;
106 }
107 }
108
109 return lastFileInfo.fileName();
110}
111
112qint64 QBenchmarkValgrindUtils::extractLastResult()
113{
114 return extractResult(getNewestFileName());
115}
116
117void QBenchmarkValgrindUtils::cleanup()
118{
119 QStringList nameFilters;
120 QString base = QBenchmarkGlobalData::current->callgrindOutFileBase;
121 Q_ASSERT(!base.isEmpty());
122 nameFilters
123 << base // overall summary
124 << QString::fromLatin1("%1.*").arg(base); // individual dumps
125 const QFileInfoList fiList = QDir().entryInfoList(nameFilters, QDir::Files | QDir::Readable);
126 for (const QFileInfo &fileInfo : fiList) {
127 const bool removeOk = QFile::remove(fileInfo.fileName());
128 Q_ASSERT(removeOk);
129 Q_UNUSED(removeOk);
130 }
131}
132
133QString QBenchmarkValgrindUtils::outFileBase(qint64 pid)
134{
135 return QString::fromLatin1("callgrind.out.%1").arg(
136 pid != -1 ? pid : QCoreApplication::applicationPid());
137}
138
139// Reruns this program through callgrind, storing callgrind result files in the
140// current directory.
141// Returns \c true upon success, otherwise false.
142bool QBenchmarkValgrindUtils::runCallgrindSubProcess(const QStringList &origAppArgs, int &exitCode)
143{
144 const QString &execFile = origAppArgs.at(0);
145 QStringList args{ u"--tool=callgrind"_s, u"--instr-atstart=yes"_s,
146 u"--quiet"_s, execFile, u"-callgrindchild"_s };
147
148 // pass on original arguments that make sense (e.g. avoid wasting time producing output
149 // that will be ignored anyway) ...
150 for (int i = 1; i < origAppArgs.size(); ++i) {
151 const QString &arg = origAppArgs.at(i);
152 if (arg == "-callgrind"_L1)
153 continue;
154 args << arg; // ok to pass on
155 }
156
157 QProcess process;
158 process.start(u"valgrind"_s, args);
159 process.waitForStarted(-1);
160 QBenchmarkGlobalData::current->callgrindOutFileBase =
161 QBenchmarkValgrindUtils::outFileBase(process.processId());
162 const bool finishedOk = process.waitForFinished(-1);
163 exitCode = process.exitCode();
164
165 dumpOutput(process.readAllStandardOutput(), stdout);
166 dumpOutput(process.readAllStandardError(), stderr);
167
168 return finishedOk;
169}
170
171void QBenchmarkCallgrindMeasurer::start()
172{
173 CALLGRIND_ZERO_STATS;
174}
175
176QList<QBenchmarkMeasurerBase::Measurement> QBenchmarkCallgrindMeasurer::stop()
177{
178 CALLGRIND_DUMP_STATS;
179 const qint64 result = QBenchmarkValgrindUtils::extractLastResult();
180 return { { qreal(result), QTest::InstructionReads } };
181}
182
183bool QBenchmarkCallgrindMeasurer::isMeasurementAccepted(Measurement measurement)
184{
185 Q_UNUSED(measurement);
186 return true;
187}
188
189int QBenchmarkCallgrindMeasurer::adjustIterationCount(int)
190{
191 return 1;
192}
193
194int QBenchmarkCallgrindMeasurer::adjustMedianCount(int)
195{
196 return 1;
197}
198
199bool QBenchmarkCallgrindMeasurer::needsWarmupIteration()
200{
201 return true;
202}
203
204QT_END_NAMESPACE
static void dumpOutput(const QByteArray &data, FILE *fh)