diff options
author | Christiaan Janssen <christiaan.janssen@digia.com> | 2013-08-08 13:28:08 +0200 |
---|---|---|
committer | Christiaan Janssen <christiaan.janssen@digia.com> | 2013-08-09 17:19:32 +0200 |
commit | 0a3b20f5f99bec75b590aa81ae26aac046efd794 (patch) | |
tree | 13cdc00fe9b5a3e5c91cba19950ff4d788ee42f8 | |
parent | 7764f35107e901e74458847315ffd114199ce26c (diff) | |
download | qt-creator-0a3b20f5f99bec75b590aa81ae26aac046efd794.tar.gz |
QmlProfiler: reworked
Change-Id: I66a236a024d76e7bef6edfb91ae30b5dd098b76b
Reviewed-by: Kai Koehne <kai.koehne@digia.com>
62 files changed, 7923 insertions, 3136 deletions
diff --git a/src/libs/qmldebug/qmldebug.qbs b/src/libs/qmldebug/qmldebug.qbs index 601c0e4c5d..beba356937 100644 --- a/src/libs/qmldebug/qmldebug.qbs +++ b/src/libs/qmldebug/qmldebug.qbs @@ -6,6 +6,7 @@ QtcLibrary { cpp.defines: base.concat("QMLDEBUG_LIB") + Depends { name: "cpp" } Depends { name: "Qt"; submodules: ["gui", "network"] } files: [ diff --git a/src/libs/qmldebug/qmlprofilereventtypes.h b/src/libs/qmldebug/qmlprofilereventtypes.h index b0498ab755..a5ece2928c 100644 --- a/src/libs/qmldebug/qmlprofilereventtypes.h +++ b/src/libs/qmldebug/qmlprofilereventtypes.h @@ -38,6 +38,8 @@ enum QmlEventType { Creating, Binding, HandlingSignal, + PixmapCacheEvent, + SceneGraphFrameEvent, MaximumQmlEventType }; @@ -46,6 +48,8 @@ enum BindingType { QmlBinding, V8Binding, OptimizedBinding, + QPainterEvent, + AnimationFrame, MaximumBindingType }; diff --git a/src/libs/qmldebug/qmlprofilertraceclient.cpp b/src/libs/qmldebug/qmlprofilertraceclient.cpp index 157c8ac4f6..b0824ddfdb 100644 --- a/src/libs/qmldebug/qmlprofilertraceclient.cpp +++ b/src/libs/qmldebug/qmlprofilertraceclient.cpp @@ -170,7 +170,8 @@ void QmlProfilerTraceClient::messageReceived(const QByteArray &data) } else if (event == AnimationFrame) { int frameRate, animationCount; stream >> frameRate >> animationCount; - emit this->frame(time, frameRate, animationCount); + emit rangedEvent(QmlDebug::Painting, QmlDebug::AnimationFrame, time, 0, + QStringList(), QmlDebug::QmlEventLocation(), frameRate, animationCount, 0,0,0); d->maximumTime = qMax(time, d->maximumTime); } else if (event == StartTrace) { emit this->traceStarted(time); @@ -181,6 +182,32 @@ void QmlProfilerTraceClient::messageReceived(const QByteArray &data) } } else if (messageType == Complete) { emit complete(); + } else if (messageType == SceneGraphFrame) { + int sgEventType; + int count = 0; + qint64 params[5]; + + stream >> sgEventType; + while (!stream.atEnd()) { + stream >> params[count++]; + } + while (count<5) + params[count++] = 0; + emit rangedEvent(SceneGraphFrameEvent, sgEventType,time, 0, QStringList(), + QmlDebug::QmlEventLocation(), params[0], params[1], params[2], params[3], params[4]); + } else if (messageType == PixmapCacheEvent) { + int pixEvTy, width = 0, height = 0, refcount = 0; + QString pixUrl; + stream >> pixEvTy >> pixUrl; + if (pixEvTy == (int)PixmapReferenceCountChanged || pixEvTy == (int)PixmapCacheCountChanged) { + stream >> refcount; + } else if (pixEvTy == (int)PixmapSizeKnown) { + stream >> width >> height; + refcount = 1; + } + emit rangedEvent(QmlDebug::PixmapCacheEvent, pixEvTy, time, 0, QStringList(), + QmlDebug::QmlEventLocation(pixUrl,0,0), width, height, refcount, 0, 0); + d->maximumTime = qMax(time, d->maximumTime); } else { int range; stream >> range; @@ -240,7 +267,10 @@ void QmlProfilerTraceClient::messageReceived(const QByteArray &data) BindingType bindingType = QmlBinding; if ((QmlEventType)range == Binding) bindingType = d->bindingTypes.pop(); - emit this->range((QmlEventType)range, bindingType, startTime, time - startTime, data, location); + if ((QmlEventType)range == Painting) + bindingType = QPainterEvent; + emit rangedEvent((QmlEventType)range, bindingType, startTime, time - startTime, data, + location, 0, 0, 0, 0, 0); if (d->rangeCount[range] == 0) { int count = d->rangeDatas[range].count() + d->rangeStartTimes[range].count() + diff --git a/src/libs/qmldebug/qmlprofilertraceclient.h b/src/libs/qmldebug/qmlprofilertraceclient.h index 6e0cf1470a..b8364c0ab0 100644 --- a/src/libs/qmldebug/qmlprofilertraceclient.h +++ b/src/libs/qmldebug/qmlprofilertraceclient.h @@ -71,10 +71,23 @@ public: RangeLocation, RangeEnd, Complete, + PixmapCacheEvent, + SceneGraphFrame, MaximumMessage }; + enum PixmapEventType { + PixmapSizeKnown, + PixmapReferenceCountChanged, + PixmapCacheCountChanged, + PixmapLoadingStarted, + PixmapLoadingFinished, + PixmapLoadingError, + + MaximumPixmapEventType + }; + bool isEnabled() const; bool isRecording() const; void setRecording(bool); @@ -89,10 +102,9 @@ signals: void event(int event, qint64 time); void traceFinished( qint64 time ); void traceStarted( qint64 time ); - void range(int type, int bindingType, qint64 startTime, qint64 length, - const QStringList &data, const QmlDebug::QmlEventLocation &location); - void frame(qint64 time, int frameRate, int animationCount); - + void rangedEvent(int type, int bindingType, qint64 startTime, qint64 length, + const QStringList &data, const QmlDebug::QmlEventLocation &location, + qint64 param1, qint64 param2, qint64 param3, qint64 param4, qint64 param5); void recordingChanged(bool arg); void enabledChanged(); diff --git a/src/plugins/qmlprofiler/abstracttimelinemodel.cpp b/src/plugins/qmlprofiler/abstracttimelinemodel.cpp new file mode 100644 index 0000000000..b45b3cc0c1 --- /dev/null +++ b/src/plugins/qmlprofiler/abstracttimelinemodel.cpp @@ -0,0 +1,82 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, 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, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + +#include "abstracttimelinemodel.h" + +namespace QmlProfiler { + +AbstractTimelineModel::AbstractTimelineModel(QObject *parent) : QObject(parent), m_modelManager(0) +{} + +AbstractTimelineModel::~AbstractTimelineModel() +{} + +void AbstractTimelineModel::setModelManager(QmlProfilerModelManager *modelManager) +{ + m_modelManager = modelManager; + connect(modelManager->simpleModel(),SIGNAL(changed()),this,SLOT(dataChanged())); + m_modelId = modelManager->registerModelProxy(); +} + +qint64 AbstractTimelineModel::traceStartTime() const +{ + return m_modelManager->traceTime()->startTime(); +} + +qint64 AbstractTimelineModel::traceEndTime() const +{ + return m_modelManager->traceTime()->endTime(); +} + +qint64 AbstractTimelineModel::traceDuration() const +{ + return m_modelManager->traceTime()->duration(); +} + +int AbstractTimelineModel::getState() const +{ + return (int)m_modelManager->state(); +} + +int AbstractTimelineModel::rowCount() const +{ + int count = 0; + for (int i=0; i<categoryCount(); i++) + count += categoryDepth(i); + return count; +} + +int AbstractTimelineModel::getBindingLoopDest(int index) const +{ + Q_UNUSED(index); + return -1; +} + + +} diff --git a/src/plugins/qmlprofiler/abstracttimelinemodel.h b/src/plugins/qmlprofiler/abstracttimelinemodel.h new file mode 100644 index 0000000000..0efcd60631 --- /dev/null +++ b/src/plugins/qmlprofiler/abstracttimelinemodel.h @@ -0,0 +1,113 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, 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, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + +#ifndef ABSTRACTTIMELINEMODEL_H +#define ABSTRACTTIMELINEMODEL_H + + +#include "qmlprofiler_global.h" +#include "qmlprofilermodelmanager.h" +#include "qmlprofilersimplemodel.h" +#include <QObject> +#include <QVariant> +#include <QColor> + +namespace QmlProfiler { + +class QMLPROFILER_EXPORT AbstractTimelineModel : public QObject +{ + Q_OBJECT + +public: + explicit AbstractTimelineModel(QObject *parent = 0); + ~AbstractTimelineModel(); + + void setModelManager(QmlProfilerModelManager *modelManager); + + virtual int categories() const = 0; + virtual QStringList categoryTitles() const = 0; + virtual QString name() const = 0; + virtual int count() const = 0; + + virtual bool isEmpty() const = 0; + + virtual bool eventAccepted(const QmlProfilerSimpleModel::QmlEventData &event) const = 0; + + Q_INVOKABLE virtual qint64 lastTimeMark() const = 0; + Q_INVOKABLE qint64 traceStartTime() const; + Q_INVOKABLE qint64 traceEndTime() const; + Q_INVOKABLE qint64 traceDuration() const; + Q_INVOKABLE int getState() const; + + Q_INVOKABLE virtual bool expanded(int category) const = 0; + Q_INVOKABLE virtual void setExpanded(int category, bool expanded) = 0; + Q_INVOKABLE virtual int categoryDepth(int categoryIndex) const = 0; + Q_INVOKABLE virtual int categoryCount() const = 0; + Q_INVOKABLE virtual int rowCount() const; + Q_INVOKABLE virtual const QString categoryLabel(int categoryIndex) const = 0; + + virtual int findFirstIndex(qint64 startTime) const = 0; + virtual int findFirstIndexNoParents(qint64 startTime) const = 0; + virtual int findLastIndex(qint64 endTime) const = 0; + + virtual int getEventType(int index) const = 0; + virtual int getEventCategory(int index) const = 0; + virtual int getEventRow(int index) const = 0; + Q_INVOKABLE virtual qint64 getDuration(int index) const = 0; + Q_INVOKABLE virtual qint64 getStartTime(int index) const = 0; + Q_INVOKABLE virtual qint64 getEndTime(int index) const = 0; + Q_INVOKABLE virtual int getEventId(int index) const = 0; + Q_INVOKABLE virtual int getBindingLoopDest(int index) const; + Q_INVOKABLE virtual QColor getColor(int index) const = 0; + Q_INVOKABLE virtual float getHeight(int index) const = 0; + + Q_INVOKABLE virtual const QVariantList getLabelsForCategory(int category) const = 0; + + Q_INVOKABLE virtual const QVariantList getEventDetails(int index) const = 0; + + // returned map should contain "file", "line", "column" properties, or be empty + Q_INVOKABLE virtual const QVariantMap getEventLocation(int index) const = 0; + Q_INVOKABLE virtual int getEventIdForHash(const QString &eventHash) const = 0; + Q_INVOKABLE virtual int getEventIdForLocation(const QString &filename, int line, int column) const = 0; + +signals: + void countChanged(); + void dataAvailable(); + void stateChanged(); + void emptyChanged(); + void expandedChanged(); + +protected: + QmlProfilerModelManager *m_modelManager; + int m_modelId; +}; + +} + +#endif // ABSTRACTTIMELINEMODEL_H diff --git a/src/plugins/qmlprofiler/qml/Detail.qml b/src/plugins/qmlprofiler/qml/Detail.qml index 567ea7f02a..7bc28178d9 100644 --- a/src/plugins/qmlprofiler/qml/Detail.qml +++ b/src/plugins/qmlprofiler/qml/Detail.qml @@ -34,7 +34,6 @@ Item { id: detail property string label property string content - signal linkActivated(string url) height: childrenRect.height+2 width: childrenRect.width @@ -55,7 +54,6 @@ Item { font.pixelSize: 12 anchors.baseline: lbl.baseline anchors.left: guideline.right - onLinkActivated: detail.linkActivated(link) textFormat: Text.PlainText } } diff --git a/src/plugins/qmlprofiler/qml/Label.qml b/src/plugins/qmlprofiler/qml/Label.qml index f7db9ab34c..015391a3f5 100644 --- a/src/plugins/qmlprofiler/qml/Label.qml +++ b/src/plugins/qmlprofiler/qml/Label.qml @@ -31,23 +31,24 @@ import QtQuick 1.0 Item { id: labelContainer - property alias text: txt.text + property string text: qmlProfilerModelProxy.categoryLabel(modelIndex, categoryIndex) property bool expanded: false - property int typeIndex: index + property int categoryIndex: qmlProfilerModelProxy.correctedCategoryIndexForModel(modelIndex, index) + property int modelIndex: qmlProfilerModelProxy.modelIndexForCategory(index); property variant descriptions: [] property variant extdescriptions: [] property variant eventIds: [] + visible: qmlProfilerModelProxy.categoryDepth(modelIndex, categoryIndex) > 0; + height: root.singleRowHeight width: 150 onExpandedChanged: { - var rE = labels.rowExpanded; - rE[typeIndex] = expanded; - labels.rowExpanded = rE; + qmlProfilerModelProxy.setExpanded(modelIndex, categoryIndex, expanded); backgroundMarks.requestRedraw(); - view.setRowExpanded(typeIndex, expanded); + getDescriptions(); updateHeight(); } @@ -56,20 +57,24 @@ Item { } function updateHeight() { - height = root.singleRowHeight * (1 + - (expanded ? qmlProfilerDataModel.uniqueEventsOfType(typeIndex) : - qmlProfilerDataModel.maxNestingForType(typeIndex))); + if (expanded != qmlProfilerModelProxy.expanded(modelIndex, categoryIndex)) + expanded = qmlProfilerModelProxy.expanded(modelIndex, categoryIndex); + height = root.singleRowHeight * qmlProfilerModelProxy.categoryDepth(modelIndex, categoryIndex); } function getDescriptions() { + visible = qmlProfilerModelProxy.categoryDepth(modelIndex, categoryIndex) > 0; + if (!visible) + return; + var desc=[]; var ids=[]; var extdesc=[]; - for (var i=0; i<qmlProfilerDataModel.uniqueEventsOfType(typeIndex); i++) { - desc[i] = qmlProfilerDataModel.eventTextForType(typeIndex, i); - ids[i] = qmlProfilerDataModel.eventIdForType(typeIndex, i); - extdesc[i] = qmlProfilerDataModel.eventDisplayNameForType(typeIndex, i) + - " : " + desc[i]; + var labelList = qmlProfilerModelProxy.getLabelsForCategory(modelIndex, categoryIndex); + for (var i = 0; i < labelList.length; i++ ) { + desc[i] = labelList[i].description; + ids[i] = labelList[i].id; + extdesc[i] = labelList[i].displayName + ":" + labelList[i].description; } descriptions = desc; eventIds = ids; @@ -78,20 +83,13 @@ Item { } Connections { - target: qmlProfilerDataModel - onReloadDetailLabels: getDescriptions(); + target: qmlProfilerModelProxy + onExpandedChanged: { + updateHeight(); + } + onStateChanged: { - // Empty - if (qmlProfilerDataModel.getCurrentStateFromQml() == 0) { - descriptions = []; - eventIds = []; - extdescriptions = []; - updateHeight(); - } else - // Done - if (qmlProfilerDataModel.getCurrentStateFromQml() == 3) { - getDescriptions(); - } + getDescriptions(); } } @@ -99,6 +97,7 @@ Item { id: txt x: 5 font.pixelSize: 12 + text: labelContainer.text color: "#232323" height: root.singleRowHeight width: 140 @@ -140,9 +139,9 @@ Item { onExited: changeToolTip(""); onClicked: { if (mouse.modifiers & Qt.ShiftModifier) - view.selectPrevFromId(eventIds[index]); + view.selectPrevFromId(modelIndex,eventIds[index]); else - view.selectNextFromId(eventIds[index]); + view.selectNextFromId(modelIndex,eventIds[index]); } } } @@ -150,7 +149,6 @@ Item { } Image { - visible: descriptions.length > 0 source: expanded ? "arrow_down.png" : "arrow_right.png" x: parent.width - 12 y: root.singleRowHeight / 2 - height / 2 diff --git a/src/plugins/qmlprofiler/qml/MainView.qml b/src/plugins/qmlprofiler/qml/MainView.qml index df8b8922a3..9e859258ee 100644 --- a/src/plugins/qmlprofiler/qml/MainView.qml +++ b/src/plugins/qmlprofiler/qml/MainView.qml @@ -51,14 +51,6 @@ Rectangle { signal selectedEventChanged(int eventId) property bool lockItemSelection : false - property variant names: [ qsTr("Painting"), - qsTr("Compiling"), - qsTr("Creating"), - qsTr("Binding"), - qsTr("Handling Signal")] - property variant colors : [ "#99CCB3", "#99CCCC", "#99B3CC", - "#9999CC", "#CC99B3", "#CC99CC", "#CCCC99", "#CCB399" ] - property variant mainviewTimePerPixel : 0 signal updateCursorPosition @@ -95,7 +87,7 @@ Rectangle { backgroundMarks.updateMarks(startTime, endTime); view.updateFlickRange(startTime, endTime); if (duration > 0) { - var candidateWidth = qmlProfilerDataModel.traceDuration() * + var candidateWidth = qmlProfilerModelProxy.traceDuration() * flick.width / duration; if (flick.contentWidth !== candidateWidth) flick.contentWidth = candidateWidth; @@ -104,22 +96,23 @@ Rectangle { } } + Connections { - target: qmlProfilerDataModel + target: qmlProfilerModelProxy onCountChanged: { - eventCount = qmlProfilerDataModel.count(); + eventCount = qmlProfilerModelProxy.count(); if (eventCount === 0) root.clearAll(); if (eventCount > 1) { root.progress = Math.min(1.0, - (qmlProfilerDataModel.lastTimeMark() - - qmlProfilerDataModel.traceStartTime()) / root.elapsedTime * 1e-9 ); + (qmlProfilerModelProxy.lastTimeMark() - + qmlProfilerModelProxy.traceStartTime()) / root.elapsedTime * 1e-9 ); } else { root.progress = 0; } } onStateChanged: { - switch (qmlProfilerDataModel.getCurrentStateFromQml()) { + switch (qmlProfilerModelProxy.getState()) { case 0: { root.clearAll(); break; @@ -132,27 +125,30 @@ Rectangle { root.progress = 0.9; // jump to 90% break; } - case 3: { - view.clearData(); - progress = 1.0; - dataAvailable = true; - view.visible = true; - view.requestPaint(); - zoomControl.setRange(qmlProfilerDataModel.traceStartTime(), - qmlProfilerDataModel.traceStartTime() + - qmlProfilerDataModel.traceDuration()/10); - break; - } } } + onDataAvailable: { + view.clearData(); + zoomControl.setRange(0,0); + progress = 1.0; + dataAvailable = true; + view.visible = true; + view.requestPaint(); + zoomControl.setRange(qmlProfilerModelProxy.traceStartTime(), + qmlProfilerModelProxy.traceStartTime() + + qmlProfilerModelProxy.traceDuration()/10); + } } + // ***** functions function gotoSourceLocation(file,line,column) { - root.fileName = file; - root.lineNumber = line; - root.columnNumber = column; - root.updateCursorPosition(); + if (file !== undefined) { + root.fileName = file; + root.lineNumber = line; + root.columnNumber = column; + root.updateCursorPosition(); + } } function clearData() { @@ -186,10 +182,10 @@ Rectangle { function updateWindowLength(absoluteFactor) { var windowLength = view.endTime - view.startTime; - if (qmlProfilerDataModel.traceEndTime() <= qmlProfilerDataModel.traceStartTime() || + if (qmlProfilerModelProxy.traceEndTime() <= qmlProfilerModelProxy.traceStartTime() || windowLength <= 0) return; - var currentFactor = windowLength / qmlProfilerDataModel.traceDuration(); + var currentFactor = windowLength / qmlProfilerModelProxy.traceDuration(); updateZoom(absoluteFactor / currentFactor); } @@ -200,8 +196,8 @@ Rectangle { windowLength = min_length; var newWindowLength = windowLength * relativeFactor; - if (newWindowLength > qmlProfilerDataModel.traceDuration()) { - newWindowLength = qmlProfilerDataModel.traceDuration(); + if (newWindowLength > qmlProfilerModelProxy.traceDuration()) { + newWindowLength = qmlProfilerModelProxy.traceDuration(); relativeFactor = newWindowLength / windowLength; } if (newWindowLength < min_length) { @@ -210,13 +206,15 @@ Rectangle { } var fixedPoint = (view.startTime + view.endTime) / 2; + if (view.selectedItem !== -1) { // center on selected item if it's inside the current screen - var newFixedPoint = qmlProfilerDataModel.getStartTime(view.selectedItem); + var newFixedPoint = qmlProfilerModelProxy.getStartTime(view.selectedModel, view.selectedItem); if (newFixedPoint >= view.startTime && newFixedPoint < view.endTime) fixedPoint = newFixedPoint; } + var startTime = fixedPoint - relativeFactor*(fixedPoint - view.startTime); zoomControl.setRange(startTime, startTime + newWindowLength); } @@ -229,8 +227,8 @@ Rectangle { windowLength = min_length; var newWindowLength = windowLength * relativeFactor; - if (newWindowLength > qmlProfilerDataModel.traceDuration()) { - newWindowLength = qmlProfilerDataModel.traceDuration(); + if (newWindowLength > qmlProfilerModelProxy.traceDuration()) { + newWindowLength = qmlProfilerModelProxy.traceDuration(); relativeFactor = newWindowLength / windowLength; } if (newWindowLength < min_length) { @@ -248,26 +246,27 @@ Rectangle { var newStart = Math.floor(centerPoint - windowLength/2); if (newStart < 0) newStart = 0; - if (newStart + windowLength > qmlProfilerDataModel.traceEndTime()) - newStart = qmlProfilerDataModel.traceEndTime() - windowLength; + if (newStart + windowLength > qmlProfilerModelProxy.traceEndTime()) + newStart = qmlProfilerModelProxy.traceEndTime() - windowLength; zoomControl.setRange(newStart, newStart + windowLength); } - function recenterOnItem( itemIndex ) + function recenterOnItem( modelIndex, itemIndex ) { if (itemIndex === -1) return; // if item is outside of the view, jump back to its position - if (qmlProfilerDataModel.getEndTime(itemIndex) < view.startTime || - qmlProfilerDataModel.getStartTime(itemIndex) > view.endTime) { - recenter((qmlProfilerDataModel.getStartTime(itemIndex) + - qmlProfilerDataModel.getEndTime(itemIndex)) / 2); + if (qmlProfilerModelProxy.getEndTime(modelIndex, itemIndex) < view.startTime || + qmlProfilerModelProxy.getStartTime(modelIndex, itemIndex) > view.endTime) { + recenter((qmlProfilerModelProxy.getStartTime(modelIndex, itemIndex) + + qmlProfilerModelProxy.getEndTime(modelIndex, itemIndex)) / 2); } + } function wheelZoom(wheelCenter, wheelDelta) { - if (qmlProfilerDataModel.traceEndTime() > qmlProfilerDataModel.traceStartTime() && + if (qmlProfilerModelProxy.traceEndTime() > qmlProfilerModelProxy.traceStartTime() && wheelDelta !== 0) { if (wheelDelta>0) updateZoomCentered(wheelCenter, 1/1.2); @@ -280,24 +279,35 @@ Rectangle { rangeDetails.visible = false; rangeDetails.duration = ""; rangeDetails.label = ""; - rangeDetails.type = ""; + //rangeDetails.type = ""; rangeDetails.file = ""; rangeDetails.line = -1; rangeDetails.column = 0; rangeDetails.isBindingLoop = false; } - function selectNextWithId( eventId ) + function selectNextByHash(hash) { + var eventId = qmlProfilerModelProxy.getEventIdForHash(hash); + if (eventId !== -1) { + selectNextById(eventId); + } + } + + function selectNextById(eventId) { + // this is a slot responding to events from the other pane + // which tracks only events from the basic model if (!lockItemSelection) { lockItemSelection = true; - var itemIndex = view.nextItemFromId( eventId ); + var modelIndex = qmlProfilerModelProxy.basicModelIndex(); + var itemIndex = view.nextItemFromId( modelIndex, eventId ); // select an item, lock to it, and recenter if necessary - if (view.selectedItem != itemIndex) { + if (view.selectedItem != itemIndex || view.selectedModel != modelIndex) { + view.selectedModel = modelIndex; view.selectedItem = itemIndex; if (itemIndex !== -1) { view.selectionLocked = true; - recenterOnItem(itemIndex); + recenterOnItem(modelIndex, itemIndex); } } lockItemSelection = false; @@ -317,7 +327,9 @@ Rectangle { onSelectedItemChanged: { if (selectedItem != -1 && !lockItemSelection) { lockItemSelection = true; - selectedEventChanged( qmlProfilerDataModel.getEventId(selectedItem) ); + // update in other views + var eventLocation = qmlProfilerModelProxy.getEventLocation(view.selectedModel, view.selectedItem); + gotoSourceLocation(eventLocation.file, eventLocation.line, eventLocation.column); lockItemSelection = false; } } @@ -392,7 +404,7 @@ Rectangle { TimelineRenderer { id: view - profilerDataModel: qmlProfilerDataModel + profilerModelProxy: qmlProfilerModelProxy x: flick.contentX width: flick.width @@ -401,12 +413,12 @@ Rectangle { property variant startX: 0 onStartXChanged: { var newStartTime = Math.round(startX * (endTime - startTime) / flick.width) + - qmlProfilerDataModel.traceStartTime(); + qmlProfilerModelProxy.traceStartTime(); if (Math.abs(newStartTime - startTime) > 1) { var newEndTime = Math.round((startX+flick.width) * (endTime - startTime) / flick.width) + - qmlProfilerDataModel.traceStartTime(); + qmlProfilerModelProxy.traceStartTime(); zoomControl.setRange(newStartTime, newEndTime); } @@ -418,7 +430,7 @@ Rectangle { if (start !== startTime || end !== endTime) { startTime = start; endTime = end; - var newStartX = (startTime - qmlProfilerDataModel.traceStartTime()) * + var newStartX = (startTime - qmlProfilerModelProxy.traceStartTime()) * flick.width / (endTime-startTime); if (Math.abs(newStartX - startX) >= 1) startX = newStartX; @@ -428,32 +440,26 @@ Rectangle { onSelectedItemChanged: { if (selectedItem !== -1) { // display details - rangeDetails.duration = qmlProfilerDataModel.getDuration(selectedItem)/1000.0; - rangeDetails.label = qmlProfilerDataModel.getDetails(selectedItem); - rangeDetails.file = qmlProfilerDataModel.getFilename(selectedItem); - rangeDetails.line = qmlProfilerDataModel.getLine(selectedItem); - rangeDetails.column = qmlProfilerDataModel.getColumn(selectedItem); - rangeDetails.type = root.names[qmlProfilerDataModel.getType(selectedItem)]; - rangeDetails.isBindingLoop = qmlProfilerDataModel.getBindingLoopDest(selectedItem)!==-1; - - rangeDetails.visible = true; + rangeDetails.showInfo(qmlProfilerModelProxy.getEventDetails(selectedModel, selectedItem)); + rangeDetails.setLocation(qmlProfilerModelProxy.getEventLocation(selectedModel, selectedItem)); // center view (horizontally) var windowLength = view.endTime - view.startTime; - var eventStartTime = qmlProfilerDataModel.getStartTime(selectedItem); + var eventStartTime = qmlProfilerModelProxy.getStartTime(selectedModel, selectedItem); var eventEndTime = eventStartTime + - qmlProfilerDataModel.getDuration(selectedItem); + qmlProfilerModelProxy.getDuration(selectedModel, selectedItem); if (eventEndTime < view.startTime || eventStartTime > view.endTime) { var center = (eventStartTime + eventEndTime)/2; - var from = Math.min(qmlProfilerDataModel.traceEndTime()-windowLength, + var from = Math.min(qmlProfilerModelProxy.traceEndTime()-windowLength, Math.max(0, Math.floor(center - windowLength/2))); zoomControl.setRange(from, from + windowLength); + } // center view (vertically) - var itemY = view.getYPosition(selectedItem); + var itemY = view.getYPosition(selectedModel, selectedItem); if (itemY < root.scrollY) { root.updateVerticalScroll(itemY); } else @@ -462,17 +468,16 @@ Rectangle { root.updateVerticalScroll(itemY + root.singleRowHeight - root.candidateHeight); } + } else { root.hideRangeDetails(); } } onItemPressed: { - if (pressedItem !== -1) { - root.gotoSourceLocation(qmlProfilerDataModel.getFilename(pressedItem), - qmlProfilerDataModel.getLine(pressedItem), - qmlProfilerDataModel.getColumn(pressedItem)); - } + var location = qmlProfilerModelProxy.getEventLocation(modelIndex, pressedItem); + if (location.hasOwnProperty("file")) // not empty + root.gotoSourceLocation(location.file, location.line, location.column); } // hack to pass mouse events to the other mousearea if enabled @@ -522,17 +527,13 @@ Rectangle { color: "#dcdcdc" height: col.height - property int rowCount: 5 - property variant rowExpanded: [false,false,false,false,false]; + property int rowCount: qmlProfilerModelProxy.categories(); Column { id: col Repeater { model: labels.rowCount - delegate: Label { - text: root.names[index] - height: labels.height/labels.rowCount - } + delegate: Label { } } } } diff --git a/src/plugins/qmlprofiler/qml/Overview.js b/src/plugins/qmlprofiler/qml/Overview.js index 06b4d7c91d..4ef2187b43 100644 --- a/src/plugins/qmlprofiler/qml/Overview.js +++ b/src/plugins/qmlprofiler/qml/Overview.js @@ -29,7 +29,7 @@ .pragma library -var qmlProfilerDataModel = 0; +var qmlProfilerModelProxy = 0; //draw background of the graph function drawGraph(canvas, ctxt, region) @@ -41,89 +41,82 @@ function drawGraph(canvas, ctxt, region) //draw the actual data to be graphed function drawData(canvas, ctxt, region) { - if ((!qmlProfilerDataModel) || qmlProfilerDataModel.count() == 0) + if ((!qmlProfilerModelProxy) || qmlProfilerModelProxy.count() == 0) return; - var typeCount = 5; var width = canvas.width; var bump = 10; var height = canvas.height - bump; + + var typeCount = qmlProfilerModelProxy.visibleCategories(); var blockHeight = height / typeCount; - var spacing = width / qmlProfilerDataModel.traceDuration(); - - var highest = [0,0,0,0,0]; // note: change if typeCount changes - - for (var ii = 0; ii < qmlProfilerDataModel.count(); ++ii) { - - var xx = (qmlProfilerDataModel.getStartTime(ii) - - qmlProfilerDataModel.traceStartTime()) * spacing; - if (xx > region.x + region.width) - continue; - - var eventWidth = qmlProfilerDataModel.getDuration(ii) * spacing; - if (xx + eventWidth < region.x) - continue; - - if (eventWidth < 1) - eventWidth = 1; - - xx = Math.round(xx); - var ty = qmlProfilerDataModel.getType(ii); - if (xx + eventWidth > highest[ty]) { - // special: animations - if (ty === 0 && qmlProfilerDataModel.getAnimationCount(ii) >= 0) { - var vertScale = qmlProfilerDataModel.getMaximumAnimationCount() - - qmlProfilerDataModel.getMinimumAnimationCount(); - if (vertScale < 1) - vertScale = 1; - var fraction = (qmlProfilerDataModel.getAnimationCount(ii) - - qmlProfilerDataModel.getMinimumAnimationCount()) / vertScale; - var eventHeight = blockHeight * (fraction * 0.85 + 0.15); - var yy = bump + ty*blockHeight + blockHeight - eventHeight; - - var fpsFraction = qmlProfilerDataModel.getFramerate(ii) / 60.0; - if (fpsFraction > 1.0) - fpsFraction = 1.0; - ctxt.fillStyle = "hsl("+(fpsFraction*0.27+0.028)+",0.3,0.65)"; - ctxt.fillRect(xx, yy, eventWidth, eventHeight); - } else { - var hue = ( qmlProfilerDataModel.getEventId(ii) * 25 ) % 360; - ctxt.fillStyle = "hsl("+(hue/360.0+0.001)+",0.3,0.65)"; - ctxt.fillRect(xx, bump + ty*blockHeight, eventWidth, blockHeight); - } - highest[ty] = xx+eventWidth; + var spacing = width / qmlProfilerModelProxy.traceDuration(); + + var modelRowStart = 0; + for (var modelIndex = 0; modelIndex < qmlProfilerModelProxy.modelCount(); modelIndex++) { + for (var ii = 0; ii < qmlProfilerModelProxy.count(modelIndex); ++ii) { + + var xx = (qmlProfilerModelProxy.getStartTime(modelIndex,ii) - + qmlProfilerModelProxy.traceStartTime()) * spacing; + if (xx > region.x + region.width) + continue; + + var eventWidth = qmlProfilerModelProxy.getDuration(modelIndex,ii) * spacing; + if (xx + eventWidth < region.x) + continue; + + if (eventWidth < 1) + eventWidth = 1; + + xx = Math.round(xx); + + var rowNumber = modelRowStart + qmlProfilerModelProxy.getEventCategoryInModel(modelIndex, ii); + + var itemHeight = qmlProfilerModelProxy.getHeight(modelIndex,ii) * blockHeight; + var yy = (rowNumber + 1) * blockHeight - itemHeight ; + + var itemColor = qmlProfilerModelProxy.getColorRGB(modelIndex, ii); + ctxt.fillStyle = "rgb("+itemColor[0]+","+itemColor[1]+","+itemColor[2]+")"; + ctxt.fillRect(xx, bump + yy, eventWidth, itemHeight); } + modelRowStart += qmlProfilerModelProxy.categoryCount(modelIndex); } // binding loops ctxt.strokeStyle = "orange"; ctxt.lineWidth = 2; var radius = 1; - for (var ii = 0; ii < qmlProfilerDataModel.count(); ++ii) { - if (qmlProfilerDataModel.getBindingLoopDest(ii) >= 0) { - var xcenter = Math.round(qmlProfilerDataModel.getStartTime(ii) + - qmlProfilerDataModel.getDuration(ii) - - qmlProfilerDataModel.traceStartTime()) * spacing; - var ycenter = Math.round(bump + qmlProfilerDataModel.getType(ii) * - blockHeight + blockHeight/2); - ctxt.arc(xcenter, ycenter, radius, 0, 2*Math.PI, true); - ctxt.stroke(); + modelRowStart = 0; + for (modelIndex = 0; modelIndex < qmlProfilerModelProxy.modelCount(); modelIndex++) { + for (ii = 0; ii < qmlProfilerModelProxy.count(modelIndex); ++ii) { + if (qmlProfilerModelProxy.getBindingLoopDest(modelIndex,ii) >= 0) { + var xcenter = Math.round(qmlProfilerModelProxy.getStartTime(modelIndex,ii) + + qmlProfilerModelProxy.getDuration(modelIndex,ii) - + qmlProfilerModelProxy.traceStartTime()) * spacing; + var ycenter = Math.round(bump + (modelRowStart + + qmlProfilerModelProxy.getEventCategoryInModel(modelIndex, ii)) * + blockHeight + blockHeight/2); + ctxt.arc(xcenter, ycenter, radius, 0, 2*Math.PI, true); + ctxt.stroke(); + } } + modelRowStart += qmlProfilerModelProxy.categoryCount(modelIndex); } + } function drawTimeBar(canvas, ctxt, region) { - if (!qmlProfilerDataModel) + if (!qmlProfilerModelProxy) return; var width = canvas.width; var height = 10; - var startTime = qmlProfilerDataModel.traceStartTime(); - var endTime = qmlProfilerDataModel.traceEndTime(); + var startTime = qmlProfilerModelProxy.traceStartTime(); + var endTime = qmlProfilerModelProxy.traceEndTime(); - var totalTime = qmlProfilerDataModel.traceDuration(); + var totalTime = qmlProfilerModelProxy.traceDuration(); var spacing = width / totalTime; var initialBlockLength = 120; diff --git a/src/plugins/qmlprofiler/qml/Overview.qml b/src/plugins/qmlprofiler/qml/Overview.qml index 65686be9de..d90e1e6c7d 100644 --- a/src/plugins/qmlprofiler/qml/Overview.qml +++ b/src/plugins/qmlprofiler/qml/Overview.qml @@ -36,36 +36,37 @@ Canvas2D { // ***** properties height: 50 - property bool dataAvailable: false + property bool dataReady: false property variant startTime : 0 property variant endTime : 0 // ***** functions function clearDisplay() { - dataAvailable = false; + dataReady = false; requestRedraw(); } function updateRange() { - var newStartTime = Math.round(rangeMover.x * qmlProfilerDataModel.traceDuration() / width) + qmlProfilerDataModel.traceStartTime(); - var newEndTime = Math.round((rangeMover.x + rangeMover.width) * qmlProfilerDataModel.traceDuration() / width) + qmlProfilerDataModel.traceStartTime(); + var newStartTime = Math.round(rangeMover.x * qmlProfilerModelProxy.traceDuration() / width) + qmlProfilerModelProxy.traceStartTime(); + var newEndTime = Math.round((rangeMover.x + rangeMover.width) * qmlProfilerModelProxy.traceDuration() / width) + qmlProfilerModelProxy.traceStartTime(); if (startTime !== newStartTime || endTime !== newEndTime) { zoomControl.setRange(newStartTime, newEndTime); } + } // ***** connections to external objects Connections { target: zoomControl onRangeChanged: { - if (qmlProfilerDataModel) { + if (qmlProfilerModelProxy) { startTime = zoomControl.startTime(); endTime = zoomControl.endTime(); - var newRangeX = (startTime - qmlProfilerDataModel.traceStartTime()) * width / qmlProfilerDataModel.traceDuration(); + var newRangeX = (startTime - qmlProfilerModelProxy.traceStartTime()) * width / qmlProfilerModelProxy.traceDuration(); if (rangeMover.x !== newRangeX) rangeMover.x = newRangeX; - var newWidth = (endTime-startTime) * width / qmlProfilerDataModel.traceDuration(); + var newWidth = (endTime-startTime) * width / qmlProfilerModelProxy.traceDuration(); if (rangeMover.width !== newWidth) rangeMover.width = newWidth; } @@ -73,20 +74,18 @@ Canvas2D { } Connections { - target: qmlProfilerDataModel - onStateChanged: { - // State is "done" - if (qmlProfilerDataModel.getCurrentStateFromQml() == 3) { - dataAvailable = true; + target: qmlProfilerModelProxy + onDataAvailable: { + dataReady = true; requestRedraw(); - } } } + // ***** slots onDrawRegion: { - Plotter.qmlProfilerDataModel = qmlProfilerDataModel; - if (dataAvailable) { + Plotter.qmlProfilerModelProxy = qmlProfilerModelProxy; + if (dataReady) { Plotter.plot(canvas, ctxt, region); } else { Plotter.drawGraph(canvas, ctxt, region) //just draw the background @@ -116,7 +115,7 @@ Canvas2D { RangeMover { id: rangeMover - visible: dataAvailable + visible: dataReady } Rectangle { diff --git a/src/plugins/qmlprofiler/qml/RangeDetails.qml b/src/plugins/qmlprofiler/qml/RangeDetails.qml index 965458c690..a0fb3e8d77 100644 --- a/src/plugins/qmlprofiler/qml/RangeDetails.qml +++ b/src/plugins/qmlprofiler/qml/RangeDetails.qml @@ -35,7 +35,7 @@ Item { property string duration property string label - property string type + property string dialogTitle property string file property int line property int column @@ -65,6 +65,36 @@ Item { onCandidateHeightChanged: fitInView(); } + //property variant eventInfo + + ListModel { + id: eventInfo + } + + function showInfo(eventData) { + eventInfo.clear(); + rangeDetails.dialogTitle = eventData[0]["title"]; + for (var i = 1; i < eventData.length; i++) { + for (var k in eventData[i]) { + eventInfo.append({"key": k, "value":eventData[i][k]}); + } + } + rangeDetails.visible = true; + } + + function setLocation(location) { + if (location.hasOwnProperty("file")) { // not empty + file = location.file; + line = location.line; + column = location.column; + } else { + // reset to default values + file = ""; + line = 0; + column = -1; + } + } + function fitInView() { // don't reposition if it does not fit if (root.width < width || root.candidateHeight < height) @@ -122,7 +152,7 @@ Item { //title Text { id: typeTitle - text: " "+rangeDetails.type + text: " "+rangeDetails.dialogTitle font.bold: true height: 18 y: 2 @@ -145,42 +175,14 @@ Item { id: col x: 10 y: 5 - Detail { - label: qsTr("Duration:") - content: rangeDetails.duration < 1000 ? rangeDetails.duration + "μs" : Math.floor(rangeDetails.duration/10)/100 + "ms" - } - Detail { - opacity: content.length !== 0 ? 1 : 0 - label: qsTr("Details:") - content: { - var inputString = rangeDetails.label; - if (inputString.length > 7 && inputString.substring(0,7) == "file://") { - var pos = inputString.lastIndexOf("/"); - return inputString.substr(pos+1); - } - var maxLen = 40; - if (inputString.length > maxLen) - inputString = inputString.substring(0,maxLen)+"..."; - - return inputString; - } - } - Detail { - opacity: content.length !== 0 ? 1 : 0 - label: qsTr("Location:") - content: { - var file = rangeDetails.file; - var pos = file.lastIndexOf("/"); - if (pos != -1) - file = file.substr(pos+1); - return (file.length !== 0) ? (file + ":" + rangeDetails.line) : ""; + + Repeater { + model: eventInfo + Detail { + label: key + content: value } } - Detail { - visible: isBindingLoop - opacity: content.length != 0 ? 1 : 0 - label: qsTr("Binding loop detected") - } } } @@ -194,7 +196,7 @@ Item { drag.maximumY: root.candidateHeight - parent.height + yoffset onClicked: { root.gotoSourceLocation(file, line, column); - root.recenterOnItem(view.selectedItem); + root.recenterOnItem(view.selectedModel, view.selectedItem); } } diff --git a/src/plugins/qmlprofiler/qml/SelectionRange.qml b/src/plugins/qmlprofiler/qml/SelectionRange.qml index cd8c986ddd..b302de614a 100644 --- a/src/plugins/qmlprofiler/qml/SelectionRange.qml +++ b/src/plugins/qmlprofiler/qml/SelectionRange.qml @@ -47,7 +47,7 @@ Rectangle { property string endTimeString: detailedPrintTime(startTime+duration) property string durationString: detailedPrintTime(duration) - property variant startTime: x * selectionRange.viewTimePerPixel + qmlProfilerDataModel.traceStartTime() + property variant startTime: x * selectionRange.viewTimePerPixel + qmlProfilerModelProxy.traceStartTime() property variant duration: width * selectionRange.viewTimePerPixel property variant viewTimePerPixel: 1 property variant creationState : 0 diff --git a/src/plugins/qmlprofiler/qml/TimeMarks.qml b/src/plugins/qmlprofiler/qml/TimeMarks.qml index bb0c07e400..f2ba65f66e 100644 --- a/src/plugins/qmlprofiler/qml/TimeMarks.qml +++ b/src/plugins/qmlprofiler/qml/TimeMarks.qml @@ -48,6 +48,11 @@ Canvas2D { requestRedraw(); } + Connections { + target: labels + onHeightChanged: { requestRedraw(); } + } + onDrawRegion: { drawBackgroundBars( ctxt, region ); @@ -86,19 +91,21 @@ Canvas2D { } } + // gray off out-of-bounds areas var rectWidth; - if (startTime < qmlProfilerDataModel.traceStartTime()) { + if (startTime < qmlProfilerModelProxy.traceStartTime()) { ctxt.fillStyle = "rgba(127,127,127,0.2)"; - rectWidth = (qmlProfilerDataModel.traceStartTime() - startTime) * spacing; + rectWidth = (qmlProfilerModelProxy.traceStartTime() - startTime) * spacing; ctxt.fillRect(0, 0, rectWidth, height); } - if (endTime > qmlProfilerDataModel.traceEndTime()) { + if (endTime > qmlProfilerModelProxy.traceEndTime()) { ctxt.fillStyle = "rgba(127,127,127,0.2)"; - var rectX = (qmlProfilerDataModel.traceEndTime() - startTime) * spacing; - rectWidth = (endTime - qmlProfilerDataModel.traceEndTime()) * spacing; + var rectX = (qmlProfilerModelProxy.traceEndTime() - startTime) * spacing; + rectWidth = (endTime - qmlProfilerModelProxy.traceEndTime()) * spacing; ctxt.fillRect(rectX, 0, rectWidth, height); } + } function updateMarks(start, end) { @@ -121,16 +128,16 @@ Canvas2D { // separators var cumulatedHeight = 0; - for (var i=0; i<labels.rowCount; i++) { - cumulatedHeight += root.singleRowHeight + (labels.rowExpanded[i] ? - qmlProfilerDataModel.uniqueEventsOfType(i) * root.singleRowHeight : - qmlProfilerDataModel.maxNestingForType(i) * root.singleRowHeight); + for (var modelIndex = 0; modelIndex < qmlProfilerModelProxy.modelCount(); modelIndex++) { + for (var i=0; i<qmlProfilerModelProxy.categoryCount(modelIndex); i++) { + cumulatedHeight += root.singleRowHeight * qmlProfilerModelProxy.categoryDepth(modelIndex, i); - ctxt.strokeStyle = "#B0B0B0"; - ctxt.beginPath(); - ctxt.moveTo(0, cumulatedHeight); - ctxt.lineTo(width, cumulatedHeight); - ctxt.stroke(); + ctxt.strokeStyle = "#B0B0B0"; + ctxt.beginPath(); + ctxt.moveTo(0, cumulatedHeight); + ctxt.lineTo(width, cumulatedHeight); + ctxt.stroke(); + } } // bottom diff --git a/src/plugins/qmlprofiler/qmlprofiler.pro b/src/plugins/qmlprofiler/qmlprofiler.pro index d644e47527..c9f7ba7ed7 100644 --- a/src/plugins/qmlprofiler/qmlprofiler.pro +++ b/src/plugins/qmlprofiler/qmlprofiler.pro @@ -12,16 +12,26 @@ SOURCES += \ qmlprofilerattachdialog.cpp \ localqmlprofilerrunner.cpp \ qmlprofilereventview.cpp \ + qv8profilereventview.cpp \ qmlprofilerdetailsrewriter.cpp \ qmlprofilertraceview.cpp \ timelinerenderer.cpp \ qmlprofilerstatemanager.cpp \ qv8profilerdatamodel.cpp \ - qmlprofilerdatamodel.cpp \ qmlprofilerclientmanager.cpp \ qmlprofilerviewmanager.cpp \ qmlprofilerstatewidget.cpp \ - qmlprofilerruncontrolfactory.cpp + qmlprofilerruncontrolfactory.cpp \ + qmlprofilermodelmanager.cpp \ + qmlprofilersimplemodel.cpp \ + qmlprofilerprocessedmodel.cpp \ + qmlprofilereventsmodelproxy.cpp \ + qmlprofilertimelinemodelproxy.cpp \ + qmlprofilertreeview.cpp \ + qmlprofilertracefile.cpp \ + abstracttimelinemodel.cpp \ + timelinemodelaggregator.cpp \ + qmlprofilerpainteventsmodelproxy.cpp HEADERS += \ qmlprofilerconstants.h \ @@ -33,16 +43,26 @@ HEADERS += \ abstractqmlprofilerrunner.h \ localqmlprofilerrunner.h \ qmlprofilereventview.h \ + qv8profilereventview.h \ qmlprofilerdetailsrewriter.h \ qmlprofilertraceview.h \ timelinerenderer.h \ qmlprofilerstatemanager.h \ qv8profilerdatamodel.h \ - qmlprofilerdatamodel.h \ qmlprofilerclientmanager.h \ qmlprofilerviewmanager.h \ qmlprofilerstatewidget.h \ - qmlprofilerruncontrolfactory.h + qmlprofilerruncontrolfactory.h \ + qmlprofilermodelmanager.h \ + qmlprofilersimplemodel.h \ + qmlprofilerprocessedmodel.h \ + qmlprofilereventsmodelproxy.h \ + qmlprofilertimelinemodelproxy.h \ + qmlprofilertreeview.h \ + qmlprofilertracefile.h \ + abstracttimelinemodel.h \ + timelinemodelaggregator.h \ + qmlprofilerpainteventsmodelproxy.h RESOURCES += \ qml/qmlprofiler.qrc diff --git a/src/plugins/qmlprofiler/qmlprofiler.qbs b/src/plugins/qmlprofiler/qmlprofiler.qbs index 047b4f78c5..ca9a4a09dc 100644 --- a/src/plugins/qmlprofiler/qmlprofiler.qbs +++ b/src/plugins/qmlprofiler/qmlprofiler.qbs @@ -22,6 +22,8 @@ QtcPlugin { cpp.includePaths: base.concat("canvas") files: [ + "abstracttimelinemodel.h", + "abstracttimelinemodel.cpp", "abstractqmlprofilerrunner.h", "localqmlprofilerrunner.cpp", "localqmlprofilerrunner.h", @@ -31,32 +33,50 @@ QtcPlugin { "qmlprofilerclientmanager.cpp", "qmlprofilerclientmanager.h", "qmlprofilerconstants.h", - "qmlprofilerdatamodel.cpp", - "qmlprofilerdatamodel.h", "qmlprofilerdetailsrewriter.cpp", "qmlprofilerdetailsrewriter.h", "qmlprofilerengine.cpp", "qmlprofilerengine.h", + "qmlprofilereventsmodelproxy.cpp", + "qmlprofilereventsmodelproxy.h", "qmlprofilereventview.cpp", "qmlprofilereventview.h", + "qmlprofilermodelmanager.cpp", + "qmlprofilermodelmanager.h", "qmlprofilerplugin.cpp", "qmlprofilerplugin.h", "qmlprofilerruncontrolfactory.cpp", "qmlprofilerruncontrolfactory.h", + "qmlprofilerprocessedmodel.cpp", + "qmlprofilerprocessedmodel.h", + "qmlprofilersimplemodel.cpp", + "qmlprofilersimplemodel.h", "qmlprofilerstatemanager.cpp", "qmlprofilerstatemanager.h", "qmlprofilerstatewidget.cpp", "qmlprofilerstatewidget.h", + "qmlprofilertimelinemodelproxy.cpp", + "qmlprofilertimelinemodelproxy.h", "qmlprofilertool.cpp", "qmlprofilertool.h", + "qmlprofilertreeview.cpp", + "qmlprofilertreeview.h", + "qmlprofilertracefile.cpp", + "qmlprofilertracefile.h", "qmlprofilertraceview.cpp", "qmlprofilertraceview.h", "qmlprofilerviewmanager.cpp", "qmlprofilerviewmanager.h", + "qv8profilereventview.h", + "qv8profilereventview.cpp", "qv8profilerdatamodel.cpp", "qv8profilerdatamodel.h", + "timelinemodelaggregator.cpp", + "timelinemodelaggregator.h", "timelinerenderer.cpp", "timelinerenderer.h", + "qmlprofilerpainteventsmodelproxy.h", + "qmlprofilerpainteventsmodelproxy.cpp", "canvas/qdeclarativecanvas.cpp", "canvas/qdeclarativecanvas_p.h", "canvas/qdeclarativecanvastimer.cpp", diff --git a/src/plugins/qmlprofiler/qmlprofilerclientmanager.cpp b/src/plugins/qmlprofiler/qmlprofilerclientmanager.cpp index 5d517e503c..926a382ce7 100644 --- a/src/plugins/qmlprofiler/qmlprofilerclientmanager.cpp +++ b/src/plugins/qmlprofiler/qmlprofilerclientmanager.cpp @@ -40,16 +40,19 @@ #include <QTimer> #include <QMessageBox> +#include "qmlprofilermodelmanager.h" + using namespace QmlDebug; using namespace Core; namespace QmlProfiler { namespace Internal { -class QmlProfilerClientManager::QmlProfilerClientManagerPrivate -{ +class QmlProfilerClientManager::QmlProfilerClientManagerPrivate { public: - QmlProfilerStateManager *profilerState; + QmlProfilerClientManagerPrivate(QmlProfilerClientManager *qq) { Q_UNUSED(qq); } + + QmlProfilerStateManager* profilerState; QmlDebugConnection *connection; QPointer<QmlProfilerTraceClient> qmlclientplugin; @@ -58,7 +61,9 @@ public: QTimer connectionTimer; int connectionAttempts; - enum ConnectMode { TcpConnection, OstConnection }; + enum ConnectMode { + TcpConnection, OstConnection + }; ConnectMode connectMode; QString tcpHost; quint64 tcpPort; @@ -67,10 +72,12 @@ public: bool v8DataReady; bool qmlDataReady; + + QmlProfilerModelManager *modelManager; }; QmlProfilerClientManager::QmlProfilerClientManager(QObject *parent) : - QObject(parent), d(new QmlProfilerClientManagerPrivate) + QObject(parent), d(new QmlProfilerClientManagerPrivate(this)) { setObjectName(QLatin1String("QML Profiler Connections")); @@ -81,6 +88,8 @@ QmlProfilerClientManager::QmlProfilerClientManager(QObject *parent) : d->v8DataReady = false; d->qmlDataReady = false; + d->modelManager = 0; + d->connectionTimer.setInterval(200); connect(&d->connectionTimer, SIGNAL(timeout()), SLOT(tryToConnect())); } @@ -95,6 +104,17 @@ QmlProfilerClientManager::~QmlProfilerClientManager() delete d; } +void QmlProfilerClientManager::setModelManager(QmlProfilerModelManager *m) +{ + if (d->modelManager) { + disconnect(this,SIGNAL(dataReadyForProcessing()), d->modelManager, SLOT(complete())); + } + d->modelManager = m; + if (d->modelManager) { + connect(this,SIGNAL(dataReadyForProcessing()), d->modelManager, SLOT(complete())); + } +} + //////////////////////////////////////////////////////////////// // Interface void QmlProfilerClientManager::setTcpConnection(QString host, quint64 port) @@ -159,15 +179,15 @@ void QmlProfilerClientManager::connectClientSignals() connect(d->qmlclientplugin.data(), SIGNAL(complete()), this, SLOT(qmlComplete())); connect(d->qmlclientplugin.data(), - SIGNAL(range(int,int,qint64,qint64,QStringList,QmlDebug::QmlEventLocation)), - this, - SIGNAL(addRangedEvent(int,int,qint64,qint64,QStringList,QmlDebug::QmlEventLocation))); + SIGNAL(rangedEvent(int,int,qint64,qint64,QStringList,QmlDebug::QmlEventLocation, + qint64,qint64,qint64,qint64,qint64)), + d->modelManager, + SLOT(addQmlEvent(int,int,qint64,qint64,QStringList,QmlDebug::QmlEventLocation, + qint64,qint64,qint64,qint64,qint64))); connect(d->qmlclientplugin.data(), SIGNAL(traceFinished(qint64)), - this, SIGNAL(traceFinished(qint64))); + d->modelManager->traceTime(), SLOT(setEndTime(qint64))); connect(d->qmlclientplugin.data(), SIGNAL(traceStarted(qint64)), - this, SIGNAL(traceStarted(qint64))); - connect(d->qmlclientplugin.data(), SIGNAL(frame(qint64,int,int)), - this, SIGNAL(addFrameEvent(qint64,int,int))); + d->modelManager->traceTime(), SLOT(setStartTime(qint64))); connect(d->qmlclientplugin.data(), SIGNAL(enabledChanged()), d->qmlclientplugin.data(), SLOT(sendRecordingStatus())); // fixme: this should be unified for both clients @@ -178,8 +198,8 @@ void QmlProfilerClientManager::connectClientSignals() connect(d->v8clientplugin.data(), SIGNAL(complete()), this, SLOT(v8Complete())); connect(d->v8clientplugin.data(), SIGNAL(v8range(int,QString,QString,int,double,double)), - this, - SIGNAL(addV8Event(int,QString,QString,int,double,double))); + d->modelManager, + SLOT(addV8Event(int,QString,QString,int,double,double))); connect(d->v8clientplugin.data(), SIGNAL(enabledChanged()), d->v8clientplugin.data(), SLOT(sendRecordingStatus())); } @@ -191,15 +211,17 @@ void QmlProfilerClientManager::disconnectClientSignals() disconnect(d->qmlclientplugin.data(), SIGNAL(complete()), this, SLOT(qmlComplete())); disconnect(d->qmlclientplugin.data(), - SIGNAL(range(int,int,qint64,qint64,QStringList,QmlDebug::QmlEventLocation)), - this, - SIGNAL(addRangedEvent(int,int,qint64,qint64,QStringList,QmlDebug::QmlEventLocation))); + SIGNAL(rangedEvent(int,int,qint64,qint64,QStringList,QmlDebug::QmlEventLocation, + qint64,qint64,qint64,qint64,qint64)), + d->modelManager, + SLOT(addQmlEvent(int,int,qint64,qint64,QStringList,QmlDebug::QmlEventLocation, + qint64,qint64,qint64,qint64,qint64))); disconnect(d->qmlclientplugin.data(), SIGNAL(traceFinished(qint64)), - this, SIGNAL(traceFinished(qint64))); + d->modelManager->traceTime(), SLOT(setEndTime(qint64))); disconnect(d->qmlclientplugin.data(), SIGNAL(traceStarted(qint64)), - this, SIGNAL(traceStarted(qint64))); + d->modelManager->traceTime(), SLOT(setStartTime(qint64))); disconnect(d->qmlclientplugin.data(), SIGNAL(frame(qint64,int,int)), - this, SIGNAL(addFrameEvent(qint64,int,int))); + d->modelManager, SLOT(addFrameEvent(qint64,int,int))); disconnect(d->qmlclientplugin.data(), SIGNAL(enabledChanged()), d->qmlclientplugin.data(), SLOT(sendRecordingStatus())); // fixme: this should be unified for both clients @@ -210,8 +232,8 @@ void QmlProfilerClientManager::disconnectClientSignals() disconnect(d->v8clientplugin.data(), SIGNAL(complete()), this, SLOT(v8Complete())); disconnect(d->v8clientplugin.data(), SIGNAL(v8range(int,QString,QString,int,double,double)), - this, - SIGNAL(addV8Event(int,QString,QString,int,double,double))); + d->modelManager, + SLOT(addV8Event(int,QString,QString,int,double,double))); disconnect(d->v8clientplugin.data(), SIGNAL(enabledChanged()), d->v8clientplugin.data(), SLOT(sendRecordingStatus())); } diff --git a/src/plugins/qmlprofiler/qmlprofilerclientmanager.h b/src/plugins/qmlprofiler/qmlprofilerclientmanager.h index 518445d5f8..c1e2f22214 100644 --- a/src/plugins/qmlprofiler/qmlprofilerclientmanager.h +++ b/src/plugins/qmlprofiler/qmlprofilerclientmanager.h @@ -37,12 +37,13 @@ #include <QStringList> namespace QmlProfiler { +class QmlProfilerModelManager; + namespace Internal { class QmlProfilerClientManager : public QObject { Q_OBJECT - public: explicit QmlProfilerClientManager(QObject *parent = 0); ~QmlProfilerClientManager(); @@ -56,16 +57,10 @@ public: void discardPendingData(); bool isConnected() const; + void setModelManager(QmlProfilerModelManager *m); signals: void connectionFailed(); void connectionClosed(); - - // data - void addRangedEvent(int,int,qint64,qint64,QStringList,QmlDebug::QmlEventLocation); - void addV8Event(int,QString,QString,int,double,double); - void addFrameEvent(qint64,int,int); - void traceStarted(qint64); - void traceFinished(qint64); void dataReadyForProcessing(); public slots: @@ -85,6 +80,9 @@ private slots: void serverRecordingChanged(); private: + class QmlProfilerClientManagerPrivate; + QmlProfilerClientManagerPrivate *d; + void connectToClient(); void enableServices(); @@ -92,12 +90,9 @@ private: void disconnectClientSignals(); void stopClientsRecording(); - - class QmlProfilerClientManagerPrivate; - QmlProfilerClientManagerPrivate *d; }; -} // namespace Internal -} // namespace QmlProfiler +} +} #endif // QMLPROFILERCLIENTMANAGER_H diff --git a/src/plugins/qmlprofiler/qmlprofilerdatamodel.cpp b/src/plugins/qmlprofiler/qmlprofilerdatamodel.cpp deleted file mode 100644 index 640b206d2b..0000000000 --- a/src/plugins/qmlprofiler/qmlprofilerdatamodel.cpp +++ /dev/null @@ -1,1685 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). -** Contact: http://www.qt-project.org/legal -** -** This file is part of Qt Creator. -** -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and Digia. For licensing terms and -** conditions see http://qt.digia.com/licensing. For further information -** use the contact form at http://qt.digia.com/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, 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, Digia gives you certain additional -** rights. These rights are described in the Digia Qt LGPL Exception -** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. -** -****************************************************************************/ - -#include "qmlprofilerdatamodel.h" - -#include <QUrl> -#include <QHash> -#include <QtAlgorithms> -#include <QString> -#include <QStringList> - -#include <QFile> -#include <QXmlStreamReader> -#include <QXmlStreamWriter> - -#include <QTimer> -#include <utils/qtcassert.h> - -using namespace QmlDebug; - -namespace QmlProfiler { -namespace Internal { - -typedef QHash <QString, QmlRangeEventRelative *> EventHash; - -static EventHash cloneEventHash(const EventHash &src) -{ - EventHash result; - const EventHash::ConstIterator cend = src.constEnd(); - for (EventHash::ConstIterator it = src.constBegin(); it != cend; ++it) - result.insert(it.key(), new QmlRangeEventRelative(it.value())); - return result; -} - -/////////////////////////////////////////////////////////// -QmlRangeEventData::QmlRangeEventData() -{ - eventType = MaximumQmlEventType; - eventId = -1; - duration = 0; - calls = 0; - minTime = 0; - maxTime = 0; - timePerCall = 0; - percentOfTime = 0; - medianTime = 0; - isBindingLoop = false; -} - -QmlRangeEventData::~QmlRangeEventData() -{ - qDeleteAll(parentHash); - parentHash.clear(); - qDeleteAll(childrenHash); - childrenHash.clear(); -} - -QmlRangeEventData &QmlRangeEventData::operator=(const QmlRangeEventData &ref) -{ - if (this == &ref) - return *this; - - displayName = ref.displayName; - location = ref.location; - eventHashStr = ref.eventHashStr; - details = ref.details; - eventType = ref.eventType; - duration = ref.duration; - calls = ref.calls; - minTime = ref.minTime; - maxTime = ref.maxTime; - timePerCall = ref.timePerCall; - percentOfTime = ref.percentOfTime; - medianTime = ref.medianTime; - eventId = ref.eventId; - isBindingLoop = ref.isBindingLoop; - - qDeleteAll(parentHash); - parentHash = cloneEventHash(ref.parentHash); - - qDeleteAll(childrenHash); - childrenHash = cloneEventHash(ref.childrenHash); - - return *this; -} - -/////////////////////////////////////////////////////////// - -// endtimedata -struct QmlRangeEventEndInstance { - qint64 endTime; - int startTimeIndex; - QmlRangeEventData *description; -}; - -// starttimedata -struct QmlRangeEventStartInstance { - qint64 startTime; - qint64 duration; - qint64 level; - int endTimeIndex; - qint64 nestingLevel; - qint64 nestingDepth; - QmlRangeEventData *statsInfo; - - int baseEventIndex; - - // animation-related data - int frameRate; - int animationCount; - - int bindingLoopHead; -}; - -struct QmlRangeEventTypeCount { - QVector<int> eventIds; - int nestingCount; -}; - -// used by quicksort -bool compareEndTimes(const QmlRangeEventEndInstance &t1, const QmlRangeEventEndInstance &t2) -{ - return t1.endTime < t2.endTime; -} - -bool compareStartTimes(const QmlRangeEventStartInstance &t1, const QmlRangeEventStartInstance &t2) -{ - return t1.startTime < t2.startTime; -} - -bool compareStartIndexes(const QmlRangeEventEndInstance &t1, const QmlRangeEventEndInstance &t2) -{ - return t1.startTimeIndex < t2.startTimeIndex; -} - -////////////////////////////////////////////////////////////// -class QmlProfilerDataModel::QmlProfilerDataModelPrivate -{ -public: - QmlProfilerDataModelPrivate(QmlProfilerDataModel *qq) : q(qq) {} - - QmlProfilerDataModel *q; - - // convenience functions - void clearQmlRootEvent(); - void insertQmlRootEvent(); - void postProcess(); - void sortEndTimes(); - void findAnimationLimits(); - void sortStartTimes(); - void computeNestingLevels(); - void computeNestingDepth(); - void prepareForDisplay(); - void linkStartsToEnds(); - void linkEndsToStarts(); - - - // stats - void clearStatistics(); - void redoTree(qint64 fromTime, qint64 toTime); - void computeMedianTime(qint64 fromTime, qint64 toTime); - void findBindingLoops(qint64 fromTime, qint64 toTime); - - QmlProfilerDataModel::State listState; - - // Stored data - QHash<QString, QmlRangeEventData *> rangeEventDictionary; - QVector<QmlRangeEventEndInstance> endInstanceList; - QVector<QmlRangeEventStartInstance> startInstanceList; - - QmlRangeEventData qmlRootEvent; - - QV8ProfilerDataModel *v8DataModel; - - QHash<int, QmlRangeEventTypeCount *> typeCounts; - - qint64 traceEndTime; - qint64 traceStartTime; - qint64 qmlMeasuredTime; - - int lastFrameEventIndex; - qint64 maxAnimationCount; - qint64 minAnimationCount; - - // file to load - QString fileName; -}; - - -//////////////////////////////////////////////////////////////////////////////////// - - -QmlProfilerDataModel::QmlProfilerDataModel(QObject *parent) : - QObject(parent), d(new QmlProfilerDataModelPrivate(this)) -{ - setObjectName(QLatin1String("QmlProfilerDataModel")); - - d->listState = Empty; - - d->traceEndTime = 0; - d->traceStartTime = -1; - d->qmlMeasuredTime = 0; - d->clearQmlRootEvent(); - d->lastFrameEventIndex = -1; - d->maxAnimationCount = 0; - d->minAnimationCount = 0; - d->v8DataModel = new QV8ProfilerDataModel(this, this); -} - -QmlProfilerDataModel::~QmlProfilerDataModel() -{ - clear(); - delete d; -} - -//////////////////////////////////////////////////////////////////////////////////// -QList<QmlRangeEventData *> QmlProfilerDataModel::getEventDescriptions() const -{ - return d->rangeEventDictionary.values(); -} - -QmlRangeEventData *QmlProfilerDataModel::eventDescription(int eventId) const -{ - foreach (QmlRangeEventData *event, d->rangeEventDictionary.values()) { - if (event->eventId == eventId) - return event; - } - return 0; -} - -QList<QV8EventData *> QmlProfilerDataModel::getV8Events() const -{ - return d->v8DataModel->getV8Events(); -} - -QV8EventData *QmlProfilerDataModel::v8EventDescription(int eventId) const -{ - return d->v8DataModel->v8EventDescription(eventId); -} - -//////////////////////////////////////////////////////////////////////////////////// - -void QmlProfilerDataModel::clear() -{ - qDeleteAll(d->rangeEventDictionary); - d->rangeEventDictionary.clear(); - - d->endInstanceList.clear(); - d->startInstanceList.clear(); - - d->clearQmlRootEvent(); - - foreach (QmlRangeEventTypeCount *typeCount, d->typeCounts.values()) - delete typeCount; - d->typeCounts.clear(); - - d->traceEndTime = 0; - d->traceStartTime = -1; - d->qmlMeasuredTime = 0; - - d->lastFrameEventIndex = -1; - d->maxAnimationCount = 0; - d->minAnimationCount = 0; - - d->v8DataModel->clear(); - - emit countChanged(); - setState(Empty); -} - -void QmlProfilerDataModel::prepareForWriting() -{ - setState(AcquiringData); -} - -void QmlProfilerDataModel::addRangedEvent(int type, int bindingType, qint64 startTime, - qint64 length, const QStringList &data, - const QmlDebug::QmlEventLocation &location) -{ - const QChar colon = QLatin1Char(':'); - QString displayName, eventHashStr, details; - QmlDebug::QmlEventLocation eventLocation = location; - - setState(AcquiringData); - - // generate details string - if (data.isEmpty()) { - details = tr("Source code not available."); - } else { - details = data.join(QLatin1String(" ")).replace(QLatin1Char('\n'),QLatin1Char(' ')).simplified(); - QRegExp rewrite(QLatin1String("\\(function \\$(\\w+)\\(\\) \\{ (return |)(.+) \\}\\)")); - bool match = rewrite.exactMatch(details); - if (match) - details = rewrite.cap(1) + QLatin1String(": ") + rewrite.cap(3); - if (details.startsWith(QLatin1String("file://"))) - details = details.mid(details.lastIndexOf(QLatin1Char('/')) + 1); - } - - // backwards compatibility: "compiling" events don't have a proper location in older - // version of the protocol, but the filename is passed in the details string - if ((type == QmlDebug::Creating || type == QmlDebug::Compiling) && eventLocation.filename.isEmpty()) { - eventLocation.filename = details; - eventLocation.line = 1; - eventLocation.column = 1; - } - - // generate hash - if (eventLocation.filename.isEmpty()) { - displayName = tr("<bytecode>"); - eventHashStr = getHashStringForQmlEvent(eventLocation, type); - } else { - const QString filePath = QUrl(eventLocation.filename).path(); - displayName = filePath.mid(filePath.lastIndexOf(QLatin1Char('/')) + 1) + colon + - QString::number(eventLocation.line); - eventHashStr = getHashStringForQmlEvent(eventLocation, type); - } - - QmlRangeEventData *newEvent; - if (d->rangeEventDictionary.contains(eventHashStr)) { - newEvent = d->rangeEventDictionary[eventHashStr]; - } else { - newEvent = new QmlRangeEventData; - newEvent->displayName = displayName; - newEvent->location = eventLocation; - newEvent->eventHashStr = eventHashStr; - newEvent->eventType = (QmlDebug::QmlEventType)type; - newEvent->details = details; - newEvent->bindingType = bindingType; - d->rangeEventDictionary.insert(eventHashStr, newEvent); - } - - QmlRangeEventEndInstance endTimeData; - endTimeData.endTime = startTime + length; - endTimeData.description = newEvent; - endTimeData.startTimeIndex = d->startInstanceList.count(); - - QmlRangeEventStartInstance startTimeData; - startTimeData.startTime = startTime; - startTimeData.duration = length; - startTimeData.statsInfo = newEvent; - startTimeData.endTimeIndex = d->endInstanceList.count(); - startTimeData.animationCount = -1; - startTimeData.frameRate = 1e9/length; - startTimeData.baseEventIndex = d->startInstanceList.count(); // point to itself by default - - d->endInstanceList << endTimeData; - d->startInstanceList << startTimeData; - - emit countChanged(); -} - -void QmlProfilerDataModel::addV8Event(int depth, const QString &function, - const QString &filename, int lineNumber, - double totalTime, double selfTime) -{ - d->v8DataModel->addV8Event(depth, function, filename, lineNumber, totalTime, selfTime); -} - -void QmlProfilerDataModel::addFrameEvent(qint64 time, int framerate, int animationcount) -{ - QString displayName, eventHashStr, details; - - setState(AcquiringData); - - details = tr("Animation Timer Update"); - displayName = tr("<Animation Update>"); - eventHashStr = displayName; - - QmlRangeEventData *newEvent; - if (d->rangeEventDictionary.contains(eventHashStr)) { - newEvent = d->rangeEventDictionary[eventHashStr]; - } else { - newEvent = new QmlRangeEventData; - newEvent->displayName = displayName; - newEvent->eventHashStr = eventHashStr; - newEvent->eventType = QmlDebug::Painting; - newEvent->details = details; - d->rangeEventDictionary.insert(eventHashStr, newEvent); - } - - qint64 length = 1e9/framerate; - // avoid overlap - QmlRangeEventStartInstance *lastFrameEvent = 0; - if (d->lastFrameEventIndex > -1) { - lastFrameEvent = &d->startInstanceList[d->lastFrameEventIndex]; - if (lastFrameEvent->startTime + lastFrameEvent->duration >= time) { - lastFrameEvent->duration = time - 1 - lastFrameEvent->startTime; - d->endInstanceList[lastFrameEvent->endTimeIndex].endTime = - lastFrameEvent->startTime + lastFrameEvent->duration; - } - } - - QmlRangeEventEndInstance endTimeData; - endTimeData.endTime = time + length; - endTimeData.description = newEvent; - endTimeData.startTimeIndex = d->startInstanceList.count(); - - QmlRangeEventStartInstance startTimeData; - startTimeData.startTime = time; - startTimeData.duration = length; - startTimeData.statsInfo = newEvent; - startTimeData.endTimeIndex = d->endInstanceList.count(); - startTimeData.animationCount = animationcount; - startTimeData.frameRate = framerate; - startTimeData.baseEventIndex = d->startInstanceList.count(); // point to itself by default - - d->endInstanceList << endTimeData; - d->startInstanceList << startTimeData; - - d->lastFrameEventIndex = d->startInstanceList.count() - 1; - - emit countChanged(); -} - -void QmlProfilerDataModel::setTraceEndTime(qint64 time) -{ - d->traceEndTime = time; -} - -void QmlProfilerDataModel::setTraceStartTime(qint64 time) -{ - d->traceStartTime = time; -} - -//////////////////////////////////////////////////////////////////////////////////// - -QString QmlProfilerDataModel::getHashStringForQmlEvent( - const QmlDebug::QmlEventLocation &location, int eventType) -{ - return QString::fromLatin1("%1:%2:%3:%4").arg(location.filename, - QString::number(location.line), - QString::number(location.column), - QString::number(eventType)); -} - -QString QmlProfilerDataModel::getHashStringForV8Event(const QString &displayName, - const QString &function) -{ - return QString::fromLatin1("%1:%2").arg(displayName, function); -} - -QString QmlProfilerDataModel::rootEventName() -{ - return tr("<program>"); -} - -QString QmlProfilerDataModel::rootEventDescription() -{ - return tr("Main Program"); -} - -QString QmlProfilerDataModel::qmlEventTypeAsString(QmlEventType typeEnum) -{ - switch (typeEnum) { - case Painting: - return QLatin1String(Constants::TYPE_PAINTING_STR); - break; - case Compiling: - return QLatin1String(Constants::TYPE_COMPILING_STR); - break; - case Creating: - return QLatin1String(Constants::TYPE_CREATING_STR); - break; - case Binding: - return QLatin1String(Constants::TYPE_BINDING_STR); - break; - case HandlingSignal: - return QLatin1String(Constants::TYPE_HANDLINGSIGNAL_STR); - break; - default: - return QString::number((int)typeEnum); - } -} - -QmlEventType QmlProfilerDataModel::qmlEventTypeAsEnum(const QString &typeString) -{ - if (typeString == QLatin1String(Constants::TYPE_PAINTING_STR)) { - return Painting; - } else if (typeString == QLatin1String(Constants::TYPE_COMPILING_STR)) { - return Compiling; - } else if (typeString == QLatin1String(Constants::TYPE_CREATING_STR)) { - return Creating; - } else if (typeString == QLatin1String(Constants::TYPE_BINDING_STR)) { - return Binding; - } else if (typeString == QLatin1String(Constants::TYPE_HANDLINGSIGNAL_STR)) { - return HandlingSignal; - } else { - bool isNumber = false; - int type = typeString.toUInt(&isNumber); - if (isNumber) - return (QmlEventType)type; - else - return MaximumQmlEventType; - } -} - -//////////////////////////////////////////////////////////////////////////////////// - -int QmlProfilerDataModel::findFirstIndex(qint64 startTime) const -{ - int candidate = -1; - // in the "endtime" list, find the first event that ends after startTime - if (d->endInstanceList.isEmpty()) - return 0; // -1 - if (d->endInstanceList.count() == 1 || d->endInstanceList.first().endTime >= startTime) - candidate = 0; - else - if (d->endInstanceList.last().endTime <= startTime) - return 0; // -1 - - if (candidate == -1) - { - int fromIndex = 0; - int toIndex = d->endInstanceList.count()-1; - while (toIndex - fromIndex > 1) { - int midIndex = (fromIndex + toIndex)/2; - if (d->endInstanceList[midIndex].endTime < startTime) - fromIndex = midIndex; - else - toIndex = midIndex; - } - - candidate = toIndex; - } - - int eventIndex = d->endInstanceList[candidate].startTimeIndex; - return d->startInstanceList[eventIndex].baseEventIndex; -} - -int QmlProfilerDataModel::findFirstIndexNoParents(qint64 startTime) const -{ - int candidate = -1; - // in the "endtime" list, find the first event that ends after startTime - if (d->endInstanceList.isEmpty()) - return 0; // -1 - if (d->endInstanceList.count() == 1 || d->endInstanceList.first().endTime >= startTime) - candidate = 0; - else - if (d->endInstanceList.last().endTime <= startTime) - return 0; // -1 - - if (candidate == -1) { - int fromIndex = 0; - int toIndex = d->endInstanceList.count()-1; - while (toIndex - fromIndex > 1) { - int midIndex = (fromIndex + toIndex)/2; - if (d->endInstanceList[midIndex].endTime < startTime) - fromIndex = midIndex; - else - toIndex = midIndex; - } - - candidate = toIndex; - } - - int ndx = d->endInstanceList[candidate].startTimeIndex; - - return ndx; -} - -int QmlProfilerDataModel::findLastIndex(qint64 endTime) const -{ - // in the "starttime" list, find the last event that starts before endtime - if (d->startInstanceList.isEmpty()) - return 0; // -1 - if (d->startInstanceList.first().startTime >= endTime) - return 0; // -1 - if (d->startInstanceList.count() == 1) - return 0; - if (d->startInstanceList.last().startTime <= endTime) - return d->startInstanceList.count()-1; - - int fromIndex = 0; - int toIndex = d->startInstanceList.count()-1; - while (toIndex - fromIndex > 1) { - int midIndex = (fromIndex + toIndex)/2; - if (d->startInstanceList[midIndex].startTime < endTime) - fromIndex = midIndex; - else - toIndex = midIndex; - } - - return fromIndex; -} - -qint64 QmlProfilerDataModel::firstTimeMark() const -{ - if (d->startInstanceList.isEmpty()) - return 0; - return d->startInstanceList[0].startTime; -} - -qint64 QmlProfilerDataModel::lastTimeMark() const -{ - if (d->endInstanceList.isEmpty()) - return 0; - return d->endInstanceList.last().endTime; -} - -//////////////////////////////////////////////////////////////////////////////////// - -int QmlProfilerDataModel::count() const -{ - return d->startInstanceList.count(); -} - -bool QmlProfilerDataModel::isEmpty() const -{ - return d->startInstanceList.isEmpty() && d->v8DataModel->isEmpty(); -} - -qint64 QmlProfilerDataModel::getStartTime(int index) const -{ - return d->startInstanceList[index].startTime; -} - -qint64 QmlProfilerDataModel::getEndTime(int index) const -{ - return d->startInstanceList[index].startTime + d->startInstanceList[index].duration; -} - -qint64 QmlProfilerDataModel::getDuration(int index) const -{ - return d->startInstanceList[index].duration; -} - -int QmlProfilerDataModel::getType(int index) const -{ - return d->startInstanceList[index].statsInfo->eventType; -} - -int QmlProfilerDataModel::getNestingLevel(int index) const -{ - return d->startInstanceList[index].nestingLevel; -} - -int QmlProfilerDataModel::getNestingDepth(int index) const -{ - return d->startInstanceList[index].nestingDepth; -} - -QString QmlProfilerDataModel::getFilename(int index) const -{ - return d->startInstanceList[index].statsInfo->location.filename; -} - -int QmlProfilerDataModel::getLine(int index) const -{ - return d->startInstanceList[index].statsInfo->location.line; -} - -int QmlProfilerDataModel::getColumn(int index) const -{ - return d->startInstanceList[index].statsInfo->location.column; -} - -QString QmlProfilerDataModel::getDetails(int index) const -{ - // special: animations - if (d->startInstanceList[index].statsInfo->eventType == QmlDebug::Painting && - d->startInstanceList[index].animationCount >= 0) - return tr("%1 animations at %2 FPS.").arg( - QString::number(d->startInstanceList[index].animationCount), - QString::number(d->startInstanceList[index].frameRate)); - return d->startInstanceList[index].statsInfo->details; -} - -int QmlProfilerDataModel::getEventId(int index) const -{ - return d->startInstanceList[index].statsInfo->eventId; -} - -int QmlProfilerDataModel::getBindingLoopDest(int index) const -{ - return d->startInstanceList[index].bindingLoopHead; -} - -int QmlProfilerDataModel::getFramerate(int index) const -{ - return d->startInstanceList[index].frameRate; -} - -int QmlProfilerDataModel::getAnimationCount(int index) const -{ - return d->startInstanceList[index].animationCount; -} - -int QmlProfilerDataModel::getMaximumAnimationCount() const -{ - return d->maxAnimationCount; -} - -int QmlProfilerDataModel::getMinimumAnimationCount() const -{ - return d->minAnimationCount; -} - -///////////////////////////////////////// - -int QmlProfilerDataModel::uniqueEventsOfType(int type) const -{ - if (!d->typeCounts.contains(type)) - return 0; - return d->typeCounts[type]->eventIds.count(); -} - -int QmlProfilerDataModel::maxNestingForType(int type) const -{ - if (!d->typeCounts.contains(type)) - return 0; - return d->typeCounts[type]->nestingCount; -} - -QString QmlProfilerDataModel::eventTextForType(int type, int index) const -{ - if (!d->typeCounts.contains(type)) - return QString(); - return d->rangeEventDictionary.values().at(d->typeCounts[type]->eventIds[index])->details; -} - -QString QmlProfilerDataModel::eventDisplayNameForType(int type, int index) const -{ - if (!d->typeCounts.contains(type)) - return QString(); - return d->rangeEventDictionary.values().at(d->typeCounts[type]->eventIds[index])->displayName; -} - -int QmlProfilerDataModel::eventIdForType(int type, int index) const -{ - if (!d->typeCounts.contains(type)) - return -1; - return d->typeCounts[type]->eventIds[index]; -} - -int QmlProfilerDataModel::eventPosInType(int index) const -{ - int eventType = d->startInstanceList[index].statsInfo->eventType; - return d->typeCounts[eventType]->eventIds.indexOf(d->startInstanceList[index].statsInfo->eventId); -} - -///////////////////////////////////////// - -qint64 QmlProfilerDataModel::traceStartTime() const -{ - return d->traceStartTime != -1? d->traceStartTime : firstTimeMark(); -} - -qint64 QmlProfilerDataModel::traceEndTime() const -{ - return d->traceEndTime ? d->traceEndTime : lastTimeMark(); -} - -qint64 QmlProfilerDataModel::traceDuration() const -{ - return traceEndTime() - traceStartTime(); -} - -qint64 QmlProfilerDataModel::qmlMeasuredTime() const -{ - return d->qmlMeasuredTime; -} -qint64 QmlProfilerDataModel::v8MeasuredTime() const -{ - return d->v8DataModel->v8MeasuredTime(); -} - -//////////////////////////////////////////////////////////////////////////////////// - -void QmlProfilerDataModel::complete() -{ - if (currentState() == AcquiringData) { - setState(ProcessingData); - d->v8DataModel->collectV8Statistics(); - d->postProcess(); - } else - if (currentState() == Empty) { - d->v8DataModel->collectV8Statistics(); - compileStatistics(traceStartTime(), traceEndTime()); - setState(Done); - } else - if (currentState() == Done) { - // ignore duplicated complete signals - } else { - emit error(tr("Unexpected complete signal in data model.")); - } -} - -void QmlProfilerDataModel::QmlProfilerDataModelPrivate::postProcess() -{ - if (q->count() != 0) { - sortStartTimes(); - sortEndTimes(); - findAnimationLimits(); - computeNestingLevels(); - computeNestingDepth(); - linkEndsToStarts(); - insertQmlRootEvent(); - q->reloadDetails(); - prepareForDisplay(); - q->compileStatistics(q->traceStartTime(), q->traceEndTime()); - } - q->setState(Done); -} - - -void QmlProfilerDataModel::QmlProfilerDataModelPrivate::prepareForDisplay() -{ - // generate numeric ids - int ndx = 0; - foreach (QmlRangeEventData *binding, rangeEventDictionary.values()) { - binding->eventId = ndx++; - } - - // collect type counts - foreach (const QmlRangeEventStartInstance &eventStartData, startInstanceList) { - int typeNumber = eventStartData.statsInfo->eventType; - if (!typeCounts.contains(typeNumber)) { - typeCounts[typeNumber] = new QmlRangeEventTypeCount; - typeCounts[typeNumber]->nestingCount = 0; - } - if (eventStartData.nestingLevel > typeCounts[typeNumber]->nestingCount) - typeCounts[typeNumber]->nestingCount = eventStartData.nestingLevel; - if (!typeCounts[typeNumber]->eventIds.contains(eventStartData.statsInfo->eventId)) - typeCounts[typeNumber]->eventIds << eventStartData.statsInfo->eventId; - } -} - -void QmlProfilerDataModel::QmlProfilerDataModelPrivate::sortStartTimes() -{ - if (startInstanceList.count() < 2) - return; - - // assuming startTimes is partially sorted - // identify blocks of events and sort them with quicksort - QVector<QmlRangeEventStartInstance>::iterator itFrom = startInstanceList.end() - 2; - QVector<QmlRangeEventStartInstance>::iterator itTo = startInstanceList.end() - 1; - - while (itFrom != startInstanceList.begin() && itTo != startInstanceList.begin()) { - // find block to sort - while (itFrom != startInstanceList.begin() - && itTo->startTime > itFrom->startTime) { - itTo--; - itFrom = itTo - 1; - } - - // if we're at the end of the list - if (itFrom == startInstanceList.begin()) - break; - - // find block length - while (itFrom != startInstanceList.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 - linkEndsToStarts(); -} - -void QmlProfilerDataModel::QmlProfilerDataModelPrivate::sortEndTimes() -{ - // assuming endTimes is partially sorted - // identify blocks of events and sort them with quicksort - - if (endInstanceList.count() < 2) - return; - - QVector<QmlRangeEventEndInstance>::iterator itFrom = endInstanceList.begin(); - QVector<QmlRangeEventEndInstance>::iterator itTo = endInstanceList.begin() + 1; - - while (itTo != endInstanceList.end() && itFrom != endInstanceList.end()) { - // find block to sort - while (itTo != endInstanceList.end() - && startInstanceList[itTo->startTimeIndex].startTime > - startInstanceList[itFrom->startTimeIndex].startTime + - startInstanceList[itFrom->startTimeIndex].duration) { - itFrom++; - itTo = itFrom+1; - } - - // if we're at the end of the list - if (itTo == endInstanceList.end()) - break; - - // find block length - while (itTo != endInstanceList.end() - && startInstanceList[itTo->startTimeIndex].startTime <= - startInstanceList[itFrom->startTimeIndex].startTime + - startInstanceList[itFrom->startTimeIndex].duration) - itTo++; - - // sort block - qSort(itFrom, itTo, compareEndTimes); - - // move to next block - itFrom = itTo; - itTo = itFrom+1; - - } - - // link back the startTimes - linkStartsToEnds(); -} - -void QmlProfilerDataModel::QmlProfilerDataModelPrivate::linkStartsToEnds() -{ - for (int i = 0; i < endInstanceList.count(); i++) - startInstanceList[endInstanceList[i].startTimeIndex].endTimeIndex = i; -} - -void QmlProfilerDataModel::QmlProfilerDataModelPrivate::findAnimationLimits() -{ - maxAnimationCount = 0; - minAnimationCount = 0; - lastFrameEventIndex = -1; - - for (int i = 0; i < startInstanceList.count(); i++) { - if (startInstanceList[i].statsInfo->eventType == QmlDebug::Painting && - startInstanceList[i].animationCount >= 0) { - int animationcount = startInstanceList[i].animationCount; - if (lastFrameEventIndex > -1) { - if (animationcount > maxAnimationCount) - maxAnimationCount = animationcount; - if (animationcount < minAnimationCount) - minAnimationCount = animationcount; - } else { - maxAnimationCount = animationcount; - minAnimationCount = animationcount; - } - lastFrameEventIndex = i; - } - } -} - -void QmlProfilerDataModel::QmlProfilerDataModelPrivate::computeNestingLevels() -{ - // compute levels - QHash<int, qint64> endtimesPerLevel; - QList<int> nestingLevels; - QList< QHash<int, qint64> > endtimesPerNestingLevel; - int level = Constants::QML_MIN_LEVEL; - endtimesPerLevel[Constants::QML_MIN_LEVEL] = 0; - int lastBaseEventIndex = 0; - qint64 lastBaseEventEndTime = traceStartTime; - - for (int i = 0; i < QmlDebug::MaximumQmlEventType; i++) { - nestingLevels << Constants::QML_MIN_LEVEL; - QHash<int, qint64> dummyHash; - dummyHash[Constants::QML_MIN_LEVEL] = 0; - endtimesPerNestingLevel << dummyHash; - } - - for (int i=0; i<startInstanceList.count(); i++) { - qint64 st = startInstanceList[i].startTime; - int type = startInstanceList[i].statsInfo->eventType; - - if (type == QmlDebug::Painting) { - // animation/paint events have level 0 by definition (same as "mainprogram"), - // but are not considered parents of other events for statistical purposes - startInstanceList[i].level = Constants::QML_MIN_LEVEL - 1; - startInstanceList[i].nestingLevel = Constants::QML_MIN_LEVEL; - if (lastBaseEventEndTime < startInstanceList[i].startTime) { - lastBaseEventIndex = i; - lastBaseEventEndTime = startInstanceList[i].startTime + startInstanceList[i].duration; - } - startInstanceList[i].baseEventIndex = lastBaseEventIndex; - continue; - } - - // general level - if (endtimesPerLevel[level] > st) { - level++; - } else { - while (level > Constants::QML_MIN_LEVEL && endtimesPerLevel[level-1] <= st) - level--; - } - endtimesPerLevel[level] = st + startInstanceList[i].duration; - - // per type - if (endtimesPerNestingLevel[type][nestingLevels[type]] > st) { - nestingLevels[type]++; - } else { - while (nestingLevels[type] > Constants::QML_MIN_LEVEL && - endtimesPerNestingLevel[type][nestingLevels[type]-1] <= st) - nestingLevels[type]--; - } - endtimesPerNestingLevel[type][nestingLevels[type]] = - st + startInstanceList[i].duration; - - startInstanceList[i].level = level; - startInstanceList[i].nestingLevel = nestingLevels[type]; - - if (level == Constants::QML_MIN_LEVEL) { - qmlMeasuredTime += startInstanceList[i].duration; - if (lastBaseEventEndTime < startInstanceList[i].startTime) { - lastBaseEventIndex = i; - lastBaseEventEndTime = startInstanceList[i].startTime + startInstanceList[i].duration; - } - } - startInstanceList[i].baseEventIndex = lastBaseEventIndex; - } -} - -void QmlProfilerDataModel::QmlProfilerDataModelPrivate::computeNestingDepth() -{ - QHash<int, int> nestingDepth; - for (int i = 0; i < endInstanceList.count(); i++) { - int type = endInstanceList[i].description->eventType; - int nestingInType = startInstanceList[endInstanceList[i].startTimeIndex].nestingLevel; - if (!nestingDepth.contains(type)) { - nestingDepth[type] = nestingInType; - } else { - int nd = nestingDepth[type]; - nestingDepth[type] = nd > nestingInType ? nd : nestingInType; - } - - startInstanceList[endInstanceList[i].startTimeIndex].nestingDepth = nestingDepth[type]; - if (nestingInType == Constants::QML_MIN_LEVEL) - nestingDepth[type] = Constants::QML_MIN_LEVEL; - } -} - -void QmlProfilerDataModel::QmlProfilerDataModelPrivate::linkEndsToStarts() -{ - for (int i = 0; i < startInstanceList.count(); i++) - endInstanceList[startInstanceList[i].endTimeIndex].startTimeIndex = i; -} - -//////////////////////////////////////////////////////////////////////////////////// - -void QmlProfilerDataModel::compileStatistics(qint64 startTime, qint64 endTime) -{ - d->clearStatistics(); - if (traceDuration() > 0) { - if (count() > 0) { - d->redoTree(startTime, endTime); - d->computeMedianTime(startTime, endTime); - d->findBindingLoops(startTime, endTime); - } else { - d->insertQmlRootEvent(); - QmlRangeEventData *listedRootEvent = d->rangeEventDictionary.value(rootEventName()); - listedRootEvent->calls = 1; - listedRootEvent->percentOfTime = 100; - } - } -} - -void QmlProfilerDataModel::QmlProfilerDataModelPrivate::clearStatistics() -{ - // clear existing statistics - foreach (QmlRangeEventData *eventDescription, rangeEventDictionary.values()) { - eventDescription->calls = 0; - // maximum possible value - eventDescription->minTime = traceEndTime; - eventDescription->maxTime = 0; - eventDescription->medianTime = 0; - eventDescription->duration = 0; - qDeleteAll(eventDescription->parentHash); - qDeleteAll(eventDescription->childrenHash); - eventDescription->parentHash.clear(); - eventDescription->childrenHash.clear(); - } -} - -void QmlProfilerDataModel::QmlProfilerDataModelPrivate::redoTree(qint64 fromTime, - qint64 toTime) -{ - double totalTime = 0; - int fromIndex = q->findFirstIndex(fromTime); - int toIndex = q->findLastIndex(toTime); - - QmlRangeEventData *listedRootEvent = rangeEventDictionary.value(rootEventName()); - QTC_ASSERT(listedRootEvent, /**/); - - // compute parent-child relationship and call count - QHash<int, QmlRangeEventData*> lastParent; - for (int index = fromIndex; index <= toIndex; index++) { - QmlRangeEventData *eventDescription = startInstanceList[index].statsInfo; - - if (startInstanceList[index].startTime > toTime || - startInstanceList[index].startTime+startInstanceList[index].duration < fromTime) { - continue; - } - - if (eventDescription->eventType == QmlDebug::Painting) { - // skip animation/paint events - continue; - } - - eventDescription->calls++; - qint64 duration = startInstanceList[index].duration; - eventDescription->duration += duration; - if (eventDescription->maxTime < duration) - eventDescription->maxTime = duration; - if (eventDescription->minTime > duration) - eventDescription->minTime = duration; - - int level = startInstanceList[index].level; - - QmlRangeEventData *parentEvent = listedRootEvent; - if (level > Constants::QML_MIN_LEVEL && lastParent.contains(level-1)) - parentEvent = lastParent[level-1]; - - if (!eventDescription->parentHash.contains(parentEvent->eventHashStr)) { - QmlRangeEventRelative *newParentEvent = new QmlRangeEventRelative(parentEvent); - newParentEvent->calls = 1; - newParentEvent->duration = duration; - - eventDescription->parentHash.insert(parentEvent->eventHashStr, newParentEvent); - } else { - QmlRangeEventRelative *newParentEvent = - eventDescription->parentHash.value(parentEvent->eventHashStr); - newParentEvent->duration += duration; - newParentEvent->calls++; - } - - if (!parentEvent->childrenHash.contains(eventDescription->eventHashStr)) { - QmlRangeEventRelative *newChildEvent = new QmlRangeEventRelative(eventDescription); - newChildEvent->calls = 1; - newChildEvent->duration = duration; - - parentEvent->childrenHash.insert(eventDescription->eventHashStr, newChildEvent); - } else { - QmlRangeEventRelative *newChildEvent = - parentEvent->childrenHash.value(eventDescription->eventHashStr); - newChildEvent->duration += duration; - newChildEvent->calls++; - } - - lastParent[level] = eventDescription; - - if (level == Constants::QML_MIN_LEVEL) - totalTime += duration; - } - - // fake rootEvent statistics - // the +1 nanosecond is to force it to be on top of the sorted list - listedRootEvent->duration = totalTime+1; - listedRootEvent->minTime = totalTime+1; - listedRootEvent->maxTime = totalTime+1; - listedRootEvent->medianTime = totalTime+1; - if (totalTime > 0) - listedRootEvent->calls = 1; - - // copy to the global root reference - qmlRootEvent = *listedRootEvent; - - // compute percentages - foreach (QmlRangeEventData *binding, rangeEventDictionary.values()) { - binding->percentOfTime = binding->duration * 100.0 / totalTime; - binding->timePerCall = binding->calls > 0 ? double(binding->duration) / binding->calls : 0; - } -} - -void QmlProfilerDataModel::QmlProfilerDataModelPrivate::computeMedianTime(qint64 fromTime, - qint64 toTime) -{ - int fromIndex = q->findFirstIndex(fromTime); - int toIndex = q->findLastIndex(toTime); - - // compute median time - QHash< QmlRangeEventData* , QList<qint64> > durationLists; - for (int index = fromIndex; index <= toIndex; index++) { - QmlRangeEventData *desc = startInstanceList[index].statsInfo; - qint64 len = startInstanceList[index].duration; - durationLists[desc].append(len); - } - QMutableHashIterator < QmlRangeEventData* , QList<qint64> > iter(durationLists); - while (iter.hasNext()) { - iter.next(); - if (!iter.value().isEmpty()) { - qSort(iter.value()); - iter.key()->medianTime = iter.value().at(iter.value().count()/2); - } - } -} - -void QmlProfilerDataModel::QmlProfilerDataModelPrivate::findBindingLoops(qint64 fromTime, - qint64 toTime) -{ - int fromIndex = q->findFirstIndex(fromTime); - int toIndex = q->findLastIndex(toTime); - - // first clear existing data - foreach (QmlRangeEventData *event, rangeEventDictionary.values()) { - event->isBindingLoop = false; - foreach (QmlRangeEventRelative *parentEvent, event->parentHash.values()) - parentEvent->inLoopPath = false; - foreach (QmlRangeEventRelative *childEvent, event->childrenHash.values()) - childEvent->inLoopPath = false; - } - - QList<QmlRangeEventData *> stackRefs; - QList<QmlRangeEventStartInstance *> stack; - - for (int i = 0; i < startInstanceList.count(); i++) { - QmlRangeEventData *currentEvent = startInstanceList[i].statsInfo; - QmlRangeEventStartInstance *inTimeEvent = &startInstanceList[i]; - inTimeEvent->bindingLoopHead = -1; - - // special: skip animation events (but not old paint events) - if (inTimeEvent->statsInfo->eventType == Painting && inTimeEvent->animationCount >= 0) - continue; - - // managing call stack - for (int j = stack.count() - 1; j >= 0; j--) { - if (stack[j]->startTime + stack[j]->duration <= inTimeEvent->startTime) { - stack.removeAt(j); - stackRefs.removeAt(j); - } - } - - bool loopDetected = stackRefs.contains(currentEvent); - stack << inTimeEvent; - stackRefs << currentEvent; - - // skip loops if bindings are anonymous - if (loopDetected && !currentEvent->location.filename.isEmpty()) { - if (i >= fromIndex && i <= toIndex) { - // for the statistics - currentEvent->isBindingLoop = true; - for (int j = stackRefs.indexOf(currentEvent); j < stackRefs.count()-1; j++) { - QmlRangeEventRelative *nextEventSub = - stackRefs[j]->childrenHash.value(stackRefs[j+1]->eventHashStr); - nextEventSub->inLoopPath = true; - QmlRangeEventRelative *prevEventSub = - stackRefs[j+1]->parentHash.value(stackRefs[j]->eventHashStr); - prevEventSub->inLoopPath = true; - } - } - - // use crossed references to find index in starttimesortedlist - QmlRangeEventStartInstance *head = stack[stackRefs.indexOf(currentEvent)]; - inTimeEvent->bindingLoopHead = endInstanceList[head->endTimeIndex].startTimeIndex; - startInstanceList[inTimeEvent->bindingLoopHead].bindingLoopHead = i; - } - } -} - -//////////////////////////////////////////////////////////////////////////////////// - -void QmlProfilerDataModel::QmlProfilerDataModelPrivate::clearQmlRootEvent() -{ - qmlRootEvent.displayName = rootEventName(); - qmlRootEvent.location = QmlEventLocation(); - qmlRootEvent.eventHashStr = rootEventName(); - qmlRootEvent.details = rootEventDescription(); - qmlRootEvent.eventType = QmlDebug::Binding; - qmlRootEvent.duration = 0; - qmlRootEvent.calls = 0; - qmlRootEvent.minTime = 0; - qmlRootEvent.maxTime = 0; - qmlRootEvent.timePerCall = 0; - qmlRootEvent.percentOfTime = 0; - qmlRootEvent.medianTime = 0; - qmlRootEvent.eventId = -1; - - qDeleteAll(qmlRootEvent.parentHash); - qDeleteAll(qmlRootEvent.childrenHash); - qmlRootEvent.parentHash.clear(); - qmlRootEvent.childrenHash.clear(); -} - -void QmlProfilerDataModel::QmlProfilerDataModelPrivate::insertQmlRootEvent() -{ - // create root event for statistics & insert into list - clearQmlRootEvent(); - QmlRangeEventData *listedRootEvent = rangeEventDictionary.value(rootEventName()); - if (!listedRootEvent) { - listedRootEvent = new QmlRangeEventData; - rangeEventDictionary.insert(rootEventName(), listedRootEvent); - } - *listedRootEvent = qmlRootEvent; -} - -void QmlProfilerDataModel::reloadDetails() -{ - // request binding/signal details from the AST - foreach (QmlRangeEventData *event, d->rangeEventDictionary.values()) { - if (event->eventType != Binding && event->eventType != HandlingSignal) - continue; - - // This skips anonymous bindings in Qt4.8 (we don't have valid location data for them) - if (event->location.filename.isEmpty()) - continue; - - // Skip non-anonymous bindings from Qt4.8 (we already have correct details for them) - if (event->location.column == -1) - continue; - - emit requestDetailsForLocation(event->eventType, event->location); - } - emit reloadDocumentsForDetails(); -} - -void QmlProfilerDataModel::rewriteDetailsString(int eventType, - const QmlDebug::QmlEventLocation &location, - const QString &newString) -{ - QString eventHashStr = getHashStringForQmlEvent(location, eventType); - QTC_ASSERT(d->rangeEventDictionary.contains(eventHashStr), return); - d->rangeEventDictionary.value(eventHashStr)->details = newString; - emit detailsChanged(d->rangeEventDictionary.value(eventHashStr)->eventId, newString); -} - -void QmlProfilerDataModel::finishedRewritingDetails() -{ - emit reloadDetailLabels(); -} - -//////////////////////////////////////////////////////////////////////////////////// - -bool QmlProfilerDataModel::save(const QString &filename) -{ - if (isEmpty()) { - emit error(tr("No data to save.")); - return false; - } - - QFile file(filename); - if (!file.open(QIODevice::WriteOnly)) { - emit error(tr("Could not open %1 for writing.").arg(filename)); - return false; - } - - QXmlStreamWriter stream(&file); - stream.setAutoFormatting(true); - stream.writeStartDocument(); - - stream.writeStartElement(QLatin1String("trace")); - stream.writeAttribute(QLatin1String("version"), QLatin1String(Constants::PROFILER_FILE_VERSION)); - - stream.writeAttribute(QLatin1String("traceStart"), QString::number(traceStartTime())); - stream.writeAttribute(QLatin1String("traceEnd"), QString::number(traceEndTime())); - - stream.writeStartElement(QLatin1String("eventData")); - stream.writeAttribute(QLatin1String("totalTime"), QString::number(d->qmlMeasuredTime)); - - foreach (const QmlRangeEventData *eventData, d->rangeEventDictionary.values()) { - stream.writeStartElement(QLatin1String("event")); - stream.writeAttribute(QLatin1String("index"), QString::number(d->rangeEventDictionary.keys().indexOf(eventData->eventHashStr))); - stream.writeTextElement(QLatin1String("displayname"), eventData->displayName); - stream.writeTextElement(QLatin1String("type"), qmlEventTypeAsString(eventData->eventType)); - if (!eventData->location.filename.isEmpty()) { - stream.writeTextElement(QLatin1String("filename"), eventData->location.filename); - stream.writeTextElement(QLatin1String("line"), QString::number(eventData->location.line)); - stream.writeTextElement(QLatin1String("column"), QString::number(eventData->location.column)); - } - stream.writeTextElement(QLatin1String("details"), eventData->details); - if (eventData->eventType == Binding) - stream.writeTextElement(QLatin1String("bindingType"), QString::number((int)eventData->bindingType)); - stream.writeEndElement(); - } - stream.writeEndElement(); // eventData - - stream.writeStartElement(QLatin1String("profilerDataModel")); - foreach (const QmlRangeEventStartInstance &rangedEvent, d->startInstanceList) { - stream.writeStartElement(QLatin1String("range")); - stream.writeAttribute(QLatin1String("startTime"), QString::number(rangedEvent.startTime)); - stream.writeAttribute(QLatin1String("duration"), QString::number(rangedEvent.duration)); - stream.writeAttribute(QLatin1String("eventIndex"), QString::number(d->rangeEventDictionary.keys().indexOf(rangedEvent.statsInfo->eventHashStr))); - if (rangedEvent.statsInfo->eventType == QmlDebug::Painting && rangedEvent.animationCount >= 0) { - // animation frame - stream.writeAttribute(QLatin1String("framerate"), QString::number(rangedEvent.frameRate)); - stream.writeAttribute(QLatin1String("animationcount"), QString::number(rangedEvent.animationCount)); - } - stream.writeEndElement(); - } - stream.writeEndElement(); // profilerDataModel - - d->v8DataModel->save(stream); - - stream.writeEndElement(); // trace - stream.writeEndDocument(); - - file.close(); - return true; -} - -void QmlProfilerDataModel::setFilename(const QString &filename) -{ - d->fileName = filename; -} - -void QmlProfilerDataModel::load(const QString &filename) -{ - setFilename(filename); - load(); -} - -// "be strict in your output but tolerant in your inputs" -void QmlProfilerDataModel::load() -{ - QString filename = d->fileName; - - QFile file(filename); - - if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { - emit error(tr("Could not open %1 for reading.").arg(filename)); - return; - } - - // erase current - clear(); - - setState(AcquiringData); - - bool readingQmlEvents = false; - QHash<int, QmlRangeEventData *> descriptionBuffer; - QmlRangeEventData *currentEvent = 0; - bool startTimesAreSorted = true; - bool validVersion = true; - - // time computation - d->qmlMeasuredTime = 0; - - QXmlStreamReader stream(&file); - - while (validVersion && !stream.atEnd() && !stream.hasError()) { - QXmlStreamReader::TokenType token = stream.readNext(); - QString elementName = stream.name().toString(); - switch (token) { - case QXmlStreamReader::StartDocument : continue; - case QXmlStreamReader::StartElement : { - if (elementName == QLatin1String("trace")) { - QXmlStreamAttributes attributes = stream.attributes(); - if (attributes.hasAttribute(QLatin1String("version"))) - validVersion = attributes.value(QLatin1String("version")).toString() == QLatin1String(Constants::PROFILER_FILE_VERSION); - else - validVersion = false; - if (attributes.hasAttribute(QLatin1String("traceStart"))) - setTraceStartTime(attributes.value(QLatin1String("traceStart")).toString().toLongLong()); - if (attributes.hasAttribute(QLatin1String("traceEnd"))) - setTraceEndTime(attributes.value(QLatin1String("traceEnd")).toString().toLongLong()); - } - if (elementName == QLatin1String("eventData")) { - readingQmlEvents = true; - QXmlStreamAttributes attributes = stream.attributes(); - if (attributes.hasAttribute(QLatin1String("totalTime"))) - d->qmlMeasuredTime = attributes.value(QLatin1String("totalTime")).toString().toDouble(); - break; - } - if (elementName == QLatin1String("v8profile") && !readingQmlEvents) { - d->v8DataModel->load(stream); - break; - } - - if (elementName == QLatin1String("trace")) { - QXmlStreamAttributes attributes = stream.attributes(); - if (attributes.hasAttribute(QLatin1String("traceStart"))) - setTraceStartTime(attributes.value(QLatin1String("traceStart")).toString().toLongLong()); - if (attributes.hasAttribute(QLatin1String("traceEnd"))) - setTraceEndTime(attributes.value(QLatin1String("traceEnd")).toString().toLongLong()); - } - - if (elementName == QLatin1String("range")) { - QmlRangeEventStartInstance rangedEvent; - QXmlStreamAttributes attributes = stream.attributes(); - if (attributes.hasAttribute(QLatin1String("startTime"))) - rangedEvent.startTime = attributes.value(QLatin1String("startTime")).toString().toLongLong(); - if (attributes.hasAttribute(QLatin1String("duration"))) - rangedEvent.duration = attributes.value(QLatin1String("duration")).toString().toLongLong(); - if (attributes.hasAttribute(QLatin1String("framerate"))) - rangedEvent.frameRate = attributes.value(QLatin1String("framerate")).toString().toInt(); - if (attributes.hasAttribute(QLatin1String("animationcount"))) - rangedEvent.animationCount = attributes.value(QLatin1String("animationcount")).toString().toInt(); - else - rangedEvent.animationCount = -1; - if (attributes.hasAttribute(QLatin1String("eventIndex"))) { - int ndx = attributes.value(QLatin1String("eventIndex")).toString().toInt(); - if (!descriptionBuffer.value(ndx)) - descriptionBuffer[ndx] = new QmlRangeEventData; - rangedEvent.statsInfo = descriptionBuffer.value(ndx); - } - rangedEvent.endTimeIndex = d->endInstanceList.count(); - - if (!d->startInstanceList.isEmpty() - && rangedEvent.startTime < d->startInstanceList.last().startTime) - startTimesAreSorted = false; - d->startInstanceList << rangedEvent; - - QmlRangeEventEndInstance endTimeEvent; - endTimeEvent.endTime = rangedEvent.startTime + rangedEvent.duration; - endTimeEvent.startTimeIndex = d->startInstanceList.count()-1; - endTimeEvent.description = rangedEvent.statsInfo; - d->endInstanceList << endTimeEvent; - break; - } - - if (readingQmlEvents) { - if (elementName == QLatin1String("event")) { - QXmlStreamAttributes attributes = stream.attributes(); - if (attributes.hasAttribute(QLatin1String("index"))) { - int ndx = attributes.value(QLatin1String("index")).toString().toInt(); - if (!descriptionBuffer.value(ndx)) - descriptionBuffer[ndx] = new QmlRangeEventData; - currentEvent = descriptionBuffer[ndx]; - // backwards compatibility: default bindingType - currentEvent->bindingType = QmlBinding; - } else { - currentEvent = 0; - } - break; - } - - // the remaining are eventdata or v8eventdata elements - if (!currentEvent) - break; - - stream.readNext(); - if (stream.tokenType() != QXmlStreamReader::Characters) - break; - QString readData = stream.text().toString(); - - if (elementName == QLatin1String("displayname")) { - currentEvent->displayName = readData; - break; - } - if (elementName == QLatin1String("type")) { - currentEvent->eventType = qmlEventTypeAsEnum(readData); - break; - } - if (elementName == QLatin1String("filename")) { - currentEvent->location.filename = readData; - break; - } - if (elementName == QLatin1String("line")) { - currentEvent->location.line = readData.toInt(); - break; - } - if (elementName == QLatin1String("column")) - currentEvent->location.column = readData.toInt(); - if (elementName == QLatin1String("details")) { - currentEvent->details = readData; - break; - } - if (elementName == QLatin1String("bindingType")) { - currentEvent->bindingType = readData.toInt(); - break; - } - } - break; - } - case QXmlStreamReader::EndElement : { - if (elementName == QLatin1String("event")) { - currentEvent = 0; - break; - } - if (elementName == QLatin1String("eventData")) { - readingQmlEvents = false; - break; - } - } - default: break; - } - } - - file.close(); - - if (stream.hasError()) { - emit error(tr("Error while parsing %1.").arg(filename)); - clear(); - return; - } - - stream.clear(); - - if (!validVersion) { - clear(); - emit error(tr("Invalid version of QML Trace file.")); - return; - } - - // move the buffered data to the details cache - foreach (QmlRangeEventData *desc, descriptionBuffer.values()) { - desc->eventHashStr = getHashStringForQmlEvent(desc->location, desc->eventType);; - d->rangeEventDictionary[desc->eventHashStr] = desc; - } - - // sort startTimeSortedList - if (!startTimesAreSorted) { - qSort(d->startInstanceList.begin(), d->startInstanceList.end(), compareStartTimes); - for (int i = 0; i< d->startInstanceList.count(); i++) { - QmlRangeEventStartInstance startTimeData = d->startInstanceList[i]; - d->endInstanceList[startTimeData.endTimeIndex].startTimeIndex = i; - } - qSort(d->endInstanceList.begin(), d->endInstanceList.end(), compareStartIndexes); - } - - emit countChanged(); - - descriptionBuffer.clear(); - - setState(ProcessingData); - d->postProcess(); -} - -//////////////////////////////////////////////////////////////////////////////////// - -QmlProfilerDataModel::State QmlProfilerDataModel::currentState() const -{ - return d->listState; -} - -int QmlProfilerDataModel::getCurrentStateFromQml() const -{ - return (int)d->listState; -} - -void QmlProfilerDataModel::setState(QmlProfilerDataModel::State state) -{ - // It's not an error, we are continuously calling "AcquiringData" for example - if (d->listState == state) - return; - - switch (state) { - case Empty: - // if it's not empty, complain but go on - QTC_ASSERT(isEmpty(), /**/); - break; - case AcquiringData: - // we're not supposed to receive new data while processing older data - QTC_ASSERT(d->listState != ProcessingData, return); - break; - case ProcessingData: - QTC_ASSERT(d->listState == AcquiringData, return); - break; - case Done: - QTC_ASSERT(d->listState == ProcessingData || d->listState == Empty, return); - break; - default: - emit error(tr("Trying to set unknown state in events list.")); - break; - } - - d->listState = state; - emit stateChanged(); - - return; -} - -} // namespace Internal -} // namespace QmlProfiler - -QT_BEGIN_NAMESPACE -Q_DECLARE_TYPEINFO(QmlProfiler::Internal::QmlRangeEventData, Q_MOVABLE_TYPE); -Q_DECLARE_TYPEINFO(QmlProfiler::Internal::QmlRangeEventStartInstance, Q_MOVABLE_TYPE); -Q_DECLARE_TYPEINFO(QmlProfiler::Internal::QmlRangeEventEndInstance, Q_MOVABLE_TYPE); -Q_DECLARE_TYPEINFO(QmlProfiler::Internal::QmlRangeEventRelative, Q_MOVABLE_TYPE); -QT_END_NAMESPACE diff --git a/src/plugins/qmlprofiler/qmlprofilerdatamodel.h b/src/plugins/qmlprofiler/qmlprofilerdatamodel.h deleted file mode 100644 index 0eb31b43f2..0000000000 --- a/src/plugins/qmlprofiler/qmlprofilerdatamodel.h +++ /dev/null @@ -1,201 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). -** Contact: http://www.qt-project.org/legal -** -** This file is part of Qt Creator. -** -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and Digia. For licensing terms and -** conditions see http://qt.digia.com/licensing. For further information -** use the contact form at http://qt.digia.com/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, 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, Digia gives you certain additional -** rights. These rights are described in the Digia Qt LGPL Exception -** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. -** -****************************************************************************/ - -#ifndef QMLPROFILERDATAMODEL_H -#define QMLPROFILERDATAMODEL_H - -#include <qmldebug/qmlprofilereventtypes.h> -#include <qmldebug/qmlprofilereventlocation.h> -#include "qv8profilerdatamodel.h" - -#include <QHash> -#include <QObject> - -namespace QmlProfiler { -namespace Internal { - -// used for parents and children -struct QmlRangeEventRelative; - -struct QmlRangeEventData -{ - QmlRangeEventData(); - ~QmlRangeEventData(); - - int eventId; - int bindingType; - QString displayName; - QString eventHashStr; - QString details; - QmlDebug::QmlEventLocation location; - QmlDebug::QmlEventType eventType; - - bool isBindingLoop; - - QHash <QString, QmlRangeEventRelative *> parentHash; - QHash <QString, QmlRangeEventRelative *> childrenHash; - - qint64 duration; - qint64 calls; - qint64 minTime; - qint64 maxTime; - double timePerCall; - double percentOfTime; - qint64 medianTime; - - QmlRangeEventData &operator=(const QmlRangeEventData &ref); -}; - -struct QmlRangeEventRelative { - QmlRangeEventRelative(QmlRangeEventData *from) : reference(from), duration(0), calls(0), inLoopPath(false) {} - QmlRangeEventRelative(QmlRangeEventRelative *from) : reference(from->reference), duration(from->duration), calls(from->calls), inLoopPath(from->inLoopPath) {} - QmlRangeEventData *reference; - qint64 duration; - qint64 calls; - bool inLoopPath; -}; - -class QmlProfilerDataModel : public QObject -{ - Q_OBJECT -public: - enum State { - Empty, - AcquiringData, - ProcessingData, - Done - }; - - explicit QmlProfilerDataModel(QObject *parent = 0); - ~QmlProfilerDataModel(); - - QList<QmlRangeEventData *> getEventDescriptions() const; - QmlRangeEventData *eventDescription(int eventId) const; - QList<QV8EventData *> getV8Events() const; - QV8EventData *v8EventDescription(int eventId) const; - - static QString getHashStringForQmlEvent(const QmlDebug::QmlEventLocation &location, int eventType); - static QString getHashStringForV8Event(const QString &displayName, const QString &function); - static QString rootEventName(); - static QString rootEventDescription(); - static QString qmlEventTypeAsString(QmlDebug::QmlEventType typeEnum); - static QmlDebug::QmlEventType qmlEventTypeAsEnum(const QString &typeString); - - int findFirstIndex(qint64 startTime) const; - int findFirstIndexNoParents(qint64 startTime) const; - int findLastIndex(qint64 endTime) const; - Q_INVOKABLE qint64 firstTimeMark() const; - Q_INVOKABLE qint64 lastTimeMark() const; - - // data access - Q_INVOKABLE int count() const; - Q_INVOKABLE bool isEmpty() const; - Q_INVOKABLE qint64 getStartTime(int index) const; - Q_INVOKABLE qint64 getEndTime(int index) const; - Q_INVOKABLE qint64 getDuration(int index) const; - Q_INVOKABLE int getType(int index) const; - Q_INVOKABLE int getNestingLevel(int index) const; - Q_INVOKABLE int getNestingDepth(int index) const; - Q_INVOKABLE QString getFilename(int index) const; - Q_INVOKABLE int getLine(int index) const; - Q_INVOKABLE int getColumn(int index) const; - Q_INVOKABLE QString getDetails(int index) const; - Q_INVOKABLE int getEventId(int index) const; - Q_INVOKABLE int getBindingLoopDest(int index) const; - Q_INVOKABLE int getFramerate(int index) const; - Q_INVOKABLE int getAnimationCount(int index) const; - Q_INVOKABLE int getMaximumAnimationCount() const; - Q_INVOKABLE int getMinimumAnimationCount() const; - - - // per-type data - Q_INVOKABLE int uniqueEventsOfType(int type) const; - Q_INVOKABLE int maxNestingForType(int type) const; - Q_INVOKABLE QString eventTextForType(int type, int index) const; - Q_INVOKABLE QString eventDisplayNameForType(int type, int index) const; - Q_INVOKABLE int eventIdForType(int type, int index) const; - Q_INVOKABLE int eventPosInType(int index) const; - - Q_INVOKABLE qint64 traceStartTime() const; - Q_INVOKABLE qint64 traceEndTime() const; - Q_INVOKABLE qint64 traceDuration() const; - Q_INVOKABLE qint64 qmlMeasuredTime() const; - Q_INVOKABLE qint64 v8MeasuredTime() const; - - void compileStatistics(qint64 startTime, qint64 endTime); - State currentState() const; - Q_INVOKABLE int getCurrentStateFromQml() const; - -signals: - void stateChanged(); - void countChanged(); - void error(const QString &error); - - void requestDetailsForLocation(int eventType, const QmlDebug::QmlEventLocation &location); - void detailsChanged(int eventId, const QString &newString); - void reloadDetailLabels(); - void reloadDocumentsForDetails(); - -public slots: - void clear(); - - void prepareForWriting(); - void addRangedEvent(int type, int bindingType, qint64 startTime, qint64 length, - const QStringList &data, const QmlDebug::QmlEventLocation &location); - void addV8Event(int depth,const QString &function,const QString &filename, int lineNumber, double totalTime, double selfTime); - void addFrameEvent(qint64 time, int framerate, int animationcount); - void setTraceStartTime(qint64 time); - void setTraceEndTime(qint64 time); - - void complete(); - - bool save(const QString &filename); - void load(const QString &filename); - void setFilename(const QString &filename); - void load(); - - void rewriteDetailsString(int eventType, const QmlDebug::QmlEventLocation &location, const QString &newString); - void finishedRewritingDetails(); - -private: - void setState(State state); - void reloadDetails(); - -private: - class QmlProfilerDataModelPrivate; - QmlProfilerDataModelPrivate *d; - - friend class QV8ProfilerDataModel; -}; - - -} // namespace Internal -} // namespace QmlProfiler - -#endif // QMLPROFILERDATAMODEL_H diff --git a/src/plugins/qmlprofiler/qmlprofilerdetailsrewriter.cpp b/src/plugins/qmlprofiler/qmlprofilerdetailsrewriter.cpp index 2385e42c1c..0b078f9c86 100644 --- a/src/plugins/qmlprofiler/qmlprofilerdetailsrewriter.cpp +++ b/src/plugins/qmlprofiler/qmlprofilerdetailsrewriter.cpp @@ -41,7 +41,7 @@ namespace Internal { struct PendingEvent { QmlDebug::QmlEventLocation location; QString localFile; - int eventType; + int requestId; }; class PropertyVisitor: protected QmlJS::AST::Visitor @@ -122,7 +122,7 @@ QmlProfilerDetailsRewriter::~QmlProfilerDetailsRewriter() delete d; } -void QmlProfilerDetailsRewriter::requestDetailsForLocation(int type, +void QmlProfilerDetailsRewriter::requestDetailsForLocation(int requestId, const QmlDebug::QmlEventLocation &location) { const QString localFile = d->m_projectFinder->findFile(location.filename); @@ -132,7 +132,7 @@ void QmlProfilerDetailsRewriter::requestDetailsForLocation(int type, if (!QmlJS::Document::isQmlLikeLanguage(QmlJSTools::languageOfFile(localFile))) return; - PendingEvent ev = {location, localFile, type}; + PendingEvent ev = {location, localFile, requestId}; d->m_pendingEvents << ev; if (!d->m_pendingDocs.contains(localFile)) { if (d->m_pendingDocs.isEmpty()) @@ -154,7 +154,7 @@ void QmlProfilerDetailsRewriter::reloadDocuments() } void QmlProfilerDetailsRewriter::rewriteDetailsForLocation(QTextStream &textDoc, - QmlJS::Document::Ptr doc, int type, const QmlDebug::QmlEventLocation &location) + QmlJS::Document::Ptr doc, int requestId, const QmlDebug::QmlEventLocation &location) { PropertyVisitor propertyVisitor; QmlJS::AST::Node *node = propertyVisitor(doc->ast(), location.line, location.column); @@ -168,7 +168,12 @@ void QmlProfilerDetailsRewriter::rewriteDetailsForLocation(QTextStream &textDoc, textDoc.seek(startPos); QString details = textDoc.read(len).replace(QLatin1Char('\n'), QLatin1Char(' ')).simplified(); - emit rewriteDetailsString(type, location, details); + emit rewriteDetailsString(requestId, details); +} + +void QmlProfilerDetailsRewriter::clearRequests() +{ + d->m_pendingDocs.clear(); } void QmlProfilerDetailsRewriter::documentReady(QmlJS::Document::Ptr doc) @@ -186,7 +191,7 @@ void QmlProfilerDetailsRewriter::documentReady(QmlJS::Document::Ptr doc) PendingEvent ev = d->m_pendingEvents[i]; if (ev.localFile == doc->fileName()) { d->m_pendingEvents.removeAt(i); - rewriteDetailsForLocation(st, doc, ev.eventType, ev.location); + rewriteDetailsForLocation(st, doc, ev.requestId, ev.location); } } } diff --git a/src/plugins/qmlprofiler/qmlprofilerdetailsrewriter.h b/src/plugins/qmlprofiler/qmlprofilerdetailsrewriter.h index 2ec65606d2..21e94a1bf3 100644 --- a/src/plugins/qmlprofiler/qmlprofilerdetailsrewriter.h +++ b/src/plugins/qmlprofiler/qmlprofilerdetailsrewriter.h @@ -46,16 +46,18 @@ public: explicit QmlProfilerDetailsRewriter(QObject *parent, Utils::FileInProjectFinder *fileFinder); ~QmlProfilerDetailsRewriter(); + void clearRequests(); + private: - void rewriteDetailsForLocation(QTextStream &textDoc,QmlJS::Document::Ptr doc, int type, + void rewriteDetailsForLocation(QTextStream &textDoc, QmlJS::Document::Ptr doc, int requestId, const QmlDebug::QmlEventLocation &location); + public slots: - void requestDetailsForLocation(int type, const QmlDebug::QmlEventLocation &location); + void requestDetailsForLocation(int requestId, const QmlDebug::QmlEventLocation &location); void reloadDocuments(); void documentReady(QmlJS::Document::Ptr doc); signals: - void rewriteDetailsString(int type, const QmlDebug::QmlEventLocation &location, - const QString &details); + void rewriteDetailsString(int requestId, const QString &details); void eventDetailsChanged(); private: class QmlProfilerDetailsRewriterPrivate; diff --git a/src/plugins/qmlprofiler/qmlprofilerengine.cpp b/src/plugins/qmlprofiler/qmlprofilerengine.cpp index 392588d5a9..0dff45f4d5 100644 --- a/src/plugins/qmlprofiler/qmlprofilerengine.cpp +++ b/src/plugins/qmlprofiler/qmlprofilerengine.cpp @@ -82,7 +82,7 @@ QmlProfilerRunControl::QmlProfilerRunControl(const Analyzer::AnalyzerStartParame { d->m_profilerState = 0; - // Only wait 4 seconds for the 'Waiting for connection' on application ouput, then just try to connect + // Only wait 4 seconds for the 'Waiting for connection' on application output, then just try to connect // (application output might be redirected / blocked) d->m_noDebugOutputTimer.setSingleShot(true); d->m_noDebugOutputTimer.setInterval(4000); diff --git a/src/plugins/qmlprofiler/qmlprofilereventsmodelproxy.cpp b/src/plugins/qmlprofiler/qmlprofilereventsmodelproxy.cpp new file mode 100644 index 0000000000..d1be7cfdb3 --- /dev/null +++ b/src/plugins/qmlprofiler/qmlprofilereventsmodelproxy.cpp @@ -0,0 +1,479 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, 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, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + +#include "qmlprofilereventsmodelproxy.h" +#include "qmlprofilermodelmanager.h" +#include "qmlprofilersimplemodel.h" + +#include <utils/qtcassert.h> + +#include <QVector> +#include <QHash> +#include <QUrl> +#include <QString> +#include <QStack> +#include <QElapsedTimer> + +#include <QDebug> + +namespace QmlProfiler { +namespace Internal { + +class QmlProfilerEventsModelProxy::QmlProfilerEventsModelProxyPrivate +{ +public: + QmlProfilerEventsModelProxyPrivate(QmlProfilerEventsModelProxy *qq) : q(qq) {} + ~QmlProfilerEventsModelProxyPrivate() {} + + QHash<QString, QmlProfilerEventsModelProxy::QmlEventStats> data; + + QmlProfilerModelManager *modelManager; + QmlProfilerEventsModelProxy *q; + + int modelId; + + QVector<int> acceptedTypes; + QSet<QString> eventsInBindingLoop; +}; + +QmlProfilerEventsModelProxy::QmlProfilerEventsModelProxy(QmlProfilerModelManager *modelManager, QObject *parent) + : QObject(parent), d(new QmlProfilerEventsModelProxyPrivate(this)) +{ + d->modelManager = modelManager; + connect(modelManager->simpleModel(), SIGNAL(changed()), this, SLOT(dataChanged())); + d->modelId = modelManager->registerModelProxy(); + + d->acceptedTypes << QmlDebug::Compiling << QmlDebug::Creating << QmlDebug::Binding << QmlDebug::HandlingSignal; +} + +QmlProfilerEventsModelProxy::~QmlProfilerEventsModelProxy() +{ + delete d; +} + +const QList<QmlProfilerEventsModelProxy::QmlEventStats> QmlProfilerEventsModelProxy::getData() const +{ + return d->data.values(); +} + +void QmlProfilerEventsModelProxy::clear() +{ + d->modelManager->modelProxyCountUpdated(d->modelId, 0, 1); + d->data.clear(); + d->eventsInBindingLoop.clear(); +} + +void QmlProfilerEventsModelProxy::limitToRange(qint64 rangeStart, qint64 rangeEnd) +{ + loadData(rangeStart, rangeEnd); +} + +void QmlProfilerEventsModelProxy::dataChanged() +{ + if (d->modelManager->state() == QmlProfilerDataState::ProcessingData) + loadData(); + + if (d->modelManager->state() == QmlProfilerDataState::Empty) + clear(); +} + +QSet<QString> QmlProfilerEventsModelProxy::eventsInBindingLoop() const +{ + return d->eventsInBindingLoop; +} + +void QmlProfilerEventsModelProxy::loadData(qint64 rangeStart, qint64 rangeEnd) +{ + clear(); + + qint64 qmlTime = 0; + qint64 lastEndTime = 0; + QHash <QString, QVector<qint64> > durations; + + const bool checkRanges = (rangeStart != -1) && (rangeEnd != -1); + + const QVector<QmlProfilerSimpleModel::QmlEventData> eventList + = d->modelManager->simpleModel()->getEvents(); + + // used by binding loop detection + typedef QPair<QString, const QmlProfilerSimpleModel::QmlEventData*> CallStackEntry; + QStack<CallStackEntry> callStack; + callStack.push(CallStackEntry(QString(), 0)); // artificial root + + for (int i = 0; i < eventList.size(); ++i) { + const QmlProfilerSimpleModel::QmlEventData *event = &eventList[i]; + + if (!d->acceptedTypes.contains(event->eventType)) + continue; + + if (checkRanges) { + if ((event->startTime + event->duration < rangeStart) + || (event->startTime > rangeEnd)) + continue; + } + + // put event in hash + QString hash = QmlProfilerSimpleModel::getHashString(*event); + if (!d->data.contains(hash)) { + QmlEventStats stats = { + event->displayName, + hash, + event->data.join(QLatin1String(" ")), + event->location, + event->eventType, + event->bindingType, + event->duration, + 1, //calls + event->duration, //minTime + event->duration, // maxTime + 0, //timePerCall + 0, //percentOfTime + 0, //medianTime + false //isBindingLoop + }; + + d->data.insert(hash, stats); + + // for median computing + durations.insert(hash, QVector<qint64>()); + durations[hash].append(event->duration); + } else { + // update stats + QmlEventStats *stats = &d->data[hash]; + + stats->duration += event->duration; + if (event->duration < stats->minTime) + stats->minTime = event->duration; + if (event->duration > stats->maxTime) + stats->maxTime = event->duration; + stats->calls++; + + // for median computing + durations[hash].append(event->duration); + } + + // qml time computation + if (event->startTime > lastEndTime) { // assume parent event if starts before last end + qmlTime += event->duration; + lastEndTime = event->startTime + event->duration; + } + + + // + // binding loop detection + // + const QmlProfilerSimpleModel::QmlEventData *potentialParent = callStack.top().second; + while (potentialParent + && !(potentialParent->startTime + potentialParent->duration > event->startTime)) { + callStack.pop(); + potentialParent = callStack.top().second; + } + + // check whether event is already in stack + bool inLoop = false; + for (int ii = 1; ii < callStack.size(); ++ii) { + if (callStack.at(ii).first == hash) + inLoop = true; + if (inLoop) + d->eventsInBindingLoop.insert(hash); + } + + + CallStackEntry newEntry(hash, event); + callStack.push(newEntry); + + d->modelManager->modelProxyCountUpdated(d->modelId, i, eventList.count()*2); + } + + // post-process: calc mean time, median time, percentoftime + foreach (const QString &hash, d->data.keys()) { + QmlEventStats* stats = &d->data[hash]; + if (stats->calls > 0) + stats->timePerCall = stats->duration / (double)stats->calls; + + QVector<qint64> eventDurations = durations.value(hash); + if (!eventDurations.isEmpty()) { + qSort(eventDurations); + stats->medianTime = eventDurations.at(eventDurations.count()/2); + } + + stats->percentOfTime = stats->duration * 100.0 / qmlTime; + } + + // set binding loop flag + foreach (const QString &eventHash, d->eventsInBindingLoop) + d->data[eventHash].isBindingLoop = true; + + QString rootEventName = tr("<program>"); + QmlDebug::QmlEventLocation rootEventLocation(rootEventName, 1, 1); + + // insert root event + QmlEventStats rootEvent = { + rootEventName, //event.displayName, + rootEventName, // hash + tr("Main Program"), //event.details, + rootEventLocation, // location + (int)QmlDebug::Binding, // event type + 0, // binding type + qmlTime + 1, + 1, //calls + qmlTime + 1, //minTime + qmlTime + 1, // maxTime + qmlTime + 1, //timePerCall + 100.0, //percentOfTime + qmlTime + 1, //medianTime; + false + }; + + d->data.insert(rootEventName, rootEvent); + + d->modelManager->modelProxyCountUpdated(d->modelId, 1, 1); + emit dataAvailable(); +} + +int QmlProfilerEventsModelProxy::count() const +{ + return d->data.count(); +} + +////////////////////////////////////////////////////////////////////////////////// +QmlProfilerEventRelativesModelProxy::QmlProfilerEventRelativesModelProxy(QmlProfilerModelManager *modelManager, + QmlProfilerEventsModelProxy *eventsModel, + QObject *parent) + : QObject(parent) +{ + QTC_CHECK(modelManager); + m_modelManager = modelManager; + connect(modelManager->simpleModel(), SIGNAL(changed()), this, SLOT(dataChanged())); + + QTC_CHECK(eventsModel); + m_eventsModel = eventsModel; + + m_acceptedTypes << QmlDebug::Compiling << QmlDebug::Creating << QmlDebug::Binding << QmlDebug::HandlingSignal; +} + +QmlProfilerEventRelativesModelProxy::~QmlProfilerEventRelativesModelProxy() +{ +} + +const QmlProfilerEventRelativesModelProxy::QmlEventRelativesMap QmlProfilerEventRelativesModelProxy::getData(const QString &hash) const +{ + if (m_data.contains(hash)) + return m_data[hash]; + return QmlEventRelativesMap(); +} + +int QmlProfilerEventRelativesModelProxy::count() const +{ + return m_data.count(); +} + +void QmlProfilerEventRelativesModelProxy::clear() +{ + m_data.clear(); +} + +void QmlProfilerEventRelativesModelProxy::dataChanged() +{ + loadData(); + + emit dataAvailable(); +} + + +////////////////////////////////////////////////////////////////////////////////// +QmlProfilerEventParentsModelProxy::QmlProfilerEventParentsModelProxy(QmlProfilerModelManager *modelManager, + QmlProfilerEventsModelProxy *eventsModel, + QObject *parent) + : QmlProfilerEventRelativesModelProxy(modelManager, eventsModel, parent) +{} + +QmlProfilerEventParentsModelProxy::~QmlProfilerEventParentsModelProxy() +{} + +void QmlProfilerEventParentsModelProxy::loadData() +{ + clear(); + QmlProfilerSimpleModel *simpleModel = m_modelManager->simpleModel(); + if (simpleModel->isEmpty()) + return; + + QHash<QString, QmlProfilerSimpleModel::QmlEventData> cachedEvents; + QString rootEventName = tr("<program>"); + QmlProfilerSimpleModel::QmlEventData rootEvent = { + rootEventName, + QmlDebug::Binding, + 0, + 0, + 0, + QStringList() << tr("Main Program"), + QmlDebug::QmlEventLocation(rootEventName, 0, 0), + 0,0,0,0,0 // numericData fields + }; + cachedEvents.insert(rootEventName, rootEvent); + + // for level computation + QHash<int, qint64> endtimesPerLevel; + int level = QmlDebug::Constants::QML_MIN_LEVEL; + endtimesPerLevel[0] = 0; + + const QSet<QString> eventsInBindingLoop = m_eventsModel->eventsInBindingLoop(); + + // compute parent-child relationship and call count + QHash<int, QString> lastParent; + //for (int index = fromIndex; index <= toIndex; index++) { + const QVector<QmlProfilerSimpleModel::QmlEventData> eventList = simpleModel->getEvents(); + foreach (const QmlProfilerSimpleModel::QmlEventData &event, eventList) { + // whitelist + if (!m_acceptedTypes.contains(event.eventType)) + continue; + + // level computation + if (endtimesPerLevel[level] > event.startTime) { + level++; + } else { + while (level > QmlDebug::Constants::QML_MIN_LEVEL && endtimesPerLevel[level-1] <= event.startTime) + level--; + } + endtimesPerLevel[level] = event.startTime + event.duration; + + + QString parentHash = rootEventName; + QString eventHash = QmlProfilerSimpleModel::getHashString(event); + + // save in cache + if (!cachedEvents.contains(eventHash)) + cachedEvents.insert(eventHash, event); + + if (level > QmlDebug::Constants::QML_MIN_LEVEL && lastParent.contains(level-1)) + parentHash = lastParent[level-1]; + + QmlProfilerSimpleModel::QmlEventData *parentEvent = &(cachedEvents[parentHash]); + + // generate placeholder if needed + if (!m_data.contains(eventHash)) + m_data.insert(eventHash, QmlEventRelativesMap()); + + if (m_data[eventHash].contains(parentHash)) { + QmlEventRelativesData *parent = &(m_data[eventHash][parentHash]); + parent->calls++; + parent->duration += event.duration; + } else { + m_data[eventHash].insert(parentHash, QmlEventRelativesData()); + QmlEventRelativesData *parent = &(m_data[eventHash][parentHash]); + parent->displayName = parentEvent->displayName; + parent->eventType = parentEvent->eventType; + parent->duration = event.duration; + parent->calls = 1; + parent->details = parentEvent->data.join(QLatin1String("")); + parent->isBindingLoop = eventsInBindingLoop.contains(parentHash); + } + + // now lastparent is a string with the hash + lastParent[level] = eventHash; + } +} + +////////////////////////////////////////////////////////////////////////////////// +QmlProfilerEventChildrenModelProxy::QmlProfilerEventChildrenModelProxy(QmlProfilerModelManager *modelManager, + QmlProfilerEventsModelProxy *eventsModel, + QObject *parent) + : QmlProfilerEventRelativesModelProxy(modelManager, eventsModel, parent) +{} + +QmlProfilerEventChildrenModelProxy::~QmlProfilerEventChildrenModelProxy() +{} + +void QmlProfilerEventChildrenModelProxy::loadData() +{ + clear(); + QmlProfilerSimpleModel *simpleModel = m_modelManager->simpleModel(); + if (simpleModel->isEmpty()) + return; + + QString rootEventName = tr("<program>"); + + // for level computation + QHash<int, qint64> endtimesPerLevel; + int level = QmlDebug::Constants::QML_MIN_LEVEL; + endtimesPerLevel[0] = 0; + + const QSet<QString> eventsInBindingLoop = m_eventsModel->eventsInBindingLoop(); + + // compute parent-child relationship and call count + QHash<int, QString> lastParent; + const QVector<QmlProfilerSimpleModel::QmlEventData> eventList = simpleModel->getEvents(); + foreach (const QmlProfilerSimpleModel::QmlEventData &event, eventList) { + // whitelist + if (!m_acceptedTypes.contains(event.eventType)) + continue; + + // level computation + if (endtimesPerLevel[level] > event.startTime) { + level++; + } else { + while (level > QmlDebug::Constants::QML_MIN_LEVEL && endtimesPerLevel[level-1] <= event.startTime) + level--; + } + endtimesPerLevel[level] = event.startTime + event.duration; + + QString parentHash = rootEventName; + QString eventHash = QmlProfilerSimpleModel::getHashString(event); + + if (level > QmlDebug::Constants::QML_MIN_LEVEL && lastParent.contains(level-1)) + parentHash = lastParent[level-1]; + + // generate placeholder if needed + if (!m_data.contains(parentHash)) + m_data.insert(parentHash, QmlEventRelativesMap()); + + if (m_data[parentHash].contains(eventHash)) { + QmlEventRelativesData *child = &(m_data[parentHash][eventHash]); + child->calls++; + child->duration += event.duration; + } else { + m_data[parentHash].insert(eventHash, QmlEventRelativesData()); + QmlEventRelativesData *child = &(m_data[parentHash][eventHash]); + child->displayName = event.displayName; + child->eventType = event.eventType; + child->duration = event.duration; + child->calls = 1; + child->details = event.data.join(QLatin1String("")); + child->isBindingLoop = eventsInBindingLoop.contains(parentHash); + } + + // now lastparent is a string with the hash + lastParent[level] = eventHash; + } +} + + + +} +} diff --git a/src/plugins/qmlprofiler/qmlprofilereventsmodelproxy.h b/src/plugins/qmlprofiler/qmlprofilereventsmodelproxy.h new file mode 100644 index 0000000000..b10dd7e49a --- /dev/null +++ b/src/plugins/qmlprofiler/qmlprofilereventsmodelproxy.h @@ -0,0 +1,172 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, 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, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + + +#ifndef QMLPROFILEREVENTSMODELPROXY_H +#define QMLPROFILEREVENTSMODELPROXY_H + +#include "qmlprofilersimplemodel.h" +#include <QObject> +#include <qmldebug/qmlprofilereventtypes.h> +#include <qmldebug/qmlprofilereventlocation.h> +#include <QHash> +#include <QVector> + + +namespace QmlProfiler { +class QmlProfilerModelManager; + +namespace Internal { + +class QmlProfilerEventsModelProxy : public QObject +{ + Q_OBJECT +public: + struct QmlEventStats { + QString displayName; + QString eventHashStr; + QString details; + QmlDebug::QmlEventLocation location; + int eventType; + int bindingType; + + qint64 duration; + qint64 calls; + qint64 minTime; + qint64 maxTime; + qint64 timePerCall; + double percentOfTime; + qint64 medianTime; + + bool isBindingLoop; + }; + + QmlProfilerEventsModelProxy(QmlProfilerModelManager *modelManager, QObject *parent = 0); + ~QmlProfilerEventsModelProxy(); + + const QList<QmlEventStats> getData() const; + int count() const; + void clear(); + + void limitToRange(qint64 rangeStart, qint64 rangeEnd); + +signals: + void dataAvailable(); + +private: + void loadData(qint64 rangeStart = -1, qint64 rangeEnd = -1); + + QSet<QString> eventsInBindingLoop() const; + +private slots: + void dataChanged(); + +private: + class QmlProfilerEventsModelProxyPrivate; + QmlProfilerEventsModelProxyPrivate *d; + + friend class QmlProfilerEventParentsModelProxy; + friend class QmlProfilerEventChildrenModelProxy; +}; + +class QmlProfilerEventRelativesModelProxy : public QObject +{ + Q_OBJECT +public: + struct QmlEventRelativesData { + QString displayName; + int eventType; + qint64 duration; + qint64 calls; + QString details; + bool isBindingLoop; + }; + typedef QHash <QString, QmlEventRelativesData> QmlEventRelativesMap; + + QmlProfilerEventRelativesModelProxy(QmlProfilerModelManager *modelManager, + QmlProfilerEventsModelProxy *eventsModel, + QObject *parent = 0); + ~QmlProfilerEventRelativesModelProxy(); + + + int count() const; + void clear(); + + const QmlEventRelativesMap getData(const QString &hash) const; + +protected: + virtual void loadData() = 0; + +signals: + void dataAvailable(); + +protected slots: + void dataChanged(); + +protected: + QHash <QString, QmlEventRelativesMap> m_data; + QmlProfilerModelManager *m_modelManager; + QmlProfilerEventsModelProxy *m_eventsModel; + QVector <int> m_acceptedTypes; +}; + +class QmlProfilerEventParentsModelProxy : public QmlProfilerEventRelativesModelProxy +{ + Q_OBJECT +public: + QmlProfilerEventParentsModelProxy(QmlProfilerModelManager *modelManager, + QmlProfilerEventsModelProxy *eventsModel, + QObject *parent = 0); + ~QmlProfilerEventParentsModelProxy(); + +protected: + virtual void loadData(); +signals: + void dataAvailable(); +}; + +class QmlProfilerEventChildrenModelProxy : public QmlProfilerEventRelativesModelProxy +{ + Q_OBJECT +public: + QmlProfilerEventChildrenModelProxy(QmlProfilerModelManager *modelManager, + QmlProfilerEventsModelProxy *eventsModel, + QObject *parent = 0); + ~QmlProfilerEventChildrenModelProxy(); + +protected: + virtual void loadData(); +signals: + void dataAvailable(); +}; + +} +} + +#endif diff --git a/src/plugins/qmlprofiler/qmlprofilereventview.cpp b/src/plugins/qmlprofiler/qmlprofilereventview.cpp index 5f4f16b24d..01d8504510 100644 --- a/src/plugins/qmlprofiler/qmlprofilereventview.cpp +++ b/src/plugins/qmlprofiler/qmlprofilereventview.cpp @@ -68,6 +68,9 @@ Q_GLOBAL_STATIC(Colors, colors) //////////////////////////////////////////////////////////////////////////////////// + +//////////////////////////////////////////////////////////////////////////////////// + class EventsViewItem : public QStandardItem { public: @@ -104,46 +107,48 @@ public: QmlProfilerViewManager *m_viewContainer; QmlProfilerEventsMainView *m_eventTree; - QmlProfilerEventsParentsAndChildrenView *m_eventChildren; - QmlProfilerEventsParentsAndChildrenView *m_eventParents; - QmlProfilerDataModel *m_profilerDataModel; + QmlProfilerEventRelativesView *m_eventChildren; + QmlProfilerEventRelativesView *m_eventParents; - bool m_globalStatsEnabled; + QmlProfilerEventsModelProxy *modelProxy; + bool globalStats; }; QmlProfilerEventsWidget::QmlProfilerEventsWidget(QWidget *parent, QmlProfilerTool *profilerTool, QmlProfilerViewManager *container, - QmlProfilerDataModel *profilerDataModel ) + QmlProfilerModelManager *profilerModelManager ) + : QWidget(parent), d(new QmlProfilerEventsWidgetPrivate(this)) { setObjectName(QLatin1String("QmlProfilerEventsView")); - d->m_profilerDataModel = profilerDataModel; - connect(d->m_profilerDataModel, SIGNAL(stateChanged()), + d->modelProxy = new QmlProfilerEventsModelProxy(profilerModelManager, this); + connect(profilerModelManager, SIGNAL(stateChanged()), this, SLOT(profilerDataModelStateChanged())); - d->m_eventTree = new QmlProfilerEventsMainView(QmlProfilerEventsMainView::EventsView, this, d->m_profilerDataModel); + d->m_eventTree = new QmlProfilerEventsMainView(this, d->modelProxy); connect(d->m_eventTree, SIGNAL(gotoSourceLocation(QString,int,int)), this, SIGNAL(gotoSourceLocation(QString,int,int))); - connect(d->m_eventTree, SIGNAL(showEventInTimeline(int)), this, SIGNAL(showEventInTimeline(int))); - - d->m_eventChildren = new QmlProfilerEventsParentsAndChildrenView( - QmlProfilerEventsParentsAndChildrenView::ChildrenView, - this, - d->m_profilerDataModel); - d->m_eventParents = new QmlProfilerEventsParentsAndChildrenView( - QmlProfilerEventsParentsAndChildrenView::ParentsView, - this, - d->m_profilerDataModel); - connect(d->m_eventTree, SIGNAL(eventSelected(int)), d->m_eventChildren, SLOT(displayEvent(int))); - connect(d->m_eventTree, SIGNAL(eventSelected(int)), d->m_eventParents, SLOT(displayEvent(int))); - connect(d->m_eventChildren, SIGNAL(eventClicked(int)), d->m_eventTree, SLOT(selectEvent(int))); - connect(d->m_eventParents, SIGNAL(eventClicked(int)), d->m_eventTree, SLOT(selectEvent(int))); + connect(d->m_eventTree, SIGNAL(eventSelected(QString)), this, SIGNAL(eventSelectedByHash(QString))); + + d->m_eventChildren = new QmlProfilerEventRelativesView( + profilerModelManager, + new QmlProfilerEventChildrenModelProxy(profilerModelManager, d->modelProxy, this), + this); + d->m_eventParents = new QmlProfilerEventRelativesView( + profilerModelManager, + new QmlProfilerEventParentsModelProxy(profilerModelManager, d->modelProxy, this), + this); + connect(d->m_eventTree, SIGNAL(eventSelected(QString)), d->m_eventChildren, SLOT(displayEvent(QString))); + connect(d->m_eventTree, SIGNAL(eventSelected(QString)), d->m_eventParents, SLOT(displayEvent(QString))); + connect(d->m_eventChildren, SIGNAL(eventClicked(QString)), d->m_eventTree, SLOT(selectEvent(QString))); + connect(d->m_eventParents, SIGNAL(eventClicked(QString)), d->m_eventTree, SLOT(selectEvent(QString))); // widget arrangement QVBoxLayout *groupLayout = new QVBoxLayout; groupLayout->setContentsMargins(0,0,0,0); groupLayout->setSpacing(0); + Core::MiniSplitter *splitterVertical = new Core::MiniSplitter; splitterVertical->addWidget(d->m_eventTree); Core::MiniSplitter *splitterHorizontal = new Core::MiniSplitter; @@ -159,30 +164,17 @@ QmlProfilerEventsWidget::QmlProfilerEventsWidget(QWidget *parent, d->m_profilerTool = profilerTool; d->m_viewContainer = container; - d->m_globalStatsEnabled = true; + d->globalStats = true; } QmlProfilerEventsWidget::~QmlProfilerEventsWidget() { + delete d->modelProxy; delete d; } void QmlProfilerEventsWidget::profilerDataModelStateChanged() { - if (d->m_profilerDataModel) { - QmlProfilerDataModel::State newState = d->m_profilerDataModel->currentState(); - if (newState == QmlProfilerDataModel::Empty) - clear(); - } -} - -void QmlProfilerEventsWidget::switchToV8View() -{ - setObjectName(QLatin1String("QmlProfilerV8ProfileView")); - d->m_eventTree->setViewType(QmlProfilerEventsMainView::V8ProfileView); - d->m_eventParents->setViewType(QmlProfilerEventsParentsAndChildrenView::V8ParentsView); - d->m_eventChildren->setViewType(QmlProfilerEventsParentsAndChildrenView::V8ChildrenView); - setToolTip(tr("Trace information from the v8 JavaScript engine. Available only in Qt5 based applications.")); } void QmlProfilerEventsWidget::clear() @@ -194,9 +186,8 @@ void QmlProfilerEventsWidget::clear() void QmlProfilerEventsWidget::getStatisticsInRange(qint64 rangeStart, qint64 rangeEnd) { - clear(); - d->m_eventTree->getStatisticsInRange(rangeStart, rangeEnd); - d->m_globalStatsEnabled = d->m_eventTree->isRangeGlobal(rangeStart, rangeEnd); + d->modelProxy->limitToRange(rangeStart, rangeEnd); + d->globalStats = (rangeStart == -1) && (rangeEnd == -1); } QModelIndex QmlProfilerEventsWidget::selectedItem() const @@ -230,23 +221,18 @@ void QmlProfilerEventsWidget::contextMenuEvent(QContextMenuEvent *ev) copyRowAction = menu.addAction(tr("Copy Row")); copyTableAction = menu.addAction(tr("Copy Table")); - if (isQml()) { - // only for qml events view, not for v8 - showExtendedStatsAction = menu.addAction(tr("Extended Event Statistics")); - showExtendedStatsAction->setCheckable(true); - showExtendedStatsAction->setChecked(showExtendedStatistics()); - } + showExtendedStatsAction = menu.addAction(tr("Extended Event Statistics")); + showExtendedStatsAction->setCheckable(true); + showExtendedStatsAction->setChecked(showExtendedStatistics()); } - if (isQml()) { - menu.addSeparator(); - getLocalStatsAction = menu.addAction(tr("Limit Events Pane to Current Range")); - if (!d->m_viewContainer->hasValidSelection()) - getLocalStatsAction->setEnabled(false); - getGlobalStatsAction = menu.addAction(tr("Reset Events Pane")); - if (hasGlobalStats()) - getGlobalStatsAction->setEnabled(false); - } + menu.addSeparator(); + getLocalStatsAction = menu.addAction(tr("Limit Events Pane to Current Range")); + if (!d->m_viewContainer->hasValidSelection()) + getLocalStatsAction->setEnabled(false); + getGlobalStatsAction = menu.addAction(tr("Reset Events Pane")); + if (hasGlobalStats()) + getGlobalStatsAction->setEnabled(false); QAction *selectedAction = menu.exec(position); @@ -259,12 +245,8 @@ void QmlProfilerEventsWidget::contextMenuEvent(QContextMenuEvent *ev) getStatisticsInRange(d->m_viewContainer->selectionStart(), d->m_viewContainer->selectionEnd()); } - if (selectedAction == getGlobalStatsAction) { - if (d->m_profilerDataModel) { - getStatisticsInRange(d->m_profilerDataModel->traceStartTime(), - d->m_profilerDataModel->traceEndTime()); - } - } + if (selectedAction == getGlobalStatsAction) + getStatisticsInRange(-1, -1); if (selectedAction == showExtendedStatsAction) setShowExtendedStatistics(!showExtendedStatistics()); } @@ -293,24 +275,20 @@ void QmlProfilerEventsWidget::copyRowToClipboard() const d->m_eventTree->copyRowToClipboard(); } -void QmlProfilerEventsWidget::updateSelectedEvent(int eventId) const +void QmlProfilerEventsWidget::updateSelectedEvent(const QString &eventHash) const { - if (d->m_eventTree->selectedEventId() != eventId) - d->m_eventTree->selectEvent(eventId); + if (d->m_eventTree->selectedEventHash() != eventHash) + d->m_eventTree->selectEvent(eventHash); } void QmlProfilerEventsWidget::selectBySourceLocation(const QString &filename, int line, int column) { - // This slot is used to connect the javascript pane with the qml events pane - // Our javascript trace data does not store column information - // thus we ignore it here - Q_UNUSED(column); - d->m_eventTree->selectEventByLocation(filename, line); + d->m_eventTree->selectEventByLocation(filename, line, column); } bool QmlProfilerEventsWidget::hasGlobalStats() const { - return d->m_globalStatsEnabled; + return d->globalStats; } void QmlProfilerEventsWidget::setShowExtendedStatistics(bool show) @@ -323,15 +301,6 @@ bool QmlProfilerEventsWidget::showExtendedStatistics() const return d->m_eventTree->showExtendedStatistics(); } -bool QmlProfilerEventsWidget::isQml() const -{ - return d->m_eventTree->viewType() == QmlProfilerEventsMainView::EventsView; -} -bool QmlProfilerEventsWidget::isV8() const -{ - return d->m_eventTree->viewType() == QmlProfilerEventsMainView::V8ProfileView; -} - //////////////////////////////////////////////////////////////////////////////////// class QmlProfilerEventsMainView::QmlProfilerEventsMainViewPrivate @@ -339,17 +308,14 @@ class QmlProfilerEventsMainView::QmlProfilerEventsMainViewPrivate public: QmlProfilerEventsMainViewPrivate(QmlProfilerEventsMainView *qq) : q(qq) {} - void buildModelFromList(const QList<QmlRangeEventData *> &list, QStandardItem *parentItem ); - void buildV8ModelFromList( const QList<QV8EventData *> &list ); int getFieldCount(); - QString textForItem(QStandardItem *item, bool recursive) const; + QString textForItem(QStandardItem *item, bool recursive = false) const; QmlProfilerEventsMainView *q; - QmlProfilerEventsMainView::ViewTypes m_viewType; - QmlProfilerDataModel *m_profilerDataModel; + QmlProfilerEventsModelProxy *modelProxy; QStandardItemModel *m_model; QList<bool> m_fieldShown; QHash<int, int> m_columnIndex; // maps field enum to column index @@ -361,49 +327,52 @@ public: //////////////////////////////////////////////////////////////////////////////////// -QmlProfilerEventsMainView::QmlProfilerEventsMainView(ViewTypes viewType, - QWidget *parent, - QmlProfilerDataModel *dataModel) - : QTreeView(parent), d(new QmlProfilerEventsMainViewPrivate(this)) +QmlProfilerEventsMainView::QmlProfilerEventsMainView(QWidget *parent, + QmlProfilerEventsModelProxy *modelProxy) +: QmlProfilerTreeView(parent), d(new QmlProfilerEventsMainViewPrivate(this)) { setObjectName(QLatin1String("QmlProfilerEventsTable")); - header()->setResizeMode(QHeaderView::Interactive); - header()->setDefaultSectionSize(100); - header()->setMinimumSectionSize(50); + setSortingEnabled(false); - setFrameStyle(QFrame::NoFrame); d->m_model = new QStandardItemModel(this); setModel(d->m_model); connect(this,SIGNAL(clicked(QModelIndex)), this,SLOT(jumpToItem(QModelIndex))); - d->m_profilerDataModel = dataModel; - connect(d->m_profilerDataModel,SIGNAL(stateChanged()), - this,SLOT(profilerDataModelStateChanged())); - connect(d->m_profilerDataModel,SIGNAL(detailsChanged(int,QString)), - this,SLOT(changeDetailsForEvent(int,QString))); - + d->modelProxy = modelProxy; + connect(d->modelProxy,SIGNAL(dataAvailable()), this, SLOT(buildModel())); +// connect(d->modelProxy,SIGNAL(stateChanged()), +// this,SLOT(profilerDataModelStateChanged())); d->m_firstNumericColumn = 0; d->m_preventSelectBounce = false; d->m_showExtendedStatistics = false; - setViewType(viewType); + setFieldViewable(Name, true); + setFieldViewable(Type, true); + setFieldViewable(TimeInPercent, true); + setFieldViewable(TotalTime, true); + setFieldViewable(SelfTimeInPercent, false); + setFieldViewable(SelfTime, false); + setFieldViewable(CallCount, true); + setFieldViewable(TimePerCall, true); + setFieldViewable(MaxTime, true); + setFieldViewable(MinTime, true); + setFieldViewable(MedianTime, true); + setFieldViewable(Details, true); + + buildModel(); } QmlProfilerEventsMainView::~QmlProfilerEventsMainView() { clear(); + //delete d->modelProxy; delete d->m_model; delete d; } void QmlProfilerEventsMainView::profilerDataModelStateChanged() { - if (d->m_profilerDataModel) { - QmlProfilerDataModel::State newState = d->m_profilerDataModel->currentState(); - if (newState == QmlProfilerDataModel::Done) - buildModel(); - } } void QmlProfilerEventsMainView::setFieldViewable(Fields field, bool show) @@ -418,52 +387,6 @@ void QmlProfilerEventsMainView::setFieldViewable(Fields field, bool show) } } -QmlProfilerEventsMainView::ViewTypes QmlProfilerEventsMainView::viewType() const -{ - return d->m_viewType; -} - -void QmlProfilerEventsMainView::setViewType(ViewTypes type) -{ - d->m_viewType = type; - switch (type) { - case EventsView: { - setObjectName(QLatin1String("QmlProfilerEventsTable")); - setFieldViewable(Name, true); - setFieldViewable(Type, true); - setFieldViewable(Percent, true); - setFieldViewable(TotalDuration, true); - setFieldViewable(SelfPercent, false); - setFieldViewable(SelfDuration, false); - setFieldViewable(CallCount, true); - setFieldViewable(TimePerCall, true); - setFieldViewable(MaxTime, true); - setFieldViewable(MinTime, true); - setFieldViewable(MedianTime, true); - setFieldViewable(Details, true); - break; - } - case V8ProfileView: { - setObjectName(QLatin1String("QmlProfilerV8ProfileTable")); - setFieldViewable(Name, true); - setFieldViewable(Type, false); - setFieldViewable(Percent, true); - setFieldViewable(TotalDuration, true); - setFieldViewable(SelfPercent, true); - setFieldViewable(SelfDuration, true); - setFieldViewable(CallCount, false); - setFieldViewable(TimePerCall, false); - setFieldViewable(MaxTime, false); - setFieldViewable(MinTime, false); - setFieldViewable(MedianTime, false); - setFieldViewable(Details, true); - break; - } - default: break; - } - - buildModel(); -} void QmlProfilerEventsMainView::setHeaderLabels() { @@ -473,53 +396,53 @@ void QmlProfilerEventsMainView::setHeaderLabels() d->m_columnIndex.clear(); if (d->m_fieldShown[Name]) { d->m_columnIndex[Name] = fieldIndex; - d->m_model->setHeaderData(fieldIndex++, Qt::Horizontal, QVariant(tr("Location"))); + d->m_model->setHeaderData(fieldIndex++, Qt::Horizontal, QVariant(displayHeader(Location))); d->m_firstNumericColumn++; } if (d->m_fieldShown[Type]) { d->m_columnIndex[Type] = fieldIndex; - d->m_model->setHeaderData(fieldIndex++, Qt::Horizontal, QVariant(tr("Type"))); + d->m_model->setHeaderData(fieldIndex++, Qt::Horizontal, QVariant(displayHeader(Type))); d->m_firstNumericColumn++; } - if (d->m_fieldShown[Percent]) { - d->m_columnIndex[Percent] = fieldIndex; - d->m_model->setHeaderData(fieldIndex++, Qt::Horizontal, QVariant(tr("Time in Percent"))); + if (d->m_fieldShown[TimeInPercent]) { + d->m_columnIndex[TimeInPercent] = fieldIndex; + d->m_model->setHeaderData(fieldIndex++, Qt::Horizontal, QVariant(displayHeader(TimeInPercent))); } - if (d->m_fieldShown[TotalDuration]) { - d->m_columnIndex[TotalDuration] = fieldIndex; - d->m_model->setHeaderData(fieldIndex++, Qt::Horizontal, QVariant(tr("Total Time"))); + if (d->m_fieldShown[TotalTime]) { + d->m_columnIndex[TotalTime] = fieldIndex; + d->m_model->setHeaderData(fieldIndex++, Qt::Horizontal, QVariant(displayHeader(TotalTime))); } - if (d->m_fieldShown[SelfPercent]) { + if (d->m_fieldShown[SelfTimeInPercent]) { d->m_columnIndex[Type] = fieldIndex; - d->m_model->setHeaderData(fieldIndex++, Qt::Horizontal, QVariant(tr("Self Time in Percent"))); + d->m_model->setHeaderData(fieldIndex++, Qt::Horizontal, QVariant(displayHeader(SelfTimeInPercent))); } - if (d->m_fieldShown[SelfDuration]) { - d->m_columnIndex[SelfDuration] = fieldIndex; - d->m_model->setHeaderData(fieldIndex++, Qt::Horizontal, QVariant(tr("Self Time"))); + if (d->m_fieldShown[SelfTime]) { + d->m_columnIndex[SelfTime] = fieldIndex; + d->m_model->setHeaderData(fieldIndex++, Qt::Horizontal, QVariant(displayHeader(SelfTime))); } if (d->m_fieldShown[CallCount]) { d->m_columnIndex[CallCount] = fieldIndex; - d->m_model->setHeaderData(fieldIndex++, Qt::Horizontal, QVariant(tr("Calls"))); + d->m_model->setHeaderData(fieldIndex++, Qt::Horizontal, QVariant(displayHeader(CallCount))); } if (d->m_fieldShown[TimePerCall]) { d->m_columnIndex[TimePerCall] = fieldIndex; - d->m_model->setHeaderData(fieldIndex++, Qt::Horizontal, QVariant(tr("Mean Time"))); + d->m_model->setHeaderData(fieldIndex++, Qt::Horizontal, QVariant(displayHeader(TimePerCall))); } if (d->m_fieldShown[MedianTime]) { d->m_columnIndex[MedianTime] = fieldIndex; - d->m_model->setHeaderData(fieldIndex++, Qt::Horizontal, QVariant(tr("Median Time"))); + d->m_model->setHeaderData(fieldIndex++, Qt::Horizontal, QVariant(displayHeader(MedianTime))); } if (d->m_fieldShown[MaxTime]) { d->m_columnIndex[MaxTime] = fieldIndex; - d->m_model->setHeaderData(fieldIndex++, Qt::Horizontal, QVariant(tr("Longest Time"))); + d->m_model->setHeaderData(fieldIndex++, Qt::Horizontal, QVariant(displayHeader(MaxTime))); } if (d->m_fieldShown[MinTime]) { d->m_columnIndex[MinTime] = fieldIndex; - d->m_model->setHeaderData(fieldIndex++, Qt::Horizontal, QVariant(tr("Shortest Time"))); + d->m_model->setHeaderData(fieldIndex++, Qt::Horizontal, QVariant(displayHeader(MinTime))); } if (d->m_fieldShown[Details]) { d->m_columnIndex[Details] = fieldIndex; - d->m_model->setHeaderData(fieldIndex++, Qt::Horizontal, QVariant(tr("Details"))); + d->m_model->setHeaderData(fieldIndex++, Qt::Horizontal, QVariant(displayHeader(Details))); } } @@ -569,47 +492,41 @@ int QmlProfilerEventsMainView::QmlProfilerEventsMainViewPrivate::getFieldCount() void QmlProfilerEventsMainView::buildModel() { - if (d->m_profilerDataModel) { - clear(); - if (d->m_viewType == V8ProfileView) - d->buildV8ModelFromList( d->m_profilerDataModel->getV8Events() ); - else - d->buildModelFromList( d->m_profilerDataModel->getEventDescriptions(), d->m_model->invisibleRootItem() ); - - setShowExtendedStatistics(d->m_showExtendedStatistics); + clear(); + parseModelProxy(); + setShowExtendedStatistics(d->m_showExtendedStatistics); - setRootIsDecorated(false); - setSortingEnabled(true); - sortByColumn(d->m_firstNumericColumn,Qt::DescendingOrder); + setRootIsDecorated(false); + setSortingEnabled(true); + sortByColumn(d->m_firstNumericColumn,Qt::DescendingOrder); - expandAll(); - if (d->m_fieldShown[Name]) - resizeColumnToContents(0); + expandAll(); + if (d->m_fieldShown[Name]) + resizeColumnToContents(0); - if (d->m_fieldShown[Type]) - resizeColumnToContents(d->m_fieldShown[Name]?1:0); - collapseAll(); - } + if (d->m_fieldShown[Type]) + resizeColumnToContents(d->m_fieldShown[Name]?1:0); + collapseAll(); } -void QmlProfilerEventsMainView::QmlProfilerEventsMainViewPrivate::buildModelFromList( const QList<QmlRangeEventData *> &list, QStandardItem *parentItem) +void QmlProfilerEventsMainView::parseModelProxy() { - foreach (QmlRangeEventData *binding, list) { - if (binding->calls == 0) - continue; - + const QList <QmlProfilerEventsModelProxy::QmlEventStats> eventList = d->modelProxy->getData(); + foreach (const QmlProfilerEventsModelProxy::QmlEventStats &event, eventList) { + QStandardItem *parentItem = d->m_model->invisibleRootItem(); QList<QStandardItem *> newRow; - if (m_fieldShown[Name]) - newRow << new EventsViewItem(binding->displayName); - if (m_fieldShown[Type]) { - QString typeString = QmlProfilerEventsMainView::nameForType(binding->eventType); + if (d->m_fieldShown[Name]) + newRow << new EventsViewItem(event.displayName); + + if (d->m_fieldShown[Type]) { + QString typeString = QmlProfilerEventsMainView::nameForType(event.eventType); QString toolTipText; - if (binding->eventType == Binding) { - if (binding->bindingType == (int)OptimizedBinding) { + if (event.eventType == Binding) { + if (event.bindingType == (int)OptimizedBinding) { typeString = typeString + tr(" (Opt)"); toolTipText = tr("Binding is evaluated by the optimized engine."); - } else if (binding->bindingType == (int)V8Binding) { + } else if (event.bindingType == (int)V8Binding) { toolTipText = tr("Binding not optimized (e.g. has side effects or assignments,\n" "references to elements in other files, loops, etc.)"); @@ -621,44 +538,44 @@ void QmlProfilerEventsMainView::QmlProfilerEventsMainViewPrivate::buildModelFrom newRow.last()->setToolTip(toolTipText); } - if (m_fieldShown[Percent]) { - newRow << new EventsViewItem(QString::number(binding->percentOfTime,'f',2)+QLatin1String(" %")); - newRow.last()->setData(QVariant(binding->percentOfTime)); + if (d->m_fieldShown[TimeInPercent]) { + newRow << new EventsViewItem(QString::number(event.percentOfTime,'f',2)+QLatin1String(" %")); + newRow.last()->setData(QVariant(event.percentOfTime)); } - if (m_fieldShown[TotalDuration]) { - newRow << new EventsViewItem(displayTime(binding->duration)); - newRow.last()->setData(QVariant(binding->duration)); + if (d->m_fieldShown[TotalTime]) { + newRow << new EventsViewItem(displayTime(event.duration)); + newRow.last()->setData(QVariant(event.duration)); } - if (m_fieldShown[CallCount]) { - newRow << new EventsViewItem(QString::number(binding->calls)); - newRow.last()->setData(QVariant(binding->calls)); + if (d->m_fieldShown[CallCount]) { + newRow << new EventsViewItem(QString::number(event.calls)); + newRow.last()->setData(QVariant(event.calls)); } - if (m_fieldShown[TimePerCall]) { - newRow << new EventsViewItem(displayTime(binding->timePerCall)); - newRow.last()->setData(QVariant(binding->timePerCall)); + if (d->m_fieldShown[TimePerCall]) { + newRow << new EventsViewItem(displayTime(event.timePerCall)); + newRow.last()->setData(QVariant(event.timePerCall)); } - if (m_fieldShown[MedianTime]) { - newRow << new EventsViewItem(displayTime(binding->medianTime)); - newRow.last()->setData(QVariant(binding->medianTime)); + if (d->m_fieldShown[MedianTime]) { + newRow << new EventsViewItem(displayTime(event.medianTime)); + newRow.last()->setData(QVariant(event.medianTime)); } - if (m_fieldShown[MaxTime]) { - newRow << new EventsViewItem(displayTime(binding->maxTime)); - newRow.last()->setData(QVariant(binding->maxTime)); + if (d->m_fieldShown[MaxTime]) { + newRow << new EventsViewItem(displayTime(event.maxTime)); + newRow.last()->setData(QVariant(event.maxTime)); } - if (m_fieldShown[MinTime]) { - newRow << new EventsViewItem(displayTime(binding->minTime)); - newRow.last()->setData(QVariant(binding->minTime)); + if (d->m_fieldShown[MinTime]) { + newRow << new EventsViewItem(displayTime(event.minTime)); + newRow.last()->setData(QVariant(event.minTime)); } - if (m_fieldShown[Details]) { - newRow << new EventsViewItem(binding->details); - newRow.last()->setData(QVariant(binding->details)); + if (d->m_fieldShown[Details]) { + newRow << new EventsViewItem(event.details); + newRow.last()->setData(QVariant(event.details)); } @@ -669,16 +586,17 @@ void QmlProfilerEventsMainView::QmlProfilerEventsMainViewPrivate::buildModelFrom item->setEditable(false); // metadata - newRow.at(0)->setData(QVariant(binding->eventHashStr),EventHashStrRole); - newRow.at(0)->setData(QVariant(binding->location.filename),FilenameRole); - newRow.at(0)->setData(QVariant(binding->location.line),LineRole); - newRow.at(0)->setData(QVariant(binding->location.column),ColumnRole); - newRow.at(0)->setData(QVariant(binding->eventId),EventIdRole); - if (binding->isBindingLoop) + newRow.at(0)->setData(QVariant(event.eventHashStr),EventHashStrRole); + newRow.at(0)->setData(QVariant(event.location.filename),FilenameRole); + newRow.at(0)->setData(QVariant(event.location.line),LineRole); + newRow.at(0)->setData(QVariant(event.location.column),ColumnRole); + + if (event.isBindingLoop) { foreach (QStandardItem *item, newRow) { item->setBackground(colors()->bindingLoopBackground); item->setToolTip(tr("Binding loop detected.")); } + } // append parentItem->appendRow(newRow); @@ -686,58 +604,6 @@ void QmlProfilerEventsMainView::QmlProfilerEventsMainViewPrivate::buildModelFrom } } -void QmlProfilerEventsMainView::QmlProfilerEventsMainViewPrivate::buildV8ModelFromList(const QList<QV8EventData *> &list) -{ - for (int index = 0; index < list.count(); index++) { - QV8EventData *v8event = list.at(index); - QList<QStandardItem *> newRow; - - if (m_fieldShown[Name]) - newRow << new EventsViewItem(v8event->displayName); - - if (m_fieldShown[Percent]) { - newRow << new EventsViewItem(QString::number(v8event->totalPercent,'f',2)+QLatin1String(" %")); - newRow.last()->setData(QVariant(v8event->totalPercent)); - } - - if (m_fieldShown[TotalDuration]) { - newRow << new EventsViewItem(displayTime(v8event->totalTime)); - newRow.last()->setData(QVariant(v8event->totalTime)); - } - - if (m_fieldShown[SelfPercent]) { - newRow << new EventsViewItem(QString::number(v8event->selfPercent,'f',2)+QLatin1String(" %")); - newRow.last()->setData(QVariant(v8event->selfPercent)); - } - - if (m_fieldShown[SelfDuration]) { - newRow << new EventsViewItem(displayTime(v8event->selfTime)); - newRow.last()->setData(QVariant(v8event->selfTime)); - } - - if (m_fieldShown[Details]) { - newRow << new EventsViewItem(v8event->functionName); - newRow.last()->setData(QVariant(v8event->functionName)); - } - - if (!newRow.isEmpty()) { - // no edit - foreach (QStandardItem *item, newRow) - item->setEditable(false); - - // metadata - newRow.at(0)->setData(QString::fromLatin1("%1:%2").arg(v8event->filename, QString::number(v8event->line)), EventHashStrRole); - newRow.at(0)->setData(QVariant(v8event->filename), FilenameRole); - newRow.at(0)->setData(QVariant(v8event->line), LineRole); - newRow.at(0)->setData(QVariant(0),ColumnRole); // v8 events have no column info - newRow.at(0)->setData(QVariant(v8event->eventId), EventIdRole); - - // append - m_model->invisibleRootItem()->appendRow(newRow); - } - } -} - QString QmlProfilerEventsMainView::displayTime(double time) { if (time < 1e6) @@ -762,26 +628,16 @@ QString QmlProfilerEventsMainView::nameForType(int typeNumber) void QmlProfilerEventsMainView::getStatisticsInRange(qint64 rangeStart, qint64 rangeEnd) { - if (d->m_profilerDataModel) - d->m_profilerDataModel->compileStatistics(rangeStart, rangeEnd); - buildModel(); -} - -bool QmlProfilerEventsMainView::isRangeGlobal(qint64 rangeStart, qint64 rangeEnd) const -{ - if (d->m_profilerDataModel) - return d->m_profilerDataModel->traceStartTime() == rangeStart && d->m_profilerDataModel->traceEndTime() == rangeEnd; - else - return true; + d->modelProxy->limitToRange(rangeStart, rangeEnd); } -int QmlProfilerEventsMainView::selectedEventId() const +QString QmlProfilerEventsMainView::selectedEventHash() const { QModelIndex index = selectedItem(); if (!index.isValid()) - return -1; + return QString(); QStandardItem *item = d->m_model->item(index.row(), 0); - return item->data(EventIdRole).toInt(); + return item->data(EventHashStrRole).toString(); } @@ -806,20 +662,16 @@ void QmlProfilerEventsMainView::jumpToItem(const QModelIndex &index) emit gotoSourceLocation(fileName, line, column); // show in callers/callees subwindow - emit eventSelected(infoItem->data(EventIdRole).toInt()); - - // show in timelinerenderer - if (d->m_viewType == EventsView) - emit showEventInTimeline(infoItem->data(EventIdRole).toInt()); + emit eventSelected(infoItem->data(EventHashStrRole).toString()); d->m_preventSelectBounce = false; } -void QmlProfilerEventsMainView::selectEvent(int eventId) +void QmlProfilerEventsMainView::selectEvent(const QString &eventHash) { for (int i=0; i<d->m_model->rowCount(); i++) { QStandardItem *infoItem = d->m_model->item(i, 0); - if (infoItem->data(EventIdRole).toInt() == eventId) { + if (infoItem->data(EventHashStrRole).toString() == eventHash) { setCurrentIndex(d->m_model->indexFromItem(infoItem)); jumpToItem(currentIndex()); return; @@ -827,14 +679,18 @@ void QmlProfilerEventsMainView::selectEvent(int eventId) } } -void QmlProfilerEventsMainView::selectEventByLocation(const QString &filename, int line) +void QmlProfilerEventsMainView::selectEventByLocation(const QString &filename, int line, int column) { if (d->m_preventSelectBounce) return; for (int i=0; i<d->m_model->rowCount(); i++) { QStandardItem *infoItem = d->m_model->item(i, 0); - if (currentIndex() != d->m_model->indexFromItem(infoItem) && infoItem->data(FilenameRole).toString() == filename && infoItem->data(LineRole).toInt() == line) { + if (currentIndex() != d->m_model->indexFromItem(infoItem) && + infoItem->data(FilenameRole).toString() == filename && + infoItem->data(LineRole).toInt() == line && + (column == -1 || + infoItem->data(ColumnRole).toInt() == column)) { setCurrentIndex(d->m_model->indexFromItem(infoItem)); jumpToItem(currentIndex()); return; @@ -851,23 +707,7 @@ QModelIndex QmlProfilerEventsMainView::selectedItem() const return sel.first(); } -void QmlProfilerEventsMainView::changeDetailsForEvent(int eventId, const QString &newString) -{ - // available only for QML - if (d->m_viewType != EventsView) - return; - - for (int i=0; i<d->m_model->rowCount(); i++) { - QStandardItem *infoItem = d->m_model->item(i, 0); - if (infoItem->data(EventIdRole).toInt() == eventId) { - d->m_model->item(i,d->m_columnIndex[Details])->setData(QVariant(newString),Qt::DisplayRole); - d->m_model->item(i,d->m_columnIndex[Details])->setData(QVariant(newString)); - return; - } - } -} - -QString QmlProfilerEventsMainView::QmlProfilerEventsMainViewPrivate::textForItem(QStandardItem *item, bool recursive = true) const +QString QmlProfilerEventsMainView::QmlProfilerEventsMainViewPrivate::textForItem(QStandardItem *item, bool recursive) const { QString str; @@ -931,114 +771,82 @@ void QmlProfilerEventsMainView::copyRowToClipboard() const //////////////////////////////////////////////////////////////////////////////////// -QmlProfilerEventsParentsAndChildrenView::QmlProfilerEventsParentsAndChildrenView( - SubViewType subtableType, QWidget *parent, QmlProfilerDataModel *model) - : QTreeView(parent) +class QmlProfilerEventRelativesView::QmlProfilerEventParentsViewPrivate +{ +public: + QmlProfilerEventParentsViewPrivate(QmlProfilerEventRelativesView *qq):q(qq) {} + ~QmlProfilerEventParentsViewPrivate() {} + + QmlProfilerEventRelativesModelProxy *modelProxy; + + QmlProfilerEventRelativesView *q; +}; + +QmlProfilerEventRelativesView::QmlProfilerEventRelativesView(QmlProfilerModelManager *modelManager, QmlProfilerEventRelativesModelProxy *modelProxy, QWidget *parent) + : QmlProfilerTreeView(parent), d(new QmlProfilerEventParentsViewPrivate(this)) { - m_profilerDataModel = model; + Q_UNUSED(modelManager); + setSortingEnabled(false); + d->modelProxy = modelProxy; setModel(new QStandardItemModel(this)); setRootIsDecorated(false); - setFrameStyle(QFrame::NoFrame); - m_subtableType = subtableType; updateHeader(); connect(this,SIGNAL(clicked(QModelIndex)), this,SLOT(jumpToItem(QModelIndex))); } -QmlProfilerEventsParentsAndChildrenView::~QmlProfilerEventsParentsAndChildrenView() -{ -} - -void QmlProfilerEventsParentsAndChildrenView::setViewType(SubViewType type) +QmlProfilerEventRelativesView::~QmlProfilerEventRelativesView() { - m_subtableType = type; - updateHeader(); + delete d; } -void QmlProfilerEventsParentsAndChildrenView::displayEvent(int eventId) +void QmlProfilerEventRelativesView::displayEvent(const QString &eventHash) { - if (!m_profilerDataModel) - return; - - bool isV8 = m_subtableType == V8ParentsView || m_subtableType == V8ChildrenView; - bool isChildren = m_subtableType == ChildrenView || m_subtableType == V8ChildrenView; - - if (isV8) { - QV8EventData *v8event = m_profilerDataModel->v8EventDescription(eventId); - if (v8event) { - if (isChildren) { - QList <QV8EventSub *> childrenList = v8event->childrenHash.values(); - rebuildTree((QObject *)&childrenList); - } else { - QList <QV8EventSub *> parentList = v8event->parentHash.values(); - rebuildTree((QObject *)&parentList); - } - } - } else { - QmlRangeEventData *qmlEvent = m_profilerDataModel->eventDescription(eventId); - if (qmlEvent) { - if (isChildren) { - QList <QmlRangeEventRelative *> childrenList = qmlEvent->childrenHash.values(); - rebuildTree((QObject *)&childrenList); - } else { - QList <QmlRangeEventRelative *> parentList = qmlEvent->parentHash.values(); - rebuildTree((QObject *)&parentList); - } - } - } + rebuildTree(d->modelProxy->getData(eventHash)); updateHeader(); resizeColumnToContents(0); setSortingEnabled(true); - if (isV8) - sortByColumn(1); - else - sortByColumn(2); + sortByColumn(2); } -void QmlProfilerEventsParentsAndChildrenView::rebuildTree(void *profilerDataModel) +void QmlProfilerEventRelativesView::rebuildTree(QmlProfilerEventRelativesModelProxy::QmlEventRelativesMap eventMap) { Q_ASSERT(treeModel()); treeModel()->clear(); QStandardItem *topLevelItem = treeModel()->invisibleRootItem(); - bool isV8 = m_subtableType == V8ParentsView || m_subtableType == V8ChildrenView; - - QList <QmlRangeEventRelative *> *qmlList = static_cast< QList <QmlRangeEventRelative *> *>(profilerDataModel); - QList <QV8EventSub*> *v8List = static_cast< QList <QV8EventSub *> *>(profilerDataModel); - int listLength; - if (!isV8) - listLength = qmlList->length(); - else - listLength = v8List->length(); - - for (int index=0; index < listLength; index++) { + //foreach (const QmlProfilerEventParentsModelProxy::QmlEventParentData &event, eventMap.values()) { + foreach (const QString &key, eventMap.keys()) { + const QmlProfilerEventRelativesModelProxy::QmlEventRelativesData &event = eventMap[key]; QList<QStandardItem *> newRow; - if (!isV8) { - QmlRangeEventRelative *event = qmlList->at(index); - - newRow << new EventsViewItem(event->reference->displayName); - newRow << new EventsViewItem(QmlProfilerEventsMainView::nameForType(event->reference->eventType)); - newRow << new EventsViewItem(QmlProfilerEventsMainView::displayTime(event->duration)); - newRow << new EventsViewItem(QString::number(event->calls)); - newRow << new EventsViewItem(event->reference->details); - newRow.at(0)->setData(QVariant(event->reference->eventId), EventIdRole); - newRow.at(2)->setData(QVariant(event->duration)); - newRow.at(3)->setData(QVariant(event->calls)); - if (event->inLoopPath) - foreach (QStandardItem *item, newRow) { - item->setBackground(colors()->bindingLoopBackground); - item->setToolTip(tr("Part of binding loop.")); - } - } else { - QV8EventSub *event = v8List->at(index); - newRow << new EventsViewItem(event->reference->displayName); - newRow << new EventsViewItem(QmlProfilerEventsMainView::displayTime(event->totalTime)); - newRow << new EventsViewItem(event->reference->functionName); - newRow.at(0)->setData(QVariant(event->reference->eventId), EventIdRole); - newRow.at(1)->setData(QVariant(event->totalTime)); + + // ToDo: here we were going to search for the data in the other modelproxy + // maybe we should store the data in this proxy and get it here + // no indirections at this level of abstraction! + newRow << new EventsViewItem(event.displayName); + newRow << new EventsViewItem(QmlProfilerEventsMainView::nameForType(event.eventType)); + newRow << new EventsViewItem(QmlProfilerEventsMainView::displayTime(event.duration)); + newRow << new EventsViewItem(QString::number(event.calls)); + newRow << new EventsViewItem(event.details); + +// newRow << new EventsViewItem(event->reference->displayName); +// newRow << new EventsViewItem(QmlProfilerEventsMainView::nameForType(event->reference->eventType)); +// newRow << new EventsViewItem(QmlProfilerEventsMainView::displayTime(event->duration)); +// newRow << new EventsViewItem(QString::number(event->calls)); +// newRow << new EventsViewItem(event->reference->details); + newRow.at(0)->setData(QVariant(key), EventHashStrRole); + newRow.at(2)->setData(QVariant(event.duration)); + newRow.at(3)->setData(QVariant(event.calls)); + + if (event.isBindingLoop) { + foreach (QStandardItem *item, newRow) { + item->setBackground(colors()->bindingLoopBackground); + item->setToolTip(tr("Part of binding loop.")); + } } + foreach (QStandardItem *item, newRow) item->setEditable(false); @@ -1046,7 +854,7 @@ void QmlProfilerEventsParentsAndChildrenView::rebuildTree(void *profilerDataMode } } -void QmlProfilerEventsParentsAndChildrenView::clear() +void QmlProfilerEventRelativesView::clear() { if (treeModel()) { treeModel()->clear(); @@ -1054,52 +862,43 @@ void QmlProfilerEventsParentsAndChildrenView::clear() } } -void QmlProfilerEventsParentsAndChildrenView::updateHeader() +void QmlProfilerEventRelativesView::updateHeader() { - bool isV8 = m_subtableType == V8ParentsView || m_subtableType == V8ChildrenView; - bool isChildren = m_subtableType == ChildrenView || m_subtableType == V8ChildrenView; - - header()->setResizeMode(QHeaderView::Interactive); - header()->setDefaultSectionSize(100); - header()->setMinimumSectionSize(50); + bool calleesView = qobject_cast<QmlProfilerEventChildrenModelProxy *>(d->modelProxy) != 0; if (treeModel()) { - if (isV8) - treeModel()->setColumnCount(3); - else - treeModel()->setColumnCount(5); + treeModel()->setColumnCount(5); int columnIndex = 0; - if (isChildren) - treeModel()->setHeaderData(columnIndex++, Qt::Horizontal, QVariant(tr("Callee"))); + if (calleesView) + treeModel()->setHeaderData(columnIndex++, Qt::Horizontal, QVariant(displayHeader(Callee))); else - treeModel()->setHeaderData(columnIndex++, Qt::Horizontal, QVariant(tr("Caller"))); + treeModel()->setHeaderData(columnIndex++, Qt::Horizontal, QVariant(displayHeader(Caller))); + + treeModel()->setHeaderData(columnIndex++, Qt::Horizontal, QVariant(displayHeader(Type))); - if (!isV8) - treeModel()->setHeaderData(columnIndex++, Qt::Horizontal, QVariant(tr("Type"))); + treeModel()->setHeaderData(columnIndex++, Qt::Horizontal, QVariant(displayHeader(TotalTime))); - treeModel()->setHeaderData(columnIndex++, Qt::Horizontal, QVariant(tr("Total Time"))); + treeModel()->setHeaderData(columnIndex++, Qt::Horizontal, QVariant(displayHeader(CallCount))); - if (!isV8) - treeModel()->setHeaderData(columnIndex++, Qt::Horizontal, QVariant(tr("Calls"))); - if (isChildren) - treeModel()->setHeaderData(columnIndex++, Qt::Horizontal, QVariant(tr("Callee Description"))); + if (calleesView) + treeModel()->setHeaderData(columnIndex++, Qt::Horizontal, QVariant(displayHeader(CalleeDescription))); else - treeModel()->setHeaderData(columnIndex++, Qt::Horizontal, QVariant(tr("Caller Description"))); + treeModel()->setHeaderData(columnIndex++, Qt::Horizontal, QVariant(displayHeader(CallerDescription))); } } -QStandardItemModel *QmlProfilerEventsParentsAndChildrenView::treeModel() +QStandardItemModel *QmlProfilerEventRelativesView::treeModel() { return qobject_cast<QStandardItemModel *>(model()); } -void QmlProfilerEventsParentsAndChildrenView::jumpToItem(const QModelIndex &index) +void QmlProfilerEventRelativesView::jumpToItem(const QModelIndex &index) { if (treeModel()) { QStandardItem *infoItem = treeModel()->item(index.row(), 0); - emit eventClicked(infoItem->data(EventIdRole).toInt()); + emit eventClicked(infoItem->data(EventHashStrRole).toString()); } } diff --git a/src/plugins/qmlprofiler/qmlprofilereventview.h b/src/plugins/qmlprofiler/qmlprofilereventview.h index 2dd0145c2e..747cc613d3 100644 --- a/src/plugins/qmlprofiler/qmlprofilereventview.h +++ b/src/plugins/qmlprofiler/qmlprofilereventview.h @@ -33,7 +33,9 @@ #include <QTreeView> #include <QStandardItemModel> #include <qmldebug/qmlprofilereventtypes.h> -#include "qmlprofilerdatamodel.h" +#include "qmlprofilermodelmanager.h" +#include "qmlprofilereventsmodelproxy.h" +#include "qmlprofilertreeview.h" #include <analyzerbase/ianalyzertool.h> @@ -43,7 +45,8 @@ namespace QmlProfiler { namespace Internal { class QmlProfilerEventsMainView; -class QmlProfilerEventsParentsAndChildrenView; +class QmlProfilerEventChildrenView; +class QmlProfilerEventRelativesView; enum ItemRole { EventHashStrRole = Qt::UserRole+1, @@ -60,10 +63,9 @@ public: explicit QmlProfilerEventsWidget(QWidget *parent, QmlProfilerTool *profilerTool, QmlProfilerViewManager *container, - QmlProfilerDataModel *profilerDataModel ); + QmlProfilerModelManager *profilerModelManager ); ~QmlProfilerEventsWidget(); - void switchToV8View(); void clear(); void getStatisticsInRange(qint64 rangeStart, qint64 rangeEnd); @@ -76,16 +78,14 @@ public: void setShowExtendedStatistics(bool show); bool showExtendedStatistics() const; - bool isQml() const; - bool isV8() const; signals: void gotoSourceLocation(const QString &fileName, int lineNumber, int columnNumber); - void showEventInTimeline(int eventId); + void eventSelectedByHash(const QString &eventHash); void resized(); public slots: - void updateSelectedEvent(int eventId) const; + void updateSelectedEvent(const QString &eventHash) const; void selectBySourceLocation(const QString &filename, int line, int column); private slots: @@ -100,44 +100,15 @@ private: QmlProfilerEventsWidgetPrivate *d; }; -class QmlProfilerEventsMainView : public QTreeView +class QmlProfilerEventsMainView : public QmlProfilerTreeView { Q_OBJECT public: - enum Fields { - Name, - Type, - Percent, - TotalDuration, - SelfPercent, - SelfDuration, - CallCount, - TimePerCall, - MaxTime, - MinTime, - MedianTime, - Details, - - MaxFields - }; - - enum ViewTypes { - EventsView, - CallersView, - CalleesView, - V8ProfileView, - - MaxViewTypes - }; - - explicit QmlProfilerEventsMainView(ViewTypes viewType, - QWidget *parent, - QmlProfilerDataModel *dataModel); + explicit QmlProfilerEventsMainView(QWidget *parent, + QmlProfilerEventsModelProxy *modelProxy); ~QmlProfilerEventsMainView(); void setFieldViewable(Fields field, bool show); - void setViewType(ViewTypes type); - ViewTypes viewType() const; void setShowAnonymousEvents( bool showThem ); QModelIndex selectedItem() const; @@ -148,30 +119,30 @@ public: static QString nameForType(int typeNumber); void getStatisticsInRange(qint64 rangeStart, qint64 rangeEnd); - bool isRangeGlobal(qint64 rangeStart, qint64 rangeEnd) const; - int selectedEventId() const; +// int selectedEventId() const; + QString selectedEventHash() const; void setShowExtendedStatistics(bool); bool showExtendedStatistics() const; + signals: void gotoSourceLocation(const QString &fileName, int lineNumber, int columnNumber); - void eventSelected(int eventId); - void showEventInTimeline(int eventId); + void eventSelected(const QString &eventHash); public slots: void clear(); void jumpToItem(const QModelIndex &index); - void selectEvent(int eventId); - void selectEventByLocation(const QString &filename, int line); + void selectEvent(const QString &eventHash); + void selectEventByLocation(const QString &filename, int line, int column); void buildModel(); - void changeDetailsForEvent(int eventId, const QString &newString); private slots: void profilerDataModelStateChanged(); private: void setHeaderLabels(); + void parseModelProxy(); private: class QmlProfilerEventsMainViewPrivate; @@ -179,40 +150,31 @@ private: }; -class QmlProfilerEventsParentsAndChildrenView : public QTreeView +class QmlProfilerEventRelativesView : public QmlProfilerTreeView { Q_OBJECT public: - enum SubViewType { - ParentsView, - ChildrenView, - V8ParentsView, - V8ChildrenView, - MaxSubtableTypes - }; - - explicit QmlProfilerEventsParentsAndChildrenView(SubViewType subtableType, - QWidget *parent, - QmlProfilerDataModel *model); - ~QmlProfilerEventsParentsAndChildrenView(); - - void setViewType(SubViewType type); + explicit QmlProfilerEventRelativesView(QmlProfilerModelManager *modelManager, + QmlProfilerEventRelativesModelProxy *modelProxy, + QWidget *parent ); + ~QmlProfilerEventRelativesView(); signals: - void eventClicked(int eventId); + void eventClicked(const QString &eventHash); public slots: - void displayEvent(int eventId); + void displayEvent(const QString &eventHash); void jumpToItem(const QModelIndex &); void clear(); private: - void rebuildTree(void *profilerDataModel); + void rebuildTree(QmlProfilerEventParentsModelProxy::QmlEventRelativesMap eventMap); void updateHeader(); QStandardItemModel *treeModel(); - QmlProfilerDataModel *m_profilerDataModel; +// QmlProfilerModelManager *m_profilerModelManager; - SubViewType m_subtableType; + class QmlProfilerEventParentsViewPrivate; + QmlProfilerEventParentsViewPrivate *d; }; } // namespace Internal diff --git a/src/plugins/qmlprofiler/qmlprofilermodelmanager.cpp b/src/plugins/qmlprofiler/qmlprofilermodelmanager.cpp new file mode 100644 index 0000000000..46df68f9b3 --- /dev/null +++ b/src/plugins/qmlprofiler/qmlprofilermodelmanager.cpp @@ -0,0 +1,364 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, 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, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + +#include "qmlprofilermodelmanager.h" +#include "qmlprofilersimplemodel.h" +#include "qmlprofilerprocessedmodel.h" +#include "qv8profilerdatamodel.h" +#include "qmlprofilertracefile.h" + +#include <utils/qtcassert.h> + +#include <QDebug> +#include <QFile> + +namespace QmlProfiler { +namespace Internal { + + +///////////////////////////////////////////////////////////////////// +QmlProfilerDataState::QmlProfilerDataState(QmlProfilerModelManager *modelManager, QObject *parent) + : QObject(parent), m_state(Empty), m_modelManager(modelManager) +{ + connect(this, SIGNAL(error(QString)), m_modelManager, SIGNAL(error(QString))); + connect(this, SIGNAL(stateChanged()), m_modelManager, SIGNAL(stateChanged())); +} + +void QmlProfilerDataState::setState(QmlProfilerDataState::State state) +{ + // It's not an error, we are continuously calling "AcquiringData" for example + if (m_state == state) + return; + + switch (state) { + case Empty: + // if it's not empty, complain but go on + QTC_ASSERT(m_modelManager->isEmpty(), /**/); + break; + case AcquiringData: + // we're not supposed to receive new data while processing older data + QTC_ASSERT(m_state != ProcessingData, return); + break; + case ProcessingData: + QTC_ASSERT(m_state == AcquiringData, return); + break; + case Done: + QTC_ASSERT(m_state == ProcessingData || m_state == Empty, return); + break; + default: + emit error(tr("Trying to set unknown state in events list")); + break; + } + + m_state = state; + emit stateChanged(); + + return; +} + + +///////////////////////////////////////////////////////////////////// +QmlProfilerTraceTime::QmlProfilerTraceTime(QObject *parent) : QObject(parent) +{ + clear(); +} + +QmlProfilerTraceTime::~QmlProfilerTraceTime() +{ +} + +qint64 QmlProfilerTraceTime::startTime() const +{ + return m_startTime; +} + +qint64 QmlProfilerTraceTime::endTime() const +{ + return m_endTime; +} + +qint64 QmlProfilerTraceTime::duration() const +{ + return endTime() - startTime(); +} + +void QmlProfilerTraceTime::clear() +{ + m_startTime = -1; + m_endTime = 0; +} + +void QmlProfilerTraceTime::setStartTime(qint64 time) +{ + m_startTime = time; +} + +void QmlProfilerTraceTime::setEndTime(qint64 time) +{ + m_endTime = time; +} + + +} // namespace Internal + +///////////////////////////////////////////////////////////////////// + +class QmlProfilerModelManager::QmlProfilerModelManagerPrivate +{ +public: + QmlProfilerModelManagerPrivate(QmlProfilerModelManager *qq) : q(qq) {} + ~QmlProfilerModelManagerPrivate() {} + QmlProfilerModelManager *q; + + QmlProfilerSimpleModel *model; + QV8ProfilerDataModel *v8Model; + QmlProfilerDataState *dataState; + QmlProfilerTraceTime *traceTime; + + QVector <double> partialCounts; + double progress; + qint64 estimatedTime; + + // file to load + QString fileName; +}; + + +QmlProfilerModelManager::QmlProfilerModelManager(Utils::FileInProjectFinder *finder, QObject *parent) : + QObject(parent), d(new QmlProfilerModelManagerPrivate(this)) +{ + d->model = new QmlProfilerProcessedModel(finder, this); + d->v8Model = new QV8ProfilerDataModel(this); +// d->model = new QmlProfilerSimpleModel(this); + d->dataState = new QmlProfilerDataState(this, this); + d->traceTime = new QmlProfilerTraceTime(this); +} + +QmlProfilerModelManager::~QmlProfilerModelManager() +{ + delete d; +} + +QmlProfilerTraceTime *QmlProfilerModelManager::traceTime() const +{ + return d->traceTime; +} + +QmlProfilerSimpleModel *QmlProfilerModelManager::simpleModel() const +{ + return d->model; +} + +QV8ProfilerDataModel *QmlProfilerModelManager::v8Model() const +{ + return d->v8Model; +} + +bool QmlProfilerModelManager::isEmpty() const +{ + return d->model->isEmpty() && d->v8Model->isEmpty(); +} + +int QmlProfilerModelManager::count() const +{ + return d->model->count(); +} + +double QmlProfilerModelManager::progress() const +{ + return d->progress; +} + +int QmlProfilerModelManager::registerModelProxy() +{ + d->partialCounts << 0; + return d->partialCounts.count()-1; +} + +void QmlProfilerModelManager::modelProxyCountUpdated(int proxyId, qint64 count, qint64 max) +{ + d->progress -= d->partialCounts[proxyId] / d->partialCounts.count(); + + if (max <= 0) + d->partialCounts[proxyId] = 1; + else + d->partialCounts[proxyId] = (double)count / (double) max; + + d->progress += d->partialCounts[proxyId] / d->partialCounts.count(); + + emit progressChanged(); + if (d->progress > 0.99) + emit dataAvailable(); +} + +qint64 QmlProfilerModelManager::estimatedProfilingTime() const +{ + return d->estimatedTime; +} + +void QmlProfilerModelManager::newTimeEstimation(qint64 estimation) +{ + d->estimatedTime = estimation; +} + +void QmlProfilerModelManager::addQmlEvent(int type, + int bindingType, + qint64 startTime, + qint64 length, + const QStringList &data, + const QmlDebug::QmlEventLocation &location, + qint64 ndata1, + qint64 ndata2, + qint64 ndata3, + qint64 ndata4, + qint64 ndata5) +{ + // If trace start time was not explicitly set, use the first event + if (d->traceTime->startTime() == -1) + d->traceTime->setStartTime(startTime); + + QTC_ASSERT(state() == QmlProfilerDataState::AcquiringData, /**/); + d->model->addQmlEvent(type, bindingType, startTime, length, data, location, ndata1, ndata2, ndata3, ndata4, ndata5); + emit countChanged(); +} + +void QmlProfilerModelManager::addV8Event(int depth, const QString &function, const QString &filename, + int lineNumber, double totalTime, double selfTime) +{ + d->v8Model->addV8Event(depth, function, filename, lineNumber,totalTime, selfTime); +} + +void QmlProfilerModelManager::complete() +{ + if (state() == QmlProfilerDataState::AcquiringData) { + // If trace end time was not explicitly set, use the last event + if (d->traceTime->endTime() == 0) + d->traceTime->setEndTime(d->model->lastTimeMark()); + setState(QmlProfilerDataState::ProcessingData); + d->model->complete(); + d->v8Model->complete(); + setState(QmlProfilerDataState::Done); + } else + if (state() == QmlProfilerDataState::Empty) { + setState(QmlProfilerDataState::Done); + } else + if (state() == QmlProfilerDataState::Done) { + // repeated Done states are ignored + } else { + emit error(tr("Unexpected complete signal in data model")); + } +} + +void QmlProfilerModelManager::save(const QString &filename) +{ + QFile file(filename); + if (!file.open(QIODevice::WriteOnly)) { + emit error(tr("Could not open %1 for writing.").arg(filename)); + return; + } + + QmlProfilerFileWriter writer; + + writer.setTraceTime(traceTime()->startTime(), traceTime()->endTime(), traceTime()->duration()); + writer.setV8DataModel(d->v8Model); + writer.setQmlEvents(d->model->getEvents()); + writer.save(&file); +} + +void QmlProfilerModelManager::load(const QString &filename) +{ + d->fileName = filename; + load(); +} + +void QmlProfilerModelManager::setFilename(const QString &filename) +{ + d->fileName = filename; +} + +void QmlProfilerModelManager::load() +{ + QString filename = d->fileName; + + QFile file(filename); + + if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { + emit error(tr("Could not open %1 for reading.").arg(filename)); + return; + } + + // erase current + clear(); + + setState(QmlProfilerDataState::AcquiringData); + + QmlProfilerFileReader reader; + connect(&reader, SIGNAL(error(QString)), this, SIGNAL(error(QString))); + connect(&reader, SIGNAL(rangedEvent(int,int,qint64,qint64,QStringList,QmlDebug::QmlEventLocation, + qint64, qint64, qint64, qint64, qint64)), + this, SLOT(addQmlEvent(int,int,qint64,qint64,QStringList,QmlDebug::QmlEventLocation, + qint64, qint64, qint64, qint64, qint64))); + connect(&reader, SIGNAL(traceStartTime(qint64)), traceTime(), SLOT(setStartTime(qint64))); + connect(&reader, SIGNAL(traceEndTime(qint64)), traceTime(), SLOT(setEndTime(qint64))); + reader.setV8DataModel(d->v8Model); + reader.load(&file); + + complete(); +} + + +void QmlProfilerModelManager::setState(QmlProfilerDataState::State state) +{ + d->dataState->setState(state); +} + +QmlProfilerDataState::State QmlProfilerModelManager::state() const +{ + return d->dataState->state(); +} + +void QmlProfilerModelManager::clear() +{ + for (int i = 0; i < d->partialCounts.count(); i++) + d->partialCounts[i] = 0; + d->progress = 0; + d->model->clear(); + d->v8Model->clear(); + d->traceTime->clear(); + + emit countChanged(); + setState(QmlProfilerDataState::Empty); +} + +void QmlProfilerModelManager::prepareForWriting() +{ + setState(QmlProfilerDataState::AcquiringData); +} + +} // namespace QmlProfiler diff --git a/src/plugins/qmlprofiler/qmlprofilermodelmanager.h b/src/plugins/qmlprofiler/qmlprofilermodelmanager.h new file mode 100644 index 0000000000..43e2c46844 --- /dev/null +++ b/src/plugins/qmlprofiler/qmlprofilermodelmanager.h @@ -0,0 +1,159 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, 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, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + +#ifndef QMLPROFILERMODELMANAGER_H +#define QMLPROFILERMODELMANAGER_H + +#include <QObject> +#include "qmlprofiler_global.h" +#include "qmldebug/qmlprofilereventlocation.h" +#include <utils/fileinprojectfinder.h> + +namespace QmlProfiler { +class QmlProfilerSimpleModel; +class QmlProfilerModelManager; + +namespace Internal { + +class QV8ProfilerDataModel; +class QmlProfilerDataState : public QObject +{ + Q_OBJECT +public: + enum State { + Empty, + AcquiringData, + ProcessingData, + Done + }; + + explicit QmlProfilerDataState(QmlProfilerModelManager *modelManager, QObject *parent = 0); + ~QmlProfilerDataState() {} + + State state() const { return m_state; } + +signals: + void stateChanged(); + void error(const QString &error); + +private: + void setState(State state); + State m_state; + QmlProfilerModelManager *m_modelManager; + + friend class QmlProfiler::QmlProfilerModelManager; +}; + +class QmlProfilerTraceTime : public QObject +{ + Q_OBJECT +public: + explicit QmlProfilerTraceTime(QObject *parent); + ~QmlProfilerTraceTime(); + + qint64 startTime() const; + qint64 endTime() const; + qint64 duration() const; + +public slots: + void clear(); + void setStartTime(qint64 time); + void setEndTime(qint64 time); + +private: + qint64 m_startTime; + qint64 m_endTime; +}; + +} // End internal namespace + +using namespace Internal; + +// Interface between the Data Model and the Engine/Tool +class QMLPROFILER_EXPORT QmlProfilerModelManager : public QObject +{ + Q_OBJECT +public: + + explicit QmlProfilerModelManager(Utils::FileInProjectFinder *finder, QObject *parent = 0); + ~QmlProfilerModelManager(); + + QmlProfilerDataState::State state() const; + QmlProfilerTraceTime *traceTime() const; + QmlProfilerSimpleModel *simpleModel() const; + QV8ProfilerDataModel *v8Model() const; + + bool isEmpty() const; + int count() const; + + double progress() const; + int registerModelProxy(); + void modelProxyCountUpdated(int proxyId, qint64 count, qint64 max); + + qint64 estimatedProfilingTime() const; + +signals: + void countChanged(); + void error(const QString &error); + void stateChanged(); + void progressChanged(); + void dataAvailable(); + + void requestDetailsForLocation(int eventType, const QmlDebug::QmlEventLocation &location); + +public slots: + void clear(); + + void prepareForWriting(); + void addQmlEvent(int type, int bindingType, qint64 startTime, qint64 length, + const QStringList &data, const QmlDebug::QmlEventLocation &location, + qint64 ndata1, qint64 ndata2, qint64 ndata3, qint64 ndata4, qint64 ndata5); + void addV8Event(int depth, const QString &function,const QString &filename, int lineNumber, + double totalTime, double selfTime); + + void complete(); + + void save(const QString &filename); + void load(const QString &filename); + void setFilename(const QString &filename); + void load(); + + void newTimeEstimation(qint64 estimation); +private: + void setState(QmlProfilerDataState::State state); + + +private: + class QmlProfilerModelManagerPrivate; + QmlProfilerModelManagerPrivate *d; +}; + +} + +#endif diff --git a/src/plugins/qmlprofiler/qmlprofilerpainteventsmodelproxy.cpp b/src/plugins/qmlprofiler/qmlprofilerpainteventsmodelproxy.cpp new file mode 100644 index 0000000000..9cc160b1a5 --- /dev/null +++ b/src/plugins/qmlprofiler/qmlprofilerpainteventsmodelproxy.cpp @@ -0,0 +1,458 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, 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, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + +#include "qmlprofilerpainteventsmodelproxy.h" +#include "qmlprofilermodelmanager.h" +#include "qmlprofilersimplemodel.h" +#include <QCoreApplication> + +#include <QVector> +#include <QHash> +#include <QUrl> +#include <QString> +#include <QStack> + +#include <QDebug> + +namespace QmlProfiler { +namespace Internal { + +struct CategorySpan { + bool expanded; + int expandedRows; + int contractedRows; +}; + +class PaintEventsModelProxy::PaintEventsModelProxyPrivate +{ +public: + PaintEventsModelProxyPrivate(PaintEventsModelProxy *qq) : q(qq) {} + ~PaintEventsModelProxyPrivate() {} + + QString displayTime(double time); + void computeAnimationCountLimit(); + + QVector <PaintEventsModelProxy::QmlPaintEventData> eventList; + int minAnimationCount; + int maxAnimationCount; + bool expanded; + + PaintEventsModelProxy *q; +}; + +PaintEventsModelProxy::PaintEventsModelProxy(QObject *parent) + : AbstractTimelineModel(parent), d(new PaintEventsModelProxyPrivate(this)) +{ +} + +PaintEventsModelProxy::~PaintEventsModelProxy() +{ + delete d; +} + +int PaintEventsModelProxy::categories() const +{ + return categoryCount(); +} + +QStringList PaintEventsModelProxy::categoryTitles() const +{ + QStringList retString; + for (int i=0; i<categories(); i++) + retString << categoryLabel(i); + return retString; +} + +QString PaintEventsModelProxy::name() const +{ + return QLatin1String("PaintEventsModelProxy"); +} + +const QVector<PaintEventsModelProxy::QmlPaintEventData> PaintEventsModelProxy::getData() const +{ + return d->eventList; +} + +const QVector<PaintEventsModelProxy::QmlPaintEventData> PaintEventsModelProxy::getData(qint64 fromTime, qint64 toTime) const +{ + int fromIndex = findFirstIndex(fromTime); + int toIndex = findLastIndex(toTime); + if (fromIndex != -1 && toIndex > fromIndex) + return d->eventList.mid(fromIndex, toIndex - fromIndex + 1); + else + return QVector<PaintEventsModelProxy::QmlPaintEventData>(); +} + +void PaintEventsModelProxy::clear() +{ + d->eventList.clear(); + d->minAnimationCount = 1; + d->maxAnimationCount = 1; + d->expanded = false; + m_modelManager->modelProxyCountUpdated(m_modelId, 0, 1); +} + +void PaintEventsModelProxy::dataChanged() +{ + if (m_modelManager->state() == QmlProfilerDataState::ProcessingData) + loadData(); + + if (m_modelManager->state() == QmlProfilerDataState::Empty) + clear(); + + emit stateChanged(); + emit dataAvailable(); + emit emptyChanged(); + emit expandedChanged(); +} + +bool compareStartTimes(const PaintEventsModelProxy::QmlPaintEventData &t1, const PaintEventsModelProxy::QmlPaintEventData &t2) +{ + return t1.startTime < t2.startTime; +} + +bool PaintEventsModelProxy::eventAccepted(const QmlProfilerSimpleModel::QmlEventData &event) const +{ + return (event.eventType == QmlDebug::Painting && event.bindingType == QmlDebug::AnimationFrame); +} + +void PaintEventsModelProxy::loadData() +{ + clear(); + QmlProfilerSimpleModel *simpleModel = m_modelManager->simpleModel(); + if (simpleModel->isEmpty()) + return; + + // collect events + const QVector<QmlProfilerSimpleModel::QmlEventData> referenceList = simpleModel->getEvents(); + foreach (const QmlProfilerSimpleModel::QmlEventData &event, referenceList) { + if (!eventAccepted(event)) + continue; + + qint64 estimatedDuration = 0; + // initial estimation of the event duration: 1/framerate + if (event.numericData1 > 0) + estimatedDuration = 1e9/event.numericData1; + + // the profiler registers the animation events at the end of them + qint64 realStartTime = event.startTime - estimatedDuration; + + // the duration of the events is estimated from the framerate + // we need to correct it before appending a new event + if (d->eventList.count() > 0) { + QmlPaintEventData *lastEvent = &d->eventList[d->eventList.count()-1]; + if (lastEvent->startTime + lastEvent->duration >= realStartTime) { + // 1 nanosecond less to prevent overlap + lastEvent->duration = realStartTime - lastEvent->startTime - 1; + lastEvent->framerate = 1e9/lastEvent->duration; + } + } + + QmlPaintEventData newEvent = { + realStartTime, + estimatedDuration, + (int)event.numericData1, + (int)event.numericData2 + }; + + d->eventList.append(newEvent); + + m_modelManager->modelProxyCountUpdated(m_modelId, d->eventList.count(), referenceList.count()); + } + + d->computeAnimationCountLimit(); + + qSort(d->eventList.begin(), d->eventList.end(), compareStartTimes); + + m_modelManager->modelProxyCountUpdated(m_modelId, 1, 1); + + emit countChanged(); +} + +/////////////////// QML interface + +bool PaintEventsModelProxy::isEmpty() const +{ + return count() == 0; +} + +int PaintEventsModelProxy::count() const +{ + return d->eventList.count(); +} + +qint64 PaintEventsModelProxy::lastTimeMark() const +{ + return d->eventList.last().startTime + d->eventList.last().duration; +} + +bool PaintEventsModelProxy::expanded(int ) const +{ + return d->expanded; +} + +void PaintEventsModelProxy::setExpanded(int category, bool expanded) +{ + Q_UNUSED(category); + d->expanded = expanded; + emit expandedChanged(); +} + +int PaintEventsModelProxy::categoryDepth(int categoryIndex) const +{ + Q_UNUSED(categoryIndex); + if (isEmpty()) + return 0; + else + return 2; +} + +int PaintEventsModelProxy::categoryCount() const +{ + return 1; +} + +const QString PaintEventsModelProxy::categoryLabel(int categoryIndex) const +{ + Q_UNUSED(categoryIndex); + return tr("Painting"); +} + + +int PaintEventsModelProxy::findFirstIndex(qint64 startTime) const +{ + return findFirstIndexNoParents(startTime); +} + +int PaintEventsModelProxy::findFirstIndexNoParents(qint64 startTime) const +{ + if (d->eventList.isEmpty()) + return -1; + if (d->eventList.count() == 1 || d->eventList.first().startTime+d->eventList.first().duration >= startTime) + return 0; + else + if (d->eventList.last().startTime+d->eventList.last().duration <= startTime) + return -1; + + int fromIndex = 0; + int toIndex = d->eventList.count()-1; + while (toIndex - fromIndex > 1) { + int midIndex = (fromIndex + toIndex)/2; + if (d->eventList[midIndex].startTime + d->eventList[midIndex].duration < startTime) + fromIndex = midIndex; + else + toIndex = midIndex; + } + return toIndex; +} + +int PaintEventsModelProxy::findLastIndex(qint64 endTime) const +{ + if (d->eventList.isEmpty()) + return -1; + if (d->eventList.first().startTime >= endTime) + return -1; + if (d->eventList.count() == 1) + return 0; + if (d->eventList.last().startTime <= endTime) + return d->eventList.count()-1; + + int fromIndex = 0; + int toIndex = d->eventList.count()-1; + while (toIndex - fromIndex > 1) { + int midIndex = (fromIndex + toIndex)/2; + if (d->eventList[midIndex].startTime < endTime) + fromIndex = midIndex; + else + toIndex = midIndex; + } + + return fromIndex; +} + +int PaintEventsModelProxy::getEventType(int index) const +{ + Q_UNUSED(index); + return (int)QmlDebug::Painting; +} + +int PaintEventsModelProxy::getEventCategory(int index) const +{ + Q_UNUSED(index); + // there is only one category, all events belong to it + return 0; +} + +int PaintEventsModelProxy::getEventRow(int index) const +{ + Q_UNUSED(index); + return 1; +} + +qint64 PaintEventsModelProxy::getDuration(int index) const +{ + return d->eventList[index].duration; +} + +qint64 PaintEventsModelProxy::getStartTime(int index) const +{ + return d->eventList[index].startTime; +} + +qint64 PaintEventsModelProxy::getEndTime(int index) const +{ + return d->eventList[index].startTime + d->eventList[index].duration; +} + +int PaintEventsModelProxy::getEventId(int index) const +{ + // there is only one event Id for all painting events + Q_UNUSED(index); + return 0; +} + +QColor PaintEventsModelProxy::getColor(int index) const +{ + double fpsFraction = d->eventList[index].framerate / 60.0; + if (fpsFraction > 1.0) + fpsFraction = 1.0; + if (fpsFraction < 0.0) + fpsFraction = 0.0; + return QColor::fromHsl((fpsFraction*96)+10, 76, 166); +} + +float PaintEventsModelProxy::getHeight(int index) const +{ + float scale = d->maxAnimationCount - d->minAnimationCount; + float fraction = 1.0f; + if (scale > 1) + fraction = (float)(d->eventList[index].animationcount - + d->minAnimationCount) / scale; + + return fraction * 0.85f + 0.15f; +} + +const QVariantList PaintEventsModelProxy::getLabelsForCategory(int category) const +{ + Q_UNUSED(category); + QVariantList result; + + if (!isEmpty()) { + QVariantMap element; + element.insert(QLatin1String("displayName"), QVariant(QLatin1String("Animations"))); + element.insert(QLatin1String("description"), QVariant(QLatin1String("Animations"))); + element.insert(QLatin1String("id"), QVariant(0)); + result << element; + } + + return result; +} + +QString PaintEventsModelProxy::PaintEventsModelProxyPrivate::displayTime(double time) +{ + if (time < 1e6) + return QString::number(time/1e3,'f',3) + trUtf8(" \xc2\xb5s"); + if (time < 1e9) + return QString::number(time/1e6,'f',3) + tr(" ms"); + + return QString::number(time/1e9,'f',3) + tr(" s"); +} + +void PaintEventsModelProxy::PaintEventsModelProxyPrivate::computeAnimationCountLimit() +{ + minAnimationCount = 1; + maxAnimationCount = 1; + if (eventList.isEmpty()) + return; + + for (int i=0; i < eventList.count(); i++) { + if (eventList[i].animationcount < minAnimationCount) + minAnimationCount = eventList[i].animationcount; + if (eventList[i].animationcount > maxAnimationCount) + maxAnimationCount = eventList[i].animationcount; + } +} + +const QVariantList PaintEventsModelProxy::getEventDetails(int index) const +{ + QVariantList result; +// int eventId = getEventId(index); + + static const char trContext[] = "RangeDetails"; + { + QVariantMap valuePair; + valuePair.insert(QLatin1String("title"), QVariant(categoryLabel(0))); + result << valuePair; + } + + // duration + { + QVariantMap valuePair; + valuePair.insert(QCoreApplication::translate(trContext, "Duration:"), QVariant(d->displayTime(d->eventList[index].duration))); + result << valuePair; + } + + // duration + { + QVariantMap valuePair; + valuePair.insert(QCoreApplication::translate(trContext, "Framerate:"), QVariant(QString::fromLatin1("%1 FPS").arg(d->eventList[index].framerate))); + result << valuePair; + } + + // duration + { + QVariantMap valuePair; + valuePair.insert(QCoreApplication::translate(trContext, "Animations:"), QVariant(QString::fromLatin1("%1").arg(d->eventList[index].animationcount))); + result << valuePair; + } + + return result; +} + +const QVariantMap PaintEventsModelProxy::getEventLocation(int /*index*/) const +{ + QVariantMap map; + return map; +} + +int PaintEventsModelProxy::getEventIdForHash(const QString &/*eventHash*/) const +{ + // paint events do not have an eventHash + return -1; +} + +int PaintEventsModelProxy::getEventIdForLocation(const QString &/*filename*/, int /*line*/, int /*column*/) const +{ + // paint events do not have a defined location + return -1; +} + +} +} + diff --git a/src/plugins/qmlprofiler/qmlprofilerpainteventsmodelproxy.h b/src/plugins/qmlprofiler/qmlprofilerpainteventsmodelproxy.h new file mode 100644 index 0000000000..4be60850ef --- /dev/null +++ b/src/plugins/qmlprofiler/qmlprofilerpainteventsmodelproxy.h @@ -0,0 +1,123 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, 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, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + + +#ifndef QMLPROFILERPAINTEVENTSMODELPROXY_H +#define QMLPROFILERPAINTEVENTSMODELPROXY_H + +#include <QObject> +#include "abstracttimelinemodel.h" +#include <qmldebug/qmlprofilereventtypes.h> +#include <qmldebug/qmlprofilereventlocation.h> +//#include <QHash> +//#include <QVector> +#include <QVariantList> +//#include <QVariantMap> +#include "qmlprofilersimplemodel.h" +#include <QColor> + + +namespace QmlProfiler { +class QmlProfilerModelManager; + +namespace Internal { + +class PaintEventsModelProxy : public AbstractTimelineModel +{ +// Q_PROPERTY(bool empty READ isEmpty NOTIFY emptyChanged) + + Q_OBJECT +public: + + struct QmlPaintEventData { + qint64 startTime; + qint64 duration; + int framerate; + int animationcount; + }; + + PaintEventsModelProxy(QObject *parent = 0); + ~PaintEventsModelProxy(); + + + int categories() const; + QStringList categoryTitles() const; + QString name() const; + + const QVector<QmlPaintEventData> getData() const; + const QVector<QmlPaintEventData> getData(qint64 fromTime, qint64 toTime) const; + void loadData(); + Q_INVOKABLE int count() const; + void clear(); + + bool isEmpty() const; + + Q_INVOKABLE qint64 lastTimeMark() const; + + Q_INVOKABLE bool expanded(int category) const; + Q_INVOKABLE void setExpanded(int category, bool expanded); + Q_INVOKABLE int categoryDepth(int categoryIndex) const; + Q_INVOKABLE int categoryCount() const; + Q_INVOKABLE const QString categoryLabel(int categoryIndex) const; + + int findFirstIndex(qint64 startTime) const; + int findFirstIndexNoParents(qint64 startTime) const; + int findLastIndex(qint64 endTime) const; + + int getEventType(int index) const; + Q_INVOKABLE int getEventCategory(int index) const; + int getEventRow(int index) const; + Q_INVOKABLE qint64 getDuration(int index) const; + Q_INVOKABLE qint64 getStartTime(int index) const; + Q_INVOKABLE qint64 getEndTime(int index) const; + Q_INVOKABLE int getEventId(int index) const; + Q_INVOKABLE QColor getColor(int index) const; + Q_INVOKABLE float getHeight(int index) const; + + Q_INVOKABLE const QVariantList getLabelsForCategory(int category) const; + Q_INVOKABLE const QVariantList getEventDetails(int index) const; + Q_INVOKABLE const QVariantMap getEventLocation(int index) const; + Q_INVOKABLE int getEventIdForHash(const QString &eventHash) const; + Q_INVOKABLE int getEventIdForLocation(const QString &filename, int line, int column) const; + +private slots: + bool eventAccepted(const QmlProfilerSimpleModel::QmlEventData &event) const; +protected slots: + void dataChanged(); + +private: + class PaintEventsModelProxyPrivate; + PaintEventsModelProxyPrivate *d; + +}; + +} +} + +#endif diff --git a/src/plugins/qmlprofiler/qmlprofilerplugin.cpp b/src/plugins/qmlprofiler/qmlprofilerplugin.cpp index 87f580ce3d..e4fd95a5a4 100644 --- a/src/plugins/qmlprofiler/qmlprofilerplugin.cpp +++ b/src/plugins/qmlprofiler/qmlprofilerplugin.cpp @@ -31,8 +31,10 @@ #include "qmlprofilerruncontrolfactory.h" #include "qmlprofilertool.h" +#include "abstracttimelinemodel.h" #include <analyzerbase/analyzermanager.h> +#include <extensionsystem/pluginmanager.h> #include <QtPlugin> @@ -48,6 +50,7 @@ public: }; bool QmlProfilerPlugin::debugOutput = false; +QmlProfilerPlugin *QmlProfilerPlugin::instance = 0; bool QmlProfilerPlugin::initialize(const QStringList &arguments, QString *errorString) { @@ -81,15 +84,14 @@ bool QmlProfilerPlugin::initialize(const QStringList &arguments, QString *errorS AnalyzerManager::addAction(action); addAutoReleasedObject(new QmlProfilerRunControlFactory()); + QmlProfilerPlugin::instance = this; return true; } void QmlProfilerPlugin::extensionsInitialized() { - // Retrieve objects from the plugin manager's object pool. - // "In the extensionsInitialized method, a plugin can be sure that all - // plugins that depend on it are completely initialized." + timelineModels = ExtensionSystem::PluginManager::getObjects<AbstractTimelineModel>(); } ExtensionSystem::IPlugin::ShutdownFlag QmlProfilerPlugin::aboutToShutdown() @@ -100,8 +102,12 @@ ExtensionSystem::IPlugin::ShutdownFlag QmlProfilerPlugin::aboutToShutdown() return SynchronousShutdown; } +QList<AbstractTimelineModel *> QmlProfilerPlugin::getModels() const +{ + return timelineModels; +} + } // namespace Internal } // namespace QmlProfiler Q_EXPORT_PLUGIN(QmlProfiler::Internal::QmlProfilerPlugin) - diff --git a/src/plugins/qmlprofiler/qmlprofilerplugin.h b/src/plugins/qmlprofiler/qmlprofilerplugin.h index aa8d15d730..0550fa1eec 100644 --- a/src/plugins/qmlprofiler/qmlprofilerplugin.h +++ b/src/plugins/qmlprofiler/qmlprofilerplugin.h @@ -34,6 +34,8 @@ #include <extensionsystem/iplugin.h> +#include "abstracttimelinemodel.h" + namespace QmlProfiler { namespace Internal { @@ -50,6 +52,14 @@ public: ShutdownFlag aboutToShutdown(); static bool debugOutput; + static QmlProfilerPlugin *instance; + + QList<AbstractTimelineModel *> getModels() const; + +private: + QList<AbstractTimelineModel*> timelineModels; + + }; } // namespace Internal diff --git a/src/plugins/qmlprofiler/qmlprofilerprocessedmodel.cpp b/src/plugins/qmlprofiler/qmlprofilerprocessedmodel.cpp new file mode 100644 index 0000000000..2e4331f735 --- /dev/null +++ b/src/plugins/qmlprofiler/qmlprofilerprocessedmodel.cpp @@ -0,0 +1,182 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, 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, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + +#include "qmlprofilerprocessedmodel.h" +#include <qmldebug/qmlprofilereventtypes.h> +#include <utils/qtcassert.h> +#include <QUrl> +#include <QDebug> + +namespace QmlProfiler { +namespace Internal { + +QmlDebug::QmlEventLocation getLocation(const QmlProfilerSimpleModel::QmlEventData &event); +QString getDisplayName(const QmlProfilerSimpleModel::QmlEventData &event); +QString getInitialDetails(const QmlProfilerSimpleModel::QmlEventData &event); + +QmlDebug::QmlEventLocation getLocation(const QmlProfilerSimpleModel::QmlEventData &event) +{ + QmlDebug::QmlEventLocation eventLocation = event.location; + if ((event.eventType == QmlDebug::Creating || event.eventType == QmlDebug::Compiling) + && eventLocation.filename.isEmpty()) { + eventLocation.filename = getInitialDetails(event); + eventLocation.line = 1; + eventLocation.column = 1; + } + return eventLocation; +} + +QString getDisplayName(const QmlProfilerSimpleModel::QmlEventData &event) +{ + const QmlDebug::QmlEventLocation eventLocation = getLocation(event); + QString displayName; + + // generate hash + if (eventLocation.filename.isEmpty()) { + displayName = QmlProfilerProcessedModel::tr("<bytecode>"); + } else { + const QString filePath = QUrl(eventLocation.filename).path(); + displayName = filePath.mid(filePath.lastIndexOf(QLatin1Char('/')) + 1) + QLatin1Char(':') + + QString::number(eventLocation.line); + } + + return displayName; +} + +QString getInitialDetails(const QmlProfilerSimpleModel::QmlEventData &event) +{ + QString details; + // generate details string + if (event.data.isEmpty()) + details = QmlProfilerProcessedModel::tr("Source code not available."); + else { + details = event.data.join(QLatin1String(" ")).replace(QLatin1Char('\n'),QLatin1Char(' ')).simplified(); + QRegExp rewrite(QLatin1String("\\(function \\$(\\w+)\\(\\) \\{ (return |)(.+) \\}\\)")); + bool match = rewrite.exactMatch(details); + if (match) + details = rewrite.cap(1) + QLatin1String(": ") + rewrite.cap(3); + if (details.startsWith(QLatin1String("file://"))) + details = details.mid(details.lastIndexOf(QLatin1Char('/')) + 1); + } + + return details; +} + + +bool compareStartTimes(const QmlProfilerSimpleModel::QmlEventData &t1, const QmlProfilerSimpleModel::QmlEventData &t2) +{ + return t1.startTime < t2.startTime; +} + +////////////////////////////////////////////////////////////////////////////// + +QmlProfilerProcessedModel::QmlProfilerProcessedModel(Utils::FileInProjectFinder *fileFinder, QObject *parent) + : QmlProfilerSimpleModel(parent) + , m_detailsRewriter(new QmlProfilerDetailsRewriter(this, fileFinder)) + , m_emitChanged(false) +{ + connect(m_detailsRewriter, SIGNAL(rewriteDetailsString(int,QString)), + this, SLOT(detailsChanged(int,QString))); + connect(m_detailsRewriter, SIGNAL(eventDetailsChanged()), + this, SLOT(detailsDone())); +} + +QmlProfilerProcessedModel::~QmlProfilerProcessedModel() +{ +} + +void QmlProfilerProcessedModel::clear() +{ + m_detailsRewriter->clearRequests(); + QmlProfilerSimpleModel::clear(); + + emit changed(); + m_emitChanged = false; +} + +void QmlProfilerProcessedModel::complete() +{ + // post-processing + + // sort events by start time + qSort(eventList.begin(), eventList.end(), compareStartTimes); + + // rewrite strings + int n = eventList.count(); + for (int i = 0; i < n; i++) { + QmlEventData *event = &eventList[i]; + event->location = getLocation(*event); + event->displayName = getDisplayName(*event); + event->data = QStringList() << getInitialDetails(*event); + + // + // request further details from files + // + + if (event->eventType != QmlDebug::Binding && event->eventType != QmlDebug::HandlingSignal) + continue; + + // This skips anonymous bindings in Qt4.8 (we don't have valid location data for them) + if (event->location.filename.isEmpty()) + continue; + + // Skip non-anonymous bindings from Qt4.8 (we already have correct details for them) + if (event->location.column == -1) + continue; + + m_detailsRewriter->requestDetailsForLocation(i, event->location); + } + + m_detailsRewriter->reloadDocuments(); + + QmlProfilerSimpleModel::complete(); + emit changed(); + m_emitChanged = false; +} + +void QmlProfilerProcessedModel::detailsChanged(int requestId, const QString &newString) +{ + QTC_ASSERT(requestId < eventList.count(), return); + + QmlEventData *event = &eventList[requestId]; + event->data = QStringList(newString); + + m_emitChanged = true; +} + +void QmlProfilerProcessedModel::detailsDone() +{ + if (m_emitChanged) { + emit changed(); + m_emitChanged = false; + } +} + +} +} diff --git a/src/plugins/qmlprofiler/qmlprofilerprocessedmodel.h b/src/plugins/qmlprofiler/qmlprofilerprocessedmodel.h new file mode 100644 index 0000000000..4c293711f2 --- /dev/null +++ b/src/plugins/qmlprofiler/qmlprofilerprocessedmodel.h @@ -0,0 +1,62 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, 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, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + +#ifndef QMLPROFILERPROCESSEDMODEL_H +#define QMLPROFILERPROCESSEDMODEL_H + +#include "qmlprofilersimplemodel.h" +#include "qmlprofilerdetailsrewriter.h" + +namespace QmlProfiler { +namespace Internal { + +class QmlProfilerProcessedModel : public QmlProfilerSimpleModel +{ + Q_OBJECT + +public: + explicit QmlProfilerProcessedModel(Utils::FileInProjectFinder *fileFinder, QObject *parent = 0); + ~QmlProfilerProcessedModel(); + + virtual void clear(); + virtual void complete(); + +private slots: + void detailsChanged(int requestId, const QString &newString); + void detailsDone(); + +private: + QmlProfilerDetailsRewriter *m_detailsRewriter; + bool m_emitChanged; +}; + +} +} + +#endif diff --git a/src/plugins/qmlprofiler/qmlprofilersimplemodel.cpp b/src/plugins/qmlprofiler/qmlprofilersimplemodel.cpp new file mode 100644 index 0000000000..99e353d59e --- /dev/null +++ b/src/plugins/qmlprofiler/qmlprofilersimplemodel.cpp @@ -0,0 +1,114 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, 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, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + +#include "qmlprofilersimplemodel.h" +#include "qmlprofilermodelmanager.h" +#include <QStringList> +#include <QVector> +#include <QDebug> +#include "qmldebug/qmlprofilereventtypes.h" + +namespace QmlProfiler { + +QmlProfilerSimpleModel::QmlProfilerSimpleModel(QObject *parent) + : QObject(parent) +{ + m_modelManager = qobject_cast<QmlProfilerModelManager *>(parent); + Q_ASSERT(m_modelManager); + m_modelId = m_modelManager->registerModelProxy(); +} + +QmlProfilerSimpleModel::~QmlProfilerSimpleModel() +{ +} + +void QmlProfilerSimpleModel::clear() +{ + m_modelManager->modelProxyCountUpdated(m_modelId, 0, 1); + eventList.clear(); + emit changed(); +} + +bool QmlProfilerSimpleModel::isEmpty() const +{ + return eventList.isEmpty(); +} + +const QVector<QmlProfilerSimpleModel::QmlEventData> &QmlProfilerSimpleModel::getEvents() const +{ + return eventList; +} + +int QmlProfilerSimpleModel::count() const +{ + return eventList.count(); +} + +void QmlProfilerSimpleModel::addQmlEvent(int type, int bindingType, qint64 startTime, qint64 duration, const QStringList &data, const QmlDebug::QmlEventLocation &location, qint64 ndata1, qint64 ndata2, qint64 ndata3, qint64 ndata4, qint64 ndata5) +{ + QString displayName; + if (type == QmlDebug::Painting && bindingType == QmlDebug::AnimationFrame) { + displayName = tr("Animations"); + } else { + displayName = QString::fromLatin1("%1:%2").arg( + location.filename, + QString::number(location.line)); + } + + QmlEventData eventData = {displayName, type, bindingType, startTime, duration, data, location, ndata1, ndata2, ndata3, ndata4, ndata5}; + eventList.append(eventData); + + m_modelManager->modelProxyCountUpdated(m_modelId, startTime, m_modelManager->estimatedProfilingTime()); +} + +qint64 QmlProfilerSimpleModel::lastTimeMark() const +{ + if (eventList.isEmpty()) + return 0; + + return eventList.last().startTime + eventList.last().duration; +} + +void QmlProfilerSimpleModel::complete() +{ + m_modelManager->modelProxyCountUpdated(m_modelId, 1, 1); + emit changed(); +} + +QString QmlProfilerSimpleModel::getHashString(const QmlProfilerSimpleModel::QmlEventData &event) +{ + return QString::fromLatin1("%1:%2:%3:%4:%5").arg( + event.location.filename, + QString::number(event.location.line), + QString::number(event.location.column), + QString::number(event.eventType), + QString::number(event.bindingType)); +} + +} diff --git a/src/plugins/qmlprofiler/qmlprofilersimplemodel.h b/src/plugins/qmlprofiler/qmlprofilersimplemodel.h new file mode 100644 index 0000000000..30b0de124c --- /dev/null +++ b/src/plugins/qmlprofiler/qmlprofilersimplemodel.h @@ -0,0 +1,88 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, 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, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + +#ifndef QMLPROFILERSIMPLEMODEL_H +#define QMLPROFILERSIMPLEMODEL_H + +#include "qmlprofiler_global.h" +#include <QObject> +#include <QVector> +#include <QStringList> +#include "qmldebug/qmlprofilereventlocation.h" + +namespace QmlProfiler { + +class QmlProfilerModelManager; + +// stores the data from the client as-is +class QMLPROFILER_EXPORT QmlProfilerSimpleModel : public QObject +{ + Q_OBJECT +public: + struct QmlEventData { + QString displayName; + int eventType; + int bindingType; + qint64 startTime; + qint64 duration; + QStringList data; + QmlDebug::QmlEventLocation location; + qint64 numericData1; + qint64 numericData2; + qint64 numericData3; + qint64 numericData4; + qint64 numericData5; + }; + + explicit QmlProfilerSimpleModel(QObject *parent = 0); + ~QmlProfilerSimpleModel(); + + virtual void clear(); + bool isEmpty() const; + const QVector<QmlEventData> &getEvents() const; + int count() const; + void addQmlEvent(int type, int bindingType, qint64 startTime, qint64 duration, const QStringList &data, const QmlDebug::QmlEventLocation &location, + qint64 ndata1, qint64 ndata2, qint64 ndata3, qint64 ndata4, qint64 ndata5); + qint64 lastTimeMark() const; + virtual void complete(); + + static QString getHashString(const QmlProfilerSimpleModel::QmlEventData &event); + +signals: + void changed(); + +protected: + QVector<QmlEventData> eventList; + QmlProfilerModelManager *m_modelManager; + int m_modelId; +}; + +} + +#endif diff --git a/src/plugins/qmlprofiler/qmlprofilerstatemanager.cpp b/src/plugins/qmlprofiler/qmlprofilerstatemanager.cpp index 25ec6c5789..953ed9852f 100644 --- a/src/plugins/qmlprofiler/qmlprofilerstatemanager.cpp +++ b/src/plugins/qmlprofiler/qmlprofilerstatemanager.cpp @@ -38,8 +38,7 @@ namespace QmlProfiler { namespace Internal { -static QString stringForState(int state) -{ +inline QString stringForState(int state) { switch (state) { case QmlProfilerStateManager::Idle: return QLatin1String("Idle"); case QmlProfilerStateManager::AppStarting: return QLatin1String("AppStarting"); @@ -54,79 +53,92 @@ static QString stringForState(int state) return QString(); } +class QmlProfilerStateManager::QmlProfilerStateManagerPrivate +{ +public: + QmlProfilerStateManagerPrivate(QmlProfilerStateManager *qq) : q(qq) {} + ~QmlProfilerStateManagerPrivate() {} + + QmlProfilerStateManager *q; + + QmlProfilerStateManager::QmlProfilerState m_currentState; + bool m_clientRecording; + bool m_serverRecording; +}; QmlProfilerStateManager::QmlProfilerStateManager(QObject *parent) : - QObject(parent) + QObject(parent),d(new QmlProfilerStateManagerPrivate(this)) { - m_currentState = Idle; - m_clientRecording = true; - m_serverRecording = false; + d->m_currentState = Idle; + d->m_clientRecording = true; + d->m_serverRecording = false; } QmlProfilerStateManager::~QmlProfilerStateManager() { + delete d; } -QmlProfilerStateManager::QmlProfilerState QmlProfilerStateManager::currentState() const +QmlProfilerStateManager::QmlProfilerState QmlProfilerStateManager::currentState() { - return m_currentState; + return d->m_currentState; } -bool QmlProfilerStateManager::clientRecording() const +bool QmlProfilerStateManager::clientRecording() { - return m_clientRecording; + return d->m_clientRecording; } -bool QmlProfilerStateManager::serverRecording() const +bool QmlProfilerStateManager::serverRecording() { - return m_serverRecording; + return d->m_serverRecording; } -QString QmlProfilerStateManager::currentStateAsString() const +QString QmlProfilerStateManager::currentStateAsString() { - return stringForState(m_currentState); + return stringForState(d->m_currentState); } void QmlProfilerStateManager::setCurrentState(QmlProfilerState newState) { #ifdef _DEBUG_PROFILERSTATE_ - qDebug() << "Profiler state change request from" << currentStateAsString() << "to" << stringForState(newState); + qDebug() << "Profiler state change request from" << stringForState(d->m_currentState) << "to" << stringForState(newState); #endif - QTC_ASSERT(m_currentState != newState, /**/); + QTC_ASSERT(d->m_currentState != newState, /**/); switch (newState) { case Idle: - QTC_ASSERT(m_currentState == AppStarting || - m_currentState == AppStopped || - m_currentState == AppKilled, - qDebug() << "from" << currentStateAsString()); + QTC_ASSERT(d->m_currentState == AppStarting || + d->m_currentState == AppStopped || + d->m_currentState == AppKilled, + qDebug() << "from" << stringForState(d->m_currentState)); break; case AppStarting: - QTC_ASSERT(m_currentState == Idle, - qDebug() << "from" << currentStateAsString()); + QTC_ASSERT(d->m_currentState == Idle, + qDebug() << "from" << stringForState(d->m_currentState)); break; case AppRunning: - QTC_ASSERT(m_currentState == AppStarting, - qDebug() << "from" << currentStateAsString()); + QTC_ASSERT(d->m_currentState == AppStarting, + qDebug() << "from" << stringForState(d->m_currentState)); break; case AppStopRequested: - QTC_ASSERT(m_currentState == AppRunning, - qDebug() << "from" << currentStateAsString()); + QTC_ASSERT(d->m_currentState == AppRunning, + qDebug() << "from" << stringForState(d->m_currentState)); break; case AppReadyToStop: - QTC_ASSERT(m_currentState == AppStopRequested, - qDebug() << "from" << currentStateAsString()); + QTC_ASSERT(d->m_currentState == AppStopRequested, + qDebug() << "from" << stringForState(d->m_currentState)); break; case AppStopped: - QTC_ASSERT(m_currentState == AppReadyToStop || - m_currentState == AppDying, - qDebug() << "from" << currentStateAsString()); + QTC_ASSERT(d->m_currentState == AppReadyToStop || + d->m_currentState == AppDying, + qDebug() << "from" << stringForState(d->m_currentState)); break; case AppDying: - QTC_ASSERT(m_currentState == AppRunning, - qDebug() << "from" << currentStateAsString()); + QTC_ASSERT(d->m_currentState == AppRunning, + qDebug() << "from" << stringForState(d->m_currentState)); break; case AppKilled: - QTC_ASSERT(m_currentState == AppDying, - qDebug() << "from" << currentStateAsString()); + QTC_ASSERT(d->m_currentState == AppDying, + qDebug() << "from" << stringForState(d->m_currentState)); break; default: { const QString message = QString::fromLatin1("Switching to unknown state in %1:%2").arg(QString::fromLatin1(__FILE__), QString::number(__LINE__)); @@ -135,17 +147,17 @@ void QmlProfilerStateManager::setCurrentState(QmlProfilerState newState) break; } - m_currentState = newState; + d->m_currentState = newState; emit stateChanged(); } void QmlProfilerStateManager::setClientRecording(bool recording) { #ifdef _DEBUG_PROFILERSTATE_ - qDebug() << "Setting client recording flag from" << m_serverRecording << "to" << recording; + qDebug() << "Setting client recording flag from" << d->m_serverRecording << "to" << recording; #endif - if (m_clientRecording != recording) { - m_clientRecording = recording; + if (d->m_clientRecording != recording) { + d->m_clientRecording = recording; emit clientRecordingChanged(); } } @@ -153,13 +165,13 @@ void QmlProfilerStateManager::setClientRecording(bool recording) void QmlProfilerStateManager::setServerRecording(bool recording) { #ifdef _DEBUG_PROFILERSTATE_ - qDebug() << "Setting server recording flag from" << m_serverRecording << "to" << recording; + qDebug() << "Setting server recording flag from" << d->m_serverRecording << "to" << recording; #endif - if (m_serverRecording != recording) { - m_serverRecording = recording; + if (d->m_serverRecording != recording) { + d->m_serverRecording = recording; emit serverRecordingChanged(); } } -} // namespace Internal -} // namespace QmlProfiler +} +} diff --git a/src/plugins/qmlprofiler/qmlprofilerstatemanager.h b/src/plugins/qmlprofiler/qmlprofilerstatemanager.h index 250c3325c3..be93fd4d32 100644 --- a/src/plugins/qmlprofiler/qmlprofilerstatemanager.h +++ b/src/plugins/qmlprofiler/qmlprofilerstatemanager.h @@ -38,7 +38,6 @@ namespace Internal { class QmlProfilerStateManager : public QObject { Q_OBJECT - public: enum QmlProfilerState { Idle, @@ -54,11 +53,11 @@ public: explicit QmlProfilerStateManager(QObject *parent = 0); ~QmlProfilerStateManager(); - QmlProfilerState currentState() const; - bool clientRecording() const; - bool serverRecording() const; + QmlProfilerState currentState(); + bool clientRecording(); + bool serverRecording(); - QString currentStateAsString() const; + QString currentStateAsString(); signals: void stateChanged(); @@ -71,12 +70,11 @@ public slots: void setServerRecording(bool recording); private: - QmlProfilerStateManager::QmlProfilerState m_currentState; - bool m_clientRecording; - bool m_serverRecording; + class QmlProfilerStateManagerPrivate; + QmlProfilerStateManagerPrivate *d; }; -} // namespace Internal -} // namespace QmlProfiler +} +} #endif // QMLPROFILERSTATEMANAGER_H diff --git a/src/plugins/qmlprofiler/qmlprofilerstatewidget.cpp b/src/plugins/qmlprofiler/qmlprofilerstatewidget.cpp index cf46cbeeb1..193f4f7569 100644 --- a/src/plugins/qmlprofiler/qmlprofilerstatewidget.cpp +++ b/src/plugins/qmlprofiler/qmlprofilerstatewidget.cpp @@ -35,6 +35,7 @@ #include <QLabel> #include <QProgressBar> #include <QTime> +#include <QDebug> namespace QmlProfiler { namespace Internal { @@ -49,7 +50,7 @@ class QmlProfilerStateWidget::QmlProfilerStateWidgetPrivate QPixmap shadowPic; QmlProfilerStateManager *m_profilerState; - QmlProfilerDataModel *m_profilerDataModel; + QmlProfilerModelManager *m_modelManager; bool isRecording; bool appKilled; @@ -60,11 +61,9 @@ class QmlProfilerStateWidget::QmlProfilerStateWidgetPrivate qint64 estimatedProfilingTime; }; - - QmlProfilerStateWidget::QmlProfilerStateWidget(QmlProfilerStateManager *stateManager, - QmlProfilerDataModel *dataModel, QWidget *parent) : - QWidget(parent), d(new QmlProfilerStateWidgetPrivate(this)) + QmlProfilerModelManager *modelManager, QWidget *parent) + : QWidget(parent), d(new QmlProfilerStateWidgetPrivate(this)) { setObjectName(QLatin1String("QML Profiler State Display")); @@ -80,6 +79,7 @@ QmlProfilerStateWidget::QmlProfilerStateWidget(QmlProfilerStateManager *stateMan d->progressBar = new QProgressBar(this); layout->addWidget(d->progressBar); + d->progressBar->setMaximum(1000); d->progressBar->setVisible(false); setLayout(layout); @@ -92,9 +92,11 @@ QmlProfilerStateWidget::QmlProfilerStateWidget(QmlProfilerStateManager *stateMan d->emptyList = true; // profiler state - d->m_profilerDataModel = dataModel; - connect(d->m_profilerDataModel,SIGNAL(stateChanged()), this, SLOT(dataStateChanged())); - connect(d->m_profilerDataModel,SIGNAL(countChanged()), this, SLOT(dataStateChanged())); + d->m_modelManager = modelManager; + connect(d->m_modelManager,SIGNAL(stateChanged()), this, SLOT(dataStateChanged())); + connect(d->m_modelManager,SIGNAL(countChanged()), this, SLOT(dataStateChanged())); + connect(d->m_modelManager,SIGNAL(progressChanged()), this, SLOT(dataStateChanged())); + connect(this, SIGNAL(newTimeEstimation(qint64)), d->m_modelManager, SLOT(newTimeEstimation(qint64))); d->m_profilerState = stateManager; connect(d->m_profilerState,SIGNAL(stateChanged()), this, SLOT(profilerStateChanged())); connect(d->m_profilerState, SIGNAL(serverRecordingChanged()), @@ -196,9 +198,9 @@ void QmlProfilerStateWidget::updateDisplay() if (d->isRecording) { d->isRecording = false; d->estimatedProfilingTime = d->profilingTimer.elapsed(); + emit newTimeEstimation(d->estimatedProfilingTime); } - d->progressBar->setMaximum(d->estimatedProfilingTime); - d->progressBar->setValue(d->m_profilerDataModel->lastTimeMark() * 1e-6); + d->progressBar->setValue(d->m_modelManager->progress() * 1000); d->progressBar->setVisible(true); resize(300,70); reposition(); @@ -232,9 +234,9 @@ void QmlProfilerStateWidget::updateDisplay() if (d->isRecording) { d->isRecording = false; d->estimatedProfilingTime = d->profilingTimer.elapsed(); + emit newTimeEstimation(d->estimatedProfilingTime); } - d->progressBar->setMaximum(d->estimatedProfilingTime); - d->progressBar->setValue(d->m_profilerDataModel->lastTimeMark() * 1e-6); + d->progressBar->setValue(d->m_modelManager->progress() * 1000); d->progressBar->setVisible(true); resize(300,70); reposition(); @@ -252,15 +254,17 @@ void QmlProfilerStateWidget::updateDisplay() // } // There is a trace on view, hide this dialog + d->progressBar->setVisible(false); setVisible(false); } void QmlProfilerStateWidget::dataStateChanged() { - d->loadingDone = d->m_profilerDataModel->currentState() == QmlProfilerDataModel::Done || - d->m_profilerDataModel->currentState() == QmlProfilerDataModel::Empty; - d->traceAvailable = d->m_profilerDataModel->traceDuration() > 0; - d->emptyList = d->m_profilerDataModel->count() == 0; + // consider possible rounding errors + d->loadingDone = d->m_modelManager->progress() >= 0.99 || + d->m_modelManager->state() == QmlProfilerDataState::Empty; + d->traceAvailable = d->m_modelManager->traceTime()->duration() > 0; + d->emptyList = d->m_modelManager->isEmpty() || d->m_modelManager->progress() == 0; updateDisplay(); } @@ -275,8 +279,11 @@ void QmlProfilerStateWidget::profilerStateChanged() d->isRecording = d->m_profilerState->serverRecording(); if (d->isRecording) d->profilingTimer.start(); - else - d->estimatedProfilingTime = d->profilingTimer.elapsed(); + else { + // estimated time in ns + d->estimatedProfilingTime = d->profilingTimer.elapsed() * 1e6; + emit newTimeEstimation(d->estimatedProfilingTime); + } updateDisplay(); } diff --git a/src/plugins/qmlprofiler/qmlprofilerstatewidget.h b/src/plugins/qmlprofiler/qmlprofilerstatewidget.h index 57c27f78de..bf32aa2b47 100644 --- a/src/plugins/qmlprofiler/qmlprofilerstatewidget.h +++ b/src/plugins/qmlprofiler/qmlprofilerstatewidget.h @@ -33,7 +33,7 @@ #include <QWidget> #include "qmlprofilerstatemanager.h" -#include "qmlprofilerdatamodel.h" +#include "qmlprofilermodelmanager.h" namespace QmlProfiler { namespace Internal { @@ -43,7 +43,7 @@ class QmlProfilerStateWidget : public QWidget Q_OBJECT public: explicit QmlProfilerStateWidget(QmlProfilerStateManager *stateManager, - QmlProfilerDataModel *dataModel, QWidget *parent = 0); + QmlProfilerModelManager *modelManager, QWidget *parent = 0); ~QmlProfilerStateWidget(); private slots: @@ -52,6 +52,9 @@ private slots: void profilerStateChanged(); void reposition(); +signals: + void newTimeEstimation(qint64); + protected: void paintEvent(QPaintEvent *event); diff --git a/src/plugins/qmlprofiler/qmlprofilertimelinemodelproxy.cpp b/src/plugins/qmlprofiler/qmlprofilertimelinemodelproxy.cpp new file mode 100644 index 0000000000..e6a280aadd --- /dev/null +++ b/src/plugins/qmlprofiler/qmlprofilertimelinemodelproxy.cpp @@ -0,0 +1,721 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, 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, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + +#include "qmlprofilertimelinemodelproxy.h" +#include "qmlprofilermodelmanager.h" +#include "qmlprofilersimplemodel.h" + +#include <QCoreApplication> +#include <QVector> +#include <QHash> +#include <QUrl> +#include <QString> +#include <QStack> + +#include <QDebug> + +namespace QmlProfiler { +namespace Internal { + +struct CategorySpan { + bool expanded; + int expandedRows; + int contractedRows; + int rowStart; + bool empty; +}; + +class BasicTimelineModel::BasicTimelineModelPrivate +{ +public: + BasicTimelineModelPrivate(BasicTimelineModel *qq) : q(qq) {} + ~BasicTimelineModelPrivate() {} + + // convenience functions + void prepare(); + void computeNestingContracted(); + void computeExpandedLevels(); + void buildEndTimeList(); + void findBindingLoops(); + void computeRowStarts(); + + QString displayTime(double time); + + QVector <BasicTimelineModel::QmlRangeEventData> eventDict; + QVector <QString> eventHashes; + QVector <BasicTimelineModel::QmlRangeEventStartInstance> startTimeData; + QVector <BasicTimelineModel::QmlRangeEventEndInstance> endTimeData; + QVector <CategorySpan> categorySpan; + + BasicTimelineModel *q; +}; + +BasicTimelineModel::BasicTimelineModel(QObject *parent) + : AbstractTimelineModel(parent), d(new BasicTimelineModelPrivate(this)) +{ +} + +BasicTimelineModel::~BasicTimelineModel() +{ + delete d; +} + +int BasicTimelineModel::categories() const +{ + return categoryCount(); +} + +QStringList BasicTimelineModel::categoryTitles() const +{ + QStringList retString; + for (int i=0; i<categories(); i++) + retString << categoryLabel(i); + return retString; +} + +QString BasicTimelineModel::name() const +{ + return QLatin1String("BasicTimelineModel"); +} + +const QVector<BasicTimelineModel::QmlRangeEventStartInstance> BasicTimelineModel::getData() const +{ + return d->startTimeData; +} + +const QVector<BasicTimelineModel::QmlRangeEventStartInstance> BasicTimelineModel::getData(qint64 fromTime, qint64 toTime) const +{ + int fromIndex = findFirstIndex(fromTime); + int toIndex = findLastIndex(toTime); + if (fromIndex != -1 && toIndex > fromIndex) + return d->startTimeData.mid(fromIndex, toIndex - fromIndex + 1); + else + return QVector<BasicTimelineModel::QmlRangeEventStartInstance>(); +} + +void BasicTimelineModel::clear() +{ + d->eventDict.clear(); + d->eventHashes.clear(); + d->startTimeData.clear(); + d->endTimeData.clear(); + d->categorySpan.clear(); + + m_modelManager->modelProxyCountUpdated(m_modelId, 0, 1); +} + +void BasicTimelineModel::dataChanged() +{ + if (m_modelManager->state() == QmlProfilerDataState::ProcessingData) + loadData(); + + if (m_modelManager->state() == QmlProfilerDataState::Empty) + clear(); + + emit stateChanged(); + emit dataAvailable(); + emit emptyChanged(); + emit expandedChanged(); +} + +void BasicTimelineModel::BasicTimelineModelPrivate::prepare() +{ + categorySpan.clear(); + for (int i = 0; i < QmlDebug::MaximumQmlEventType; i++) { + CategorySpan newCategory = {false, 1, 1, i, true}; + categorySpan << newCategory; + } +} + +bool compareStartTimes(const BasicTimelineModel::QmlRangeEventStartInstance&t1, const BasicTimelineModel::QmlRangeEventStartInstance &t2) +{ + return t1.startTime < t2.startTime; +} + +bool compareEndTimes(const BasicTimelineModel::QmlRangeEventEndInstance &t1, const BasicTimelineModel::QmlRangeEventEndInstance &t2) +{ + return t1.endTime < t2.endTime; +} + +bool BasicTimelineModel::eventAccepted(const QmlProfilerSimpleModel::QmlEventData &event) const +{ + // only accept Qt4.x Painting events + if (event.eventType == QmlDebug::Painting) + return event.bindingType == QmlDebug::QPainterEvent; + + return (event.eventType <= QmlDebug::HandlingSignal); +} + +void BasicTimelineModel::loadData() +{ + clear(); + QmlProfilerSimpleModel *simpleModel = m_modelManager->simpleModel(); + if (simpleModel->isEmpty()) + return; + + int lastEventId = 0; + + d->prepare(); + + // collect events + const QVector<QmlProfilerSimpleModel::QmlEventData> eventList = simpleModel->getEvents(); + foreach (const QmlProfilerSimpleModel::QmlEventData &event, eventList) { + if (!eventAccepted(event)) + continue; + + QString eventHash = QmlProfilerSimpleModel::getHashString(event); + + // store in dictionary + if (!d->eventHashes.contains(eventHash)) { + QmlRangeEventData rangeEventData = { + event.displayName, + event.data.join(QLatin1String(" ")), + event.location, + (QmlDebug::QmlEventType)event.eventType, + lastEventId++ // event id + }; + d->eventDict << rangeEventData; + d->eventHashes << eventHash; + } + + // store starttime-based instance + QmlRangeEventStartInstance eventStartInstance = { + event.startTime, + event.duration, + d->eventHashes.indexOf(eventHash), // event id + QmlDebug::Constants::QML_MIN_LEVEL, // displayRowExpanded; + QmlDebug::Constants::QML_MIN_LEVEL, // displayRowCollapsed; + 1, + -1 // bindingLoopHead + }; + d->startTimeData.append(eventStartInstance); + + m_modelManager->modelProxyCountUpdated(m_modelId, d->startTimeData.count(), eventList.count() * 7); + } + + qSort(d->startTimeData.begin(), d->startTimeData.end(), compareStartTimes); + + m_modelManager->modelProxyCountUpdated(m_modelId, 2, 7); + + // compute nestingLevel - nonexpanded + d->computeNestingContracted(); + + m_modelManager->modelProxyCountUpdated(m_modelId, 3, 7); + + // compute nestingLevel - expanded + d->computeExpandedLevels(); + + m_modelManager->modelProxyCountUpdated(m_modelId, 4, 7); + + // populate endtimelist + d->buildEndTimeList(); + + m_modelManager->modelProxyCountUpdated(m_modelId, 5, 7); + + d->findBindingLoops(); + + m_modelManager->modelProxyCountUpdated(m_modelId, 6, 7); + + d->computeRowStarts(); + + m_modelManager->modelProxyCountUpdated(m_modelId, 1, 1); + + emit countChanged(); +} + +void BasicTimelineModel::BasicTimelineModelPrivate::computeNestingContracted() +{ + int i; + int eventCount = startTimeData.count(); + + QHash<int, qint64> endtimesPerLevel; + QList<int> nestingLevels; + QList< QHash<int, qint64> > endtimesPerNestingLevel; + int level = QmlDebug::Constants::QML_MIN_LEVEL; + endtimesPerLevel[QmlDebug::Constants::QML_MIN_LEVEL] = 0; + int lastBaseEventIndex = 0; + qint64 lastBaseEventEndTime = q->m_modelManager->traceTime()->startTime(); + + for (i = 0; i < QmlDebug::MaximumQmlEventType; i++) { + nestingLevels << QmlDebug::Constants::QML_MIN_LEVEL; + QHash<int, qint64> dummyHash; + dummyHash[QmlDebug::Constants::QML_MIN_LEVEL] = 0; + endtimesPerNestingLevel << dummyHash; + } + + for (i = 0; i < eventCount; i++) { + qint64 st = startTimeData[i].startTime; + int type = q->getEventType(i); + + // general level + if (endtimesPerLevel[level] > st) { + level++; + } else { + while (level > QmlDebug::Constants::QML_MIN_LEVEL && endtimesPerLevel[level-1] <= st) + level--; + } + endtimesPerLevel[level] = st + startTimeData[i].duration; + + // per type + if (endtimesPerNestingLevel[type][nestingLevels[type]] > st) { + nestingLevels[type]++; + } else { + while (nestingLevels[type] > QmlDebug::Constants::QML_MIN_LEVEL && + endtimesPerNestingLevel[type][nestingLevels[type]-1] <= st) + nestingLevels[type]--; + } + endtimesPerNestingLevel[type][nestingLevels[type]] = + st + startTimeData[i].duration; + + startTimeData[i].displayRowCollapsed = nestingLevels[type]; + + if (level == QmlDebug::Constants::QML_MIN_LEVEL) { + if (lastBaseEventEndTime < startTimeData[i].startTime) { + lastBaseEventIndex = i; + lastBaseEventEndTime = startTimeData[i].startTime + startTimeData[i].duration; + } + } + startTimeData[i].baseEventIndex = lastBaseEventIndex; + } + + // nestingdepth + for (i = 0; i < eventCount; i++) { + int eventType = q->getEventType(i); + categorySpan[eventType].empty = false; + if (categorySpan[eventType].contractedRows <= startTimeData[i].displayRowCollapsed) + categorySpan[eventType].contractedRows = startTimeData[i].displayRowCollapsed + 1; + } +} + +void BasicTimelineModel::BasicTimelineModelPrivate::computeExpandedLevels() +{ + QHash<int, int> eventRow; + int eventCount = startTimeData.count(); + for (int i = 0; i < eventCount; i++) { + int eventId = startTimeData[i].eventId; + int eventType = eventDict[eventId].eventType; + if (!eventRow.contains(eventId)) { + categorySpan[eventType].empty = false; + eventRow[eventId] = categorySpan[eventType].expandedRows++; + } + startTimeData[i].displayRowExpanded = eventRow[eventId]; + } +} + +void BasicTimelineModel::BasicTimelineModelPrivate::buildEndTimeList() +{ + endTimeData.clear(); + + int eventCount = startTimeData.count(); + for (int i = 0; i < eventCount; i++) { + BasicTimelineModel::QmlRangeEventEndInstance endInstance = { + i, + startTimeData[i].startTime + startTimeData[i].duration + }; + + endTimeData << endInstance; + } + + qSort(endTimeData.begin(), endTimeData.end(), compareEndTimes); +} + +void BasicTimelineModel::BasicTimelineModelPrivate::findBindingLoops() +{ + typedef QPair<QString, int> CallStackEntry; + QStack<CallStackEntry> callStack; + + for (int i = 0; i < startTimeData.size(); ++i) { + QmlRangeEventStartInstance *event = &startTimeData[i]; + + BasicTimelineModel::QmlRangeEventData data = eventDict.at(event->eventId); + + static QVector<QmlDebug::QmlEventType> acceptedTypes = + QVector<QmlDebug::QmlEventType>() << QmlDebug::Compiling << QmlDebug::Creating + << QmlDebug::Binding << QmlDebug::HandlingSignal; + + if (!acceptedTypes.contains(data.eventType)) + continue; + + const QString eventHash = eventHashes.at(event->eventId); + const QmlRangeEventStartInstance *potentialParent = callStack.isEmpty() + ? 0 : &startTimeData[callStack.top().second]; + + while (potentialParent + && !(potentialParent->startTime + potentialParent->duration > event->startTime)) { + callStack.pop(); + potentialParent = callStack.isEmpty() ? 0 + : &startTimeData[callStack.top().second]; + } + + // check whether event is already in stack + for (int ii = 0; ii < callStack.size(); ++ii) { + if (callStack.at(ii).first == eventHash) { + event->bindingLoopHead = callStack.at(ii).second; + break; + } + } + + + CallStackEntry newEntry(eventHash, i); + callStack.push(newEntry); + } + +} + +void BasicTimelineModel::BasicTimelineModelPrivate::computeRowStarts() +{ + int rowStart = 0; + for (int i = 0; i < categorySpan.count(); i++) { + categorySpan[i].rowStart = rowStart; + rowStart += q->categoryDepth(i); + } +} + +/////////////////// QML interface + +bool BasicTimelineModel::isEmpty() const +{ + return count() == 0; +} + +int BasicTimelineModel::count() const +{ + return d->startTimeData.count(); +} + +qint64 BasicTimelineModel::lastTimeMark() const +{ + return d->startTimeData.last().startTime + d->startTimeData.last().duration; +} + +bool BasicTimelineModel::expanded(int category) const +{ + if (d->categorySpan.count() <= category) + return false; + return d->categorySpan[category].expanded; +} + +void BasicTimelineModel::setExpanded(int category, bool expanded) +{ + if (d->categorySpan.count() <= category) + return; + + d->categorySpan[category].expanded = expanded; + d->computeRowStarts(); + emit expandedChanged(); +} + +int BasicTimelineModel::categoryDepth(int categoryIndex) const +{ + if (d->categorySpan.count() <= categoryIndex) + return 1; + // special for paint events: show only when empty model or there's actual events + if (categoryIndex == QmlDebug::Painting && d->categorySpan[categoryIndex].empty && !isEmpty()) + return 0; + if (d->categorySpan[categoryIndex].expanded) + return d->categorySpan[categoryIndex].expandedRows; + else + return d->categorySpan[categoryIndex].contractedRows; +} + +int BasicTimelineModel::categoryCount() const +{ + return 5; +} + +const QString BasicTimelineModel::categoryLabel(int categoryIndex) const +{ + switch (categoryIndex) { + case 0: return QCoreApplication::translate("MainView", "Painting"); break; + case 1: return QCoreApplication::translate("MainView", "Compiling"); break; + case 2: return QCoreApplication::translate("MainView", "Creating"); break; + case 3: return QCoreApplication::translate("MainView", "Binding"); break; + case 4: return QCoreApplication::translate("MainView", "Handling Signal"); break; + default: return QString(); + } +} + + +int BasicTimelineModel::findFirstIndex(qint64 startTime) const +{ + int candidate = -1; + // in the "endtime" list, find the first event that ends after startTime + if (d->endTimeData.isEmpty()) + return -1; + if (d->endTimeData.count() == 1 || d->endTimeData.first().endTime >= startTime) + candidate = 0; + else + if (d->endTimeData.last().endTime <= startTime) + return -1; + + if (candidate == -1) + { + int fromIndex = 0; + int toIndex = d->endTimeData.count()-1; + while (toIndex - fromIndex > 1) { + int midIndex = (fromIndex + toIndex)/2; + if (d->endTimeData[midIndex].endTime < startTime) + fromIndex = midIndex; + else + toIndex = midIndex; + } + + candidate = toIndex; + } + + int eventIndex = d->endTimeData[candidate].startTimeIndex; + return d->startTimeData[eventIndex].baseEventIndex; + +} + +int BasicTimelineModel::findFirstIndexNoParents(qint64 startTime) const +{ + int candidate = -1; + // in the "endtime" list, find the first event that ends after startTime + if (d->endTimeData.isEmpty()) + return -1; + if (d->endTimeData.count() == 1 || d->endTimeData.first().endTime >= startTime) + candidate = 0; + else + if (d->endTimeData.last().endTime <= startTime) + return -1; + + if (candidate == -1) { + int fromIndex = 0; + int toIndex = d->endTimeData.count()-1; + while (toIndex - fromIndex > 1) { + int midIndex = (fromIndex + toIndex)/2; + if (d->endTimeData[midIndex].endTime < startTime) + fromIndex = midIndex; + else + toIndex = midIndex; + } + + candidate = toIndex; + } + + int ndx = d->endTimeData[candidate].startTimeIndex; + + return ndx; +} + +int BasicTimelineModel::findLastIndex(qint64 endTime) const +{ + // in the "starttime" list, find the last event that starts before endtime + if (d->startTimeData.isEmpty()) + return -1; + if (d->startTimeData.first().startTime >= endTime) + return -1; + if (d->startTimeData.count() == 1) + return 0; + if (d->startTimeData.last().startTime <= endTime) + return d->startTimeData.count()-1; + + int fromIndex = 0; + int toIndex = d->startTimeData.count()-1; + while (toIndex - fromIndex > 1) { + int midIndex = (fromIndex + toIndex)/2; + if (d->startTimeData[midIndex].startTime < endTime) + fromIndex = midIndex; + else + toIndex = midIndex; + } + + return fromIndex; +} + +int BasicTimelineModel::getEventType(int index) const +{ + return d->eventDict[d->startTimeData[index].eventId].eventType; +} + +int BasicTimelineModel::getEventCategory(int index) const +{ + int evTy = getEventType(index); + // special: paint events shown? + if (d->categorySpan[0].empty && !isEmpty()) + return evTy - 1; + return evTy; +} + +int BasicTimelineModel::getEventRow(int index) const +{ + if (d->categorySpan[getEventType(index)].expanded) + return d->startTimeData[index].displayRowExpanded + d->categorySpan[getEventType(index)].rowStart; + else + return d->startTimeData[index].displayRowCollapsed + d->categorySpan[getEventType(index)].rowStart; +} + +qint64 BasicTimelineModel::getDuration(int index) const +{ + return d->startTimeData[index].duration; +} + +qint64 BasicTimelineModel::getStartTime(int index) const +{ + return d->startTimeData[index].startTime; +} + +qint64 BasicTimelineModel::getEndTime(int index) const +{ + return d->startTimeData[index].startTime + d->startTimeData[index].duration; +} + +int BasicTimelineModel::getEventId(int index) const +{ + return d->startTimeData[index].eventId; +} + +int BasicTimelineModel::getBindingLoopDest(int index) const +{ + return d->startTimeData[index].bindingLoopHead; +} + +QColor BasicTimelineModel::getColor(int index) const +{ + int ndx = getEventId(index); + return QColor::fromHsl((ndx*25)%360, 76, 166); +} + +float BasicTimelineModel::getHeight(int index) const +{ + Q_UNUSED(index); + // 100% height for regular events + return 1.0f; +} + +const QVariantList BasicTimelineModel::getLabelsForCategory(int category) const +{ + QVariantList result; + + if (d->categorySpan.count() > category && d->categorySpan[category].expanded) { + int eventCount = d->eventDict.count(); + for (int i = 0; i < eventCount; i++) { + if (d->eventDict[i].eventType == category) { + QVariantMap element; + element.insert(QLatin1String("displayName"), QVariant(d->eventDict[i].displayName)); + element.insert(QLatin1String("description"), QVariant(d->eventDict[i].details)); + element.insert(QLatin1String("id"), QVariant(d->eventDict[i].eventId)); + result << element; + } + } + } + + return result; +} + +QString BasicTimelineModel::BasicTimelineModelPrivate::displayTime(double time) +{ + if (time < 1e6) + return QString::number(time/1e3,'f',3) + trUtf8(" \xc2\xb5s"); + if (time < 1e9) + return QString::number(time/1e6,'f',3) + tr(" ms"); + + return QString::number(time/1e9,'f',3) + tr(" s"); +} + +const QVariantList BasicTimelineModel::getEventDetails(int index) const +{ + QVariantList result; + int eventId = getEventId(index); + + static const char trContext[] = "RangeDetails"; + { + QVariantMap valuePair; + valuePair.insert(QLatin1String("title"), QVariant(categoryLabel(d->eventDict[eventId].eventType))); + result << valuePair; + } + + // duration + { + QVariantMap valuePair; + valuePair.insert(QCoreApplication::translate(trContext, "Duration:"), QVariant(d->displayTime(d->startTimeData[index].duration))); + result << valuePair; + } + + // details + { + QVariantMap valuePair; + QString detailsString = d->eventDict[eventId].details; + if (detailsString.length() > 40) + detailsString = detailsString.left(40) + QLatin1String("..."); + valuePair.insert(QCoreApplication::translate(trContext, "Details:"), QVariant(detailsString)); + result << valuePair; + } + + // location + { + QVariantMap valuePair; + valuePair.insert(QCoreApplication::translate(trContext, "Location:"), QVariant(d->eventDict[eventId].displayName)); + result << valuePair; + } + + // isbindingloop + {} + + + return result; +} + +const QVariantMap BasicTimelineModel::getEventLocation(int index) const +{ + QVariantMap result; + int eventId = getEventId(index); + + QmlDebug::QmlEventLocation location + = d->eventDict.at(eventId).location; + + result.insert(QLatin1String("file"), location.filename); + result.insert(QLatin1String("line"), location.line); + result.insert(QLatin1String("column"), location.column); + + return result; +} + +int BasicTimelineModel::getEventIdForHash(const QString &eventHash) const +{ + return d->eventHashes.indexOf(eventHash); +} + +int BasicTimelineModel::getEventIdForLocation(const QString &filename, int line, int column) const +{ + // if this is called from v8 view, we don't have the column number, it will be -1 + foreach (const QmlRangeEventData &eventData, d->eventDict) { + if (eventData.location.filename == filename && + eventData.location.line == line && + (column == -1 || eventData.location.column == column)) + return eventData.eventId; + } + return -1; +} + + + +} +} diff --git a/src/plugins/qmlprofiler/qmlprofilertimelinemodelproxy.h b/src/plugins/qmlprofiler/qmlprofilertimelinemodelproxy.h new file mode 100644 index 0000000000..3ffcf707ff --- /dev/null +++ b/src/plugins/qmlprofiler/qmlprofilertimelinemodelproxy.h @@ -0,0 +1,146 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, 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, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + + +#ifndef QMLPROFILERTIMELINEMODELPROXY_H +#define QMLPROFILERTIMELINEMODELPROXY_H + +#include <QObject> +#include "abstracttimelinemodel.h" +#include <qmldebug/qmlprofilereventtypes.h> +#include <qmldebug/qmlprofilereventlocation.h> +//#include <QHash> +//#include <QVector> +#include <QVariantList> +//#include <QVariantMap> +#include "qmlprofilersimplemodel.h" +#include <QColor> + + +namespace QmlProfiler { +class QmlProfilerModelManager; + +namespace Internal { + +class BasicTimelineModel : public AbstractTimelineModel +{ +// Q_PROPERTY(bool empty READ isEmpty NOTIFY emptyChanged) + + Q_OBJECT +public: + struct QmlRangeEventData + { + QString displayName; + QString details; + QmlDebug::QmlEventLocation location; + QmlDebug::QmlEventType eventType; + + int eventId; // separate + }; + + struct QmlRangeEventStartInstance { + qint64 startTime; + qint64 duration; + int eventId; + + // not-expanded, per type + int displayRowExpanded; + int displayRowCollapsed; + int baseEventIndex; // used by findfirstindex + int bindingLoopHead; + }; + + struct QmlRangeEventEndInstance { + int startTimeIndex; + qint64 endTime; + }; + + BasicTimelineModel(QObject *parent = 0); + ~BasicTimelineModel(); + + + int categories() const; + QStringList categoryTitles() const; + QString name() const; + + const QVector<QmlRangeEventStartInstance> getData() const; + const QVector<QmlRangeEventStartInstance> getData(qint64 fromTime, qint64 toTime) const; + void loadData(); + Q_INVOKABLE int count() const; + void clear(); + + +// QML interface + bool isEmpty() const; + + Q_INVOKABLE qint64 lastTimeMark() const; + + Q_INVOKABLE bool expanded(int category) const; + Q_INVOKABLE void setExpanded(int category, bool expanded); + Q_INVOKABLE int categoryDepth(int categoryIndex) const; + Q_INVOKABLE int categoryCount() const; + Q_INVOKABLE const QString categoryLabel(int categoryIndex) const; + + int findFirstIndex(qint64 startTime) const; + int findFirstIndexNoParents(qint64 startTime) const; + int findLastIndex(qint64 endTime) const; + + int getEventType(int index) const; + int getEventCategory(int index) const; + int getEventRow(int index) const; + Q_INVOKABLE qint64 getDuration(int index) const; + Q_INVOKABLE qint64 getStartTime(int index) const; + Q_INVOKABLE qint64 getEndTime(int index) const; + Q_INVOKABLE int getEventId(int index) const; + int getBindingLoopDest(int index) const; + Q_INVOKABLE QColor getColor(int index) const; + Q_INVOKABLE float getHeight(int index) const; + + Q_INVOKABLE const QVariantList getLabelsForCategory(int category) const; + Q_INVOKABLE const QVariantList getEventDetails(int index) const; + Q_INVOKABLE const QVariantMap getEventLocation(int index) const; + + Q_INVOKABLE int getEventIdForHash(const QString &eventHash) const; + Q_INVOKABLE int getEventIdForLocation(const QString &filename, int line, int column) const; + +private slots: + bool eventAccepted(const QmlProfilerSimpleModel::QmlEventData &event) const; +protected slots: + void dataChanged(); + +private: + class BasicTimelineModelPrivate; + BasicTimelineModelPrivate *d; + +}; + +} +} + +#endif diff --git a/src/plugins/qmlprofiler/qmlprofilertool.cpp b/src/plugins/qmlprofiler/qmlprofilertool.cpp index 2064db5763..ed9235d623 100644 --- a/src/plugins/qmlprofiler/qmlprofilertool.cpp +++ b/src/plugins/qmlprofiler/qmlprofilertool.cpp @@ -34,7 +34,7 @@ #include "qmlprofilerattachdialog.h" #include "qmlprofilerviewmanager.h" #include "qmlprofilerclientmanager.h" -#include "qmlprofilerdatamodel.h" +#include "qmlprofilermodelmanager.h" #include "qmlprofilerdetailsrewriter.h" #include "timelinerenderer.h" @@ -98,8 +98,7 @@ class QmlProfilerTool::QmlProfilerToolPrivate public: QmlProfilerStateManager *m_profilerState; QmlProfilerClientManager *m_profilerConnections; - QmlProfilerDataModel *m_profilerDataModel; - QmlProfilerDetailsRewriter *m_detailsRewriter; + QmlProfilerModelManager *m_profilerModelManager; QmlProfilerViewManager *m_viewContainer; Utils::FileInProjectFinder m_projectFinder; @@ -138,31 +137,11 @@ QmlProfilerTool::QmlProfilerTool(QObject *parent) d->m_profilerConnections->registerProfilerStateManager(d->m_profilerState); connect(d->m_profilerConnections, SIGNAL(connectionClosed()), this, SLOT(clientsDisconnected())); - d->m_profilerDataModel = new QmlProfilerDataModel(this); - connect(d->m_profilerDataModel, SIGNAL(stateChanged()), this, SLOT(profilerDataModelStateChanged())); - connect(d->m_profilerDataModel, SIGNAL(error(QString)), this, SLOT(showErrorDialog(QString))); - connect(d->m_profilerConnections, - SIGNAL(addRangedEvent(int,int,qint64,qint64,QStringList,QmlDebug::QmlEventLocation)), - d->m_profilerDataModel, - SLOT(addRangedEvent(int,int,qint64,qint64,QStringList,QmlDebug::QmlEventLocation))); - connect(d->m_profilerConnections, - SIGNAL(addV8Event(int,QString,QString,int,double,double)), - d->m_profilerDataModel, - SLOT(addV8Event(int,QString,QString,int,double,double))); - connect(d->m_profilerConnections, SIGNAL(addFrameEvent(qint64,int,int)), d->m_profilerDataModel, SLOT(addFrameEvent(qint64,int,int))); - connect(d->m_profilerConnections, SIGNAL(traceStarted(qint64)), d->m_profilerDataModel, SLOT(setTraceStartTime(qint64))); - connect(d->m_profilerConnections, SIGNAL(traceFinished(qint64)), d->m_profilerDataModel, SLOT(setTraceEndTime(qint64))); - connect(d->m_profilerConnections, SIGNAL(dataReadyForProcessing()), d->m_profilerDataModel, SLOT(complete())); - - - d->m_detailsRewriter = new QmlProfilerDetailsRewriter(this, &d->m_projectFinder); - connect(d->m_profilerDataModel, SIGNAL(requestDetailsForLocation(int,QmlDebug::QmlEventLocation)), - d->m_detailsRewriter, SLOT(requestDetailsForLocation(int,QmlDebug::QmlEventLocation))); - connect(d->m_detailsRewriter, SIGNAL(rewriteDetailsString(int,QmlDebug::QmlEventLocation,QString)), - d->m_profilerDataModel, SLOT(rewriteDetailsString(int,QmlDebug::QmlEventLocation,QString))); - connect(d->m_detailsRewriter, SIGNAL(eventDetailsChanged()), d->m_profilerDataModel, SLOT(finishedRewritingDetails())); - connect(d->m_profilerDataModel, SIGNAL(reloadDocumentsForDetails()), d->m_detailsRewriter, SLOT(reloadDocuments())); + d->m_profilerModelManager = new QmlProfilerModelManager(&d->m_projectFinder, this); + connect(d->m_profilerModelManager, SIGNAL(stateChanged()), this, SLOT(profilerDataModelStateChanged())); + connect(d->m_profilerModelManager, SIGNAL(error(QString)), this, SLOT(showErrorDialog(QString))); + d->m_profilerConnections->setModelManager(d->m_profilerModelManager); Command *command = 0; const Context globalContext(C_GLOBAL); @@ -266,7 +245,7 @@ QWidget *QmlProfilerTool::createWidgets() d->m_viewContainer = new QmlProfilerViewManager(this, this, - d->m_profilerDataModel, + d->m_profilerModelManager, d->m_profilerState); connect(d->m_viewContainer, SIGNAL(gotoSourceLocation(QString,int,int)), this, SLOT(gotoSourceLocation(QString,int,int))); @@ -398,8 +377,9 @@ void QmlProfilerTool::updateTimeDisplay() if (d->m_profilerState->serverRecording() && d->m_profilerState->currentState() == QmlProfilerStateManager::AppRunning) { seconds = d->m_recordingElapsedTime.elapsed() / 1000.0; - } else if (d->m_profilerDataModel->currentState() != QmlProfilerDataModel::Empty ) { - seconds = (d->m_profilerDataModel->traceEndTime() - d->m_profilerDataModel->traceStartTime()) / 1.0e9; + } else if (d->m_profilerModelManager->state() != QmlProfilerDataState::Empty ) { + //seconds = d->m_profilerModelManager->traceDuration() / 1.0e9; + seconds = d->m_profilerModelManager->traceTime()->duration() / 1.0e9; } QString timeString = QString::number(seconds,'f',1); QString profilerTimeStr = QmlProfilerTool::tr("%1 s").arg(timeString, 6); @@ -408,7 +388,7 @@ void QmlProfilerTool::updateTimeDisplay() void QmlProfilerTool::clearData() { - d->m_profilerDataModel->clear(); + d->m_profilerModelManager->clear(); d->m_profilerConnections->discardPendingData(); } @@ -514,7 +494,7 @@ void QmlProfilerTool::showErrorDialog(const QString &error) void QmlProfilerTool::showSaveOption() { - d->m_saveQmlTrace->setEnabled(!d->m_profilerDataModel->isEmpty()); + d->m_saveQmlTrace->setEnabled(!d->m_profilerModelManager->isEmpty()); } void QmlProfilerTool::showSaveDialog() @@ -524,7 +504,7 @@ void QmlProfilerTool::showSaveDialog() if (!filename.isEmpty()) { if (!filename.endsWith(QLatin1String(TraceFileExtension))) filename += QLatin1String(TraceFileExtension); - d->m_profilerDataModel->save(filename); + d->m_profilerModelManager->save(filename); } } @@ -540,8 +520,8 @@ void QmlProfilerTool::showLoadDialog() if (!filename.isEmpty()) { // delayed load (prevent graphical artifacts due to long load time) - d->m_profilerDataModel->setFilename(filename); - QTimer::singleShot(100, d->m_profilerDataModel, SLOT(load())); + d->m_profilerModelManager->setFilename(filename); + QTimer::singleShot(100, d->m_profilerModelManager, SLOT(load())); } } @@ -549,7 +529,7 @@ void QmlProfilerTool::clientsDisconnected() { // If the application stopped by itself, check if we have all the data if (d->m_profilerState->currentState() == QmlProfilerStateManager::AppDying) { - if (d->m_profilerDataModel->currentState() == QmlProfilerDataModel::AcquiringData) + if (d->m_profilerModelManager->state() == QmlProfilerDataState::AcquiringData) d->m_profilerState->setCurrentState(QmlProfilerStateManager::AppKilled); else d->m_profilerState->setCurrentState(QmlProfilerStateManager::AppStopped); @@ -557,21 +537,20 @@ void QmlProfilerTool::clientsDisconnected() // ... and return to the "base" state d->m_profilerState->setCurrentState(QmlProfilerStateManager::Idle); } - // If the connection is closed while the app is still running, no special action is needed } void QmlProfilerTool::profilerDataModelStateChanged() { - switch (d->m_profilerDataModel->currentState()) { - case QmlProfilerDataModel::Empty : + switch (d->m_profilerModelManager->state()) { + case QmlProfilerDataState::Empty : clearDisplay(); break; - case QmlProfilerDataModel::AcquiringData : - case QmlProfilerDataModel::ProcessingData : + case QmlProfilerDataState::AcquiringData : + case QmlProfilerDataState::ProcessingData : // nothing to be done for these two break; - case QmlProfilerDataModel::Done : + case QmlProfilerDataState::Done : if (d->m_profilerState->currentState() == QmlProfilerStateManager::AppStopRequested) d->m_profilerState->setCurrentState(QmlProfilerStateManager::AppReadyToStop); showSaveOption(); @@ -623,7 +602,7 @@ void QmlProfilerTool::profilerStateChanged() } case QmlProfilerStateManager::AppKilled : { showNonmodalWarning(tr("Application finished before loading profiled data.\nPlease use the stop button instead.")); - d->m_profilerDataModel->clear(); + d->m_profilerModelManager->clear(); break; } case QmlProfilerStateManager::Idle : @@ -650,9 +629,13 @@ void QmlProfilerTool::serverRecordingChanged() setRecording(d->m_profilerState->serverRecording()); // clear the old data each time we start a new profiling session if (d->m_profilerState->serverRecording()) { + d->m_clearButton->setEnabled(false); clearData(); - d->m_profilerDataModel->prepareForWriting(); + d->m_profilerModelManager->prepareForWriting(); + } else { + d->m_clearButton->setEnabled(true); } + } else { + d->m_clearButton->setEnabled(true); } } - diff --git a/src/plugins/qmlprofiler/qmlprofilertracefile.cpp b/src/plugins/qmlprofiler/qmlprofilertracefile.cpp new file mode 100644 index 0000000000..c8405c3d3c --- /dev/null +++ b/src/plugins/qmlprofiler/qmlprofilertracefile.cpp @@ -0,0 +1,562 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, 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, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + +#include "qmlprofilertracefile.h" + +#include <utils/qtcassert.h> + +#include <QIODevice> +#include <QStringList> +#include <QXmlStreamReader> +#include <QXmlStreamWriter> +#include <QDebug> + +// import QmlEventType, QmlBindingType enums, QmlEventLocation +using namespace QmlDebug; + + +const char PROFILER_FILE_VERSION[] = "1.02"; + +const char TYPE_PAINTING_STR[] = "Painting"; +const char TYPE_COMPILING_STR[] = "Compiling"; +const char TYPE_CREATING_STR[] = "Creating"; +const char TYPE_BINDING_STR[] = "Binding"; +const char TYPE_HANDLINGSIGNAL_STR[] = "HandlingSignal"; +const char TYPE_PIXMAPCACHE_STR[] = "PixmapCache"; +const char TYPE_SCENEGRAPH_STR[] = "SceneGraph"; + +#define _(X) QLatin1String(X) + + +// +// "be strict in your output but tolerant in your inputs" +// + +namespace QmlProfiler { +namespace Internal { + +static QmlEventType qmlEventTypeAsEnum(const QString &typeString) +{ + if (typeString == _(TYPE_PAINTING_STR)) { + return Painting; + } else if (typeString == _(TYPE_COMPILING_STR)) { + return Compiling; + } else if (typeString == _(TYPE_CREATING_STR)) { + return Creating; + } else if (typeString == _(TYPE_BINDING_STR)) { + return Binding; + } else if (typeString == _(TYPE_HANDLINGSIGNAL_STR)) { + return HandlingSignal; + } else if (typeString == _(TYPE_PIXMAPCACHE_STR)) { + return PixmapCacheEvent; + } else if (typeString == _(TYPE_SCENEGRAPH_STR)) { + return SceneGraphFrameEvent; + } else { + bool isNumber = false; + int type = typeString.toUInt(&isNumber); + if (isNumber) + return (QmlEventType)type; + else + return MaximumQmlEventType; + } +} + +static QString qmlEventTypeAsString(QmlEventType typeEnum) +{ + switch (typeEnum) { + case Painting: + return _(TYPE_PAINTING_STR); + break; + case Compiling: + return _(TYPE_COMPILING_STR); + break; + case Creating: + return _(TYPE_CREATING_STR); + break; + case Binding: + return _(TYPE_BINDING_STR); + break; + case HandlingSignal: + return _(TYPE_HANDLINGSIGNAL_STR); + break; + case PixmapCacheEvent: + return _(TYPE_PIXMAPCACHE_STR); + break; + case SceneGraphFrameEvent: + return _(TYPE_SCENEGRAPH_STR); + break; + default: + return QString::number((int)typeEnum); + } +} + + +QmlProfilerFileReader::QmlProfilerFileReader(QObject *parent) : + QObject(parent), + m_v8Model(0) +{ +} + +void QmlProfilerFileReader::setV8DataModel(QV8ProfilerDataModel *dataModel) +{ + m_v8Model = dataModel; +} + +bool QmlProfilerFileReader::load(QIODevice *device) +{ + QXmlStreamReader stream(device); + + bool validVersion = true; + + while (validVersion && !stream.atEnd() && !stream.hasError()) { + QXmlStreamReader::TokenType token = stream.readNext(); + QString elementName = stream.name().toString(); + switch (token) { + case QXmlStreamReader::StartDocument : continue; + case QXmlStreamReader::StartElement : { + if (elementName == _("trace")) { + QXmlStreamAttributes attributes = stream.attributes(); + if (attributes.hasAttribute(_("version"))) + validVersion = attributes.value(_("version")).toString() + == _(PROFILER_FILE_VERSION); + else + validVersion = false; + if (attributes.hasAttribute(_("traceStart"))) + emit traceStartTime(attributes.value(_("traceStart")).toString().toLongLong()); + if (attributes.hasAttribute(_("traceEnd"))) + emit traceEndTime(attributes.value(_("traceEnd")).toString().toLongLong()); + } + + if (elementName == _("eventData")) { + loadEventData(stream); + break; + } + + if (elementName == _("profilerDataModel")) { + loadProfilerDataModel(stream); + break; + } + + if (elementName == _("v8profile")) { + if (m_v8Model) + m_v8Model->load(stream); + break; + } + + break; + } + default: break; + } + } + + if (stream.hasError()) { + emit error(tr("Error while parsing trace data file: %1").arg(stream.errorString())); + return false; + } else { + processQmlEvents(); + + return true; + } +} + +void QmlProfilerFileReader::loadEventData(QXmlStreamReader &stream) +{ + QTC_ASSERT(stream.name().toString() == _("eventData"), return); + + QXmlStreamAttributes attributes = stream.attributes(); + if (attributes.hasAttribute(_("totalTime"))) { + // not used any more + } + + int eventIndex = -1; + QmlEvent event = { + QString(), // displayname + QString(), // filename + QString(), // details + Painting, // type + QmlBinding, // bindingType, set for backwards compatibility + 0, // line + 0 // column + }; + const QmlEvent defaultEvent = event; + + while (!stream.atEnd() && !stream.hasError()) { + QXmlStreamReader::TokenType token = stream.readNext(); + const QString elementName = stream.name().toString(); + + switch (token) { + case QXmlStreamReader::StartElement: { + if (elementName == _("event")) { + event = defaultEvent; + + const QXmlStreamAttributes attributes = stream.attributes(); + if (attributes.hasAttribute(_("index"))) { + eventIndex = attributes.value(_("index")).toString().toInt(); + } else { + // ignore event + eventIndex = -1; + } + break; + } + + stream.readNext(); + if (stream.tokenType() != QXmlStreamReader::Characters) + break; + + const QString readData = stream.text().toString(); + + if (elementName == _("displayname")) { + event.displayName = readData; + break; + } + + if (elementName == _("type")) { + event.type = qmlEventTypeAsEnum(readData); + break; + } + + if (elementName == _("filename")) { + event.filename = readData; + break; + } + + if (elementName == _("line")) { + event.line = readData.toInt(); + break; + } + + if (elementName == _("column")) { + event.column = readData.toInt(); + break; + } + + if (elementName == _("details")) { + event.details = readData; + break; + } + + if (elementName == _("bindingType") || + elementName == _("animationFrame") || + elementName == _("cacheEventType") || + elementName == _("sgEventType")) { + event.bindingType = readData.toInt(); + break; + } + + break; + } + case QXmlStreamReader::EndElement: { + if (elementName == _("event")) { + if (eventIndex >= 0) { + if (eventIndex >= m_qmlEvents.size()) + m_qmlEvents.resize(eventIndex + 1); + m_qmlEvents[eventIndex] = event; + } + break; + } + + if (elementName == _("eventData")) { + // done reading eventData + return; + } + break; + } + default: break; + } // switch + } +} + +void QmlProfilerFileReader::loadProfilerDataModel(QXmlStreamReader &stream) +{ + QTC_ASSERT(stream.name().toString() == _("profilerDataModel"), return); + + while (!stream.atEnd() && !stream.hasError()) { + QXmlStreamReader::TokenType token = stream.readNext(); + const QString elementName = stream.name().toString(); + + switch (token) { + case QXmlStreamReader::StartElement: { + if (elementName == _("range")) { + Range range = { 0, 0, 0, 0, 0, 0, 0 }; + + const QXmlStreamAttributes attributes = stream.attributes(); + if (!attributes.hasAttribute(_("startTime")) + || !attributes.hasAttribute(_("eventIndex"))) { + // ignore incomplete entry + continue; + } + + range.startTime = attributes.value(_("startTime")).toString().toLongLong(); + if (attributes.hasAttribute(_("duration"))) + range.duration = attributes.value(_("duration")).toString().toLongLong(); + + // attributes for special events + if (attributes.hasAttribute(_("framerate"))) + range.numericData1 = attributes.value(_("framerate")).toString().toLongLong(); + if (attributes.hasAttribute(_("animationcount"))) + range.numericData2 = attributes.value(_("animationcount")).toString().toLongLong(); + if (attributes.hasAttribute(_("width"))) + range.numericData1 = attributes.value(_("width")).toString().toLongLong(); + if (attributes.hasAttribute(_("height"))) + range.numericData2 = attributes.value(_("height")).toString().toLongLong(); + if (attributes.hasAttribute(_("refCount"))) + range.numericData3 = attributes.value(_("refCount")).toString().toLongLong(); + if (attributes.hasAttribute(_("timing1"))) + range.numericData1 = attributes.value(_("timing1")).toString().toLongLong(); + if (attributes.hasAttribute(_("timing2"))) + range.numericData2 = attributes.value(_("timing2")).toString().toLongLong(); + if (attributes.hasAttribute(_("timing3"))) + range.numericData3 = attributes.value(_("timing3")).toString().toLongLong(); + if (attributes.hasAttribute(_("timing4"))) + range.numericData4 = attributes.value(_("timing4")).toString().toLongLong(); + if (attributes.hasAttribute(_("timing5"))) + range.numericData5 = attributes.value(_("timing5")).toString().toLongLong(); + + + int eventIndex = attributes.value(_("eventIndex")).toString().toInt(); + + + m_ranges.append(QPair<Range,int>(range, eventIndex)); + } + break; + } + case QXmlStreamReader::EndElement: { + if (elementName == _("profilerDataModel")) { + // done reading profilerDataModel + return; + } + break; + } + default: break; + } // switch + } +} + +void QmlProfilerFileReader::processQmlEvents() +{ + for (int i = 0; i < m_ranges.size(); ++i) { + Range range = m_ranges[i].first; + int eventIndex = m_ranges[i].second; + + if (eventIndex < 0 || eventIndex >= m_qmlEvents.size()) { + qWarning() << ".qtd file - range index" << eventIndex + << "is outside of bounds (0, " << m_qmlEvents.size() << ")"; + continue; + } + + QmlEvent &event = m_qmlEvents[eventIndex]; + + emit rangedEvent(event.type, event.bindingType, range.startTime, range.duration, + QStringList(event.displayName), + QmlEventLocation(event.filename, event.line, event.column), + range.numericData1,range.numericData2, range.numericData3, range.numericData4, range.numericData5); + + } +} + +QmlProfilerFileWriter::QmlProfilerFileWriter(QObject *parent) : + QObject(parent), + m_startTime(0), + m_endTime(0), + m_measuredTime(0), + m_v8Model(0) +{ + m_acceptedTypes << QmlDebug::Compiling << QmlDebug::Creating << QmlDebug::Binding << QmlDebug::HandlingSignal; +} + +void QmlProfilerFileWriter::setTraceTime(qint64 startTime, qint64 endTime, qint64 measuredTime) +{ + m_startTime = startTime; + m_endTime = endTime; + m_measuredTime = measuredTime; +} + +void QmlProfilerFileWriter::setV8DataModel(QV8ProfilerDataModel *dataModel) +{ + m_v8Model = dataModel; +} + +void QmlProfilerFileWriter::setQmlEvents(const QVector<QmlProfilerSimpleModel::QmlEventData> &events) +{ + foreach (const QmlProfilerSimpleModel::QmlEventData &event, events) { + const QString hashStr = QmlProfilerSimpleModel::getHashString(event); + if (!m_qmlEvents.contains(hashStr)) { + QmlEvent e = { event.displayName, + event.location.filename, + event.data.join(_("")), + static_cast<QmlDebug::QmlEventType>(event.eventType), + event.bindingType, + event.location.line, + event.location.column + }; + m_qmlEvents.insert(hashStr, e); + } + + Range r = { event.startTime, event.duration, event.numericData1, event.numericData2, event.numericData3, event.numericData4, event.numericData5 }; + m_ranges.append(QPair<Range, QString>(r, hashStr)); + } + + calculateMeasuredTime(events); +} + +void QmlProfilerFileWriter::save(QIODevice *device) +{ + QXmlStreamWriter stream(device); + + stream.setAutoFormatting(true); + stream.writeStartDocument(); + + stream.writeStartElement(_("trace")); + stream.writeAttribute(_("version"), _(PROFILER_FILE_VERSION)); + + stream.writeAttribute(_("traceStart"), QString::number(m_startTime)); + stream.writeAttribute(_("traceEnd"), QString::number(m_endTime)); + + stream.writeStartElement(_("eventData")); + stream.writeAttribute(_("totalTime"), QString::number(m_measuredTime)); + + QHash<QString,QmlEvent>::const_iterator eventIter = m_qmlEvents.constBegin(); + for (; eventIter != m_qmlEvents.constEnd(); ++eventIter) { + QmlEvent event = eventIter.value(); + + stream.writeStartElement(_("event")); + stream.writeAttribute(_("index"), QString::number(m_qmlEvents.keys().indexOf(eventIter.key()))); + stream.writeTextElement(_("displayname"), event.displayName); + stream.writeTextElement(_("type"), qmlEventTypeAsString(event.type)); + if (!event.filename.isEmpty()) { + stream.writeTextElement(_("filename"), event.filename); + stream.writeTextElement(_("line"), QString::number(event.line)); + stream.writeTextElement(_("column"), QString::number(event.column)); + } + stream.writeTextElement(_("details"), event.details); + if (event.type == Binding) + stream.writeTextElement(_("bindingType"), QString::number(event.bindingType)); + if (event.type == Painting && event.bindingType == AnimationFrame) + stream.writeTextElement(_("animationFrame"), QString::number(event.bindingType)); + if (event.type == PixmapCacheEvent) + stream.writeTextElement(_("cacheEventType"), QString::number(event.bindingType)); + if (event.type == SceneGraphFrameEvent) + stream.writeTextElement(_("sgEventType"), QString::number(event.bindingType)); + stream.writeEndElement(); + } + stream.writeEndElement(); // eventData + + stream.writeStartElement(_("profilerDataModel")); + + QVector<QPair<Range, QString> >::const_iterator rangeIter = m_ranges.constBegin(); + for (; rangeIter != m_ranges.constEnd(); ++rangeIter) { + Range range = rangeIter->first; + QString eventHash = rangeIter->second; + + stream.writeStartElement(_("range")); + stream.writeAttribute(_("startTime"), QString::number(range.startTime)); + if (range.duration > 0) // no need to store duration of instantaneous events + stream.writeAttribute(_("duration"), QString::number(range.duration)); + stream.writeAttribute(_("eventIndex"), QString::number(m_qmlEvents.keys().indexOf(eventHash))); + + QmlEvent event = m_qmlEvents.value(eventHash); + + // special: animation event + if (event.type == QmlDebug::Painting && event.bindingType == QmlDebug::AnimationFrame) { + + stream.writeAttribute(_("framerate"), QString::number(range.numericData1)); + stream.writeAttribute(_("animationcount"), QString::number(range.numericData2)); + } + + // special: pixmap cache event + if (event.type == QmlDebug::PixmapCacheEvent) { + // pixmap image size + if (event.bindingType == 0) { + stream.writeAttribute(_("width"), QString::number(range.numericData1)); + stream.writeAttribute(_("height"), QString::number(range.numericData2)); + } + + // reference count (1) / cache size changed (2) + if (event.bindingType == 1 || event.bindingType == 2) + stream.writeAttribute(_("refCount"), QString::number(range.numericData3)); + } + + if (event.type == QmlDebug::SceneGraphFrameEvent) { + // special: scenegraph frame events + if (range.numericData1 > 0) + stream.writeAttribute(_("timing1"), QString::number(range.numericData1)); + if (range.numericData2 > 0) + stream.writeAttribute(_("timing2"), QString::number(range.numericData2)); + if (range.numericData3 > 0) + stream.writeAttribute(_("timing3"), QString::number(range.numericData3)); + if (range.numericData4 > 0) + stream.writeAttribute(_("timing4"), QString::number(range.numericData4)); + if (range.numericData5 > 0) + stream.writeAttribute(_("timing5"), QString::number(range.numericData5)); + } + stream.writeEndElement(); + } + stream.writeEndElement(); // profilerDataModel + + m_v8Model->save(stream); + + stream.writeEndElement(); // trace + stream.writeEndDocument(); +} + +void QmlProfilerFileWriter::calculateMeasuredTime(const QVector<QmlProfilerSimpleModel::QmlEventData> &events) +{ + // measured time isn't used, but old clients might still need it + // -> we calculate it explicitly + + qint64 duration = 0; + + QHash<int, qint64> endtimesPerLevel; + int level = QmlDebug::Constants::QML_MIN_LEVEL; + endtimesPerLevel[0] = 0; + + foreach (const QmlProfilerSimpleModel::QmlEventData &event, events) { + // whitelist + if (!m_acceptedTypes.contains(event.eventType)) + continue; + + // level computation + if (endtimesPerLevel[level] > event.startTime) { + level++; + } else { + while (level > QmlDebug::Constants::QML_MIN_LEVEL && endtimesPerLevel[level-1] <= event.startTime) + level--; + } + endtimesPerLevel[level] = event.startTime + event.duration; + if (level == QmlDebug::Constants::QML_MIN_LEVEL) { + duration += event.duration; + } + } + + m_measuredTime = duration; +} + + +} // namespace Internal +} // namespace QmlProfiler diff --git a/src/plugins/qmlprofiler/qmlprofilertracefile.h b/src/plugins/qmlprofiler/qmlprofilertracefile.h new file mode 100644 index 0000000000..f4e1b71d10 --- /dev/null +++ b/src/plugins/qmlprofiler/qmlprofilertracefile.h @@ -0,0 +1,134 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, 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, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + +#ifndef QMLPROFILERTRACEFILE_H +#define QMLPROFILERTRACEFILE_H + +#include <QObject> +#include <QVector> +#include <QString> + +#include <qmldebug/qmlprofilereventlocation.h> +#include <qmldebug/qmlprofilereventtypes.h> + +#include "qmlprofilersimplemodel.h" +#include "qv8profilerdatamodel.h" + +QT_FORWARD_DECLARE_CLASS(QIODevice) +QT_FORWARD_DECLARE_CLASS(QXmlStreamReader) + + +namespace QmlProfiler { +namespace Internal { + + +struct QmlEvent { + QString displayName; + QString filename; + QString details; + QmlDebug::QmlEventType type; + int bindingType; + int line; + int column; +}; + +struct Range { + qint64 startTime; + qint64 duration; + + // numeric data used by animations, pixmap cache, scenegraph + qint64 numericData1; + qint64 numericData2; + qint64 numericData3; + qint64 numericData4; + qint64 numericData5; +}; + +class QmlProfilerFileReader : public QObject +{ + Q_OBJECT + +public: + explicit QmlProfilerFileReader(QObject *parent = 0); + + void setV8DataModel(QV8ProfilerDataModel *dataModel); + + bool load(QIODevice *device); + +signals: + void traceStartTime(qint64 traceStartTime); + void traceEndTime(qint64 traceStartTime); + + void rangedEvent(int type, int bindingType, qint64 startTime, qint64 length, + const QStringList &data, const QmlDebug::QmlEventLocation &location, + qint64 param1, qint64 param2, qint64 param3, qint64 param4, qint64 param5); + void error(const QString &error); + +private: + void loadEventData(QXmlStreamReader &reader); + void loadProfilerDataModel(QXmlStreamReader &reader); + + void processQmlEvents(); + + + QV8ProfilerDataModel *m_v8Model; + QVector<QmlEvent> m_qmlEvents; + QVector<QPair<Range, int> > m_ranges; +}; + + +class QmlProfilerFileWriter : public QObject +{ + Q_OBJECT + +public: + explicit QmlProfilerFileWriter(QObject *parent = 0); + + void setTraceTime(qint64 startTime, qint64 endTime, qint64 measturedTime); + void setV8DataModel(QV8ProfilerDataModel *dataModel); + void setQmlEvents(const QVector<QmlProfilerSimpleModel::QmlEventData> &events); + + void save(QIODevice *device); + +private: + void calculateMeasuredTime(const QVector<QmlProfilerSimpleModel::QmlEventData> &events); + + + qint64 m_startTime, m_endTime, m_measuredTime; + QV8ProfilerDataModel *m_v8Model; + QHash<QString,QmlEvent> m_qmlEvents; + QVector<QPair<Range, QString> > m_ranges; + QVector <int> m_acceptedTypes; +}; + + +} // namespace Internal +} // namespace QmlProfiler + +#endif // QMLPROFILERTRACEFILE_H diff --git a/src/plugins/qmlprofiler/qmlprofilertraceview.cpp b/src/plugins/qmlprofiler/qmlprofilertraceview.cpp index 5fbad1247a..c094a07f13 100644 --- a/src/plugins/qmlprofiler/qmlprofilertraceview.cpp +++ b/src/plugins/qmlprofiler/qmlprofilertraceview.cpp @@ -30,12 +30,14 @@ #include "qmlprofilertraceview.h" #include "qmlprofilertool.h" #include "qmlprofilerstatemanager.h" -#include "qmlprofilerdatamodel.h" +#include "qmlprofilermodelmanager.h" +#include "qmlprofilertimelinemodelproxy.h" +#include "timelinemodelaggregator.h" // Needed for the load&save actions in the context menu #include <analyzerbase/ianalyzertool.h> -// Comunication with the other views (limit events to range) +// Communication with the other views (limit events to range) #include "qmlprofilerviewmanager.h" #include <utils/styledbar.h> @@ -119,7 +121,9 @@ public: ScrollableDeclarativeView *m_mainView; QDeclarativeView *m_timebar; QDeclarativeView *m_overview; - QmlProfilerDataModel *m_profilerDataModel; + QmlProfilerModelManager *m_modelManager; + TimelineModelAggregator *m_modelProxy; + ZoomControl *m_zoomControl; @@ -129,7 +133,7 @@ public: int m_currentZoomLevel; }; -QmlProfilerTraceView::QmlProfilerTraceView(QWidget *parent, Analyzer::IAnalyzerTool *profilerTool, QmlProfilerViewManager *container, QmlProfilerDataModel *model, QmlProfilerStateManager *profilerState) +QmlProfilerTraceView::QmlProfilerTraceView(QWidget *parent, Analyzer::IAnalyzerTool *profilerTool, QmlProfilerViewManager *container, QmlProfilerModelManager *modelManager, QmlProfilerStateManager *profilerState) : QWidget(parent), d(new QmlProfilerTraceViewPrivate(this)) { setObjectName(QLatin1String("QML Profiler")); @@ -180,13 +184,15 @@ QmlProfilerTraceView::QmlProfilerTraceView(QWidget *parent, Analyzer::IAnalyzerT d->m_profilerTool = profilerTool; d->m_viewContainer = container; - d->m_profilerDataModel = model; - connect(d->m_profilerDataModel, SIGNAL(stateChanged()), + d->m_modelManager = modelManager; + d->m_modelProxy = new TimelineModelAggregator(this); + d->m_modelProxy->setModelManager(modelManager); + connect(d->m_modelManager, SIGNAL(stateChanged()), this, SLOT(profilerDataModelStateChanged())); - d->m_mainView->rootContext()->setContextProperty(QLatin1String("qmlProfilerDataModel"), - d->m_profilerDataModel); - d->m_overview->rootContext()->setContextProperty(QLatin1String("qmlProfilerDataModel"), - d->m_profilerDataModel); + d->m_mainView->rootContext()->setContextProperty(QLatin1String("qmlProfilerModelProxy"), + d->m_modelProxy); + d->m_overview->rootContext()->setContextProperty(QLatin1String("qmlProfilerModelProxy"), + d->m_modelProxy); d->m_profilerState = profilerState; connect(d->m_profilerState, SIGNAL(stateChanged()), @@ -372,12 +378,25 @@ void QmlProfilerTraceView::clearDisplay() QMetaObject::invokeMethod(d->m_overview->rootObject(), "clearDisplay"); } -void QmlProfilerTraceView::selectNextEventWithId(int eventId) +void QmlProfilerTraceView::selectNextEventByHash(const QString &hash) { QGraphicsObject *rootObject = d->m_mainView->rootObject(); + if (rootObject) - QMetaObject::invokeMethod(rootObject, "selectNextWithId", - Q_ARG(QVariant,QVariant(eventId))); + QMetaObject::invokeMethod(rootObject, "selectNextByHash", + Q_ARG(QVariant,QVariant(hash))); +} + +void QmlProfilerTraceView::selectNextEventByLocation(const QString &filename, const int line, const int column) +{ + int eventId = d->m_modelProxy->getEventIdForLocation(filename, line, column); + + if (eventId != -1) { + QGraphicsObject *rootObject = d->m_mainView->rootObject(); + if (rootObject) + QMetaObject::invokeMethod(rootObject, "selectNextById", + Q_ARG(QVariant,QVariant(eventId))); + } } ///////////////////////////////////////////////////////// @@ -444,14 +463,14 @@ void QmlProfilerTraceView::setZoomLevel(int zoomLevel) void QmlProfilerTraceView::updateRange() { - if (!d->m_profilerDataModel) + if (!d->m_modelManager) return; qreal duration = d->m_zoomControl->endTime() - d->m_zoomControl->startTime(); if (duration <= 0) return; - if (d->m_profilerDataModel->traceDuration() <= 0) + if (d->m_modelManager->traceTime()->duration() <= 0) return; - int newLevel = pow(duration / d->m_profilerDataModel->traceDuration(), 1/sliderExp) * sliderTicks; + int newLevel = pow(duration / d->m_modelManager->traceTime()->duration(), 1/sliderExp) * sliderTicks; if (d->m_currentZoomLevel != newLevel) { d->m_currentZoomLevel = newLevel; emit zoomLevelChanged(newLevel); @@ -513,7 +532,7 @@ void QmlProfilerTraceView::contextMenuEvent(QContextMenuEvent *ev) if (d->m_viewContainer->hasGlobalStats()) getGlobalStatsAction->setEnabled(false); - if (d->m_profilerDataModel->count() > 0) { + if (!d->m_modelProxy->isEmpty()) { menu.addSeparator(); viewAllAction = menu.addAction(tr("Reset Zoom")); } @@ -523,8 +542,8 @@ void QmlProfilerTraceView::contextMenuEvent(QContextMenuEvent *ev) if (selectedAction) { if (selectedAction == viewAllAction) { d->m_zoomControl->setRange( - d->m_profilerDataModel->traceStartTime(), - d->m_profilerDataModel->traceEndTime()); + d->m_modelManager->traceTime()->startTime(), + d->m_modelManager->traceTime()->endTime()); } if (selectedAction == getLocalStatsAction) { d->m_viewContainer->getStatisticsInRange( @@ -532,9 +551,7 @@ void QmlProfilerTraceView::contextMenuEvent(QContextMenuEvent *ev) d->m_viewContainer->selectionEnd()); } if (selectedAction == getGlobalStatsAction) { - d->m_viewContainer->getStatisticsInRange( - d->m_profilerDataModel->traceStartTime(), - d->m_profilerDataModel->traceEndTime()); + d->m_viewContainer->getStatisticsInRange(-1, -1); } } } @@ -558,19 +575,15 @@ void QmlProfilerTraceView::setAppKilled() // Profiler State void QmlProfilerTraceView::profilerDataModelStateChanged() { - switch (d->m_profilerDataModel->currentState()) { - case QmlProfilerDataModel::Empty : - emit enableToolbar(false); - break; - case QmlProfilerDataModel::AcquiringData : - // nothing to be done + switch (d->m_modelManager->state()) { + case QmlProfilerDataState::Empty: + emit enableToolbar(false); break; - case QmlProfilerDataModel::ProcessingData : - // nothing to be done + case QmlProfilerDataState::AcquiringData: break; + case QmlProfilerDataState::ProcessingData: break; + case QmlProfilerDataState::Done: + emit enableToolbar(true); break; - case QmlProfilerDataModel::Done : - emit enableToolbar(true); - break; default: break; } @@ -580,7 +593,7 @@ void QmlProfilerTraceView::profilerStateChanged() { switch (d->m_profilerState->currentState()) { case QmlProfilerStateManager::AppKilled : { - if (d->m_profilerDataModel->currentState() == QmlProfilerDataModel::AcquiringData) + if (d->m_modelManager->state() == QmlProfilerDataState::AcquiringData) setAppKilled(); break; } diff --git a/src/plugins/qmlprofiler/qmlprofilertraceview.h b/src/plugins/qmlprofiler/qmlprofilertraceview.h index 0ff15025ab..dd7d233ff0 100644 --- a/src/plugins/qmlprofiler/qmlprofilertraceview.h +++ b/src/plugins/qmlprofiler/qmlprofilertraceview.h @@ -37,11 +37,12 @@ class IAnalyzerTool; } namespace QmlProfiler { + +class QmlProfilerModelManager; namespace Internal { class QmlProfilerStateManager; class QmlProfilerViewManager; -class QmlProfilerDataModel; // capture mouse wheel events class MouseWheelResizer : public QObject { @@ -83,12 +84,13 @@ protected: void scrollContentsBy(int dx, int dy); }; + class QmlProfilerTraceView : public QWidget { Q_OBJECT public: - explicit QmlProfilerTraceView(QWidget *parent, Analyzer::IAnalyzerTool *profilerTool, QmlProfilerViewManager *container, QmlProfilerDataModel *model, QmlProfilerStateManager *profilerState); + explicit QmlProfilerTraceView(QWidget *parent, Analyzer::IAnalyzerTool *profilerTool, QmlProfilerViewManager *container, QmlProfilerModelManager *modelManager, QmlProfilerStateManager *profilerState); ~QmlProfilerTraceView(); void reset(); @@ -99,7 +101,8 @@ public: public slots: void clearDisplay(); - void selectNextEventWithId(int eventId); + void selectNextEventByHash(const QString &eventHash); + void selectNextEventByLocation(const QString &filename, const int line, const int column); private slots: void updateCursorPosition(); diff --git a/src/plugins/qmlprofiler/qmlprofilertreeview.cpp b/src/plugins/qmlprofiler/qmlprofilertreeview.cpp new file mode 100644 index 0000000000..0d10bbb4e9 --- /dev/null +++ b/src/plugins/qmlprofiler/qmlprofilertreeview.cpp @@ -0,0 +1,91 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, 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, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + +#include "qmlprofilertreeview.h" +#include <QCoreApplication> +#include <QHeaderView> + +namespace QmlProfiler { +namespace Internal { + +QmlProfilerTreeView::QmlProfilerTreeView(QWidget *parent) + : QTreeView(parent) +{ + setFrameStyle(QFrame::NoFrame); + header()->setResizeMode(QHeaderView::Interactive); + header()->setDefaultSectionSize(100); + header()->setMinimumSectionSize(50); +} + + +// Translate from "old" context to keep 2.8 string freeze +QString QmlProfilerTreeView::displayHeader(Fields header) const +{ + static const char ctxt1[] = "QmlProfiler::Internal::QmlProfilerEventsParentsAndChildrenView"; + static const char ctxt2[] = "QmlProfiler::Internal::QmlProfilerEventsMainView"; + + switch (header) { + case Callee: + return QCoreApplication::translate(ctxt1, "Callee"); + case CalleeDescription: + return QCoreApplication::translate(ctxt1, "Callee Description"); + case Caller: + return QCoreApplication::translate(ctxt1, "Caller"); + case CallerDescription: + return QCoreApplication::translate(ctxt1, "Caller Description"); + case CallCount: + return QCoreApplication::translate(ctxt2, "Calls"); + case Details: + return QCoreApplication::translate(ctxt2, "Details"); + case Location: + return QCoreApplication::translate(ctxt2, "Location"); + case MaxTime: + return QCoreApplication::translate(ctxt2, "Longest Time"); + case TimePerCall: + return QCoreApplication::translate(ctxt2, "Mean Time"); + case SelfTime: + return QCoreApplication::translate(ctxt2, "Self Time"); + case SelfTimeInPercent: + return QCoreApplication::translate(ctxt2, "Self Time in Percent"); + case MinTime: + return QCoreApplication::translate(ctxt2, "Shortest Time"); + case TimeInPercent: + return QCoreApplication::translate(ctxt2, "Time in Percent"); + case TotalTime: + return QCoreApplication::translate(ctxt2, "Total Time"); + case Type: + return QCoreApplication::translate(ctxt2, "Type"); + case MedianTime: + return QCoreApplication::translate(ctxt2, "Median Time"); + } + return QString(); +} + +} // namespace Internal +} // namespace QmlProfiler diff --git a/src/plugins/qmlprofiler/qmlprofilertreeview.h b/src/plugins/qmlprofiler/qmlprofilertreeview.h new file mode 100644 index 0000000000..73b2b5d769 --- /dev/null +++ b/src/plugins/qmlprofiler/qmlprofilertreeview.h @@ -0,0 +1,72 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, 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, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + +#ifndef QMLPROFILERTREEVIEW +#define QMLPROFILERTREEVIEW + +#include <QTreeView> + +namespace QmlProfiler { +namespace Internal { + +class QmlProfilerTreeView : public QTreeView +{ + Q_OBJECT + +protected: + QmlProfilerTreeView(QWidget *parent = 0); + + + enum Fields { + Name, + Callee, + CalleeDescription, + Caller, + CallerDescription, + CallCount, + Details, + Location, + MaxTime, + TimePerCall, + SelfTime, + SelfTimeInPercent, + MinTime, + TimeInPercent, + TotalTime, + Type, + MedianTime, + MaxFields + }; + QString displayHeader(Fields header) const; +}; + +} // namespace Internal +} // namespace QmlProfiler + +#endif // QMLPROFILERTREEVIEW diff --git a/src/plugins/qmlprofiler/qmlprofilerv8eventsview.cpp b/src/plugins/qmlprofiler/qmlprofilerv8eventsview.cpp new file mode 100644 index 0000000000..89bd948cfb --- /dev/null +++ b/src/plugins/qmlprofiler/qmlprofilerv8eventsview.cpp @@ -0,0 +1,1016 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, 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, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + +#include "qmlprofilereventview.h" + +#include <QUrl> +#include <QHash> + +#include <QStandardItem> +#include <QHeaderView> + +#include <QApplication> +#include <QClipboard> + +#include <QContextMenuEvent> +#include <QDebug> + +#include <coreplugin/minisplitter.h> +#include <QVBoxLayout> +#include <QHBoxLayout> + +#include "qmlprofilerviewmanager.h" +#include "qmlprofilertool.h" +#include <QMenu> + +#include <utils/qtcassert.h> + +using namespace QmlDebug; + +namespace QmlProfiler { +namespace Internal { + +#ifdef SHOW_BINDINGLOOPS +struct Colors { + Colors () { + this->bindingLoopBackground = QColor("orange").lighter(); + } + + QColor bindingLoopBackground; +}; + +Q_GLOBAL_STATIC(Colors, colors) +#endif + +//////////////////////////////////////////////////////////////////////////////////// + + +//////////////////////////////////////////////////////////////////////////////////// + +class EventsViewItem : public QStandardItem +{ +public: + EventsViewItem(const QString &text) : QStandardItem(text) {} + + virtual bool operator<(const QStandardItem &other) const + { + if (data().type() == QVariant::String) { + // first column + if (column() == 0) { + return data(FilenameRole).toString() == other.data(FilenameRole).toString() ? + data(LineRole).toInt() < other.data(LineRole).toInt() : + data(FilenameRole).toString() < other.data(FilenameRole).toString(); + } else { + return data().toString().toLower() < other.data().toString().toLower(); + } + } + + return data().toDouble() < other.data().toDouble(); + } +}; + +//////////////////////////////////////////////////////////////////////////////////// + +class QmlProfilerEventsWidget::QmlProfilerEventsWidgetPrivate +{ +public: + QmlProfilerEventsWidgetPrivate(QmlProfilerEventsWidget *qq):q(qq) {} + ~QmlProfilerEventsWidgetPrivate() {} + + QmlProfilerEventsWidget *q; + + Analyzer::IAnalyzerTool *m_profilerTool; + QmlProfilerViewManager *m_viewContainer; + + QmlProfilerEventsMainView *m_eventTree; + QmlProfilerEventRelativesView *m_eventChildren; + QmlProfilerEventRelativesView *m_eventParents; + + QmlProfilerEventsModelProxy *modelProxy; + + bool m_globalStatsEnabled; +}; + +QmlProfilerEventsWidget::QmlProfilerEventsWidget(QWidget *parent, + Analyzer::IAnalyzerTool *profilerTool, + QmlProfilerViewManager *container, + QmlProfilerModelManager *profilerModelManager ) + : QWidget(parent), d(new QmlProfilerEventsWidgetPrivate(this)) +{ + setObjectName(QLatin1String("QmlProfilerEventsView")); + + d->modelProxy = new QmlProfilerEventsModelProxy(profilerModelManager, this); + connect(profilerModelManager, SIGNAL(stateChanged()), + this, SLOT(profilerDataModelStateChanged())); + + d->m_eventTree = new QmlProfilerEventsMainView(this, d->modelProxy); + connect(d->m_eventTree, SIGNAL(gotoSourceLocation(QString,int,int)), this, SIGNAL(gotoSourceLocation(QString,int,int))); + connect(d->m_eventTree, SIGNAL(showEventInTimeline(int)), this, SIGNAL(showEventInTimeline(int))); + + d->m_eventChildren = new QmlProfilerEventRelativesView( + profilerModelManager, + new QmlProfilerEventChildrenModelProxy(profilerModelManager, this), + this); + d->m_eventParents = new QmlProfilerEventRelativesView( + profilerModelManager, + new QmlProfilerEventParentsModelProxy(profilerModelManager, this), + this); + connect(d->m_eventTree, SIGNAL(eventSelected(QString)), d->m_eventChildren, SLOT(displayEvent(QString))); + connect(d->m_eventTree, SIGNAL(eventSelected(QString)), d->m_eventParents, SLOT(displayEvent(QString))); + connect(d->m_eventChildren, SIGNAL(eventClicked(QString)), d->m_eventTree, SLOT(selectEvent(QString))); + connect(d->m_eventParents, SIGNAL(eventClicked(QString)), d->m_eventTree, SLOT(selectEvent(QString))); + + // widget arrangement + QVBoxLayout *groupLayout = new QVBoxLayout; + groupLayout->setContentsMargins(0,0,0,0); + groupLayout->setSpacing(0); + + Core::MiniSplitter *splitterVertical = new Core::MiniSplitter; + splitterVertical->addWidget(d->m_eventTree); + Core::MiniSplitter *splitterHorizontal = new Core::MiniSplitter; + splitterHorizontal->addWidget(d->m_eventParents); + splitterHorizontal->addWidget(d->m_eventChildren); + splitterHorizontal->setOrientation(Qt::Horizontal); + splitterVertical->addWidget(splitterHorizontal); + splitterVertical->setOrientation(Qt::Vertical); + splitterVertical->setStretchFactor(0,5); + splitterVertical->setStretchFactor(1,2); + groupLayout->addWidget(splitterVertical); + setLayout(groupLayout); + + d->m_profilerTool = profilerTool; + d->m_viewContainer = container; + d->m_globalStatsEnabled = true; + +} + +QmlProfilerEventsWidget::~QmlProfilerEventsWidget() +{ + delete d->modelProxy; + delete d; +} + +void QmlProfilerEventsWidget::profilerDataModelStateChanged() +{ +} + +void QmlProfilerEventsWidget::clear() +{ + d->m_eventTree->clear(); + d->m_eventChildren->clear(); + d->m_eventParents->clear(); +} + +void QmlProfilerEventsWidget::getStatisticsInRange(qint64 rangeStart, qint64 rangeEnd) +{ + clear(); + d->m_eventTree->getStatisticsInRange(rangeStart, rangeEnd); + d->m_globalStatsEnabled = d->m_eventTree->isRangeGlobal(rangeStart, rangeEnd); +} + +QModelIndex QmlProfilerEventsWidget::selectedItem() const +{ + return d->m_eventTree->selectedItem(); +} + +void QmlProfilerEventsWidget::contextMenuEvent(QContextMenuEvent *ev) +{ + QTC_ASSERT(d->m_viewContainer, return;); + + QMenu menu; + QAction *copyRowAction = 0; + QAction *copyTableAction = 0; + QAction *showExtendedStatsAction = 0; + QAction *getLocalStatsAction = 0; + QAction *getGlobalStatsAction = 0; + + QmlProfilerTool *profilerTool = qobject_cast<QmlProfilerTool *>(d->m_profilerTool); + QPoint position = ev->globalPos(); + + if (profilerTool) { + QList <QAction *> commonActions = profilerTool->profilerContextMenuActions(); + foreach (QAction *act, commonActions) { + menu.addAction(act); + } + } + + if (mouseOnTable(position)) { + menu.addSeparator(); + if (selectedItem().isValid()) + copyRowAction = menu.addAction(tr("Copy Row")); + copyTableAction = menu.addAction(tr("Copy Table")); + + showExtendedStatsAction = menu.addAction(tr("Extended Event Statistics")); + showExtendedStatsAction->setCheckable(true); + showExtendedStatsAction->setChecked(showExtendedStatistics()); + } + + menu.addSeparator(); + getLocalStatsAction = menu.addAction(tr("Limit Events Pane to Current Range")); + if (!d->m_viewContainer->hasValidSelection()) + getLocalStatsAction->setEnabled(false); + getGlobalStatsAction = menu.addAction(tr("Reset Events Pane")); + if (hasGlobalStats()) + getGlobalStatsAction->setEnabled(false); + + QAction *selectedAction = menu.exec(position); + + if (selectedAction) { + if (selectedAction == copyRowAction) + copyRowToClipboard(); + if (selectedAction == copyTableAction) + copyTableToClipboard(); + if (selectedAction == getLocalStatsAction) { + getStatisticsInRange(d->m_viewContainer->selectionStart(), + d->m_viewContainer->selectionEnd()); + } + + if (selectedAction == showExtendedStatsAction) + setShowExtendedStatistics(!showExtendedStatistics()); + } +} + +void QmlProfilerEventsWidget::resizeEvent(QResizeEvent *event) +{ + QWidget::resizeEvent(event); + emit resized(); +} + +bool QmlProfilerEventsWidget::mouseOnTable(const QPoint &position) const +{ + QPoint tableTopLeft = d->m_eventTree->mapToGlobal(QPoint(0,0)); + QPoint tableBottomRight = d->m_eventTree->mapToGlobal(QPoint(d->m_eventTree->width(), d->m_eventTree->height())); + return (position.x() >= tableTopLeft.x() && position.x() <= tableBottomRight.x() && position.y() >= tableTopLeft.y() && position.y() <= tableBottomRight.y()); +} + +void QmlProfilerEventsWidget::copyTableToClipboard() const +{ + d->m_eventTree->copyTableToClipboard(); +} + +void QmlProfilerEventsWidget::copyRowToClipboard() const +{ + d->m_eventTree->copyRowToClipboard(); +} + +void QmlProfilerEventsWidget::updateSelectedEvent(const QString &eventHash) const +{ + if (d->m_eventTree->selectedEventHash() != eventHash) + d->m_eventTree->selectEvent(eventHash); +} + +void QmlProfilerEventsWidget::selectBySourceLocation(const QString &filename, int line, int column) +{ + // This slot is used to connect the javascript pane with the qml events pane + // Our javascript trace data does not store column information + // thus we ignore it here + Q_UNUSED(column); + d->m_eventTree->selectEventByLocation(filename, line); +} + +bool QmlProfilerEventsWidget::hasGlobalStats() const +{ + return d->m_globalStatsEnabled; +} + +void QmlProfilerEventsWidget::setShowExtendedStatistics(bool show) +{ + d->m_eventTree->setShowExtendedStatistics(show); +} + +bool QmlProfilerEventsWidget::showExtendedStatistics() const +{ + return d->m_eventTree->showExtendedStatistics(); +} + +//////////////////////////////////////////////////////////////////////////////////// + +class QmlProfilerEventsMainView::QmlProfilerEventsMainViewPrivate +{ +public: + QmlProfilerEventsMainViewPrivate(QmlProfilerEventsMainView *qq) : q(qq) {} + + int getFieldCount(); + + QString textForItem(QStandardItem *item, bool recursive) const; + + + QmlProfilerEventsMainView *q; + + QmlProfilerEventsModelProxy *modelProxy; + QStandardItemModel *m_model; + QList<bool> m_fieldShown; + QHash<int, int> m_columnIndex; // maps field enum to column index + bool m_showExtendedStatistics; + int m_firstNumericColumn; + bool m_preventSelectBounce; +}; + + +//////////////////////////////////////////////////////////////////////////////////// + +QmlProfilerEventsMainView::QmlProfilerEventsMainView(QWidget *parent, + QmlProfilerEventsModelProxy *modelProxy) +: QTreeView(parent), d(new QmlProfilerEventsMainViewPrivate(this)) +{ + setObjectName(QLatin1String("QmlProfilerEventsTable")); + header()->setResizeMode(QHeaderView::Interactive); + header()->setDefaultSectionSize(100); + header()->setMinimumSectionSize(50); + setSortingEnabled(false); + setFrameStyle(QFrame::NoFrame); + + d->m_model = new QStandardItemModel(this); + setModel(d->m_model); + connect(this,SIGNAL(clicked(QModelIndex)), this,SLOT(jumpToItem(QModelIndex))); + + d->modelProxy = modelProxy; + connect(d->modelProxy,SIGNAL(dataAvailable()), this, SLOT(buildModel())); +// connect(d->modelProxy,SIGNAL(stateChanged()), +// this,SLOT(profilerDataModelStateChanged())); + d->m_firstNumericColumn = 0; + d->m_preventSelectBounce = false; + d->m_showExtendedStatistics = false; + + setFieldViewable(Name, true); + setFieldViewable(Type, true); + setFieldViewable(Percent, true); + setFieldViewable(TotalDuration, true); + setFieldViewable(SelfPercent, false); + setFieldViewable(SelfDuration, false); + setFieldViewable(CallCount, true); + setFieldViewable(TimePerCall, true); + setFieldViewable(MaxTime, true); + setFieldViewable(MinTime, true); + setFieldViewable(MedianTime, true); + setFieldViewable(Details, true); + + buildModel(); +} + +QmlProfilerEventsMainView::~QmlProfilerEventsMainView() +{ + clear(); + //delete d->modelProxy; + delete d->m_model; + delete d; +} + +void QmlProfilerEventsMainView::profilerDataModelStateChanged() +{ +} + +void QmlProfilerEventsMainView::setFieldViewable(Fields field, bool show) +{ + if (field < MaxFields) { + int length = d->m_fieldShown.count(); + if (field >= length) { + for (int i=length; i<MaxFields; i++) + d->m_fieldShown << false; + } + d->m_fieldShown[field] = show; + } +} + + +void QmlProfilerEventsMainView::setHeaderLabels() +{ + int fieldIndex = 0; + d->m_firstNumericColumn = 0; + + d->m_columnIndex.clear(); + if (d->m_fieldShown[Name]) { + d->m_columnIndex[Name] = fieldIndex; + d->m_model->setHeaderData(fieldIndex++, Qt::Horizontal, QVariant(tr("Location"))); + d->m_firstNumericColumn++; + } + if (d->m_fieldShown[Type]) { + d->m_columnIndex[Type] = fieldIndex; + d->m_model->setHeaderData(fieldIndex++, Qt::Horizontal, QVariant(tr("Type"))); + d->m_firstNumericColumn++; + } + if (d->m_fieldShown[Percent]) { + d->m_columnIndex[Percent] = fieldIndex; + d->m_model->setHeaderData(fieldIndex++, Qt::Horizontal, QVariant(tr("Time in Percent"))); + } + if (d->m_fieldShown[TotalDuration]) { + d->m_columnIndex[TotalDuration] = fieldIndex; + d->m_model->setHeaderData(fieldIndex++, Qt::Horizontal, QVariant(tr("Total Time"))); + } + if (d->m_fieldShown[SelfPercent]) { + d->m_columnIndex[Type] = fieldIndex; + d->m_model->setHeaderData(fieldIndex++, Qt::Horizontal, QVariant(tr("Self Time in Percent"))); + } + if (d->m_fieldShown[SelfDuration]) { + d->m_columnIndex[SelfDuration] = fieldIndex; + d->m_model->setHeaderData(fieldIndex++, Qt::Horizontal, QVariant(tr("Self Time"))); + } + if (d->m_fieldShown[CallCount]) { + d->m_columnIndex[CallCount] = fieldIndex; + d->m_model->setHeaderData(fieldIndex++, Qt::Horizontal, QVariant(tr("Calls"))); + } + if (d->m_fieldShown[TimePerCall]) { + d->m_columnIndex[TimePerCall] = fieldIndex; + d->m_model->setHeaderData(fieldIndex++, Qt::Horizontal, QVariant(tr("Mean Time"))); + } + if (d->m_fieldShown[MedianTime]) { + d->m_columnIndex[MedianTime] = fieldIndex; + d->m_model->setHeaderData(fieldIndex++, Qt::Horizontal, QVariant(tr("Median Time"))); + } + if (d->m_fieldShown[MaxTime]) { + d->m_columnIndex[MaxTime] = fieldIndex; + d->m_model->setHeaderData(fieldIndex++, Qt::Horizontal, QVariant(tr("Longest Time"))); + } + if (d->m_fieldShown[MinTime]) { + d->m_columnIndex[MinTime] = fieldIndex; + d->m_model->setHeaderData(fieldIndex++, Qt::Horizontal, QVariant(tr("Shortest Time"))); + } + if (d->m_fieldShown[Details]) { + d->m_columnIndex[Details] = fieldIndex; + d->m_model->setHeaderData(fieldIndex++, Qt::Horizontal, QVariant(tr("Details"))); + } +} + +void QmlProfilerEventsMainView::setShowExtendedStatistics(bool show) +{ + // Not checking if already set because we don't want the first call to skip + d->m_showExtendedStatistics = show; + if (show) { + if (d->m_fieldShown[MedianTime]) + showColumn(d->m_columnIndex[MedianTime]); + if (d->m_fieldShown[MaxTime]) + showColumn(d->m_columnIndex[MaxTime]); + if (d->m_fieldShown[MinTime]) + showColumn(d->m_columnIndex[MinTime]); + } else{ + if (d->m_fieldShown[MedianTime]) + hideColumn(d->m_columnIndex[MedianTime]); + if (d->m_fieldShown[MaxTime]) + hideColumn(d->m_columnIndex[MaxTime]); + if (d->m_fieldShown[MinTime]) + hideColumn(d->m_columnIndex[MinTime]); + } +} + +bool QmlProfilerEventsMainView::showExtendedStatistics() const +{ + return d->m_showExtendedStatistics; +} + +void QmlProfilerEventsMainView::clear() +{ + d->m_model->clear(); + d->m_model->setColumnCount(d->getFieldCount()); + + setHeaderLabels(); + setSortingEnabled(false); +} + +int QmlProfilerEventsMainView::QmlProfilerEventsMainViewPrivate::getFieldCount() +{ + int count = 0; + for (int i=0; i < m_fieldShown.count(); ++i) + if (m_fieldShown[i]) + count++; + return count; +} + +void QmlProfilerEventsMainView::buildModel() +{ + clear(); + parseModelProxy(); + setShowExtendedStatistics(d->m_showExtendedStatistics); + + setRootIsDecorated(false); + setSortingEnabled(true); + sortByColumn(d->m_firstNumericColumn,Qt::DescendingOrder); + + expandAll(); + if (d->m_fieldShown[Name]) + resizeColumnToContents(0); + + if (d->m_fieldShown[Type]) + resizeColumnToContents(d->m_fieldShown[Name]?1:0); + collapseAll(); +} + +void QmlProfilerEventsMainView::parseModelProxy() +{ + const QList <QmlProfilerEventsModelProxy::QmlEventStats> eventList = d->modelProxy->getData(); + foreach (const QmlProfilerEventsModelProxy::QmlEventStats &event, eventList) { + QStandardItem *parentItem = d->m_model->invisibleRootItem(); + QList<QStandardItem *> newRow; + + if (d->m_fieldShown[Name]) + newRow << new EventsViewItem(event.displayName); + + if (d->m_fieldShown[Type]) { + QString typeString = QmlProfilerEventsMainView::nameForType(event.eventType); + QString toolTipText; + if (event.eventType == Binding) { + if (event.bindingType == (int)OptimizedBinding) { + typeString = typeString + tr(" (Opt)"); + toolTipText = tr("Binding is evaluated by the optimized engine."); + } else if (event.bindingType == (int)V8Binding) { + toolTipText = tr("Binding not optimized (e.g. has side effects or assignments,\n" + "references to elements in other files, loops, etc.)"); + + } + } + newRow << new EventsViewItem(typeString); + newRow.last()->setData(QVariant(typeString)); + if (!toolTipText.isEmpty()) + newRow.last()->setToolTip(toolTipText); + } + + if (d->m_fieldShown[Percent]) { + newRow << new EventsViewItem(QString::number(event.percentOfTime,'f',2)+QLatin1String(" %")); + newRow.last()->setData(QVariant(event.percentOfTime)); + } + + if (d->m_fieldShown[TotalDuration]) { + newRow << new EventsViewItem(displayTime(event.duration)); + newRow.last()->setData(QVariant(event.duration)); + } + + if (d->m_fieldShown[CallCount]) { + newRow << new EventsViewItem(QString::number(event.calls)); + newRow.last()->setData(QVariant(event.calls)); + } + + if (d->m_fieldShown[TimePerCall]) { + newRow << new EventsViewItem(displayTime(event.timePerCall)); + newRow.last()->setData(QVariant(event.timePerCall)); + } + + if (d->m_fieldShown[MedianTime]) { + newRow << new EventsViewItem(displayTime(event.medianTime)); + newRow.last()->setData(QVariant(event.medianTime)); + } + + if (d->m_fieldShown[MaxTime]) { + newRow << new EventsViewItem(displayTime(event.maxTime)); + newRow.last()->setData(QVariant(event.maxTime)); + } + + if (d->m_fieldShown[MinTime]) { + newRow << new EventsViewItem(displayTime(event.minTime)); + newRow.last()->setData(QVariant(event.minTime)); + } + + if (d->m_fieldShown[Details]) { + newRow << new EventsViewItem(event.details); + newRow.last()->setData(QVariant(event.details)); + } + + + + if (!newRow.isEmpty()) { + // no edit + foreach (QStandardItem *item, newRow) + item->setEditable(false); + + // metadata + newRow.at(0)->setData(QVariant(event.eventHashStr),EventHashStrRole); + newRow.at(0)->setData(QVariant(event.location.filename),FilenameRole); + newRow.at(0)->setData(QVariant(event.location.line),LineRole); + newRow.at(0)->setData(QVariant(event.location.column),ColumnRole); +// newRow.at(0)->setData(QVariant(event.eventId),EventIdRole); +// if (event.isBindingLoop) +// foreach (QStandardItem *item, newRow) { +// item->setBackground(colors()->bindingLoopBackground); +// item->setToolTip(tr("Binding loop detected.")); +// } + + // append + parentItem->appendRow(newRow); + } + } +} + +QString QmlProfilerEventsMainView::displayTime(double time) +{ + if (time < 1e6) + return QString::number(time/1e3,'f',3) + trUtf8(" \xc2\xb5s"); + if (time < 1e9) + return QString::number(time/1e6,'f',3) + tr(" ms"); + + return QString::number(time/1e9,'f',3) + tr(" s"); +} + +QString QmlProfilerEventsMainView::nameForType(int typeNumber) +{ + switch (typeNumber) { + case 0: return QmlProfilerEventsMainView::tr("Paint"); + case 1: return QmlProfilerEventsMainView::tr("Compile"); + case 2: return QmlProfilerEventsMainView::tr("Create"); + case 3: return QmlProfilerEventsMainView::tr("Binding"); + case 4: return QmlProfilerEventsMainView::tr("Signal"); + } + return QString(); +} + +void QmlProfilerEventsMainView::getStatisticsInRange(qint64 rangeStart, qint64 rangeEnd) +{ +} + +bool QmlProfilerEventsMainView::isRangeGlobal(qint64 rangeStart, qint64 rangeEnd) const +{ +} + +const QString &QmlProfilerEventsMainView::selectedEventHash() const +{ + QModelIndex index = selectedItem(); + if (!index.isValid()) + return QString(); + QStandardItem *item = d->m_model->item(index.row(), 0); + return item->data(EventHashStrRole).toString(); +} + + +void QmlProfilerEventsMainView::jumpToItem(const QModelIndex &index) +{ + if (d->m_preventSelectBounce) + return; + + d->m_preventSelectBounce = true; + QStandardItem *clickedItem = d->m_model->itemFromIndex(index); + QStandardItem *infoItem; + if (clickedItem->parent()) + infoItem = clickedItem->parent()->child(clickedItem->row(), 0); + else + infoItem = d->m_model->item(index.row(), 0); + + // show in editor + int line = infoItem->data(LineRole).toInt(); + int column = infoItem->data(ColumnRole).toInt(); + QString fileName = infoItem->data(FilenameRole).toString(); + if (line!=-1 && !fileName.isEmpty()) + emit gotoSourceLocation(fileName, line, column); + + // show in callers/callees subwindow + emit eventSelected(infoItem->data(EventHashStrRole).toString()); + + // show in timelinerenderer + emit showEventInTimeline(infoItem->data(EventIdRole).toInt()); + + d->m_preventSelectBounce = false; +} + +void QmlProfilerEventsMainView::selectEvent(const QString &eventHash) +{ + for (int i=0; i<d->m_model->rowCount(); i++) { + QStandardItem *infoItem = d->m_model->item(i, 0); + if (infoItem->data(EventHashStrRole).toString() == eventHash) { + setCurrentIndex(d->m_model->indexFromItem(infoItem)); + jumpToItem(currentIndex()); + return; + } + } +} + +void QmlProfilerEventsMainView::selectEventByLocation(const QString &filename, int line) +{ + if (d->m_preventSelectBounce) + return; + + for (int i=0; i<d->m_model->rowCount(); i++) { + QStandardItem *infoItem = d->m_model->item(i, 0); + if (currentIndex() != d->m_model->indexFromItem(infoItem) && infoItem->data(FilenameRole).toString() == filename && infoItem->data(LineRole).toInt() == line) { + setCurrentIndex(d->m_model->indexFromItem(infoItem)); + jumpToItem(currentIndex()); + return; + } + } +} + +QModelIndex QmlProfilerEventsMainView::selectedItem() const +{ + QModelIndexList sel = selectedIndexes(); + if (sel.isEmpty()) + return QModelIndex(); + else + return sel.first(); +} + +void QmlProfilerEventsMainView::changeDetailsForEvent(int eventId, const QString &newString) +{ + for (int i=0; i<d->m_model->rowCount(); i++) { + QStandardItem *infoItem = d->m_model->item(i, 0); + if (infoItem->data(EventIdRole).toInt() == eventId) { + d->m_model->item(i,d->m_columnIndex[Details])->setData(QVariant(newString),Qt::DisplayRole); + d->m_model->item(i,d->m_columnIndex[Details])->setData(QVariant(newString)); + return; + } + } +} + +QString QmlProfilerEventsMainView::QmlProfilerEventsMainViewPrivate::textForItem(QStandardItem *item, bool recursive = true) const +{ + QString str; + + if (recursive) { + // indentation + QStandardItem *itemParent = item->parent(); + while (itemParent) { + str += QLatin1String(" "); + itemParent = itemParent->parent(); + } + } + + // item's data + int colCount = m_model->columnCount(); + for (int j = 0; j < colCount; ++j) { + QStandardItem *colItem = item->parent() ? item->parent()->child(item->row(),j) : m_model->item(item->row(),j); + str += colItem->data(Qt::DisplayRole).toString(); + if (j < colCount-1) str += QLatin1Char('\t'); + } + str += QLatin1Char('\n'); + + // recursively print children + if (recursive && item->child(0)) + for (int j = 0; j != item->rowCount(); j++) + str += textForItem(item->child(j)); + + return str; +} + +void QmlProfilerEventsMainView::copyTableToClipboard() const +{ + QString str; + // headers + int columnCount = d->m_model->columnCount(); + for (int i = 0; i < columnCount; ++i) { + str += d->m_model->headerData(i, Qt::Horizontal, Qt::DisplayRole).toString(); + if (i < columnCount - 1) + str += QLatin1Char('\t'); + else + str += QLatin1Char('\n'); + } + // data + int rowCount = d->m_model->rowCount(); + for (int i = 0; i != rowCount; ++i) { + str += d->textForItem(d->m_model->item(i)); + } + QClipboard *clipboard = QApplication::clipboard(); + clipboard->setText(str, QClipboard::Selection); + clipboard->setText(str, QClipboard::Clipboard); +} + +void QmlProfilerEventsMainView::copyRowToClipboard() const +{ + QString str; + str = d->textForItem(d->m_model->itemFromIndex(selectedItem()), false); + + QClipboard *clipboard = QApplication::clipboard(); + clipboard->setText(str, QClipboard::Selection); + clipboard->setText(str, QClipboard::Clipboard); +} + +//////////////////////////////////////////////////////////////////////////////////// +#ifdef SEE_CHILDREN + +QmlProfilerEventsParentsAndChildrenView::~QmlProfilerEventsParentsAndChildrenView() +{ +} + +void QmlProfilerEventsParentsAndChildrenView::setViewType(SubViewType type) +{ + m_subtableType = type; + updateHeader(); +} + +void QmlProfilerEventsParentsAndChildrenView::displayEvent(int eventId) +{ + +} + +void QmlProfilerEventsParentsAndChildrenView::rebuildTree(void *profilerDataModel) +{ +} + +void QmlProfilerEventsParentsAndChildrenView::clear() +{ + if (treeModel()) { + treeModel()->clear(); + updateHeader(); + } +} + +void QmlProfilerEventsParentsAndChildrenView::updateHeader() +{ + bool isV8 = m_subtableType == V8ParentsView || m_subtableType == V8ChildrenView; + bool isChildren = m_subtableType == ChildrenView || m_subtableType == V8ChildrenView; + + header()->setResizeMode(QHeaderView::Interactive); + header()->setDefaultSectionSize(100); + header()->setMinimumSectionSize(50); + + if (treeModel()) { + if (isV8) + treeModel()->setColumnCount(3); + else + treeModel()->setColumnCount(5); + + int columnIndex = 0; + if (isChildren) + treeModel()->setHeaderData(columnIndex++, Qt::Horizontal, QVariant(tr("Callee"))); + else + treeModel()->setHeaderData(columnIndex++, Qt::Horizontal, QVariant(tr("Caller"))); + + if (!isV8) + treeModel()->setHeaderData(columnIndex++, Qt::Horizontal, QVariant(tr("Type"))); + + treeModel()->setHeaderData(columnIndex++, Qt::Horizontal, QVariant(tr("Total Time"))); + + if (!isV8) + treeModel()->setHeaderData(columnIndex++, Qt::Horizontal, QVariant(tr("Calls"))); + + if (isChildren) + treeModel()->setHeaderData(columnIndex++, Qt::Horizontal, QVariant(tr("Callee Description"))); + else + treeModel()->setHeaderData(columnIndex++, Qt::Horizontal, QVariant(tr("Caller Description"))); + } +} + +QStandardItemModel *QmlProfilerEventsParentsAndChildrenView::treeModel() +{ + return qobject_cast<QStandardItemModel *>(model()); +} + +void QmlProfilerEventsParentsAndChildrenView::jumpToItem(const QModelIndex &index) +{ + if (treeModel()) { + QStandardItem *infoItem = treeModel()->item(index.row(), 0); + emit eventClicked(infoItem->data(EventIdRole).toInt()); + } +} +#else +class QmlProfilerEventRelativesView::QmlProfilerEventParentsViewPrivate +{ +public: + QmlProfilerEventParentsViewPrivate(QmlProfilerEventRelativesView *qq):q(qq) {} + ~QmlProfilerEventParentsViewPrivate() {} + + QmlProfilerEventRelativesModelProxy *modelProxy; + + QmlProfilerEventRelativesView *q; +}; + +QmlProfilerEventRelativesView::QmlProfilerEventRelativesView(QmlProfilerModelManager *modelManager, QmlProfilerEventRelativesModelProxy *modelProxy, QWidget *parent) + : QTreeView(parent), d(new QmlProfilerEventParentsViewPrivate(this)) +{ + Q_UNUSED(modelManager); + //m_profilerModelManager = modelManager; + d->modelProxy = modelProxy; //new QmlProfilerEventParentsModelProxy(modelManager, this); + setModel(new QStandardItemModel(this)); + setRootIsDecorated(false); + setFrameStyle(QFrame::NoFrame); + updateHeader(); + + connect(this,SIGNAL(clicked(QModelIndex)), this,SLOT(jumpToItem(QModelIndex))); +} + +QmlProfilerEventRelativesView::~QmlProfilerEventRelativesView() +{ + //delete d->modelProxy; + delete d; +} + +void QmlProfilerEventRelativesView::displayEvent(const QString &eventHash) +{ + // TODO: what if it's not there? +// QmlProfilerEventParentsModelProxy::QmlEventLinks qmlEvent = d->modelProxy->getData(eventHash); + rebuildTree(d->modelProxy->getData(eventHash)); + + updateHeader(); + resizeColumnToContents(0); + setSortingEnabled(true); + sortByColumn(2); +} + +void QmlProfilerEventRelativesView::rebuildTree(QmlProfilerEventRelativesModelProxy::QmlEventRelativesMap eventMap) +{ + Q_ASSERT(treeModel()); + treeModel()->clear(); + + QStandardItem *topLevelItem = treeModel()->invisibleRootItem(); + + //foreach (const QmlProfilerEventParentsModelProxy::QmlEventParentData &event, eventMap.values()) { + foreach (const QString &key, eventMap.keys()) { + const QmlProfilerEventRelativesModelProxy::QmlEventRelativesData &event = eventMap[key]; + QList<QStandardItem *> newRow; + + // ToDo: here we were going to search for the data in the other modelproxy + // maybe we should store the data in this proxy and get it here + // no indirections at this level of abstraction! + newRow << new EventsViewItem(event.displayName); + newRow << new EventsViewItem(QmlProfilerEventsMainView::nameForType(event.eventType)); + newRow << new EventsViewItem(QmlProfilerEventsMainView::displayTime(event.duration)); + newRow << new EventsViewItem(QString::number(event.calls)); + newRow << new EventsViewItem(event.details); + +// newRow << new EventsViewItem(event->reference->displayName); +// newRow << new EventsViewItem(QmlProfilerEventsMainView::nameForType(event->reference->eventType)); +// newRow << new EventsViewItem(QmlProfilerEventsMainView::displayTime(event->duration)); +// newRow << new EventsViewItem(QString::number(event->calls)); +// newRow << new EventsViewItem(event->reference->details); + newRow.at(0)->setData(QVariant(key), EventHashStrRole); +// newRow.at(0)->setData(QVariant(event->reference->eventId), EventIdRole); + newRow.at(2)->setData(QVariant(event.duration)); + newRow.at(3)->setData(QVariant(event.calls)); +// if (event->inLoopPath) +// foreach (QStandardItem *item, newRow) { +// item->setBackground(colors()->bindingLoopBackground); +// item->setToolTip(tr("Part of binding loop.")); +// } + + foreach (QStandardItem *item, newRow) + item->setEditable(false); + + topLevelItem->appendRow(newRow); + } +} + +void QmlProfilerEventRelativesView::clear() +{ + if (treeModel()) { + treeModel()->clear(); + updateHeader(); + } +} + +void QmlProfilerEventRelativesView::updateHeader() +{ + bool calleesView = qobject_cast<QmlProfilerEventChildrenModelProxy *>(d->modelProxy) != 0; + + header()->setResizeMode(QHeaderView::Interactive); + header()->setDefaultSectionSize(100); + header()->setMinimumSectionSize(50); + + if (treeModel()) { + treeModel()->setColumnCount(5); + + int columnIndex = 0; + if (calleesView) + treeModel()->setHeaderData(columnIndex++, Qt::Horizontal, QVariant(tr("Callee"))); + else + treeModel()->setHeaderData(columnIndex++, Qt::Horizontal, QVariant(tr("Caller"))); + + treeModel()->setHeaderData(columnIndex++, Qt::Horizontal, QVariant(tr("Type"))); + + treeModel()->setHeaderData(columnIndex++, Qt::Horizontal, QVariant(tr("Total Time"))); + + treeModel()->setHeaderData(columnIndex++, Qt::Horizontal, QVariant(tr("Calls"))); + + + if (calleesView) + treeModel()->setHeaderData(columnIndex++, Qt::Horizontal, QVariant(tr("Callee Description"))); + else + treeModel()->setHeaderData(columnIndex++, Qt::Horizontal, QVariant(tr("Caller Description"))); + } +} + +QStandardItemModel *QmlProfilerEventRelativesView::treeModel() +{ + return qobject_cast<QStandardItemModel *>(model()); +} + +void QmlProfilerEventRelativesView::jumpToItem(const QModelIndex &index) +{ + if (treeModel()) { + QStandardItem *infoItem = treeModel()->item(index.row(), 0); + emit eventClicked(infoItem->data(EventHashStrRole).toString()); + } +} + +#endif + +} // namespace Internal +} // namespace QmlProfiler diff --git a/src/plugins/qmlprofiler/qmlprofilerv8eventsview.h b/src/plugins/qmlprofiler/qmlprofilerv8eventsview.h new file mode 100644 index 0000000000..c15443b7f2 --- /dev/null +++ b/src/plugins/qmlprofiler/qmlprofilerv8eventsview.h @@ -0,0 +1,202 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, 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, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + +#ifndef QMLPROFILEREVENTVIEW_H +#define QMLPROFILEREVENTVIEW_H + +#include <QTreeView> +#include <QStandardItemModel> +#include <qmldebug/qmlprofilereventtypes.h> +#include "qmlprofilermodelmanager.h" +#include "qmlprofilereventsmodelproxy.h" + +#include <analyzerbase/ianalyzertool.h> + +#include "qmlprofilerviewmanager.h" + +namespace QmlProfiler { +namespace Internal { + +class QmlProfilerEventsMainView; +class QmlProfilerEventChildrenView; +class QmlProfilerEventRelativesView; + +enum ItemRole { + EventHashStrRole = Qt::UserRole+1, + FilenameRole = Qt::UserRole+2, + LineRole = Qt::UserRole+3, + ColumnRole = Qt::UserRole+4, + EventIdRole = Qt::UserRole+5 +}; + +class QmlProfilerEventsWidget : public QWidget +{ + Q_OBJECT +public: + explicit QmlProfilerEventsWidget(QWidget *parent, + Analyzer::IAnalyzerTool *profilerTool, + QmlProfilerViewManager *container, + QmlProfilerModelManager *profilerModelManager ); + ~QmlProfilerEventsWidget(); + + void clear(); + + void getStatisticsInRange(qint64 rangeStart, qint64 rangeEnd); + QModelIndex selectedItem() const; + bool mouseOnTable(const QPoint &position) const; + void copyTableToClipboard() const; + void copyRowToClipboard() const; + + bool hasGlobalStats() const; + void setShowExtendedStatistics(bool show); + bool showExtendedStatistics() const; + + +signals: + void gotoSourceLocation(const QString &fileName, int lineNumber, int columnNumber); + void showEventInTimeline(int eventId); + void resized(); + +public slots: + void updateSelectedEvent(const QString &eventHash) const; + void selectBySourceLocation(const QString &filename, int line, int column); + +private slots: + void profilerDataModelStateChanged(); + +protected: + void contextMenuEvent(QContextMenuEvent *ev); + virtual void resizeEvent(QResizeEvent *event); + +private: + class QmlProfilerEventsWidgetPrivate; + QmlProfilerEventsWidgetPrivate *d; +}; + +class QmlProfilerEventsMainView : public QTreeView +{ + Q_OBJECT +public: + enum Fields { + Name, + Type, + Percent, + TotalDuration, + SelfPercent, + SelfDuration, + CallCount, + TimePerCall, + MaxTime, + MinTime, + MedianTime, + Details, + + MaxFields + }; + + explicit QmlProfilerEventsMainView(QWidget *parent, + QmlProfilerEventsModelProxy *modelProxy); + ~QmlProfilerEventsMainView(); + + void setFieldViewable(Fields field, bool show); + void setShowAnonymousEvents( bool showThem ); + + QModelIndex selectedItem() const; + void copyTableToClipboard() const; + void copyRowToClipboard() const; + + static QString displayTime(double time); + static QString nameForType(int typeNumber); + + void getStatisticsInRange(qint64 rangeStart, qint64 rangeEnd); + bool isRangeGlobal(qint64 rangeStart, qint64 rangeEnd) const; +// int selectedEventId() const; + const QString &selectedEventHash() const; + + void setShowExtendedStatistics(bool); + bool showExtendedStatistics() const; + + +signals: + void gotoSourceLocation(const QString &fileName, int lineNumber, int columnNumber); + void eventSelected(const QString &eventHash); + void showEventInTimeline(int eventId); + +public slots: + void clear(); + void jumpToItem(const QModelIndex &index); + void selectEvent(const QString &eventHash); + void selectEventByLocation(const QString &filename, int line); + void buildModel(); + void changeDetailsForEvent(int eventId, const QString &newString); + +private slots: + void profilerDataModelStateChanged(); + +private: + void setHeaderLabels(); + void parseModelProxy(); + +private: + class QmlProfilerEventsMainViewPrivate; + QmlProfilerEventsMainViewPrivate *d; + +}; + +class QmlProfilerEventRelativesView : public QTreeView +{ + Q_OBJECT +public: + explicit QmlProfilerEventRelativesView(QmlProfilerModelManager *modelManager, + QmlProfilerEventRelativesModelProxy *modelProxy, + QWidget *parent ); + ~QmlProfilerEventRelativesView(); + +signals: + void eventClicked(const QString &eventHash); + +public slots: + void displayEvent(const QString &eventHash); + void jumpToItem(const QModelIndex &); + void clear(); + +private: + void rebuildTree(QmlProfilerEventParentsModelProxy::QmlEventRelativesMap eventMap); + void updateHeader(); + QStandardItemModel *treeModel(); +// QmlProfilerModelManager *m_profilerModelManager; + + class QmlProfilerEventParentsViewPrivate; + QmlProfilerEventParentsViewPrivate *d; +}; + +} // namespace Internal +} // namespace QmlProfiler + +#endif // QMLPROFILEREVENTVIEW_H diff --git a/src/plugins/qmlprofiler/qmlprofilerviewmanager.cpp b/src/plugins/qmlprofiler/qmlprofilerviewmanager.cpp index 9b9fa32e12..5374b1c3c7 100644 --- a/src/plugins/qmlprofiler/qmlprofilerviewmanager.cpp +++ b/src/plugins/qmlprofiler/qmlprofilerviewmanager.cpp @@ -33,8 +33,9 @@ #include "qmlprofilereventview.h" #include "qmlprofilertool.h" #include "qmlprofilerstatemanager.h" -#include "qmlprofilerdatamodel.h" +#include "qmlprofilermodelmanager.h" #include "qmlprofilerstatewidget.h" +#include "qv8profilereventview.h" #include <utils/qtcassert.h> #include <utils/fancymainwindow.h> @@ -53,15 +54,15 @@ public: QmlProfilerTraceView *traceView; QmlProfilerEventsWidget *eventsView; - QmlProfilerEventsWidget *v8profilerView; + QV8ProfilerEventsWidget *v8profilerView; QmlProfilerStateManager *profilerState; - QmlProfilerDataModel *profilerDataModel; + QmlProfilerModelManager *profilerModelManager; QmlProfilerTool *profilerTool; }; QmlProfilerViewManager::QmlProfilerViewManager(QObject *parent, QmlProfilerTool *profilerTool, - QmlProfilerDataModel *model, + QmlProfilerModelManager *modelManager, QmlProfilerStateManager *profilerState) : QObject(parent), d(new QmlProfilerViewManagerPrivate(this)) { @@ -70,7 +71,7 @@ QmlProfilerViewManager::QmlProfilerViewManager(QObject *parent, d->eventsView = 0; d->v8profilerView = 0; d->profilerState = profilerState; - d->profilerDataModel = model; + d->profilerModelManager = modelManager; d->profilerTool = profilerTool; createViews(); } @@ -84,7 +85,8 @@ QmlProfilerViewManager::~QmlProfilerViewManager() // Views void QmlProfilerViewManager::createViews() { - QTC_ASSERT(d->profilerDataModel, return); + + QTC_ASSERT(d->profilerModelManager, return); QTC_ASSERT(d->profilerState, return); Utils::FancyMainWindow *mw = AnalyzerManager::mainWindow(); @@ -92,26 +94,28 @@ void QmlProfilerViewManager::createViews() d->traceView = new QmlProfilerTraceView(mw, d->profilerTool, this, - d->profilerDataModel, + d->profilerModelManager, d->profilerState); connect(d->traceView, SIGNAL(gotoSourceLocation(QString,int,int)), this, SIGNAL(gotoSourceLocation(QString,int,int))); - d->traceView->reset(); - d->eventsView = new QmlProfilerEventsWidget(mw, d->profilerTool, this, d->profilerDataModel); + + d->eventsView = new QmlProfilerEventsWidget(mw, d->profilerTool, this, + d->profilerModelManager); connect(d->eventsView, SIGNAL(gotoSourceLocation(QString,int,int)), this, SIGNAL(gotoSourceLocation(QString,int,int))); - connect(d->eventsView, SIGNAL(showEventInTimeline(int)), d->traceView, - SLOT(selectNextEventWithId(int))); - connect(d->traceView, SIGNAL(selectedEventChanged(int)), d->eventsView, - SLOT(updateSelectedEvent(int))); - - d->v8profilerView = new QmlProfilerEventsWidget(mw, d->profilerTool, - this, d->profilerDataModel); - d->v8profilerView->switchToV8View(); + connect(d->eventsView, SIGNAL(eventSelectedByHash(QString)), d->traceView, + SLOT(selectNextEventByHash(QString))); + connect(d->traceView, SIGNAL(gotoSourceLocation(QString,int,int)), + d->eventsView, SLOT(selectBySourceLocation(QString,int,int))); + + d->v8profilerView = new QV8ProfilerEventsWidget(mw, d->profilerTool, this, + d->profilerModelManager); + connect(d->traceView, SIGNAL(gotoSourceLocation(QString,int,int)), + d->v8profilerView, SLOT(selectBySourceLocation(QString,int,int))); connect(d->v8profilerView, SIGNAL(gotoSourceLocation(QString,int,int)), - this, SIGNAL(gotoSourceLocation(QString,int,int))); + d->traceView, SLOT(selectNextEventByLocation(QString,int,int))); connect(d->v8profilerView, SIGNAL(gotoSourceLocation(QString,int,int)), d->eventsView, SLOT(selectBySourceLocation(QString,int,int))); connect(d->eventsView, SIGNAL(gotoSourceLocation(QString,int,int)), @@ -121,8 +125,8 @@ void QmlProfilerViewManager::createViews() (d->profilerTool, tr("Events"), d->eventsView, Qt::BottomDockWidgetArea); QDockWidget *timelineDock = AnalyzerManager::createDockWidget (d->profilerTool, tr("Timeline"), d->traceView, Qt::BottomDockWidgetArea); - QDockWidget *v8profilerDock = AnalyzerManager::createDockWidget - (d->profilerTool, tr("JavaScript"), d->v8profilerView, Qt::BottomDockWidgetArea); + QDockWidget *v8profilerDock = AnalyzerManager::createDockWidget( + d->profilerTool, tr("JavaScript"), d->v8profilerView, Qt::BottomDockWidgetArea); eventsDock->show(); timelineDock->show(); @@ -132,9 +136,9 @@ void QmlProfilerViewManager::createViews() mw->tabifyDockWidget(timelineDock, eventsDock); mw->tabifyDockWidget(eventsDock, v8profilerDock); - new QmlProfilerStateWidget(d->profilerState, d->profilerDataModel, d->traceView); - new QmlProfilerStateWidget(d->profilerState, d->profilerDataModel, d->eventsView); - new QmlProfilerStateWidget(d->profilerState, d->profilerDataModel, d->v8profilerView); + new QmlProfilerStateWidget(d->profilerState, d->profilerModelManager, d->eventsView); + new QmlProfilerStateWidget(d->profilerState, d->profilerModelManager, d->traceView); + new QmlProfilerStateWidget(d->profilerState, d->profilerModelManager, d->v8profilerView); } bool QmlProfilerViewManager::hasValidSelection() const diff --git a/src/plugins/qmlprofiler/qmlprofilerviewmanager.h b/src/plugins/qmlprofiler/qmlprofilerviewmanager.h index 8b4b2bda8d..122e5bb644 100644 --- a/src/plugins/qmlprofiler/qmlprofilerviewmanager.h +++ b/src/plugins/qmlprofiler/qmlprofilerviewmanager.h @@ -33,10 +33,11 @@ #include <QObject> namespace QmlProfiler { +class QmlProfilerModelManager; + namespace Internal { class QmlProfilerTool; -class QmlProfilerDataModel; class QmlProfilerStateManager; class QmlProfilerViewManager : public QObject @@ -45,7 +46,7 @@ class QmlProfilerViewManager : public QObject public: explicit QmlProfilerViewManager(QObject *parent, QmlProfilerTool *profilerTool, - QmlProfilerDataModel *model, + QmlProfilerModelManager *modelManager, QmlProfilerStateManager *profilerState); ~QmlProfilerViewManager(); diff --git a/src/plugins/qmlprofiler/qv8profilerdatamodel.cpp b/src/plugins/qmlprofiler/qv8profilerdatamodel.cpp index 482677d31d..406d010e80 100644 --- a/src/plugins/qmlprofiler/qv8profilerdatamodel.cpp +++ b/src/plugins/qmlprofiler/qv8profilerdatamodel.cpp @@ -28,12 +28,9 @@ ****************************************************************************/ #include "qv8profilerdatamodel.h" -#include "qmlprofilerdatamodel.h" #include <QStringList> -using namespace QmlDebug; - QT_BEGIN_NAMESPACE Q_DECLARE_TYPEINFO(QmlProfiler::Internal::QV8EventData, Q_MOVABLE_TYPE); Q_DECLARE_TYPEINFO(QmlProfiler::Internal::QV8EventSub, Q_MOVABLE_TYPE); @@ -66,7 +63,7 @@ QV8EventData &QV8EventData::operator=(const QV8EventData &ref) totalTime = ref.totalTime; totalPercent = ref.totalPercent; selfTime = ref.selfTime; - selfPercent = ref.selfPercent; + SelfTimeInPercent = ref.SelfTimeInPercent; eventId = ref.eventId; qDeleteAll(parentHash); @@ -85,7 +82,7 @@ QV8EventData::QV8EventData() totalTime = 0; selfTime = 0; totalPercent = 0; - selfPercent = 0; + SelfTimeInPercent = 0; } QV8EventData::~QV8EventData() @@ -101,8 +98,6 @@ class QV8ProfilerDataModel::QV8ProfilerDataModelPrivate public: QV8ProfilerDataModelPrivate(QV8ProfilerDataModel *qq) {Q_UNUSED(qq);} - QmlProfilerDataModel *qmlProfilerDataModel; - void clearV8RootEvent(); void collectV8Statistics(); @@ -112,13 +107,12 @@ public: qint64 v8MeasuredTime; }; -QV8ProfilerDataModel::QV8ProfilerDataModel(QObject *parent, - QmlProfilerDataModel *profilerDataModel) - : QObject(parent), d(new QV8ProfilerDataModelPrivate(this)) +QV8ProfilerDataModel::QV8ProfilerDataModel(QObject *parent) + : QObject(parent) + , d(new QV8ProfilerDataModelPrivate(this)) { d->v8MeasuredTime = 0; d->clearV8RootEvent(); - d->qmlProfilerDataModel = profilerDataModel; } QV8ProfilerDataModel::~QV8ProfilerDataModel() @@ -133,6 +127,8 @@ void QV8ProfilerDataModel::clear() d->v8parents.clear(); d->clearV8RootEvent(); d->v8MeasuredTime = 0; + + emit changed(); } bool QV8ProfilerDataModel::isEmpty() const @@ -159,6 +155,11 @@ QList<QV8EventData *> QV8ProfilerDataModel::getV8Events() const return d->v8EventHash.values(); } +QString getHashStringForV8Event(const QString &displayName, const QString &function) +{ + return QString::fromLatin1("%1:%2").arg(displayName, function); +} + void QV8ProfilerDataModel::addV8Event(int depth, const QString &function, const QString &filename, @@ -168,9 +169,7 @@ void QV8ProfilerDataModel::addV8Event(int depth, { QString displayName = filename.mid(filename.lastIndexOf(QLatin1Char('/')) + 1) + QLatin1Char(':') + QString::number(lineNumber); - QString hashStr = QmlProfilerDataModel::getHashStringForV8Event(displayName, function); - - d->qmlProfilerDataModel->setState(QmlProfilerDataModel::AcquiringData); + QString hashStr = getHashStringForV8Event(displayName, function); // time is given in milliseconds, but internally we store it in microseconds totalTime *= 1e6; @@ -223,6 +222,7 @@ void QV8ProfilerDataModel::addV8Event(int depth, newChildSub->totalTime += totalTime; } } + } void QV8ProfilerDataModel::collectV8Statistics() @@ -250,9 +250,9 @@ void QV8ProfilerDataModel::QV8ProfilerDataModelPrivate::collectV8Statistics() v8RootEvent.totalTime = v8MeasuredTime + 1; v8RootEvent.selfTime = 0; - QString rootEventHash = QmlProfilerDataModel::getHashStringForV8Event( - QmlProfilerDataModel::rootEventName(), - QmlProfilerDataModel::rootEventDescription()); + QString rootEventHash = getHashStringForV8Event( + tr("<program>"), + tr("Main Program")); QV8EventData *v8RootEventPointer = v8EventHash[rootEventHash]; if (v8RootEventPointer) { v8RootEvent = *v8RootEventPointer; @@ -263,7 +263,7 @@ void QV8ProfilerDataModel::QV8ProfilerDataModelPrivate::collectV8Statistics() foreach (QV8EventData *v8event, v8EventHash.values()) { v8event->totalPercent = v8event->totalTime * 100.0 / totalTimes; - v8event->selfPercent = v8event->selfTime * 100.0 / selfTimes; + v8event->SelfTimeInPercent = v8event->selfTime * 100.0 / selfTimes; } int index = 0; @@ -274,25 +274,20 @@ void QV8ProfilerDataModel::QV8ProfilerDataModelPrivate::collectV8Statistics() } else { // On empty data, still add a fake root event clearV8RootEvent(); - v8RootEvent.totalPercent = 100; - QString rootEventHash = QmlProfilerDataModel::getHashStringForV8Event( - QmlProfilerDataModel::rootEventName(), - QmlProfilerDataModel::rootEventDescription()); - v8EventHash[rootEventHash] = new QV8EventData; - *v8EventHash[rootEventHash] = v8RootEvent; } } void QV8ProfilerDataModel::QV8ProfilerDataModelPrivate::clearV8RootEvent() { - v8RootEvent.displayName = QmlProfilerDataModel::rootEventName(); - v8RootEvent.eventHashStr = QmlProfilerDataModel::rootEventName(); - v8RootEvent.functionName = QmlProfilerDataModel::rootEventDescription(); + v8RootEvent.displayName = tr("<program>"); + v8RootEvent.eventHashStr = tr("<program>"); + v8RootEvent.functionName = tr("Main Program"); + v8RootEvent.line = -1; v8RootEvent.totalTime = 0; v8RootEvent.totalPercent = 0; v8RootEvent.selfTime = 0; - v8RootEvent.selfPercent = 0; + v8RootEvent.SelfTimeInPercent = 0; v8RootEvent.eventId = -1; qDeleteAll(v8RootEvent.parentHash.values()); @@ -439,7 +434,7 @@ void QV8ProfilerDataModel::load(QXmlStreamReader &stream) } default: break; } -} + } // backwards compatibility if (d->v8MeasuredTime == 0) @@ -472,11 +467,10 @@ void QV8ProfilerDataModel::load(QXmlStreamReader &stream) } } } - // store v8 events foreach (QV8EventData *storedV8Event, v8eventBuffer.values()) { storedV8Event->eventHashStr = - QmlProfilerDataModel::getHashStringForV8Event( + getHashStringForV8Event( storedV8Event->displayName, storedV8Event->functionName); d->v8EventHash[storedV8Event->eventHashStr] = storedV8Event; } @@ -485,5 +479,11 @@ void QV8ProfilerDataModel::load(QXmlStreamReader &stream) } +void QV8ProfilerDataModel::complete() +{ + collectV8Statistics(); + emit changed(); +} + } // namespace Internal } // namespace QmlProfiler diff --git a/src/plugins/qmlprofiler/qv8profilerdatamodel.h b/src/plugins/qmlprofiler/qv8profilerdatamodel.h index b4430613d0..633c2107f2 100644 --- a/src/plugins/qmlprofiler/qv8profilerdatamodel.h +++ b/src/plugins/qmlprofiler/qv8profilerdatamodel.h @@ -39,7 +39,6 @@ namespace QmlProfiler { namespace Internal { -class QmlProfilerDataModel; struct QV8EventSub; struct QV8EventData @@ -55,7 +54,7 @@ struct QV8EventData double totalTime; // given in milliseconds double totalPercent; double selfTime; - double selfPercent; + double SelfTimeInPercent; QHash <QString, QV8EventSub *> parentHash; QHash <QString, QV8EventSub *> childrenHash; int eventId; @@ -75,7 +74,7 @@ class QV8ProfilerDataModel : public QObject { Q_OBJECT public: - explicit QV8ProfilerDataModel(QObject *parent, QmlProfilerDataModel *profilerDataModel); + QV8ProfilerDataModel(QObject *parent = 0); ~QV8ProfilerDataModel(); void clear(); @@ -89,6 +88,11 @@ public: void save(QXmlStreamWriter &stream); void load(QXmlStreamReader &stream); + void complete(); + +signals: + void changed(); + public slots: void addV8Event(int depth, const QString &function, diff --git a/src/plugins/qmlprofiler/qv8profilereventview.cpp b/src/plugins/qmlprofiler/qv8profilereventview.cpp new file mode 100644 index 0000000000..fdc3ee9592 --- /dev/null +++ b/src/plugins/qmlprofiler/qv8profilereventview.cpp @@ -0,0 +1,725 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, 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, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + +#include "qv8profilereventview.h" + +#include <QUrl> +#include <QHash> + +#include <QStandardItem> +#include <QHeaderView> + +#include <QApplication> +#include <QClipboard> + +#include <QContextMenuEvent> +#include <QDebug> + +#include <coreplugin/minisplitter.h> +#include <QVBoxLayout> +#include <QHBoxLayout> + +#include "qmlprofilerviewmanager.h" +#include "qmlprofilertool.h" +#include "qv8profilerdatamodel.h" +#include <QMenu> + +#include <utils/qtcassert.h> + +using namespace QmlDebug; + +namespace QmlProfiler { +namespace Internal { + +enum ItemRole { + EventHashStrRole = Qt::UserRole+1, + FilenameRole = Qt::UserRole+2, + LineRole = Qt::UserRole+3, + ColumnRole = Qt::UserRole+4, + EventIdRole = Qt::UserRole+5 +}; + +//////////////////////////////////////////////////////////////////////////////////// + + +//////////////////////////////////////////////////////////////////////////////////// + +class EventsViewItem : public QStandardItem +{ +public: + EventsViewItem(const QString &text) : QStandardItem(text) {} + + virtual bool operator<(const QStandardItem &other) const + { + if (data().type() == QVariant::String) { + // first column + if (column() == 0) { + return data(FilenameRole).toString() == other.data(FilenameRole).toString() ? + data(LineRole).toInt() < other.data(LineRole).toInt() : + data(FilenameRole).toString() < other.data(FilenameRole).toString(); + } else { + return data().toString().toLower() < other.data().toString().toLower(); + } + } + + return data().toDouble() < other.data().toDouble(); + } +}; + +//////////////////////////////////////////////////////////////////////////////////// + +class QV8ProfilerEventsWidget::QV8ProfilerEventsWidgetPrivate +{ +public: + QV8ProfilerEventsWidgetPrivate(QV8ProfilerEventsWidget *qq):q(qq) {} + ~QV8ProfilerEventsWidgetPrivate() {} + + QV8ProfilerEventsWidget *q; + + Analyzer::IAnalyzerTool *m_profilerTool; + QmlProfilerViewManager *m_viewContainer; + + QV8ProfilerEventsMainView *m_eventTree; + QV8ProfilerEventRelativesView *m_eventChildren; + QV8ProfilerEventRelativesView *m_eventParents; + + QV8ProfilerDataModel *v8Model; +}; + +QV8ProfilerEventsWidget::QV8ProfilerEventsWidget(QWidget *parent, + Analyzer::IAnalyzerTool *profilerTool, + QmlProfilerViewManager *container, + QmlProfilerModelManager *profilerModelManager ) + : QWidget(parent), d(new QV8ProfilerEventsWidgetPrivate(this)) +{ + setObjectName(QLatin1String("QmlProfilerV8ProfileView")); + + d->v8Model = profilerModelManager->v8Model(); + + d->m_eventTree = new QV8ProfilerEventsMainView(this, d->v8Model); + connect(d->m_eventTree, SIGNAL(gotoSourceLocation(QString,int,int)), this, SIGNAL(gotoSourceLocation(QString,int,int))); + + d->m_eventChildren = new QV8ProfilerEventRelativesView(d->v8Model, + QV8ProfilerEventRelativesView::ChildrenView, + this); + d->m_eventParents = new QV8ProfilerEventRelativesView(d->v8Model, + QV8ProfilerEventRelativesView::ParentsView, + this); + connect(d->m_eventTree, SIGNAL(eventSelected(int)), d->m_eventChildren, SLOT(displayEvent(int))); + connect(d->m_eventTree, SIGNAL(eventSelected(int)), d->m_eventParents, SLOT(displayEvent(int))); + connect(d->m_eventChildren, SIGNAL(eventClicked(int)), d->m_eventTree, SLOT(selectEvent(int))); + connect(d->m_eventParents, SIGNAL(eventClicked(int)), d->m_eventTree, SLOT(selectEvent(int))); + + // widget arrangement + QVBoxLayout *groupLayout = new QVBoxLayout; + groupLayout->setContentsMargins(0,0,0,0); + groupLayout->setSpacing(0); + + Core::MiniSplitter *splitterVertical = new Core::MiniSplitter; + splitterVertical->addWidget(d->m_eventTree); + Core::MiniSplitter *splitterHorizontal = new Core::MiniSplitter; + splitterHorizontal->addWidget(d->m_eventParents); + splitterHorizontal->addWidget(d->m_eventChildren); + splitterHorizontal->setOrientation(Qt::Horizontal); + splitterVertical->addWidget(splitterHorizontal); + splitterVertical->setOrientation(Qt::Vertical); + splitterVertical->setStretchFactor(0,5); + splitterVertical->setStretchFactor(1,2); + groupLayout->addWidget(splitterVertical); + setLayout(groupLayout); + + d->m_profilerTool = profilerTool; + d->m_viewContainer = container; + +} + +QV8ProfilerEventsWidget::~QV8ProfilerEventsWidget() +{ + delete d; +} + +void QV8ProfilerEventsWidget::clear() +{ + d->m_eventTree->clear(); + d->m_eventChildren->clear(); + d->m_eventParents->clear(); +} + +QModelIndex QV8ProfilerEventsWidget::selectedItem() const +{ + return d->m_eventTree->selectedItem(); +} + +void QV8ProfilerEventsWidget::contextMenuEvent(QContextMenuEvent *ev) +{ + QTC_ASSERT(d->m_viewContainer, return;); + + QMenu menu; + QAction *copyRowAction = 0; + QAction *copyTableAction = 0; + + QmlProfilerTool *profilerTool = qobject_cast<QmlProfilerTool *>(d->m_profilerTool); + QPoint position = ev->globalPos(); + + if (profilerTool) { + QList <QAction *> commonActions = profilerTool->profilerContextMenuActions(); + foreach (QAction *act, commonActions) { + menu.addAction(act); + } + } + + if (mouseOnTable(position)) { + menu.addSeparator(); + if (selectedItem().isValid()) + copyRowAction = menu.addAction(QCoreApplication::translate("QmlProfiler::Internal::QmlProfilerEventsWidget", "Copy Row")); + copyTableAction = menu.addAction(QCoreApplication::translate("QmlProfiler::Internal::QmlProfilerEventsWidget", "Copy Table")); + } + + QAction *selectedAction = menu.exec(position); + + if (selectedAction) { + if (selectedAction == copyRowAction) + copyRowToClipboard(); + if (selectedAction == copyTableAction) + copyTableToClipboard(); + } +} + +void QV8ProfilerEventsWidget::resizeEvent(QResizeEvent *event) +{ + QWidget::resizeEvent(event); + emit resized(); +} + +bool QV8ProfilerEventsWidget::mouseOnTable(const QPoint &position) const +{ + QPoint tableTopLeft = d->m_eventTree->mapToGlobal(QPoint(0,0)); + QPoint tableBottomRight = d->m_eventTree->mapToGlobal(QPoint(d->m_eventTree->width(), d->m_eventTree->height())); + return (position.x() >= tableTopLeft.x() && position.x() <= tableBottomRight.x() && position.y() >= tableTopLeft.y() && position.y() <= tableBottomRight.y()); +} + +void QV8ProfilerEventsWidget::copyTableToClipboard() const +{ + d->m_eventTree->copyTableToClipboard(); +} + +void QV8ProfilerEventsWidget::copyRowToClipboard() const +{ + d->m_eventTree->copyRowToClipboard(); +} + +void QV8ProfilerEventsWidget::updateSelectedEvent(int eventId) const +{ + if (d->m_eventTree->selectedEventId() != eventId) + d->m_eventTree->selectEvent(eventId); +} + +void QV8ProfilerEventsWidget::selectBySourceLocation(const QString &filename, int line, int column) +{ + // This slot is used to connect the javascript pane with the qml events pane + // Our javascript trace data does not store column information + // thus we ignore it here + Q_UNUSED(column); + d->m_eventTree->selectEventByLocation(filename, line); +} + +//////////////////////////////////////////////////////////////////////////////////// + +class QV8ProfilerEventsMainView::QV8ProfilerEventsMainViewPrivate +{ +public: + QV8ProfilerEventsMainViewPrivate(QV8ProfilerEventsMainView *qq) : q(qq) {} + + void buildV8ModelFromList( const QList<QV8EventData *> &list ); + int getFieldCount(); + + QString textForItem(QStandardItem *item, bool recursive) const; + + + QV8ProfilerEventsMainView *q; + + QV8ProfilerDataModel *m_v8Model; + QStandardItemModel *m_model; + QList<bool> m_fieldShown; + QHash<int, int> m_columnIndex; // maps field enum to column index + int m_firstNumericColumn; + bool m_preventSelectBounce; +}; + + +//////////////////////////////////////////////////////////////////////////////////// + +QV8ProfilerEventsMainView::QV8ProfilerEventsMainView(QWidget *parent, + QV8ProfilerDataModel *v8Model) +: QmlProfilerTreeView(parent), d(new QV8ProfilerEventsMainViewPrivate(this)) +{ + setObjectName(QLatin1String("QmlProfilerEventsTable")); + setSortingEnabled(false); + + d->m_model = new QStandardItemModel(this); + setModel(d->m_model); + connect(this,SIGNAL(clicked(QModelIndex)), this,SLOT(jumpToItem(QModelIndex))); + + d->m_v8Model = v8Model; + connect(d->m_v8Model, SIGNAL(changed()), this, SLOT(buildModel())); + d->m_firstNumericColumn = 0; + d->m_preventSelectBounce = false; + + setFieldViewable(Name, true); + setFieldViewable(Type, false); + setFieldViewable(TimeInPercent, true); + setFieldViewable(TotalTime, true); + setFieldViewable(SelfTimeInPercent, true); + setFieldViewable(SelfTime, true); + setFieldViewable(CallCount, false); + setFieldViewable(TimePerCall, false); + setFieldViewable(MaxTime, false); + setFieldViewable(MinTime, false); + setFieldViewable(MedianTime, false); + setFieldViewable(Details, true); + + buildModel(); +} + +QV8ProfilerEventsMainView::~QV8ProfilerEventsMainView() +{ + clear(); + delete d->m_model; + delete d; +} + +void QV8ProfilerEventsMainView::setFieldViewable(Fields field, bool show) +{ + if (field < MaxFields) { + int length = d->m_fieldShown.count(); + if (field >= length) { + for (int i=length; i<MaxFields; i++) + d->m_fieldShown << false; + } + d->m_fieldShown[field] = show; + } +} + + +void QV8ProfilerEventsMainView::setHeaderLabels() +{ + int fieldIndex = 0; + d->m_firstNumericColumn = 0; + + d->m_columnIndex.clear(); + if (d->m_fieldShown[Name]) { + d->m_columnIndex[Name] = fieldIndex; + d->m_model->setHeaderData(fieldIndex++, Qt::Horizontal, QVariant(displayHeader(Location))); + d->m_firstNumericColumn++; + } + if (d->m_fieldShown[Type]) { + d->m_columnIndex[Type] = fieldIndex; + d->m_model->setHeaderData(fieldIndex++, Qt::Horizontal, QVariant(displayHeader(Type))); + d->m_firstNumericColumn++; + } + if (d->m_fieldShown[TimeInPercent]) { + d->m_columnIndex[TimeInPercent] = fieldIndex; + d->m_model->setHeaderData(fieldIndex++, Qt::Horizontal, QVariant(displayHeader(TimeInPercent))); + } + if (d->m_fieldShown[TotalTime]) { + d->m_columnIndex[TotalTime] = fieldIndex; + d->m_model->setHeaderData(fieldIndex++, Qt::Horizontal, QVariant(displayHeader(TotalTime))); + } + if (d->m_fieldShown[SelfTimeInPercent]) { + d->m_columnIndex[Type] = fieldIndex; + d->m_model->setHeaderData(fieldIndex++, Qt::Horizontal, QVariant(displayHeader(SelfTimeInPercent))); + } + if (d->m_fieldShown[SelfTime]) { + d->m_columnIndex[SelfTime] = fieldIndex; + d->m_model->setHeaderData(fieldIndex++, Qt::Horizontal, QVariant(displayHeader(SelfTime))); + } + if (d->m_fieldShown[CallCount]) { + d->m_columnIndex[CallCount] = fieldIndex; + d->m_model->setHeaderData(fieldIndex++, Qt::Horizontal, QVariant(displayHeader(CallCount))); + } + if (d->m_fieldShown[TimePerCall]) { + d->m_columnIndex[TimePerCall] = fieldIndex; + d->m_model->setHeaderData(fieldIndex++, Qt::Horizontal, QVariant(displayHeader(TimePerCall))); + } + if (d->m_fieldShown[MedianTime]) { + d->m_columnIndex[MedianTime] = fieldIndex; + d->m_model->setHeaderData(fieldIndex++, Qt::Horizontal, QVariant(displayHeader(MedianTime))); + } + if (d->m_fieldShown[MaxTime]) { + d->m_columnIndex[MaxTime] = fieldIndex; + d->m_model->setHeaderData(fieldIndex++, Qt::Horizontal, QVariant(displayHeader(MaxTime))); + } + if (d->m_fieldShown[MinTime]) { + d->m_columnIndex[MinTime] = fieldIndex; + d->m_model->setHeaderData(fieldIndex++, Qt::Horizontal, QVariant(displayHeader(MinTime))); + } + if (d->m_fieldShown[Details]) { + d->m_columnIndex[Details] = fieldIndex; + d->m_model->setHeaderData(fieldIndex++, Qt::Horizontal, QVariant(displayHeader(Details))); + } +} + +void QV8ProfilerEventsMainView::clear() +{ + d->m_model->clear(); + d->m_model->setColumnCount(d->getFieldCount()); + + setHeaderLabels(); + setSortingEnabled(false); +} + +int QV8ProfilerEventsMainView::QV8ProfilerEventsMainViewPrivate::getFieldCount() +{ + int count = 0; + for (int i=0; i < m_fieldShown.count(); ++i) + if (m_fieldShown[i]) + count++; + return count; +} + +void QV8ProfilerEventsMainView::buildModel() +{ + clear(); + d->buildV8ModelFromList( d->m_v8Model->getV8Events() ); + + setRootIsDecorated(false); + setSortingEnabled(true); + sortByColumn(d->m_firstNumericColumn,Qt::DescendingOrder); + + expandAll(); + if (d->m_fieldShown[Name]) + resizeColumnToContents(0); + + if (d->m_fieldShown[Type]) + resizeColumnToContents(d->m_fieldShown[Name]?1:0); + collapseAll(); +} + +void QV8ProfilerEventsMainView::QV8ProfilerEventsMainViewPrivate::buildV8ModelFromList(const QList<QV8EventData *> &list) +{ + for (int index = 0; index < list.count(); index++) { + QV8EventData *v8event = list.at(index); + QList<QStandardItem *> newRow; + + if (m_fieldShown[Name]) + newRow << new EventsViewItem(v8event->displayName); + + if (m_fieldShown[TimeInPercent]) { + newRow << new EventsViewItem(QString::number(v8event->totalPercent,'f',2)+QLatin1String(" %")); + newRow.last()->setData(QVariant(v8event->totalPercent)); + } + + if (m_fieldShown[TotalTime]) { + newRow << new EventsViewItem(displayTime(v8event->totalTime)); + newRow.last()->setData(QVariant(v8event->totalTime)); + } + + if (m_fieldShown[SelfTimeInPercent]) { + newRow << new EventsViewItem(QString::number(v8event->SelfTimeInPercent,'f',2)+QLatin1String(" %")); + newRow.last()->setData(QVariant(v8event->SelfTimeInPercent)); + } + + if (m_fieldShown[SelfTime]) { + newRow << new EventsViewItem(displayTime(v8event->selfTime)); + newRow.last()->setData(QVariant(v8event->selfTime)); + } + + if (m_fieldShown[Details]) { + newRow << new EventsViewItem(v8event->functionName); + newRow.last()->setData(QVariant(v8event->functionName)); + } + + if (!newRow.isEmpty()) { + // no edit + foreach (QStandardItem *item, newRow) + item->setEditable(false); + + // metadata + newRow.at(0)->setData(QString::fromLatin1("%1:%2").arg(v8event->filename, QString::number(v8event->line)), EventHashStrRole); + newRow.at(0)->setData(QVariant(v8event->filename), FilenameRole); + newRow.at(0)->setData(QVariant(v8event->line), LineRole); + newRow.at(0)->setData(QVariant(-1),ColumnRole); // v8 events have no column info + newRow.at(0)->setData(QVariant(v8event->eventId), EventIdRole); + + // append + m_model->invisibleRootItem()->appendRow(newRow); + } + } +} + +QString QV8ProfilerEventsMainView::displayTime(double time) +{ + if (time < 1e6) + return QString::number(time/1e3,'f',3) + trUtf8(" \xc2\xb5s"); + if (time < 1e9) + return QString::number(time/1e6,'f',3) + tr(" ms"); + + return QString::number(time/1e9,'f',3) + tr(" s"); +} + +QString QV8ProfilerEventsMainView::nameForType(int typeNumber) +{ + switch (typeNumber) { + case 0: return QV8ProfilerEventsMainView::tr("Paint"); + case 1: return QV8ProfilerEventsMainView::tr("Compile"); + case 2: return QV8ProfilerEventsMainView::tr("Create"); + case 3: return QV8ProfilerEventsMainView::tr("Binding"); + case 4: return QV8ProfilerEventsMainView::tr("Signal"); + } + return QString(); +} + +int QV8ProfilerEventsMainView::selectedEventId() const +{ + QModelIndex index = selectedItem(); + if (!index.isValid()) + return -1; + QStandardItem *item = d->m_model->item(index.row(), 0); + return item->data(EventIdRole).toInt(); +} + +void QV8ProfilerEventsMainView::jumpToItem(const QModelIndex &index) +{ + if (d->m_preventSelectBounce) + return; + + d->m_preventSelectBounce = true; + QStandardItem *clickedItem = d->m_model->itemFromIndex(index); + QStandardItem *infoItem; + if (clickedItem->parent()) + infoItem = clickedItem->parent()->child(clickedItem->row(), 0); + else + infoItem = d->m_model->item(index.row(), 0); + + // show in editor + int line = infoItem->data(LineRole).toInt(); + int column = infoItem->data(ColumnRole).toInt(); + QString fileName = infoItem->data(FilenameRole).toString(); + if (line!=-1 && !fileName.isEmpty()) + emit gotoSourceLocation(fileName, line, column); + + // show in callers/callees subwindow + emit eventSelected(infoItem->data(EventIdRole).toInt()); + + d->m_preventSelectBounce = false; +} + +void QV8ProfilerEventsMainView::selectEvent(int eventId) +{ + for (int i=0; i<d->m_model->rowCount(); i++) { + QStandardItem *infoItem = d->m_model->item(i, 0); + if (infoItem->data(EventIdRole).toInt() == eventId) { + setCurrentIndex(d->m_model->indexFromItem(infoItem)); + jumpToItem(currentIndex()); + return; + } + } +} + +void QV8ProfilerEventsMainView::selectEventByLocation(const QString &filename, int line) +{ + if (d->m_preventSelectBounce) + return; + + for (int i=0; i<d->m_model->rowCount(); i++) { + QStandardItem *infoItem = d->m_model->item(i, 0); + if (currentIndex() != d->m_model->indexFromItem(infoItem) && + infoItem->data(FilenameRole).toString() == filename && + infoItem->data(LineRole).toInt() == line) { + setCurrentIndex(d->m_model->indexFromItem(infoItem)); + jumpToItem(currentIndex()); + return; + } + } +} + +QModelIndex QV8ProfilerEventsMainView::selectedItem() const +{ + QModelIndexList sel = selectedIndexes(); + if (sel.isEmpty()) + return QModelIndex(); + else + return sel.first(); +} + +QString QV8ProfilerEventsMainView::QV8ProfilerEventsMainViewPrivate::textForItem(QStandardItem *item, bool recursive = true) const +{ + QString str; + + if (recursive) { + // indentation + QStandardItem *itemParent = item->parent(); + while (itemParent) { + str += QLatin1String(" "); + itemParent = itemParent->parent(); + } + } + + // item's data + int colCount = m_model->columnCount(); + for (int j = 0; j < colCount; ++j) { + QStandardItem *colItem = item->parent() ? item->parent()->child(item->row(),j) : m_model->item(item->row(),j); + str += colItem->data(Qt::DisplayRole).toString(); + if (j < colCount-1) str += QLatin1Char('\t'); + } + str += QLatin1Char('\n'); + + // recursively print children + if (recursive && item->child(0)) + for (int j = 0; j != item->rowCount(); j++) + str += textForItem(item->child(j)); + + return str; +} + +void QV8ProfilerEventsMainView::copyTableToClipboard() const +{ + QString str; + // headers + int columnCount = d->m_model->columnCount(); + for (int i = 0; i < columnCount; ++i) { + str += d->m_model->headerData(i, Qt::Horizontal, Qt::DisplayRole).toString(); + if (i < columnCount - 1) + str += QLatin1Char('\t'); + else + str += QLatin1Char('\n'); + } + // data + int rowCount = d->m_model->rowCount(); + for (int i = 0; i != rowCount; ++i) { + str += d->textForItem(d->m_model->item(i)); + } + QClipboard *clipboard = QApplication::clipboard(); + clipboard->setText(str, QClipboard::Selection); + clipboard->setText(str, QClipboard::Clipboard); +} + +void QV8ProfilerEventsMainView::copyRowToClipboard() const +{ + QString str; + str = d->textForItem(d->m_model->itemFromIndex(selectedItem()), false); + + QClipboard *clipboard = QApplication::clipboard(); + clipboard->setText(str, QClipboard::Selection); + clipboard->setText(str, QClipboard::Clipboard); +} + +//////////////////////////////////////////////////////////////////////////////////// + +QV8ProfilerEventRelativesView::QV8ProfilerEventRelativesView(QV8ProfilerDataModel *model, + SubViewType viewType, + QWidget *parent) + : QmlProfilerTreeView(parent) + , m_type(viewType) + , m_v8Model(model) + , m_model(new QStandardItemModel(this)) +{ + setModel(m_model); + + updateHeader(); + setSortingEnabled(false); + + connect(this, SIGNAL(clicked(QModelIndex)), this, SLOT(jumpToItem(QModelIndex))); +} + +QV8ProfilerEventRelativesView::~QV8ProfilerEventRelativesView() +{ +} + +void QV8ProfilerEventRelativesView::displayEvent(int index) +{ + QV8EventData *event = m_v8Model->v8EventDescription(index); + QTC_CHECK(event); + + QList<QV8EventSub*> events; + if (m_type == ParentsView) + events = event->parentHash.values(); + else + events = event->childrenHash.values(); + + rebuildTree(events); + + updateHeader(); + resizeColumnToContents(0); + setSortingEnabled(true); + sortByColumn(1); +} + +void QV8ProfilerEventRelativesView::rebuildTree(QList<QV8EventSub*> events) +{ + clear(); + + QStandardItem *topLevelItem = m_model->invisibleRootItem(); + + foreach (QV8EventSub *event, events) { + QList<QStandardItem *> newRow; + newRow << new EventsViewItem(event->reference->displayName); + newRow << new EventsViewItem(QV8ProfilerEventsMainView::displayTime(event->totalTime)); + newRow << new EventsViewItem(event->reference->functionName); + newRow.at(0)->setData(QVariant(event->reference->eventId), EventIdRole); + newRow.at(1)->setData(QVariant(event->totalTime)); + + foreach (QStandardItem *item, newRow) + item->setEditable(false); + + topLevelItem->appendRow(newRow); + } +} + +void QV8ProfilerEventRelativesView::clear() +{ + m_model->clear(); +} + +void QV8ProfilerEventRelativesView::updateHeader() +{ + m_model->setColumnCount(3); + + int columnIndex = 0; + if (m_type == ChildrenView) + m_model->setHeaderData(columnIndex++, Qt::Horizontal, QVariant(displayHeader(Callee))); + else + m_model->setHeaderData(columnIndex++, Qt::Horizontal, QVariant(displayHeader(Caller))); + + m_model->setHeaderData(columnIndex++, Qt::Horizontal, QVariant(displayHeader(TotalTime))); + + if (m_type == ChildrenView) + m_model->setHeaderData(columnIndex++, Qt::Horizontal, QVariant(displayHeader(CalleeDescription))); + else + m_model->setHeaderData(columnIndex++, Qt::Horizontal, QVariant(displayHeader(CallerDescription))); +} + +void QV8ProfilerEventRelativesView::jumpToItem(const QModelIndex &index) +{ + QStandardItem *infoItem = m_model->item(index.row(), 0); + emit eventClicked(infoItem->data(EventIdRole).toInt()); +} + +} // namespace Internal +} // namespace QmlProfiler diff --git a/src/plugins/qmlprofiler/qv8profilereventview.h b/src/plugins/qmlprofiler/qv8profilereventview.h new file mode 100644 index 0000000000..d32b499c1c --- /dev/null +++ b/src/plugins/qmlprofiler/qv8profilereventview.h @@ -0,0 +1,163 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, 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, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + +#ifndef QV8PROFILEREVENTVIEW_H +#define QV8PROFILEREVENTVIEW_H + +#include <QStandardItemModel> +#include <qmldebug/qmlprofilereventtypes.h> +#include "qmlprofilermodelmanager.h" +#include "qmlprofilereventsmodelproxy.h" +#include "qmlprofilertreeview.h" + +#include <analyzerbase/ianalyzertool.h> + +#include "qmlprofilerviewmanager.h" + +namespace QmlProfiler { +namespace Internal { + +class QV8ProfilerEventsMainView; +class QV8ProfilerEventRelativesView; +struct QV8EventSub; + + +class QV8ProfilerEventsWidget : public QWidget +{ + Q_OBJECT +public: + explicit QV8ProfilerEventsWidget(QWidget *parent, + Analyzer::IAnalyzerTool *profilerTool, + QmlProfilerViewManager *container, + QmlProfilerModelManager *profilerModelManager ); + ~QV8ProfilerEventsWidget(); + + void clear(); + + void getStatisticsInRange(qint64 rangeStart, qint64 rangeEnd); + QModelIndex selectedItem() const; + bool mouseOnTable(const QPoint &position) const; + void copyTableToClipboard() const; + void copyRowToClipboard() const; + +signals: + void gotoSourceLocation(const QString &fileName, int lineNumber, int columnNumber); + void showEventInTimeline(int eventId); + void resized(); + +public slots: + void updateSelectedEvent(int eventId) const; + void selectBySourceLocation(const QString &filename, int line, int column); + +protected: + void contextMenuEvent(QContextMenuEvent *ev); + virtual void resizeEvent(QResizeEvent *event); + +private: + class QV8ProfilerEventsWidgetPrivate; + QV8ProfilerEventsWidgetPrivate *d; +}; + +class QV8ProfilerEventsMainView : public QmlProfilerTreeView +{ + Q_OBJECT +public: + + explicit QV8ProfilerEventsMainView(QWidget *parent, + QV8ProfilerDataModel *v8Model); + ~QV8ProfilerEventsMainView(); + + void setFieldViewable(Fields field, bool show); + void setShowAnonymousEvents( bool showThem ); + + QModelIndex selectedItem() const; + void copyTableToClipboard() const; + void copyRowToClipboard() const; + + static QString displayTime(double time); + static QString nameForType(int typeNumber); + + int selectedEventId() const; + + void setShowExtendedStatistics(bool); + bool showExtendedStatistics() const; + +signals: + void gotoSourceLocation(const QString &fileName, int lineNumber, int columnNumber); + void eventSelected(int eventId); + +public slots: + void clear(); + void jumpToItem(const QModelIndex &index); + void selectEvent(int eventId); + void selectEventByLocation(const QString &filename, int line); + void buildModel(); + +private: + void setHeaderLabels(); + +private: + class QV8ProfilerEventsMainViewPrivate; + QV8ProfilerEventsMainViewPrivate *d; +}; + +class QV8ProfilerEventRelativesView : public QmlProfilerTreeView +{ + Q_OBJECT +public: + enum SubViewType { + ParentsView, + ChildrenView + }; + + QV8ProfilerEventRelativesView(QV8ProfilerDataModel *model, SubViewType viewType, + QWidget *parent); + ~QV8ProfilerEventRelativesView(); + +signals: + void eventClicked(int eventId); + +public slots: + void displayEvent(int eventId); + void jumpToItem(const QModelIndex &); + void clear(); + +private: + void rebuildTree(QList<QV8EventSub*> events); + void updateHeader(); + + QV8ProfilerEventRelativesView::SubViewType m_type; + QV8ProfilerDataModel *m_v8Model; + QStandardItemModel *m_model; +}; + +} // namespace Internal +} // namespace QmlProfiler + +#endif // QV8PROFILEREVENTVIEW_H diff --git a/src/plugins/qmlprofiler/timelinemodelaggregator.cpp b/src/plugins/qmlprofiler/timelinemodelaggregator.cpp new file mode 100644 index 0000000000..ccdf84dd5f --- /dev/null +++ b/src/plugins/qmlprofiler/timelinemodelaggregator.cpp @@ -0,0 +1,373 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, 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, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + +#include "timelinemodelaggregator.h" + +#include "qmlprofilertimelinemodelproxy.h" +#include "qmlprofilerpainteventsmodelproxy.h" +#include "qmlprofilerplugin.h" + +#include <QStringList> +#include <QVariant> + +namespace QmlProfiler { +namespace Internal { + + +class TimelineModelAggregator::TimelineModelAggregatorPrivate { +public: + TimelineModelAggregatorPrivate(TimelineModelAggregator *qq):q(qq) {} + ~TimelineModelAggregatorPrivate() {} + + TimelineModelAggregator *q; + + int basicModelIndex; + QList <AbstractTimelineModel *> modelList; + QmlProfilerModelManager *modelManager; +}; + +TimelineModelAggregator::TimelineModelAggregator(QObject *parent) + : QObject(parent), d(new TimelineModelAggregatorPrivate(this)) +{ +} + +TimelineModelAggregator::~TimelineModelAggregator() +{ + delete d; +} + +void TimelineModelAggregator::setModelManager(QmlProfilerModelManager *modelManager) +{ + d->modelManager = modelManager; + connect(modelManager,SIGNAL(stateChanged()),this,SLOT(dataChanged())); + connect(modelManager,SIGNAL(countChanged()),this,SIGNAL(countChanged())); + connect(modelManager,SIGNAL(dataAvailable()),this,SIGNAL(dataAvailable())); + + // external models pushed on top + foreach (AbstractTimelineModel *timelineModel, QmlProfilerPlugin::instance->getModels()) { + timelineModel->setModelManager(modelManager); + addModel(timelineModel); + } + + PaintEventsModelProxy *paintEventsModelProxy = new PaintEventsModelProxy(this); + paintEventsModelProxy->setModelManager(modelManager); + addModel(paintEventsModelProxy); + + BasicTimelineModel *basicTimelineModel = new BasicTimelineModel(this); + basicTimelineModel->setModelManager(modelManager); + addModel(basicTimelineModel); + // the basic model is the last one here + d->basicModelIndex = d->modelList.count() - 1; + +} + +void TimelineModelAggregator::addModel(AbstractTimelineModel *m) +{ + d->modelList << m; + connect(m,SIGNAL(countChanged()),this,SIGNAL(countChanged())); + connect(m,SIGNAL(emptyChanged()),this,SIGNAL(emptyChanged())); + connect(m,SIGNAL(expandedChanged()),this,SIGNAL(expandedChanged())); + connect(m,SIGNAL(stateChanged()),this,SIGNAL(stateChanged())); +} + +// order? +int TimelineModelAggregator::categories() const +{ + int categoryCount = 0; + foreach (const AbstractTimelineModel *modelProxy, d->modelList) + categoryCount += modelProxy->categories(); + return categoryCount; +} + +int TimelineModelAggregator::visibleCategories() const +{ + int categoryCount = 0; + foreach (const AbstractTimelineModel *modelProxy, d->modelList) { + for (int i = 0; i < modelProxy->categories(); i++) + if (modelProxy->categoryDepth(i) > 0) + categoryCount ++; + } + return categoryCount; +} + +QStringList TimelineModelAggregator::categoryTitles() const +{ + QStringList retString; + foreach (const AbstractTimelineModel *modelProxy, d->modelList) + retString += modelProxy->categoryTitles(); + return retString; +} + +int TimelineModelAggregator::count(int modelIndex) const +{ + if (modelIndex == -1) { + int totalCount = 0; + foreach (const AbstractTimelineModel *modelProxy, d->modelList) + totalCount += modelProxy->count(); + + return totalCount; + } else { + return d->modelList[modelIndex]->count(); + } +} + +bool TimelineModelAggregator::isEmpty() const +{ + foreach (const AbstractTimelineModel *modelProxy, d->modelList) + if (!modelProxy->isEmpty()) + return false; + return true; +} + +bool TimelineModelAggregator::eventAccepted(const QmlProfilerSimpleModel::QmlEventData &/*event*/) const +{ + // accept all events + return true; +} + +int TimelineModelAggregator::basicModelIndex() const +{ + return d->basicModelIndex; +} + +qint64 TimelineModelAggregator::lastTimeMark() const +{ + qint64 mark = -1; + foreach (const AbstractTimelineModel *modelProxy, d->modelList) { + if (!modelProxy->isEmpty()) { + qint64 mk = modelProxy->lastTimeMark(); + if (mark > mk) + mark = mk; + } + } + return mark; +} + +bool TimelineModelAggregator::expanded(int modelIndex, int category) const +{ + return d->modelList[modelIndex]->expanded(category); +} + +void TimelineModelAggregator::setExpanded(int modelIndex, int category, bool expanded) +{ +// int modelIndex = modelIndexForCategory(category); +// category = correctedCategoryIndexForModel(modelIndex, categoryIndex); + d->modelList[modelIndex]->setExpanded(category, expanded); +} + +int TimelineModelAggregator::categoryDepth(int modelIndex, int categoryIndex) const +{ + return d->modelList[modelIndex]->categoryDepth(categoryIndex); +} + +int TimelineModelAggregator::categoryCount(int modelIndex) const +{ + return d->modelList[modelIndex]->categoryCount(); +} + +int TimelineModelAggregator::rowCount(int modelIndex) const +{ + return d->modelList[modelIndex]->rowCount(); +} + +const QString TimelineModelAggregator::categoryLabel(int modelIndex, int categoryIndex) const +{ +// int modelIndex = modelIndexForCategory(categoryIndex); +// categoryIndex = correctedCategoryIndexForModel(modelIndex, categoryIndex); + return d->modelList[modelIndex]->categoryLabel(categoryIndex); +} + +int TimelineModelAggregator::modelIndexForCategory(int absoluteCategoryIndex) const +{ + int categoryIndex = absoluteCategoryIndex; + for (int modelIndex = 0; modelIndex < d->modelList.count(); modelIndex++) + if (categoryIndex < d->modelList[modelIndex]->categoryCount()) { + return modelIndex; + } else { + categoryIndex -= d->modelList[modelIndex]->categoryCount(); + } + + return modelCount()-1; +} + +int TimelineModelAggregator::correctedCategoryIndexForModel(int modelIndex, int absoluteCategoryIndex) const +{ + int categoryIndex = absoluteCategoryIndex; + for (int mi = 0; mi < modelIndex; mi++) + categoryIndex -= d->modelList[mi]->categoryCount(); + return categoryIndex; +} + +int TimelineModelAggregator::findFirstIndex(int modelIndex, qint64 startTime) const +{ + return d->modelList[modelIndex]->findFirstIndex(startTime); +} + +int TimelineModelAggregator::findFirstIndexNoParents(int modelIndex, qint64 startTime) const +{ + return d->modelList[modelIndex]->findFirstIndexNoParents(startTime); +} + +int TimelineModelAggregator::findLastIndex(int modelIndex, qint64 endTime) const +{ + return d->modelList[modelIndex]->findLastIndex(endTime); +} + +int TimelineModelAggregator::getEventType(int modelIndex, int index) const +{ + return d->modelList[modelIndex]->getEventType(index); +} + +int TimelineModelAggregator::getEventCategoryInModel(int modelIndex, int index) const +{ + return d->modelList[modelIndex]->getEventCategory(index); +} + +int TimelineModelAggregator::getEventRow(int modelIndex, int index) const +{ + return d->modelList[modelIndex]->getEventRow(index); +} + +qint64 TimelineModelAggregator::getDuration(int modelIndex, int index) const +{ + return d->modelList[modelIndex]->getDuration(index); +} + +qint64 TimelineModelAggregator::getStartTime(int modelIndex, int index) const +{ + return d->modelList[modelIndex]->getStartTime(index); +} + +qint64 TimelineModelAggregator::getEndTime(int modelIndex, int index) const +{ + return d->modelList[modelIndex]->getEndTime(index); +} + +int TimelineModelAggregator::getEventId(int modelIndex, int index) const +{ + return d->modelList[modelIndex]->getEventId(index); +} + +int TimelineModelAggregator::getBindingLoopDest(int modelIndex,int index) const +{ + return d->modelList[modelIndex]->getBindingLoopDest(index); +} + +QColor TimelineModelAggregator::getColor(int modelIndex, int index) const +{ + return d->modelList[modelIndex]->getColor(index); +} + +QVariantList TimelineModelAggregator::getColorRGB(int modelIndex, int itemIndex) const +{ + // return color as RGB list, for use in Qml + QColor c = getColor(modelIndex, itemIndex); + QVariantList res; + res.append(QVariant(c.red())); + res.append(QVariant(c.green())); + res.append(QVariant(c.blue())); + return res; +} + +float TimelineModelAggregator::getHeight(int modelIndex, int index) const +{ + return d->modelList[modelIndex]->getHeight(index); +} + +const QVariantList TimelineModelAggregator::getLabelsForCategory(int modelIndex, int category) const +{ +// int modelIndex = modelIndexForCategory(category); +// category = correctedCategoryIndexForModel(modelIndex, category); + return d->modelList[modelIndex]->getLabelsForCategory(category); +} + +const QVariantList TimelineModelAggregator::getEventDetails(int modelIndex, int index) const +{ + return d->modelList[modelIndex]->getEventDetails(index); +} + +const QVariantMap TimelineModelAggregator::getEventLocation(int modelIndex, int index) const +{ + return d->modelList[modelIndex]->getEventLocation(index); +} + +int TimelineModelAggregator::getEventIdForHash(const QString &hash) const +{ + foreach (const AbstractTimelineModel *model, d->modelList) { + int eventId = model->getEventIdForHash(hash); + if (eventId != -1) + return eventId; + } + return -1; +} + +int TimelineModelAggregator::getEventIdForLocation(const QString &filename, int line, int column) const +{ + foreach (const AbstractTimelineModel *model, d->modelList) { + int eventId = model->getEventIdForLocation(filename, line, column); + if (eventId != -1) + return eventId; + } + return -1; +} + +void TimelineModelAggregator::dataChanged() +{ + // this is a slot connected for every modelproxy + // nothing to do here, each model will take care of itself +} + +int TimelineModelAggregator::modelCount() const +{ + return d->modelList.count(); +} + +qint64 TimelineModelAggregator::traceStartTime() const +{ + return d->modelManager->traceTime()->startTime(); +} + +qint64 TimelineModelAggregator::traceEndTime() const +{ + return d->modelManager->traceTime()->endTime(); +} + +qint64 TimelineModelAggregator::traceDuration() const +{ + return d->modelManager->traceTime()->duration(); +} + +int TimelineModelAggregator::getState() const +{ + return (int)d->modelManager->state(); +} + + +} // namespace Internal +} // namespace QmlProfiler diff --git a/src/plugins/qmlprofiler/timelinemodelaggregator.h b/src/plugins/qmlprofiler/timelinemodelaggregator.h new file mode 100644 index 0000000000..7b1e832840 --- /dev/null +++ b/src/plugins/qmlprofiler/timelinemodelaggregator.h @@ -0,0 +1,122 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, 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, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + +#ifndef TIMELINEMODELAGGREGATOR_H +#define TIMELINEMODELAGGREGATOR_H + +#include "abstracttimelinemodel.h" +#include "qmlprofilermodelmanager.h" + +namespace QmlProfiler { +namespace Internal { + +class TimelineModelAggregator : public QObject +{ + Q_OBJECT +public: + TimelineModelAggregator(QObject *parent = 0); + ~TimelineModelAggregator(); + + void setModelManager(QmlProfilerModelManager *modelManager); + void addModel(AbstractTimelineModel *m); + + + Q_INVOKABLE int categories() const; + Q_INVOKABLE int visibleCategories() const; + Q_INVOKABLE QStringList categoryTitles() const; + Q_INVOKABLE int count(int modelIndex = -1) const; + void clear(); + Q_INVOKABLE int modelCount() const; + + Q_INVOKABLE qint64 traceStartTime() const; + Q_INVOKABLE qint64 traceEndTime() const; + Q_INVOKABLE qint64 traceDuration() const; + Q_INVOKABLE int getState() const; + + bool isEmpty() const; + + bool eventAccepted(const QmlProfilerSimpleModel::QmlEventData &event) const; + + Q_INVOKABLE int basicModelIndex() const; + + Q_INVOKABLE qint64 lastTimeMark() const; + + Q_INVOKABLE bool expanded(int modelIndex, int category) const; + Q_INVOKABLE void setExpanded(int modelIndex, int category, bool expanded); + Q_INVOKABLE int categoryDepth(int modelIndex, int categoryIndex) const; + Q_INVOKABLE int categoryCount(int modelIndex) const; + Q_INVOKABLE int rowCount(int modelIndex) const; + Q_INVOKABLE const QString categoryLabel(int modelIndex, int categoryIndex) const; + + int findFirstIndex(int modelIndex, qint64 startTime) const; + int findFirstIndexNoParents(int modelIndex, qint64 startTime) const; + int findLastIndex(int modelIndex, qint64 endTime) const; + + int getEventType(int modelIndex, int index) const; + Q_INVOKABLE int getEventCategoryInModel(int modelIndex, int index) const; + int getEventRow(int modelIndex, int index) const; + Q_INVOKABLE qint64 getDuration(int modelIndex, int index) const; + Q_INVOKABLE qint64 getStartTime(int modelIndex, int index) const; + Q_INVOKABLE qint64 getEndTime(int modelIndex, int index) const; + Q_INVOKABLE int getEventId(int modelIndex, int index) const; + Q_INVOKABLE int getBindingLoopDest(int modelIndex, int index) const; + Q_INVOKABLE QColor getColor(int modelIndex, int index) const; + Q_INVOKABLE QVariantList getColorRGB(int modelIndex, int itemIndex) const; + Q_INVOKABLE float getHeight(int modelIndex, int index) const; + + Q_INVOKABLE const QVariantList getLabelsForCategory(int modelIndex, int category) const; + + Q_INVOKABLE const QVariantList getEventDetails(int modelIndex, int index) const; + Q_INVOKABLE const QVariantMap getEventLocation(int modelIndex, int index) const; + + Q_INVOKABLE int getEventIdForHash(const QString &hash) const; + Q_INVOKABLE int getEventIdForLocation(const QString &filename, int line, int column) const; + + Q_INVOKABLE int modelIndexForCategory(int absoluteCategoryIndex) const; + Q_INVOKABLE int correctedCategoryIndexForModel(int modelIndex, int absoluteCategoryIndex) const; + +signals: + void countChanged(); + void dataAvailable(); + void stateChanged(); + void emptyChanged(); + void expandedChanged(); + +protected slots: + void dataChanged(); + +private: + class TimelineModelAggregatorPrivate; + TimelineModelAggregatorPrivate *d; +}; + +} +} + +#endif // TIMELINEMODELAGGREGATOR_H diff --git a/src/plugins/qmlprofiler/timelinerenderer.cpp b/src/plugins/qmlprofiler/timelinerenderer.cpp index 9af59c3d64..4f86f2cd9a 100644 --- a/src/plugins/qmlprofiler/timelinerenderer.cpp +++ b/src/plugins/qmlprofiler/timelinerenderer.cpp @@ -35,6 +35,7 @@ #include <QPixmap> #include <QPainter> #include <QGraphicsSceneMouseEvent> +#include <QVarLengthArray> #include <math.h> @@ -44,14 +45,26 @@ const int DefaultRowHeight = 30; TimelineRenderer::TimelineRenderer(QDeclarativeItem *parent) : QDeclarativeItem(parent), m_startTime(0), m_endTime(0), m_spacing(0), - m_lastStartTime(0), m_lastEndTime(0), m_profilerDataModel(0) + m_lastStartTime(0), m_lastEndTime(0) + , m_profilerModelProxy(0) { clearData(); setFlag(QGraphicsItem::ItemHasNoContents, false); setAcceptedMouseButtons(Qt::LeftButton); setAcceptHoverEvents(true); - for (int i=0; i<QmlDebug::MaximumQmlEventType; i++) - m_rowsExpanded << false; +} + +void TimelineRenderer::setProfilerModelProxy(QObject *profilerModelProxy) +{ + if (m_profilerModelProxy) { + disconnect(m_profilerModelProxy, SIGNAL(expandedChanged()), this, SLOT(requestPaint())); + } + m_profilerModelProxy = qobject_cast<TimelineModelAggregator *>(profilerModelProxy); + + if (m_profilerModelProxy) { + connect(m_profilerModelProxy, SIGNAL(expandedChanged()), this, SLOT(requestPaint())); + } + emit profilerModelProxyChanged(m_profilerModelProxy); } void TimelineRenderer::componentComplete() @@ -80,105 +93,68 @@ void TimelineRenderer::paint(QPainter *p, const QStyleOptionGraphicsItem *, QWid m_spacing = qreal(width()) / windowDuration; - m_rowWidths.clear(); - // The "1+" is because the reference screenshot features an empty row per type, in order to leave space for the title - for (int i=0; i<QmlDebug::MaximumQmlEventType; i++) { - m_rowWidths << 1 + (m_rowsExpanded[i] ? m_profilerDataModel->uniqueEventsOfType(i) : - m_profilerDataModel->maxNestingForType(i)); - } - - // event rows - m_rowStarts.clear(); - int pos = 0; - for (int i=0; i<QmlDebug::MaximumQmlEventType; i++) { - m_rowStarts << pos; - pos += DefaultRowHeight * m_rowWidths[i]; - } - p->setPen(Qt::transparent); - // speedup: don't draw overlapping events, just skip them - m_rowLastX.clear(); - for (int i=0; i<QmlDebug::MaximumQmlEventType; i++) - for (int j=0; j<m_rowWidths[i]; j++) - m_rowLastX << -m_startTime * m_spacing; - - int firstIndex = m_profilerDataModel->findFirstIndex(m_startTime); - int lastIndex = m_profilerDataModel->findLastIndex(m_endTime); - - if (lastIndex < m_profilerDataModel->count()) { - drawItemsToPainter(p, firstIndex, lastIndex); - drawSelectionBoxes(p, firstIndex, lastIndex); - drawBindingLoopMarkers(p, firstIndex, lastIndex); + for (int modelIndex = 0; modelIndex < m_profilerModelProxy->modelCount(); modelIndex++) { + int lastIndex = m_profilerModelProxy->findLastIndex(modelIndex, m_endTime); + if (lastIndex >= 0 && lastIndex < m_profilerModelProxy->count(modelIndex)) { + int firstIndex = m_profilerModelProxy->findFirstIndex(modelIndex, m_startTime); + if (firstIndex >= 0) { + drawItemsToPainter(p, modelIndex, firstIndex, lastIndex); + if (m_selectedModel == modelIndex) + drawSelectionBoxes(p, modelIndex, firstIndex, lastIndex); + drawBindingLoopMarkers(p, modelIndex, firstIndex, lastIndex); + } + } } - m_lastStartTime = m_startTime; m_lastEndTime = m_endTime; -} -QColor TimelineRenderer::colorForItem(int itemIndex) -{ - int ndx = m_profilerDataModel->getEventId(itemIndex); - return QColor::fromHsl((ndx*25)%360, 76, 166); } -void TimelineRenderer::drawItemsToPainter(QPainter *p, int fromIndex, int toIndex) +void TimelineRenderer::drawItemsToPainter(QPainter *p, int modelIndex, int fromIndex, int toIndex) { - int x, y, width, height, rowNumber, eventType; + p->save(); + p->setPen(Qt::transparent); + int modelRowStart = 0; + for (int mi = 0; mi < modelIndex; mi++) + modelRowStart += m_profilerModelProxy->rowCount(mi); + for (int i = fromIndex; i <= toIndex; i++) { - x = (m_profilerDataModel->getStartTime(i) - m_startTime) * m_spacing; + int x, y, width, height; + x = (m_profilerModelProxy->getStartTime(modelIndex, i) - m_startTime) * m_spacing; - eventType = m_profilerDataModel->getType(i); - if (m_rowsExpanded[eventType]) - y = m_rowStarts[eventType] + DefaultRowHeight * - (m_profilerDataModel->eventPosInType(i) + 1); - else - y = m_rowStarts[eventType] + DefaultRowHeight * - m_profilerDataModel->getNestingLevel(i); + int rowNumber = m_profilerModelProxy->getEventRow(modelIndex, i); + y = (modelRowStart + rowNumber) * DefaultRowHeight; - width = m_profilerDataModel->getDuration(i)*m_spacing; - if (width<1) + width = m_profilerModelProxy->getDuration(modelIndex, i) * m_spacing; + if (width < 1) width = 1; - rowNumber = y/DefaultRowHeight; - if (m_rowLastX[rowNumber] > x+width) - continue; - m_rowLastX[rowNumber] = x+width; - - // special: animations - if (eventType == 0 && m_profilerDataModel->getAnimationCount(i) >= 0) { - double scale = m_profilerDataModel->getMaximumAnimationCount() - - m_profilerDataModel->getMinimumAnimationCount(); - double fraction; - if (scale > 1) - fraction = (double)(m_profilerDataModel->getAnimationCount(i) - - m_profilerDataModel->getMinimumAnimationCount()) / scale; - else - fraction = 1.0; - height = DefaultRowHeight * (fraction * 0.85 + 0.15); - y += DefaultRowHeight - height; - - double fpsFraction = m_profilerDataModel->getFramerate(i) / 60.0; - if (fpsFraction > 1.0) - fpsFraction = 1.0; - p->setBrush(QColor::fromHsl((fpsFraction*96)+10, 76, 166)); - p->drawRect(x, y, width, height); - } else { - // normal events - p->setBrush(colorForItem(i)); - p->drawRect(x, y, width, DefaultRowHeight); - } + height = DefaultRowHeight * m_profilerModelProxy->getHeight(modelIndex, i); + y += DefaultRowHeight - height; + + // normal events + p->setBrush(m_profilerModelProxy->getColor(modelIndex, i)); + p->drawRect(x, y, width, height); } + p->restore(); } -void TimelineRenderer::drawSelectionBoxes(QPainter *p, int fromIndex, int toIndex) +void TimelineRenderer::drawSelectionBoxes(QPainter *p, int modelIndex, int fromIndex, int toIndex) { if (m_selectedItem == -1) return; - int id = m_profilerDataModel->getEventId(m_selectedItem); - p->setBrush(Qt::transparent); + int id = m_profilerModelProxy->getEventId(modelIndex, m_selectedItem); + + int modelRowStart = 0; + for (int mi = 0; mi < modelIndex; mi++) + modelRowStart += m_profilerModelProxy->rowCount(mi); + + p->save(); + QColor selectionColor = Qt::blue; if (m_selectionLocked) selectionColor = QColor(96,0,255); @@ -186,25 +162,18 @@ void TimelineRenderer::drawSelectionBoxes(QPainter *p, int fromIndex, int toInde QPen lightPen(QBrush(selectionColor.lighter(130)), 2); lightPen.setJoinStyle(Qt::MiterJoin); p->setPen(lightPen); + p->setBrush(Qt::transparent); - int x, y, width, eventType; - p->setPen(lightPen); - + int x, y, width; QRect selectedItemRect(0,0,0,0); for (int i = fromIndex; i <= toIndex; i++) { - if (m_profilerDataModel->getEventId(i) != id) + if (m_profilerModelProxy->getEventId(modelIndex, i) != id) continue; - x = (m_profilerDataModel->getStartTime(i) - m_startTime) * m_spacing; - eventType = m_profilerDataModel->getType(i); - if (m_rowsExpanded[eventType]) - y = m_rowStarts[eventType] + DefaultRowHeight * - (m_profilerDataModel->eventPosInType(i) + 1); - else - y = m_rowStarts[eventType] + DefaultRowHeight * - m_profilerDataModel->getNestingLevel(i); + x = (m_profilerModelProxy->getStartTime(modelIndex, i) - m_startTime) * m_spacing; + y = (modelRowStart + m_profilerModelProxy->getEventRow(modelIndex, i)) * DefaultRowHeight; - width = m_profilerDataModel->getDuration(i)*m_spacing; + width = m_profilerModelProxy->getDuration(modelIndex, i)*m_spacing; if (width<1) width = 1; @@ -219,12 +188,14 @@ void TimelineRenderer::drawSelectionBoxes(QPainter *p, int fromIndex, int toInde p->setPen(strongPen); p->drawRect(selectedItemRect); } + + p->restore(); } -void TimelineRenderer::drawBindingLoopMarkers(QPainter *p, int fromIndex, int toIndex) +void TimelineRenderer::drawBindingLoopMarkers(QPainter *p, int modelIndex, int fromIndex, int toIndex) { int destindex; - int xfrom, xto, eventType; + int xfrom, xto; int yfrom, yto; int radius = DefaultRowHeight / 3; QPen shadowPen = QPen(QColor("grey"),2); @@ -234,38 +205,24 @@ void TimelineRenderer::drawBindingLoopMarkers(QPainter *p, int fromIndex, int to p->save(); for (int i = fromIndex; i <= toIndex; i++) { - destindex = m_profilerDataModel->getBindingLoopDest(i); + destindex = m_profilerModelProxy->getBindingLoopDest(modelIndex, i); if (destindex >= 0) { // from - xfrom = (m_profilerDataModel->getStartTime(i) + - m_profilerDataModel->getDuration(i)/2 - + xfrom = (m_profilerModelProxy->getStartTime(modelIndex, i) + + m_profilerModelProxy->getDuration(modelIndex, i)/2 - m_startTime) * m_spacing; - eventType = m_profilerDataModel->getType(i); - if (m_rowsExpanded[eventType]) - yfrom = m_rowStarts[eventType] + DefaultRowHeight* - (m_profilerDataModel->eventPosInType(i) + 1); - else - yfrom = m_rowStarts[eventType] + DefaultRowHeight * - m_profilerDataModel->getNestingLevel(i); - + yfrom = getYPosition(modelIndex, i); yfrom += DefaultRowHeight / 2; // to - xto = (m_profilerDataModel->getStartTime(destindex) + - m_profilerDataModel->getDuration(destindex)/2 - + xto = (m_profilerModelProxy->getStartTime(modelIndex, destindex) + + m_profilerModelProxy->getDuration(modelIndex, destindex)/2 - m_startTime) * m_spacing; - eventType = m_profilerDataModel->getType(destindex); - if (m_rowsExpanded[eventType]) - yto = m_rowStarts[eventType] + DefaultRowHeight * - (m_profilerDataModel->eventPosInType(destindex) + 1); - else - yto = m_rowStarts[eventType] + DefaultRowHeight * - m_profilerDataModel->getNestingLevel(destindex); - + yto = getYPosition(modelIndex, destindex); yto += DefaultRowHeight / 2; // radius - int eventWidth = m_profilerDataModel->getDuration(i) * m_spacing; + int eventWidth = m_profilerModelProxy->getDuration(modelIndex, i) * m_spacing; radius = 5; if (radius * 2 > eventWidth) radius = eventWidth / 2; @@ -292,6 +249,17 @@ void TimelineRenderer::drawBindingLoopMarkers(QPainter *p, int fromIndex, int to p->restore(); } +int TimelineRenderer::modelFromPosition(int y) +{ + y = y / DefaultRowHeight; + for (int modelIndex = 0; modelIndex < m_profilerModelProxy->modelCount(); modelIndex++) { + y -= m_profilerModelProxy->rowCount(modelIndex); + if (y < 0) + return modelIndex; + } + return 0; +} + void TimelineRenderer::mousePressEvent(QGraphicsSceneMouseEvent *event) { // special case: if there is a drag area below me, don't accept the @@ -326,15 +294,17 @@ void TimelineRenderer::hoverMoveEvent(QGraphicsSceneHoverEvent *event) void TimelineRenderer::manageClicked() { if (m_currentSelection.eventIndex != -1) { - if (m_currentSelection.eventIndex == m_selectedItem) + if (m_currentSelection.eventIndex == m_selectedItem && m_currentSelection.modelIndex == m_selectedModel) setSelectionLocked(!m_selectionLocked); else setSelectionLocked(true); - emit itemPressed(m_currentSelection.eventIndex); + emit itemPressed(m_currentSelection.modelIndex, m_currentSelection.eventIndex); } else { -// setSelectionLocked(false); + setSelectionLocked(false); } + setSelectedModel(m_currentSelection.modelIndex); setSelectedItem(m_currentSelection.eventIndex); + } void TimelineRenderer::manageHovered(int x, int y) @@ -344,6 +314,7 @@ void TimelineRenderer::manageHovered(int x, int y) qint64 time = x * (m_endTime - m_startTime) / width() + m_startTime; int row = y / DefaultRowHeight; + int modelIndex = modelFromPosition(y); // already covered? nothing to do if (m_currentSelection.eventIndex != -1 && @@ -354,34 +325,37 @@ void TimelineRenderer::manageHovered(int x, int y) } // find if there's items in the time range - int eventFrom = m_profilerDataModel->findFirstIndex(time); - int eventTo = m_profilerDataModel->findLastIndex(time); - if (eventTo < eventFrom || eventTo >= m_profilerDataModel->count()) { + int eventFrom = m_profilerModelProxy->findFirstIndex(modelIndex, time); + int eventTo = m_profilerModelProxy->findLastIndex(modelIndex, time); + if (eventFrom == -1 || + eventTo < eventFrom || eventTo >= m_profilerModelProxy->count()) { m_currentSelection.eventIndex = -1; return; } + int modelRowStart = 0; + for (int mi = 0; mi < modelIndex; mi++) + modelRowStart += m_profilerModelProxy->rowCount(mi); + // find if we are in the right column - int itemRow, eventType; + int itemRow; for (int i=eventTo; i>=eventFrom; --i) { - if (ceil(m_profilerDataModel->getEndTime(i)*m_spacing) < floor(time*m_spacing)) + if (ceil(m_profilerModelProxy->getEndTime(modelIndex, i)*m_spacing) < floor(time*m_spacing)) continue; - eventType = m_profilerDataModel->getType(i); - if (m_rowsExpanded[eventType]) - itemRow = m_rowStarts[eventType]/DefaultRowHeight + - m_profilerDataModel->eventPosInType(i) + 1; - else - itemRow = m_rowStarts[eventType]/DefaultRowHeight + - m_profilerDataModel->getNestingLevel(i); + itemRow = modelRowStart + m_profilerModelProxy->getEventRow(modelIndex, i); + if (itemRow == row) { // match m_currentSelection.eventIndex = i; - m_currentSelection.startTime = m_profilerDataModel->getStartTime(i); - m_currentSelection.endTime = m_profilerDataModel->getEndTime(i); + m_currentSelection.startTime = m_profilerModelProxy->getStartTime(modelIndex, i); + m_currentSelection.endTime = m_profilerModelProxy->getEndTime(modelIndex, i); m_currentSelection.row = row; - if (!m_selectionLocked) + m_currentSelection.modelIndex = modelIndex; + if (!m_selectionLocked) { + setSelectedModel(modelIndex); setSelectedItem(i); + } return; } } @@ -400,131 +374,189 @@ void TimelineRenderer::clearData() m_currentSelection.endTime = -1; m_currentSelection.row = -1; m_currentSelection.eventIndex = -1; + m_currentSelection.modelIndex = -1; m_selectedItem = -1; + m_selectedModel = -1; m_selectionLocked = true; } -qint64 TimelineRenderer::getDuration(int index) const -{ - Q_ASSERT(m_profilerDataModel); - return m_profilerDataModel->getEndTime(index) - - m_profilerDataModel->getStartTime(index); -} - -QString TimelineRenderer::getFilename(int index) const -{ - Q_ASSERT(m_profilerDataModel); - return m_profilerDataModel->getFilename(index); -} - -int TimelineRenderer::getLine(int index) const +int TimelineRenderer::getYPosition(int modelIndex, int index) const { - Q_ASSERT(m_profilerDataModel); - return m_profilerDataModel->getLine(index); -} + Q_ASSERT(m_profilerModelProxy); + if (index >= m_profilerModelProxy->count()) + return 0; -QString TimelineRenderer::getDetails(int index) const -{ - Q_ASSERT(m_profilerDataModel); - return m_profilerDataModel->getDetails(index); -} + int modelRowStart = 0; + for (int mi = 0; mi < modelIndex; mi++) + modelRowStart += m_profilerModelProxy->rowCount(mi); -int TimelineRenderer::getYPosition(int index) const -{ - Q_ASSERT(m_profilerDataModel); - if (index >= m_profilerDataModel->count() || m_rowStarts.isEmpty()) - return 0; - int y, eventType = m_profilerDataModel->getType(index); - if (m_rowsExpanded[eventType]) - y = m_rowStarts[eventType] + DefaultRowHeight * - (m_profilerDataModel->eventPosInType(index) + 1); - else - y = m_rowStarts[eventType] + DefaultRowHeight * - m_profilerDataModel->getNestingLevel(index); + int y = DefaultRowHeight * (modelRowStart + m_profilerModelProxy->getEventRow(modelIndex, index)); return y; } -void TimelineRenderer::setRowExpanded(int rowIndex, bool expanded) -{ - m_rowsExpanded[rowIndex] = expanded; - update(); -} - void TimelineRenderer::selectNext() { - if (m_profilerDataModel->count() == 0) + if (m_profilerModelProxy->count() == 0) return; - // select next in view or after - int newIndex = m_selectedItem+1; - if (newIndex >= m_profilerDataModel->count()) - newIndex = 0; - if (m_profilerDataModel->getEndTime(newIndex) < m_startTime) - newIndex = m_profilerDataModel->findFirstIndexNoParents(m_startTime); - setSelectedItem(newIndex); + qint64 searchTime = m_startTime; + if (m_selectedItem != -1) + searchTime = m_profilerModelProxy->getStartTime(m_selectedModel, m_selectedItem); + + QVarLengthArray<int> itemIndexes(m_profilerModelProxy->modelCount()); + for (int i = 0; i < m_profilerModelProxy->modelCount(); i++) { + if (m_profilerModelProxy->count(i) > 0) { + if (m_selectedModel == i) { + itemIndexes[i] = (m_selectedItem + 1) % m_profilerModelProxy->count(i); + } else { + if (m_profilerModelProxy->getStartTime(i, 0) > searchTime) + itemIndexes[i] = 0; + else + itemIndexes[i] = (m_profilerModelProxy->findLastIndex(i, searchTime) + 1) % m_profilerModelProxy->count(i); + } + } else { + itemIndexes[i] = -1; + } + } + + int candidateModelIndex = -1; + qint64 candidateStartTime = m_profilerModelProxy->traceEndTime(); + for (int i = 0; i < m_profilerModelProxy->modelCount(); i++) { + if (itemIndexes[i] == -1) + continue; + qint64 newStartTime = m_profilerModelProxy->getStartTime(i, itemIndexes[i]); + if (newStartTime > searchTime && newStartTime < candidateStartTime) { + candidateStartTime = newStartTime; + candidateModelIndex = i; + } + } + + int itemIndex; + if (candidateModelIndex != -1) { + itemIndex = itemIndexes[candidateModelIndex]; + } else { + // find the first index of them all (todo: the modelproxy should do this) + itemIndex = -1; + candidateStartTime = m_profilerModelProxy->traceEndTime(); + for (int i = 0; i < m_profilerModelProxy->modelCount(); i++) + if (m_profilerModelProxy->count(i) > 0 && + m_profilerModelProxy->getStartTime(i,0) < candidateStartTime) { + candidateModelIndex = i; + itemIndex = 0; + candidateStartTime = m_profilerModelProxy->getStartTime(i,0); + } + } + + setSelectedModel(candidateModelIndex); + setSelectedItem(itemIndex); } void TimelineRenderer::selectPrev() { - if (m_profilerDataModel->count() == 0) + if (m_profilerModelProxy->count() == 0) return; - // select last in view or before - int newIndex = m_selectedItem-1; - if (newIndex < 0) - newIndex = m_profilerDataModel->count()-1; - if (m_profilerDataModel->getStartTime(newIndex) > m_endTime) - newIndex = m_profilerDataModel->findLastIndex(m_endTime); - setSelectedItem(newIndex); + qint64 searchTime = m_endTime; + if (m_selectedItem != -1) + searchTime = m_profilerModelProxy->getEndTime(m_selectedModel, m_selectedItem); + + QVarLengthArray<int> itemIndexes(m_profilerModelProxy->modelCount()); + for (int i = 0; i < m_profilerModelProxy->modelCount(); i++) { + if (m_selectedModel == i) { + itemIndexes[i] = m_selectedItem - 1; + if (itemIndexes[i] < 0) + itemIndexes[i] = m_profilerModelProxy->count(m_selectedModel) -1; + } + else + itemIndexes[i] = m_profilerModelProxy->findLastIndex(i, searchTime); + } + + int candidateModelIndex = -1; + qint64 candidateStartTime = m_profilerModelProxy->traceStartTime(); + for (int i = 0; i < m_profilerModelProxy->modelCount(); i++) { + if (itemIndexes[i] == -1 + || itemIndexes[i] >= m_profilerModelProxy->count(i)) + continue; + qint64 newStartTime = m_profilerModelProxy->getStartTime(i, itemIndexes[i]); + if (newStartTime < searchTime && newStartTime > candidateStartTime) { + candidateStartTime = newStartTime; + candidateModelIndex = i; + } + } + + int itemIndex = -1; + if (candidateModelIndex != -1) { + itemIndex = itemIndexes[candidateModelIndex]; + } else { + // find the last index of them all (todo: the modelproxy should do this) + candidateModelIndex = 0; + candidateStartTime = m_profilerModelProxy->traceStartTime(); + for (int i = 0; i < m_profilerModelProxy->modelCount(); i++) + if (m_profilerModelProxy->count(i) > 0 && + m_profilerModelProxy->getStartTime(i,m_profilerModelProxy->count(i)-1) > candidateStartTime) { + candidateModelIndex = i; + itemIndex = m_profilerModelProxy->count(candidateModelIndex) - 1; + candidateStartTime = m_profilerModelProxy->getStartTime(i,m_profilerModelProxy->count(i)-1); + } + } + + setSelectedModel(candidateModelIndex); + setSelectedItem(itemIndex); } -int TimelineRenderer::nextItemFromId(int eventId) const +int TimelineRenderer::nextItemFromId(int modelIndex, int eventId) const { int ndx = -1; if (m_selectedItem == -1) - ndx = m_profilerDataModel->findFirstIndexNoParents(m_startTime); + ndx = m_profilerModelProxy->findFirstIndexNoParents(modelIndex, m_startTime); else ndx = m_selectedItem + 1; - if (ndx >= m_profilerDataModel->count()) + if (ndx < 0) + return -1; + if (ndx >= m_profilerModelProxy->count(modelIndex)) ndx = 0; int startIndex = ndx; do { - if (m_profilerDataModel->getEventId(ndx) == eventId) + if (m_profilerModelProxy->getEventId(modelIndex, ndx) == eventId) return ndx; - ndx = (ndx + 1) % m_profilerDataModel->count(); + ndx = (ndx + 1) % m_profilerModelProxy->count(modelIndex); } while (ndx != startIndex); return -1; } -int TimelineRenderer::prevItemFromId(int eventId) const +int TimelineRenderer::prevItemFromId(int modelIndex, int eventId) const { int ndx = -1; if (m_selectedItem == -1) - ndx = m_profilerDataModel->findFirstIndexNoParents(m_startTime); + ndx = m_profilerModelProxy->findFirstIndexNoParents(modelIndex, m_startTime); else ndx = m_selectedItem - 1; if (ndx < 0) - ndx = m_profilerDataModel->count() - 1; + ndx = m_profilerModelProxy->count(modelIndex) - 1; int startIndex = ndx; do { - if (m_profilerDataModel->getEventId(ndx) == eventId) + if (m_profilerModelProxy->getEventId(modelIndex, ndx) == eventId) return ndx; if (--ndx < 0) - ndx = m_profilerDataModel->count()-1; + ndx = m_profilerModelProxy->count(modelIndex)-1; } while (ndx != startIndex); return -1; } -void TimelineRenderer::selectNextFromId(int eventId) +void TimelineRenderer::selectNextFromId(int modelIndex, int eventId) { - int eventIndex = nextItemFromId(eventId); - if (eventIndex != -1) + int eventIndex = nextItemFromId(modelIndex, eventId); + if (eventIndex != -1) { + setSelectedModel(modelIndex); setSelectedItem(eventIndex); + } } -void TimelineRenderer::selectPrevFromId(int eventId) +void TimelineRenderer::selectPrevFromId(int modelIndex, int eventId) { - int eventIndex = prevItemFromId(eventId); - if (eventIndex != -1) + int eventIndex = prevItemFromId(modelIndex, eventId); + if (eventIndex != -1) { + setSelectedModel(modelIndex); setSelectedItem(eventIndex); + } } diff --git a/src/plugins/qmlprofiler/timelinerenderer.h b/src/plugins/qmlprofiler/timelinerenderer.h index d408b5ef25..d4c70a4d9e 100644 --- a/src/plugins/qmlprofiler/timelinerenderer.h +++ b/src/plugins/qmlprofiler/timelinerenderer.h @@ -32,7 +32,8 @@ #include <QDeclarativeItem> #include <QScriptValue> -#include "qmlprofilerdatamodel.h" +#include "qmlprofilertimelinemodelproxy.h" +#include "timelinemodelaggregator.h" namespace QmlProfiler { namespace Internal { @@ -42,9 +43,10 @@ class TimelineRenderer : public QDeclarativeItem Q_OBJECT Q_PROPERTY(qint64 startTime READ startTime WRITE setStartTime NOTIFY startTimeChanged) Q_PROPERTY(qint64 endTime READ endTime WRITE setEndTime NOTIFY endTimeChanged) - Q_PROPERTY(QObject* profilerDataModel READ profilerDataModel WRITE setProfilerDataModel NOTIFY profilerDataModelChanged) + Q_PROPERTY(QObject *profilerModelProxy READ profilerModelProxy WRITE setProfilerModelProxy NOTIFY profilerModelProxyChanged) Q_PROPERTY(bool selectionLocked READ selectionLocked WRITE setSelectionLocked NOTIFY selectionLockedChanged) Q_PROPERTY(int selectedItem READ selectedItem WRITE setSelectedItem NOTIFY selectedItemChanged) + Q_PROPERTY(int selectedModel READ selectedModel WRITE setSelectedModel NOTIFY selectedModelChanged) Q_PROPERTY(int startDragArea READ startDragArea WRITE setStartDragArea NOTIFY startDragAreaChanged) Q_PROPERTY(int endDragArea READ endDragArea WRITE setEndDragArea NOTIFY endDragAreaChanged) @@ -71,6 +73,11 @@ public: return m_selectedItem; } + int selectedModel() const + { + return m_selectedModel; + } + int startDragArea() const { return m_startDragArea; @@ -81,37 +88,28 @@ public: return m_endDragArea; } - QmlProfilerDataModel *profilerDataModel() const { return m_profilerDataModel; } - void setProfilerDataModel(QObject *profilerDataModel) - { - m_profilerDataModel = qobject_cast<QmlProfilerDataModel *>(profilerDataModel); - emit profilerDataModelChanged(m_profilerDataModel); - } - - 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 int getYPosition(int index) const; + TimelineModelAggregator *profilerModelProxy() const { return m_profilerModelProxy; } + void setProfilerModelProxy(QObject *profilerModelProxy); - Q_INVOKABLE void setRowExpanded(int rowIndex, bool expanded); + Q_INVOKABLE int getYPosition(int modelIndex, int index) const; Q_INVOKABLE void selectNext(); Q_INVOKABLE void selectPrev(); - Q_INVOKABLE int nextItemFromId(int eventId) const; - Q_INVOKABLE int prevItemFromId(int eventId) const; - Q_INVOKABLE void selectNextFromId(int eventId); - Q_INVOKABLE void selectPrevFromId(int eventId); + Q_INVOKABLE int nextItemFromId(int modelIndex, int eventId) const; + Q_INVOKABLE int prevItemFromId(int modelIndex, int eventId) const; + Q_INVOKABLE void selectNextFromId(int modelIndex, int eventId); + Q_INVOKABLE void selectPrevFromId(int modelIndex, int eventId); signals: void startTimeChanged(qint64 arg); void endTimeChanged(qint64 arg); - void profilerDataModelChanged(QmlProfilerDataModel *list); + void profilerModelProxyChanged(TimelineModelAggregator *list); void selectionLockedChanged(bool locked); - void selectedItemChanged(int itemIndex); + void selectedItemChanged(int modelIndex, int itemIndex); + void selectedModelChanged(int modelIndex); void startDragAreaChanged(int startDragArea); void endDragAreaChanged(int endDragArea); - void itemPressed(int pressedItem); + void itemPressed(int modelIndex, int pressedItem); public slots: void clearData(); @@ -148,7 +146,16 @@ public slots: if (m_selectedItem != itemIndex) { m_selectedItem = itemIndex; update(); - emit selectedItemChanged(itemIndex); + emit selectedItemChanged(m_selectedModel, itemIndex); + } + } + + void setSelectedModel(int modelIndex) + { + if (m_selectedModel != modelIndex) { + m_selectedModel = modelIndex; + update(); + emit selectedModelChanged(modelIndex); } } @@ -177,10 +184,10 @@ protected: virtual void hoverMoveEvent(QGraphicsSceneHoverEvent *event); private: - QColor colorForItem(int itemIndex); - void drawItemsToPainter(QPainter *p, int fromIndex, int toIndex); - void drawSelectionBoxes(QPainter *p, int fromIndex, int toIndex); - void drawBindingLoopMarkers(QPainter *p, int fromIndex, int toIndex); + void drawItemsToPainter(QPainter *p, int modelIndex, int fromIndex, int toIndex); + void drawSelectionBoxes(QPainter *p, int modelIndex, int fromIndex, int toIndex); + void drawBindingLoopMarkers(QPainter *p, int modelIndex, int fromIndex, int toIndex); + int modelFromPosition(int y); void manageClicked(); void manageHovered(int x, int y); @@ -192,21 +199,18 @@ private: qint64 m_lastStartTime; qint64 m_lastEndTime; - QmlProfilerDataModel *m_profilerDataModel; - - QList<int> m_rowLastX; - QList<int> m_rowStarts; - QList<int> m_rowWidths; - QList<bool> m_rowsExpanded; + TimelineModelAggregator *m_profilerModelProxy; struct { qint64 startTime; qint64 endTime; int row; int eventIndex; + int modelIndex; } m_currentSelection; int m_selectedItem; + int m_selectedModel; bool m_selectionLocked; int m_startDragArea; int m_endDragArea; |