From d2911d70f36b7fd1de4ebbd41915782669f8d721 Mon Sep 17 00:00:00 2001 From: Christiaan Janssen <christiaan.janssen@nokia.com> Date: Tue, 26 Jul 2011 13:56:14 +0200 Subject: [PATCH] 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> --- .../qmlprofilertraceclient.cpp | 13 +- .../qmljsdebugclient/qmlprofilertraceclient.h | 2 +- src/plugins/qmlprofiler/qml/MainView.js | 26 +- src/plugins/qmlprofiler/qml/MainView.qml | 108 +-- src/plugins/qmlprofiler/qml/StatusDisplay.qml | 9 +- src/plugins/qmlprofiler/qml/TimeDisplay.qml | 2 +- src/plugins/qmlprofiler/qmlprofiler.pro | 6 +- .../qmlprofiler/qmlprofilereventlist.cpp | 849 ++++++++++++++++++ .../qmlprofiler/qmlprofilereventlist.h | 138 +++ .../qmlprofiler/qmlprofilereventview.cpp | 213 +---- .../qmlprofiler/qmlprofilereventview.h | 61 +- src/plugins/qmlprofiler/qmlprofilertool.cpp | 58 +- src/plugins/qmlprofiler/qmlprofilertool.h | 6 + src/plugins/qmlprofiler/timelineview.cpp | 320 ++++--- src/plugins/qmlprofiler/timelineview.h | 49 +- src/plugins/qmlprofiler/tracewindow.cpp | 35 +- src/plugins/qmlprofiler/tracewindow.h | 11 +- 17 files changed, 1417 insertions(+), 489 deletions(-) create mode 100644 src/plugins/qmlprofiler/qmlprofilereventlist.cpp create mode 100644 src/plugins/qmlprofiler/qmlprofilereventlist.h diff --git a/src/libs/qmljsdebugclient/qmlprofilertraceclient.cpp b/src/libs/qmljsdebugclient/qmlprofilertraceclient.cpp index 6042f38a566..62e8bd58782 100644 --- a/src/libs/qmljsdebugclient/qmlprofilertraceclient.cpp +++ b/src/libs/qmljsdebugclient/qmlprofilertraceclient.cpp @@ -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() + diff --git a/src/libs/qmljsdebugclient/qmlprofilertraceclient.h b/src/libs/qmljsdebugclient/qmlprofilertraceclient.h index abb64afd037..ca596b706ad 100644 --- a/src/libs/qmljsdebugclient/qmlprofilertraceclient.h +++ b/src/libs/qmljsdebugclient/qmlprofilertraceclient.h @@ -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); diff --git a/src/plugins/qmlprofiler/qml/MainView.js b/src/plugins/qmlprofiler/qml/MainView.js index 128ea396a33..a95df7daeef 100644 --- a/src/plugins/qmlprofiler/qml/MainView.js +++ b/src/plugins/qmlprofiler/qml/MainView.js @@ -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; } diff --git a/src/plugins/qmlprofiler/qml/MainView.qml b/src/plugins/qmlprofiler/qml/MainView.qml index 72e026b5d77..025eac39b57 100644 --- a/src/plugins/qmlprofiler/qml/MainView.qml +++ b/src/plugins/qmlprofiler/qml/MainView.qml @@ -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; } - onDataCleared: { - root.clearAll(); + 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; diff --git a/src/plugins/qmlprofiler/qml/StatusDisplay.qml b/src/plugins/qmlprofiler/qml/StatusDisplay.qml index f09e886fa51..9089e42134a 100644 --- a/src/plugins/qmlprofiler/qml/StatusDisplay.qml +++ b/src/plugins/qmlprofiler/qml/StatusDisplay.qml @@ -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 diff --git a/src/plugins/qmlprofiler/qml/TimeDisplay.qml b/src/plugins/qmlprofiler/qml/TimeDisplay.qml index 3b5c67852c7..56c816245a5 100644 --- a/src/plugins/qmlprofiler/qml/TimeDisplay.qml +++ b/src/plugins/qmlprofiler/qml/TimeDisplay.qml @@ -164,7 +164,7 @@ TiledCanvas { } onMousePositionChanged: { - if (!Plotter.ranges.length) + if (!root.eventCount) return; if (!pressed && timeDisplayEnd.visible) diff --git a/src/plugins/qmlprofiler/qmlprofiler.pro b/src/plugins/qmlprofiler/qmlprofiler.pro index 107acdcfc5a..231d4bf36cb 100644 --- a/src/plugins/qmlprofiler/qmlprofiler.pro +++ b/src/plugins/qmlprofiler/qmlprofiler.pro @@ -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 diff --git a/src/plugins/qmlprofiler/qmlprofilereventlist.cpp b/src/plugins/qmlprofiler/qmlprofilereventlist.cpp new file mode 100644 index 00000000000..dd05407dbc1 --- /dev/null +++ b/src/plugins/qmlprofiler/qmlprofilereventlist.cpp @@ -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 diff --git a/src/plugins/qmlprofiler/qmlprofilereventlist.h b/src/plugins/qmlprofiler/qmlprofilereventlist.h new file mode 100644 index 00000000000..767da098d72 --- /dev/null +++ b/src/plugins/qmlprofiler/qmlprofilereventlist.h @@ -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 diff --git a/src/plugins/qmlprofiler/qmlprofilereventview.cpp b/src/plugins/qmlprofiler/qmlprofilereventview.cpp index 28de7cb49e3..114c696e0d9 100644 --- a/src/plugins/qmlprofiler/qmlprofilereventview.cpp +++ b/src/plugins/qmlprofiler/qmlprofilereventview.cpp @@ -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 diff --git a/src/plugins/qmlprofiler/qmlprofilereventview.h b/src/plugins/qmlprofiler/qmlprofilereventview.h index a04b6d3128a..60f5b13acc7 100644 --- a/src/plugins/qmlprofiler/qmlprofilereventview.h +++ b/src/plugins/qmlprofiler/qmlprofilereventview.h @@ -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; diff --git a/src/plugins/qmlprofiler/qmlprofilertool.cpp b/src/plugins/qmlprofiler/qmlprofilertool.cpp index 855472c19dc..00738542f20 100644 --- a/src/plugins/qmlprofiler/qmlprofilertool.cpp +++ b/src/plugins/qmlprofiler/qmlprofilertool.cpp @@ -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())); + } +} diff --git a/src/plugins/qmlprofiler/qmlprofilertool.h b/src/plugins/qmlprofiler/qmlprofilertool.h index 2c627e9c876..594f1465857 100644 --- a/src/plugins/qmlprofiler/qmlprofilertool.h +++ b/src/plugins/qmlprofiler/qmlprofilertool.h @@ -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(); diff --git a/src/plugins/qmlprofiler/timelineview.cpp b/src/plugins/qmlprofiler/timelineview.cpp index 930672a8471..7e141f35683 100644 --- a/src/plugins/qmlprofiler/timelineview.cpp +++ b/src/plugins/qmlprofiler/timelineview.cpp @@ -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 oldtw = m_totalWidth; - m_totalWidth = totalRange * spacing; - - // Find region samples - int minsample = 0; - int maxsample = 0; + qreal newSpacing = width() / window; + bool spacingChanged = (newSpacing != m_spacing); + m_spacing = newSpacing; - 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; - } + qreal oldtw = m_totalWidth; + m_totalWidth = totalRange * m_spacing; - //### 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; - - 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); - } + // the next loops have to be modified with the new implementation of the cache - label += sub; + // 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); } - ctxt->setContextProperty("label", label); - ctxt->setContextProperty("type", type); - item->setParentItem(this); + } 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); + } + + 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); } diff --git a/src/plugins/qmlprofiler/timelineview.h b/src/plugins/qmlprofiler/timelineview.h index ca0bffc7925..8bf35ee6966 100644 --- a/src/plugins/qmlprofiler/timelineview.h +++ b/src/plugins/qmlprofiler/timelineview.h @@ -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 diff --git a/src/plugins/qmlprofiler/tracewindow.cpp b/src/plugins/qmlprofiler/tracewindow.cpp index b54115e0f98..48403cf6491 100644 --- a/src/plugins/qmlprofiler/tracewindow.cpp +++ b/src/plugins/qmlprofiler/tracewindow.cpp @@ -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) diff --git a/src/plugins/qmlprofiler/tracewindow.h b/src/plugins/qmlprofiler/tracewindow.h index 5629f977f04..04bbcab2613 100644 --- a/src/plugins/qmlprofiler/tracewindow.h +++ b/src/plugins/qmlprofiler/tracewindow.h @@ -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 -- GitLab