diff options
author | Kai Koehne <kai.koehne@nokia.com> | 2011-08-03 16:41:12 +0200 |
---|---|---|
committer | Kai Koehne <kai.koehne@nokia.com> | 2011-08-22 15:30:15 +0200 |
commit | e65c040caa31aee61caf78024c1556d815a8b201 (patch) | |
tree | 7994ddb67856dc77837c8add95803e1921377837 /src/tools | |
parent | 3f957f22e70ea232e6fe65389655a582de2d119a (diff) | |
download | qt-creator-e65c040caa31aee61caf78024c1556d815a8b201.tar.gz |
QmlProfiler standalone tool
Change-Id: I9c3acdf4ef400adf3aa96adc65d49d441d57ddc0
Reviewed-on: http://codereview.qt.nokia.com/3223
Reviewed-by: Qt Sanity Bot <qt_sanity_bot@ovi.com>
Reviewed-by: Christiaan Janssen <christiaan.janssen@nokia.com>
Diffstat (limited to 'src/tools')
-rw-r--r-- | src/tools/qmlprofiler/commandlistener.cpp | 50 | ||||
-rw-r--r-- | src/tools/qmlprofiler/commandlistener.h | 54 | ||||
-rw-r--r-- | src/tools/qmlprofiler/main.cpp | 54 | ||||
-rw-r--r-- | src/tools/qmlprofiler/qmlprofiler.pro | 24 | ||||
-rw-r--r-- | src/tools/qmlprofiler/qmlprofilerapplication.cpp | 379 | ||||
-rw-r--r-- | src/tools/qmlprofiler/qmlprofilerapplication.h | 106 | ||||
-rw-r--r-- | src/tools/tools.pro | 3 |
7 files changed, 669 insertions, 1 deletions
diff --git a/src/tools/qmlprofiler/commandlistener.cpp b/src/tools/qmlprofiler/commandlistener.cpp new file mode 100644 index 0000000000..f6943704ca --- /dev/null +++ b/src/tools/qmlprofiler/commandlistener.cpp @@ -0,0 +1,50 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (info@qt.nokia.com) +** +** +** 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 +** Nokia at info@qt.nokia.com. +** +**************************************************************************/ + +#include "commandlistener.h" +#include <QTextStream> + +CommandListener::CommandListener(QObject *parent) + : QThread(parent) + , m_stopRequested(false) +{ +} + +void CommandListener::run() +{ + QString line; + QTextStream in(stdin, QIODevice::ReadOnly); + do { + line = in.readLine(); + emit command(line); + } while (!m_stopRequested && !line.isNull()); +} diff --git a/src/tools/qmlprofiler/commandlistener.h b/src/tools/qmlprofiler/commandlistener.h new file mode 100644 index 0000000000..3ed654790d --- /dev/null +++ b/src/tools/qmlprofiler/commandlistener.h @@ -0,0 +1,54 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (info@qt.nokia.com) +** +** +** 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 +** Nokia at info@qt.nokia.com. +** +**************************************************************************/ + +#ifndef COMMANDLISTENER_H +#define COMMANDLISTENER_H + +#include <QtCore/QThread> + +class CommandListener : public QThread +{ + Q_OBJECT +public: + CommandListener(QObject *parent = 0); + + void run(); + + void requestStop() { m_stopRequested = true; } +signals: + void command(const QString &command); + +private: + bool m_stopRequested; +}; + +#endif // COMMANDLISTENER_H diff --git a/src/tools/qmlprofiler/main.cpp b/src/tools/qmlprofiler/main.cpp new file mode 100644 index 0000000000..9b03ad8adf --- /dev/null +++ b/src/tools/qmlprofiler/main.cpp @@ -0,0 +1,54 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (info@qt.nokia.com) +** +** +** 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 +** Nokia at info@qt.nokia.com. +** +**************************************************************************/ + +#include "qmlprofilerapplication.h" +#include "commandlistener.h" + +int main(int argc, char *argv[]) +{ + QmlProfilerApplication app(argc, argv); + + if (!app.parseArguments()) { + app.printUsage(); + return 1; + } + + CommandListener listener; + QObject::connect(&listener, SIGNAL(command(QString)), &app, SLOT(userCommand(QString))); + listener.start(); + + int exitValue = app.exec(); + listener.terminate(); + listener.wait(); + + return exitValue; +} diff --git a/src/tools/qmlprofiler/qmlprofiler.pro b/src/tools/qmlprofiler/qmlprofiler.pro new file mode 100644 index 0000000000..3e15c56d2d --- /dev/null +++ b/src/tools/qmlprofiler/qmlprofiler.pro @@ -0,0 +1,24 @@ +include(../../../qtcreator.pri) + +TEMPLATE = app +TARGET = qmlprofiler +DESTDIR = $$IDE_APP_PATH + +QT = core +CONFIG += console +CONFIG -= app_bundle + +include(../../shared/symbianutils/symbianutils.pri) +include(../../libs/qmljsdebugclient/qmljsdebugclient-lib.pri) + +INCLUDEPATH += ../../libs/qmljsdebugclient + +SOURCES += main.cpp \ + qmlprofilerapplication.cpp \ + commandlistener.cpp + +HEADERS += \ + qmlprofilerapplication.h \ + commandlistener.h + + diff --git a/src/tools/qmlprofiler/qmlprofilerapplication.cpp b/src/tools/qmlprofiler/qmlprofilerapplication.cpp new file mode 100644 index 0000000000..e882c5f6fc --- /dev/null +++ b/src/tools/qmlprofiler/qmlprofilerapplication.cpp @@ -0,0 +1,379 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (info@qt.nokia.com) +** +** +** 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 +** Nokia at info@qt.nokia.com. +** +**************************************************************************/ + +#include "qmlprofilerapplication.h" +#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" +"QML Profiler is a command line client to retrieve tracing data from a QML engine.\n" +"The tracing data collected can then be visualized in Qt Creator.\n" +"\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" +" -p=<number>, -port=<number>\n" +" 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."; + +QmlProfilerApplication::QmlProfilerApplication(int &argc, char **argv) : + QCoreApplication(argc, argv), + m_runMode(LaunchMode), + m_process(0), + m_tracePrefix("trace"), + m_hostName(QLatin1String("127.0.0.1")), + m_port(3768), + m_recordFromStart(false), + m_verbose(false), + m_quitAfterSave(false), + m_traceClient(&m_connection), + 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))); + + connect(&m_traceClient, SIGNAL(enabled()), this, SLOT(traceClientEnabled())); + connect(&m_traceClient, SIGNAL(recordingChanged(bool)), this, SLOT(recordingChanged())); + connect(&m_traceClient, SIGNAL(range(int,qint64,qint64,QStringList,QString,int)), &m_eventList, SLOT(addRangedEvent(int,qint64,qint64,QStringList,QString,int))); + connect(&m_traceClient, SIGNAL(complete()), &m_eventList, SLOT(complete())); + + connect(&m_eventList, SIGNAL(error(QString)), this, SLOT(logError(QString))); + connect(&m_eventList, SIGNAL(dataReady()), this, SLOT(traceFinished())); + connect(&m_eventList, SIGNAL(parsingStatusChanged()), this, SLOT(parsingStatusChanged())); +} + +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); + if (arg == "-attach" || arg == "-a") { + if (argPos + 1 == arguments().size()) { + return false; + } + m_hostName = arguments().at(++argPos); + m_runMode = AttachMode; + } else if (arg == "-port" || arg == "-p") { + 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; + } + } else if (arg == "-fromStart") { + m_recordFromStart = true; + } else if (arg == "-help" || arg == "-h" || arg == "/h" || arg == "/?") { + return false; + } else if (arg == "-verbose" || arg == "-v") { + m_verbose = true; + } else if (arg == "-version") { + 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 + "_" + + QDateTime::currentDateTime().toString("yyMMdd_hhmmss") + ".xml"; + if (QFileInfo(fileName).exists()) { + QString baseName; + int suffixIndex = 0; + do { + baseName = QFileInfo(fileName).baseName() + + QString::number(suffixIndex++); + } while (QFileInfo(baseName + ".xml").exists()); + fileName = baseName + ".xml"; + } + return fileName; +} + +void QmlProfilerApplication::userCommand(const QString &command) +{ + QString cmd = command.trimmed(); + if (cmd == "help" || cmd == "h" || cmd == "?") { + printCommands(); + } else if (cmd == "r" || cmd == "record") { + m_traceClient.setRecording(!m_traceClient.isRecording()); + } else if (cmd == "q" || cmd == "quit") { + if (m_traceClient.isRecording()) { + m_quitAfterSave = true; + m_traceClient.setRecording(false); + } else { + quit(); + } + } else { + logError(QString("Unknown command '%1'").arg(cmd)); + printCommands(); + } +} + +void QmlProfilerApplication::run() +{ + if (m_runMode == LaunchMode) { + m_process = new QProcess(this); + QStringList arguments; + arguments << QString(QLatin1String("-qmljsdebugger=port:%1,block")).arg(m_port); + 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; + + if (m_connectionAttempts > 10) {// 10 seconds + 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(); + if (m_traceClient.isRecording()) { + logStatus("Connected. Recording is on."); + } else { + logStatus("Connected. Recording is off."); + } +} + +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())); + + if (m_traceClient.isRecording()) { + logError("Process exited while recording, last trace is lost!"); + exit(2); + } else { + exit(0); + } + } else { + logError("Process crashed! Exiting ..."); + exit(3); + } +} + +void QmlProfilerApplication::traceClientEnabled() +{ + logStatus("Trace client is attached."); +} + +void QmlProfilerApplication::traceFinished() +{ + const QString fileName = traceFileName(); + print("Saving trace to " + fileName); + m_eventList.save(fileName); + if (m_quitAfterSave) + quit(); +} + +void QmlProfilerApplication::parsingStatusChanged() +{ + if (m_verbose) { + switch (m_eventList.getParsingStatus()) { + case GettingDataStatus: + logStatus("Parsing - Getting data ..."); + break; + case SortingListsStatus: + logStatus("Parsing - Sorting ..."); + break; + case SortingEndsStatus: + logStatus("Parsing - Sorting done"); + break; + case ComputingLevelsStatus: + logStatus("Parsing - Computing levels ..."); + break; + case CompilingStatisticsStatus: + logStatus("Parsing - Computing statistics ..."); + break; + case DoneStatus: + logStatus("Parsing - Done."); + break; + } + } +} + +void QmlProfilerApplication::recordingChanged() +{ + QTextStream err(stderr); + if (m_traceClient.isRecording()) { + err << "Recording is on." << endl; + } else { + err << "Recording is off." << endl; + } +} + +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; +} diff --git a/src/tools/qmlprofiler/qmlprofilerapplication.h b/src/tools/qmlprofiler/qmlprofilerapplication.h new file mode 100644 index 0000000000..3bbed05ea1 --- /dev/null +++ b/src/tools/qmlprofiler/qmlprofilerapplication.h @@ -0,0 +1,106 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (info@qt.nokia.com) +** +** +** 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 +** Nokia at info@qt.nokia.com. +** +**************************************************************************/ + +#ifndef QMLPROFILERAPPLICATION_H +#define QMLPROFILERAPPLICATION_H + +#include <QtCore/QCoreApplication> +#include <QtCore/QStringList> +#include <QtCore/QTimer> + +#include <qdeclarativedebugclient.h> +#include <qmlprofilertraceclient.h> +#include <qmlprofilereventlist.h> + +QT_FORWARD_DECLARE_CLASS(QProcess) + +class QmlProfilerApplication : public QCoreApplication +{ + Q_OBJECT +public: + QmlProfilerApplication(int &argc, char **argv); + ~QmlProfilerApplication(); + + bool parseArguments(); + void printUsage(); + int exec(); + +public slots: + void userCommand(const QString &command); + +private slots: + void run(); + void tryToConnect(); + void connected(); + void connectionStateChanged(QAbstractSocket::SocketState state); + void connectionError(QAbstractSocket::SocketError error); + void processHasOutput(); + void processFinished(); + + void traceClientEnabled(); + void traceFinished(); + void parsingStatusChanged(); + void recordingChanged(); + + void print(const QString &line); + void logError(const QString &error); + void logStatus(const QString &status); + +private: + void printCommands(); + QString traceFileName() const; + + enum ApplicationMode { + LaunchMode, + AttachMode + } m_runMode; + + // LaunchMode + QString m_programPath; + QStringList m_programArguments; + QProcess *m_process; + QString m_tracePrefix; + + QString m_hostName; + quint16 m_port; + bool m_recordFromStart; + bool m_verbose; + bool m_quitAfterSave; + + QmlJsDebugClient::QDeclarativeDebugConnection m_connection; + QmlJsDebugClient::QmlProfilerTraceClient m_traceClient; + QmlJsDebugClient::QmlProfilerEventList m_eventList; + QTimer m_connectTimer; + uint m_connectionAttempts; +}; + +#endif // QMLPROFILERAPPLICATION_H diff --git a/src/tools/tools.pro b/src/tools/tools.pro index f2e6b0f0b6..3607d1b6b0 100644 --- a/src/tools/tools.pro +++ b/src/tools/tools.pro @@ -1,7 +1,8 @@ TEMPLATE = subdirs win32:SUBDIRS = qtcdebugger -SUBDIRS += qtpromaker +SUBDIRS += qtpromaker \ + qmlprofiler SUBDIRS += qmlpuppet !win32 { |