qmlprofilerapplication.cpp 13.8 KB
Newer Older
Kai Koehne's avatar
Kai Koehne committed
1
2
3
4
/**************************************************************************
**
** This file is part of Qt Creator
**
hjk's avatar
hjk committed
5
** Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies).
Kai Koehne's avatar
Kai Koehne committed
6
**
7
** Contact: Nokia Corporation (qt-info@nokia.com)
Kai Koehne's avatar
Kai Koehne committed
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
**
**
** GNU Lesser General Public License Usage
**
** This file may be used under the terms of the GNU Lesser General Public
** License version 2.1 as published by the Free Software Foundation and
** appearing in the file LICENSE.LGPL included in the packaging of this file.
** Please review the following information to ensure the GNU Lesser General
** Public License version 2.1 requirements will be met:
** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** In addition, as a special exception, Nokia gives you certain additional
** rights. These rights are described in the Nokia Qt LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
** Other Usage
**
** Alternatively, this file may be used in accordance with the terms and
** conditions contained in a signed written agreement between you and Nokia.
**
** If you have questions regarding the use of this file, please contact
29
** Nokia at qt-info@nokia.com.
Kai Koehne's avatar
Kai Koehne committed
30
31
32
33
**
**************************************************************************/

#include "qmlprofilerapplication.h"
Kai Koehne's avatar
Kai Koehne committed
34
#include "constants.h"
Kai Koehne's avatar
Kai Koehne committed
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
#include <utils/qtcassert.h>
#include <QtCore/QStringList>
#include <QtCore/QTextStream>
#include <QtCore/QProcess>
#include <QtCore/QTimer>
#include <QtCore/QDateTime>
#include <QtCore/QFileInfo>
#include <QtCore/QDebug>

using namespace QmlJsDebugClient;

static const char usageTextC[] =
"Usage:\n"
"    qmlprofiler [options] [program] [program-options]\n"
"    qmlprofiler [options] -attach [hostname]\n"
"\n"
Kai Koehne's avatar
Kai Koehne committed
51
"QML Profiler retrieves QML tracing data from a running application.\n"
Kai Koehne's avatar
Kai Koehne committed
52
"The data collected can then be visualized in Qt Creator.\n"
Kai Koehne's avatar
Kai Koehne committed
53
54
55
56
57
58
59
60
"\n"
"The application to be profiled has to enable QML debugging. See the Qt Creator\n"
"documentation on how to do this for different Qt versions.\n"
"\n"
"Options:\n"
"    -help  Show this information and exit.\n"
"    -fromStart\n"
"           Record as soon as the engine is started, default is false.\n"
61
"    -p <number>, -port <number>\n"
Kai Koehne's avatar
Kai Koehne committed
62
63
64
65
66
67
68
69
70
71
72
73
74
"           TCP/IP port to use, default is 3768.\n"
"    -v, -verbose\n"
"           Print debugging output.\n"
"    -version\n"
"           Show the version of qmlprofiler and exit.\n";

static const char commandTextC[] =
"Commands:\n"
"    r, record\n"
"           Switch recording on or off.\n"
"    q, quit\n"
"           Terminate program.";

75
76
static const char TraceFileExtension[] = ".qtd";

Kai Koehne's avatar
Kai Koehne committed
77
78
79
80
QmlProfilerApplication::QmlProfilerApplication(int &argc, char **argv) :
    QCoreApplication(argc, argv),
    m_runMode(LaunchMode),
    m_process(0),
Kai Koehne's avatar
Kai Koehne committed
81
    m_tracePrefix(QLatin1String("trace")),
Kai Koehne's avatar
Kai Koehne committed
82
83
84
85
    m_hostName(QLatin1String("127.0.0.1")),
    m_port(3768),
    m_verbose(false),
    m_quitAfterSave(false),
86
    m_qmlProfilerClient(&m_connection),
Christiaan Janssen's avatar
Christiaan Janssen committed
87
    m_v8profilerClient(&m_connection),
