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:
Christiaan Janssen
2011-07-26 13:56:14 +02:00
parent 8fbaa0d10a
commit d2911d70f3
17 changed files with 1417 additions and 489 deletions

View File

@@ -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() +

View File

@@ -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);

View File

@@ -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;
}

View File

@@ -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;
}
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;
}
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
onParsingStatusChanged: {
root.dataAvailable = false;
}
onDataReady: {
if (eventCount > 0) {
view.clearData();
view.rebuildCache();
}
}
onDataCleared: {
root.clearAll();
}
}
// 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;

View File

@@ -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

View File

@@ -164,7 +164,7 @@ TiledCanvas {
}
onMousePositionChanged: {
if (!Plotter.ranges.length)
if (!root.eventCount)
return;
if (!pressed && timeDisplayEnd.visible)

View File

@@ -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

View 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

View 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

View File

@@ -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

View File

@@ -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;

View File

@@ -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()));
}
}

View File

@@ -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();

View File

@@ -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;
}
while (prevMax > maxsample) {
delete m_items.take(prevMax);
--prevMax;
}
// 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;
// the next loops have to be modified with the new implementation of the cache
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);
// 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);
}
} 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);
}
label += sub;
}
ctxt->setContextProperty("label", label);
ctxt->setContextProperty("type", type);
item->setParentItem(this);
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);
}
}
// Update visible items
for (int i = minsample; i <= maxsample; ++i) {
if (!m_items.contains(i)) {
createItem(i);
m_items.value(i)->setVisible(true);
}
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);
else
if (spacingChanged || !m_items.value(i)->isVisible()) {
m_items.value(i)->setVisible(true);
updateItemPosition(i);
}
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);
}

View File

@@ -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

View File

@@ -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();
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)

View File

@@ -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