1 /****************************************************************************
3 ** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
4 ** Contact: http://www.qt-project.org/
6 ** This file is part of the QtQml module of the Qt Toolkit.
8 ** $QT_BEGIN_LICENSE:LGPL$
9 ** GNU Lesser General Public License Usage
10 ** This file may be used under the terms of the GNU Lesser General Public
11 ** License version 2.1 as published by the Free Software Foundation and
12 ** appearing in the file LICENSE.LGPL included in the packaging of this
13 ** file. Please review the following information to ensure the GNU Lesser
14 ** General Public License version 2.1 requirements will be met:
15 ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
17 ** In addition, as a special exception, Nokia gives you certain additional
18 ** rights. These rights are described in the Nokia Qt LGPL Exception
19 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
21 ** GNU General Public License Usage
22 ** Alternatively, this file may be used under the terms of the GNU General
23 ** Public License version 3.0 as published by the Free Software Foundation
24 ** and appearing in the file LICENSE.GPL included in the packaging of this
25 ** file. Please review the following information to ensure the GNU General
26 ** Public License version 3.0 requirements will be met:
27 ** http://www.gnu.org/copyleft/gpl.html.
30 ** Alternatively, this file may be used in accordance with the terms and
31 ** conditions contained in a signed written agreement between you and Nokia.
40 ****************************************************************************/
42 #include "qmlprofilerapplication.h"
43 #include "constants.h"
44 #include <QtCore/QStringList>
45 #include <QtCore/QTextStream>
46 #include <QtCore/QProcess>
47 #include <QtCore/QTimer>
48 #include <QtCore/QDateTime>
49 #include <QtCore/QFileInfo>
50 #include <QtCore/QDebug>
52 static const char usageTextC[] =
54 " qmlprofiler [options] [program] [program-options]\n"
55 " qmlprofiler [options] -attach [hostname]\n"
57 "QML Profiler retrieves QML tracing data from a running application.\n"
58 "The data collected can then be visualized in Qt Creator.\n"
60 "The application to be profiled has to enable QML debugging. See the Qt Creator\n"
61 "documentation on how to do this for different Qt versions.\n"
64 " -help Show this information and exit.\n"
66 " Record as soon as the engine is started, default is false.\n"
67 " -p <number>, -port <number>\n"
68 " TCP/IP port to use, default is 3768.\n"
70 " Print debugging output.\n"
72 " Show the version of qmlprofiler and exit.\n";
74 static const char commandTextC[] =
77 " Switch recording on or off.\n"
79 " Terminate program.";
81 static const char TraceFileExtension[] = ".qtd";
83 QmlProfilerApplication::QmlProfilerApplication(int &argc, char **argv) :
84 QCoreApplication(argc, argv),
85 m_runMode(LaunchMode),
87 m_tracePrefix(QLatin1String("trace")),
88 m_hostName(QLatin1String("127.0.0.1")),
91 m_quitAfterSave(false),
92 m_qmlProfilerClient(&m_connection),
93 m_v8profilerClient(&m_connection),
94 m_connectionAttempts(0),
95 m_qmlDataReady(false),
98 m_connectTimer.setInterval(1000);
99 connect(&m_connectTimer, SIGNAL(timeout()), this, SLOT(tryToConnect()));
101 connect(&m_connection, SIGNAL(connected()), this, SLOT(connected()));
102 connect(&m_connection, SIGNAL(stateChanged(QAbstractSocket::SocketState)), this, SLOT(connectionStateChanged(QAbstractSocket::SocketState)));
103 connect(&m_connection, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(connectionError(QAbstractSocket::SocketError)));
105 connect(&m_qmlProfilerClient, SIGNAL(enabledChanged()), this, SLOT(traceClientEnabled()));
106 connect(&m_qmlProfilerClient, SIGNAL(recordingChanged(bool)), this, SLOT(recordingChanged()));
107 connect(&m_qmlProfilerClient, SIGNAL(range(QQmlProfilerService::RangeType,qint64,qint64,QStringList,EventLocation)),
108 &m_profileData, SLOT(addQmlEvent(QQmlProfilerService::RangeType,qint64,qint64,QStringList,EventLocation)));
109 connect(&m_qmlProfilerClient, SIGNAL(traceFinished(qint64)), &m_profileData, SLOT(setTraceEndTime(qint64)));
110 connect(&m_qmlProfilerClient, SIGNAL(traceStarted(qint64)), &m_profileData, SLOT(setTraceStartTime(qint64)));
111 connect(&m_qmlProfilerClient, SIGNAL(frame(qint64,int,int)), &m_profileData, SLOT(addFrameEvent(qint64,int,int)));
112 connect(&m_qmlProfilerClient, SIGNAL(complete()), this, SLOT(qmlComplete()));
114 connect(&m_v8profilerClient, SIGNAL(enabledChanged()), this, SLOT(profilerClientEnabled()));
115 connect(&m_v8profilerClient, SIGNAL(range(int,QString,QString,int,double,double)),
116 &m_profileData, SLOT(addV8Event(int,QString,QString,int,double,double)));
117 connect(&m_v8profilerClient, SIGNAL(complete()), this, SLOT(v8Complete()));
119 connect(&m_profileData, SIGNAL(error(QString)), this, SLOT(logError(QString)));
120 connect(&m_profileData, SIGNAL(dataReady()), this, SLOT(traceFinished()));
124 QmlProfilerApplication::~QmlProfilerApplication()
128 logStatus("Terminating process ...");
129 m_process->disconnect();
130 m_process->terminate();
131 if (!m_process->waitForFinished(1000)) {
132 logStatus("Killing process ...");
138 bool QmlProfilerApplication::parseArguments()
140 for (int argPos = 1; argPos < arguments().size(); ++argPos) {
141 const QString arg = arguments().at(argPos);
142 if (arg == QLatin1String("-attach") || arg == QLatin1String("-a")) {
143 if (argPos + 1 == arguments().size()) {
146 m_hostName = arguments().at(++argPos);
147 m_runMode = AttachMode;
148 } else if (arg == QLatin1String("-port") || arg == QLatin1String("-p")) {
149 if (argPos + 1 == arguments().size()) {
152 const QString portStr = arguments().at(++argPos);
154 m_port = portStr.toUShort(&isNumber);
156 logError(QString("'%1' is not a valid port").arg(portStr));
159 } else if (arg == QLatin1String("-fromStart")) {
160 m_qmlProfilerClient.setRecording(true);
161 m_v8profilerClient.setRecording(true);
162 } else if (arg == QLatin1String("-help") || arg == QLatin1String("-h") || arg == QLatin1String("/h") || arg == QLatin1String("/?")) {
164 } else if (arg == QLatin1String("-verbose") || arg == QLatin1String("-v")) {
166 } else if (arg == QLatin1String("-version")) {
167 print(QString("QML Profiler based on Qt %1.").arg(qVersion()));
171 if (m_programPath.isEmpty()) {
173 m_tracePrefix = QFileInfo(m_programPath).fileName();
175 m_programArguments << arg;
180 if (m_runMode == LaunchMode
181 && m_programPath.isEmpty())
184 if (m_runMode == AttachMode
185 && !m_programPath.isEmpty())
191 void QmlProfilerApplication::printUsage()
193 print(QLatin1String(usageTextC));
194 print(QLatin1String(commandTextC));
197 int QmlProfilerApplication::exec()
199 QTimer::singleShot(0, this, SLOT(run()));
200 return QCoreApplication::exec();
203 void QmlProfilerApplication::printCommands()
205 print(QLatin1String(commandTextC));
208 QString QmlProfilerApplication::traceFileName() const
210 QString fileName = m_tracePrefix + "_" +
211 QDateTime::currentDateTime().toString(QLatin1String("yyMMdd_hhmmss")) +
213 if (QFileInfo(fileName).exists()) {
217 baseName = QFileInfo(fileName).baseName()
218 + QString::number(suffixIndex++);
219 } while (QFileInfo(baseName + TraceFileExtension).exists());
220 fileName = baseName + TraceFileExtension;
223 return QFileInfo(fileName).absoluteFilePath();
226 void QmlProfilerApplication::userCommand(const QString &command)
228 QString cmd = command.trimmed();
229 if (cmd == Constants::CMD_HELP
230 || cmd == Constants::CMD_HELP2
231 || cmd == Constants::CMD_HELP3) {
233 } else if (cmd == Constants::CMD_RECORD
234 || cmd == Constants::CMD_RECORD2) {
235 m_qmlProfilerClient.setRecording(
236 !m_qmlProfilerClient.isRecording());
237 m_v8profilerClient.setRecording(!m_v8profilerClient.isRecording());
238 m_qmlDataReady = false;
239 m_v8DataReady = false;
240 } else if (cmd == Constants::CMD_QUIT
241 || cmd == Constants::CMD_QUIT2) {
242 print(QLatin1String("Quit"));
243 if (m_qmlProfilerClient.isRecording()) {
244 m_quitAfterSave = true;
245 m_qmlDataReady = false;
246 m_v8DataReady = false;
247 m_qmlProfilerClient.setRecording(false);
248 m_v8profilerClient.setRecording(false);
255 void QmlProfilerApplication::run()
257 if (m_runMode == LaunchMode) {
258 m_process = new QProcess(this);
259 QStringList arguments;
260 arguments << QString::fromLatin1("-qmljsdebugger=port:%1,block").arg(m_port);
261 arguments << m_programArguments;
263 m_process->setProcessChannelMode(QProcess::MergedChannels);
264 connect(m_process, SIGNAL(readyRead()), this, SLOT(processHasOutput()));
265 connect(m_process, SIGNAL(finished(int,QProcess::ExitStatus)), this,
266 SLOT(processFinished()));
267 logStatus(QString("Starting '%1 %2' ...").arg(m_programPath,
268 arguments.join(" ")));
269 m_process->start(m_programPath, arguments);
270 if (!m_process->waitForStarted()) {
271 logError(QString("Could not run '%1': %2").arg(m_programPath,
272 m_process->errorString()));
277 m_connectTimer.start();
280 void QmlProfilerApplication::tryToConnect()
282 Q_ASSERT(!m_connection.isConnected());
283 ++ m_connectionAttempts;
285 if (!m_verbose && !(m_connectionAttempts % 5)) {// print every 5 seconds
287 logError(QString("Could not connect to %1:%2 for %3 seconds ...").arg(
288 m_hostName, QString::number(m_port),
289 QString::number(m_connectionAttempts)));
292 if (m_connection.state() == QAbstractSocket::UnconnectedState) {
293 logStatus(QString("Connecting to %1:%2 ...").arg(m_hostName,
294 QString::number(m_port)));
295 m_connection.connectToHost(m_hostName, m_port);
299 void QmlProfilerApplication::connected()
301 m_connectTimer.stop();
302 print(QString(QLatin1String("Connected to host:port %1:%2."
303 "Wait for profile data or type a command"
304 "(type 'help'' to show list of commands).")
305 ).arg(m_hostName).arg((m_port)));
306 QString recordingStatus(QLatin1String("Recording Status: %1"));
307 if (!m_qmlProfilerClient.isRecording() &&
308 !m_v8profilerClient.isRecording())
309 recordingStatus = recordingStatus.arg(QLatin1String("Off"));
311 recordingStatus = recordingStatus.arg(QLatin1String("On"));
312 print(recordingStatus);
315 void QmlProfilerApplication::connectionStateChanged(
316 QAbstractSocket::SocketState state)
322 void QmlProfilerApplication::connectionError(QAbstractSocket::SocketError error)
328 void QmlProfilerApplication::processHasOutput()
331 while (m_process->bytesAvailable()) {
332 QTextStream out(stdout);
333 out << m_process->readAll();
337 void QmlProfilerApplication::processFinished()
340 if (m_process->exitStatus() == QProcess::NormalExit) {
341 logStatus(QString("Process exited (%1).").arg(m_process->exitCode()));
343 if (m_qmlProfilerClient.isRecording()) {
344 logError("Process exited while recording, last trace is lost!");
350 logError("Process crashed! Exiting ...");
355 void QmlProfilerApplication::traceClientEnabled()
357 logStatus("Trace client is attached.");
358 // blocked server is waiting for recording message from both clients
359 // once the last one is connected, both messages should be sent
360 m_qmlProfilerClient.sendRecordingStatus();
361 m_v8profilerClient.sendRecordingStatus();
364 void QmlProfilerApplication::profilerClientEnabled()
366 logStatus("Profiler client is attached.");
368 // blocked server is waiting for recording message from both clients
369 // once the last one is connected, both messages should be sent
370 m_qmlProfilerClient.sendRecordingStatus();
371 m_v8profilerClient.sendRecordingStatus();
374 void QmlProfilerApplication::traceFinished()
376 const QString fileName = traceFileName();
378 if (m_profileData.save(fileName))
379 print(QString("Saving trace to %1.").arg(fileName));
385 void QmlProfilerApplication::recordingChanged()
387 if (m_qmlProfilerClient.isRecording()) {
388 print(QLatin1String("Recording is on."));
390 print(QLatin1String("Recording is off."));
394 void QmlProfilerApplication::print(const QString &line)
396 QTextStream err(stderr);
400 void QmlProfilerApplication::logError(const QString &error)
402 QTextStream err(stderr);
403 err << "Error: " << error << endl;
406 void QmlProfilerApplication::logStatus(const QString &status)
410 QTextStream err(stderr);
411 err << status << endl;
414 void QmlProfilerApplication::qmlComplete()
416 m_qmlDataReady = true;
417 if (m_v8profilerClient.state() != QQmlDebugClient::Enabled ||
419 m_profileData.complete();
420 // once complete is sent, reset the flag
421 m_qmlDataReady = false;
425 void QmlProfilerApplication::v8Complete()
427 m_v8DataReady = true;
428 if (m_qmlProfilerClient.state() != QQmlDebugClient::Enabled ||
430 m_profileData.complete();
431 // once complete is sent, reset the flag
432 m_v8DataReady = false;