1 /****************************************************************************
2 **
3 ** Copyright (C) 2016 The Qt Company Ltd.
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of Qt Creator.
7 **
8 ** Commercial License Usage
9 ** Licensees holding valid commercial Qt licenses may use this file in
10 ** accordance with the commercial license agreement provided with the
11 ** Software or, alternatively, in accordance with the terms contained in
12 ** a written agreement between you and The Qt Company. For licensing terms
13 ** and conditions see https://www.qt.io/terms-conditions. For further
14 ** information use the contact form at https://www.qt.io/contact-us.
15 **
16 ** GNU General Public License Usage
17 ** Alternatively, this file may be used under the terms of the GNU
18 ** General Public License version 3 as published by the Free Software
19 ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
20 ** included in the packaging of this file. Please review the following
21 ** information to ensure the GNU General Public License requirements will
22 ** be met: https://www.gnu.org/licenses/gpl-3.0.html.
23 **
24 ****************************************************************************/
25
26 #include "callgrindcontroller.h"
27
28 #include <ssh/sftpsession.h>
29 #include <ssh/sshconnectionmanager.h>
30
31 #include <utils/fileutils.h>
32 #include <utils/hostosinfo.h>
33 #include <utils/qtcassert.h>
34 #include <utils/qtcprocess.h>
35 #include <utils/temporaryfile.h>
36
37 #include <QDebug>
38 #include <QDir>
39 #include <QEventLoop>
40
41 #define CALLGRIND_CONTROL_DEBUG 0
42
43 using namespace ProjectExplorer;
44 using namespace Utils;
45
46 namespace Valgrind {
47 namespace Callgrind {
48
49 const char CALLGRIND_CONTROL_BINARY[] = "callgrind_control";
50
51 CallgrindController::CallgrindController() = default;
52
~CallgrindController()53 CallgrindController::~CallgrindController()
54 {
55 cleanupTempFile();
56 }
57
toOptionString(CallgrindController::Option option)58 static QString toOptionString(CallgrindController::Option option)
59 {
60 /* callgrind_control help from v3.9.0
61
62 Options:
63 -h --help Show this help text
64 --version Show version
65 -s --stat Show statistics
66 -b --back Show stack/back trace
67 -e [<A>,...] Show event counters for <A>,... (default: all)
68 --dump[=<s>] Request a dump optionally using <s> as description
69 -z --zero Zero all event counters
70 -k --kill Kill
71 --instr=<on|off> Switch instrumentation state on/off
72 */
73
74 switch (option) {
75 case CallgrindController::Dump:
76 return QLatin1String("--dump");
77 case CallgrindController::ResetEventCounters:
78 return QLatin1String("--zero");
79 case CallgrindController::Pause:
80 return QLatin1String("--instr=off");
81 case CallgrindController::UnPause:
82 return QLatin1String("--instr=on");
83 default:
84 return QString(); // never reached
85 }
86 }
87
run(Option option)88 void CallgrindController::run(Option option)
89 {
90 if (m_controllerProcess) {
91 emit statusMessage(tr("Previous command has not yet finished."));
92 return;
93 }
94
95 // save back current running operation
96 m_lastOption = option;
97
98 m_controllerProcess = new ApplicationLauncher;
99
100 switch (option) {
101 case CallgrindController::Dump:
102 emit statusMessage(tr("Dumping profile data..."));
103 break;
104 case CallgrindController::ResetEventCounters:
105 emit statusMessage(tr("Resetting event counters..."));
106 break;
107 case CallgrindController::Pause:
108 emit statusMessage(tr("Pausing instrumentation..."));
109 break;
110 case CallgrindController::UnPause:
111 emit statusMessage(tr("Unpausing instrumentation..."));
112 break;
113 default:
114 break;
115 }
116
117 #if CALLGRIND_CONTROL_DEBUG
118 m_controllerProcess->setProcessChannelMode(QProcess::ForwardedChannels);
119 #endif
120 connect(m_controllerProcess, &ApplicationLauncher::processExited,
121 this, &CallgrindController::controllerProcessFinished);
122 connect(m_controllerProcess, &ApplicationLauncher::error,
123 this, &CallgrindController::handleControllerProcessError);
124 connect(m_controllerProcess, &ApplicationLauncher::finished,
125 this, &CallgrindController::controllerProcessClosed);
126
127 Runnable controller = m_valgrindRunnable;
128 controller.executable = FilePath::fromString(CALLGRIND_CONTROL_BINARY);
129 controller.commandLineArguments = QString("%1 %2").arg(toOptionString(option)).arg(m_pid);
130
131 if (!m_valgrindRunnable.device
132 || m_valgrindRunnable.device->type() == ProjectExplorer::Constants::DESKTOP_DEVICE_TYPE)
133 m_controllerProcess->start(controller);
134 else
135 m_controllerProcess->start(controller, m_valgrindRunnable.device);
136 }
137
setValgrindPid(qint64 pid)138 void CallgrindController::setValgrindPid(qint64 pid)
139 {
140 m_pid = pid;
141 }
142
handleControllerProcessError(QProcess::ProcessError)143 void CallgrindController::handleControllerProcessError(QProcess::ProcessError)
144 {
145 QTC_ASSERT(m_controllerProcess, return);
146 const QString error = m_controllerProcess->errorString();
147 emit statusMessage(tr("An error occurred while trying to run %1: %2").arg(CALLGRIND_CONTROL_BINARY).arg(error));
148
149 m_controllerProcess->deleteLater();
150 m_controllerProcess = nullptr;
151 }
152
controllerProcessFinished(int rc,QProcess::ExitStatus status)153 void CallgrindController::controllerProcessFinished(int rc, QProcess::ExitStatus status)
154 {
155 QTC_ASSERT(m_controllerProcess, return);
156 const QString error = m_controllerProcess->errorString();
157
158 m_controllerProcess->deleteLater(); // Called directly from finished() signal in m_process
159 m_controllerProcess = nullptr;
160
161 if (rc != 0 || status != QProcess::NormalExit) {
162 qWarning() << "Controller exited abnormally:" << error;
163 return;
164 }
165
166 // this call went fine, we might run another task after this
167 switch (m_lastOption) {
168 case ResetEventCounters:
169 // lets dump the new reset profiling info
170 run(Dump);
171 return;
172 case Pause:
173 break;
174 case Dump:
175 emit statusMessage(tr("Callgrind dumped profiling info"));
176 break;
177 case UnPause:
178 emit statusMessage(tr("Callgrind unpaused."));
179 break;
180 default:
181 break;
182 }
183
184 emit finished(m_lastOption);
185 m_lastOption = Unknown;
186 }
187
controllerProcessClosed(bool success)188 void CallgrindController::controllerProcessClosed(bool success)
189 {
190 Q_UNUSED(success)
191 // QTC_ASSERT(m_remote.m_process, return);
192
193 // m_remote.m_errorString = m_remote.m_process->errorString();
194 // if (status == QSsh::SshRemoteProcess::FailedToStart) {
195 // m_remote.m_error = QProcess::FailedToStart;
196 // emit ValgrindProcessX::error(QProcess::FailedToStart);
197 // } else if (status == QSsh::SshRemoteProcess::NormalExit) {
198 // emit finished(m_remote.m_process->exitCode(), QProcess::NormalExit);
199 // } else if (status == QSsh::SshRemoteProcess::CrashExit) {
200 // m_remote.m_error = QProcess::Crashed;
201 // emit finished(m_remote.m_process->exitCode(), QProcess::CrashExit);
202 // }
203 controllerProcessFinished(0, QProcess::NormalExit);
204 }
205
getLocalDataFile()206 void CallgrindController::getLocalDataFile()
207 {
208 // we look for callgrind.out.PID, but there may be updated ones called ~.PID.NUM
209 const QString baseFileName = QString("callgrind.out.%1").arg(m_pid);
210 const QString workingDir = m_valgrindRunnable.workingDirectory;
211 // first, set the to-be-parsed file to callgrind.out.PID
212 QString fileName = workingDir.isEmpty() ? baseFileName : (workingDir + '/' + baseFileName);
213
214 if (m_valgrindRunnable.device
215 && m_valgrindRunnable.device->type() != ProjectExplorer::Constants::DESKTOP_DEVICE_TYPE) {
216 // ///TODO: error handling
217 // emit statusMessage(tr("Downloading remote profile data..."));
218 // m_ssh = m_valgrindProc->connection();
219 // // if there are files like callgrind.out.PID.NUM, set it to the most recent one of those
220 // QString cmd = QString::fromLatin1("ls -t %1* | head -n 1").arg(fileName);
221 // m_findRemoteFile = m_ssh->createRemoteProcess(cmd.toUtf8());
222 // connect(m_findRemoteFile.data(), &QSsh::SshRemoteProcess::readyReadStandardOutput,
223 // this, &CallgrindController::foundRemoteFile);
224 // m_findRemoteFile->start();
225 } else {
226 const QDir dir(workingDir, QString::fromLatin1("%1.*").arg(baseFileName), QDir::Time);
227 const QStringList outputFiles = dir.entryList();
228 // if there are files like callgrind.out.PID.NUM, set it to the most recent one of those
229 if (!outputFiles.isEmpty())
230 fileName = workingDir + '/' + outputFiles.first();
231
232 emit localParseDataAvailable(fileName);
233 }
234 }
235
foundRemoteFile()236 void CallgrindController::foundRemoteFile()
237 {
238 m_remoteFile = m_findRemoteFile->readAllStandardOutput().trimmed();
239
240 m_sftp = m_ssh->createSftpSession();
241 connect(m_sftp.get(), &QSsh::SftpSession::commandFinished,
242 this, &CallgrindController::sftpJobFinished);
243 connect(m_sftp.get(), &QSsh::SftpSession::started,
244 this, &CallgrindController::sftpInitialized);
245 m_sftp->start();
246 }
247
sftpInitialized()248 void CallgrindController::sftpInitialized()
249 {
250 cleanupTempFile();
251 Utils::TemporaryFile dataFile("callgrind.out.");
252 QTC_ASSERT(dataFile.open(), return);
253 m_tempDataFile = dataFile.fileName();
254 dataFile.setAutoRemove(false);
255 dataFile.close();
256
257 m_downloadJob = m_sftp->downloadFile(QString::fromUtf8(m_remoteFile), m_tempDataFile);
258 }
259
sftpJobFinished(QSsh::SftpJobId job,const QString & error)260 void CallgrindController::sftpJobFinished(QSsh::SftpJobId job, const QString &error)
261 {
262 QTC_ASSERT(job == m_downloadJob, return);
263
264 m_sftp->quit();
265
266 if (error.isEmpty())
267 emit localParseDataAvailable(m_tempDataFile);
268 }
269
cleanupTempFile()270 void CallgrindController::cleanupTempFile()
271 {
272 if (!m_tempDataFile.isEmpty() && QFile::exists(m_tempDataFile))
273 QFile::remove(m_tempDataFile);
274
275 m_tempDataFile.clear();
276 }
277
setValgrindRunnable(const Runnable & runnable)278 void CallgrindController::setValgrindRunnable(const Runnable &runnable)
279 {
280 m_valgrindRunnable = runnable;
281 }
282
283 } // namespace Callgrind
284 } // namespace Valgrind
285