QmlProfilerTool: add tool to QDeclarative
[profile/ivi/qtdeclarative.git] / tools / qmlprofiler / qmlprofilerapplication.cpp
1 /****************************************************************************
2 **
3 ** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
4 ** Contact: http://www.qt-project.org/
5 **
6 ** This file is part of the QtDeclarative module of the Qt Toolkit.
7 **
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.
16 **
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.
20 **
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.
28 **
29 ** Other Usage
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.
32 **
33 **
34 **
35 **
36 **
37 **
38 ** $QT_END_LICENSE$
39 **
40 ****************************************************************************/
41
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>
51
52 static const char usageTextC[] =
53 "Usage:\n"
54 "    qmlprofiler [options] [program] [program-options]\n"
55 "    qmlprofiler [options] -attach [hostname]\n"
56 "\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"
59 "\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"
62 "\n"
63 "Options:\n"
64 "    -help  Show this information and exit.\n"
65 "    -fromStart\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"
69 "    -v, -verbose\n"
70 "           Print debugging output.\n"
71 "    -version\n"
72 "           Show the version of qmlprofiler and exit.\n";
73
74 static const char commandTextC[] =
75 "Commands:\n"
76 "    r, record\n"
77 "           Switch recording on or off.\n"
78 "    q, quit\n"
79 "           Terminate program.";
80
81 static const char TraceFileExtension[] = ".qtd";
82
83 QmlProfilerApplication::QmlProfilerApplication(int &argc, char **argv) :
84     QCoreApplication(argc, argv),
85     m_runMode(LaunchMode),
86     m_process(0),
87     m_tracePrefix(QLatin1String("trace")),
88     m_hostName(QLatin1String("127.0.0.1")),
89     m_port(3768),
90     m_verbose(false),
91     m_quitAfterSave(false),
92     m_declarativeProfilerClient(&m_connection),
93     m_v8profilerClient(&m_connection),
94     m_connectionAttempts(0),
95     m_declarativeDataReady(false),
96     m_v8DataReady(false)
97 {
98     m_connectTimer.setInterval(1000);
99     connect(&m_connectTimer, SIGNAL(timeout()), this, SLOT(tryToConnect()));
100
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)));
104
105     connect(&m_declarativeProfilerClient, SIGNAL(enabledChanged()), this, SLOT(traceClientEnabled()));
106     connect(&m_declarativeProfilerClient, SIGNAL(recordingChanged(bool)), this, SLOT(recordingChanged()));
107     connect(&m_declarativeProfilerClient, SIGNAL(range(QDeclarativeProfilerService::RangeType,qint64,qint64,QStringList,EventLocation)),
108             &m_profileData, SLOT(addDeclarativeEvent(QDeclarativeProfilerService::RangeType,qint64,qint64,QStringList,EventLocation)));
109     connect(&m_declarativeProfilerClient, SIGNAL(traceFinished(qint64)), &m_profileData, SLOT(setTraceEndTime(qint64)));
110     connect(&m_declarativeProfilerClient, SIGNAL(traceStarted(qint64)), &m_profileData, SLOT(setTraceStartTime(qint64)));
111     connect(&m_declarativeProfilerClient, SIGNAL(frame(qint64,int,int)), &m_profileData, SLOT(addFrameEvent(qint64,int,int)));
112     connect(&m_declarativeProfilerClient, SIGNAL(complete()), this, SLOT(declarativeComplete()));
113
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()));
118
119     connect(&m_profileData, SIGNAL(error(QString)), this, SLOT(logError(QString)));
120     connect(&m_profileData, SIGNAL(dataReady()), this, SLOT(traceFinished()));
121
122 }
123
124 QmlProfilerApplication::~QmlProfilerApplication()
125 {
126     if (!m_process)
127         return;
128     logStatus("Terminating process ...");
129     m_process->disconnect();
130     m_process->terminate();
131     if (!m_process->waitForFinished(1000)) {
132         logStatus("Killing process ...");
133         m_process->kill();
134     }
135     delete m_process;
136 }
137
138 bool QmlProfilerApplication::parseArguments()
139 {
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()) {
144                 return false;
145             }
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()) {
150                 return false;
151             }
152             const QString portStr = arguments().at(++argPos);
153             bool isNumber;
154             m_port = portStr.toUShort(&isNumber);
155             if (!isNumber) {
156                 logError(QString("'%1' is not a valid port").arg(portStr));
157                 return false;
158             }
159         } else if (arg == QLatin1String("-fromStart")) {
160             m_declarativeProfilerClient.setRecording(true);
161             m_v8profilerClient.setRecording(true);
162         } else if (arg == QLatin1String("-help") || arg == QLatin1String("-h") || arg == QLatin1String("/h") || arg == QLatin1String("/?")) {
163             return false;
164         } else if (arg == QLatin1String("-verbose") || arg == QLatin1String("-v")) {
165             m_verbose = true;
166         } else if (arg == QLatin1String("-version")) {
167             print(QString("QML Profiler based on Qt %1.").arg(qVersion()));
168             ::exit(1);
169             return false;
170         } else {
171             if (m_programPath.isEmpty()) {
172                 m_programPath = arg;
173                 m_tracePrefix = QFileInfo(m_programPath).fileName();
174             } else {
175                 m_programArguments << arg;
176             }
177         }
178     }
179
180     if (m_runMode == LaunchMode
181             && m_programPath.isEmpty())
182         return false;
183
184     if (m_runMode == AttachMode
185             && !m_programPath.isEmpty())
186         return false;
187
188     return true;
189 }
190
191 void QmlProfilerApplication::printUsage()
192 {
193     print(QLatin1String(usageTextC));
194     print(QLatin1String(commandTextC));
195 }
196
197 int QmlProfilerApplication::exec()
198 {
199     QTimer::singleShot(0, this, SLOT(run()));
200     return QCoreApplication::exec();
201 }
202
203 void QmlProfilerApplication::printCommands()
204 {
205     print(QLatin1String(commandTextC));
206 }
207
208 QString QmlProfilerApplication::traceFileName() const
209 {
210     QString fileName = m_tracePrefix + "_" +
211             QDateTime::currentDateTime().toString(QLatin1String("yyMMdd_hhmmss")) +
212             TraceFileExtension;
213     if (QFileInfo(fileName).exists()) {
214         QString baseName;
215         int suffixIndex = 0;
216         do {
217             baseName = QFileInfo(fileName).baseName()
218                     + QString::number(suffixIndex++);
219         } while (QFileInfo(baseName + TraceFileExtension).exists());
220         fileName = baseName + TraceFileExtension;
221     }
222
223     return QFileInfo(fileName).absoluteFilePath();
224 }
225
226 void QmlProfilerApplication::userCommand(const QString &command)
227 {
228     QString cmd = command.trimmed();
229     if (cmd == Constants::CMD_HELP
230             || cmd == Constants::CMD_HELP2
231             || cmd == Constants::CMD_HELP3) {
232         printCommands();
233     } else if (cmd == Constants::CMD_RECORD
234                || cmd == Constants::CMD_RECORD2) {
235         m_declarativeProfilerClient.setRecording(
236                     !m_declarativeProfilerClient.isRecording());
237         m_v8profilerClient.setRecording(!m_v8profilerClient.isRecording());
238         m_declarativeDataReady = false;
239         m_v8DataReady = false;
240     } else if (cmd == Constants::CMD_QUIT
241                || cmd == Constants::CMD_QUIT2) {
242         print(QLatin1String("Quit"));
243         if (m_declarativeProfilerClient.isRecording()) {
244             m_quitAfterSave = true;
245             m_declarativeDataReady = false;
246             m_v8DataReady = false;
247             m_declarativeProfilerClient.setRecording(false);
248             m_v8profilerClient.setRecording(false);
249         } else {
250             quit();
251         }
252     }
253 }
254
255 void QmlProfilerApplication::run()
256 {
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;
262
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()));
273             exit(1);
274         }
275
276     }
277     m_connectTimer.start();
278 }
279
280 void QmlProfilerApplication::tryToConnect()
281 {
282     Q_ASSERT(!m_connection.isConnected());
283     ++ m_connectionAttempts;
284
285     if (!m_verbose && !(m_connectionAttempts % 5)) {// print every 5 seconds
286         if (!m_verbose)
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)));
290     }
291
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);
296     }
297 }
298
299 void QmlProfilerApplication::connected()
300 {
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_declarativeProfilerClient.isRecording() &&
308             !m_v8profilerClient.isRecording())
309         recordingStatus = recordingStatus.arg(QLatin1String("Off"));
310     else
311         recordingStatus = recordingStatus.arg(QLatin1String("On"));
312     print(recordingStatus);
313 }
314
315 void QmlProfilerApplication::connectionStateChanged(
316         QAbstractSocket::SocketState state)
317 {
318     if (m_verbose)
319         qDebug() << state;
320 }
321
322 void QmlProfilerApplication::connectionError(QAbstractSocket::SocketError error)
323 {
324     if (m_verbose)
325         qDebug() << error;
326 }
327
328 void QmlProfilerApplication::processHasOutput()
329 {
330     Q_ASSERT(m_process);
331     while (m_process->bytesAvailable()) {
332         QTextStream out(stdout);
333         out << m_process->readAll();
334     }
335 }
336
337 void QmlProfilerApplication::processFinished()
338 {
339     Q_ASSERT(m_process);
340     if (m_process->exitStatus() == QProcess::NormalExit) {
341         logStatus(QString("Process exited (%1).").arg(m_process->exitCode()));
342
343         if (m_declarativeProfilerClient.isRecording()) {
344             logError("Process exited while recording, last trace is lost!");
345             exit(2);
346         } else {
347             exit(0);
348         }
349     } else {
350         logError("Process crashed! Exiting ...");
351         exit(3);
352     }
353 }
354
355 void QmlProfilerApplication::traceClientEnabled()
356 {
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_declarativeProfilerClient.sendRecordingStatus();
361     m_v8profilerClient.sendRecordingStatus();
362 }
363
364 void QmlProfilerApplication::profilerClientEnabled()
365 {
366     logStatus("Profiler client is attached.");
367
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_declarativeProfilerClient.sendRecordingStatus();
371     m_v8profilerClient.sendRecordingStatus();
372 }
373
374 void QmlProfilerApplication::traceFinished()
375 {
376     const QString fileName = traceFileName();
377
378     if (m_profileData.save(fileName))
379         print(QString("Saving trace to %1.").arg(fileName));
380
381     if (m_quitAfterSave)
382         quit();
383 }
384
385 void QmlProfilerApplication::recordingChanged()
386 {
387     if (m_declarativeProfilerClient.isRecording()) {
388         print(QLatin1String("Recording is on."));
389     } else {
390         print(QLatin1String("Recording is off."));
391     }
392 }
393
394 void QmlProfilerApplication::print(const QString &line)
395 {
396     QTextStream err(stderr);
397     err << line << endl;
398 }
399
400 void QmlProfilerApplication::logError(const QString &error)
401 {
402     QTextStream err(stderr);
403     err << "Error: " << error << endl;
404 }
405
406 void QmlProfilerApplication::logStatus(const QString &status)
407 {
408     if (!m_verbose)
409         return;
410     QTextStream err(stderr);
411     err << status << endl;
412 }
413
414 void QmlProfilerApplication::declarativeComplete()
415 {
416     m_declarativeDataReady = true;
417     if (m_v8profilerClient.state() != QDeclarativeDebugClient::Enabled ||
418             m_v8DataReady) {
419         m_profileData.complete();
420         // once complete is sent, reset the flag
421         m_declarativeDataReady = false;
422     }
423 }
424
425 void QmlProfilerApplication::v8Complete()
426 {
427     m_v8DataReady = true;
428     if (m_declarativeProfilerClient.state() != QDeclarativeDebugClient::Enabled ||
429             m_declarativeDataReady) {
430         m_profileData.complete();
431         // once complete is sent, reset the flag
432         m_v8DataReady = false;
433     }
434 }