forked from qt-creator/qt-creator
QmlProfiler: New event list with caching, load, save
Change-Id: I640a16649156a49f2d7e7006d6b2ea38fe218620 Reviewed-on: http://codereview.qt.nokia.com/3043 Reviewed-by: Qt Sanity Bot <qt_sanity_bot@ovi.com> Reviewed-by: Kai Koehne <kai.koehne@nokia.com>
This commit is contained in:
@@ -40,10 +40,8 @@ public:
|
||||
: inProgressRanges(0)
|
||||
, maximumTime(0)
|
||||
, recording(false)
|
||||
, nestingLevel(0)
|
||||
{
|
||||
::memset(rangeCount, 0, MaximumQmlEventType * sizeof(int));
|
||||
::memset(nestingInType, 0, MaximumQmlEventType * sizeof(int));
|
||||
}
|
||||
|
||||
qint64 inProgressRanges;
|
||||
@@ -53,8 +51,6 @@ public:
|
||||
int rangeCount[MaximumQmlEventType];
|
||||
qint64 maximumTime;
|
||||
bool recording;
|
||||
int nestingLevel;
|
||||
int nestingInType[MaximumQmlEventType];
|
||||
};
|
||||
|
||||
} // namespace QmlJsDebugClient
|
||||
@@ -77,8 +73,6 @@ QmlProfilerTraceClient::~QmlProfilerTraceClient()
|
||||
void QmlProfilerTraceClient::clearData()
|
||||
{
|
||||
::memset(d->rangeCount, 0, MaximumQmlEventType * sizeof(int));
|
||||
::memset(d->nestingInType, 0, MaximumQmlEventType * sizeof(int));
|
||||
d->nestingLevel = 0;
|
||||
emit cleared();
|
||||
}
|
||||
|
||||
@@ -151,8 +145,6 @@ void QmlProfilerTraceClient::messageReceived(const QByteArray &data)
|
||||
d->rangeStartTimes[range].push(time);
|
||||
d->inProgressRanges |= (static_cast<qint64>(1) << range);
|
||||
++d->rangeCount[range];
|
||||
++d->nestingLevel;
|
||||
++d->nestingInType[range];
|
||||
} else if (messageType == RangeData) {
|
||||
QString data;
|
||||
stream >> data;
|
||||
@@ -183,10 +175,7 @@ void QmlProfilerTraceClient::messageReceived(const QByteArray &data)
|
||||
Location location = d->rangeLocations[range].count() ? d->rangeLocations[range].pop() : Location();
|
||||
|
||||
qint64 startTime = d->rangeStartTimes[range].pop();
|
||||
emit this->range((QmlEventType)range, d->nestingLevel, d->nestingInType[range], startTime,
|
||||
time - startTime, data, location.fileName, location.line);
|
||||
--d->nestingLevel;
|
||||
--d->nestingInType[range];
|
||||
emit this->range((QmlEventType)range, startTime, time - startTime, data, location.fileName, location.line);
|
||||
if (d->rangeCount[range] == 0) {
|
||||
int count = d->rangeDatas[range].count() +
|
||||
d->rangeStartTimes[range].count() +
|
||||
|
@@ -91,7 +91,7 @@ signals:
|
||||
void complete();
|
||||
void gap(qint64 time);
|
||||
void event(int event, qint64 time);
|
||||
void range(int type, int nestingLevel, int nestingInType, qint64 startTime, qint64 length,
|
||||
void range(int type, qint64 startTime, qint64 length,
|
||||
const QStringList &data, const QString &fileName, int line);
|
||||
|
||||
void recordingChanged(bool arg);
|
||||
|
@@ -32,10 +32,9 @@
|
||||
|
||||
.pragma library
|
||||
|
||||
var ranges = [ ];
|
||||
var xmargin = 0;
|
||||
var ymargin = 0;
|
||||
var nestingDepth = [];
|
||||
var qmlEventList = 0;
|
||||
|
||||
var names = [ "Painting", "Compiling", "Creating", "Binding", "Handling Signal"]
|
||||
//### need better way to manipulate color from QML. In the meantime, these need to be kept in sync.
|
||||
@@ -45,10 +44,8 @@ var xRayColors = [ "#6699CCB3", "#6699CCCC", "#6699B3CC", "#669999CC", "#66CC99B
|
||||
|
||||
function reset()
|
||||
{
|
||||
ranges = [];
|
||||
xmargin = 0;
|
||||
ymargin = 0;
|
||||
nestingDepth = [];
|
||||
}
|
||||
|
||||
//draw background of the graph
|
||||
@@ -65,26 +62,26 @@ function drawGraph(canvas, ctxt, region)
|
||||
//draw the actual data to be graphed
|
||||
function drawData(canvas, ctxt, region)
|
||||
{
|
||||
if (ranges.length == 0)
|
||||
if ((!qmlEventList) || qmlEventList.count() == 0)
|
||||
return;
|
||||
|
||||
var width = canvas.canvasSize.width - xmargin;
|
||||
var height = canvas.height - ymargin;
|
||||
|
||||
var sumValue = ranges[ranges.length - 1].start + ranges[ranges.length - 1].duration - ranges[0].start;
|
||||
var sumValue = qmlEventList.lastTimeMark() - qmlEventList.firstTimeMark();
|
||||
var spacing = width / sumValue;
|
||||
|
||||
ctxt.fillStyle = "rgba(0,0,0,1)";
|
||||
var highest = [0,0,0,0,0];
|
||||
|
||||
//### only draw those in range
|
||||
for (var ii = 0; ii < ranges.length; ++ii) {
|
||||
for (var ii = 0; ii < qmlEventList.count(); ++ii) {
|
||||
|
||||
var xx = (ranges[ii].start - ranges[0].start) * spacing + xmargin;
|
||||
var xx = (qmlEventList.getStartTime(ii) - qmlEventList.firstTimeMark()) * spacing + xmargin;
|
||||
if (xx > region.x + region.width)
|
||||
continue;
|
||||
|
||||
var size = ranges[ii].duration * spacing;
|
||||
var size = qmlEventList.getDuration(ii) * spacing;
|
||||
if (xx + size < region.x)
|
||||
continue;
|
||||
|
||||
@@ -92,9 +89,10 @@ function drawData(canvas, ctxt, region)
|
||||
size = 0.5;
|
||||
|
||||
xx = Math.round(xx);
|
||||
if (xx + size > highest[ranges[ii].type]) {
|
||||
ctxt.fillRect(xx, ranges[ii].type*10, size, 10);
|
||||
highest[ranges[ii].type] = xx+size;
|
||||
var ty = qmlEventList.getType(ii);
|
||||
if (xx + size > highest[ty]) {
|
||||
ctxt.fillRect(xx, ty*10, size, 10);
|
||||
highest[ty] = xx+size;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -107,12 +105,12 @@ function plot(canvas, ctxt, region)
|
||||
|
||||
function xScale(canvas)
|
||||
{
|
||||
if (ranges.length === 0)
|
||||
if ((!qmlEventList) || qmlEventList.count() == 0)
|
||||
return;
|
||||
|
||||
var width = canvas.canvasSize.width - xmargin;
|
||||
|
||||
var sumValue = ranges[ranges.length - 1].start + ranges[ranges.length - 1].duration - ranges[0].start;
|
||||
var sumValue = qmlEventList.lastTimeMark() - qmlEventList.firstTimeMark();
|
||||
var spacing = sumValue / width;
|
||||
return spacing;
|
||||
}
|
||||
|
@@ -38,7 +38,8 @@ Rectangle {
|
||||
id: root
|
||||
|
||||
property bool dataAvailable: true;
|
||||
property int eventCount: Plotter.ranges.length;
|
||||
property int eventCount: 0;
|
||||
property real progress: 0;
|
||||
|
||||
// move the cursor in the editor
|
||||
signal updateCursorPosition
|
||||
@@ -72,34 +73,35 @@ Rectangle {
|
||||
property bool mouseTracking: false;
|
||||
|
||||
onSelectedEventIndexChanged: {
|
||||
if ((!mouseTracking) && Plotter.ranges.length > 0
|
||||
&& selectedEventIndex > -1 && selectedEventIndex < Plotter.ranges.length) {
|
||||
if ((!mouseTracking) && eventCount > 0
|
||||
&& selectedEventIndex > -1 && selectedEventIndex < eventCount) {
|
||||
// re-center flickable if necessary
|
||||
var event = Plotter.ranges[selectedEventIndex];
|
||||
var xs = Plotter.xScale(canvas);
|
||||
var startTime = Plotter.ranges[0].start;
|
||||
if (rangeMover.value + startTime> event.start) {
|
||||
var startTime = qmlEventList.firstTimeMark();
|
||||
var eventStartTime = qmlEventList.getStartTime(selectedEventIndex);
|
||||
var eventDuration = qmlEventList.getDuration(selectedEventIndex);
|
||||
if (rangeMover.value + startTime > eventStartTime) {
|
||||
rangeMover.x = Math.max(0,
|
||||
Math.floor((event.start - startTime) / xs - canvas.canvasWindow.x - rangeMover.zoomWidth/2) );
|
||||
} else if (rangeMover.value + startTime + rangeMover.zoomWidth * xs < event.start + event.duration) {
|
||||
rangeMover.x = Math.floor((event.start + event.duration - startTime) / xs - canvas.canvasWindow.x - rangeMover.zoomWidth/2);
|
||||
Math.floor((eventStartTime - startTime) / xs - canvas.canvasWindow.x - rangeMover.zoomWidth/2) );
|
||||
} else if (rangeMover.value + startTime + rangeMover.zoomWidth * xs < eventStartTime + eventDuration) {
|
||||
rangeMover.x = Math.floor((eventStartTime + eventDuration - startTime) / xs - canvas.canvasWindow.x - rangeMover.zoomWidth/2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function nextEvent() {
|
||||
if (Plotter.ranges.length > 0) {
|
||||
if (eventCount > 0) {
|
||||
++selectedEventIndex;
|
||||
if (selectedEventIndex >= Plotter.ranges.length)
|
||||
if (selectedEventIndex >= eventCount)
|
||||
selectedEventIndex = 0;
|
||||
}
|
||||
}
|
||||
|
||||
function prevEvent() {
|
||||
if (Plotter.ranges.length > 0) {
|
||||
if (eventCount > 0) {
|
||||
--selectedEventIndex;
|
||||
if (selectedEventIndex < 0)
|
||||
selectedEventIndex = Plotter.ranges.length - 1;
|
||||
selectedEventIndex = eventCount - 1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -127,46 +129,29 @@ Rectangle {
|
||||
rangeMover.updateZoomControls();
|
||||
}
|
||||
|
||||
//handle debug data coming from C++
|
||||
Connections {
|
||||
target: connection
|
||||
|
||||
onRange: {
|
||||
if (root.dataAvailable) {
|
||||
root.clearData();
|
||||
}
|
||||
|
||||
// todo: consider nestingLevel
|
||||
if (!root.dataAvailable) {
|
||||
if (!Plotter.nestingDepth[type])
|
||||
Plotter.nestingDepth[type] = nestingInType;
|
||||
else
|
||||
Plotter.nestingDepth[type] = Math.max(Plotter.nestingDepth[type], nestingInType);
|
||||
Plotter.ranges.push( { type: type, start: startTime, duration: length, label: data, fileName: fileName, line: line, nestingLevel: nestingInType, nestingDepth: Plotter.nestingDepth[type] } );
|
||||
if (nestingInType == 1)
|
||||
Plotter.nestingDepth[type] = 1;
|
||||
root.eventCount = Plotter.ranges.length;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
onComplete: {
|
||||
root.dataAvailable = true;
|
||||
if (Plotter.ranges.length > 0) {
|
||||
view.visible = true;
|
||||
view.setRanges(Plotter.ranges);
|
||||
view.updateTimeline();
|
||||
canvas.requestPaint();
|
||||
rangeMover.x = 1 //### hack to get view to display things immediately
|
||||
rangeMover.x = 0
|
||||
rangeMover.opacity = 1
|
||||
}
|
||||
}
|
||||
|
||||
onDataCleared: {
|
||||
target: qmlEventList
|
||||
onCountChanged: {
|
||||
eventCount = qmlEventList.count();
|
||||
if (eventCount == 0)
|
||||
root.clearAll();
|
||||
if (eventCount > 1) {
|
||||
root.progress = Math.min(1.0,
|
||||
(qmlEventList.lastTimeMark() - qmlEventList.firstTimeMark()) / root.elapsedTime * 1e-9 ) * 0.5;
|
||||
} else
|
||||
root.progress = 0;
|
||||
}
|
||||
|
||||
onParsingStatusChanged: {
|
||||
root.dataAvailable = false;
|
||||
}
|
||||
|
||||
onDataReady: {
|
||||
if (eventCount > 0) {
|
||||
view.clearData();
|
||||
view.rebuildCache();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Elapsed
|
||||
@@ -198,8 +183,8 @@ Rectangle {
|
||||
height: flick.height + labels.y
|
||||
anchors.left: flick.left
|
||||
anchors.right: flick.right
|
||||
startTime: rangeMover.x * Plotter.xScale(canvas);
|
||||
endTime: (rangeMover.x + rangeMover.zoomWidth) * Plotter.xScale(canvas);
|
||||
startTime: rangeMover.x * Plotter.xScale(canvas) + qmlEventList.firstTimeMark();
|
||||
endTime: (rangeMover.x + rangeMover.zoomWidth) * Plotter.xScale(canvas) + qmlEventList.firstTimeMark();
|
||||
}
|
||||
|
||||
function hideRangeDetails() {
|
||||
@@ -241,6 +226,9 @@ Rectangle {
|
||||
TimelineView {
|
||||
id: view
|
||||
|
||||
eventList: qmlEventList;
|
||||
onEventListChanged: Plotter.qmlEventList = qmlEventList;
|
||||
|
||||
width: flick.width;
|
||||
height: flick.contentHeight;
|
||||
|
||||
@@ -256,6 +244,20 @@ Rectangle {
|
||||
endTime: startTime + (rangeMover.zoomWidth*Plotter.xScale(canvas))
|
||||
onEndTimeChanged: updateTimeline()
|
||||
|
||||
onCachedProgressChanged: root.progress = 0.5 + cachedProgress * 0.5;
|
||||
onCacheReady: {
|
||||
root.progress = 1.0;
|
||||
root.dataAvailable = true;
|
||||
if (root.eventCount > 0) {
|
||||
view.visible = true;
|
||||
view.updateTimeline();
|
||||
canvas.requestPaint();
|
||||
rangeMover.x = 1 //### hack to get view to display things immediately
|
||||
rangeMover.x = 0
|
||||
rangeMover.opacity = 1
|
||||
}
|
||||
}
|
||||
|
||||
delegate: Rectangle {
|
||||
id: obj
|
||||
|
||||
@@ -298,10 +300,10 @@ Rectangle {
|
||||
|
||||
function enableSelected(x,y) {
|
||||
myColor = Qt.darker(baseColor, 1.2)
|
||||
rangeDetails.duration = duration
|
||||
rangeDetails.label = label
|
||||
rangeDetails.file = fileName
|
||||
rangeDetails.line = line
|
||||
rangeDetails.duration = qmlEventList.getDuration(index)/1000.0;
|
||||
rangeDetails.label = qmlEventList.getDetails(index);
|
||||
rangeDetails.file = qmlEventList.getFilename(index);
|
||||
rangeDetails.line = qmlEventList.getLine(index);
|
||||
rangeDetails.type = Plotter.names[type]
|
||||
|
||||
var margin = 10;
|
||||
|
@@ -4,14 +4,7 @@ import "MainView.js" as Plotter
|
||||
Rectangle {
|
||||
id: statusDisplay
|
||||
|
||||
property real percentage : 0
|
||||
property int eventCount: root.eventCount
|
||||
onEventCountChanged: {
|
||||
if (state=="loading" && eventCount > 0 && root.elapsedTime > 0) {
|
||||
percentage = Math.min(1.0,
|
||||
(Plotter.ranges[Plotter.ranges.length-1].start - Plotter.ranges[0].start) / root.elapsedTime * 1e-9 );
|
||||
}
|
||||
}
|
||||
property real percentage : root.progress
|
||||
|
||||
width: Math.max(200, statusText.width+20);
|
||||
height: displayColumn.height + 20
|
||||
|
@@ -164,7 +164,7 @@ TiledCanvas {
|
||||
}
|
||||
|
||||
onMousePositionChanged: {
|
||||
if (!Plotter.ranges.length)
|
||||
if (!root.eventCount)
|
||||
return;
|
||||
|
||||
if (!pressed && timeDisplayEnd.visible)
|
||||
|
@@ -27,7 +27,8 @@ SOURCES += \
|
||||
codaqmlprofilerrunner.cpp \
|
||||
remotelinuxqmlprofilerrunner.cpp \
|
||||
qmlprofilereventview.cpp \
|
||||
qmlprofilerruncontrolfactory.cpp
|
||||
qmlprofilerruncontrolfactory.cpp \
|
||||
qmlprofilereventlist.cpp
|
||||
|
||||
HEADERS += \
|
||||
qmlprofilerconstants.h \
|
||||
@@ -43,7 +44,8 @@ HEADERS += \
|
||||
codaqmlprofilerrunner.h \
|
||||
remotelinuxqmlprofilerrunner.h \
|
||||
qmlprofilereventview.h \
|
||||
qmlprofilerruncontrolfactory.h
|
||||
qmlprofilerruncontrolfactory.h\
|
||||
qmlprofilereventlist.h
|
||||
|
||||
RESOURCES += \
|
||||
qml/qml.qrc
|
||||
|
849
src/plugins/qmlprofiler/qmlprofilereventlist.cpp
Normal file
849
src/plugins/qmlprofiler/qmlprofilereventlist.cpp
Normal file
@@ -0,0 +1,849 @@
|
||||
/**************************************************************************
|
||||
**
|
||||
** 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 "qmlprofilereventlist.h"
|
||||
|
||||
#include <coreplugin/icore.h>
|
||||
#include <QtCore/QUrl>
|
||||
#include <QtCore/QHash>
|
||||
#include <QtCore/QtAlgorithms>
|
||||
#include <QtCore/QString>
|
||||
#include <QtCore/QStringList>
|
||||
|
||||
#include <QtCore/QFile>
|
||||
#include <QtCore/QXmlStreamReader>
|
||||
#include <QtCore/QXmlStreamWriter>
|
||||
|
||||
#include <QtCore/QTimer>
|
||||
#include <QtGui/QMainWindow>
|
||||
#include <QtGui/QMessageBox>
|
||||
|
||||
#include <QDebug>
|
||||
|
||||
namespace QmlProfiler {
|
||||
namespace Internal {
|
||||
|
||||
#define MIN_LEVEL 1
|
||||
|
||||
// description
|
||||
struct QmlEventDescription {
|
||||
QmlEventDescription() : displayname(0), location(0), filename(0), details(0) {}
|
||||
~QmlEventDescription() {
|
||||
delete displayname;
|
||||
delete location;
|
||||
delete filename;
|
||||
delete details;
|
||||
}
|
||||
|
||||
QString *displayname;
|
||||
QString *location;
|
||||
QString *filename;
|
||||
QString *details;
|
||||
int eventType;
|
||||
int line;
|
||||
};
|
||||
|
||||
// endtimedata
|
||||
struct QmlEventEndTimeData {
|
||||
qint64 endTime;
|
||||
int startTimeIndex;
|
||||
QmlEventData *description;
|
||||
};
|
||||
|
||||
// starttimedata
|
||||
struct QmlEventStartTimeData {
|
||||
qint64 startTime;
|
||||
qint64 length;
|
||||
qint64 level;
|
||||
int endTimeIndex;
|
||||
qint64 nestingLevel;
|
||||
qint64 nestingDepth;
|
||||
QmlEventData *description;
|
||||
|
||||
};
|
||||
|
||||
// used by quicksort
|
||||
bool compareEndTimes(const QmlEventEndTimeData &t1, const QmlEventEndTimeData &t2)
|
||||
{
|
||||
return t1.endTime < t2.endTime;
|
||||
}
|
||||
|
||||
bool compareStartTimes(const QmlEventStartTimeData &t1, const QmlEventStartTimeData &t2)
|
||||
{
|
||||
return t1.startTime < t2.startTime;
|
||||
}
|
||||
|
||||
bool compareStartIndexes(const QmlEventEndTimeData &t1, const QmlEventEndTimeData &t2)
|
||||
{
|
||||
return t1.startTimeIndex < t2.startTimeIndex;
|
||||
}
|
||||
|
||||
class QmlProfilerEventList::QmlProfilerEventListPrivate
|
||||
{
|
||||
public:
|
||||
QmlProfilerEventListPrivate(QmlProfilerEventList *qq) : q(qq) {}
|
||||
|
||||
QmlProfilerEventList *q;
|
||||
|
||||
// Stored data
|
||||
QmlEventHash m_eventDescriptions;
|
||||
QList<QmlEventEndTimeData> m_endTimeSortedList;
|
||||
QList<QmlEventStartTimeData> m_startTimeSortedList;
|
||||
|
||||
// file to load
|
||||
QString m_filename;
|
||||
ParsingStatus m_parsingStatus;
|
||||
};
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
QmlProfilerEventList::QmlProfilerEventList(QObject *parent) :
|
||||
QObject(parent), d(new QmlProfilerEventListPrivate(this))
|
||||
{
|
||||
d->m_parsingStatus = DoneStatus;
|
||||
setObjectName("QmlProfilerEventStatistics");
|
||||
}
|
||||
|
||||
QmlProfilerEventList::~QmlProfilerEventList()
|
||||
{
|
||||
clear();
|
||||
}
|
||||
|
||||
void QmlProfilerEventList::clear()
|
||||
{
|
||||
foreach (QmlEventData *binding, d->m_eventDescriptions.values())
|
||||
delete binding;
|
||||
d->m_eventDescriptions.clear();
|
||||
|
||||
d->m_endTimeSortedList.clear();
|
||||
d->m_startTimeSortedList.clear();
|
||||
emit countChanged();
|
||||
}
|
||||
|
||||
QList <QmlEventData *> QmlProfilerEventList::getEventDescriptions() const
|
||||
{
|
||||
return d->m_eventDescriptions.values();
|
||||
}
|
||||
|
||||
void QmlProfilerEventList::addRangedEvent(int type, qint64 startTime, qint64 length,
|
||||
const QStringList &data, const QString &fileName, int line)
|
||||
{
|
||||
setParsingStatus(GettingDataStatus);
|
||||
|
||||
const QChar colon = QLatin1Char(':');
|
||||
QString displayName, location, details;
|
||||
|
||||
if (fileName.isEmpty()) {
|
||||
displayName = tr("<bytecode>");
|
||||
location = QString("--:%1:%2").arg(QString::number(type), data.join(" "));
|
||||
} else {
|
||||
const QString filePath = QUrl(fileName).path();
|
||||
displayName = filePath.mid(filePath.lastIndexOf(QChar('/')) + 1) + colon + QString::number(line);
|
||||
location = fileName+colon+QString::number(line);
|
||||
}
|
||||
|
||||
QmlEventData *newEvent;
|
||||
if (d->m_eventDescriptions.contains(location)) {
|
||||
newEvent = d->m_eventDescriptions[location];
|
||||
} else {
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
newEvent = new QmlEventData;
|
||||
newEvent->displayname = displayName;
|
||||
newEvent->filename = fileName;
|
||||
newEvent->location = location;
|
||||
newEvent->line = line;
|
||||
newEvent->eventType = (QmlJsDebugClient::QmlEventType)type;
|
||||
newEvent->details = details;
|
||||
d->m_eventDescriptions.insert(location, newEvent);
|
||||
}
|
||||
|
||||
QmlEventEndTimeData endTimeData;
|
||||
endTimeData.endTime = startTime + length;
|
||||
endTimeData.description = newEvent;
|
||||
endTimeData.startTimeIndex = d->m_startTimeSortedList.count();
|
||||
|
||||
QmlEventStartTimeData startTimeData;
|
||||
startTimeData.startTime = startTime;
|
||||
startTimeData.length = length;
|
||||
startTimeData.description = newEvent;
|
||||
startTimeData.endTimeIndex = d->m_endTimeSortedList.count();
|
||||
|
||||
d->m_endTimeSortedList << endTimeData;
|
||||
d->m_startTimeSortedList << startTimeData;
|
||||
|
||||
emit countChanged();
|
||||
}
|
||||
|
||||
void QmlProfilerEventList::complete()
|
||||
{
|
||||
postProcess();
|
||||
}
|
||||
|
||||
void QmlProfilerEventList::compileStatistics()
|
||||
{
|
||||
// clear existing statistics
|
||||
foreach (QmlEventData *eventDescription, d->m_eventDescriptions.values()) {
|
||||
eventDescription->calls = 0;
|
||||
// maximum possible value
|
||||
eventDescription->minTime = d->m_endTimeSortedList.last().endTime;
|
||||
eventDescription->maxTime = 0;
|
||||
eventDescription->cumulatedDuration = 0;
|
||||
eventDescription->parentList.clear();
|
||||
eventDescription->childrenList.clear();
|
||||
}
|
||||
|
||||
// compute parent-child relationship and call count
|
||||
QHash<int, QmlEventData*> lastParent;
|
||||
foreach (QmlEventStartTimeData eventStartData, d->m_startTimeSortedList) {
|
||||
QmlEventData *eventDescription = eventStartData.description;
|
||||
eventDescription->calls++;
|
||||
eventDescription->cumulatedDuration += eventStartData.length;
|
||||
if (eventDescription->maxTime < eventStartData.length)
|
||||
eventDescription->maxTime = eventStartData.length;
|
||||
if (eventDescription->minTime > eventStartData.length)
|
||||
eventDescription->minTime = eventStartData.length;
|
||||
|
||||
|
||||
if (eventStartData.level > MIN_LEVEL) {
|
||||
if (lastParent.contains(eventStartData.level-1)) {
|
||||
QmlEventData *parentEvent = lastParent[eventStartData.level-1];
|
||||
if (!eventDescription->parentList.contains(parentEvent))
|
||||
eventDescription->parentList.append(parentEvent);
|
||||
if (!parentEvent->childrenList.contains(eventDescription))
|
||||
parentEvent->childrenList.append(eventDescription);
|
||||
}
|
||||
}
|
||||
|
||||
lastParent[eventStartData.level] = eventDescription;
|
||||
}
|
||||
|
||||
// compute percentages
|
||||
double totalTime = 0;
|
||||
foreach (QmlEventData *binding, d->m_eventDescriptions.values()) {
|
||||
if (binding->filename.isEmpty())
|
||||
continue;
|
||||
totalTime += binding->cumulatedDuration;
|
||||
}
|
||||
|
||||
foreach (QmlEventData *binding, d->m_eventDescriptions.values()) {
|
||||
if (binding->filename.isEmpty())
|
||||
continue;
|
||||
binding->percentOfTime = binding->cumulatedDuration * 100.0 / totalTime;
|
||||
binding->timePerCall = binding->calls > 0 ? double(binding->cumulatedDuration) / binding->calls : 0;
|
||||
}
|
||||
|
||||
// continue postprocess
|
||||
postProcess();
|
||||
}
|
||||
|
||||
void QmlProfilerEventList::sortStartTimes()
|
||||
{
|
||||
if (d->m_startTimeSortedList.count() < 2)
|
||||
return;
|
||||
|
||||
// assuming startTimes is partially sorted
|
||||
// identify blocks of events and sort them with quicksort
|
||||
QList<QmlEventStartTimeData>::iterator itFrom = d->m_startTimeSortedList.end() - 2;
|
||||
QList<QmlEventStartTimeData>::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;
|
||||
|
||||
// continue postprocess
|
||||
postProcess();
|
||||
}
|
||||
|
||||
void QmlProfilerEventList::sortEndTimes()
|
||||
{
|
||||
// assuming endTimes is partially sorted
|
||||
// identify blocks of events and sort them with quicksort
|
||||
|
||||
if (d->m_endTimeSortedList.count() < 2)
|
||||
return;
|
||||
|
||||
QList<QmlEventEndTimeData>::iterator itFrom = d->m_endTimeSortedList.begin();
|
||||
QList<QmlEventEndTimeData>::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;
|
||||
|
||||
// continue postprocess
|
||||
postProcess();
|
||||
}
|
||||
|
||||
void QmlProfilerEventList::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 < QmlJsDebugClient::MaximumQmlEventType; 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;
|
||||
|
||||
// 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];
|
||||
}
|
||||
}
|
||||
|
||||
void QmlProfilerEventList::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 QmlProfilerEventList::postProcess()
|
||||
{
|
||||
switch (d->m_parsingStatus) {
|
||||
case GettingDataStatus: {
|
||||
setParsingStatus(SortingListsStatus);
|
||||
QTimer::singleShot(50, this, SLOT(sortStartTimes()));
|
||||
break;
|
||||
}
|
||||
case SortingEndsStatus: {
|
||||
setParsingStatus(SortingListsStatus);
|
||||
QTimer::singleShot(50, this, SLOT(sortEndTimes()));
|
||||
break;
|
||||
}
|
||||
case SortingListsStatus: {
|
||||
setParsingStatus(ComputingLevelsStatus);
|
||||
QTimer::singleShot(50, this, SLOT(computeLevels()));
|
||||
break;
|
||||
}
|
||||
case ComputingLevelsStatus: {
|
||||
setParsingStatus(CompilingStatisticsStatus);
|
||||
QTimer::singleShot(50, this, SLOT(compileStatistics()));
|
||||
break;
|
||||
}
|
||||
case CompilingStatisticsStatus: {
|
||||
linkEndsToStarts();
|
||||
setParsingStatus(DoneStatus);
|
||||
emit dataReady();
|
||||
break;
|
||||
}
|
||||
default: break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void QmlProfilerEventList::setParsingStatus(ParsingStatus ps)
|
||||
{
|
||||
if (d->m_parsingStatus != ps) {
|
||||
d->m_parsingStatus = ps;
|
||||
emit parsingStatusChanged();
|
||||
}
|
||||
}
|
||||
|
||||
ParsingStatus QmlProfilerEventList::getParsingStatus() const
|
||||
{
|
||||
return d->m_parsingStatus;
|
||||
}
|
||||
|
||||
void QmlProfilerEventList::linkEndsToStarts()
|
||||
{
|
||||
for (int i = 0; i < d->m_startTimeSortedList.count(); i++)
|
||||
d->m_endTimeSortedList[d->m_startTimeSortedList[i].endTimeIndex].startTimeIndex = i;
|
||||
}
|
||||
|
||||
void QmlProfilerEventList::computeLevels()
|
||||
{
|
||||
computeNestingLevels();
|
||||
computeNestingDepth();
|
||||
// continue postprocess
|
||||
postProcess();
|
||||
}
|
||||
|
||||
// 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 QmlProfilerEventList::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 QmlProfilerEventList::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 QmlProfilerEventList::firstTimeMark() const
|
||||
{
|
||||
if (d->m_startTimeSortedList.isEmpty())
|
||||
return 0;
|
||||
else {
|
||||
return d->m_startTimeSortedList[0].startTime;
|
||||
}
|
||||
}
|
||||
|
||||
qint64 QmlProfilerEventList::lastTimeMark() const
|
||||
{
|
||||
if (d->m_endTimeSortedList.isEmpty())
|
||||
return 0;
|
||||
else {
|
||||
return d->m_endTimeSortedList.last().endTime;
|
||||
}
|
||||
}
|
||||
|
||||
int QmlProfilerEventList::count() const
|
||||
{
|
||||
return d->m_startTimeSortedList.count();
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
void QmlProfilerEventList::save(const QString &filename) const
|
||||
{
|
||||
if (count() == 0) {
|
||||
showErrorDialog(tr("No data to save"));
|
||||
return;
|
||||
}
|
||||
|
||||
QFile file(filename);
|
||||
if (!file.open(QIODevice::WriteOnly)) {
|
||||
showErrorDialog(tr("Could not open %1 for writing").arg(filename));
|
||||
return;
|
||||
}
|
||||
|
||||
QXmlStreamWriter stream(&file);
|
||||
stream.setAutoFormatting(true);
|
||||
stream.writeStartDocument();
|
||||
|
||||
stream.writeStartElement("trace");
|
||||
|
||||
stream.writeStartElement("eventData");
|
||||
foreach (const QmlEventData *eventData, d->m_eventDescriptions.values()) {
|
||||
stream.writeStartElement("event");
|
||||
stream.writeAttribute("index", QString::number(d->m_eventDescriptions.keys().indexOf(eventData->location)));
|
||||
stream.writeTextElement("displayname", eventData->displayname);
|
||||
stream.writeTextElement("type", QString::number(eventData->eventType));
|
||||
if (!eventData->filename.isEmpty()) {
|
||||
stream.writeTextElement("filename", eventData->filename);
|
||||
stream.writeTextElement("line", QString::number(eventData->line));
|
||||
}
|
||||
stream.writeTextElement("details", eventData->details);
|
||||
stream.writeEndElement();
|
||||
}
|
||||
stream.writeEndElement(); // eventData
|
||||
|
||||
stream.writeStartElement("eventList");
|
||||
foreach (const QmlEventStartTimeData &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->location)));
|
||||
stream.writeEndElement();
|
||||
}
|
||||
stream.writeEndElement(); // eventList
|
||||
|
||||
stream.writeEndElement(); // trace
|
||||
stream.writeEndDocument();
|
||||
|
||||
file.close();
|
||||
}
|
||||
|
||||
void QmlProfilerEventList::setFilename(const QString &filename)
|
||||
{
|
||||
d->m_filename = filename;
|
||||
}
|
||||
|
||||
void QmlProfilerEventList::load(const QString &filename)
|
||||
{
|
||||
setFilename(filename);
|
||||
load();
|
||||
}
|
||||
|
||||
// "be strict in your output but tolerant in your inputs"
|
||||
void QmlProfilerEventList::load()
|
||||
{
|
||||
QString filename = d->m_filename;
|
||||
|
||||
QFile file(filename);
|
||||
|
||||
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
||||
showErrorDialog(tr("Could not open %1 for reading").arg(filename));
|
||||
return;
|
||||
}
|
||||
|
||||
setParsingStatus(GettingDataStatus);
|
||||
|
||||
// erase current
|
||||
clear();
|
||||
|
||||
QHash <int, QmlEventData *> descriptionBuffer;
|
||||
QmlEventData *currentEvent = 0;
|
||||
bool startTimesAreSorted = true;
|
||||
|
||||
QXmlStreamReader stream(&file);
|
||||
|
||||
while (!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 == "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];
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (elementName == "range") {
|
||||
QmlEventStartTimeData 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("eventIndex")) {
|
||||
int ndx = attributes.value("eventIndex").toString().toInt();
|
||||
if (!descriptionBuffer.value(ndx))
|
||||
descriptionBuffer[ndx] = new QmlEventData();
|
||||
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;
|
||||
|
||||
QmlEventEndTimeData endTimeEvent;
|
||||
endTimeEvent.endTime = rangedEvent.startTime + rangedEvent.length;
|
||||
endTimeEvent.startTimeIndex = d->m_startTimeSortedList.length()-1;
|
||||
endTimeEvent.description = rangedEvent.description;
|
||||
d->m_endTimeSortedList << endTimeEvent;
|
||||
break;
|
||||
}
|
||||
|
||||
// the remaining are eventdata 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 = QmlJsDebugClient::QmlEventType(readData.toInt());
|
||||
break;
|
||||
}
|
||||
if (elementName == "filename") {
|
||||
currentEvent->filename = readData;
|
||||
break;
|
||||
}
|
||||
if (elementName == "line") {
|
||||
currentEvent->line = readData.toInt();
|
||||
break;
|
||||
}
|
||||
if (elementName == "details") {
|
||||
currentEvent->details = readData;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case QXmlStreamReader::EndElement : {
|
||||
if (elementName == "event")
|
||||
currentEvent = 0;
|
||||
break;
|
||||
}
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
|
||||
file.close();
|
||||
|
||||
if (stream.hasError()) {
|
||||
showErrorDialog(tr("Error while parsing %1").arg(filename));
|
||||
clear();
|
||||
return;
|
||||
}
|
||||
|
||||
stream.clear();
|
||||
|
||||
// move the buffered data to the details cache
|
||||
foreach (QmlEventData *desc, descriptionBuffer.values()) {
|
||||
QString location = QString("%1:%2:%3").arg(QString::number(desc->eventType), desc->displayname, desc->details);
|
||||
desc->location = location;
|
||||
d->m_eventDescriptions[location] = 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++) {
|
||||
QmlEventStartTimeData startTimeData = d->m_startTimeSortedList[i];
|
||||
d->m_endTimeSortedList[startTimeData.endTimeIndex].startTimeIndex = i;
|
||||
}
|
||||
qSort(d->m_endTimeSortedList.begin(), d->m_endTimeSortedList.end(), compareStartIndexes);
|
||||
}
|
||||
|
||||
emit countChanged();
|
||||
|
||||
setParsingStatus(SortingEndsStatus);
|
||||
|
||||
descriptionBuffer.clear();
|
||||
|
||||
postProcess();
|
||||
}
|
||||
|
||||
void QmlProfilerEventList::showErrorDialog(const QString &st ) const
|
||||
{
|
||||
Core::ICore * const core = Core::ICore::instance();
|
||||
QMessageBox *errorDialog = new QMessageBox(core->mainWindow());
|
||||
errorDialog->setIcon(QMessageBox::Warning);
|
||||
errorDialog->setWindowTitle(tr("QML Profiler"));
|
||||
errorDialog->setText( st );
|
||||
errorDialog->setStandardButtons(QMessageBox::Ok);
|
||||
errorDialog->setDefaultButton(QMessageBox::Ok);
|
||||
errorDialog->setModal(false);
|
||||
errorDialog->show();
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////
|
||||
qint64 QmlProfilerEventList::getStartTime(int index) const {
|
||||
return d->m_startTimeSortedList[index].startTime;
|
||||
}
|
||||
|
||||
qint64 QmlProfilerEventList::getEndTime(int index) const {
|
||||
return d->m_startTimeSortedList[index].startTime + d->m_startTimeSortedList[index].length;
|
||||
}
|
||||
|
||||
qint64 QmlProfilerEventList::getDuration(int index) const {
|
||||
return d->m_startTimeSortedList[index].length;
|
||||
}
|
||||
|
||||
int QmlProfilerEventList::getType(int index) const {
|
||||
return d->m_startTimeSortedList[index].description->eventType;
|
||||
}
|
||||
|
||||
int QmlProfilerEventList::getNestingLevel(int index) const {
|
||||
return d->m_startTimeSortedList[index].nestingLevel;
|
||||
}
|
||||
|
||||
int QmlProfilerEventList::getNestingDepth(int index) const {
|
||||
return d->m_startTimeSortedList[index].nestingDepth;
|
||||
}
|
||||
|
||||
QString QmlProfilerEventList::getFilename(int index) const {
|
||||
return d->m_startTimeSortedList[index].description->filename;
|
||||
}
|
||||
|
||||
int QmlProfilerEventList::getLine(int index) const {
|
||||
return d->m_startTimeSortedList[index].description->line;
|
||||
}
|
||||
|
||||
QString QmlProfilerEventList::getDetails(int index) const {
|
||||
return d->m_startTimeSortedList[index].description->details;
|
||||
}
|
||||
|
||||
|
||||
} // namespace Internal
|
||||
} // namespace QmlProfiler
|
138
src/plugins/qmlprofiler/qmlprofilereventlist.h
Normal file
138
src/plugins/qmlprofiler/qmlprofilereventlist.h
Normal file
@@ -0,0 +1,138 @@
|
||||
/**************************************************************************
|
||||
**
|
||||
** 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 QMLPROFILEREVENTLIST_H
|
||||
#define QMLPROFILEREVENTLIST_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QtCore/QHash>
|
||||
#include "qmljsdebugclient/qmlprofilereventtypes.h"
|
||||
|
||||
namespace QmlProfiler {
|
||||
namespace Internal {
|
||||
|
||||
struct QmlEventData
|
||||
{
|
||||
QString displayname;
|
||||
QString filename;
|
||||
QString location;
|
||||
QString details;
|
||||
int line;
|
||||
QmlJsDebugClient::QmlEventType eventType;
|
||||
QList< QmlEventData *> parentList;
|
||||
QList< QmlEventData *> childrenList;
|
||||
qint64 cumulatedDuration;
|
||||
qint64 calls;
|
||||
qint64 minTime;
|
||||
qint64 maxTime;
|
||||
double timePerCall;
|
||||
double percentOfTime;
|
||||
};
|
||||
|
||||
typedef QHash<QString, QmlEventData *> QmlEventHash;
|
||||
typedef QList<QmlEventData *> QmlEventDescriptions;
|
||||
|
||||
enum ParsingStatus {
|
||||
GettingDataStatus = 0,
|
||||
SortingListsStatus = 1,
|
||||
SortingEndsStatus = 2,
|
||||
ComputingLevelsStatus = 3,
|
||||
CompilingStatisticsStatus = 4,
|
||||
DoneStatus = 5
|
||||
};
|
||||
|
||||
class QmlProfilerEventList : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
|
||||
explicit QmlProfilerEventList(QObject *parent = 0);
|
||||
~QmlProfilerEventList();
|
||||
|
||||
QmlEventDescriptions getEventDescriptions() const;
|
||||
|
||||
int findFirstIndex(qint64 startTime) const;
|
||||
int findLastIndex(qint64 endTime) const;
|
||||
Q_INVOKABLE qint64 firstTimeMark() const;
|
||||
Q_INVOKABLE qint64 lastTimeMark() const;
|
||||
|
||||
Q_INVOKABLE int count() const;
|
||||
void setParsingStatus(ParsingStatus ps);
|
||||
Q_INVOKABLE ParsingStatus getParsingStatus() 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 QString getDetails(int index) const;
|
||||
|
||||
void showErrorDialog(const QString &st ) const;
|
||||
signals:
|
||||
void dataReady();
|
||||
void countChanged();
|
||||
void parsingStatusChanged();
|
||||
|
||||
public slots:
|
||||
void clear();
|
||||
void addRangedEvent(int type, qint64 startTime, qint64 length,
|
||||
const QStringList &data, const QString &fileName, int line);
|
||||
void complete();
|
||||
void save(const QString &filename) const;
|
||||
void load(const QString &filename);
|
||||
void setFilename(const QString &filename);
|
||||
void load();
|
||||
|
||||
private slots:
|
||||
void postProcess();
|
||||
void sortEndTimes();
|
||||
void sortStartTimes();
|
||||
void computeLevels();
|
||||
void computeNestingLevels();
|
||||
void computeNestingDepth();
|
||||
void compileStatistics();
|
||||
void linkEndsToStarts();
|
||||
|
||||
private:
|
||||
class QmlProfilerEventListPrivate;
|
||||
QmlProfilerEventListPrivate *d;
|
||||
};
|
||||
|
||||
|
||||
} // namespace Internal
|
||||
} // namespace QmlProfiler
|
||||
|
||||
#endif // QMLPROFILEREVENTLIST_H
|
@@ -35,14 +35,20 @@
|
||||
#include <QtCore/QUrl>
|
||||
#include <QtCore/QHash>
|
||||
|
||||
#include <QtGui/QStandardItem>
|
||||
#include <QtGui/QHeaderView>
|
||||
#include <QtGui/QStandardItemModel>
|
||||
|
||||
#include <QtGui/QContextMenuEvent>
|
||||
#include <QDebug>
|
||||
|
||||
|
||||
using namespace QmlJsDebugClient;
|
||||
|
||||
namespace QmlProfiler {
|
||||
namespace Internal {
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
class EventsViewItem : public QStandardItem
|
||||
{
|
||||
public:
|
||||
@@ -66,22 +72,6 @@ public:
|
||||
};
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
class QmlProfilerEventStatistics::QmlProfilerEventStatisticsPrivate
|
||||
{
|
||||
public:
|
||||
QmlProfilerEventStatisticsPrivate(QmlProfilerEventStatistics *qq) : q(qq) {}
|
||||
|
||||
void postProcess();
|
||||
|
||||
QmlProfilerEventStatistics *q;
|
||||
QmlEventHash m_rootHash;
|
||||
QHash<int, QmlEventList> m_pendingEvents;
|
||||
int m_lastLevel;
|
||||
};
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
class QmlProfilerEventsView::QmlProfilerEventsViewPrivate
|
||||
@@ -89,7 +79,7 @@ class QmlProfilerEventsView::QmlProfilerEventsViewPrivate
|
||||
public:
|
||||
QmlProfilerEventsViewPrivate(QmlProfilerEventsView *qq) : q(qq) {}
|
||||
|
||||
void buildModelFromList(const QmlEventList &list, QStandardItem *parentItem, const QmlEventList &visitedFunctionsList = QmlEventList() );
|
||||
void buildModelFromList(const QmlEventDescriptions &list, QStandardItem *parentItem, const QmlEventDescriptions &visitedFunctionsList = QmlEventDescriptions() );
|
||||
int getFieldCount();
|
||||
QString displayTime(double time) const;
|
||||
QString nameForType(int typeNumber) const;
|
||||
@@ -97,7 +87,7 @@ public:
|
||||
|
||||
QmlProfilerEventsView *q;
|
||||
|
||||
QmlProfilerEventStatistics *m_eventStatistics;
|
||||
QmlProfilerEventList *m_eventStatistics;
|
||||
QStandardItemModel *m_model;
|
||||
QList<bool> m_fieldShown;
|
||||
bool m_showAnonymous;
|
||||
@@ -107,151 +97,7 @@ public:
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
QmlProfilerEventStatistics::QmlProfilerEventStatistics(QObject *parent) :
|
||||
QObject(parent), d(new QmlProfilerEventStatisticsPrivate(this))
|
||||
{
|
||||
setObjectName("QmlProfilerEventStatistics");
|
||||
d->m_lastLevel = -1;
|
||||
}
|
||||
|
||||
QmlProfilerEventStatistics::~QmlProfilerEventStatistics()
|
||||
{
|
||||
clear();
|
||||
}
|
||||
|
||||
void QmlProfilerEventStatistics::clear()
|
||||
{
|
||||
foreach (int levelNumber, d->m_pendingEvents.keys())
|
||||
d->m_pendingEvents[levelNumber].clear();
|
||||
|
||||
d->m_lastLevel = -1;
|
||||
|
||||
foreach (QmlEventData *binding, d->m_rootHash.values())
|
||||
delete binding;
|
||||
d->m_rootHash.clear();
|
||||
}
|
||||
|
||||
QList <QmlEventData *> QmlProfilerEventStatistics::getEventList() const
|
||||
{
|
||||
return d->m_rootHash.values();
|
||||
}
|
||||
|
||||
int QmlProfilerEventStatistics::eventCount() const
|
||||
{
|
||||
return d->m_rootHash.size();
|
||||
}
|
||||
|
||||
void QmlProfilerEventStatistics::addRangedEvent(int type, int nestingLevel, int nestingInType, qint64 startTime, qint64 length,
|
||||
const QStringList &data, const QString &fileName, int line)
|
||||
{
|
||||
Q_UNUSED(startTime);
|
||||
Q_UNUSED(nestingInType);
|
||||
|
||||
const QChar colon = QLatin1Char(':');
|
||||
QString displayName, location, details;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
if (fileName.isEmpty()) {
|
||||
displayName = tr("<bytecode>");
|
||||
location = QString("--:%1:%2").arg(QString::number(type), details);
|
||||
} else {
|
||||
const QString filePath = QUrl(fileName).path();
|
||||
displayName = filePath.mid(filePath.lastIndexOf(QChar('/')) + 1) + colon + QString::number(line);
|
||||
location = fileName+colon+QString::number(line);
|
||||
}
|
||||
|
||||
|
||||
// New Data: if it's not in the hash, put it there
|
||||
// if it's in the hash, get the reference from the hash
|
||||
QmlEventData *newBinding;
|
||||
QmlEventHash::iterator it = d->m_rootHash.find(location);
|
||||
if (it != d->m_rootHash.end()) {
|
||||
newBinding = it.value();
|
||||
newBinding->duration += length;
|
||||
newBinding->calls++;
|
||||
if (newBinding->maxTime < length)
|
||||
newBinding->maxTime = length;
|
||||
if (newBinding->minTime > length)
|
||||
newBinding->minTime = length;
|
||||
} else {
|
||||
newBinding = new QmlEventData;
|
||||
newBinding->calls = 1;
|
||||
newBinding->duration = length;
|
||||
newBinding->displayname = new QString(displayName);
|
||||
newBinding->filename = new QString(fileName);
|
||||
newBinding->location = new QString(location);
|
||||
newBinding->line = line;
|
||||
newBinding->minTime = length;
|
||||
newBinding->maxTime = length;
|
||||
newBinding->level = nestingLevel;
|
||||
newBinding->eventType = (QmlEventType)type;
|
||||
newBinding->details = new QString(details);
|
||||
newBinding->parentList = new QmlEventList();
|
||||
newBinding->childrenList = new QmlEventList();
|
||||
d->m_rootHash.insert(location, newBinding);
|
||||
}
|
||||
|
||||
if (nestingLevel < d->m_lastLevel) {
|
||||
// I'm the parent of the former
|
||||
if (d->m_pendingEvents.contains(nestingLevel+1)) {
|
||||
foreach (QmlEventData *child, d->m_pendingEvents[nestingLevel + 1]) {
|
||||
if (!newBinding->childrenList->contains(child))
|
||||
newBinding->childrenList->append(child);
|
||||
if (!child->parentList->contains(newBinding))
|
||||
child->parentList->append(newBinding);
|
||||
}
|
||||
d->m_pendingEvents[nestingLevel + 1].clear();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (nestingLevel > 1 && !d->m_pendingEvents[nestingLevel].contains(newBinding)) {
|
||||
// I'm not root... there will come a parent later
|
||||
d->m_pendingEvents[nestingLevel].append(newBinding);
|
||||
}
|
||||
|
||||
d->m_lastLevel = nestingLevel;
|
||||
}
|
||||
|
||||
void QmlProfilerEventStatistics::complete()
|
||||
{
|
||||
d->postProcess();
|
||||
emit dataReady();
|
||||
}
|
||||
|
||||
void QmlProfilerEventStatistics::QmlProfilerEventStatisticsPrivate::postProcess()
|
||||
{
|
||||
double totalTime = 0;
|
||||
|
||||
foreach (QmlEventData *binding, m_rootHash.values()) {
|
||||
if (binding->filename->isEmpty())
|
||||
continue;
|
||||
totalTime += binding->duration;
|
||||
}
|
||||
|
||||
foreach (QmlEventData *binding, m_rootHash.values()) {
|
||||
if (binding->filename->isEmpty())
|
||||
continue;
|
||||
binding->percentOfTime = binding->duration * 100.0 / totalTime;
|
||||
binding->timePerCall = binding->calls > 0 ? double(binding->duration) / binding->calls : 0;
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
QmlProfilerEventsView::QmlProfilerEventsView(QWidget *parent, QmlProfilerEventStatistics *model) :
|
||||
QmlProfilerEventsView::QmlProfilerEventsView(QWidget *parent, QmlProfilerEventList *model) :
|
||||
QTreeView(parent), d(new QmlProfilerEventsViewPrivate(this))
|
||||
{
|
||||
setObjectName("QmlProfilerEventsView");
|
||||
@@ -281,7 +127,7 @@ QmlProfilerEventsView::~QmlProfilerEventsView()
|
||||
delete d->m_model;
|
||||
}
|
||||
|
||||
void QmlProfilerEventsView::setEventStatisticsModel( QmlProfilerEventStatistics *model )
|
||||
void QmlProfilerEventsView::setEventStatisticsModel( QmlProfilerEventList *model )
|
||||
{
|
||||
if (d->m_eventStatistics)
|
||||
disconnect(d->m_eventStatistics,SIGNAL(dataReady()),this,SLOT(buildModel()));
|
||||
@@ -414,7 +260,7 @@ void QmlProfilerEventsView::buildModel()
|
||||
{
|
||||
if (d->m_eventStatistics) {
|
||||
clear();
|
||||
d->buildModelFromList( d->m_eventStatistics->getEventList(), d->m_model->invisibleRootItem() );
|
||||
d->buildModelFromList( d->m_eventStatistics->getEventDescriptions(), d->m_model->invisibleRootItem() );
|
||||
|
||||
bool hasBranches = d->m_fieldShown[Parents] || d->m_fieldShown[Children];
|
||||
setRootIsDecorated(hasBranches);
|
||||
@@ -434,18 +280,18 @@ void QmlProfilerEventsView::buildModel()
|
||||
}
|
||||
}
|
||||
|
||||
void QmlProfilerEventsView::QmlProfilerEventsViewPrivate::buildModelFromList( const QmlEventList &list, QStandardItem *parentItem, const QmlEventList &visitedFunctionsList )
|
||||
void QmlProfilerEventsView::QmlProfilerEventsViewPrivate::buildModelFromList( const QmlEventDescriptions &list, QStandardItem *parentItem, const QmlEventDescriptions &visitedFunctionsList )
|
||||
{
|
||||
foreach (QmlEventData *binding, list) {
|
||||
if (visitedFunctionsList.contains(binding))
|
||||
continue;
|
||||
|
||||
if ((!m_showAnonymous) && binding->filename->isEmpty())
|
||||
if ((!m_showAnonymous) && binding->filename.isEmpty())
|
||||
continue;
|
||||
|
||||
QList<QStandardItem *> newRow;
|
||||
if (m_fieldShown[Name]) {
|
||||
newRow << new EventsViewItem(*binding->displayname);
|
||||
newRow << new EventsViewItem(binding->displayname);
|
||||
}
|
||||
|
||||
if (m_fieldShown[Type]) {
|
||||
@@ -459,8 +305,8 @@ void QmlProfilerEventsView::QmlProfilerEventsViewPrivate::buildModelFromList( co
|
||||
}
|
||||
|
||||
if (m_fieldShown[TotalDuration]) {
|
||||
newRow << new EventsViewItem(displayTime(binding->duration));
|
||||
newRow.last()->setData(QVariant(binding->duration));
|
||||
newRow << new EventsViewItem(displayTime(binding->cumulatedDuration));
|
||||
newRow.last()->setData(QVariant(binding->cumulatedDuration));
|
||||
}
|
||||
|
||||
if (m_fieldShown[CallCount]) {
|
||||
@@ -484,8 +330,8 @@ void QmlProfilerEventsView::QmlProfilerEventsViewPrivate::buildModelFromList( co
|
||||
}
|
||||
|
||||
if (m_fieldShown[Details]) {
|
||||
newRow << new EventsViewItem(*binding->details);
|
||||
newRow.last()->setData(QVariant(*binding->details));
|
||||
newRow << new EventsViewItem(binding->details);
|
||||
newRow.last()->setData(QVariant(binding->details));
|
||||
}
|
||||
|
||||
if (!newRow.isEmpty()) {
|
||||
@@ -494,25 +340,25 @@ void QmlProfilerEventsView::QmlProfilerEventsViewPrivate::buildModelFromList( co
|
||||
item->setEditable(false);
|
||||
|
||||
// metadata
|
||||
newRow.at(0)->setData(QVariant(*binding->location),LocationRole);
|
||||
newRow.at(0)->setData(QVariant(*binding->filename),FilenameRole);
|
||||
newRow.at(0)->setData(QVariant(binding->location),LocationRole);
|
||||
newRow.at(0)->setData(QVariant(binding->filename),FilenameRole);
|
||||
newRow.at(0)->setData(QVariant(binding->line),LineRole);
|
||||
|
||||
// append
|
||||
parentItem->appendRow(newRow);
|
||||
|
||||
if (m_fieldShown[Parents] && !binding->parentList->isEmpty()) {
|
||||
QmlEventList newParentList(visitedFunctionsList);
|
||||
if (m_fieldShown[Parents] && !binding->parentList.isEmpty()) {
|
||||
QmlEventDescriptions newParentList(visitedFunctionsList);
|
||||
newParentList.append(binding);
|
||||
|
||||
buildModelFromList(*binding->parentList, newRow.at(0), newParentList);
|
||||
buildModelFromList(binding->parentList, newRow.at(0), newParentList);
|
||||
}
|
||||
|
||||
if (m_fieldShown[Children] && !binding->childrenList->isEmpty()) {
|
||||
QmlEventList newChildrenList(visitedFunctionsList);
|
||||
if (m_fieldShown[Children] && !binding->childrenList.isEmpty()) {
|
||||
QmlEventDescriptions newChildrenList(visitedFunctionsList);
|
||||
newChildrenList.append(binding);
|
||||
|
||||
buildModelFromList(*binding->childrenList, newRow.at(0), newChildrenList);
|
||||
buildModelFromList(binding->childrenList, newRow.at(0), newChildrenList);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -556,5 +402,10 @@ void QmlProfilerEventsView::jumpToItem(const QModelIndex &index)
|
||||
emit gotoSourceLocation(fileName, line);
|
||||
}
|
||||
|
||||
void QmlProfilerEventsView::contextMenuEvent(QContextMenuEvent *ev)
|
||||
{
|
||||
emit contextMenuRequested(ev->globalPos());
|
||||
}
|
||||
|
||||
} // namespace Internal
|
||||
} // namespace QmlProfiler
|
||||
|
@@ -35,39 +35,11 @@
|
||||
|
||||
#include <QTreeView>
|
||||
#include <qmljsdebugclient/qmlprofilereventtypes.h>
|
||||
#include "qmlprofilereventlist.h"
|
||||
|
||||
namespace QmlProfiler {
|
||||
namespace Internal {
|
||||
|
||||
struct QmlEventData
|
||||
{
|
||||
QmlEventData() : displayname(0) , filename(0) , location(0) , details(0),
|
||||
line(0), eventType(QmlJsDebugClient::MaximumQmlEventType), level(-1), parentList(0), childrenList(0) {}
|
||||
~QmlEventData() {
|
||||
delete displayname;
|
||||
delete filename;
|
||||
delete location;
|
||||
delete parentList;
|
||||
delete childrenList;
|
||||
}
|
||||
QString *displayname;
|
||||
QString *filename;
|
||||
QString *location;
|
||||
QString *details;
|
||||
int line;
|
||||
QmlJsDebugClient::QmlEventType eventType;
|
||||
qint64 level;
|
||||
QList< QmlEventData *> *parentList;
|
||||
QList< QmlEventData *> *childrenList;
|
||||
qint64 duration;
|
||||
qint64 calls;
|
||||
qint64 minTime;
|
||||
qint64 maxTime;
|
||||
double timePerCall;
|
||||
double percentOfTime;
|
||||
};
|
||||
|
||||
|
||||
typedef QHash<QString, QmlEventData *> QmlEventHash;
|
||||
typedef QList<QmlEventData *> QmlEventList;
|
||||
|
||||
@@ -77,31 +49,6 @@ enum ItemRole {
|
||||
LineRole = Qt::UserRole+3
|
||||
};
|
||||
|
||||
class QmlProfilerEventStatistics : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
|
||||
explicit QmlProfilerEventStatistics(QObject *parent = 0);
|
||||
~QmlProfilerEventStatistics();
|
||||
|
||||
QmlEventList getEventList() const;
|
||||
int eventCount() const;
|
||||
|
||||
signals:
|
||||
void dataReady();
|
||||
|
||||
public slots:
|
||||
void clear();
|
||||
void addRangedEvent(int type, int nestingLevel, int nestingInType, qint64 startTime, qint64 length,
|
||||
const QStringList &data, const QString &fileName, int line);
|
||||
void complete();
|
||||
|
||||
private:
|
||||
class QmlProfilerEventStatisticsPrivate;
|
||||
QmlProfilerEventStatisticsPrivate *d;
|
||||
};
|
||||
|
||||
class QmlProfilerEventsView : public QTreeView
|
||||
{
|
||||
Q_OBJECT
|
||||
@@ -130,16 +77,17 @@ public:
|
||||
MaxViewTypes
|
||||
};
|
||||
|
||||
explicit QmlProfilerEventsView(QWidget *parent, QmlProfilerEventStatistics *model);
|
||||
explicit QmlProfilerEventsView(QWidget *parent, QmlProfilerEventList *model);
|
||||
~QmlProfilerEventsView();
|
||||
|
||||
void setEventStatisticsModel( QmlProfilerEventStatistics *model );
|
||||
void setEventStatisticsModel( QmlProfilerEventList *model );
|
||||
void setFieldViewable(Fields field, bool show);
|
||||
void setViewType(ViewTypes type);
|
||||
void setShowAnonymousEvents( bool showThem );
|
||||
|
||||
signals:
|
||||
void gotoSourceLocation(const QString &fileName, int lineNumber);
|
||||
void contextMenuRequested(const QPoint &position);
|
||||
|
||||
public slots:
|
||||
void clear();
|
||||
@@ -148,6 +96,7 @@ public slots:
|
||||
|
||||
private:
|
||||
void setHeaderLabels();
|
||||
void contextMenuEvent(QContextMenuEvent *ev);
|
||||
|
||||
private:
|
||||
class QmlProfilerEventsViewPrivate;
|
||||
|
@@ -35,6 +35,7 @@
|
||||
#include "qmlprofilerplugin.h"
|
||||
#include "qmlprofilerconstants.h"
|
||||
#include "qmlprofilerattachdialog.h"
|
||||
#include "qmlprofilereventlist.h"
|
||||
#include "qmlprofilereventview.h"
|
||||
|
||||
#include "tracewindow.h"
|
||||
@@ -76,6 +77,8 @@
|
||||
#include <QtGui/QToolButton>
|
||||
#include <QtGui/QMessageBox>
|
||||
#include <QtGui/QDockWidget>
|
||||
#include <QtGui/QFileDialog>
|
||||
#include <QtGui/QMenu>
|
||||
|
||||
using namespace Analyzer;
|
||||
using namespace QmlProfiler::Internal;
|
||||
@@ -93,7 +96,6 @@ public:
|
||||
QTimer m_connectionTimer;
|
||||
int m_connectionAttempts;
|
||||
TraceWindow *m_traceWindow;
|
||||
QmlProfilerEventStatistics *m_statistics;
|
||||
QmlProfilerEventsView *m_eventsView;
|
||||
QmlProfilerEventsView *m_calleeView;
|
||||
QmlProfilerEventsView *m_callerView;
|
||||
@@ -165,6 +167,19 @@ IAnalyzerTool::ToolMode QmlProfilerTool::toolMode() const
|
||||
return AnyMode;
|
||||
}
|
||||
|
||||
void QmlProfilerTool::showContextMenu(const QPoint &position)
|
||||
{
|
||||
QMenu menu;
|
||||
QAction *loadAction = menu.addAction(tr("Load QML Trace"));
|
||||
QAction *saveAction = menu.addAction(tr("Save QML Trace"));
|
||||
|
||||
QAction *selectedAction = menu.exec(position);
|
||||
if (selectedAction == loadAction)
|
||||
showLoadDialog();
|
||||
if (selectedAction == saveAction)
|
||||
showSaveDialog();
|
||||
}
|
||||
|
||||
IAnalyzerEngine *QmlProfilerTool::createEngine(const AnalyzerStartParameters &sp,
|
||||
ProjectExplorer::RunConfiguration *runConfiguration)
|
||||
{
|
||||
@@ -239,27 +254,26 @@ QWidget *QmlProfilerTool::createWidgets()
|
||||
|
||||
connect(d->m_traceWindow, SIGNAL(gotoSourceLocation(QString,int)),this, SLOT(gotoSourceLocation(QString,int)));
|
||||
connect(d->m_traceWindow, SIGNAL(timeChanged(qreal)), this, SLOT(updateTimer(qreal)));
|
||||
connect(d->m_traceWindow, SIGNAL(contextMenuRequested(QPoint)), this, SLOT(showContextMenu(QPoint)));
|
||||
|
||||
d->m_statistics = new QmlProfilerEventStatistics(mw);
|
||||
d->m_eventsView = new QmlProfilerEventsView(mw, d->m_statistics);
|
||||
d->m_eventsView = new QmlProfilerEventsView(mw, d->m_traceWindow->getEventList());
|
||||
d->m_eventsView->setViewType(QmlProfilerEventsView::EventsView);
|
||||
|
||||
connect(d->m_traceWindow, SIGNAL(range(int,int,int,qint64,qint64,QStringList,QString,int)),
|
||||
d->m_statistics, SLOT(addRangedEvent(int,int,int,qint64,qint64,QStringList,QString,int)));
|
||||
connect(d->m_traceWindow, SIGNAL(viewUpdated()),
|
||||
d->m_statistics, SLOT(complete()));
|
||||
connect(d->m_eventsView, SIGNAL(gotoSourceLocation(QString,int)),
|
||||
this, SLOT(gotoSourceLocation(QString,int)));
|
||||
connect(d->m_eventsView, SIGNAL(contextMenuRequested(QPoint)), this, SLOT(showContextMenu(QPoint)));
|
||||
|
||||
d->m_calleeView = new QmlProfilerEventsView(mw, d->m_statistics);
|
||||
d->m_calleeView = new QmlProfilerEventsView(mw, d->m_traceWindow->getEventList());
|
||||
d->m_calleeView->setViewType(QmlProfilerEventsView::CalleesView);
|
||||
connect(d->m_calleeView, SIGNAL(gotoSourceLocation(QString,int)),
|
||||
this, SLOT(gotoSourceLocation(QString,int)));
|
||||
connect(d->m_calleeView, SIGNAL(contextMenuRequested(QPoint)), this, SLOT(showContextMenu(QPoint)));
|
||||
|
||||
d->m_callerView = new QmlProfilerEventsView(mw, d->m_statistics);
|
||||
d->m_callerView = new QmlProfilerEventsView(mw, d->m_traceWindow->getEventList());
|
||||
d->m_callerView->setViewType(QmlProfilerEventsView::CallersView);
|
||||
connect(d->m_callerView, SIGNAL(gotoSourceLocation(QString,int)),
|
||||
this, SLOT(gotoSourceLocation(QString,int)));
|
||||
connect(d->m_callerView, SIGNAL(contextMenuRequested(QPoint)), this, SLOT(showContextMenu(QPoint)));
|
||||
|
||||
QDockWidget *eventsDock = AnalyzerManager::createDockWidget
|
||||
(this, tr("Events"), d->m_eventsView, Qt::BottomDockWidgetArea);
|
||||
@@ -403,7 +417,7 @@ void QmlProfilerTool::gotoSourceLocation(const QString &fileUrl, int lineNumber)
|
||||
}
|
||||
|
||||
void QmlProfilerTool::correctTimer() {
|
||||
if (d->m_statistics->eventCount() == 0)
|
||||
if (d->m_traceWindow->getEventList()->count() == 0)
|
||||
updateTimer(0);
|
||||
}
|
||||
|
||||
@@ -423,7 +437,6 @@ void QmlProfilerTool::updateProjectFileList()
|
||||
void QmlProfilerTool::clearDisplay()
|
||||
{
|
||||
d->m_traceWindow->clearDisplay();
|
||||
d->m_statistics->clear();
|
||||
d->m_eventsView->clear();
|
||||
d->m_calleeView->clear();
|
||||
d->m_callerView->clear();
|
||||
@@ -546,3 +559,26 @@ void QmlProfilerTool::logError(const QString &msg)
|
||||
Core::MessageManager *messageManager = Core::MessageManager::instance();
|
||||
messageManager->printToOutputPane(msg, true);
|
||||
}
|
||||
|
||||
void QmlProfilerTool::showSaveDialog()
|
||||
{
|
||||
Core::ICore *core = Core::ICore::instance();
|
||||
QString filename = QFileDialog::getSaveFileName(core->mainWindow(), tr("Save QML Trace"), QString(), tr("QML traces (*.xml)"));
|
||||
if (!filename.isEmpty()) {
|
||||
if (!filename.endsWith(QLatin1String(".xml")))
|
||||
filename += QLatin1String(".xml");
|
||||
d->m_traceWindow->getEventList()->save(filename);
|
||||
}
|
||||
}
|
||||
|
||||
void QmlProfilerTool::showLoadDialog()
|
||||
{
|
||||
Core::ICore *core = Core::ICore::instance();
|
||||
QString filename = QFileDialog::getOpenFileName(core->mainWindow(), tr("Load QML Trace"), QString(), tr("QML traces (*.xml)"));
|
||||
|
||||
if (!filename.isEmpty()) {
|
||||
// delayed load (prevent graphical artifacts due to long load time)
|
||||
d->m_traceWindow->getEventList()->setFilename(filename);
|
||||
QTimer::singleShot(100, d->m_traceWindow->getEventList(), SLOT(load()));
|
||||
}
|
||||
}
|
||||
|
@@ -36,6 +36,8 @@
|
||||
#include <analyzerbase/ianalyzertool.h>
|
||||
#include <analyzerbase/ianalyzerengine.h>
|
||||
|
||||
#include <QtCore/QPoint>
|
||||
|
||||
namespace QmlProfiler {
|
||||
namespace Internal {
|
||||
|
||||
@@ -74,6 +76,8 @@ public slots:
|
||||
|
||||
void clearDisplay();
|
||||
|
||||
void showContextMenu(const QPoint &position);
|
||||
|
||||
signals:
|
||||
void setTimeLabel(const QString &);
|
||||
void fetchingData(bool);
|
||||
@@ -84,6 +88,8 @@ private slots:
|
||||
void attach();
|
||||
void tryToConnect();
|
||||
void connectionStateChanged();
|
||||
void showSaveDialog();
|
||||
void showLoadDialog();
|
||||
|
||||
private:
|
||||
void connectToClient();
|
||||
|
@@ -34,12 +34,17 @@
|
||||
|
||||
#include <qdeclarativecontext.h>
|
||||
#include <qdeclarativeproperty.h>
|
||||
#include <QtCore/QTimer>
|
||||
|
||||
using namespace QmlProfiler::Internal;
|
||||
|
||||
#define CACHE_ENABLED true
|
||||
#define CACHE_UPDATEDELAY 10
|
||||
#define CACHE_STEP 200
|
||||
|
||||
TimelineView::TimelineView(QDeclarativeItem *parent) :
|
||||
QDeclarativeItem(parent), m_delegate(0), m_startTime(0), m_endTime(0), m_startX(0),
|
||||
prevMin(0), prevMax(0), m_totalWidth(0)
|
||||
QDeclarativeItem(parent), m_delegate(0), m_itemCount(0), m_startTime(0), m_endTime(0), m_startX(0), m_spacing(0),
|
||||
prevMin(0), prevMax(0), m_eventList(0), m_totalWidth(0), m_lastCachedIndex(0), m_creatingCache(false), m_oldCacheSize(0)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -50,46 +55,20 @@ void TimelineView::componentComplete()
|
||||
|
||||
void TimelineView::clearData()
|
||||
{
|
||||
m_rangeList.clear();
|
||||
m_items.clear();
|
||||
if (CACHE_ENABLED)
|
||||
foreach (QDeclarativeItem *item, m_items.values())
|
||||
item->setVisible(false);
|
||||
else
|
||||
foreach (QDeclarativeItem *item, m_items.values())
|
||||
delete m_items.take(m_items.key(item));
|
||||
|
||||
m_startTime = 0;
|
||||
m_endTime = 0;
|
||||
m_startX = 0;
|
||||
prevMin = 0;
|
||||
prevMax = 0;
|
||||
m_prevLimits.clear();
|
||||
m_totalWidth = 0;
|
||||
|
||||
}
|
||||
|
||||
void TimelineView::setRanges(const QScriptValue &value)
|
||||
{
|
||||
//TODO clear old values (always?)
|
||||
m_ranges = value;
|
||||
|
||||
//### below code not yet used anywhere
|
||||
int length = m_ranges.property("length").toInt32();
|
||||
|
||||
for (int i = 0; i < length; ++i) {
|
||||
int type = m_ranges.property(i).property("type").toNumber();
|
||||
Q_ASSERT(type >= 0);
|
||||
while (m_rangeList.count() <= type)
|
||||
m_rangeList.append(ValueList());
|
||||
m_rangeList[type] << m_ranges.property(i);
|
||||
}
|
||||
|
||||
for (int i = 0; i < m_rangeList.count(); ++i)
|
||||
m_prevLimits << PrevLimits(0, 0);
|
||||
|
||||
qreal startValue = m_ranges.property(0).property("start").toNumber();
|
||||
m_starts.clear();
|
||||
m_starts.reserve(length);
|
||||
m_ends.clear();
|
||||
m_ends.reserve(length);
|
||||
for (int i = 0; i < length; ++i) {
|
||||
m_starts.append(m_ranges.property(i).property("start").toNumber() - startValue);
|
||||
m_ends.append(m_ranges.property(i).property("start").toNumber() + m_ranges.property(i).property("duration").toNumber() - startValue);
|
||||
}
|
||||
m_lastCachedIndex = 0;
|
||||
}
|
||||
|
||||
void TimelineView::setStartX(qreal arg)
|
||||
@@ -97,9 +76,6 @@ void TimelineView::setStartX(qreal arg)
|
||||
if (arg == m_startX)
|
||||
return;
|
||||
|
||||
if (!m_ranges.isArray())
|
||||
return;
|
||||
|
||||
qreal window = m_endTime - m_startTime;
|
||||
if (window == 0) //###
|
||||
return;
|
||||
@@ -118,51 +94,29 @@ void TimelineView::updateTimeline(bool updateStartX)
|
||||
if (!m_delegate)
|
||||
return;
|
||||
|
||||
if (!m_ranges.isArray())
|
||||
if (!m_eventList)
|
||||
return;
|
||||
|
||||
int length = m_ranges.property("length").toInt32();
|
||||
|
||||
qreal startValue = m_ranges.property(0).property("start").toNumber();
|
||||
qreal endValue = m_ranges.property(length-1).property("start").toNumber() + m_ranges.property(length-1).property("duration").toNumber();
|
||||
|
||||
qreal totalRange = endValue - startValue;
|
||||
qreal totalRange = m_eventList->lastTimeMark() - m_eventList->firstTimeMark();
|
||||
qreal window = m_endTime - m_startTime;
|
||||
|
||||
if (window == 0) //###
|
||||
return;
|
||||
|
||||
qreal spacing = width() / window;
|
||||
qreal newSpacing = width() / window;
|
||||
bool spacingChanged = (newSpacing != m_spacing);
|
||||
m_spacing = newSpacing;
|
||||
|
||||
qreal oldtw = m_totalWidth;
|
||||
m_totalWidth = totalRange * spacing;
|
||||
m_totalWidth = totalRange * m_spacing;
|
||||
|
||||
// Find region samples
|
||||
int minsample = 0;
|
||||
int maxsample = 0;
|
||||
|
||||
for (int i = 0; i < length; ++i) {
|
||||
if (m_ends.at(i) >= m_startTime)
|
||||
break;
|
||||
minsample = i;
|
||||
}
|
||||
|
||||
for (int i = minsample + 1; i < length; ++i) {
|
||||
maxsample = i;
|
||||
if (m_starts.at(i) > m_endTime)
|
||||
break;
|
||||
}
|
||||
|
||||
//### overkill (if we can expose whether or not data is nested)
|
||||
for (int i = maxsample + 1; i < length; ++i) {
|
||||
if (m_starts.at(i) < m_endTime)
|
||||
maxsample = i;
|
||||
}
|
||||
|
||||
//qDebug() << maxsample - minsample;
|
||||
int minsample = m_eventList->findFirstIndex(m_startTime + m_eventList->firstTimeMark());
|
||||
int maxsample = m_eventList->findLastIndex(m_endTime + m_eventList->firstTimeMark());
|
||||
|
||||
if (updateStartX) {
|
||||
qreal oldStartX = m_startX;
|
||||
m_startX = qRound(m_startTime * spacing);
|
||||
m_startX = qRound(m_startTime * m_spacing);
|
||||
if (m_startX != oldStartX) {
|
||||
emit startXChanged(m_startX);
|
||||
}
|
||||
@@ -172,69 +126,181 @@ void TimelineView::updateTimeline(bool updateStartX)
|
||||
if (m_totalWidth != oldtw)
|
||||
emit totalWidthChanged(m_totalWidth);
|
||||
|
||||
//clear items no longer in view
|
||||
while (prevMin < minsample) {
|
||||
delete m_items.take(prevMin);
|
||||
++prevMin;
|
||||
|
||||
// the next loops have to be modified with the new implementation of the cache
|
||||
|
||||
// hide items that are not visible any more
|
||||
if (maxsample < prevMin || minsample > prevMax) {
|
||||
for (int i = prevMin; i <= prevMax; ++i)
|
||||
if (m_items.contains(i)) {
|
||||
if (CACHE_ENABLED)
|
||||
m_items.value(i)->setVisible(false);
|
||||
else
|
||||
delete m_items.take(i);
|
||||
}
|
||||
while (prevMax > maxsample) {
|
||||
delete m_items.take(prevMax);
|
||||
--prevMax;
|
||||
} else {
|
||||
if (minsample > prevMin && minsample <= prevMax)
|
||||
for (int i = prevMin; i < minsample; ++i)
|
||||
if (m_items.contains(i)) {
|
||||
if (CACHE_ENABLED)
|
||||
m_items.value(i)->setVisible(false);
|
||||
else
|
||||
delete m_items.take(i);
|
||||
}
|
||||
|
||||
// Show items
|
||||
int z = 0;
|
||||
for (int i = maxsample; i >= minsample; --i) {
|
||||
QDeclarativeItem *item = 0;
|
||||
item = m_items.value(i);
|
||||
bool creating = false;
|
||||
if (!item) {
|
||||
QDeclarativeContext *ctxt = new QDeclarativeContext(qmlContext(this));
|
||||
item = qobject_cast<QDeclarativeItem*>(m_delegate->beginCreate(ctxt));
|
||||
m_items.insert(i, item);
|
||||
creating = true;
|
||||
|
||||
int type = m_ranges.property(i).property("type").toNumber();
|
||||
|
||||
ctxt->setParent(item); //### QDeclarative_setParent_noEvent(ctxt, item); instead?
|
||||
ctxt->setContextProperty("duration", qMax(qRound(m_ranges.property(i).property("duration").toNumber()/qreal(1000)),1));
|
||||
ctxt->setContextProperty("fileName", m_ranges.property(i).property("fileName").toString());
|
||||
ctxt->setContextProperty("line", m_ranges.property(i).property("line").toNumber());
|
||||
ctxt->setContextProperty("index", i);
|
||||
ctxt->setContextProperty("nestingLevel", m_ranges.property(i).property("nestingLevel").toNumber());
|
||||
ctxt->setContextProperty("nestingDepth", m_ranges.property(i).property("nestingDepth").toNumber());
|
||||
QString label;
|
||||
QVariantList list = m_ranges.property(i).property("label").toVariant().value<QVariantList>();
|
||||
for (int i = 0; i < list.size(); ++i) {
|
||||
if (i > 0)
|
||||
label += QLatin1Char('\n');
|
||||
QString sub = list.at(i).toString();
|
||||
|
||||
//### only do rewrite for bindings...
|
||||
if (type == 3) {
|
||||
//### don't construct in loop
|
||||
QRegExp rewrite("\\(function \\$(\\w+)\\(\\) \\{ return (.+) \\}\\)");
|
||||
bool match = rewrite.exactMatch(sub);
|
||||
if (match)
|
||||
sub = rewrite.cap(1) + ": " + rewrite.cap(2);
|
||||
if (maxsample >= prevMin && maxsample < prevMax)
|
||||
for (int i = maxsample + 1; i <= prevMax; ++i)
|
||||
if (m_items.contains(i)) {
|
||||
if (CACHE_ENABLED)
|
||||
m_items.value(i)->setVisible(false);
|
||||
else
|
||||
delete m_items.take(i);
|
||||
}
|
||||
}
|
||||
|
||||
label += sub;
|
||||
// Update visible items
|
||||
for (int i = minsample; i <= maxsample; ++i) {
|
||||
if (!m_items.contains(i)) {
|
||||
createItem(i);
|
||||
m_items.value(i)->setVisible(true);
|
||||
}
|
||||
ctxt->setContextProperty("label", label);
|
||||
ctxt->setContextProperty("type", type);
|
||||
item->setParentItem(this);
|
||||
else
|
||||
if (spacingChanged || !m_items.value(i)->isVisible()) {
|
||||
m_items.value(i)->setVisible(true);
|
||||
updateItemPosition(i);
|
||||
}
|
||||
if (item) {
|
||||
item->setX(m_starts.at(i)*spacing);
|
||||
qreal width = (m_ends.at(i)-m_starts.at(i)) * spacing;
|
||||
item->setWidth(width > 1 ? width : 1);
|
||||
item->setZValue(++z);
|
||||
}
|
||||
if (creating)
|
||||
m_delegate->completeCreate();
|
||||
}
|
||||
|
||||
prevMin = minsample;
|
||||
prevMax = maxsample;
|
||||
|
||||
}
|
||||
|
||||
void TimelineView::createItem(int itemIndex)
|
||||
{
|
||||
QDeclarativeContext *ctxt = new QDeclarativeContext(qmlContext(this));
|
||||
QDeclarativeItem *item = qobject_cast<QDeclarativeItem*>(m_delegate->beginCreate(ctxt));
|
||||
m_items.insert(itemIndex, item);
|
||||
|
||||
ctxt->setParent(item); //### QDeclarative_setParent_noEvent(ctxt, item); instead?
|
||||
ctxt->setContextProperty("index", itemIndex);
|
||||
ctxt->setContextProperty("type", m_eventList->getType(itemIndex));
|
||||
ctxt->setContextProperty("nestingLevel", m_eventList->getNestingLevel(itemIndex));
|
||||
ctxt->setContextProperty("nestingDepth", m_eventList->getNestingDepth(itemIndex));
|
||||
|
||||
updateItemPosition(itemIndex);
|
||||
|
||||
item->setVisible(false);
|
||||
|
||||
item->setParentItem(this);
|
||||
m_delegate->completeCreate();
|
||||
m_itemCount++;
|
||||
}
|
||||
|
||||
void TimelineView::updateItemPosition(int itemIndex)
|
||||
{
|
||||
QDeclarativeItem *item = m_items.value(itemIndex);
|
||||
if (item) {
|
||||
qreal itemStartPos = (m_eventList->getStartTime(itemIndex) - m_eventList->firstTimeMark()) * m_spacing;
|
||||
item->setX(itemStartPos);
|
||||
qreal width = (m_eventList->getEndTime(itemIndex) - m_eventList->getStartTime(itemIndex)) * m_spacing;
|
||||
item->setWidth(width > 1 ? width : 1);
|
||||
}
|
||||
}
|
||||
|
||||
void TimelineView::rebuildCache()
|
||||
{
|
||||
if (CACHE_ENABLED) {
|
||||
m_lastCachedIndex = 0;
|
||||
m_creatingCache = false;
|
||||
m_oldCacheSize = m_items.count();
|
||||
emit cachedProgressChanged();
|
||||
QTimer::singleShot(CACHE_UPDATEDELAY, this, SLOT(purgeCache()));
|
||||
} else {
|
||||
m_creatingCache = true;
|
||||
m_lastCachedIndex = m_eventList->count();
|
||||
emit cacheReady();
|
||||
}
|
||||
}
|
||||
|
||||
qreal TimelineView::cachedProgress() const
|
||||
{
|
||||
qreal progress;
|
||||
if (!m_creatingCache) {
|
||||
if (m_oldCacheSize == 0)
|
||||
progress = 0.5;
|
||||
else
|
||||
progress = (m_lastCachedIndex * 0.5) / m_oldCacheSize;
|
||||
}
|
||||
else
|
||||
progress = 0.5 + (m_lastCachedIndex * 0.5) / m_eventList->count();
|
||||
|
||||
return progress;
|
||||
}
|
||||
|
||||
void TimelineView::increaseCache()
|
||||
{
|
||||
int totalCount = m_eventList->count();
|
||||
if (m_lastCachedIndex >= totalCount) {
|
||||
emit cacheReady();
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < CACHE_STEP; i++) {
|
||||
createItem(m_lastCachedIndex);
|
||||
m_lastCachedIndex++;
|
||||
if (m_lastCachedIndex >= totalCount)
|
||||
break;
|
||||
}
|
||||
|
||||
emit cachedProgressChanged();
|
||||
|
||||
QTimer::singleShot(CACHE_UPDATEDELAY, this, SLOT(increaseCache()));
|
||||
}
|
||||
|
||||
void TimelineView::purgeCache()
|
||||
{
|
||||
if (m_items.isEmpty()) {
|
||||
m_creatingCache = true;
|
||||
m_lastCachedIndex = 0;
|
||||
QTimer::singleShot(CACHE_UPDATEDELAY, this, SLOT(increaseCache()));
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i=0; i < CACHE_STEP; i++)
|
||||
{
|
||||
if (m_items.contains(m_lastCachedIndex))
|
||||
delete m_items.take(m_lastCachedIndex);
|
||||
|
||||
m_lastCachedIndex++;
|
||||
if (m_items.isEmpty())
|
||||
break;
|
||||
}
|
||||
|
||||
emit cachedProgressChanged();
|
||||
QTimer::singleShot(CACHE_UPDATEDELAY, this, SLOT(purgeCache()));
|
||||
}
|
||||
|
||||
qint64 TimelineView::getDuration(int index) const
|
||||
{
|
||||
Q_ASSERT(m_eventList);
|
||||
return m_eventList->getEndTime(index) - m_eventList->getStartTime(index);
|
||||
}
|
||||
|
||||
QString TimelineView::getFilename(int index) const
|
||||
{
|
||||
Q_ASSERT(m_eventList);
|
||||
return m_eventList->getFilename(index);
|
||||
}
|
||||
|
||||
int TimelineView::getLine(int index) const
|
||||
{
|
||||
Q_ASSERT(m_eventList);
|
||||
return m_eventList->getLine(index);
|
||||
}
|
||||
|
||||
QString TimelineView::getDetails(int index) const
|
||||
{
|
||||
Q_ASSERT(m_eventList);
|
||||
return m_eventList->getDetails(index);
|
||||
}
|
||||
|
@@ -35,6 +35,7 @@
|
||||
|
||||
#include <QtDeclarative/QDeclarativeItem>
|
||||
#include <QtScript/QScriptValue>
|
||||
#include <qmlprofilereventlist.h>
|
||||
|
||||
namespace QmlProfiler {
|
||||
namespace Internal {
|
||||
@@ -47,6 +48,8 @@ class TimelineView : public QDeclarativeItem
|
||||
Q_PROPERTY(qint64 endTime READ endTime WRITE setEndTime NOTIFY endTimeChanged)
|
||||
Q_PROPERTY(qreal startX READ startX WRITE setStartX NOTIFY startXChanged)
|
||||
Q_PROPERTY(qreal totalWidth READ totalWidth NOTIFY totalWidthChanged)
|
||||
Q_PROPERTY(QObject* eventList READ eventList WRITE setEventList NOTIFY eventListChanged)
|
||||
Q_PROPERTY(qreal cachedProgress READ cachedProgress NOTIFY cachedProgressChanged)
|
||||
|
||||
public:
|
||||
explicit TimelineView(QDeclarativeItem *parent = 0);
|
||||
@@ -76,16 +79,34 @@ public:
|
||||
return m_totalWidth;
|
||||
}
|
||||
|
||||
qreal cachedProgress() const;
|
||||
|
||||
QmlProfilerEventList *eventList() const { return m_eventList; }
|
||||
void setEventList(QObject *eventList)
|
||||
{
|
||||
m_eventList = qobject_cast<QmlProfilerEventList *>(eventList);
|
||||
emit eventListChanged(m_eventList);
|
||||
}
|
||||
|
||||
Q_INVOKABLE qint64 getDuration(int index) const;
|
||||
Q_INVOKABLE QString getFilename(int index) const;
|
||||
Q_INVOKABLE int getLine(int index) const;
|
||||
Q_INVOKABLE QString getDetails(int index) const;
|
||||
Q_INVOKABLE void rebuildCache();
|
||||
|
||||
signals:
|
||||
void delegateChanged(QDeclarativeComponent * arg);
|
||||
void startTimeChanged(qint64 arg);
|
||||
void endTimeChanged(qint64 arg);
|
||||
void startXChanged(qreal arg);
|
||||
void totalWidthChanged(qreal arg);
|
||||
void eventListChanged(QmlProfilerEventList *list);
|
||||
|
||||
void cachedProgressChanged();
|
||||
void cacheReady();
|
||||
|
||||
public slots:
|
||||
void clearData();
|
||||
void setRanges(const QScriptValue &value);
|
||||
void updateTimeline(bool updateStartX = true);
|
||||
|
||||
void setDelegate(QDeclarativeComponent * arg)
|
||||
@@ -117,28 +138,32 @@ public slots:
|
||||
protected:
|
||||
void componentComplete();
|
||||
|
||||
private:
|
||||
void createItem(int itemIndex);
|
||||
void updateItemPosition(int itemIndex);
|
||||
|
||||
public slots:
|
||||
void increaseCache();
|
||||
void purgeCache();
|
||||
|
||||
private:
|
||||
QDeclarativeComponent * m_delegate;
|
||||
QScriptValue m_ranges;
|
||||
typedef QList<QScriptValue> ValueList;
|
||||
QList<ValueList> m_rangeList;
|
||||
QHash<int,QDeclarativeItem*> m_items;
|
||||
qint64 m_itemCount;
|
||||
qint64 m_startTime;
|
||||
qint64 m_endTime;
|
||||
qreal m_startX;
|
||||
qreal m_spacing;
|
||||
int prevMin;
|
||||
int prevMax;
|
||||
QList<qreal> m_starts;
|
||||
QList<qreal> m_ends;
|
||||
|
||||
struct PrevLimits {
|
||||
PrevLimits(int _min, int _max) : min(_min), max(_max) {}
|
||||
int min;
|
||||
int max;
|
||||
};
|
||||
QmlProfilerEventList *m_eventList;
|
||||
|
||||
QList<PrevLimits> m_prevLimits;
|
||||
qreal m_totalWidth;
|
||||
int m_lastCachedIndex;
|
||||
bool m_creatingCache;
|
||||
int m_oldCacheSize;
|
||||
|
||||
};
|
||||
|
||||
} // namespace Internal
|
||||
|
@@ -33,6 +33,7 @@
|
||||
#include "tracewindow.h"
|
||||
|
||||
#include "qmlprofilerplugin.h"
|
||||
#include "qmlprofilereventlist.h"
|
||||
|
||||
#include <qmljsdebugclient/qdeclarativedebugclient.h>
|
||||
#include <qmljsdebugclient/qmlprofilertraceclient.h>
|
||||
@@ -43,6 +44,7 @@
|
||||
#include <QtGui/QVBoxLayout>
|
||||
#include <QtGui/QToolButton>
|
||||
#include <QtGui/QGraphicsObject>
|
||||
#include <QtGui/QContextMenuEvent>
|
||||
|
||||
using namespace QmlJsDebugClient;
|
||||
|
||||
@@ -97,14 +99,17 @@ TraceWindow::TraceWindow(QWidget *parent)
|
||||
toolBarLayout->addWidget(buttonZoomIn);
|
||||
toolBarLayout->addWidget(buttonZoomOut);
|
||||
|
||||
|
||||
|
||||
m_view->setResizeMode(QDeclarativeView::SizeRootObjectToView);
|
||||
m_view->setFocus();
|
||||
groupLayout->addWidget(m_view);
|
||||
|
||||
setLayout(groupLayout);
|
||||
|
||||
m_eventList = new QmlProfilerEventList(this);
|
||||
connect(this,SIGNAL(range(int,qint64,qint64,QStringList,QString,int)), m_eventList, SLOT(addRangedEvent(int,qint64,qint64,QStringList,QString,int)));
|
||||
connect(this,SIGNAL(viewUpdated()), m_eventList, SLOT(complete()));
|
||||
m_view->rootContext()->setContextProperty("qmlEventList", m_eventList);
|
||||
|
||||
// Minimum height: 5 rows of 20 pixels + scrollbar of 50 pixels + 20 pixels margin
|
||||
setMinimumHeight(170);
|
||||
}
|
||||
@@ -121,8 +126,8 @@ void TraceWindow::reset(QDeclarativeDebugConnection *conn)
|
||||
delete m_plugin.data();
|
||||
m_plugin = new QmlProfilerTraceClient(conn);
|
||||
connect(m_plugin.data(), SIGNAL(complete()), this, SIGNAL(viewUpdated()));
|
||||
connect(m_plugin.data(), SIGNAL(range(int,int,int,qint64,qint64,QStringList,QString,int)),
|
||||
this, SIGNAL(range(int,int,int,qint64,qint64,QStringList,QString,int)));
|
||||
connect(m_plugin.data(), SIGNAL(range(int,qint64,qint64,QStringList,QString,int)),
|
||||
this, SIGNAL(range(int,qint64,qint64,QStringList,QString,int)));
|
||||
|
||||
m_view->rootContext()->setContextProperty("connection", m_plugin.data());
|
||||
m_view->setSource(QUrl("qrc:/qmlprofiler/MainView.qml"));
|
||||
@@ -131,7 +136,7 @@ void TraceWindow::reset(QDeclarativeDebugConnection *conn)
|
||||
|
||||
connect(m_view->rootObject(), SIGNAL(updateCursorPosition()), this, SLOT(updateCursorPosition()));
|
||||
connect(m_view->rootObject(), SIGNAL(updateTimer()), this, SLOT(updateTimer()));
|
||||
connect(m_view->rootObject(), SIGNAL(dataAvailableChanged()), this, SLOT(updateToolbar()));
|
||||
connect(m_eventList, SIGNAL(countChanged()), this, SLOT(updateToolbar()));
|
||||
connect(this, SIGNAL(jumpToPrev()), m_view->rootObject(), SLOT(prevEvent()));
|
||||
connect(this, SIGNAL(jumpToNext()), m_view->rootObject(), SLOT(nextEvent()));
|
||||
connect(this, SIGNAL(zoomIn()), m_view->rootObject(), SLOT(zoomIn()));
|
||||
@@ -140,6 +145,16 @@ void TraceWindow::reset(QDeclarativeDebugConnection *conn)
|
||||
connect(this, SIGNAL(internalClearDisplay()), m_view->rootObject(), SLOT(clearAll()));
|
||||
}
|
||||
|
||||
QmlProfilerEventList *TraceWindow::getEventList() const
|
||||
{
|
||||
return m_eventList;
|
||||
}
|
||||
|
||||
void TraceWindow::contextMenuEvent(QContextMenuEvent *ev)
|
||||
{
|
||||
emit contextMenuRequested(ev->globalPos());
|
||||
}
|
||||
|
||||
void TraceWindow::updateCursorPosition()
|
||||
{
|
||||
emit gotoSourceLocation(m_view->rootObject()->property("fileName").toString(),
|
||||
@@ -153,17 +168,17 @@ void TraceWindow::updateTimer()
|
||||
|
||||
void TraceWindow::clearDisplay()
|
||||
{
|
||||
m_eventList->clear();
|
||||
|
||||
if (m_plugin)
|
||||
m_plugin.data()->clearData();
|
||||
else
|
||||
|
||||
emit internalClearDisplay();
|
||||
}
|
||||
|
||||
void TraceWindow::updateToolbar()
|
||||
{
|
||||
bool dataAvailable = m_view->rootObject()->property("dataAvailable").toBool() &&
|
||||
m_view->rootObject()->property("eventCount").toInt() > 0;
|
||||
emit enableToolbar(dataAvailable);
|
||||
emit enableToolbar(m_eventList && m_eventList->count()>0);
|
||||
}
|
||||
|
||||
void TraceWindow::setRecording(bool recording)
|
||||
|
@@ -34,6 +34,7 @@
|
||||
#define TRACEWINDOW_H
|
||||
|
||||
#include <qmljsdebugclient/qmlprofilertraceclient.h>
|
||||
#include "qmlprofilereventlist.h"
|
||||
|
||||
#include <QtCore/QPointer>
|
||||
#include <QtGui/QWidget>
|
||||
@@ -55,6 +56,8 @@ public:
|
||||
|
||||
void reset(QmlJsDebugClient::QDeclarativeDebugConnection *conn);
|
||||
|
||||
QmlProfilerEventList *getEventList() const;
|
||||
|
||||
void setRecording(bool recording);
|
||||
bool isRecording() const;
|
||||
|
||||
@@ -69,7 +72,7 @@ signals:
|
||||
void viewUpdated();
|
||||
void gotoSourceLocation(const QString &fileUrl, int lineNumber);
|
||||
void timeChanged(qreal newTime);
|
||||
void range(int type, int nestingLevel, int nestingInType, qint64 startTime, qint64 length, const QStringList &data, const QString &fileName, int line);
|
||||
void range(int type, qint64 startTime, qint64 length, const QStringList &data, const QString &fileName, int line);
|
||||
|
||||
void internalClearDisplay();
|
||||
void jumpToPrev();
|
||||
@@ -78,11 +81,17 @@ signals:
|
||||
void zoomOut();
|
||||
void enableToolbar(bool);
|
||||
|
||||
void contextMenuRequested(const QPoint& position);
|
||||
|
||||
private:
|
||||
void contextMenuEvent(QContextMenuEvent *);
|
||||
|
||||
private:
|
||||
QWeakPointer<QmlJsDebugClient::QmlProfilerTraceClient> m_plugin;
|
||||
QSize m_sizeHint;
|
||||
|
||||
QDeclarativeView *m_view;
|
||||
QmlProfilerEventList *m_eventList;
|
||||
};
|
||||
|
||||
} // namespace Internal
|
||||
|
Reference in New Issue
Block a user