Kai Koehne's avatar
Kai Koehne committed
88
89
90
91
92
93
94
95
96
    m_connectionAttempts(0)
{
    m_connectTimer.setInterval(1000);
    connect(&m_connectTimer, SIGNAL(timeout()), this, SLOT(tryToConnect()));

    connect(&m_connection, SIGNAL(connected()), this, SLOT(connected()));
    connect(&m_connection, SIGNAL(stateChanged(QAbstractSocket::SocketState)), this, SLOT(connectionStateChanged(QAbstractSocket::SocketState)));
    connect(&m_connection, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(connectionError(QAbstractSocket::SocketError)));

97
    connect(&m_qmlProfilerClient, SIGNAL(enabledChanged()), this, SLOT(traceClientEnabled()));
98
    connect(&m_qmlProfilerClient, SIGNAL(recordingChanged(bool)), this, SLOT(recordingChanged()));
99
    connect(&m_qmlProfilerClient, SIGNAL(range(int,qint64,qint64,QStringList,QmlJsDebugClient::QmlEventLocation)), &m_eventList, SLOT(addRangedEvent(int,qint64,qint64,QStringList,QmlJsDebugClient::QmlEventLocation)));
100
    connect(&m_qmlProfilerClient, SIGNAL(complete()), this, SLOT(qmlComplete()));
Christiaan Janssen's avatar
Christiaan Janssen committed
101

102
    connect(&m_v8profilerClient, SIGNAL(enabledChanged()), this, SLOT(profilerClientEnabled()));
Christiaan Janssen's avatar
Christiaan Janssen committed
103
    connect(&m_v8profilerClient, SIGNAL(v8range(int,QString,QString,int,double,double)), &m_eventList, SLOT(addV8Event(int,QString,QString,int,double,double)));
104
    connect(&m_v8profilerClient, SIGNAL(complete()), this, SLOT(v8Complete()));
Kai Koehne's avatar
Kai Koehne committed
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127

    connect(&m_eventList, SIGNAL(error(QString)), this, SLOT(logError(QString)));
    connect(&m_eventList, SIGNAL(dataReady()), this, SLOT(traceFinished()));
}

QmlProfilerApplication::~QmlProfilerApplication()
{
    if (!m_process)
        return;
    logStatus("Terminating process ...");
    m_process->disconnect();
    m_process->terminate();
    if (!m_process->waitForFinished(1000)) {
        logStatus("Killing process ...");
        m_process->kill();
    }
    delete m_process;
}

bool QmlProfilerApplication::parseArguments()
{
    for (int argPos = 1; argPos < arguments().size(); ++argPos) {
        const QString arg = arguments().at(argPos);
Kai Koehne's avatar
Kai Koehne committed
128
        if (arg == QLatin1String("-attach") || arg == QLatin1String("-a")) {
Kai Koehne's avatar
Kai Koehne committed
129
130
131
132
133
            if (argPos + 1 == arguments().size()) {
                return false;
            }
            m_hostName = arguments().at(++argPos);
            m_runMode = AttachMode;
Kai Koehne's avatar
Kai Koehne committed
134
        } else if (arg == QLatin1String("-port") || arg == QLatin1String("-p")) {
Kai Koehne's avatar
Kai Koehne committed
135
136
137
138
139
140
141
142
143
144
            if (argPos + 1 == arguments().size()) {
                return false;
            }
            const QString portStr = arguments().at(++argPos);
            bool isNumber;
            m_port = portStr.toUShort(&isNumber);
            if (!isNumber) {
                logError(QString("'%1' is not a valid port").arg(portStr));
                return false;
            }
Kai Koehne's avatar
Kai Koehne committed
145
        } else if (arg == QLatin1String("-fromStart")) {
146
            m_qmlProfilerClient.setRecording(true);
Christiaan Janssen's avatar
Christiaan Janssen committed
147
            m_v8profilerClient.setRecording(true);
Kai Koehne's avatar
Kai Koehne committed
148
        } else if (arg == QLatin1String("-help") || arg == QLatin1String("-h") || arg == QLatin1String("/h") || arg == QLatin1String("/?")) {
Kai Koehne's avatar
Kai Koehne committed
149
            return false;
Kai Koehne's avatar
Kai Koehne committed
150
        } else if (arg == QLatin1String("-verbose") || arg == QLatin1String("-v")) {
Kai Koehne's avatar
Kai Koehne committed
151
            m_verbose = true;
Kai Koehne's avatar
Kai Koehne committed
152
        } else if (arg == QLatin1String("-version")) {
Kai Koehne's avatar
Kai Koehne committed
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
            print(QString("QML Profiler based on Qt %1.").arg(qVersion()));
            ::exit(1);
            return false;
        } else {
            if (m_programPath.isEmpty()) {
                m_programPath = arg;
                m_tracePrefix = QFileInfo(m_programPath).fileName();
            } else {
                m_programArguments << arg;
            }
        }
    }

    if (m_runMode == LaunchMode
            && m_programPath.isEmpty())
        return false;

    if (m_runMode == AttachMode
            && !m_programPath.isEmpty())
        return false;

    return true;
}

void QmlProfilerApplication::printUsage()
{
    print(QLatin1String(usageTextC));
    print(QLatin1String(commandTextC));
}

int QmlProfilerApplication::exec()
{
    QTimer::singleShot(0, this, SLOT(run()));
    return QCoreApplication::exec();
}

void QmlProfilerApplication::printCommands()
{
    print(QLatin1String(commandTextC));
}

