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 <QtTest/private/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(program: u"valgrind"_s, arguments: QStringList(u"--version"_s));
30 return process.waitForStarted() && process.waitForFinished(msecs: -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(msg: "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 file.open(f: fh, ioFlags: QIODevice::WriteOnly);
49 file.write(data);
50}
51
52qint64 QBenchmarkValgrindUtils::extractResult(const QString &fileName)
53{
54 QFile file(fileName);
55 const bool openOk = file.open(flags: QIODevice::ReadOnly | QIODevice::Text);
56 Q_ASSERT(openOk);
57 Q_UNUSED(openOk);
58
59 std::optional<qint64> val = std::nullopt;
60 while (!file.atEnd()) {
61 const QByteArray line = file.readLine();
62 constexpr QByteArrayView tag = "summary: ";
63 if (line.startsWith(bv: tag)) {
64 const auto maybeNumber = line.data() + tag.size();
65 const auto end = line.data() + line.size();
66 qint64 v;
67 const auto r = std::from_chars(first: maybeNumber, last: end, value&: v);
68 if (r.ec == std::errc{}) {
69 val = v;
70 break;
71 }
72 }
73 }
74 if (Q_UNLIKELY(!val))
75 qFatal(msg: "Failed to extract result");
76 return *val;
77}
78
79// Gets the newest file name (i.e. the one with the highest integer suffix).
80QString QBenchmarkValgrindUtils::getNewestFileName()
81{
82 QStringList nameFilters;
83 QString base = QBenchmarkGlobalData::current->callgrindOutFileBase;
84 Q_ASSERT(!base.isEmpty());
85
86 nameFilters << QString::fromLatin1(ba: "%1.*").arg(a: base);
87 const QFileInfoList fiList = QDir().entryInfoList(nameFilters, filters: QDir::Files | QDir::Readable);
88 Q_ASSERT(!fiList.empty());
89 int hiSuffix = -1;
90 QFileInfo lastFileInfo;
91 const QString pattern = QString::fromLatin1(ba: "%1.(\\d+)").arg(a: base);
92 QRegularExpression rx(pattern);
93 for (const QFileInfo &fileInfo : fiList) {
94 QRegularExpressionMatch match = rx.match(subject: fileInfo.fileName());
95 Q_ASSERT(match.hasMatch());
96 bool ok;
97 const int suffix = match.captured(nth: 1).toInt(ok: &ok);
98 Q_ASSERT(ok);
99 Q_ASSERT(suffix >= 0);
100 if (suffix > hiSuffix) {
101 lastFileInfo = fileInfo;
102 hiSuffix = suffix;
103 }
104 }
105
106 return lastFileInfo.fileName();
107}
108
109qint64 QBenchmarkValgrindUtils::extractLastResult()
110{
111 return extractResult(fileName: getNewestFileName());
112}
113
114void QBenchmarkValgrindUtils::cleanup()
115{
116 QStringList nameFilters;
117 QString base = QBenchmarkGlobalData::current->callgrindOutFileBase;
118 Q_ASSERT(!base.isEmpty());
119 nameFilters
120 << base // overall summary
121 << QString::fromLatin1(ba: "%1.*").arg(a: base); // individual dumps
122 const QFileInfoList fiList = QDir().entryInfoList(nameFilters, filters: QDir::Files | QDir::Readable);
123 for (const QFileInfo &fileInfo : fiList) {
124 const bool removeOk = QFile::remove(fileName: fileInfo.fileName());
125 Q_ASSERT(removeOk);
126 Q_UNUSED(removeOk);
127 }
128}
129
130QString QBenchmarkValgrindUtils::outFileBase(qint64 pid)
131{
132 return QString::fromLatin1(ba: "callgrind.out.%1").arg(
133 a: pid != -1 ? pid : QCoreApplication::applicationPid());
134}
135
136// Reruns this program through callgrind, storing callgrind result files in the
137// current directory.
138// Returns \c true upon success, otherwise false.
139bool QBenchmarkValgrindUtils::runCallgrindSubProcess(const QStringList &origAppArgs, int &exitCode)
140{
141 const QString &execFile = origAppArgs.at(i: 0);
142 QStringList args{ u"--tool=callgrind"_s, u"--instr-atstart=yes"_s,
143 u"--quiet"_s, execFile, u"-callgrindchild"_s };
144
145 // pass on original arguments that make sense (e.g. avoid wasting time producing output
146 // that will be ignored anyway) ...
147 for (int i = 1; i < origAppArgs.size(); ++i) {
148 const QString &arg = origAppArgs.at(i);
149 if (arg == "-callgrind"_L1)
150 continue;
151 args << arg; // ok to pass on
152 }
153
154 QProcess process;
155 process.start(program: u"valgrind"_s, arguments: args);
156 process.waitForStarted(msecs: -1);
157 QBenchmarkGlobalData::current->callgrindOutFileBase =
158 QBenchmarkValgrindUtils::outFileBase(pid: process.processId());
159 const bool finishedOk = process.waitForFinished(msecs: -1);
160 exitCode = process.exitCode();
161
162 dumpOutput(data: process.readAllStandardOutput(), stdout);
163 dumpOutput(data: process.readAllStandardError(), stderr);
164
165 return finishedOk;
166}
167
168void QBenchmarkCallgrindMeasurer::start()
169{
170 CALLGRIND_ZERO_STATS;
171}
172
173QList<QBenchmarkMeasurerBase::Measurement> QBenchmarkCallgrindMeasurer::stop()
174{
175 CALLGRIND_DUMP_STATS;
176 const qint64 result = QBenchmarkValgrindUtils::extractLastResult();
177 return { { .value: qreal(result), .metric: QTest::InstructionReads } };
178}
179
180bool QBenchmarkCallgrindMeasurer::isMeasurementAccepted(Measurement measurement)
181{
182 Q_UNUSED(measurement);
183 return true;
184}
185
186int QBenchmarkCallgrindMeasurer::adjustIterationCount(int)
187{
188 return 1;
189}
190
191int QBenchmarkCallgrindMeasurer::adjustMedianCount(int)
192{
193 return 1;
194}
195
196bool QBenchmarkCallgrindMeasurer::needsWarmupIteration()
197{
198 return true;
199}
200
201QT_END_NAMESPACE
202

source code of qtbase/src/testlib/qbenchmarkvalgrind.cpp