/**************************************************************************** ** ** Copyright (C) 2016 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of Qt Creator. ** ** Commercial License Usage ** Licensees holding valid commercial Qt licenses may use this file in ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and The Qt Company. For licensing terms ** and conditions see https://www.qt.io/terms-conditions. For further ** information use the contact form at https://www.qt.io/contact-us. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 3 as published by the Free Software ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT ** included in the packaging of this file. Please review the following ** information to ensure the GNU General Public License requirements will ** be met: https://www.gnu.org/licenses/gpl-3.0.html. ** ****************************************************************************/ #include "callgrindcontroller.h" #include #include #include #include #include #include #include #include #include #include #define CALLGRIND_CONTROL_DEBUG 0 using namespace ProjectExplorer; using namespace Utils; namespace Valgrind { namespace Callgrind { const char CALLGRIND_CONTROL_BINARY[] = "callgrind_control"; CallgrindController::CallgrindController() { } CallgrindController::~CallgrindController() { cleanupTempFile(); } static QString toOptionString(CallgrindController::Option option) { /* callgrind_control help from v3.9.0 Options: -h --help Show this help text --version Show version -s --stat Show statistics -b --back Show stack/back trace -e [,...] Show event counters for ,... (default: all) --dump[=] Request a dump optionally using as description -z --zero Zero all event counters -k --kill Kill --instr= Switch instrumentation state on/off */ switch (option) { case CallgrindController::Dump: return QLatin1String("--dump"); case CallgrindController::ResetEventCounters: return QLatin1String("--zero"); case CallgrindController::Pause: return QLatin1String("--instr=off"); case CallgrindController::UnPause: return QLatin1String("--instr=on"); default: return QString(); // never reached } } void CallgrindController::run(Option option) { if (m_controllerProcess) { emit statusMessage(tr("Previous command has not yet finished.")); return; } // save back current running operation m_lastOption = option; m_controllerProcess = new ApplicationLauncher; switch (option) { case CallgrindController::Dump: emit statusMessage(tr("Dumping profile data...")); break; case CallgrindController::ResetEventCounters: emit statusMessage(tr("Resetting event counters...")); break; case CallgrindController::Pause: emit statusMessage(tr("Pausing instrumentation...")); break; case CallgrindController::UnPause: emit statusMessage(tr("Unpausing instrumentation...")); break; default: break; } #if CALLGRIND_CONTROL_DEBUG m_controllerProcess->setProcessChannelMode(QProcess::ForwardedChannels); #endif connect(m_controllerProcess, &ApplicationLauncher::processExited, this, &CallgrindController::controllerProcessFinished); connect(m_controllerProcess, &ApplicationLauncher::error, this, &CallgrindController::handleControllerProcessError); connect(m_controllerProcess, &ApplicationLauncher::finished, this, &CallgrindController::controllerProcessClosed); Runnable controller = m_valgrindRunnable; controller.executable = CALLGRIND_CONTROL_BINARY; controller.commandLineArguments = QString("%1 %2").arg(toOptionString(option)).arg(m_pid); if (!m_valgrindRunnable.device || m_valgrindRunnable.device->type() == ProjectExplorer::Constants::DESKTOP_DEVICE_TYPE) m_controllerProcess->start(controller); else m_controllerProcess->start(controller, m_valgrindRunnable.device); } void CallgrindController::setValgrindPid(qint64 pid) { m_pid = pid; } void CallgrindController::handleControllerProcessError(QProcess::ProcessError) { QTC_ASSERT(m_controllerProcess, return); const QString error = m_controllerProcess->errorString(); emit statusMessage(tr("An error occurred while trying to run %1: %2").arg(CALLGRIND_CONTROL_BINARY).arg(error)); m_controllerProcess->deleteLater(); m_controllerProcess = nullptr; } void CallgrindController::controllerProcessFinished(int rc, QProcess::ExitStatus status) { QTC_ASSERT(m_controllerProcess, return); const QString error = m_controllerProcess->errorString(); m_controllerProcess->deleteLater(); // Called directly from finished() signal in m_process m_controllerProcess = nullptr; if (rc != 0 || status != QProcess::NormalExit) { qWarning() << "Controller exited abnormally:" << error; return; } // this call went fine, we might run another task after this switch (m_lastOption) { case ResetEventCounters: // lets dump the new reset profiling info run(Dump); return; case Pause: break; case Dump: emit statusMessage(tr("Callgrind dumped profiling info")); break; case UnPause: emit statusMessage(tr("Callgrind unpaused.")); break; default: break; } emit finished(m_lastOption); m_lastOption = Unknown; } void CallgrindController::controllerProcessClosed(bool success) { Q_UNUSED(success); // QTC_ASSERT(m_remote.m_process, return); // m_remote.m_errorString = m_remote.m_process->errorString(); // if (status == QSsh::SshRemoteProcess::FailedToStart) { // m_remote.m_error = QProcess::FailedToStart; // emit ValgrindProcessX::error(QProcess::FailedToStart); // } else if (status == QSsh::SshRemoteProcess::NormalExit) { // emit finished(m_remote.m_process->exitCode(), QProcess::NormalExit); // } else if (status == QSsh::SshRemoteProcess::CrashExit) { // m_remote.m_error = QProcess::Crashed; // emit finished(m_remote.m_process->exitCode(), QProcess::CrashExit); // } controllerProcessFinished(0, QProcess::NormalExit); } void CallgrindController::getLocalDataFile() { // we look for callgrind.out.PID, but there may be updated ones called ~.PID.NUM const QString baseFileName = QString("callgrind.out.%1").arg(m_pid); const QString workingDir = m_valgrindRunnable.workingDirectory; // first, set the to-be-parsed file to callgrind.out.PID QString fileName = workingDir.isEmpty() ? baseFileName : (workingDir + '/' + baseFileName); if (m_valgrindRunnable.device && m_valgrindRunnable.device->type() != ProjectExplorer::Constants::DESKTOP_DEVICE_TYPE) { // ///TODO: error handling // emit statusMessage(tr("Downloading remote profile data...")); // 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_findRemoteFile = m_ssh->createRemoteProcess(cmd.toUtf8()); // connect(m_findRemoteFile.data(), &QSsh::SshRemoteProcess::readyReadStandardOutput, // this, &CallgrindController::foundRemoteFile); // m_findRemoteFile->start(); } else { 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); } } void CallgrindController::foundRemoteFile() { m_remoteFile = m_findRemoteFile->readAllStandardOutput().trimmed(); m_sftp = m_ssh->createSftpChannel(); connect(m_sftp.data(), &QSsh::SftpChannel::finished, this, &CallgrindController::sftpJobFinished); connect(m_sftp.data(), &QSsh::SftpChannel::initialized, this, &CallgrindController::sftpInitialized); m_sftp->initialize(); } void CallgrindController::sftpInitialized() { cleanupTempFile(); 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, QSsh::SftpOverwriteExisting); } void CallgrindController::sftpJobFinished(QSsh::SftpJobId job, const QString &error) { QTC_ASSERT(job == m_downloadJob, return); m_sftp->closeChannel(); if (error.isEmpty()) emit localParseDataAvailable(m_tempDataFile); } void CallgrindController::cleanupTempFile() { if (!m_tempDataFile.isEmpty() && QFile::exists(m_tempDataFile)) QFile::remove(m_tempDataFile); m_tempDataFile.clear(); } void CallgrindController::setValgrindRunnable(const Runnable &runnable) { m_valgrindRunnable = runnable; } } // namespace Callgrind } // namespace Valgrind