forked from qt-creator/qt-creator
Valgrind: Make callgrind work on docker
Callgrind on Remote Linux did not work for years, so its safe to ignore the remains from the previous remote support and start over. This here now handles the local case and the case where callgrind runs together with the debuggee in the same docker container. Guessing at the output file name is replaced by specifiying it in advance. Cross-setups are not yet supported. Change-Id: I97edeb4eab7475727eddb26d25f8bec650a7eeb9 Reviewed-by: Christian Stenger <christian.stenger@qt.io>
This commit is contained in:
@@ -205,56 +205,33 @@ void CallgrindController::controllerProcessClosed(bool success)
|
|||||||
|
|
||||||
void CallgrindController::getLocalDataFile()
|
void CallgrindController::getLocalDataFile()
|
||||||
{
|
{
|
||||||
// we look for callgrind.out.PID, but there may be updated ones called ~.PID.NUM
|
cleanupTempFile();
|
||||||
const QString baseFileName = QString("callgrind.out.%1").arg(m_pid);
|
{
|
||||||
const QString workingDir = m_valgrindRunnable.workingDirectory.toString();
|
TemporaryFile dataFile("callgrind.out");
|
||||||
// first, set the to-be-parsed file to callgrind.out.PID
|
dataFile.open();
|
||||||
QString fileName = workingDir.isEmpty() ? baseFileName : (workingDir + '/' + baseFileName);
|
m_hostOutputFile = FilePath::fromString(dataFile.fileName());
|
||||||
|
}
|
||||||
|
|
||||||
if (m_valgrindRunnable.device
|
|
||||||
&& m_valgrindRunnable.device->type() != ProjectExplorer::Constants::DESKTOP_DEVICE_TYPE) {
|
|
||||||
// ///TODO: error handling
|
// ///TODO: error handling
|
||||||
// emit statusMessage(tr("Downloading remote profile data..."));
|
// emit statusMessage(tr("Downloading remote profile data..."));
|
||||||
// m_ssh = m_valgrindProc->connection();
|
// m_ssh = m_valgrindProc->connection();
|
||||||
// // if there are files like callgrind.out.PID.NUM, set it to the most recent one of those
|
// [...]
|
||||||
// QString cmd = QString::fromLatin1("ls -t %1* | head -n 1").arg(fileName);
|
// m_sftp = m_ssh->createSftpSession();
|
||||||
// m_findRemoteFile = m_ssh->createRemoteProcess(cmd.toUtf8());
|
// connect(m_sftp.get(), &QSsh::SftpSession::commandFinished,
|
||||||
// connect(m_findRemoteFile.data(), &QSsh::SshRemoteProcess::readyReadStandardOutput,
|
// this, &CallgrindController::sftpJobFinished);
|
||||||
// this, &CallgrindController::foundRemoteFile);
|
// connect(m_sftp.get(), &QSsh::SftpSession::started,
|
||||||
// m_findRemoteFile->start();
|
// this, &CallgrindController::sftpInitialized);
|
||||||
} else {
|
// m_sftp->start();
|
||||||
const QDir dir(workingDir, QString::fromLatin1("%1.*").arg(baseFileName), QDir::Time);
|
|
||||||
const QStringList outputFiles = dir.entryList();
|
|
||||||
// if there are files like callgrind.out.PID.NUM, set it to the most recent one of those
|
|
||||||
if (!outputFiles.isEmpty())
|
|
||||||
fileName = workingDir + '/' + outputFiles.first();
|
|
||||||
|
|
||||||
emit localParseDataAvailable(fileName);
|
const bool res = m_valgrindOutputFile.copyFile(m_hostOutputFile);
|
||||||
}
|
QTC_CHECK(res);
|
||||||
}
|
|
||||||
|
|
||||||
void CallgrindController::foundRemoteFile()
|
emit localParseDataAvailable(m_hostOutputFile);
|
||||||
{
|
|
||||||
m_remoteFile = m_findRemoteFile->readAllStandardOutput().trimmed();
|
|
||||||
|
|
||||||
m_sftp = m_ssh->createSftpSession();
|
|
||||||
connect(m_sftp.get(), &QSsh::SftpSession::commandFinished,
|
|
||||||
this, &CallgrindController::sftpJobFinished);
|
|
||||||
connect(m_sftp.get(), &QSsh::SftpSession::started,
|
|
||||||
this, &CallgrindController::sftpInitialized);
|
|
||||||
m_sftp->start();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void CallgrindController::sftpInitialized()
|
void CallgrindController::sftpInitialized()
|
||||||
{
|
{
|
||||||
cleanupTempFile();
|
m_downloadJob = m_sftp->downloadFile(m_valgrindOutputFile.path(), m_hostOutputFile.path());
|
||||||
Utils::TemporaryFile dataFile("callgrind.out.");
|
|
||||||
QTC_ASSERT(dataFile.open(), return);
|
|
||||||
m_tempDataFile = dataFile.fileName();
|
|
||||||
dataFile.setAutoRemove(false);
|
|
||||||
dataFile.close();
|
|
||||||
|
|
||||||
m_downloadJob = m_sftp->downloadFile(QString::fromUtf8(m_remoteFile), m_tempDataFile);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void CallgrindController::sftpJobFinished(QSsh::SftpJobId job, const QString &error)
|
void CallgrindController::sftpJobFinished(QSsh::SftpJobId job, const QString &error)
|
||||||
@@ -264,15 +241,15 @@ void CallgrindController::sftpJobFinished(QSsh::SftpJobId job, const QString &er
|
|||||||
m_sftp->quit();
|
m_sftp->quit();
|
||||||
|
|
||||||
if (error.isEmpty())
|
if (error.isEmpty())
|
||||||
emit localParseDataAvailable(m_tempDataFile);
|
emit localParseDataAvailable(m_hostOutputFile);
|
||||||
}
|
}
|
||||||
|
|
||||||
void CallgrindController::cleanupTempFile()
|
void CallgrindController::cleanupTempFile()
|
||||||
{
|
{
|
||||||
if (!m_tempDataFile.isEmpty() && QFile::exists(m_tempDataFile))
|
if (!m_hostOutputFile.isEmpty() && m_hostOutputFile.exists())
|
||||||
QFile::remove(m_tempDataFile);
|
m_hostOutputFile.removeFile();
|
||||||
|
|
||||||
m_tempDataFile.clear();
|
m_hostOutputFile.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
void CallgrindController::setValgrindRunnable(const Runnable &runnable)
|
void CallgrindController::setValgrindRunnable(const Runnable &runnable)
|
||||||
|
@@ -62,16 +62,16 @@ public:
|
|||||||
void getLocalDataFile();
|
void getLocalDataFile();
|
||||||
void setValgrindPid(qint64 pid);
|
void setValgrindPid(qint64 pid);
|
||||||
void setValgrindRunnable(const ProjectExplorer::Runnable &runnable);
|
void setValgrindRunnable(const ProjectExplorer::Runnable &runnable);
|
||||||
|
void setValgrindOutputFile(const Utils::FilePath &output) { m_valgrindOutputFile = output; }
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void finished(Valgrind::Callgrind::CallgrindController::Option option);
|
void finished(Valgrind::Callgrind::CallgrindController::Option option);
|
||||||
void localParseDataAvailable(const QString &file);
|
void localParseDataAvailable(const Utils::FilePath &file);
|
||||||
void statusMessage(const QString &msg);
|
void statusMessage(const QString &msg);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void handleControllerProcessError(QProcess::ProcessError);
|
void handleControllerProcessError(QProcess::ProcessError);
|
||||||
|
|
||||||
void foundRemoteFile();
|
|
||||||
void sftpInitialized();
|
void sftpInitialized();
|
||||||
void sftpJobFinished(QSsh::SftpJobId job, const QString &error);
|
void sftpJobFinished(QSsh::SftpJobId job, const QString &error);
|
||||||
void cleanupTempFile();
|
void cleanupTempFile();
|
||||||
@@ -88,11 +88,10 @@ private:
|
|||||||
|
|
||||||
// remote callgrind support
|
// remote callgrind support
|
||||||
QSsh::SshConnection *m_ssh = nullptr;
|
QSsh::SshConnection *m_ssh = nullptr;
|
||||||
QString m_tempDataFile;
|
Utils::FilePath m_valgrindOutputFile; // On the device that runs valgrind
|
||||||
QSsh::SshRemoteProcessPtr m_findRemoteFile;
|
Utils::FilePath m_hostOutputFile; // On the device that runs creator
|
||||||
QSsh::SftpSessionPtr m_sftp;
|
QSsh::SftpSessionPtr m_sftp;
|
||||||
QSsh::SftpJobId m_downloadJob = 0;
|
QSsh::SftpJobId m_downloadJob = 0;
|
||||||
QByteArray m_remoteFile;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace Callgrind
|
} // namespace Callgrind
|
||||||
|
@@ -30,10 +30,10 @@
|
|||||||
#include "callgrindcostitem.h"
|
#include "callgrindcostitem.h"
|
||||||
#include "callgrindfunction.h"
|
#include "callgrindfunction.h"
|
||||||
|
|
||||||
|
#include <utils/filepath.h>
|
||||||
#include <utils/qtcassert.h>
|
#include <utils/qtcassert.h>
|
||||||
#include <utils/stringutils.h>
|
#include <utils/stringutils.h>
|
||||||
|
|
||||||
#include <QFileDevice>
|
|
||||||
#include <QHash>
|
#include <QHash>
|
||||||
#include <QVector>
|
#include <QVector>
|
||||||
#include <QStringList>
|
#include <QStringList>
|
||||||
@@ -41,7 +41,10 @@
|
|||||||
|
|
||||||
// #define DEBUG_PARSER
|
// #define DEBUG_PARSER
|
||||||
|
|
||||||
namespace {
|
using namespace Utils;
|
||||||
|
|
||||||
|
namespace Valgrind {
|
||||||
|
namespace Callgrind {
|
||||||
|
|
||||||
static void skipSpace(const char **current, const char *end)
|
static void skipSpace(const char **current, const char *end)
|
||||||
{
|
{
|
||||||
@@ -130,10 +133,6 @@ static int parseNameShorthand(const char **current, const char *end)
|
|||||||
return -1; // invalid
|
return -1; // invalid
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace Valgrind {
|
|
||||||
namespace Callgrind {
|
|
||||||
|
|
||||||
class Parser::Private
|
class Parser::Private
|
||||||
{
|
{
|
||||||
@@ -150,7 +149,7 @@ public:
|
|||||||
delete data;
|
delete data;
|
||||||
}
|
}
|
||||||
|
|
||||||
void parse(QIODevice *device);
|
void parse(const FilePath &filePath);
|
||||||
void parseHeader(QIODevice *device);
|
void parseHeader(QIODevice *device);
|
||||||
|
|
||||||
using NamePair = QPair<qint64, QString>;
|
using NamePair = QPair<qint64, QString>;
|
||||||
@@ -197,20 +196,22 @@ public:
|
|||||||
QSet<Function *> recursiveFunctions;
|
QSet<Function *> recursiveFunctions;
|
||||||
};
|
};
|
||||||
|
|
||||||
void Parser::Private::parse(QIODevice *device)
|
void Parser::Private::parse(const FilePath &filePath)
|
||||||
{
|
{
|
||||||
// be sure to clean up existing data before re-allocating
|
// be sure to clean up existing data before re-allocating
|
||||||
// the callee might not have taken the parse data
|
// the callee might not have taken the parse data
|
||||||
delete data;
|
delete data;
|
||||||
data = nullptr;
|
data = nullptr;
|
||||||
|
|
||||||
QString file;
|
const QString path = filePath.path(); // FIXME: Works only accidentally for docker
|
||||||
if (auto fileDevice = qobject_cast<QFileDevice *>(device))
|
QFile file(path);
|
||||||
file = fileDevice->fileName();
|
if (!file.open(QIODevice::ReadOnly))
|
||||||
data = new ParseData(file);
|
qWarning() << "Could not open file for parsing:" << filePath.toUserOutput();
|
||||||
parseHeader(device);
|
|
||||||
while (!device->atEnd()) {
|
data = new ParseData(path);
|
||||||
QByteArray line = device->readLine();
|
parseHeader(&file);
|
||||||
|
while (!file.atEnd()) {
|
||||||
|
const QByteArray line = file.readLine();
|
||||||
// empty lines actually have no meaning - only fn= starts a new function
|
// empty lines actually have no meaning - only fn= starts a new function
|
||||||
if (line.length() > 1)
|
if (line.length() > 1)
|
||||||
dispatchLine(line);
|
dispatchLine(line);
|
||||||
@@ -639,9 +640,9 @@ void Parser::Private::parseCalledObjectFile(const char *begin, const char *end)
|
|||||||
|
|
||||||
//BEGIN Parser
|
//BEGIN Parser
|
||||||
|
|
||||||
void Parser::parse(QIODevice *device)
|
void Parser::parse(const Utils::FilePath &filePath)
|
||||||
{
|
{
|
||||||
d->parse(device);
|
d->parse(filePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
Parser::Parser()
|
Parser::Parser()
|
||||||
|
@@ -27,9 +27,7 @@
|
|||||||
|
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
|
|
||||||
QT_BEGIN_NAMESPACE
|
namespace Utils { class FilePath; }
|
||||||
class QIODevice;
|
|
||||||
QT_END_NAMESPACE
|
|
||||||
|
|
||||||
namespace Valgrind {
|
namespace Valgrind {
|
||||||
namespace Callgrind {
|
namespace Callgrind {
|
||||||
@@ -56,7 +54,7 @@ public:
|
|||||||
// get and take ownership of the parsing results. If this function is not called the repository
|
// get and take ownership of the parsing results. If this function is not called the repository
|
||||||
// will be destroyed when the parser is destroyed. Subsequent calls return null.
|
// will be destroyed when the parser is destroyed. Subsequent calls return null.
|
||||||
ParseData *takeData();
|
ParseData *takeData();
|
||||||
void parse(QIODevice *stream);
|
void parse(const Utils::FilePath &filePath);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void parserDataReady();
|
void parserDataReady();
|
||||||
|
@@ -34,11 +34,13 @@
|
|||||||
|
|
||||||
#include <debugger/analyzer/analyzermanager.h>
|
#include <debugger/analyzer/analyzermanager.h>
|
||||||
|
|
||||||
|
#include <utils/filepath.h>
|
||||||
#include <utils/qtcassert.h>
|
#include <utils/qtcassert.h>
|
||||||
#include <utils/qtcprocess.h>
|
#include <utils/qtcprocess.h>
|
||||||
|
|
||||||
using namespace ProjectExplorer;
|
using namespace ProjectExplorer;
|
||||||
using namespace Valgrind::Callgrind;
|
using namespace Valgrind::Callgrind;
|
||||||
|
using namespace Utils;
|
||||||
|
|
||||||
namespace Valgrind {
|
namespace Valgrind {
|
||||||
namespace Internal {
|
namespace Internal {
|
||||||
@@ -58,7 +60,7 @@ CallgrindToolRunner::CallgrindToolRunner(RunControl *runControl)
|
|||||||
connect(&m_controller, &CallgrindController::finished,
|
connect(&m_controller, &CallgrindController::finished,
|
||||||
this, &CallgrindToolRunner::controllerFinished);
|
this, &CallgrindToolRunner::controllerFinished);
|
||||||
connect(&m_controller, &CallgrindController::localParseDataAvailable,
|
connect(&m_controller, &CallgrindController::localParseDataAvailable,
|
||||||
this, &CallgrindToolRunner::localParseDataAvailable);
|
this, &CallgrindToolRunner::handleLocalParseData);
|
||||||
connect(&m_controller, &CallgrindController::statusMessage,
|
connect(&m_controller, &CallgrindController::statusMessage,
|
||||||
this, &CallgrindToolRunner::showStatusMessage);
|
this, &CallgrindToolRunner::showStatusMessage);
|
||||||
|
|
||||||
@@ -71,6 +73,10 @@ CallgrindToolRunner::CallgrindToolRunner(RunControl *runControl)
|
|||||||
|
|
||||||
m_controller.setValgrindRunnable(runnable());
|
m_controller.setValgrindRunnable(runnable());
|
||||||
|
|
||||||
|
static int fileCount = 100;
|
||||||
|
m_valgrindOutputFile = runnable().workingDirectory / QString("callgrind.out.f%1").arg(++fileCount);
|
||||||
|
m_controller.setValgrindOutputFile(m_valgrindOutputFile);
|
||||||
|
|
||||||
setupCallgrindRunner(this);
|
setupCallgrindRunner(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -97,6 +103,8 @@ QStringList CallgrindToolRunner::toolArguments() const
|
|||||||
if (!m_argumentForToggleCollect.isEmpty())
|
if (!m_argumentForToggleCollect.isEmpty())
|
||||||
arguments << m_argumentForToggleCollect;
|
arguments << m_argumentForToggleCollect;
|
||||||
|
|
||||||
|
arguments << "--callgrind-out-file=" + m_valgrindOutputFile.path();
|
||||||
|
|
||||||
arguments << Utils::ProcessArgs::splitArgs(m_settings.callgrindArguments.value());
|
arguments << Utils::ProcessArgs::splitArgs(m_settings.callgrindArguments.value());
|
||||||
|
|
||||||
return arguments;
|
return arguments;
|
||||||
@@ -175,18 +183,11 @@ void CallgrindToolRunner::triggerParse()
|
|||||||
m_controller.getLocalDataFile();
|
m_controller.getLocalDataFile();
|
||||||
}
|
}
|
||||||
|
|
||||||
void CallgrindToolRunner::localParseDataAvailable(const QString &file)
|
void CallgrindToolRunner::handleLocalParseData(const FilePath &outputFile)
|
||||||
{
|
{
|
||||||
// parse the callgrind file
|
|
||||||
QTC_ASSERT(!file.isEmpty(), return);
|
|
||||||
QFile outputFile(file);
|
|
||||||
QTC_ASSERT(outputFile.exists(), return);
|
QTC_ASSERT(outputFile.exists(), return);
|
||||||
if (outputFile.open(QIODevice::ReadOnly)) {
|
|
||||||
showStatusMessage(tr("Parsing Profile Data..."));
|
showStatusMessage(tr("Parsing Profile Data..."));
|
||||||
m_parser.parse(&outputFile);
|
m_parser.parse(outputFile);
|
||||||
} else {
|
|
||||||
qWarning() << "Could not open file for parsing:" << outputFile.fileName();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void CallgrindToolRunner::controllerFinished(CallgrindController::Option option)
|
void CallgrindToolRunner::controllerFinished(CallgrindController::Option option)
|
||||||
|
@@ -70,10 +70,11 @@ private:
|
|||||||
void showStatusMessage(const QString &message);
|
void showStatusMessage(const QString &message);
|
||||||
|
|
||||||
void triggerParse();
|
void triggerParse();
|
||||||
void localParseDataAvailable(const QString &file);
|
void handleLocalParseData(const Utils::FilePath &filePath);
|
||||||
void controllerFinished(Callgrind::CallgrindController::Option option);
|
void controllerFinished(Callgrind::CallgrindController::Option option);
|
||||||
|
|
||||||
bool m_markAsPaused = false;
|
bool m_markAsPaused = false;
|
||||||
|
Utils::FilePath m_valgrindOutputFile;
|
||||||
Callgrind::CallgrindController m_controller;
|
Callgrind::CallgrindController m_controller;
|
||||||
Callgrind::Parser m_parser;
|
Callgrind::Parser m_parser;
|
||||||
bool m_paused = false;
|
bool m_paused = false;
|
||||||
|
@@ -895,7 +895,7 @@ void CallgrindToolPrivate::loadExternalLogFile()
|
|||||||
QCoreApplication::processEvents();
|
QCoreApplication::processEvents();
|
||||||
|
|
||||||
Parser parser;
|
Parser parser;
|
||||||
parser.parse(&logFile);
|
parser.parse(filePath);
|
||||||
takeParserData(parser.takeData());
|
takeParserData(parser.takeData());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -138,8 +138,8 @@ bool ValgrindRunner::Private::run()
|
|||||||
if (HostOsInfo::isMacHost())
|
if (HostOsInfo::isMacHost())
|
||||||
// May be slower to start but without it we get no filenames for symbols.
|
// May be slower to start but without it we get no filenames for symbols.
|
||||||
cmd.addArg("--dsymutil=yes");
|
cmd.addArg("--dsymutil=yes");
|
||||||
cmd.addArg(m_debuggee.command.executable().toString());
|
|
||||||
cmd.addArgs(m_debuggee.command.arguments(), CommandLine::Raw);
|
cmd.addCommandLineAsArgs(m_debuggee.command);
|
||||||
|
|
||||||
emit q->valgrindExecuted(cmd.toUserOutput());
|
emit q->valgrindExecuted(cmd.toUserOutput());
|
||||||
|
|
||||||
@@ -149,10 +149,14 @@ bool ValgrindRunner::Private::run()
|
|||||||
valgrind.environment = m_debuggee.environment;
|
valgrind.environment = m_debuggee.environment;
|
||||||
valgrind.device = m_device;
|
valgrind.device = m_device;
|
||||||
|
|
||||||
if (m_device->type() == ProjectExplorer::Constants::DESKTOP_DEVICE_TYPE)
|
if (m_device->type() == ProjectExplorer::Constants::DESKTOP_DEVICE_TYPE) {
|
||||||
m_valgrindProcess.start(valgrind);
|
m_valgrindProcess.start(valgrind);
|
||||||
else
|
} else if (m_device->type() == "DockerDeviceType") {
|
||||||
|
valgrind.device = {};
|
||||||
|
m_valgrindProcess.start(valgrind);
|
||||||
|
} else {
|
||||||
m_valgrindProcess.start(valgrind, m_device);
|
m_valgrindProcess.start(valgrind, m_device);
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@@ -32,8 +32,9 @@
|
|||||||
#include <valgrind/callgrind/callgrindcostitem.h>
|
#include <valgrind/callgrind/callgrindcostitem.h>
|
||||||
#include <valgrind/callgrind/callgrindparsedata.h>
|
#include <valgrind/callgrind/callgrindparsedata.h>
|
||||||
|
|
||||||
|
#include <utils/filepath.h>
|
||||||
|
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
#include <QFile>
|
|
||||||
#include <QString>
|
#include <QString>
|
||||||
#include <QTest>
|
#include <QTest>
|
||||||
|
|
||||||
@@ -103,12 +104,8 @@ void CallgrindParserTests::cleanup()
|
|||||||
|
|
||||||
ParseData* parseDataFile(const QString &dataFile)
|
ParseData* parseDataFile(const QString &dataFile)
|
||||||
{
|
{
|
||||||
QFile file(dataFile);
|
|
||||||
Q_ASSERT(file.exists());
|
|
||||||
file.open(QIODevice::ReadOnly);
|
|
||||||
|
|
||||||
Parser p;
|
Parser p;
|
||||||
p.parse(&file);
|
p.parse(Utils::FilePath::fromString(dataFile));
|
||||||
|
|
||||||
return p.takeData();
|
return p.takeData();
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user