summaryrefslogtreecommitdiffstats
path: root/src/testlib/qbenchmarkvalgrind.cpp
blob: 33479f135d7eb6a28685e5919cfc79da5343915a (plain)
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204
// Copyright (C) 2016 The Qt Company Ltd.// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only#include <QtTest/private/qbenchmark_p.h>#include <QtTest/private/qbenchmarkvalgrind_p.h>#include <QtCore/qstringlist.h>#include <QtCore/qcoreapplication.h>#include <QtCore/qprocess.h>#include <QtCore/qdir.h>#include <QtCore/qregularexpression.h>#include <QtCore/qset.h>#include"3rdparty/valgrind/callgrind_p.h"#include <charconv>#include <optional> QT_BEGIN_NAMESPACE using namespaceQt::StringLiterals;// Returns \c true if valgrind is available.boolQBenchmarkValgrindUtils::haveValgrind(){#ifdef NVALGRINDreturn false;#else QProcess process; process.start(u"valgrind"_s,QStringList(u"--version"_s));return process.waitForStarted() && process.waitForFinished(-1);#endif}// Reruns this program through callgrind.// Returns \c true upon success, otherwise false.boolQBenchmarkValgrindUtils::rerunThroughCallgrind(const QStringList &origAppArgs,int&exitCode){if(!QBenchmarkValgrindUtils::runCallgrindSubProcess(origAppArgs, exitCode)) {qWarning("failed to run callgrind subprocess");return false;}return true;}static voiddumpOutput(const QByteArray &data,FILE*fh){ QFile file;if(!file.open(fh,QIODevice::WriteOnly)) {qFatal("Could not open filehandle for dumping output: %s",qPrintable(file.errorString()));} file.write(data);} qint64 QBenchmarkValgrindUtils::extractResult(const QString &fileName){ QFile file(fileName);const bool openOk = file.open(QIODevice::ReadOnly |QIODevice::Text);Q_ASSERT(openOk);Q_UNUSED(openOk);std::optional<qint64> val =std::nullopt; QByteArray line;while(file.readLineInto(&line)) {constexpr QByteArrayView tag ="summary: ";if(line.startsWith(tag)) {constauto maybeNumber = line.data() + tag.size();constauto end = line.data() + line.size(); qint64 v;constauto r =std::from_chars(maybeNumber, end, v);if(r.ec ==std::errc{}) { val = v;break;}}}if(Q_UNLIKELY(!val))qFatal("Failed to extract result");return*val;}// Gets the newest file name (i.e. the one with the highest integer suffix). QString QBenchmarkValgrindUtils::getNewestFileName(){ QStringList nameFilters; QString base =QBenchmarkGlobalData::current->callgrindOutFileBase;Q_ASSERT(!base.isEmpty()); nameFilters <<QString::fromLatin1("%1.*").arg(base);const QFileInfoList fiList =QDir().entryInfoList(nameFilters,QDir::Files |QDir::Readable);Q_ASSERT(!fiList.empty());int hiSuffix = -1; QFileInfo lastFileInfo;const QString pattern =QString::fromLatin1("%1.(\\d+)").arg(base); QRegularExpression rx(pattern);for(const QFileInfo &fileInfo : fiList) { QRegularExpressionMatch match = rx.match(fileInfo.fileName());Q_ASSERT(match.hasMatch());bool ok;const int suffix = match.captured(1).toInt(&ok);Q_ASSERT(ok);Q_ASSERT(suffix >=0);if(suffix > hiSuffix) { lastFileInfo = fileInfo; hiSuffix = suffix;}}return lastFileInfo.fileName();} qint64 QBenchmarkValgrindUtils::extractLastResult(){returnextractResult(getNewestFileName());}voidQBenchmarkValgrindUtils::cleanup(){ QStringList nameFilters; QString base =QBenchmarkGlobalData::current->callgrindOutFileBase;Q_ASSERT(!base.isEmpty()); nameFilters << base // overall summary<<QString::fromLatin1("%1.*").arg(base);// individual dumpsconst QFileInfoList fiList =QDir().entryInfoList(nameFilters,QDir::Files |QDir::Readable);for(const QFileInfo &fileInfo : fiList) {const bool removeOk =QFile::remove(fileInfo.fileName());Q_ASSERT(removeOk);Q_UNUSED(removeOk);}} QString QBenchmarkValgrindUtils::outFileBase(qint64 pid){returnQString::fromLatin1("callgrind.out.%1").arg( pid != -1? pid :QCoreApplication::applicationPid());}// Reruns this program through callgrind, storing callgrind result files in the// current directory.// Returns \c true upon success, otherwise false.boolQBenchmarkValgrindUtils::runCallgrindSubProcess(const QStringList &origAppArgs,int&exitCode){const QString &execFile = origAppArgs.at(0); QStringList args{ u"--tool=callgrind"_s, u"--instr-atstart=yes"_s, u"--quiet"_s, execFile, u"-callgrindchild"_s };// pass on original arguments that make sense (e.g. avoid wasting time producing output// that will be ignored anyway) ...for(int i =1; i < origAppArgs.size(); ++i) {const QString &arg = origAppArgs.at(i);if(arg =="-callgrind"_L1)continue; args << arg;// ok to pass on} QProcess process; process.start(u"valgrind"_s, args); process.waitForStarted(-1);QBenchmarkGlobalData::current->callgrindOutFileBase =QBenchmarkValgrindUtils::outFileBase(process.processId());const bool finishedOk = process.waitForFinished(-1); exitCode = process.exitCode();dumpOutput(process.readAllStandardOutput(), stdout);dumpOutput(process.readAllStandardError(), stderr);return finishedOk;}voidQBenchmarkCallgrindMeasurer::start(){ CALLGRIND_ZERO_STATS;} QList<QBenchmarkMeasurerBase::Measurement>QBenchmarkCallgrindMeasurer::stop(){ CALLGRIND_DUMP_STATS;const qint64 result =QBenchmarkValgrindUtils::extractLastResult();return{ {qreal(result),QTest::InstructionReads } };}boolQBenchmarkCallgrindMeasurer::isMeasurementAccepted(Measurement measurement){Q_UNUSED(measurement);return true;}intQBenchmarkCallgrindMeasurer::adjustIterationCount(int){return1;}intQBenchmarkCallgrindMeasurer::adjustMedianCount(int){return1;}boolQBenchmarkCallgrindMeasurer::needsWarmupIteration(){return true;} QT_END_NAMESPACE 
close