QmlProfilerTool: add tool to QDeclarative
authorAurindam Jana <aurindam.jana@nokia.com>
Thu, 9 Feb 2012 16:04:43 +0000 (17:04 +0100)
committerQt by Nokia <qt-info@nokia.com>
Mon, 20 Feb 2012 16:59:32 +0000 (17:59 +0100)
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 <kai.koehne@nokia.com>
12 files changed:
tools/qmlprofiler/commandlistener.cpp [new file with mode: 0644]
tools/qmlprofiler/commandlistener.h [new file with mode: 0644]
tools/qmlprofiler/constants.h [new file with mode: 0644]
tools/qmlprofiler/main.cpp [new file with mode: 0644]
tools/qmlprofiler/profileclient.cpp [new file with mode: 0644]
tools/qmlprofiler/profileclient.h [new file with mode: 0644]
tools/qmlprofiler/profiledata.cpp [new file with mode: 0644]
tools/qmlprofiler/profiledata.h [new file with mode: 0644]
tools/qmlprofiler/qmlprofiler.pro [new file with mode: 0644]
tools/qmlprofiler/qmlprofilerapplication.cpp [new file with mode: 0644]
tools/qmlprofiler/qmlprofilerapplication.h [new file with mode: 0644]
tools/tools.pro

diff --git a/tools/qmlprofiler/commandlistener.cpp b/tools/qmlprofiler/commandlistener.cpp
new file mode 100644 (file)
index 0000000..cc96916
--- /dev/null
@@ -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 <QtCore/QTextStream>
+
+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 (file)
index 0000000..77fd793
--- /dev/null
@@ -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 <QtCore/QThread>
+
+class CommandListener : public QThread
+{
+    Q_OBJECT
+public:
+    CommandListener(QObject *parent = 0);
+
+    void run();
+
+    void requestStop() { m_stopRequested = true; }
+signals:
+    void command(const QString &command);
+
+private:
+    bool m_stopRequested;
+};
+
+#endif // COMMANDLISTENER_H
diff --git a/tools/qmlprofiler/constants.h b/tools/qmlprofiler/constants.h
new file mode 100644 (file)
index 0000000..2e6acda
--- /dev/null
@@ -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 (file)
index 0000000..7d9413d
--- /dev/null
@@ -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 (file)
index 0000000..3e84569
--- /dev/null
@@ -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 <QtCore/QStack>
+#include <QtCore/QStringList>
+
+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<qint64> rangeStartTimes[QDeclarativeProfilerService::MaximumRangeType];
+    QStack<QStringList> rangeDatas[QDeclarativeProfilerService::MaximumRangeType];
+    QStack<EventLocation> 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<qint64>(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<qint64>(1) << range))
+                    d->inProgressRanges &= ~(static_cast<qint64>(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 (file)
index 0000000..87e9dbc
--- /dev/null
@@ -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 <QtDeclarative/private/qdeclarativedebugclient_p.h>
+#include <QtDeclarative/private/qdeclarativeprofilerservice_p.h>
+
+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 (file)
index 0000000..19c282b
--- /dev/null
@@ -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 <QtCore/QStringList>
+#include <QtCore/QRegExp>
+#include <QtCore/QUrl>
+#include <QtCore/QFile>
+#include <QtCore/QXmlStreamReader>
+
+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 <int> 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<DeclarativeEventEndTime> m_endTimeSortedList;
+    QList<DeclarativeEventStartTime> m_startTimeSortedList;
+
+    void collectV8Statistics();
+    V8Events m_v8EventList;
+    QHash<int, V8Event *> m_v8parents;
+
+    DeclarativeEvent m_qmlRootEvent;
+    V8Event m_v8RootEvent;
+    QString m_rootEventName;
+    QString m_rootEventDesc;
+
+    QHash<int, DeclarativeEventTypeCount *> 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("<program>");
+    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("<bytecode>");
+        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("<Animation Update>");
+    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<int, DeclarativeEvent*> 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<qint64> > 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<qint64> > 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<DeclarativeEventStartTime>::iterator itFrom =
+            d->m_startTimeSortedList.end() - 2;
+    QList<DeclarativeEventStartTime>::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<DeclarativeEventEndTime>::iterator itFrom =
+            d->m_endTimeSortedList.begin();
+    QList<DeclarativeEventEndTime>::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 <int, qint64> endtimesPerLevel;
+    QList <int> nestingLevels;
+    QList < QHash <int, qint64> > endtimesPerNestingLevel;
+    int level = MIN_LEVEL;
+    endtimesPerLevel[MIN_LEVEL] = 0;
+
+    for (int i = 0; i < QDeclarativeProfilerService::MaximumRangeType; i++) {
+        nestingLevels << MIN_LEVEL;
+        QHash <int, qint64> dummyHash;
+        dummyHash[MIN_LEVEL] = 0;
+        endtimesPerNestingLevel << dummyHash;
+    }
+
+    for (int i=0; i<d->m_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 <int, int> 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 <DeclarativeEvent *> stackRefs;
+    QList <DeclarativeEventStartTime *> 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 <int, DeclarativeEvent *> descriptionBuffer;
+    DeclarativeEvent *currentEvent = 0;
+    QHash <int, V8Event *> v8eventBuffer;
+    QHash <int, QString> childrenIndexes;
+    QHash <int, QString> childrenTimes;
+    QHash <int, QString> 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 (file)
index 0000000..eaa2127
--- /dev/null
@@ -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 <QtDeclarative/private/qdeclarativeprofilerservice_p.h>
+
+#include <QtCore/QObject>
+#include <QtCore/QHash>
+
+struct DeclarativeEvent;
+struct V8Event;
+
+typedef QHash<QString, DeclarativeEvent *> DeclarativeEventHash;
+typedef QList<DeclarativeEvent *> DeclarativeEvents;
+typedef QList<V8Event *> 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 <QString, DeclarativeEventSub *> parentHash;
+    QHash <QString, DeclarativeEventSub *> 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 <QString, V8EventSub *> parentHash;
+    QHash <QString, V8EventSub *> 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 (file)
index 0000000..1d8f89a
--- /dev/null
@@ -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 (file)
index 0000000..af5f429
--- /dev/null
@@ -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 <QtCore/QStringList>
+#include <QtCore/QTextStream>
+#include <QtCore/QProcess>
+#include <QtCore/QTimer>
+#include <QtCore/QDateTime>
+#include <QtCore/QFileInfo>
+#include <QtCore/QDebug>
+
+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 <number>, -port <number>\n"
+"           TCP/IP port to use, default is 3768.\n"
+"    -v, -verbose\n"
+"           Print debugging output.\n"
+"    -version\n"
+"           Show the version of qmlprofiler and exit.\n";
+
+static const char commandTextC[] =
+"Commands:\n"
+"    r, record\n"
+"           Switch recording on or off.\n"
+"    q, quit\n"
+"           Terminate program.";
+
+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 (file)
index 0000000..49ddaf8
--- /dev/null
@@ -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 <QtCore/QCoreApplication>
+#include <QtCore/QProcess>
+#include <QtCore/QTimer>
+
+#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
index 207299b..34e90ed 100644 (file)
@@ -1,5 +1,5 @@
 TEMPLATE = subdirs
-SUBDIRS +=  qmlscene qmlplugindump qmlmin qmleasing
+SUBDIRS +=  qmlscene qmlplugindump qmlmin qmleasing qmlprofiler
 
 contains(QT_CONFIG, qmltest): SUBDIRS += qmltestrunner