Commit b7742f79 authored by Ulf Hermann's avatar Ulf Hermann Committed by Ulf Hermann
Browse files

QmlProfiler: Merge SortedTimelineModel into AbstractTimelineModel



In order to provide a stable interface we need most of
SortedTimelineModel to be private. There is no real benefit in keeping
a distinction between AbstractTimelineModel and SortedTimelineModel as
SortedTimelineModel isn't very useful on its own.

Change-Id: Ibc6945e2740320f430f2634f95c7807d6b460123
Reviewed-by: default avatarKai Koehne <kai.koehne@theqtcompany.com>
parent a42e07be
......@@ -31,28 +31,124 @@
#include "abstracttimelinemodel.h"
#include "abstracttimelinemodel_p.h"
#include <QLinkedList>
namespace QmlProfiler {
/*!
\class QmlProfiler::AbstractTimelineModel
\brief The AbstractTimelineModel class provides a sorted model for timeline data.
The AbstractTimelineModel lets you keep range data sorted by both start and end times, so that
visible ranges can easily be computed. The only precondition for that to work is that the ranges
must be perfectly nested. A "parent" range of a range R is defined as a range for which the
start time is smaller than R's start time and the end time is greater than R's end time. A set
of ranges is perfectly nested if all parent ranges of any given range have a common parent
range. Mind that you can always make that happen by defining a range that spans the whole
available time span. That, however, will make any code that uses firstStartTime() and
lastEndTime() for selecting subsets of the model always select all of it.
\note Indices returned from the various methods are only valid until a new range is inserted
before them. Inserting a new range before a given index moves the range pointed to by the
index by one. Incrementing the index by one will make it point to the item again.
*/
/*!
\fn qint64 AbstractTimelineModelPrivate::firstStartTime() const
Returns the begin of the first range in the model.
*/
/*!
\fn qint64 AbstractTimelineModelPrivate::lastEndTime() const
Returns the end of the last range in the model.
*/
/*!
\fn const AbstractTimelineModelPrivate::Range &AbstractTimelineModelPrivate::range(int index) const
Returns the range data at the specified index.
*/
/*!
\fn void AbstractTimelineModel::computeNesting()
Compute all ranges' parents.
\sa findFirstIndex
*/
void AbstractTimelineModel::computeNesting()
{
Q_D(AbstractTimelineModel);
QLinkedList<int> parents;
for (int range = 0; range != count(); ++range) {
AbstractTimelineModelPrivate::Range &current = d->ranges[range];
for (QLinkedList<int>::iterator parentIt = parents.begin();;) {
if (parentIt == parents.end()) {
parents.append(range);
break;
}
AbstractTimelineModelPrivate::Range &parent = d->ranges[*parentIt];
qint64 parentEnd = parent.start + parent.duration;
if (parentEnd < current.start) {
if (parent.start == current.start) {
if (parent.parent == -1) {
parent.parent = range;
} else {
AbstractTimelineModelPrivate::Range &ancestor = d->ranges[parent.parent];
if (ancestor.start == current.start &&
ancestor.duration < current.duration)
parent.parent = range;
}
// Just switch the old parent range for the new, larger one
*parentIt = range;
break;
} else {
parentIt = parents.erase(parentIt);
}
} else if (parentEnd >= current.start + current.duration) {
// no need to insert
current.parent = *parentIt;
break;
} else {
++parentIt;
}
}
}
}
void AbstractTimelineModel::AbstractTimelineModelPrivate::init(AbstractTimelineModel *q,
const QString &newDisplayName,
QmlDebug::Message newMessage,
QmlDebug::RangeType newRangeType)
{
q_ptr = q;
modelId = 0;
modelManager = 0;
expanded = false;
hidden = false;
displayName = newDisplayName;
message = newMessage;
rangeType = newRangeType;
expandedRowCount = 1;
collapsedRowCount = 1;
connect(q,SIGNAL(rowHeightChanged()),q,SIGNAL(heightChanged()));
connect(q,SIGNAL(expandedChanged()),q,SIGNAL(heightChanged()));
connect(q,SIGNAL(hiddenChanged()),q,SIGNAL(heightChanged()));
}
AbstractTimelineModel::AbstractTimelineModel(AbstractTimelineModelPrivate *dd,
const QString &displayName, QmlDebug::Message message, QmlDebug::RangeType rangeType,
QObject *parent) :
SortedTimelineModel(parent), d_ptr(dd)
QObject(parent), d_ptr(dd)
{
Q_D(AbstractTimelineModel);
connect(this,SIGNAL(rowHeightChanged()),this,SIGNAL(heightChanged()));
connect(this,SIGNAL(expandedChanged()),this,SIGNAL(heightChanged()));
connect(this,SIGNAL(hiddenChanged()),this,SIGNAL(heightChanged()));
dd->init(this, displayName, message, rangeType);
}
d->q_ptr = this;
d->modelId = 0;
d->modelManager = 0;
d->expanded = false;
d->hidden = false;
d->displayName = displayName;
d->message = message;
d->rangeType = rangeType;
d->expandedRowCount = 1;
d->collapsedRowCount = 1;
AbstractTimelineModel::AbstractTimelineModel(const QString &displayName, QmlDebug::Message message,
QmlDebug::RangeType rangeType, QObject *parent) :
QObject(parent), d_ptr(new AbstractTimelineModelPrivate)
{
d_ptr->init(this, displayName, message, rangeType);
}
AbstractTimelineModel::~AbstractTimelineModel()
......@@ -138,6 +234,97 @@ int AbstractTimelineModel::height() const
return d->rowOffsets.last() + (depth - d->rowOffsets.size()) * DefaultRowHeight;
}
/*!
\fn int AbstractTimelineModel::count() const
Returns the number of ranges in the model.
*/
int AbstractTimelineModel::count() const
{
Q_D(const AbstractTimelineModel);
return d->ranges.count();
}
qint64 AbstractTimelineModel::duration(int index) const
{
Q_D(const AbstractTimelineModel);
return d->ranges[index].duration;
}
qint64 AbstractTimelineModel::startTime(int index) const
{
Q_D(const AbstractTimelineModel);
return d->ranges[index].start;
}
qint64 AbstractTimelineModel::endTime(int index) const
{
Q_D(const AbstractTimelineModel);
return d->ranges[index].start + d->ranges[index].duration;
}
int AbstractTimelineModel::typeId(int index) const
{
Q_D(const AbstractTimelineModel);
return d->ranges[index].typeId;
}
/*!
\fn int AbstractTimelineModel::firstIndex(qint64 startTime) const
Looks up the first range with an end time greater than the given time and
returns its parent's index. If no such range is found, it returns -1. If there
is no parent, it returns the found range's index. The parent of a range is the
range with the lowest start time that completely covers the child range.
"Completely covers" means:
parent.startTime <= child.startTime && parent.endTime >= child.endTime
*/
int AbstractTimelineModel::firstIndex(qint64 startTime) const
{
Q_D(const AbstractTimelineModel);
int index = firstIndexNoParents(startTime);
if (index == -1)
return -1;
int parent = d->ranges[index].parent;
return parent == -1 ? index : parent;
}
/*!
\fn int AbstractTimelineModel::firstIndexNoParents(qint64 startTime) const
Looks up the first range with an end time greater than the given time and
returns its index. If no such range is found, it returns -1.
*/
int AbstractTimelineModel::firstIndexNoParents(qint64 startTime) const
{
Q_D(const AbstractTimelineModel);
// in the "endtime" list, find the first event that ends after startTime
if (d->endTimes.isEmpty())
return -1;
if (d->endTimes.count() == 1 || d->endTimes.first().end > startTime)
return d->endTimes.first().startIndex;
if (d->endTimes.last().end <= startTime)
return -1;
return d->endTimes[d->lowerBound(d->endTimes, startTime) + 1].startIndex;
}
/*!
\fn int AbstractTimelineModel::lastIndex(qint64 endTime) const
Looks up the last range with a start time smaller than the given time and
returns its index. If no such range is found, it returns -1.
*/
int AbstractTimelineModel::lastIndex(qint64 endTime) const
{
Q_D(const AbstractTimelineModel);
// in the "starttime" list, find the last event that starts before endtime
if (d->ranges.isEmpty() || d->ranges.first().start >= endTime)
return -1;
if (d->ranges.count() == 1)
return 0;
if (d->ranges.last().start < endTime)
return d->ranges.count() - 1;
return d->lowerBound(d->ranges, endTime);
}
QVariantMap AbstractTimelineModel::location(int index) const
{
Q_UNUSED(index);
......@@ -183,6 +370,51 @@ int AbstractTimelineModel::rowMaxValue(int rowNumber) const
return 0;
}
/*!
\fn int AbstractTimelineModel::insert(qint64 startTime, qint64 duration)
Inserts a range at the given time position and returns its index.
*/
int AbstractTimelineModel::insert(qint64 startTime, qint64 duration, int typeId)
{
Q_D(AbstractTimelineModel);
/* Doing insert-sort here is preferable as most of the time the times will actually be
* presorted in the right way. So usually this will just result in appending. */
int index = d->insertSorted(d->ranges,
AbstractTimelineModelPrivate::Range(startTime, duration, typeId));
if (index < d->ranges.size() - 1)
d->incrementStartIndices(index);
d->insertSorted(d->endTimes,
AbstractTimelineModelPrivate::RangeEnd(index, startTime + duration));
return index;
}
/*!
\fn int AbstractTimelineModel::insertStart(qint64 startTime, int typeId)
Inserts the given data as range start at the given time position and
returns its index. The range end is not set.
*/
int AbstractTimelineModel::insertStart(qint64 startTime, int typeId)
{
Q_D(AbstractTimelineModel);
int index = d->insertSorted(d->ranges,
AbstractTimelineModelPrivate::Range(startTime, 0, typeId));
if (index < d->ranges.size() - 1)
d->incrementStartIndices(index);
return index;
}
/*!
\fn int AbstractTimelineModel::insertEnd(int index, qint64 duration)
Adds a range end for the given start index.
*/
void AbstractTimelineModel::insertEnd(int index, qint64 duration)
{
Q_D(AbstractTimelineModel);
d->ranges[index].duration = duration;
d->insertSorted(d->endTimes, AbstractTimelineModelPrivate::RangeEnd(index,
d->ranges[index].start + duration));
}
void AbstractTimelineModel::dataChanged()
{
Q_D(AbstractTimelineModel);
......@@ -255,7 +487,8 @@ int AbstractTimelineModel::rowCount() const
int AbstractTimelineModel::selectionId(int index) const
{
return range(index).typeId;
Q_D(const AbstractTimelineModel);
return d->ranges[index].typeId;
}
void AbstractTimelineModel::clear()
......@@ -268,7 +501,8 @@ void AbstractTimelineModel::clear()
d->rowOffsets.clear();
d->expanded = false;
d->hidden = false;
SortedTimelineModel::clear();
d->ranges.clear();
d->endTimes.clear();
if (hadRowHeights)
emit rowHeightChanged();
if (wasExpanded)
......
......@@ -35,13 +35,12 @@
#include "qmlprofiler_global.h"
#include "qmlprofilermodelmanager.h"
#include "qmlprofilerdatamodel.h"
#include "sortedtimelinemodel.h"
#include <QVariant>
#include <QColor>
namespace QmlProfiler {
class QMLPROFILER_EXPORT AbstractTimelineModel : public SortedTimelineModel
class QMLPROFILER_EXPORT AbstractTimelineModel : public QObject
{
Q_OBJECT
Q_PROPERTY(QString displayName READ displayName CONSTANT)
......@@ -51,6 +50,9 @@ class QMLPROFILER_EXPORT AbstractTimelineModel : public SortedTimelineModel
public:
class AbstractTimelineModelPrivate;
AbstractTimelineModel(const QString &displayName, QmlDebug::Message message,
QmlDebug::RangeType rangeType, QObject *parent);
~AbstractTimelineModel();
// Trivial methods implemented by the abstract model itself
......@@ -63,6 +65,15 @@ public:
int rowOffset(int rowNumber) const;
void setRowHeight(int rowNumber, int height);
int height() const;
int count() const;
qint64 duration(int index) const;
qint64 startTime(int index) const;
qint64 endTime(int index) const;
int typeId(int index) const;
int firstIndex(qint64 startTime) const;
int firstIndexNoParents(qint64 startTime) const;
int lastIndex(qint64 endTime) const;
bool accepted(const QmlProfilerDataModel::QmlEventTypeData &event) const;
bool expanded() const;
......@@ -123,6 +134,11 @@ protected:
return QColor::fromHsl(hue % 360, Saturation, Lightness);
}
int insert(qint64 startTime, qint64 duration, int typeId);
int insertStart(qint64 startTime, int typeId);
void insertEnd(int index, qint64 duration);
void computeNesting();
explicit AbstractTimelineModel(AbstractTimelineModelPrivate *dd, const QString &displayName,
QmlDebug::Message message, QmlDebug::RangeType rangeType,
QObject *parent);
......
......@@ -37,6 +37,74 @@ namespace QmlProfiler {
class QMLPROFILER_EXPORT AbstractTimelineModel::AbstractTimelineModelPrivate {
public:
struct Range {
Range() : start(-1), duration(-1), typeId(-1), parent(-1) {}
Range(qint64 start, qint64 duration, int typeId) :
start(start), duration(duration), typeId(typeId), parent(-1) {}
qint64 start;
qint64 duration;
int typeId;
int parent;
inline qint64 timestamp() const {return start;}
};
struct RangeEnd {
RangeEnd() : startIndex(-1), end(-1) {}
RangeEnd(int startIndex, qint64 end) :
startIndex(startIndex), end(end) {}
int startIndex;
qint64 end;
inline qint64 timestamp() const {return end;}
};
void init(AbstractTimelineModel *q, const QString &displayName, QmlDebug::Message message,
QmlDebug::RangeType rangeType);
inline qint64 lastEndTime() const { return endTimes.last().end; }
inline qint64 firstStartTime() const { return ranges.first().start; }
void incrementStartIndices(int index)
{
for (int i = 0; i < endTimes.size(); ++i) {
if (endTimes[i].startIndex >= index)
endTimes[i].startIndex++;
}
}
template<typename RangeDelimiter>
static inline int insertSorted(QVector<RangeDelimiter> &container, const RangeDelimiter &item)
{
for (int i = container.count();;) {
if (i == 0) {
container.prepend(item);
return 0;
}
if (container[--i].timestamp() <= item.timestamp()) {
container.insert(++i, item);
return i;
}
}
}
template<typename RangeDelimiter>
static inline int lowerBound(const QVector<RangeDelimiter> &container, qint64 time)
{
int fromIndex = 0;
int toIndex = container.count() - 1;
while (toIndex - fromIndex > 1) {
int midIndex = (fromIndex + toIndex)/2;
if (container[midIndex].timestamp() < time)
fromIndex = midIndex;
else
toIndex = midIndex;
}
return fromIndex;
}
QVector<Range> ranges;
QVector<RangeEnd> endTimes;
QVector<int> rowOffsets;
QmlProfilerModelManager *modelManager;
int modelId;
......
......@@ -30,7 +30,6 @@ SOURCES += \
qmlprofilerviewmanager.cpp \
qv8profilerdatamodel.cpp \
qv8profilereventview.cpp \
sortedtimelinemodel.cpp \
timelinemodelaggregator.cpp \
timelinerenderer.cpp \
timelinezoomcontrol.cpp
......@@ -66,7 +65,6 @@ HEADERS += \
qmlprofilerviewmanager.h \
qv8profilerdatamodel.h \
qv8profilereventview.h \
sortedtimelinemodel.h \
timelinemodelaggregator.h \
timelinerenderer.h \
timelinezoomcontrol.h
......
......@@ -48,7 +48,6 @@ QtcPlugin {
"qmlprofilerviewmanager.cpp", "qmlprofilerviewmanager.h",
"qv8profilerdatamodel.cpp", "qv8profilerdatamodel.h",
"qv8profilereventview.h", "qv8profilereventview.cpp",
"sortedtimelinemodel.h", "sortedtimelinemodel.cpp",
"timelinemodelaggregator.cpp", "timelinemodelaggregator.h",
"timelinerenderer.cpp", "timelinerenderer.h",
"timelinezoomcontrol.cpp", "timelinezoomcontrol.h"
......
......@@ -237,7 +237,7 @@ QVariantMap QmlProfilerAnimationsModel::details(int index) const
QVariantMap result;
result.insert(QStringLiteral("displayName"), displayName());
result.insert(tr("Duration"), QmlProfilerBaseModel::formatTime(range(index).duration));
result.insert(tr("Duration"), QmlProfilerBaseModel::formatTime(duration(index)));
result.insert(tr("Framerate"), QString::fromLatin1("%1 FPS").arg(d->data[index].framerate));
result.insert(tr("Animations"), QString::fromLatin1("%1").arg(d->data[index].animationcount));
result.insert(tr("Context"), tr(d->data[index].threadId == QmlDebug::GuiThread ?
......
......@@ -112,7 +112,7 @@ int QmlProfilerNotesModel::get(int timelineModel, int timelineIndex) const
int QmlProfilerNotesModel::add(int timelineModel, int timelineIndex, const QString &text)
{
const AbstractTimelineModel *model = m_timelineModels[timelineModel];
int typeId = model->range(timelineIndex).typeId;
int typeId = model->typeId(timelineIndex);
Note note = { text, timelineModel, timelineIndex };
m_data << note;
m_modified = true;
......@@ -167,9 +167,8 @@ int QmlProfilerNotesModel::add(int typeId, qint64 start, qint64 duration, const
for (int i = model->firstIndex(start); i <= model->lastIndex(start + duration); ++i) {
if (i < 0)
continue;
const SortedTimelineModel::Range &timelineRange = model->range(i);
if (timelineRange.typeId == typeId && timelineRange.start == start &&
timelineRange.duration == duration) {
if (model->typeId(i) == typeId && model->startTime(i) == start &&
model->duration(i) == duration) {
timelineModel = model->modelId();
timelineIndex = i;
break;
......@@ -218,9 +217,10 @@ void QmlProfilerNotesModel::saveData()
if (it == m_timelineModels.end())
continue;
const SortedTimelineModel::Range &noteRange = it.value()->range(note.timelineIndex);
const AbstractTimelineModel *model = it.value();
QmlProfilerDataModel::QmlEventNoteData save = {
noteRange.typeId, noteRange.start, noteRange.duration, note.text
model->typeId(note.timelineIndex), model->startTime(note.timelineIndex),
model->duration(note.timelineIndex), note.text
};
notes.append(save);
}
......
......@@ -138,7 +138,7 @@ void QmlProfilerRangeModel::QmlProfilerRangeModelPrivate::computeNestingContract
nestingEndTimes.fill(0, nestingLevels + 1);
for (i = 0; i < eventCount; i++) {
qint64 st = q->ranges[i].start;
qint64 st = ranges[i].start;
// per type
if (nestingEndTimes[nestingLevels] > st) {
......@@ -151,7 +151,7 @@ void QmlProfilerRangeModel::QmlProfilerRangeModelPrivate::computeNestingContract
nestingEndTimes[nestingLevels-1] <= st)
nestingLevels--;
}
nestingEndTimes[nestingLevels] = st + q->ranges[i].duration;
nestingEndTimes[nestingLevels] = st + ranges[i].duration;
data[i].displayRowCollapsed = nestingLevels;
}
......@@ -163,7 +163,7 @@ void QmlProfilerRangeModel::QmlProfilerRangeModelPrivate::computeExpandedLevels(
QHash<int, int> eventRow;
int eventCount = q->count();
for (int i = 0; i < eventCount; i++) {
int typeId = q->range(i).typeId;
int typeId = ranges[i].typeId;
if (!eventRow.contains(typeId)) {
eventRow[typeId] = expandedRowTypes.size();
expandedRowTypes << typeId;
......@@ -184,25 +184,23 @@ void QmlProfilerRangeModel::QmlProfilerRangeModelPrivate::findBindingLoops()
for (int i = 0; i < q->count(); ++i) {
const Range *potentialParent = callStack.isEmpty()
? 0 : &q->ranges[callStack.top().second];
? 0 : &ranges[callStack.top().second];
while (potentialParent
&& !(potentialParent->start + potentialParent->duration > q->ranges[i].start)) {
&& !(potentialParent->start + potentialParent->duration > ranges[i].start)) {
callStack.pop();
potentialParent = callStack.isEmpty() ? 0
: &q->ranges[callStack.top().second];
potentialParent = callStack.isEmpty() ? 0 : &ranges[callStack.top().second];
}
// check whether event is already in stack
for (int ii = 0; ii < callStack.size(); ++ii) {
if (callStack.at(ii).first == q->range(i).typeId) {
if (callStack.at(ii).first == ranges[i].typeId) {
data[i].bindingLoopHead = callStack.at(ii).second;
break;
}
}
CallStackEntry newEntry(q->range(i).typeId, i);
CallStackEntry newEntry(ranges[i].typeId, i);
callStack.push(newEntry);
}
......@@ -266,7 +264,7 @@ QVariantMap QmlProfilerRangeModel::details(int index) const
d->modelManager->qmlModel()->getEventTypes();
result.insert(QStringLiteral("displayName"), categoryLabel(d->rangeType));
result.insert(tr("Duration"), QmlProfilerBaseModel::formatTime(range(index).duration));
result.insert(tr("Duration"), QmlProfilerBaseModel::formatTime(duration(index)));
result.insert(tr("Details"), types[id].data);
result.insert(tr("Location"), types[id].displayName);
......
/****************************************************************************
**
** Copyright (C) 2014 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://www.qt.io/licensing. For further information
** use the contact form at http://www.qt.io/contact-us.
**