diff --git a/src/libs/qmljsdebugclient/qmlprofilereventlist.cpp b/src/libs/qmljsdebugclient/qmlprofilereventlist.cpp index 51531af8211e40c7d0d403ba9aa283b586d59788..0bca4e029aeab614ad3ca45c642512e89d0b2b2e 100644 --- a/src/libs/qmljsdebugclient/qmlprofilereventlist.cpp +++ b/src/libs/qmljsdebugclient/qmlprofilereventlist.cpp @@ -71,6 +71,7 @@ QmlEventData::QmlEventData() timePerCall = 0; percentOfTime = 0; medianTime = 0; + isBindingLoop = false; } QmlEventData::~QmlEventData() @@ -99,6 +100,7 @@ QmlEventData &QmlEventData::operator=(const QmlEventData &ref) percentOfTime = ref.percentOfTime; medianTime = ref.medianTime; eventId = ref.eventId; + isBindingLoop = ref.isBindingLoop; qDeleteAll(parentHash.values()); parentHash.clear(); @@ -182,6 +184,8 @@ struct QmlEventStartTimeData { // animation-related data int frameRate; int animationCount; + + int bindingLoopHead; }; struct QmlEventTypeCount { @@ -790,6 +794,9 @@ void QmlProfilerEventList::compileStatistics(qint64 startTime, qint64 endTime) iter.key()->medianTime = iter.value().at(iter.value().count()/2); } } + + // find binding loops + findBindingLoops(startTime, endTime); } void QmlProfilerEventList::prepareForDisplay() @@ -1063,6 +1070,59 @@ void QmlProfilerEventList::finishedRewritingDetails() emit reloadDetailLabels(); } +void QmlProfilerEventList::findBindingLoops(qint64 startTime, qint64 endTime) +{ + // first clear existing data + foreach (QmlEventData *event, d->m_eventDescriptions.values()) { + event->isBindingLoop = false; + foreach (QmlEventSub *parentEvent, event->parentHash.values()) + parentEvent->inLoopPath = false; + foreach (QmlEventSub *childEvent, event->childrenHash.values()) + childEvent->inLoopPath = false; + } + + QList <QmlEventData *> stackRefs; + QList <QmlEventStartTimeData *> stack; + int fromIndex = findFirstIndex(startTime); + int toIndex = findLastIndex(endTime); + + for (int i = 0; i < d->m_startTimeSortedList.count(); i++) { + QmlEventData *currentEvent = d->m_startTimeSortedList[i].description; + QmlEventStartTimeData *inTimeEvent = &d->m_startTimeSortedList[i]; + inTimeEvent->bindingLoopHead = -1; + + // managing call stack + for (int j = stack.count() - 1; j >= 0; j--) { + if (stack[j]->startTime + stack[j]->length < inTimeEvent->startTime) { + stack.removeAt(j); + stackRefs.removeAt(j); + } + } + + bool loopDetected = stackRefs.contains(currentEvent); + stack << inTimeEvent; + stackRefs << currentEvent; + + if (loopDetected) { + if (i >= fromIndex && i <= toIndex) { + // for the statistics + currentEvent->isBindingLoop = true; + for (int j = stackRefs.indexOf(currentEvent); j < stackRefs.count()-1; j++) { + QmlEventSub *nextEventSub = stackRefs[j]->childrenHash.value(stackRefs[j+1]->eventHashStr); + nextEventSub->inLoopPath = true; + QmlEventSub *prevEventSub = stackRefs[j+1]->parentHash.value(stackRefs[j]->eventHashStr); + prevEventSub->inLoopPath = true; + } + } + + // use crossed references to find index in starttimesortedlist + QmlEventStartTimeData *head = stack[stackRefs.indexOf(currentEvent)]; + inTimeEvent->bindingLoopHead = d->m_endTimeSortedList[head->endTimeIndex].startTimeIndex; + d->m_startTimeSortedList[inTimeEvent->bindingLoopHead].bindingLoopHead = i; + } + } +} + // get list of events between A and B: // find fist event with endtime after A -> aa // find last event with starttime before B -> bb @@ -1689,10 +1749,16 @@ QString QmlProfilerEventList::getDetails(int index) const return d->m_startTimeSortedList[index].description->details; } -int QmlProfilerEventList::getEventId(int index) const { +int QmlProfilerEventList::getEventId(int index) const +{ return d->m_startTimeSortedList[index].description->eventId; } +int QmlProfilerEventList::getBindingLoopDest(int index) const +{ + return d->m_startTimeSortedList[index].bindingLoopHead; +} + int QmlProfilerEventList::getFramerate(int index) const { return d->m_startTimeSortedList[index].frameRate; diff --git a/src/libs/qmljsdebugclient/qmlprofilereventlist.h b/src/libs/qmljsdebugclient/qmlprofilereventlist.h index b8eef3a2bc86bafc2487e2dcd063a8388dc26707..b5b9660354e3660c6a4bb121a88722923b2dc163 100644 --- a/src/libs/qmljsdebugclient/qmlprofilereventlist.h +++ b/src/libs/qmljsdebugclient/qmlprofilereventlist.h @@ -65,16 +65,18 @@ struct QMLJSDEBUGCLIENT_EXPORT QmlEventData double percentOfTime; qint64 medianTime; int eventId; + bool isBindingLoop; QmlEventData &operator=(const QmlEventData &ref); }; struct QMLJSDEBUGCLIENT_EXPORT QmlEventSub { - QmlEventSub(QmlEventData *from) : reference(from), duration(0), calls(0) {} - QmlEventSub(QmlEventSub *from) : reference(from->reference), duration(from->duration), calls(from->calls) {} + QmlEventSub(QmlEventData *from) : reference(from), duration(0), calls(0), inLoopPath(false) {} + QmlEventSub(QmlEventSub *from) : reference(from->reference), duration(from->duration), calls(from->calls), inLoopPath(from->inLoopPath) {} QmlEventData *reference; qint64 duration; qint64 calls; + bool inLoopPath; }; struct QMLJSDEBUGCLIENT_EXPORT QV8EventData @@ -142,11 +144,13 @@ public: 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; @@ -206,6 +210,8 @@ private: void prepareForDisplay(); void linkEndsToStarts(); void reloadDetails(); + void findBindingLoops(qint64 startTime, qint64 endTime); + bool checkBindingLoop(QmlEventData *from, QmlEventData *current, QList<QmlEventData *>visited); private: class QmlProfilerEventListPrivate; diff --git a/src/plugins/qmlprofiler/qml/MainView.qml b/src/plugins/qmlprofiler/qml/MainView.qml index bf0fd6f6f6cf34b08e64ab566bd319dfd55283a1..557d8279e6b3dd8f429dec80e873cbada5b15b12 100644 --- a/src/plugins/qmlprofiler/qml/MainView.qml +++ b/src/plugins/qmlprofiler/qml/MainView.qml @@ -269,6 +269,7 @@ Rectangle { rangeDetails.file = ""; rangeDetails.line = -1; rangeDetails.column = 0; + rangeDetails.isBindingLoop = false; } function selectNextWithId( eventId ) @@ -424,6 +425,7 @@ Rectangle { rangeDetails.line = qmlEventList.getLine(selectedItem); rangeDetails.column = qmlEventList.getColumn(selectedItem); rangeDetails.type = root.names[qmlEventList.getType(selectedItem)]; + rangeDetails.isBindingLoop = qmlEventList.getBindingLoopDest(selectedItem)!==-1; rangeDetails.visible = true; diff --git a/src/plugins/qmlprofiler/qml/Overview.js b/src/plugins/qmlprofiler/qml/Overview.js index 7aeeabd971d41e24458c57903210fe4d24aa63ed..4fe625a7fd10e7cc5e7346ae2b5e208358051386 100644 --- a/src/plugins/qmlprofiler/qml/Overview.js +++ b/src/plugins/qmlprofiler/qml/Overview.js @@ -95,6 +95,21 @@ function drawData(canvas, ctxt, region) highest[ty] = xx+eventWidth; } } + + // binding loops + ctxt.strokeStyle = "orange"; + ctxt.lineWidth = 2; + var radius = 1; + for (var ii = 0; ii < qmlEventList.count(); ++ii) { + if (qmlEventList.getBindingLoopDest(ii) >= 0) { + var xcenter = Math.round(qmlEventList.getStartTime(ii) + + qmlEventList.getDuration(ii) - + qmlEventList.traceStartTime()) * spacing; + var ycenter = Math.round(bump + qmlEventList.getType(ii) * blockHeight + blockHeight/2); + ctxt.arc(xcenter, ycenter, radius, 0, 2*Math.PI, true); + ctxt.stroke(); + } + } } function drawTimeBar(canvas, ctxt, region) diff --git a/src/plugins/qmlprofiler/qml/RangeDetails.qml b/src/plugins/qmlprofiler/qml/RangeDetails.qml index 7013affe08ee3161cdaf033adf3665c190ba77c4..324c0903b0b9825ef188d1850ed105d9536c48de 100644 --- a/src/plugins/qmlprofiler/qml/RangeDetails.qml +++ b/src/plugins/qmlprofiler/qml/RangeDetails.qml @@ -42,6 +42,7 @@ Item { property string file property int line property int column + property bool isBindingLoop property bool locked: view.selectionLocked @@ -153,6 +154,11 @@ Item { return (file.length !== 0) ? (file + ":" + rangeDetails.line) : ""; } } + Detail { + visible: isBindingLoop + opacity: content.length != 0 ? 1 : 0 + label: qsTr("Binding loop detected") + } } } diff --git a/src/plugins/qmlprofiler/qmlprofilereventview.cpp b/src/plugins/qmlprofiler/qmlprofilereventview.cpp index f89a966f9b81276c1760399b1e108b7243e6b1bf..57eb3d4fa24ace07cea86511f0358633b17db43d 100644 --- a/src/plugins/qmlprofiler/qmlprofilereventview.cpp +++ b/src/plugins/qmlprofiler/qmlprofilereventview.cpp @@ -54,6 +54,16 @@ using namespace QmlJsDebugClient; namespace QmlProfiler { namespace Internal { +struct Colors { + Colors () { + this->bindingLoopBackground = QColor("orange").lighter(); + } + + QColor bindingLoopBackground; +}; + +Q_GLOBAL_STATIC(Colors, colors) + //////////////////////////////////////////////////////////////////////////////////// class EventsViewItem : public QStandardItem @@ -531,6 +541,11 @@ void QmlProfilerEventsMainView::QmlProfilerEventsMainViewPrivate::buildModelFrom 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) + foreach (QStandardItem *item, newRow) { + item->setBackground(colors()->bindingLoopBackground); + item->setToolTip(tr("Binding loop detected")); + } // append parentItem->appendRow(newRow); @@ -877,6 +892,11 @@ void QmlProfilerEventsParentsAndChildrenView::rebuildTree(void *eventList) 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); diff --git a/src/plugins/qmlprofiler/timelineview.cpp b/src/plugins/qmlprofiler/timelineview.cpp index aa971f166fcefc79a56ec66acc79a57c8c9c3a6c..3fd0d4c07d3ec780c689b649b8d633c4c3091a81 100644 --- a/src/plugins/qmlprofiler/timelineview.cpp +++ b/src/plugins/qmlprofiler/timelineview.cpp @@ -107,9 +107,10 @@ void TimelineView::paint(QPainter *p, const QStyleOptionGraphicsItem *, QWidget int firstIndex = m_eventList->findFirstIndex(m_startTime); int lastIndex = m_eventList->findLastIndex(m_endTime); - drawItemsToPainter(p, firstIndex, lastIndex); - drawSelectionBoxes(p); + drawItemsToPainter(p, firstIndex, lastIndex); + drawSelectionBoxes(p, firstIndex, lastIndex); + drawBindingLoopMarkers(p, firstIndex, lastIndex); m_lastStartTime = m_startTime; m_lastEndTime = m_endTime; @@ -166,13 +167,11 @@ void TimelineView::drawItemsToPainter(QPainter *p, int fromIndex, int toIndex) } } -void TimelineView::drawSelectionBoxes(QPainter *p) +void TimelineView::drawSelectionBoxes(QPainter *p, int fromIndex, int toIndex) { if (m_selectedItem == -1) return; - int fromIndex = m_eventList->findFirstIndex(m_startTime); - int toIndex = m_eventList->findLastIndex(m_endTime); int id = m_eventList->getEventId(m_selectedItem); p->setBrush(Qt::transparent); @@ -216,6 +215,74 @@ void TimelineView::drawSelectionBoxes(QPainter *p) } } +void TimelineView::drawBindingLoopMarkers(QPainter *p, int fromIndex, int toIndex) +{ + int destindex; + int xfrom, xto, eventType; + int yfrom, yto; + int radius = DefaultRowHeight / 3; + QPen shadowPen = QPen(QColor("grey"),2); + QPen markerPen = QPen(QColor("orange"),2); + QBrush shadowBrush = QBrush(QColor("grey")); + QBrush markerBrush = QBrush(QColor("orange")); + + p->save(); + for (int i = fromIndex; i <= toIndex; i++) { + destindex = m_eventList->getBindingLoopDest(i); + if (destindex >= 0) { + // from + xfrom = (m_eventList->getStartTime(i) + m_eventList->getDuration(i)/2 - + m_startTime) * m_spacing; + eventType = m_eventList->getType(i); + if (m_rowsExpanded[eventType]) + yfrom = m_rowStarts[eventType] + DefaultRowHeight* + (m_eventList->eventPosInType(i) + 1); + else + yfrom = m_rowStarts[eventType] + DefaultRowHeight*m_eventList->getNestingLevel(i); + + yfrom += DefaultRowHeight / 2; + + // to + xto = (m_eventList->getStartTime(destindex) + m_eventList->getDuration(destindex)/2 - + m_startTime) * m_spacing; + eventType = m_eventList->getType(destindex); + if (m_rowsExpanded[eventType]) + yto = m_rowStarts[eventType] + DefaultRowHeight* + (m_eventList->eventPosInType(destindex) + 1); + else + yto = m_rowStarts[eventType] + DefaultRowHeight * + m_eventList->getNestingLevel(destindex); + + yto += DefaultRowHeight / 2; + + // radius + int eventWidth = m_eventList->getDuration(i) * m_spacing; + radius = 5; + if (radius * 2 > eventWidth) + radius = eventWidth / 2; + if (radius < 2) + radius = 2; + + // shadow + int shadowoffset = 2; + p->setPen(shadowPen); + p->setBrush(shadowBrush); + p->drawEllipse(QPoint(xfrom, yfrom + shadowoffset), radius, radius); + p->drawEllipse(QPoint(xto, yto + shadowoffset), radius, radius); + p->drawLine(QPoint(xfrom, yfrom + shadowoffset), QPoint(xto, yto + shadowoffset)); + + + // marker + p->setPen(markerPen); + p->setBrush(markerBrush); + p->drawEllipse(QPoint(xfrom, yfrom), radius, radius); + p->drawEllipse(QPoint(xto, yto), radius, radius); + p->drawLine(QPoint(xfrom, yfrom), QPoint(xto, yto)); + } + } + p->restore(); +} + void TimelineView::mousePressEvent(QGraphicsSceneMouseEvent *event) { // special case: if there is a drag area below me, don't accept the diff --git a/src/plugins/qmlprofiler/timelineview.h b/src/plugins/qmlprofiler/timelineview.h index 9b04b6dc5bb72482d101c2fba5d0e47e6825f4af..dcb3bbfc3feccfd3b4fba91b5802e0d853a51103 100644 --- a/src/plugins/qmlprofiler/timelineview.h +++ b/src/plugins/qmlprofiler/timelineview.h @@ -182,7 +182,8 @@ protected: private: QColor colorForItem(int itemIndex); void drawItemsToPainter(QPainter *p, int fromIndex, int toIndex); - void drawSelectionBoxes(QPainter *p); + void drawSelectionBoxes(QPainter *p, int fromIndex, int toIndex); + void drawBindingLoopMarkers(QPainter *p, int fromIndex, int toIndex); void manageClicked(); void manageHovered(int x, int y);