From 6c24d2feb43f6d640d83ee6e5ac01a1bdac3d0ce Mon Sep 17 00:00:00 2001 From: Aurindam Jana Date: Thu, 9 Feb 2012 17:04:43 +0100 Subject: QmlProfilerTool: add tool to QDeclarative qmlprofiler is a standalone tool used to record profiling data of QML apps. The data is stored in a file which can be loaded in QtCreator for investigation. Change-Id: I308f4c40bc3876933bd0d32c336cef6cd6f5fb4a Reviewed-by: Kai Koehne --- tools/qmlprofiler/commandlistener.cpp | 66 + tools/qmlprofiler/commandlistener.h | 63 + tools/qmlprofiler/constants.h | 68 + tools/qmlprofiler/main.cpp | 64 + tools/qmlprofiler/profileclient.cpp | 300 ++++ tools/qmlprofiler/profileclient.h | 139 ++ tools/qmlprofiler/profiledata.cpp | 1902 ++++++++++++++++++++++++++ tools/qmlprofiler/profiledata.h | 247 ++++ tools/qmlprofiler/qmlprofiler.pro | 25 + tools/qmlprofiler/qmlprofilerapplication.cpp | 434 ++++++ tools/qmlprofiler/qmlprofilerapplication.h | 117 ++ tools/tools.pro | 2 +- 12 files changed, 3426 insertions(+), 1 deletion(-) create mode 100644 tools/qmlprofiler/commandlistener.cpp create mode 100644 tools/qmlprofiler/commandlistener.h create mode 100644 tools/qmlprofiler/constants.h create mode 100644 tools/qmlprofiler/main.cpp create mode 100644 tools/qmlprofiler/profileclient.cpp create mode 100644 tools/qmlprofiler/profileclient.h create mode 100644 tools/qmlprofiler/profiledata.cpp create mode 100644 tools/qmlprofiler/profiledata.h create mode 100644 tools/qmlprofiler/qmlprofiler.pro create mode 100644 tools/qmlprofiler/qmlprofilerapplication.cpp create mode 100644 tools/qmlprofiler/qmlprofilerapplication.h (limited to 'tools') diff --git a/tools/qmlprofiler/commandlistener.cpp b/tools/qmlprofiler/commandlistener.cpp new file mode 100644 index 0000000000..cc969160d6 --- /dev/null +++ b/tools/qmlprofiler/commandlistener.cpp @@ -0,0 +1,66 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/ +** +** This file is part of the QtDeclarative module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** 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. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL included in the packaging of this +** file. Please review the following information to ensure the GNU General +** Public License version 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** 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. +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "commandlistener.h" +#include "constants.h" +#include + +CommandListener::CommandListener(QObject *parent) + : QThread(parent) + , m_stopRequested(false) +{ +} + +void CommandListener::run() +{ + QString line; + QTextStream in(stdin, QIODevice::ReadOnly); + do { + line = in.readLine(); + line = line.trimmed(); + if (!line.isEmpty()) { + emit command(line); + if (line == QLatin1String(Constants::CMD_QUIT) + || line == QLatin1String(Constants::CMD_QUIT2)) + return; + } + } while (!m_stopRequested && !line.isNull()); +} diff --git a/tools/qmlprofiler/commandlistener.h b/tools/qmlprofiler/commandlistener.h new file mode 100644 index 0000000000..77fd79362d --- /dev/null +++ b/tools/qmlprofiler/commandlistener.h @@ -0,0 +1,63 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/ +** +** This file is part of the QtDeclarative module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** 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. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL included in the packaging of this +** file. Please review the following information to ensure the GNU General +** Public License version 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** 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. +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef COMMANDLISTENER_H +#define COMMANDLISTENER_H + +#include + +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/tools/qmlprofiler/constants.h b/tools/qmlprofiler/constants.h new file mode 100644 index 0000000000..2e6acda275 --- /dev/null +++ b/tools/qmlprofiler/constants.h @@ -0,0 +1,68 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/ +** +** This file is part of the QtDeclarative module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** 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. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL included in the packaging of this +** file. Please review the following information to ensure the GNU General +** Public License version 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** 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. +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef CONSTANTS_H +#define CONSTANTS_H + +namespace Constants { + +const char CMD_HELP[] ="help"; +const char CMD_HELP2[] = "h"; +const char CMD_HELP3[] = "?"; + +const char CMD_RECORD[] ="record"; +const char CMD_RECORD2[] ="r"; + +const char CMD_QUIT[] ="quit"; +const char CMD_QUIT2[] = "q"; + +const char TYPE_PAINTING_STR[] = "Painting"; +const char TYPE_COMPILING_STR[] = "Compiling"; +const char TYPE_CREATING_STR[] = "Creating"; +const char TYPE_BINDING_STR[] = "Binding"; +const char TYPE_HANDLINGSIGNAL_STR[] = "HandlingSignal"; +const char PROFILER_FILE_VERSION[] = "1.02"; + +const int MIN_LEVEL = 1; + +} // Contants + +#endif // CONSTANTS_H diff --git a/tools/qmlprofiler/main.cpp b/tools/qmlprofiler/main.cpp new file mode 100644 index 0000000000..7d9413d6e9 --- /dev/null +++ b/tools/qmlprofiler/main.cpp @@ -0,0 +1,64 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/ +** +** This file is part of the tools applications of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** 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. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL included in the packaging of this +** file. Please review the following information to ensure the GNU General +** Public License version 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** 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. +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "commandlistener.h" +#include "qmlprofilerapplication.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(); + // wait for listener to exit + listener.wait(); + + + return exitValue; +} diff --git a/tools/qmlprofiler/profileclient.cpp b/tools/qmlprofiler/profileclient.cpp new file mode 100644 index 0000000000..3e84569f2f --- /dev/null +++ b/tools/qmlprofiler/profileclient.cpp @@ -0,0 +1,300 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/ +** +** This file is part of the QtDeclarative module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** 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. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL included in the packaging of this +** file. Please review the following information to ensure the GNU General +** Public License version 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** 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. +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "profileclient.h" + +#include +#include + +ProfileClient::ProfileClient(const QString &clientName, + QDeclarativeDebugConnection *client) + : QDeclarativeDebugClient(clientName, client), + m_recording(false), + m_enabled(false) +{ +} + +ProfileClient::~ProfileClient() +{ + //Disable profiling if started by client + //Profiling data will be lost!! + if (isRecording()) + setRecording(false); +} + +void ProfileClient::clearData() +{ + emit cleared(); +} + +bool ProfileClient::isEnabled() const +{ + return m_enabled; +} + +void ProfileClient::sendRecordingStatus() +{ +} + +bool ProfileClient::isRecording() const +{ + return m_recording; +} + +void ProfileClient::setRecording(bool v) +{ + if (v == m_recording) + return; + + m_recording = v; + + if (state() == Enabled) { + sendRecordingStatus(); + } + + emit recordingChanged(v); +} + +void ProfileClient::stateChanged(State status) +{ + if ((m_enabled && status != Enabled) || + (!m_enabled && status == Enabled)) + emit enabledChanged(); + + m_enabled = status == Enabled; + +} + +class DeclarativeProfileClientPrivate +{ +public: + DeclarativeProfileClientPrivate() + : inProgressRanges(0) + , maximumTime(0) + { + ::memset(rangeCount, 0, + QDeclarativeProfilerService::MaximumRangeType * sizeof(int)); + } + + qint64 inProgressRanges; + QStack rangeStartTimes[QDeclarativeProfilerService::MaximumRangeType]; + QStack rangeDatas[QDeclarativeProfilerService::MaximumRangeType]; + QStack rangeLocations[QDeclarativeProfilerService::MaximumRangeType]; + int rangeCount[QDeclarativeProfilerService::MaximumRangeType]; + qint64 maximumTime; +}; + +DeclarativeProfileClient::DeclarativeProfileClient( + QDeclarativeDebugConnection *client) + : ProfileClient(QLatin1String("CanvasFrameRate"), client), + d(new DeclarativeProfileClientPrivate) +{ +} + +DeclarativeProfileClient::~DeclarativeProfileClient() +{ + delete d; +} + +void DeclarativeProfileClient::clearData() +{ + ::memset(d->rangeCount, 0, + QDeclarativeProfilerService::MaximumRangeType * sizeof(int)); + ProfileClient::clearData(); +} + +void DeclarativeProfileClient::sendRecordingStatus() +{ + QByteArray ba; + QDataStream stream(&ba, QIODevice::WriteOnly); + stream << isRecording(); + sendMessage(ba); +} + +void DeclarativeProfileClient::messageReceived(const QByteArray &data) +{ + QByteArray rwData = data; + QDataStream stream(&rwData, QIODevice::ReadOnly); + + qint64 time; + int messageType; + + stream >> time >> messageType; + + if (messageType >= QDeclarativeProfilerService::MaximumMessage) + return; + + if (messageType == QDeclarativeProfilerService::Event) { + int event; + stream >> event; + + if (event == QDeclarativeProfilerService::EndTrace) { + emit this->traceFinished(time); + d->maximumTime = time; + d->maximumTime = qMax(time, d->maximumTime); + } else if (event == QDeclarativeProfilerService::AnimationFrame) { + int frameRate, animationCount; + stream >> frameRate >> animationCount; + emit this->frame(time, frameRate, animationCount); + d->maximumTime = qMax(time, d->maximumTime); + } else if (event == QDeclarativeProfilerService::StartTrace) { + emit this->traceStarted(time); + d->maximumTime = time; + } else if (event < QDeclarativeProfilerService::MaximumEventType) { + d->maximumTime = qMax(time, d->maximumTime); + } + } else if (messageType == QDeclarativeProfilerService::Complete) { + emit complete(); + + } else { + int range; + stream >> range; + + if (range >= QDeclarativeProfilerService::MaximumRangeType) + return; + + if (messageType == QDeclarativeProfilerService::RangeStart) { + d->rangeStartTimes[range].push(time); + d->inProgressRanges |= (static_cast(1) << range); + ++d->rangeCount[range]; + } else if (messageType == QDeclarativeProfilerService::RangeData) { + QString data; + stream >> data; + + int count = d->rangeCount[range]; + if (count > 0) { + while (d->rangeDatas[range].count() < count) + d->rangeDatas[range].push(QStringList()); + d->rangeDatas[range][count-1] << data; + } + + } else if (messageType == QDeclarativeProfilerService::RangeLocation) { + QString fileName; + int line; + int column = -1; + stream >> fileName >> line; + + if (!stream.atEnd()) + stream >> column; + + if (d->rangeCount[range] > 0) { + d->rangeLocations[range].push(EventLocation(fileName, line, + column)); + } + } else { + if (d->rangeCount[range] > 0) { + --d->rangeCount[range]; + if (d->inProgressRanges & (static_cast(1) << range)) + d->inProgressRanges &= ~(static_cast(1) << range); + + d->maximumTime = qMax(time, d->maximumTime); + QStringList data = d->rangeDatas[range].count() ? + d->rangeDatas[range].pop() : QStringList(); + EventLocation location = d->rangeLocations[range].count() ? + d->rangeLocations[range].pop() : EventLocation(); + + qint64 startTime = d->rangeStartTimes[range].pop(); + emit this->range((QDeclarativeProfilerService::RangeType)range, + startTime, time - startTime, data, location); + if (d->rangeCount[range] == 0) { + int count = d->rangeDatas[range].count() + + d->rangeStartTimes[range].count() + + d->rangeLocations[range].count(); + if (count != 0) + qWarning() << "incorrectly nested data"; + } + } + } + } +} + +V8ProfileClient::V8ProfileClient(QDeclarativeDebugConnection *client) + : ProfileClient(QLatin1String("V8Profiler"), client) +{ +} + +V8ProfileClient::~V8ProfileClient() +{ +} + +void V8ProfileClient::sendRecordingStatus() +{ + QByteArray ba; + QDataStream stream(&ba, QIODevice::WriteOnly); + QByteArray cmd("V8PROFILER"); + QByteArray option(""); + QByteArray title(""); + + if (m_recording) { + option = "start"; + } else { + option = "stop"; + } + stream << cmd << option << title; + sendMessage(ba); +} + +void V8ProfileClient::messageReceived(const QByteArray &data) +{ + QByteArray rwData = data; + QDataStream stream(&rwData, QIODevice::ReadOnly); + + int messageType; + + stream >> messageType; + + if (messageType == V8Complete) { + emit complete(); + } else if (messageType == V8Entry) { + QString filename; + QString function; + int lineNumber; + double totalTime; + double selfTime; + int depth; + + stream >> filename >> function >> lineNumber >> totalTime >> + selfTime >> depth; + emit this->range(depth, function, filename, lineNumber, totalTime, + selfTime); + } +} + diff --git a/tools/qmlprofiler/profileclient.h b/tools/qmlprofiler/profileclient.h new file mode 100644 index 0000000000..87e9dbc508 --- /dev/null +++ b/tools/qmlprofiler/profileclient.h @@ -0,0 +1,139 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/ +** +** This file is part of the QtDeclarative module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** 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. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL included in the packaging of this +** file. Please review the following information to ensure the GNU General +** Public License version 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** 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. +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef PROFILECLIENT_H +#define PROFILECLIENT_H + +#include "profiledata.h" + +#include +#include + +class ProfileClientPrivate; +class ProfileClient : public QDeclarativeDebugClient +{ + Q_OBJECT + + Q_PROPERTY(bool enabled READ isEnabled NOTIFY enabledChanged) + Q_PROPERTY(bool recording READ isRecording WRITE setRecording + NOTIFY recordingChanged) + +public: + ProfileClient(const QString & clientName, + QDeclarativeDebugConnection *client); + ~ProfileClient(); + + bool isEnabled() const; + bool isRecording() const; + +public slots: + void setRecording(bool); + virtual void clearData(); + virtual void sendRecordingStatus(); + +signals: + void complete(); + void recordingChanged(bool arg); + void enabledChanged(); + void cleared(); + +protected: + virtual void stateChanged(State); + +protected: + bool m_recording; + bool m_enabled; +}; + +class DeclarativeProfileClient : public ProfileClient +{ + Q_OBJECT + +public: + DeclarativeProfileClient(QDeclarativeDebugConnection *client); + ~DeclarativeProfileClient(); + +public slots: + void clearData(); + void sendRecordingStatus(); + +signals: + void traceFinished( qint64 time ); + void traceStarted( qint64 time ); + void range(QDeclarativeProfilerService::RangeType type, qint64 startTime, + qint64 length, const QStringList &data, + const EventLocation &location); + void frame(qint64 time, int frameRate, int animationCount); + +protected: + virtual void messageReceived(const QByteArray &); + +private: + class DeclarativeProfileClientPrivate *d; +}; + +class V8ProfileClient : public ProfileClient +{ + Q_OBJECT + +public: + enum Message { + V8Entry, + V8Complete, + + V8MaximumMessage + }; + + V8ProfileClient(QDeclarativeDebugConnection *client); + ~V8ProfileClient(); + +public slots: + void sendRecordingStatus(); + +signals: + void range(int depth, const QString &function, const QString &filename, + int lineNumber, double totalTime, double selfTime); + +protected: + virtual void messageReceived(const QByteArray &); +}; + +#endif // PROFILECLIENT_H diff --git a/tools/qmlprofiler/profiledata.cpp b/tools/qmlprofiler/profiledata.cpp new file mode 100644 index 0000000000..19c282b97c --- /dev/null +++ b/tools/qmlprofiler/profiledata.cpp @@ -0,0 +1,1902 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/ +** +** This file is part of the QtDeclarative module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** 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. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL included in the packaging of this +** file. Please review the following information to ensure the GNU General +** Public License version 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** 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. +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "profiledata.h" +#include "constants.h" + +#include +#include +#include +#include +#include + +using namespace Constants; + +DeclarativeEvent::DeclarativeEvent() +{ + eventType = QDeclarativeProfilerService::MaximumRangeType; + eventId = -1; + duration = 0; + calls = 0; + minTime = 0; + maxTime = 0; + timePerCall = 0; + percentOfTime = 0; + medianTime = 0; + isBindingLoop = false; +} + +DeclarativeEvent::~DeclarativeEvent() +{ + qDeleteAll(parentHash.values()); + parentHash.clear(); + qDeleteAll(childrenHash.values()); + childrenHash.clear(); +} + +DeclarativeEvent &DeclarativeEvent::operator=(const DeclarativeEvent &ref) +{ + if (this == &ref) + return *this; + + displayname = ref.displayname; + location = ref.location; + eventHashStr = ref.eventHashStr; + details = ref.details; + eventType = ref.eventType; + duration = ref.duration; + calls = ref.calls; + minTime = ref.minTime; + maxTime = ref.maxTime; + timePerCall = ref.timePerCall; + percentOfTime = ref.percentOfTime; + medianTime = ref.medianTime; + eventId = ref.eventId; + isBindingLoop = ref.isBindingLoop; + + qDeleteAll(parentHash.values()); + parentHash.clear(); + foreach (const QString &key, ref.parentHash.keys()) { + parentHash.insert(key, + new DeclarativeEventSub(ref.parentHash.value(key))); + } + + qDeleteAll(childrenHash.values()); + childrenHash.clear(); + foreach (const QString &key, ref.childrenHash.keys()) { + childrenHash.insert(key, + new DeclarativeEventSub(ref.childrenHash.value(key))); + } + + return *this; +} + +V8Event::V8Event() +{ + line = -1; + eventId = -1; + totalTime = 0; + selfTime = 0; + totalPercent = 0; + selfPercent = 0; +} + +V8Event::~V8Event() +{ + qDeleteAll(parentHash.values()); + parentHash.clear(); + qDeleteAll(childrenHash.values()); + childrenHash.clear(); +} + +V8Event &V8Event::operator=(const V8Event &ref) +{ + if (this == &ref) + return *this; + + displayName = ref.displayName; + filename = ref.filename; + functionName = ref.functionName; + line = ref.line; + totalTime = ref.totalTime; + totalPercent = ref.totalPercent; + selfTime = ref.selfTime; + selfPercent = ref.selfPercent; + eventId = ref.eventId; + + qDeleteAll(parentHash.values()); + parentHash.clear(); + foreach (const QString &key, ref.parentHash.keys()) { + parentHash.insert(key, new V8EventSub(ref.parentHash.value(key))); + } + + qDeleteAll(childrenHash.values()); + childrenHash.clear(); + foreach (const QString &key, ref.childrenHash.keys()) { + childrenHash.insert(key, new V8EventSub(ref.childrenHash.value(key))); + } + return *this; +} + +// endtimedata +struct DeclarativeEventEndTime { + qint64 endTime; + int startTimeIndex; + DeclarativeEvent *description; +}; + +// starttimedata +struct DeclarativeEventStartTime{ + qint64 startTime; + qint64 length; + qint64 level; + int endTimeIndex; + qint64 nestingLevel; + qint64 nestingDepth; + DeclarativeEvent *description; + + // animation-related data + int frameRate; + int animationCount; + + int bindingLoopHead; +}; + +struct DeclarativeEventTypeCount { + QList eventIds; + int nestingCount; +}; + +// used by quicksort +bool compareEndTimes(const DeclarativeEventEndTime &t1, + const DeclarativeEventEndTime &t2) +{ + return t1.endTime < t2.endTime; +} + +bool compareStartTimes(const DeclarativeEventStartTime &t1, + const DeclarativeEventStartTime &t2) +{ + return t1.startTime < t2.startTime; +} + +bool compareStartIndexes(const DeclarativeEventEndTime &t1, + const DeclarativeEventEndTime &t2) +{ + return t1.startTimeIndex < t2.startTimeIndex; +} + +QString declarativeEventType(QDeclarativeProfilerService::RangeType typeEnum) +{ + switch (typeEnum) { + case QDeclarativeProfilerService::Painting: + return QLatin1String(TYPE_PAINTING_STR); + break; + case QDeclarativeProfilerService::Compiling: + return QLatin1String(TYPE_COMPILING_STR); + break; + case QDeclarativeProfilerService::Creating: + return QLatin1String(TYPE_CREATING_STR); + break; + case QDeclarativeProfilerService::Binding: + return QLatin1String(TYPE_BINDING_STR); + break; + case QDeclarativeProfilerService::HandlingSignal: + return QLatin1String(TYPE_HANDLINGSIGNAL_STR); + break; + default: + return QString::number((int)typeEnum); + } +} + +QDeclarativeProfilerService::RangeType declarativeEventType(const QString &typeString) +{ + if (typeString == QLatin1String(TYPE_PAINTING_STR)) { + return QDeclarativeProfilerService::Painting; + } else if (typeString == QLatin1String(TYPE_COMPILING_STR)) { + return QDeclarativeProfilerService::Compiling; + } else if (typeString == QLatin1String(TYPE_CREATING_STR)) { + return QDeclarativeProfilerService::Creating; + } else if (typeString == QLatin1String(TYPE_BINDING_STR)) { + return QDeclarativeProfilerService::Binding; + } else if (typeString == QLatin1String(TYPE_HANDLINGSIGNAL_STR)) { + return QDeclarativeProfilerService::HandlingSignal; + } else { + bool isNumber = false; + int type = typeString.toUInt(&isNumber); + if (isNumber) { + return (QDeclarativeProfilerService::RangeType)type; + } else { + return QDeclarativeProfilerService::MaximumRangeType; + } + } +} + +QString getHashStringForDeclarativeEvent( + EventLocation location, + QDeclarativeProfilerService::RangeType eventType) +{ + return QString("%1:%2:%3:%4").arg(location.filename, + QString::number(location.line), + QString::number(location.column), + QString::number(eventType)); +} + +class ProfileDataPrivate +{ +public: + + // convenience functions + void clearDeclarativeRootEvent(); + void clearV8RootEvent(); + + // Stored data + DeclarativeEventHash m_eventDescriptions; + QList m_endTimeSortedList; + QList m_startTimeSortedList; + + void collectV8Statistics(); + V8Events m_v8EventList; + QHash m_v8parents; + + DeclarativeEvent m_qmlRootEvent; + V8Event m_v8RootEvent; + QString m_rootEventName; + QString m_rootEventDesc; + + QHash m_typeCounts; + + qint64 m_traceEndTime; + qint64 m_traceStartTime; + qint64 m_qmlMeasuredTime; + qint64 m_v8MeasuredTime; + + DeclarativeEventStartTime *m_lastFrameEvent; + qint64 m_maximumAnimationCount; + qint64 m_minimumAnimationCount; + + // file to load + QString m_filename; +}; + +ProfileData::ProfileData(QObject *parent) : + QObject(parent), + d(new ProfileDataPrivate) +{ + setObjectName("ProfileData"); + + d->m_traceEndTime = 0; + d->m_traceStartTime = -1; + d->m_qmlMeasuredTime = 0; + d->m_v8MeasuredTime = 0; + d->m_rootEventName = tr(""); + d->m_rootEventDesc = tr("Main Program"); + d->clearDeclarativeRootEvent(); + d->clearV8RootEvent(); + d->m_lastFrameEvent = 0; + d->m_maximumAnimationCount = 0; + d->m_minimumAnimationCount = 0; +} + +ProfileData::~ProfileData() +{ + clear(); +} + +void ProfileData::clear() +{ + qDeleteAll(d->m_eventDescriptions.values()); + d->m_eventDescriptions.clear(); + + qDeleteAll(d->m_v8EventList); + d->m_v8EventList.clear(); + + d->m_endTimeSortedList.clear(); + d->m_startTimeSortedList.clear(); + + d->m_v8parents.clear(); + + d->clearDeclarativeRootEvent(); + d->clearV8RootEvent(); + + foreach (DeclarativeEventTypeCount *typeCount, d->m_typeCounts.values()) + delete typeCount; + d->m_typeCounts.clear(); + + d->m_traceEndTime = 0; + d->m_traceStartTime = -1; + d->m_qmlMeasuredTime = 0; + d->m_v8MeasuredTime = 0; + + d->m_lastFrameEvent = 0; + d->m_maximumAnimationCount = 0; + d->m_minimumAnimationCount = 0; + + emit countChanged(); + emit dataClear(); +} + +DeclarativeEvents ProfileData::getDeclarativeEvents() const +{ + return d->m_eventDescriptions.values(); +} + +DeclarativeEvent *ProfileData::declarativeEvent(int eventId) const +{ + foreach (DeclarativeEvent *event, d->m_eventDescriptions.values()) { + if (event->eventId == eventId) + return event; + } + return 0; +} + +V8Event *ProfileData::v8Event(int eventId) const +{ + foreach (V8Event *event, d->m_v8EventList) { + if (event->eventId == eventId) + return event; + } + return 0; +} + +const V8Events& ProfileData::getV8Events() const +{ + return d->m_v8EventList; +} + +void ProfileData::addDeclarativeEvent( + QDeclarativeProfilerService::RangeType type, qint64 startTime, qint64 length, + const QStringList &data, const EventLocation &location) +{ + const QChar colon = QLatin1Char(':'); + QString displayName, eventHashStr, details; + EventLocation eventLocation = location; + + emit processingData(); + + // generate details string + if (data.isEmpty()) + details = tr("Source code not available"); + else { + details = data.join(" ").replace('\n'," ").simplified(); + QRegExp rewrite("\\(function \\$(\\w+)\\(\\) \\{ (return |)(.+) \\}\\)"); + bool match = rewrite.exactMatch(details); + if (match) { + details = rewrite.cap(1) + ": " + rewrite.cap(3); + } + if (details.startsWith(QString("file://"))) + details = details.mid(details.lastIndexOf(QChar('/')) + 1); + } + + // backwards compatibility: "compiling" events don't have a proper location in older + // version of the protocol, but the filename is passed in the details string + if (type == QDeclarativeProfilerService::Compiling && eventLocation.filename.isEmpty()) { + eventLocation.filename = details; + eventLocation.line = 1; + eventLocation.column = 1; + } + + // generate hash + if (eventLocation.filename.isEmpty()) { + displayName = tr(""); + eventHashStr = getHashStringForDeclarativeEvent(eventLocation, type); + } else { + const QString filePath = QUrl(eventLocation.filename).path(); + displayName = filePath.mid(filePath.lastIndexOf(QChar('/')) + 1) + colon + QString::number(eventLocation.line); + eventHashStr = getHashStringForDeclarativeEvent(eventLocation, type); + } + + DeclarativeEvent *newEvent; + if (d->m_eventDescriptions.contains(eventHashStr)) { + newEvent = d->m_eventDescriptions[eventHashStr]; + } else { + newEvent = new DeclarativeEvent; + newEvent->displayname = displayName; + newEvent->location = eventLocation; + newEvent->eventHashStr = eventHashStr; + newEvent->eventType = type; + newEvent->details = details; + d->m_eventDescriptions.insert(eventHashStr, newEvent); + } + + DeclarativeEventEndTime endTimeData; + endTimeData.endTime = startTime + length; + endTimeData.description = newEvent; + endTimeData.startTimeIndex = d->m_startTimeSortedList.count(); + + DeclarativeEventStartTime startTimeData; + startTimeData.startTime = startTime; + startTimeData.length = length; + startTimeData.description = newEvent; + startTimeData.endTimeIndex = d->m_endTimeSortedList.count(); + startTimeData.animationCount = -1; + startTimeData.frameRate = 1e9/length; + + d->m_endTimeSortedList << endTimeData; + d->m_startTimeSortedList << startTimeData; + + emit countChanged(); +} + +void ProfileData::addV8Event(int depth, const QString &function, + const QString &filename, int lineNumber, + double totalTime, double selfTime) +{ + QString displayName = filename.mid( + filename.lastIndexOf(QLatin1Char('/')) + 1) + QLatin1Char(':') + + QString::number(lineNumber); + V8Event *eventData = 0; + + // time is given in milliseconds, but internally we store it in microseconds + totalTime *= 1e6; + selfTime *= 1e6; + + // cumulate information + foreach (V8Event *v8event, d->m_v8EventList) { + if (v8event->displayName == displayName && + v8event->functionName == function) { + eventData = v8event; + break; + } + } + + if (!eventData) { + eventData = new V8Event; + eventData->displayName = displayName; + eventData->filename = filename; + eventData->functionName = function; + eventData->line = lineNumber; + eventData->totalTime = totalTime; + eventData->selfTime = selfTime; + d->m_v8EventList << eventData; + } else { + eventData->totalTime += totalTime; + eventData->selfTime += selfTime; + } + d->m_v8parents[depth] = eventData; + + V8Event *parentEvent = 0; + if (depth == 0) { + parentEvent = &d->m_v8RootEvent; + d->m_v8MeasuredTime += totalTime; + } + if (depth > 0 && d->m_v8parents.contains(depth-1)) { + parentEvent = d->m_v8parents.value(depth-1); + } + + if (parentEvent != 0) { + if (!eventData->parentHash.contains(parentEvent->displayName)) { + V8EventSub *newParentSub = new V8EventSub(parentEvent); + newParentSub->totalTime = totalTime; + + eventData->parentHash.insert(parentEvent->displayName, newParentSub ); + } else { + V8EventSub *newParentSub = + eventData->parentHash.value(parentEvent->displayName); + newParentSub->totalTime += totalTime; + } + + if (!parentEvent->childrenHash.contains(eventData->displayName)) { + V8EventSub *newChildSub = new V8EventSub(eventData); + newChildSub->totalTime = totalTime; + + parentEvent->childrenHash.insert(eventData->displayName, newChildSub); + } else { + V8EventSub *newChildSub = + parentEvent->childrenHash.value(eventData->displayName); + newChildSub->totalTime += totalTime; + } + } +} + +void ProfileData::addFrameEvent(qint64 time, int framerate, int animationcount) +{ + QString displayName, eventHashStr, details; + + emit processingData(); + + details = tr("Animation Timer Update"); + displayName = tr(""); + eventHashStr = displayName; + + DeclarativeEvent *newEvent; + if (d->m_eventDescriptions.contains(eventHashStr)) { + newEvent = d->m_eventDescriptions[eventHashStr]; + } else { + newEvent = new DeclarativeEvent; + newEvent->displayname = displayName; + newEvent->eventHashStr = eventHashStr; + newEvent->eventType = QDeclarativeProfilerService::Painting; + newEvent->details = details; + d->m_eventDescriptions.insert(eventHashStr, newEvent); + } + + qint64 length = 1e9/framerate; + // avoid overlap + if (d->m_lastFrameEvent && + d->m_lastFrameEvent->startTime + d->m_lastFrameEvent->length >= time) { + d->m_lastFrameEvent->length = time - 1 - d->m_lastFrameEvent->startTime; + d->m_endTimeSortedList[d->m_lastFrameEvent->endTimeIndex].endTime = + d->m_lastFrameEvent->startTime + d->m_lastFrameEvent->length; + } + + DeclarativeEventEndTime endTimeData; + endTimeData.endTime = time + length; + endTimeData.description = newEvent; + endTimeData.startTimeIndex = d->m_startTimeSortedList.count(); + + DeclarativeEventStartTime startTimeData; + startTimeData.startTime = time; + startTimeData.length = length; + startTimeData.description = newEvent; + startTimeData.endTimeIndex = d->m_endTimeSortedList.count(); + startTimeData.animationCount = animationcount; + startTimeData.frameRate = framerate; + + d->m_endTimeSortedList << endTimeData; + d->m_startTimeSortedList << startTimeData; + + d->m_lastFrameEvent = &d->m_startTimeSortedList.last(); + + emit countChanged(); +} + +void ProfileDataPrivate::collectV8Statistics() +{ + if (!m_v8EventList.isEmpty()) { + double totalTimes = m_v8MeasuredTime; + double selfTimes = 0; + foreach (V8Event *v8event, m_v8EventList) { + selfTimes += v8event->selfTime; + } + + // prevent divisions by 0 + if (totalTimes == 0) + totalTimes = 1; + if (selfTimes == 0) + selfTimes = 1; + + // insert root event in eventlist + // the +1 ns is to get it on top of the sorted list + m_v8RootEvent.totalTime = m_v8MeasuredTime + 1; + m_v8RootEvent.selfTime = 0; + + int rootEventIndex = -1; + for (int ndx = 0; ndx < m_v8EventList.count(); ndx++) + { + if (m_v8EventList.at(ndx)->displayName == m_rootEventName) { + m_v8RootEvent = *m_v8EventList.at(ndx); + rootEventIndex = ndx; + break; + } + } + if (rootEventIndex == -1) { + rootEventIndex = m_v8EventList.count(); + V8Event *newRootEvent = new V8Event; + *newRootEvent = m_v8RootEvent; + m_v8EventList << newRootEvent; + } + + foreach (V8Event *v8event, m_v8EventList) { + v8event->totalPercent = v8event->totalTime * 100.0 / totalTimes; + v8event->selfPercent = v8event->selfTime * 100.0 / selfTimes; + } + + int index = 0; + foreach (V8Event *v8event, m_v8EventList) { + v8event->eventId = index++; + } + m_v8RootEvent.eventId = m_v8EventList[rootEventIndex]->eventId; + } +} + +void ProfileData::setTraceEndTime( qint64 time ) +{ + d->m_traceEndTime = time; +} + +void ProfileData::setTraceStartTime( qint64 time ) +{ + d->m_traceStartTime = time; +} + +void ProfileData::complete() +{ + emit postProcessing(); + d->collectV8Statistics(); + postProcess(); +} + +void ProfileDataPrivate::clearDeclarativeRootEvent() +{ + m_qmlRootEvent.displayname = m_rootEventName; + m_qmlRootEvent.location = EventLocation(); + m_qmlRootEvent.eventHashStr = m_rootEventName; + m_qmlRootEvent.details = m_rootEventDesc; + m_qmlRootEvent.eventType = QDeclarativeProfilerService::Binding; + m_qmlRootEvent.duration = 0; + m_qmlRootEvent.calls = 0; + m_qmlRootEvent.minTime = 0; + m_qmlRootEvent.maxTime = 0; + m_qmlRootEvent.timePerCall = 0; + m_qmlRootEvent.percentOfTime = 0; + m_qmlRootEvent.medianTime = 0; + m_qmlRootEvent.eventId = -1; + + qDeleteAll(m_qmlRootEvent.parentHash.values()); + qDeleteAll(m_qmlRootEvent.childrenHash.values()); + m_qmlRootEvent.parentHash.clear(); + m_qmlRootEvent.childrenHash.clear(); +} + +void ProfileDataPrivate::clearV8RootEvent() +{ + m_v8RootEvent.displayName = m_rootEventName; + m_v8RootEvent.functionName = m_rootEventDesc; + m_v8RootEvent.line = -1; + m_v8RootEvent.totalTime = 0; + m_v8RootEvent.totalPercent = 0; + m_v8RootEvent.selfTime = 0; + m_v8RootEvent.selfPercent = 0; + m_v8RootEvent.eventId = -1; + + qDeleteAll(m_v8RootEvent.parentHash.values()); + qDeleteAll(m_v8RootEvent.childrenHash.values()); + m_v8RootEvent.parentHash.clear(); + m_v8RootEvent.childrenHash.clear(); +} + +void ProfileData::compileStatistics(qint64 startTime, qint64 endTime) +{ + int index; + int fromIndex = findFirstIndex(startTime); + int toIndex = findLastIndex(endTime); + double totalTime = 0; + + // clear existing statistics + foreach (DeclarativeEvent *eventDescription, + d->m_eventDescriptions.values()) { + eventDescription->calls = 0; + // maximum possible value + eventDescription->minTime = d->m_endTimeSortedList.last().endTime; + eventDescription->maxTime = 0; + eventDescription->medianTime = 0; + eventDescription->duration = 0; + qDeleteAll(eventDescription->parentHash); + qDeleteAll(eventDescription->childrenHash); + eventDescription->parentHash.clear(); + eventDescription->childrenHash.clear(); + } + + // create root event for statistics + d->clearDeclarativeRootEvent(); + + // compute parent-child relationship and call count + QHash lastParent; + for (index = fromIndex; index <= toIndex; index++) { + DeclarativeEvent *eventDescription = + d->m_startTimeSortedList[index].description; + + if (d->m_startTimeSortedList[index].startTime > endTime || + d->m_startTimeSortedList[index].startTime + + d->m_startTimeSortedList[index].length < startTime) { + continue; + } + + if (eventDescription->eventType == QDeclarativeProfilerService::Painting) { + // skip animation/paint events + continue; + } + + eventDescription->calls++; + qint64 duration = d->m_startTimeSortedList[index].length; + eventDescription->duration += duration; + if (eventDescription->maxTime < duration) + eventDescription->maxTime = duration; + if (eventDescription->minTime > duration) + eventDescription->minTime = duration; + + int level = d->m_startTimeSortedList[index].level; + + DeclarativeEvent *parentEvent = &d->m_qmlRootEvent; + if (level > MIN_LEVEL && lastParent.contains(level-1)) { + parentEvent = lastParent[level-1]; + } + + if (!eventDescription->parentHash.contains(parentEvent->eventHashStr)) { + DeclarativeEventSub *newParentEvent = + new DeclarativeEventSub(parentEvent); + newParentEvent->calls = 1; + newParentEvent->duration = duration; + + eventDescription->parentHash.insert(parentEvent->eventHashStr, + newParentEvent); + } else { + DeclarativeEventSub *newParentEvent = + eventDescription->parentHash.value(parentEvent->eventHashStr); + newParentEvent->duration += duration; + newParentEvent->calls++; + } + + if (!parentEvent->childrenHash.contains(eventDescription->eventHashStr)) { + DeclarativeEventSub *newChildEvent = + new DeclarativeEventSub(eventDescription); + newChildEvent->calls = 1; + newChildEvent->duration = duration; + + parentEvent->childrenHash.insert(eventDescription->eventHashStr, + newChildEvent); + } else { + DeclarativeEventSub *newChildEvent = + parentEvent->childrenHash.value(eventDescription->eventHashStr); + newChildEvent->duration += duration; + newChildEvent->calls++; + } + + lastParent[level] = eventDescription; + + if (level == MIN_LEVEL) { + totalTime += duration; + } + } + + // fake rootEvent statistics + // the +1 nanosecond is to force it to be on top of the sorted list + d->m_qmlRootEvent.duration = totalTime+1; + d->m_qmlRootEvent.minTime = totalTime+1; + d->m_qmlRootEvent.maxTime = totalTime+1; + d->m_qmlRootEvent.medianTime = totalTime+1; + if (totalTime > 0) + d->m_qmlRootEvent.calls = 1; + + // insert into list + DeclarativeEvent *listedRootEvent = + d->m_eventDescriptions.value(d->m_rootEventName); + if (!listedRootEvent) { + listedRootEvent = new DeclarativeEvent; + d->m_eventDescriptions.insert(d->m_rootEventName, listedRootEvent); + } + *listedRootEvent = d->m_qmlRootEvent; + + // compute percentages + foreach (DeclarativeEvent *binding, d->m_eventDescriptions.values()) { + binding->percentOfTime = binding->duration * 100.0 / totalTime; + binding->timePerCall = binding->calls > 0 ? + double(binding->duration) / binding->calls : 0; + } + + // compute median time + QHash < DeclarativeEvent* , QList > durationLists; + for (index = fromIndex; index <= toIndex; index++) { + DeclarativeEvent *desc = d->m_startTimeSortedList[index].description; + qint64 len = d->m_startTimeSortedList[index].length; + durationLists[desc].append(len); + } + QMutableHashIterator < DeclarativeEvent* , QList > iter(durationLists); + while (iter.hasNext()) { + iter.next(); + if (!iter.value().isEmpty()) { + qSort(iter.value()); + iter.key()->medianTime = iter.value().at(iter.value().count()/2); + } + } +} + +void ProfileData::prepareForDisplay() +{ + // generate numeric ids + int ndx = 0; + foreach (DeclarativeEvent *binding, d->m_eventDescriptions.values()) { + binding->eventId = ndx++; + } + + // collect type counts + foreach (const DeclarativeEventStartTime &eventStartData, + d->m_startTimeSortedList) { + int typeNumber = eventStartData.description->eventType; + if (!d->m_typeCounts.contains(typeNumber)) { + d->m_typeCounts[typeNumber] = new DeclarativeEventTypeCount; + d->m_typeCounts[typeNumber]->nestingCount = 0; + } + if (eventStartData.nestingLevel > + d->m_typeCounts[typeNumber]->nestingCount) { + d->m_typeCounts[typeNumber]->nestingCount = eventStartData.nestingLevel; + } + if (!d->m_typeCounts[typeNumber]->eventIds.contains( + eventStartData.description->eventId)) + d->m_typeCounts[typeNumber]->eventIds << eventStartData.description->eventId; + } +} + +void ProfileData::sortStartTimes() +{ + if (d->m_startTimeSortedList.count() < 2) + return; + + // assuming startTimes is partially sorted + // identify blocks of events and sort them with quicksort + QList::iterator itFrom = + d->m_startTimeSortedList.end() - 2; + QList::iterator itTo = + d->m_startTimeSortedList.end() - 1; + + while (itFrom != d->m_startTimeSortedList.begin() && + itTo != d->m_startTimeSortedList.begin()) { + // find block to sort + while ( itFrom != d->m_startTimeSortedList.begin() + && itTo->startTime > itFrom->startTime ) { + itTo--; + itFrom = itTo - 1; + } + + // if we're at the end of the list + if (itFrom == d->m_startTimeSortedList.begin()) + break; + + // find block length + while ( itFrom != d->m_startTimeSortedList.begin() + && itTo->startTime <= itFrom->startTime ) + itFrom--; + + if (itTo->startTime <= itFrom->startTime) + qSort(itFrom, itTo + 1, compareStartTimes); + else + qSort(itFrom + 1, itTo + 1, compareStartTimes); + + // move to next block + itTo = itFrom; + itFrom = itTo - 1; + } + + // link back the endTimes + for (int i = 0; i < d->m_startTimeSortedList.length(); i++) + d->m_endTimeSortedList[d->m_startTimeSortedList[i].endTimeIndex].startTimeIndex = i; +} + +void ProfileData::sortEndTimes() +{ + // assuming endTimes is partially sorted + // identify blocks of events and sort them with quicksort + + if (d->m_endTimeSortedList.count() < 2) + return; + + QList::iterator itFrom = + d->m_endTimeSortedList.begin(); + QList::iterator itTo = + d->m_endTimeSortedList.begin() + 1; + + while (itTo != d->m_endTimeSortedList.end() && + itFrom != d->m_endTimeSortedList.end()) { + // find block to sort + while ( itTo != d->m_endTimeSortedList.end() + && d->m_startTimeSortedList[itTo->startTimeIndex].startTime > + d->m_startTimeSortedList[itFrom->startTimeIndex].startTime + + d->m_startTimeSortedList[itFrom->startTimeIndex].length ) { + itFrom++; + itTo = itFrom+1; + } + + // if we're at the end of the list + if (itTo == d->m_endTimeSortedList.end()) + break; + + // find block length + while ( itTo != d->m_endTimeSortedList.end() + && d->m_startTimeSortedList[itTo->startTimeIndex].startTime <= + d->m_startTimeSortedList[itFrom->startTimeIndex].startTime + + d->m_startTimeSortedList[itFrom->startTimeIndex].length ) + itTo++; + + // sort block + qSort(itFrom, itTo, compareEndTimes); + + // move to next block + itFrom = itTo; + itTo = itFrom+1; + + } + + // link back the startTimes + for (int i = 0; i < d->m_endTimeSortedList.length(); i++) + d->m_startTimeSortedList[d->m_endTimeSortedList[i].startTimeIndex].endTimeIndex = i; +} + +void ProfileData::findAnimationLimits() +{ + d->m_maximumAnimationCount = 0; + d->m_minimumAnimationCount = 0; + d->m_lastFrameEvent = 0; + + for (int i = 0; i < d->m_startTimeSortedList.count(); i++) { + if (d->m_startTimeSortedList[i].description->eventType == + QDeclarativeProfilerService::Painting && + d->m_startTimeSortedList[i].animationCount >= 0) { + int animationcount = d->m_startTimeSortedList[i].animationCount; + if (d->m_lastFrameEvent) { + if (animationcount > d->m_maximumAnimationCount) + d->m_maximumAnimationCount = animationcount; + if (animationcount < d->m_minimumAnimationCount) + d->m_minimumAnimationCount = animationcount; + } else { + d->m_maximumAnimationCount = animationcount; + d->m_minimumAnimationCount = animationcount; + } + d->m_lastFrameEvent = &d->m_startTimeSortedList[i]; + } + } +} + +void ProfileData::computeNestingLevels() +{ + // compute levels + QHash endtimesPerLevel; + QList nestingLevels; + QList < QHash > endtimesPerNestingLevel; + int level = MIN_LEVEL; + endtimesPerLevel[MIN_LEVEL] = 0; + + for (int i = 0; i < QDeclarativeProfilerService::MaximumRangeType; i++) { + nestingLevels << MIN_LEVEL; + QHash dummyHash; + dummyHash[MIN_LEVEL] = 0; + endtimesPerNestingLevel << dummyHash; + } + + for (int i=0; im_startTimeSortedList.count(); i++) { + qint64 st = d->m_startTimeSortedList[i].startTime; + int type = d->m_startTimeSortedList[i].description->eventType; + + if (type == QDeclarativeProfilerService::Painting) { + // animation/paint events have level 1 by definition, + // but are not considered parents of other events for + // statistical purposes + d->m_startTimeSortedList[i].level = MIN_LEVEL; + d->m_startTimeSortedList[i].nestingLevel = MIN_LEVEL; + continue; + } + + // general level + if (endtimesPerLevel[level] > st) { + level++; + } else { + while (level > MIN_LEVEL && endtimesPerLevel[level-1] <= st) + level--; + } + endtimesPerLevel[level] = st + d->m_startTimeSortedList[i].length; + + // per type + if (endtimesPerNestingLevel[type][nestingLevels[type]] > st) { + nestingLevels[type]++; + } else { + while (nestingLevels[type] > MIN_LEVEL && + endtimesPerNestingLevel[type][nestingLevels[type]-1] <= st) + nestingLevels[type]--; + } + endtimesPerNestingLevel[type][nestingLevels[type]] = st + + d->m_startTimeSortedList[i].length; + + d->m_startTimeSortedList[i].level = level; + d->m_startTimeSortedList[i].nestingLevel = nestingLevels[type]; + + if (level == MIN_LEVEL) { + d->m_qmlMeasuredTime += d->m_startTimeSortedList[i].length; + } + } +} + +void ProfileData::computeNestingDepth() +{ + QHash nestingDepth; + for (int i = 0; i < d->m_endTimeSortedList.count(); i++) { + int type = d->m_endTimeSortedList[i].description->eventType; + int nestingInType = d->m_startTimeSortedList[d->m_endTimeSortedList[i].startTimeIndex].nestingLevel; + if (!nestingDepth.contains(type)) + nestingDepth[type] = nestingInType; + else { + int nd = nestingDepth[type]; + nestingDepth[type] = nd > nestingInType ? nd : nestingInType; + } + + d->m_startTimeSortedList[d->m_endTimeSortedList[i].startTimeIndex].nestingDepth + = nestingDepth[type]; + if (nestingInType == MIN_LEVEL) + nestingDepth[type] = MIN_LEVEL; + } +} + +void ProfileData::postProcess() +{ + if (count() != 0) { + sortStartTimes(); + sortEndTimes(); + findAnimationLimits(); + computeLevels(); + linkEndsToStarts(); + reloadDetails(); + compileStatistics(traceStartTime(), traceEndTime()); + prepareForDisplay(); + } + // data is ready even when there's no data + emit dataReady(); +} + +void ProfileData::linkEndsToStarts() +{ + for (int i = 0; i < d->m_startTimeSortedList.count(); i++) + d->m_endTimeSortedList[d->m_startTimeSortedList[i].endTimeIndex].startTimeIndex = i; +} + +void ProfileData::computeLevels() +{ + computeNestingLevels(); + computeNestingDepth(); +} + +void ProfileData::reloadDetails() +{ + // request binding/signal details from the AST + foreach (DeclarativeEvent *event, d->m_eventDescriptions.values()) { + if (event->eventType != QDeclarativeProfilerService::Binding && + event->eventType != QDeclarativeProfilerService::HandlingSignal) + continue; + + // This skips anonymous bindings in Qt4.8 (we don't have valid location data for them) + if (event->location.filename.isEmpty()) + continue; + + // Skip non-anonymous bindings from Qt4.8 (we already have correct details for them) + if (event->location.column == -1) + continue; + + emit requestDetailsForLocation(event->eventType, event->location); + } + emit reloadDocumentsForDetails(); +} + +void ProfileData::findBindingLoops(qint64 startTime, qint64 endTime) +{ + // first clear existing data + foreach (DeclarativeEvent *event, d->m_eventDescriptions.values()) { + event->isBindingLoop = false; + foreach (DeclarativeEventSub *parentEvent, event->parentHash.values()) + parentEvent->inLoopPath = false; + foreach (DeclarativeEventSub *childEvent, event->childrenHash.values()) + childEvent->inLoopPath = false; + } + + QList stackRefs; + QList stack; + int fromIndex = findFirstIndex(startTime); + int toIndex = findLastIndex(endTime); + + for (int i = 0; i < d->m_startTimeSortedList.count(); i++) { + DeclarativeEvent *currentEvent = d->m_startTimeSortedList[i].description; + DeclarativeEventStartTime *inTimeEvent = &d->m_startTimeSortedList[i]; + inTimeEvent->bindingLoopHead = -1; + + // managing call stack + for (int j = stack.count() - 1; j >= 0; j--) { + if (stack[j]->startTime + stack[j]->length <= inTimeEvent->startTime) { + stack.removeAt(j); + stackRefs.removeAt(j); + } + } + + bool loopDetected = stackRefs.contains(currentEvent); + stack << inTimeEvent; + stackRefs << currentEvent; + + if (loopDetected) { + if (i >= fromIndex && i <= toIndex) { + // for the statistics + currentEvent->isBindingLoop = true; + for (int j = stackRefs.indexOf(currentEvent); j < stackRefs.count()-1; j++) { + DeclarativeEventSub *nextEventSub = stackRefs[j]->childrenHash.value(stackRefs[j+1]->eventHashStr); + nextEventSub->inLoopPath = true; + DeclarativeEventSub *prevEventSub = stackRefs[j+1]->parentHash.value(stackRefs[j]->eventHashStr); + prevEventSub->inLoopPath = true; + } + } + + // use crossed references to find index in starttimesortedlist + DeclarativeEventStartTime *head = stack[stackRefs.indexOf(currentEvent)]; + inTimeEvent->bindingLoopHead = d->m_endTimeSortedList[head->endTimeIndex].startTimeIndex; + d->m_startTimeSortedList[inTimeEvent->bindingLoopHead].bindingLoopHead = i; + } + } +} + +void ProfileData::rewriteDetailsString( + QDeclarativeProfilerService::RangeType eventType, + const EventLocation &location, const QString &newString) +{ + QString eventHashStr = getHashStringForDeclarativeEvent(location, + eventType); + Q_ASSERT(d->m_eventDescriptions.contains(eventHashStr)); + d->m_eventDescriptions.value(eventHashStr)->details = newString; + emit detailsChanged(d->m_eventDescriptions.value(eventHashStr)->eventId, + newString); +} + +void ProfileData::finishedRewritingDetails() +{ + emit reloadDetailLabels(); +} + +// get list of events between A and B: +// find fist event with endtime after A -> aa +// find last event with starttime before B -> bb +// list is from parent of aa with level=0 to bb, in the "sorted by starttime" list +int ProfileData::findFirstIndex(qint64 startTime) const +{ + int candidate = -1; + // in the "endtime" list, find the first event that ends after startTime + if (d->m_endTimeSortedList.isEmpty()) + return 0; // -1 + if (d->m_endTimeSortedList.length() == 1 || + d->m_endTimeSortedList.first().endTime >= startTime) + candidate = 0; + else + if (d->m_endTimeSortedList.last().endTime <= startTime) + return 0; // -1 + + if (candidate == -1) + { + int fromIndex = 0; + int toIndex = d->m_endTimeSortedList.count()-1; + while (toIndex - fromIndex > 1) { + int midIndex = (fromIndex + toIndex)/2; + if (d->m_endTimeSortedList[midIndex].endTime < startTime) + fromIndex = midIndex; + else + toIndex = midIndex; + } + + candidate = toIndex; + } + + int ndx = d->m_endTimeSortedList[candidate].startTimeIndex; + + // and then go to the parent + while (d->m_startTimeSortedList[ndx].level != MIN_LEVEL && ndx > 0) + ndx--; + + return ndx; +} + +int ProfileData::findFirstIndexNoParents(qint64 startTime) const +{ + int candidate = -1; + // in the "endtime" list, find the first event that ends after startTime + if (d->m_endTimeSortedList.isEmpty()) + return 0; // -1 + if (d->m_endTimeSortedList.length() == 1 || + d->m_endTimeSortedList.first().endTime >= startTime) + candidate = 0; + else + if (d->m_endTimeSortedList.last().endTime <= startTime) + return 0; // -1 + + if (candidate == -1) { + int fromIndex = 0; + int toIndex = d->m_endTimeSortedList.count()-1; + while (toIndex - fromIndex > 1) { + int midIndex = (fromIndex + toIndex)/2; + if (d->m_endTimeSortedList[midIndex].endTime < startTime) + fromIndex = midIndex; + else + toIndex = midIndex; + } + + candidate = toIndex; + } + + int ndx = d->m_endTimeSortedList[candidate].startTimeIndex; + + return ndx; +} + +int ProfileData::findLastIndex(qint64 endTime) const +{ + // in the "starttime" list, find the last event that starts before endtime + if (d->m_startTimeSortedList.isEmpty()) + return 0; // -1 + if (d->m_startTimeSortedList.first().startTime >= endTime) + return 0; // -1 + if (d->m_startTimeSortedList.length() == 1) + return 0; + if (d->m_startTimeSortedList.last().startTime <= endTime) + return d->m_startTimeSortedList.count()-1; + + int fromIndex = 0; + int toIndex = d->m_startTimeSortedList.count()-1; + while (toIndex - fromIndex > 1) { + int midIndex = (fromIndex + toIndex)/2; + if (d->m_startTimeSortedList[midIndex].startTime < endTime) + fromIndex = midIndex; + else + toIndex = midIndex; + } + + return fromIndex; +} + +qint64 ProfileData::firstTimeMark() const +{ + if (d->m_startTimeSortedList.isEmpty()) + return 0; + else { + return d->m_startTimeSortedList[0].startTime; + } +} + +qint64 ProfileData::lastTimeMark() const +{ + if (d->m_endTimeSortedList.isEmpty()) + return 0; + else { + return d->m_endTimeSortedList.last().endTime; + } +} + +qint64 ProfileData::traceStartTime() const +{ + return d->m_traceStartTime != -1? d->m_traceStartTime : firstTimeMark(); +} + +qint64 ProfileData::traceEndTime() const +{ + return d->m_traceEndTime ? d->m_traceEndTime : lastTimeMark(); +} + +qint64 ProfileData::traceDuration() const +{ + return traceEndTime() - traceStartTime(); +} + +qint64 ProfileData::declarativeMeasuredTime() const +{ + return d->m_qmlMeasuredTime; +} +qint64 ProfileData::v8MeasuredTime() const +{ + return d->m_v8MeasuredTime; +} + +int ProfileData::count() const +{ + return d->m_startTimeSortedList.count(); +} + +//////////////////////////////////////////////////////////////////////////////// + + +bool ProfileData::save(const QString &filename) +{ + if (count() == 0) { + emit error(tr("No data to save")); + return false; + } + + QFile file(filename); + if (!file.open(QIODevice::WriteOnly)) { + emit error(tr("Could not open %1 for writing").arg(filename)); + return false; + } + + QXmlStreamWriter stream(&file); + stream.setAutoFormatting(true); + stream.writeStartDocument(); + + stream.writeStartElement("trace"); + stream.writeAttribute("version", PROFILER_FILE_VERSION); + + stream.writeAttribute("traceStart", QString::number(traceStartTime())); + stream.writeAttribute("traceEnd", QString::number(traceEndTime())); + + stream.writeStartElement("eventData"); + stream.writeAttribute("totalTime", QString::number(d->m_qmlMeasuredTime)); + + foreach (const DeclarativeEvent *eventData, d->m_eventDescriptions.values()) { + stream.writeStartElement("event"); + stream.writeAttribute("index", + QString::number( + d->m_eventDescriptions.keys().indexOf( + eventData->eventHashStr))); + stream.writeTextElement("displayname", eventData->displayname); + stream.writeTextElement("type", declarativeEventType(eventData->eventType)); + if (!eventData->location.filename.isEmpty()) { + stream.writeTextElement("filename", eventData->location.filename); + stream.writeTextElement("line", + QString::number(eventData->location.line)); + stream.writeTextElement("column", + QString::number(eventData->location.column)); + } + stream.writeTextElement("details", eventData->details); + stream.writeEndElement(); + } + stream.writeEndElement(); // eventData + + stream.writeStartElement("eventList"); + foreach (const DeclarativeEventStartTime &rangedEvent, + d->m_startTimeSortedList) { + stream.writeStartElement("range"); + stream.writeAttribute("startTime", QString::number(rangedEvent.startTime)); + stream.writeAttribute("duration", QString::number(rangedEvent.length)); + stream.writeAttribute("eventIndex", + QString::number(d->m_eventDescriptions.keys().indexOf( + rangedEvent.description->eventHashStr))); + if (rangedEvent.description->eventType == + QDeclarativeProfilerService::Painting && rangedEvent.animationCount >= 0) { + // animation frame + stream.writeAttribute("framerate", + QString::number(rangedEvent.frameRate)); + stream.writeAttribute("animationcount", + QString::number(rangedEvent.animationCount)); + } + stream.writeEndElement(); + } + stream.writeEndElement(); // eventList + + stream.writeStartElement("v8profile"); // v8 profiler output + stream.writeAttribute("totalTime", QString::number(d->m_v8MeasuredTime)); + foreach (V8Event *v8event, d->m_v8EventList) { + stream.writeStartElement("event"); + stream.writeAttribute("index", + QString::number(d->m_v8EventList.indexOf(v8event))); + stream.writeTextElement("displayname", v8event->displayName); + stream.writeTextElement("functionname", v8event->functionName); + if (!v8event->filename.isEmpty()) { + stream.writeTextElement("filename", v8event->filename); + stream.writeTextElement("line", QString::number(v8event->line)); + } + stream.writeTextElement("totalTime", + QString::number(v8event->totalTime)); + stream.writeTextElement("selfTime", QString::number(v8event->selfTime)); + if (!v8event->childrenHash.isEmpty()) { + stream.writeStartElement("childrenEvents"); + QStringList childrenIndexes; + QStringList childrenTimes; + QStringList parentTimes; + foreach (V8EventSub *v8child, v8event->childrenHash.values()) { + childrenIndexes << QString::number(v8child->reference->eventId); + childrenTimes << QString::number(v8child->totalTime); + parentTimes << QString::number( + d->m_v8EventList[v8child->reference->eventId]-> + parentHash[v8event->displayName]->totalTime); + } + + stream.writeAttribute("list", childrenIndexes.join(QString(", "))); + stream.writeAttribute("childrenTimes", + childrenTimes.join(QString(", "))); + stream.writeAttribute("parentTimes", + parentTimes.join(QString(", "))); + stream.writeEndElement(); + } + stream.writeEndElement(); + } + stream.writeEndElement(); // v8 profiler output + + stream.writeEndElement(); // trace + stream.writeEndDocument(); + + file.close(); + return true; +} + +void ProfileData::setFilename(const QString &filename) +{ + d->m_filename = filename; +} + +void ProfileData::load(const QString &filename) +{ + setFilename(filename); + load(); +} + +// "be strict in your output but tolerant in your inputs" +void ProfileData::load() +{ + QString filename = d->m_filename; + + QFile file(filename); + + if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { + emit error(tr("Could not open %1 for reading").arg(filename)); + return; + } + + emit processingData(); + + // erase current + clear(); + + bool readingQmlEvents = false; + bool readingV8Events = false; + QHash descriptionBuffer; + DeclarativeEvent *currentEvent = 0; + QHash v8eventBuffer; + QHash childrenIndexes; + QHash childrenTimes; + QHash parentTimes; + V8Event *v8event = 0; + bool startTimesAreSorted = true; + bool validVersion = true; + + // time computation + d->m_v8MeasuredTime = 0; + d->m_qmlMeasuredTime = 0; + double cumulatedV8Time = 0; + + QXmlStreamReader stream(&file); + + while (validVersion && !stream.atEnd() && !stream.hasError()) { + QXmlStreamReader::TokenType token = stream.readNext(); + QString elementName = stream.name().toString(); + switch (token) { + case QXmlStreamReader::StartDocument : continue; + case QXmlStreamReader::StartElement : { + if (elementName == "trace") { + QXmlStreamAttributes attributes = stream.attributes(); + if (attributes.hasAttribute("version")) + validVersion = + attributes.value("version").toString() == + PROFILER_FILE_VERSION; + else + validVersion = false; + if (attributes.hasAttribute("traceStart")) + setTraceStartTime(attributes.value("traceStart"). + toString().toLongLong()); + if (attributes.hasAttribute("traceEnd")) + setTraceEndTime(attributes.value("traceEnd"). + toString().toLongLong()); + } + if (elementName == "eventData" && !readingV8Events) { + readingQmlEvents = true; + QXmlStreamAttributes attributes = stream.attributes(); + if (attributes.hasAttribute("totalTime")) + d->m_qmlMeasuredTime = attributes.value("totalTime"). + toString().toDouble(); + break; + } + if (elementName == "v8profile" && !readingQmlEvents) { + readingV8Events = true; + QXmlStreamAttributes attributes = stream.attributes(); + if (attributes.hasAttribute("totalTime")) + d->m_v8MeasuredTime = attributes.value("totalTime"). + toString().toDouble(); + break; + } + + if (elementName == "trace") { + QXmlStreamAttributes attributes = stream.attributes(); + if (attributes.hasAttribute("traceStart")) + setTraceStartTime(attributes.value("traceStart"). + toString().toLongLong()); + if (attributes.hasAttribute("traceEnd")) + setTraceEndTime(attributes.value("traceEnd"). + toString().toLongLong()); + } + + if (elementName == "range") { + DeclarativeEventStartTime rangedEvent; + QXmlStreamAttributes attributes = stream.attributes(); + if (attributes.hasAttribute("startTime")) + rangedEvent.startTime = attributes.value("startTime"). + toString().toLongLong(); + if (attributes.hasAttribute("duration")) + rangedEvent.length = attributes.value("duration"). + toString().toLongLong(); + if (attributes.hasAttribute("framerate")) + rangedEvent.frameRate = attributes.value("framerate"). + toString().toInt(); + if (attributes.hasAttribute("animationcount")) + rangedEvent.animationCount = attributes.value("animationcount"). + toString().toInt(); + else + rangedEvent.animationCount = -1; + if (attributes.hasAttribute("eventIndex")) { + int ndx = attributes.value("eventIndex").toString().toInt(); + if (!descriptionBuffer.value(ndx)) + descriptionBuffer[ndx] = new DeclarativeEvent; + rangedEvent.description = descriptionBuffer.value(ndx); + } + rangedEvent.endTimeIndex = d->m_endTimeSortedList.length(); + + if (!d->m_startTimeSortedList.isEmpty() + && rangedEvent.startTime < + d->m_startTimeSortedList.last().startTime) + startTimesAreSorted = false; + d->m_startTimeSortedList << rangedEvent; + + DeclarativeEventEndTime endTimeEvent; + endTimeEvent.endTime = rangedEvent.startTime + rangedEvent.length; + endTimeEvent.startTimeIndex = d->m_startTimeSortedList.length()-1; + endTimeEvent.description = rangedEvent.description; + d->m_endTimeSortedList << endTimeEvent; + break; + } + + if (readingQmlEvents) { + if (elementName == "event") { + QXmlStreamAttributes attributes = stream.attributes(); + if (attributes.hasAttribute("index")) { + int ndx = attributes.value("index").toString().toInt(); + if (!descriptionBuffer.value(ndx)) + descriptionBuffer[ndx] = new DeclarativeEvent; + currentEvent = descriptionBuffer[ndx]; + } else { + currentEvent = 0; + } + break; + } + + // the remaining are eventdata or v8eventdata elements + if (!currentEvent) + break; + + stream.readNext(); + if (stream.tokenType() != QXmlStreamReader::Characters) + break; + QString readData = stream.text().toString(); + + if (elementName == "displayname") { + currentEvent->displayname = readData; + break; + } + if (elementName == "type") { + currentEvent->eventType = declarativeEventType(readData); + break; + } + if (elementName == "filename") { + currentEvent->location.filename = readData; + break; + } + if (elementName == "line") { + currentEvent->location.line = readData.toInt(); + break; + } + if (elementName == "column") { + currentEvent->location.column = readData.toInt(); + } + if (elementName == "details") { + currentEvent->details = readData; + break; + } + } + + if (readingV8Events) { + if (elementName == "event") { + QXmlStreamAttributes attributes = stream.attributes(); + if (attributes.hasAttribute("index")) { + int ndx = attributes.value("index").toString().toInt(); + if (!v8eventBuffer.value(ndx)) + v8eventBuffer[ndx] = new V8Event; + v8event = v8eventBuffer[ndx]; + } else { + v8event = 0; + } + break; + } + + // the remaining are eventdata or v8eventdata elements + if (!v8event) + break; + + if (elementName == "childrenEvents") { + QXmlStreamAttributes attributes = stream.attributes(); + int eventIndex = v8eventBuffer.key(v8event); + if (attributes.hasAttribute("list")) { + // store for later parsing (we haven't read all the events yet) + childrenIndexes[eventIndex] = + attributes.value("list").toString(); + } + if (attributes.hasAttribute("childrenTimes")) { + childrenTimes[eventIndex] = + attributes.value("childrenTimes").toString(); + } + if (attributes.hasAttribute("parentTimes")) { + parentTimes[eventIndex] = + attributes.value("parentTimes").toString(); + } + } + + stream.readNext(); + if (stream.tokenType() != QXmlStreamReader::Characters) + break; + QString readData = stream.text().toString(); + + if (elementName == "displayname") { + v8event->displayName = readData; + break; + } + + if (elementName == "functionname") { + v8event->functionName = readData; + break; + } + + if (elementName == "filename") { + v8event->filename = readData; + break; + } + + if (elementName == "line") { + v8event->line = readData.toInt(); + break; + } + + if (elementName == "totalTime") { + v8event->totalTime = readData.toDouble(); + cumulatedV8Time += v8event->totalTime; + break; + } + + if (elementName == "selfTime") { + v8event->selfTime = readData.toDouble(); + break; + } + } + + break; + } + case QXmlStreamReader::EndElement : { + if (elementName == "event") { + currentEvent = 0; + break; + } + if (elementName == "eventData") { + readingQmlEvents = false; + break; + } + if (elementName == "v8profile") { + readingV8Events = false; + } + } + default: break; + } + } + + file.close(); + + if (stream.hasError()) { + emit error(tr("Error while parsing %1").arg(filename)); + clear(); + return; + } + + stream.clear(); + + if (!validVersion) { + clear(); + emit countChanged(); + emit dataReady(); + emit error(tr("Invalid version of QML Trace file.")); + return; + } + + // backwards compatibility + if (d->m_v8MeasuredTime == 0) + d->m_v8MeasuredTime = cumulatedV8Time; + + // move the buffered data to the details cache + foreach (DeclarativeEvent *desc, descriptionBuffer.values()) { + desc->eventHashStr = getHashStringForDeclarativeEvent( + desc->location, desc->eventType);; + d->m_eventDescriptions[desc->eventHashStr] = desc; + } + + // sort startTimeSortedList + if (!startTimesAreSorted) { + qSort(d->m_startTimeSortedList.begin(), + d->m_startTimeSortedList.end(), compareStartTimes); + for (int i = 0; i< d->m_startTimeSortedList.length(); i++) { + DeclarativeEventStartTime startTimeData = d->m_startTimeSortedList[i]; + d->m_endTimeSortedList[startTimeData.endTimeIndex].startTimeIndex = i; + } + qSort(d->m_endTimeSortedList.begin(), + d->m_endTimeSortedList.end(), compareStartIndexes); + } + + // find v8events' children and parents + foreach (int parentIndex, childrenIndexes.keys()) { + QStringList childrenStrings = + childrenIndexes.value(parentIndex).split(","); + QStringList childrenTimesStrings = + childrenTimes.value(parentIndex).split(", "); + QStringList parentTimesStrings = + parentTimes.value(parentIndex).split(", "); + for (int ndx = 0; ndx < childrenStrings.count(); ndx++) { + int childIndex = childrenStrings[ndx].toInt(); + if (v8eventBuffer.value(childIndex)) { + V8EventSub *newChild = new V8EventSub(v8eventBuffer[childIndex]); + V8EventSub *newParent = new V8EventSub(v8eventBuffer[parentIndex]); + if (childrenTimesStrings.count() > ndx) + newChild->totalTime = childrenTimesStrings[ndx].toDouble(); + if (parentTimesStrings.count() > ndx) + newParent->totalTime = parentTimesStrings[ndx].toDouble(); + v8eventBuffer[parentIndex]->childrenHash.insert( + newChild->reference->displayName, newChild); + v8eventBuffer[childIndex]->parentHash.insert( + newParent->reference->displayName, newParent); + } + } + } + // store v8 events + d->m_v8EventList = v8eventBuffer.values(); + + emit countChanged(); + + descriptionBuffer.clear(); + + emit postProcessing(); + d->collectV8Statistics(); + postProcess(); +} + +/////////////////////////////////////////////// +qint64 ProfileData::getStartTime(int index) const +{ + return d->m_startTimeSortedList[index].startTime; +} + +qint64 ProfileData::getEndTime(int index) const +{ + return d->m_startTimeSortedList[index].startTime + + d->m_startTimeSortedList[index].length; +} + +qint64 ProfileData::getDuration(int index) const +{ + return d->m_startTimeSortedList[index].length; +} + +int ProfileData::getType(int index) const +{ + return d->m_startTimeSortedList[index].description->eventType; +} + +int ProfileData::getNestingLevel(int index) const +{ + return d->m_startTimeSortedList[index].nestingLevel; +} + +int ProfileData::getNestingDepth(int index) const +{ + return d->m_startTimeSortedList[index].nestingDepth; +} + +QString ProfileData::getFilename(int index) const +{ + return d->m_startTimeSortedList[index].description->location.filename; +} + +int ProfileData::getLine(int index) const +{ + return d->m_startTimeSortedList[index].description->location.line; +} + +int ProfileData::getColumn(int index) const +{ + return d->m_startTimeSortedList[index].description->location.column; +} + +QString ProfileData::getDetails(int index) const +{ + // special: animations + if (d->m_startTimeSortedList[index].description->eventType == + QDeclarativeProfilerService::Painting && + d->m_startTimeSortedList[index].animationCount >= 0) + return tr("%1 animations at %2 FPS").arg( + QString::number(d->m_startTimeSortedList[index].animationCount), + QString::number(d->m_startTimeSortedList[index].frameRate)); + return d->m_startTimeSortedList[index].description->details; +} + +int ProfileData::getEventId(int index) const { + return d->m_startTimeSortedList[index].description->eventId; +} + +int ProfileData::getFramerate(int index) const +{ + return d->m_startTimeSortedList[index].frameRate; +} + +int ProfileData::getAnimationCount(int index) const +{ + return d->m_startTimeSortedList[index].animationCount; +} + +int ProfileData::getMaximumAnimationCount() const +{ + return d->m_maximumAnimationCount; +} + +int ProfileData::getMinimumAnimationCount() const +{ + return d->m_minimumAnimationCount; +} + +int ProfileData::uniqueEventsOfType(int type) const +{ + if (!d->m_typeCounts.contains(type)) + return 0; + return d->m_typeCounts[type]->eventIds.count(); +} + +int ProfileData::maxNestingForType(int type) const +{ + if (!d->m_typeCounts.contains(type)) + return 0; + return d->m_typeCounts[type]->nestingCount; +} + +QString ProfileData::eventTextForType(int type, int index) const +{ + if (!d->m_typeCounts.contains(type)) + return QString(); + return d->m_eventDescriptions.values().at( + d->m_typeCounts[type]->eventIds[index])->details; +} + +QString ProfileData::eventDisplayNameForType(int type, int index) const +{ + if (!d->m_typeCounts.contains(type)) + return QString(); + return d->m_eventDescriptions.values().at( + d->m_typeCounts[type]->eventIds[index])->displayname; +} + +int ProfileData::eventIdForType(int type, int index) const +{ + if (!d->m_typeCounts.contains(type)) + return -1; + return d->m_typeCounts[type]->eventIds[index]; +} + +int ProfileData::eventPosInType(int index) const +{ + int eventType = d->m_startTimeSortedList[index].description->eventType; + return d->m_typeCounts[eventType]->eventIds.indexOf( + d->m_startTimeSortedList[index].description->eventId); +} diff --git a/tools/qmlprofiler/profiledata.h b/tools/qmlprofiler/profiledata.h new file mode 100644 index 0000000000..eaa2127f4a --- /dev/null +++ b/tools/qmlprofiler/profiledata.h @@ -0,0 +1,247 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/ +** +** This file is part of the QtDeclarative module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** 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. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL included in the packaging of this +** file. Please review the following information to ensure the GNU General +** Public License version 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** 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. +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef PROFILEDATA_H +#define PROFILEDATA_H + +#include + +#include +#include + +struct DeclarativeEvent; +struct V8Event; + +typedef QHash DeclarativeEventHash; +typedef QList DeclarativeEvents; +typedef QList V8Events; + +struct EventLocation +{ + EventLocation() : line(-1),column(-1) {} + EventLocation(const QString &file, int lineNumber, int columnNumber) + : filename(file), line(lineNumber), column(columnNumber) {} + QString filename; + int line; + int column; +}; + +struct DeclarativeEventSub { + DeclarativeEventSub(DeclarativeEvent *from) + : reference(from), duration(0), calls(0), inLoopPath(false) + {} + DeclarativeEventSub(DeclarativeEventSub *from) + : reference(from->reference), duration(from->duration), + calls(from->calls), inLoopPath(from->inLoopPath) + {} + DeclarativeEvent *reference; + qint64 duration; + qint64 calls; + bool inLoopPath; +}; + +struct DeclarativeEvent +{ + DeclarativeEvent(); + ~DeclarativeEvent(); + + QString displayname; + QString eventHashStr; + QString details; + EventLocation location; + QDeclarativeProfilerService::RangeType eventType; + QHash parentHash; + QHash childrenHash; + qint64 duration; + qint64 calls; + qint64 minTime; + qint64 maxTime; + double timePerCall; + double percentOfTime; + qint64 medianTime; + int eventId; + bool isBindingLoop; + + DeclarativeEvent &operator=(const DeclarativeEvent &ref); +}; + +struct V8EventSub { + V8EventSub(V8Event *from) + : reference(from), totalTime(0) + {} + V8EventSub(V8EventSub *from) + : reference(from->reference), totalTime(from->totalTime) + {} + + V8Event *reference; + qint64 totalTime; +}; + +struct V8Event +{ + V8Event(); + ~V8Event(); + + QString displayName; + QString filename; + QString functionName; + int line; + double totalTime; // given in milliseconds + double totalPercent; + double selfTime; + double selfPercent; + QHash parentHash; + QHash childrenHash; + int eventId; + + V8Event &operator=(const V8Event &ref); +}; + +class ProfileData : public QObject +{ + Q_OBJECT + +public: + explicit ProfileData(QObject *parent = 0); + ~ProfileData(); + + DeclarativeEvents getDeclarativeEvents() const; + DeclarativeEvent *declarativeEvent(int eventId) const; + const V8Events& getV8Events() const; + V8Event *v8Event(int eventId) const; + + int findFirstIndex(qint64 startTime) const; + int findFirstIndexNoParents(qint64 startTime) const; + int findLastIndex(qint64 endTime) const; + Q_INVOKABLE qint64 firstTimeMark() const; + Q_INVOKABLE qint64 lastTimeMark() const; + + Q_INVOKABLE int count() const; + + // data access + Q_INVOKABLE qint64 getStartTime(int index) const; + Q_INVOKABLE qint64 getEndTime(int index) const; + Q_INVOKABLE qint64 getDuration(int index) const; + Q_INVOKABLE int getType(int index) const; + Q_INVOKABLE int getNestingLevel(int index) const; + Q_INVOKABLE int getNestingDepth(int index) const; + Q_INVOKABLE QString getFilename(int index) const; + Q_INVOKABLE int getLine(int index) const; + Q_INVOKABLE int getColumn(int index) const; + Q_INVOKABLE QString getDetails(int index) const; + Q_INVOKABLE int getEventId(int index) const; + Q_INVOKABLE int getFramerate(int index) const; + Q_INVOKABLE int getAnimationCount(int index) const; + Q_INVOKABLE int getMaximumAnimationCount() const; + Q_INVOKABLE int getMinimumAnimationCount() const; + + // per-type data + Q_INVOKABLE int uniqueEventsOfType(int type) const; + Q_INVOKABLE int maxNestingForType(int type) const; + Q_INVOKABLE QString eventTextForType(int type, int index) const; + Q_INVOKABLE QString eventDisplayNameForType(int type, int index) const; + Q_INVOKABLE int eventIdForType(int type, int index) const; + Q_INVOKABLE int eventPosInType(int index) const; + + Q_INVOKABLE qint64 traceStartTime() const; + Q_INVOKABLE qint64 traceEndTime() const; + Q_INVOKABLE qint64 traceDuration() const; + Q_INVOKABLE qint64 declarativeMeasuredTime() const; + Q_INVOKABLE qint64 v8MeasuredTime() const; + + void showErrorDialog(const QString &st ) const; + void compileStatistics(qint64 startTime, qint64 endTime); + +signals: + void dataReady(); + void countChanged(); + void error(const QString &error); + void dataClear(); + void processingData(); + void postProcessing(); + + void requestDetailsForLocation(int eventType, const EventLocation &location); + void detailsChanged(int eventId, const QString &newString); + void reloadDetailLabels(); + void reloadDocumentsForDetails(); + +public slots: + void clear(); + void addDeclarativeEvent(QDeclarativeProfilerService::RangeType type, + qint64 startTime, qint64 length, + const QStringList &data, + const EventLocation &location); + void complete(); + + void addV8Event(int depth,const QString &function,const QString &filename, + int lineNumber, double totalTime, double selfTime); + void addFrameEvent(qint64 time, int framerate, int animationcount); + bool save(const QString &filename); + void load(const QString &filename); + void setFilename(const QString &filename); + void load(); + + void setTraceEndTime( qint64 time ); + void setTraceStartTime( qint64 time ); + + void rewriteDetailsString(QDeclarativeProfilerService::RangeType eventType, + const EventLocation &location, + const QString &newString); + void finishedRewritingDetails(); + +private: + void postProcess(); + void sortEndTimes(); + void findAnimationLimits(); + void sortStartTimes(); + void computeLevels(); + void computeNestingLevels(); + void computeNestingDepth(); + void prepareForDisplay(); + void linkEndsToStarts(); + void reloadDetails(); + void findBindingLoops(qint64 startTime, qint64 endTime); + +private: + class ProfileDataPrivate *d; +}; + +#endif // PROFILEDATA_H diff --git a/tools/qmlprofiler/qmlprofiler.pro b/tools/qmlprofiler/qmlprofiler.pro new file mode 100644 index 0000000000..1d8f89a150 --- /dev/null +++ b/tools/qmlprofiler/qmlprofiler.pro @@ -0,0 +1,25 @@ +TEMPLATE = app +TARGET = qmlprofiler +DESTDIR = $$QT.declarative.bins + +QT += declarative declarative-private network + +target.path = $$[QT_INSTALL_BINS] +INSTALLS += target + +macx: CONFIG -= app_bundle + +CONFIG += console declarative_debug + +SOURCES += main.cpp \ + qmlprofilerapplication.cpp \ + commandlistener.cpp \ + profileclient.cpp \ + profiledata.cpp + +HEADERS += \ + qmlprofilerapplication.h \ + commandlistener.h \ + constants.h \ + profileclient.h \ + profiledata.h diff --git a/tools/qmlprofiler/qmlprofilerapplication.cpp b/tools/qmlprofiler/qmlprofilerapplication.cpp new file mode 100644 index 0000000000..af5f4298a8 --- /dev/null +++ b/tools/qmlprofiler/qmlprofilerapplication.cpp @@ -0,0 +1,434 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/ +** +** This file is part of the QtDeclarative module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** 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. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL included in the packaging of this +** file. Please review the following information to ensure the GNU General +** Public License version 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** 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. +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qmlprofilerapplication.h" +#include "constants.h" +#include +#include +#include +#include +#include +#include +#include + +static const char usageTextC[] = +"Usage:\n" +" qmlprofiler [options] [program] [program-options]\n" +" qmlprofiler [options] -attach [hostname]\n" +"\n" +"QML Profiler retrieves QML tracing data from a running application.\n" +"The 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 , -port \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."; + +static const char TraceFileExtension[] = ".qtd"; + +QmlProfilerApplication::QmlProfilerApplication(int &argc, char **argv) : + QCoreApplication(argc, argv), + m_runMode(LaunchMode), + m_process(0), + m_tracePrefix(QLatin1String("trace")), + m_hostName(QLatin1String("127.0.0.1")), + m_port(3768), + m_verbose(false), + m_quitAfterSave(false), + m_declarativeProfilerClient(&m_connection), + m_v8profilerClient(&m_connection), + m_connectionAttempts(0), + m_declarativeDataReady(false), + m_v8DataReady(false) +{ + 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_declarativeProfilerClient, SIGNAL(enabledChanged()), this, SLOT(traceClientEnabled())); + connect(&m_declarativeProfilerClient, SIGNAL(recordingChanged(bool)), this, SLOT(recordingChanged())); + connect(&m_declarativeProfilerClient, SIGNAL(range(QDeclarativeProfilerService::RangeType,qint64,qint64,QStringList,EventLocation)), + &m_profileData, SLOT(addDeclarativeEvent(QDeclarativeProfilerService::RangeType,qint64,qint64,QStringList,EventLocation))); + connect(&m_declarativeProfilerClient, SIGNAL(traceFinished(qint64)), &m_profileData, SLOT(setTraceEndTime(qint64))); + connect(&m_declarativeProfilerClient, SIGNAL(traceStarted(qint64)), &m_profileData, SLOT(setTraceStartTime(qint64))); + connect(&m_declarativeProfilerClient, SIGNAL(frame(qint64,int,int)), &m_profileData, SLOT(addFrameEvent(qint64,int,int))); + connect(&m_declarativeProfilerClient, SIGNAL(complete()), this, SLOT(declarativeComplete())); + + connect(&m_v8profilerClient, SIGNAL(enabledChanged()), this, SLOT(profilerClientEnabled())); + connect(&m_v8profilerClient, SIGNAL(range(int,QString,QString,int,double,double)), + &m_profileData, SLOT(addV8Event(int,QString,QString,int,double,double))); + connect(&m_v8profilerClient, SIGNAL(complete()), this, SLOT(v8Complete())); + + connect(&m_profileData, SIGNAL(error(QString)), this, SLOT(logError(QString))); + connect(&m_profileData, 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); + if (arg == QLatin1String("-attach") || arg == QLatin1String("-a")) { + if (argPos + 1 == arguments().size()) { + return false; + } + m_hostName = arguments().at(++argPos); + m_runMode = AttachMode; + } else if (arg == QLatin1String("-port") || arg == QLatin1String("-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 == QLatin1String("-fromStart")) { + m_declarativeProfilerClient.setRecording(true); + m_v8profilerClient.setRecording(true); + } else if (arg == QLatin1String("-help") || arg == QLatin1String("-h") || arg == QLatin1String("/h") || arg == QLatin1String("/?")) { + return false; + } else if (arg == QLatin1String("-verbose") || arg == QLatin1String("-v")) { + m_verbose = true; + } else if (arg == QLatin1String("-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(QLatin1String("yyMMdd_hhmmss")) + + TraceFileExtension; + if (QFileInfo(fileName).exists()) { + QString baseName; + int suffixIndex = 0; + do { + baseName = QFileInfo(fileName).baseName() + + QString::number(suffixIndex++); + } while (QFileInfo(baseName + TraceFileExtension).exists()); + fileName = baseName + TraceFileExtension; + } + + return QFileInfo(fileName).absoluteFilePath(); +} + +void QmlProfilerApplication::userCommand(const QString &command) +{ + QString cmd = command.trimmed(); + if (cmd == Constants::CMD_HELP + || cmd == Constants::CMD_HELP2 + || cmd == Constants::CMD_HELP3) { + printCommands(); + } else if (cmd == Constants::CMD_RECORD + || cmd == Constants::CMD_RECORD2) { + m_declarativeProfilerClient.setRecording( + !m_declarativeProfilerClient.isRecording()); + m_v8profilerClient.setRecording(!m_v8profilerClient.isRecording()); + m_declarativeDataReady = false; + m_v8DataReady = false; + } else if (cmd == Constants::CMD_QUIT + || cmd == Constants::CMD_QUIT2) { + print(QLatin1String("Quit")); + if (m_declarativeProfilerClient.isRecording()) { + m_quitAfterSave = true; + m_declarativeDataReady = false; + m_v8DataReady = false; + m_declarativeProfilerClient.setRecording(false); + m_v8profilerClient.setRecording(false); + } else { + quit(); + } + } +} + +void QmlProfilerApplication::run() +{ + if (m_runMode == LaunchMode) { + m_process = new QProcess(this); + QStringList arguments; + arguments << QString::fromLatin1("-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_verbose && !(m_connectionAttempts % 5)) {// print every 5 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(); + 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_declarativeProfilerClient.isRecording() && + !m_v8profilerClient.isRecording()) + recordingStatus = recordingStatus.arg(QLatin1String("Off")); + else + recordingStatus = recordingStatus.arg(QLatin1String("On")); + print(recordingStatus); +} + +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() +{ + Q_ASSERT(m_process); + while (m_process->bytesAvailable()) { + QTextStream out(stdout); + out << m_process->readAll(); + } +} + +void QmlProfilerApplication::processFinished() +{ + Q_ASSERT(m_process); + if (m_process->exitStatus() == QProcess::NormalExit) { + logStatus(QString("Process exited (%1).").arg(m_process->exitCode())); + + if (m_declarativeProfilerClient.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."); + // blocked server is waiting for recording message from both clients + // once the last one is connected, both messages should be sent + m_declarativeProfilerClient.sendRecordingStatus(); + m_v8profilerClient.sendRecordingStatus(); +} + +void QmlProfilerApplication::profilerClientEnabled() +{ + logStatus("Profiler client is attached."); + + // blocked server is waiting for recording message from both clients + // once the last one is connected, both messages should be sent + m_declarativeProfilerClient.sendRecordingStatus(); + m_v8profilerClient.sendRecordingStatus(); +} + +void QmlProfilerApplication::traceFinished() +{ + const QString fileName = traceFileName(); + + if (m_profileData.save(fileName)) + print(QString("Saving trace to %1.").arg(fileName)); + + if (m_quitAfterSave) + quit(); +} + +void QmlProfilerApplication::recordingChanged() +{ + if (m_declarativeProfilerClient.isRecording()) { + print(QLatin1String("Recording is on.")); + } else { + print(QLatin1String("Recording is off.")); + } +} + +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; +} + +void QmlProfilerApplication::declarativeComplete() +{ + m_declarativeDataReady = true; + if (m_v8profilerClient.state() != QDeclarativeDebugClient::Enabled || + m_v8DataReady) { + m_profileData.complete(); + // once complete is sent, reset the flag + m_declarativeDataReady = false; + } +} + +void QmlProfilerApplication::v8Complete() +{ + m_v8DataReady = true; + if (m_declarativeProfilerClient.state() != QDeclarativeDebugClient::Enabled || + m_declarativeDataReady) { + m_profileData.complete(); + // once complete is sent, reset the flag + m_v8DataReady = false; + } +} diff --git a/tools/qmlprofiler/qmlprofilerapplication.h b/tools/qmlprofiler/qmlprofilerapplication.h new file mode 100644 index 0000000000..49ddaf88cd --- /dev/null +++ b/tools/qmlprofiler/qmlprofilerapplication.h @@ -0,0 +1,117 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/ +** +** This file is part of the QtDeclarative module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** 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. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL included in the packaging of this +** file. Please review the following information to ensure the GNU General +** Public License version 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** 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. +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QMLPROFILERAPPLICATION_H +#define QMLPROFILERAPPLICATION_H + +#include +#include +#include + +#include "profileclient.h" + +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 profilerClientEnabled(); + void traceFinished(); + void recordingChanged(); + + void print(const QString &line); + void logError(const QString &error); + void logStatus(const QString &status); + + void declarativeComplete(); + void v8Complete(); + +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_verbose; + bool m_quitAfterSave; + + QDeclarativeDebugConnection m_connection; + DeclarativeProfileClient m_declarativeProfilerClient; + V8ProfileClient m_v8profilerClient; + ProfileData m_profileData; + QTimer m_connectTimer; + uint m_connectionAttempts; + + bool m_declarativeDataReady; + bool m_v8DataReady; +}; + +#endif // QMLPROFILERAPPLICATION_H diff --git a/tools/tools.pro b/tools/tools.pro index 207299bbfb..34e90ed816 100644 --- a/tools/tools.pro +++ b/tools/tools.pro @@ -1,5 +1,5 @@ TEMPLATE = subdirs -SUBDIRS += qmlscene qmlplugindump qmlmin qmleasing +SUBDIRS += qmlscene qmlplugindump qmlmin qmleasing qmlprofiler contains(QT_CONFIG, qmltest): SUBDIRS += qmltestrunner -- cgit v1.2.3