QString QmlProfilerApplication::traceFileName() const
{
    QString fileName = m_tracePrefix + "_" +
Kai Koehne's avatar
Kai Koehne committed
197
            QDateTime::currentDateTime().toString(QLatin1String("yyMMdd_hhmmss")) + TraceFileExtension;
Kai Koehne's avatar
Kai Koehne committed
198
199
200
201
202
203
    if (QFileInfo(fileName).exists()) {
        QString baseName;
        int suffixIndex = 0;
        do {
            baseName = QFileInfo(fileName).baseName()
                    + QString::number(suffixIndex++);
204
205
        } while (QFileInfo(baseName + TraceFileExtension).exists());
        fileName = baseName + TraceFileExtension;
Kai Koehne's avatar
Kai Koehne committed
206
    }
207
208

    return QFileInfo(fileName).absoluteFilePath();
Kai Koehne's avatar
Kai Koehne committed
209
210
211
212
213
}

void QmlProfilerApplication::userCommand(const QString &command)
{
    QString cmd = command.trimmed();
Kai Koehne's avatar
Kai Koehne committed
214
215
216
    if (cmd == Constants::CMD_HELP
            || cmd == Constants::CMD_HELP2
            || cmd == Constants::CMD_HELP3) {
Kai Koehne's avatar
Kai Koehne committed
217
        printCommands();
Kai Koehne's avatar
Kai Koehne committed
218
219
    } else if (cmd == Constants::CMD_RECORD
               || cmd == Constants::CMD_RECORD2) {
220
221
        m_qmlProfilerClient.setRecording(!m_qmlProfilerClient.isRecording());
        m_v8profilerClient.setRecording(!m_v8profilerClient.isRecording());
Christiaan Janssen's avatar
Christiaan Janssen committed
222
223
        m_qmlDataReady = false;
        m_v8DataReady = false;
Kai Koehne's avatar
Kai Koehne committed
224
225
    } else if (cmd == Constants::CMD_QUIT
               || cmd == Constants::CMD_QUIT2) {
226
227
        print(QLatin1String("Quit"));
        if (m_qmlProfilerClient.isRecording()) {
Kai Koehne's avatar
Kai Koehne committed
228
            m_quitAfterSave = true;
Christiaan Janssen's avatar
Christiaan Janssen committed
229
230
            m_qmlDataReady = false;
            m_v8DataReady = false;
231
            m_qmlProfilerClient.setRecording(false);
Christiaan Janssen's avatar
Christiaan Janssen committed
232
            m_v8profilerClient.setRecording(false);
Kai Koehne's avatar
Kai Koehne committed
233
234
235
236
237
238
239
240
241
242
243
        } else {
            quit();
        }
    }
}

void QmlProfilerApplication::run()
{
    if (m_runMode == LaunchMode) {
        m_process = new QProcess(this);
        QStringList arguments;
Kai Koehne's avatar
Kai Koehne committed
244
        arguments << QString::fromLatin1("-qmljsdebugger=port:%1,block").arg(m_port);
Kai Koehne's avatar
Kai Koehne committed
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
        arguments << m_programArguments;

        m_process->setProcessChannelMode(QProcess::MergedChannels);
        connect(m_process, SIGNAL(readyRead()), this, SLOT(processHasOutput()));
        connect(m_process, SIGNAL(finished(int,QProcess::ExitStatus)), this, SLOT(processFinished()));
        logStatus(QString("Starting '%1 %2' ...").arg(m_programPath, arguments.join(" ")));
        m_process->start(m_programPath, arguments);
        if (!m_process->waitForStarted()) {
            logError(QString("Could not run '%1': %2").arg(m_programPath, m_process->errorString()));
            exit(1);
        }

    }
    m_connectTimer.start();
}

void QmlProfilerApplication::tryToConnect()
{
    Q_ASSERT(!m_connection.isConnected());
    ++ m_connectionAttempts;

266
    if (!m_verbose && !(m_connectionAttempts % 5)) {// print every 5 seconds
Kai Koehne's avatar
Kai Koehne committed
267
268
269
270
271
272
273
274
275
276
277
278
279
280
        if (!m_verbose)
            logError(QString("Could not connect to %1:%2 for %3 seconds ...").arg(
                         m_hostName, QString::number(m_port), QString::number(m_connectionAttempts)));
    }

    if (m_connection.state() == QAbstractSocket::UnconnectedState) {
        logStatus(QString("Connecting to %1:%2 ...").arg(m_hostName, QString::number(m_port)));
        m_connection.connectToHost(m_hostName, m_port);
    }
}

