Commit a2625999 authored by Christiaan Janssen's avatar Christiaan Janssen
Browse files

QmlProfiler: V8 profiling

Change-Id: I926c5821d31453064f5dbed2b5a10f6195761f42
Reviewed-on: http://codereview.qt-project.org/5892

Reviewed-by: default avatarKai Koehne <kai.koehne@nokia.com>
Sanity-Review: Qt Sanity Bot <qt_sanity_bot@ovi.com>
parent 9057d878
......@@ -13,14 +13,16 @@ HEADERS += \
$$PWD/qmljsdebugclient_global.h \
$$PWD/qmlprofilertraceclient.h \
$$PWD/qmlprofilereventtypes.h \
$$PWD/qmlprofilereventlist.h
$$PWD/qmlprofilereventlist.h \
$$PWD/qv8profilerclient.h
SOURCES += \
$$PWD/qdeclarativeenginedebug.cpp \
$$PWD/qpacketprotocol.cpp \
$$PWD/qdeclarativedebugclient.cpp \
$$PWD/qmlprofilertraceclient.cpp \
$$PWD/qmlprofilereventlist.cpp
$$PWD/qmlprofilereventlist.cpp \
$$PWD/qv8profilerclient.cpp
OTHER_FILES += \
$$PWD/qmljsdebugclient.pri \
......
......@@ -169,6 +169,10 @@ public:
QList<QmlEventEndTimeData> m_endTimeSortedList;
QList<QmlEventStartTimeData> m_startTimeSortedList;
void collectV8Statistics();
QV8EventDescriptions m_v8EventList;
QHash<int, QV8EventData *> m_v8parents;
// file to load
QString m_filename;
ParsingStatus m_parsingStatus;
......@@ -198,6 +202,11 @@ void QmlProfilerEventList::clear()
d->m_endTimeSortedList.clear();
d->m_startTimeSortedList.clear();
qDeleteAll(d->m_v8EventList);
d->m_v8EventList.clear();
d->m_v8parents.clear();
emit countChanged();
}
......@@ -206,6 +215,11 @@ QList <QmlEventData *> QmlProfilerEventList::getEventDescriptions() const
return d->m_eventDescriptions.values();
}
const QV8EventDescriptions& QmlProfilerEventList::getV8Events() const
{
return d->m_v8EventList;
}
void QmlProfilerEventList::addRangedEvent(int type, qint64 startTime, qint64 length,
const QStringList &data, const QString &fileName, int line)
{
......@@ -269,8 +283,71 @@ void QmlProfilerEventList::addRangedEvent(int type, qint64 startTime, qint64 len
emit countChanged();
}
void QmlProfilerEventList::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);
QV8EventData *newData = 0;
// time is given in milliseconds, but internally we store it in microseconds
totalTime *= 1e6;
selfTime *= 1e6;
// cumulate information
foreach (QV8EventData *v8event, d->m_v8EventList) {
if (v8event->displayName == displayName && v8event->functionName == function)
newData = v8event;
}
if (!newData) {
newData = new QV8EventData();
newData->displayName = displayName;
newData->filename = filename;
newData->functionName = function;
newData->line = lineNumber;
newData->totalTime = totalTime;
newData->selfTime = selfTime;
d->m_v8EventList << newData;
} else {
newData->totalTime += totalTime;
newData->selfTime += selfTime;
}
d->m_v8parents[depth] = newData;
if (depth > 0) {
QV8EventData* parentEvent = d->m_v8parents.value(depth-1);
if (parentEvent) {
if (!newData->parentList.contains(parentEvent))
newData->parentList << parentEvent;
if (!parentEvent->childrenList.contains(newData))
parentEvent->childrenList << newData;
}
}
}
void QmlProfilerEventList::QmlProfilerEventListPrivate::collectV8Statistics()
{
double totalTimes = 0;
double selfTimes = 0;
foreach (QV8EventData *v8event, m_v8EventList) {
totalTimes += v8event->totalTime;
selfTimes += v8event->selfTime;
}
// prevent divisions by 0
if (totalTimes == 0)
totalTimes = 1;
if (selfTimes == 0)
selfTimes = 1;
foreach (QV8EventData *v8event, m_v8EventList) {
v8event->totalPercent = v8event->totalTime * 100.0 / totalTimes;
v8event->selfPercent = v8event->selfTime * 100.0 / selfTimes;
}
}
void QmlProfilerEventList::complete()
{
d->collectV8Statistics();
postProcess();
}
......@@ -699,6 +776,31 @@ void QmlProfilerEventList::save(const QString &filename)
}
stream.writeEndElement(); // eventList
stream.writeStartElement("v8profile"); // v8 profiler output
foreach (QV8EventData *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->childrenList.isEmpty()) {
stream.writeStartElement("childrenEvents");
QStringList childrenIndexes;
foreach (QV8EventData *v8child, v8event->childrenList) {
childrenIndexes << QString::number(d->m_v8EventList.indexOf(v8child));
}
stream.writeAttribute("list", childrenIndexes.join(QString(", ")));
stream.writeEndElement();
}
stream.writeEndElement();
}
stream.writeEndElement(); // v8 profiler output
stream.writeEndElement(); // trace
stream.writeEndDocument();
......@@ -733,8 +835,13 @@ void QmlProfilerEventList::load()
// erase current
clear();
bool readingQmlEvents = false;
bool readingV8Events = false;
QHash <int, QmlEventData *> descriptionBuffer;
QmlEventData *currentEvent = 0;
QHash <int, QV8EventData *> v8eventBuffer;
QHash <int, QString> childrenIndexes;
QV8EventData *v8event = 0;
bool startTimesAreSorted = true;
QXmlStreamReader stream(&file);
......@@ -745,16 +852,14 @@ void QmlProfilerEventList::load()
switch (token) {
case QXmlStreamReader::StartDocument : continue;
case QXmlStreamReader::StartElement : {
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 QmlEventData;
currentEvent = descriptionBuffer[ndx];
}
if (elementName == "eventData" && !readingV8Events) {
readingQmlEvents = true;
break;
}
if (elementName == "v8profile" && !readingQmlEvents) {
readingV8Events = true;
}
if (elementName == "range") {
QmlEventStartTimeData rangedEvent;
QXmlStreamAttributes attributes = stream.attributes();
......@@ -783,41 +888,127 @@ void QmlProfilerEventList::load()
break;
}
// the remaining are eventdata elements
if (!currentEvent)
break;
stream.readNext();
if (stream.tokenType() != QXmlStreamReader::Characters)
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 QmlEventData;
currentEvent = descriptionBuffer[ndx];
} else {
currentEvent = 0;
}
break;
}
QString readData = stream.text().toString();
// the remaining are eventdata or v8eventdata elements
if (!currentEvent)
break;
if (elementName == "displayname") {
currentEvent->displayname = readData;
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 = qmlEventType(readData);
break;
}
if (elementName == "filename") {
currentEvent->filename = readData;
break;
}
if (elementName == "line") {
currentEvent->line = readData.toInt();
break;
}
if (elementName == "details") {
currentEvent->details = readData;
break;
}
}
if (elementName == "type") {
currentEvent->eventType = qmlEventType(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 QV8EventData;
v8event = v8eventBuffer[ndx];
} else {
v8event = 0;
}
break;
}
// the remaining are eventdata or v8eventdata elements
if (!v8event)
break;
if (elementName == "childrenEvents") {
QXmlStreamAttributes attributes = stream.attributes();
if (attributes.hasAttribute("list")) {
// store for later parsing (we haven't read all the events yet)
childrenIndexes[v8eventBuffer.key(v8event)] = attributes.value("list").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();
break;
}
if (elementName == "selfTime") {
v8event->selfTime = readData.toDouble();
break;
}
}
if (elementName == "filename") {
currentEvent->filename = readData;
break;
}
case QXmlStreamReader::EndElement : {
if (elementName == "event") {
currentEvent = 0;
break;
}
if (elementName == "line") {
currentEvent->line = readData.toInt();
if (elementName == "eventData") {
readingQmlEvents = false;
break;
}
if (elementName == "details") {
currentEvent->details = readData;
break;
if (elementName == "v8profile") {
readingV8Events = false;
}
break;
}
case QXmlStreamReader::EndElement : {
if (elementName == "event")
currentEvent = 0;
break;
}
default: break;
}
......@@ -850,12 +1041,27 @@ void QmlProfilerEventList::load()
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((","));
foreach (const QString &childString, childrenStrings) {
int childIndex = childString.toInt();
if (v8eventBuffer.value(childIndex)) {
v8eventBuffer[parentIndex]->childrenList << v8eventBuffer[childIndex];
v8eventBuffer[childIndex]->parentList << v8eventBuffer[parentIndex];
}
}
}
// store v8 events
d->m_v8EventList = v8eventBuffer.values();
emit countChanged();
setParsingStatus(SortingEndsStatus);
descriptionBuffer.clear();
d->collectV8Statistics();
postProcess();
}
......
......@@ -60,8 +60,23 @@ struct QMLJSDEBUGCLIENT_EXPORT QmlEventData
qint64 medianTime;
};
struct QMLJSDEBUGCLIENT_EXPORT QV8EventData
{
QString displayName;
QString filename;
QString functionName;
int line;
double totalTime; // given in milliseconds
double totalPercent;
double selfTime;
double selfPercent;
QList< QV8EventData *> parentList;
QList< QV8EventData *> childrenList;
};
typedef QHash<QString, QmlEventData *> QmlEventHash;
typedef QList<QmlEventData *> QmlEventDescriptions;
typedef QList<QV8EventData *> QV8EventDescriptions;
enum ParsingStatus {
GettingDataStatus = 0,
......@@ -81,6 +96,7 @@ public:
~QmlProfilerEventList();
QmlEventDescriptions getEventDescriptions() const;
const QV8EventDescriptions& getV8Events() const;
int findFirstIndex(qint64 startTime) const;
int findLastIndex(qint64 endTime) const;
......@@ -114,6 +130,8 @@ public slots:
void addRangedEvent(int type, qint64 startTime, qint64 length,
const QStringList &data, const QString &fileName, int line);
void complete();
void addV8Event(int depth,const QString &function,const QString &filename, int lineNumber, double totalTime, double selfTime);
void save(const QString &filename);
void load(const QString &filename);
void setFilename(const QString &filename);
......
/**************************************************************************
**
** This file is part of Qt Creator
**
** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
**
** Contact: Nokia Corporation (info@qt.nokia.com)
**
**
** GNU Lesser General Public License Usage
**
** This file may be used under the terms of the GNU Lesser General Public
** License version 2.1 as published by the Free Software Foundation and
** appearing in the file LICENSE.LGPL included in the packaging of this file.
** Please review the following information to ensure the GNU Lesser General
** Public License version 2.1 requirements will be met:
** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** In addition, as a special exception, Nokia gives you certain additional
** rights. These rights are described in the Nokia Qt LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
** Other Usage
**
** Alternatively, this file may be used in accordance with the terms and
** conditions contained in a signed written agreement between you and Nokia.
**
** If you have questions regarding the use of this file, please contact
** Nokia at info@qt.nokia.com.
**
**************************************************************************/
#include "qv8profilerclient.h"
namespace QmlJsDebugClient {
class QV8ProfilerClientPrivate {
public:
QV8ProfilerClientPrivate(QV8ProfilerClient *_q)
: q(_q)
, recording(false)
{
}
void sendRecordingStatus();
QV8ProfilerClient *q;
bool recording;
};
} // namespace QmlJsDebugClient
using namespace QmlJsDebugClient;
void QV8ProfilerClientPrivate::sendRecordingStatus()
{
QByteArray ba;
QDataStream stream(&ba, QIODevice::WriteOnly);
QByteArray cmd("V8PROFILER");
QByteArray option("");
QByteArray title("");
if (recording) {
option = "start";
} else {
option = "stop";
}
stream << cmd << option << title;
q->sendMessage(ba);
}
QV8ProfilerClient::QV8ProfilerClient(QDeclarativeDebugConnection *client)
: QDeclarativeDebugClient(QLatin1String("V8Profiler"), client)
, d(new QV8ProfilerClientPrivate(this))
{
}
QV8ProfilerClient::~QV8ProfilerClient()
{
delete d;
}
void QV8ProfilerClient::clearData()
{
emit cleared();
}
bool QV8ProfilerClient::isRecording() const
{
return d->recording;
}
void QV8ProfilerClient::setRecording(bool v)
{
if (v == d->recording)
return;
d->recording = v;
if (status() == Enabled) {
d->sendRecordingStatus();
}
emit recordingChanged(v);
}
void QV8ProfilerClient::statusChanged(Status status)
{
if (status == Enabled) {
d->sendRecordingStatus();
emit enabled();
}
}
void QV8ProfilerClient::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->v8range(depth, function, filename, lineNumber, totalTime, selfTime);
}
}
/**************************************************************************
**
** This file is part of Qt Creator
**
** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
**
** Contact: Nokia Corporation (info@qt.nokia.com)
**
**
** GNU Lesser General Public License Usage
**
** This file may be used under the terms of the GNU Lesser General Public
** License version 2.1 as published by the Free Software Foundation and
** appearing in the file LICENSE.LGPL included in the packaging of this file.
** Please review the following information to ensure the GNU Lesser General
** Public License version 2.1 requirements will be met:
** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** In addition, as a special exception, Nokia gives you certain additional
** rights. These rights are described in the Nokia Qt LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
** Other Usage
**
** Alternatively, this file may be used in accordance with the terms and
** conditions contained in a signed written agreement between you and Nokia.
**
** If you have questions regarding the use of this file, please contact
** Nokia at info@qt.nokia.com.
**
**************************************************************************/
#ifndef QV8PROFILERCLIENT_H
#define QV8PROFILERCLIENT_H
#include "qdeclarativedebugclient.h"
#include "qmlprofilereventtypes.h"
#include "qmljsdebugclient_global.h"
#include <QtCore/QStack>
#include <QtCore/QStringList>
namespace QmlJsDebugClient {