void QmlProfilerApplication::connected()
{
    m_connectTimer.stop();
281
282
283
    print(QString(QLatin1String("Connected to host:port %1:%2. Wait for profile data or type a command (type 'help'' to show list of commands).")).arg(m_hostName).arg((m_port)));
    QString recordingStatus(QLatin1String("Recording Status: %1"));
    if (!m_qmlProfilerClient.isRecording() && !m_v8profilerClient.isRecording())
284
        recordingStatus = recordingStatus.arg(QLatin1String("Off"));
285
    else
286
        recordingStatus = recordingStatus.arg(QLatin1String("On"));
287
    print(recordingStatus);
Kai Koehne's avatar
Kai Koehne committed
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
}

void QmlProfilerApplication::connectionStateChanged(QAbstractSocket::SocketState state)
{
    if (m_verbose)
        qDebug() << state;
}

void QmlProfilerApplication::connectionError(QAbstractSocket::SocketError error)
{
    if (m_verbose)
        qDebug() << error;
}

void QmlProfilerApplication::processHasOutput()
{
    QTC_ASSERT(m_process, return);
    while (m_process->bytesAvailable()) {
        QTextStream out(stdout);
        out << m_process->readAll();
    }
}

void QmlProfilerApplication::processFinished()
{
    QTC_ASSERT(m_process, return);
    if (m_process->exitStatus() == QProcess::NormalExit) {
        logStatus(QString("Process exited (%1).").arg(m_process->exitCode()));

317
        if (m_qmlProfilerClient.isRecording()) {
Kai Koehne's avatar
Kai Koehne committed
318
319
320
321
322
323
324
325
326
327
328
329
330
            logError("Process exited while recording, last trace is lost!");
            exit(2);
        } else {
            exit(0);
        }
    } else {
        logError("Process crashed! Exiting ...");
        exit(3);
    }
}

void QmlProfilerApplication::traceClientEnabled()
{
331
332
    if (m_verbose)
        qDebug() << "Trace client is attached.";
Kai Koehne's avatar
Kai Koehne committed
333
    logStatus("Trace client is attached.");
334
335
336
337
    // blocked server is waiting for recording message from both clients
    // once the last one is connected, both messages should be sent
    m_qmlProfilerClient.sendRecordingStatus();
    m_v8profilerClient.sendRecordingStatus();
Kai Koehne's avatar
Kai Koehne committed
338
339
}

340
341
342
343
344
void QmlProfilerApplication::profilerClientEnabled()
{
    if (m_verbose)
        qDebug() << "Profiler client is attached.";
    logStatus("Profiler client is attached.");
345
346
347
348
349

    // blocked server is waiting for recording message from both clients
    // once the last one is connected, both messages should be sent
    m_qmlProfilerClient.sendRecordingStatus();
    m_v8profilerClient.sendRecordingStatus();
350
351
}

Kai Koehne's avatar
Kai Koehne committed
352
353
354
void QmlProfilerApplication::traceFinished()
{
    const QString fileName = traceFileName();
355
356
357
358

    if (m_eventList.save(fileName))
        print(QString("Saving trace to %1.").arg(fileName));

Kai Koehne's avatar
Kai Koehne committed
359
360
361
362
363
364
    if (m_quitAfterSave)
        quit();
}

void QmlProfilerApplication::recordingChanged()
{
365
366
    if (m_qmlProfilerClient.isRecording()) {
        print(QLatin1String("Recording is on."));
Kai Koehne's avatar
Kai Koehne committed
367
    } else {
368
        print(QLatin1String("Recording is off."));
Kai Koehne's avatar
Kai Koehne committed
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
    }
}

void QmlProfilerApplication::print(const QString &line)
{
    QTextStream err(stderr);
    err << line << endl;
}

void QmlProfilerApplication::logError(const QString &error)
{
    QTextStream err(stderr);
    err << "Error: " << error << endl;
}

void QmlProfilerApplication::logStatus(const QString &status)
{
    if (!m_verbose)
        return;
    QTextStream err(stderr);
    err << status << endl;
}
Christiaan Janssen's avatar
Christiaan Janssen committed
391
392
393
394

void QmlProfilerApplication::qmlComplete()
{
    m_qmlDataReady = true;
395
    if (m_v8profilerClient.status() != QDeclarativeDebugClient::Enabled || m_v8DataReady) {
396
        m_eventList.complete();
397
398
399
        // once complete is sent, reset the flag
        m_qmlDataReady = false;
    }
Christiaan Janssen's avatar
Christiaan Janssen committed
400
401
402
403
404
}

void QmlProfilerApplication::v8Complete()
{
    m_v8DataReady = true;
405
    if (m_qmlProfilerClient.status() != QDeclarativeDebugClient::Enabled || m_qmlDataReady) {
406
        m_eventList.complete();
407
408
409
        // once complete is sent, reset the flag
        m_v8DataReady = false;
    }
Christiaan Janssen's avatar
Christiaan Janssen committed
410
}