Commit fea6580c authored by Ulf Hermann's avatar Ulf Hermann
Browse files

QmlProfiler: get rid of hash strings



Using strings to identify equal events is terribly inefficient. By
keeping a global list of event types we can assign each event a
numerical type index and use that to compare them.

We can also avoid excessive string copying and data duplication by
referring to the global type list where event type information is
needed.

Task-number: QTCREATORBUG-11823
Change-Id: I837bd5d0f5395b0003002ef8dd278fb27679c65d
Reviewed-by: default avatarKai Koehne <kai.koehne@digia.com>
parent b0d79542
......@@ -153,9 +153,9 @@ const QVariantMap AbstractTimelineModel::getEventLocation(int index) const
return map;
}
int AbstractTimelineModel::getEventIdForHash(const QString &eventHash) const
int AbstractTimelineModel::getEventIdForTypeIndex(int typeIndex) const
{
Q_UNUSED(eventHash);
Q_UNUSED(typeIndex);
return -1;
}
......@@ -199,7 +199,7 @@ void AbstractTimelineModel::dataChanged()
emit expandedChanged();
}
bool AbstractTimelineModel::eventAccepted(const QmlProfilerDataModel::QmlEventData &event) const
bool AbstractTimelineModel::eventAccepted(const QmlProfilerDataModel::QmlEventTypeData &event) const
{
Q_D(const AbstractTimelineModel);
return (event.rangeType == d->rangeType && event.message == d->message);
......
......@@ -66,7 +66,7 @@ public:
int findFirstIndexNoParents(qint64 startTime) const;
int findLastIndex(qint64 endTime) const;
int count() const;
bool eventAccepted(const QmlProfilerDataModel::QmlEventData &event) const;
bool eventAccepted(const QmlProfilerDataModel::QmlEventTypeData &event) const;
bool expanded() const;
void setExpanded(bool expanded);
const QString title() const;
......@@ -84,7 +84,7 @@ public:
// Methods which can optionally be implemented by child models.
// returned map should contain "file", "line", "column" properties, or be empty
Q_INVOKABLE virtual const QVariantMap getEventLocation(int index) const;
Q_INVOKABLE virtual int getEventIdForHash(const QString &eventHash) const;
Q_INVOKABLE virtual int getEventIdForTypeIndex(int typeIndex) const;
Q_INVOKABLE virtual int getEventIdForLocation(const QString &filename, int line, int column) const;
Q_INVOKABLE virtual int getBindingLoopDest(int index) const;
Q_INVOKABLE virtual float getHeight(int index) const;
......
......@@ -43,16 +43,16 @@ class QmlProfilerDataModel::QmlProfilerDataModelPrivate :
{
public:
QmlProfilerDataModelPrivate(QmlProfilerDataModel *qq) : QmlProfilerBaseModelPrivate(qq) {}
QVector<QmlEventTypeData> eventTypes;
QVector<QmlEventData> eventList;
QHash<QmlEventTypeData, int> eventTypeIds;
private:
Q_DECLARE_PUBLIC(QmlProfilerDataModel)
};
QmlDebug::QmlEventLocation getLocation(const QmlProfilerDataModel::QmlEventData &event);
QString getDisplayName(const QmlProfilerDataModel::QmlEventData &event);
QString getInitialDetails(const QmlProfilerDataModel::QmlEventData &event);
QString getInitialDetails(const QmlProfilerDataModel::QmlEventTypeData &event);
QmlDebug::QmlEventLocation getLocation(const QmlProfilerDataModel::QmlEventData &event)
QmlDebug::QmlEventLocation getLocation(const QmlProfilerDataModel::QmlEventTypeData &event)
{
QmlDebug::QmlEventLocation eventLocation = event.location;
if ((event.rangeType == QmlDebug::Creating || event.rangeType == QmlDebug::Compiling)
......@@ -64,7 +64,7 @@ QmlDebug::QmlEventLocation getLocation(const QmlProfilerDataModel::QmlEventData
return eventLocation;
}
QString getDisplayName(const QmlProfilerDataModel::QmlEventData &event)
QString getDisplayName(const QmlProfilerDataModel::QmlEventTypeData &event)
{
const QmlDebug::QmlEventLocation eventLocation = getLocation(event);
QString displayName;
......@@ -81,7 +81,7 @@ QString getDisplayName(const QmlProfilerDataModel::QmlEventData &event)
return displayName;
}
QString getInitialDetails(const QmlProfilerDataModel::QmlEventData &event)
QString getInitialDetails(const QmlProfilerDataModel::QmlEventTypeData &event)
{
QString details;
// generate details string
......@@ -124,6 +124,12 @@ const QVector<QmlProfilerDataModel::QmlEventData> &QmlProfilerDataModel::getEven
return d->eventList;
}
const QVector<QmlProfilerDataModel::QmlEventTypeData> &QmlProfilerDataModel::getEventTypes() const
{
Q_D(const QmlProfilerDataModel);
return d->eventTypes;
}
int QmlProfilerDataModel::count() const
{
Q_D(const QmlProfilerDataModel);
......@@ -134,6 +140,8 @@ void QmlProfilerDataModel::clear()
{
Q_D(QmlProfilerDataModel);
d->eventList.clear();
d->eventTypes.clear();
d->eventTypeIds.clear();
// This call emits changed(). Don't emit it again here.
QmlProfilerBaseModel::clear();
}
......@@ -150,6 +158,26 @@ inline static bool operator<(const QmlProfilerDataModel::QmlEventData &t1,
return t1.startTime < t2.startTime;
}
inline static uint qHash(const QmlProfilerDataModel::QmlEventTypeData &type)
{
return qHash(type.location.filename) ^
((type.location.line & 0xfff) | // 12 bits of line number
((type.message << 12) & 0xf000) | // 4 bits of message
((type.location.column << 16) & 0xff0000) | // 8 bits of column
((type.rangeType << 24) & 0xf000000) | // 4 bits of rangeType
((type.detailType << 28) & 0xf0000000)); // 4 bits of detailType
}
inline static bool operator==(const QmlProfilerDataModel::QmlEventTypeData &type1,
const QmlProfilerDataModel::QmlEventTypeData &type2)
{
return type1.message == type2.message && type1.rangeType == type2.rangeType &&
type1.detailType == type2.detailType && type1.location.line == type2.location.line &&
type1.location.column == type2.location.column &&
// compare filename last as it's expensive.
type1.location.filename == type2.location.filename;
}
void QmlProfilerDataModel::complete()
{
Q_D(QmlProfilerDataModel);
......@@ -159,9 +187,9 @@ void QmlProfilerDataModel::complete()
std::sort(d->eventList.begin(), d->eventList.end());
// rewrite strings
int n = d->eventList.count();
int n = d->eventTypes.count();
for (int i = 0; i < n; i++) {
QmlEventData *event = &d->eventList[i];
QmlEventTypeData *event = &d->eventTypes[i];
event->location = getLocation(*event);
event->displayName = getDisplayName(*event);
event->data = getInitialDetails(*event);
......@@ -207,24 +235,24 @@ void QmlProfilerDataModel::addQmlEvent(QmlDebug::Message message, QmlDebug::Rang
QString::number(location.line));
}
QmlEventData eventData = {displayName, message, rangeType, detailType, startTime, duration,
data, location, ndata1, ndata2, ndata3, ndata4, ndata5};
QmlEventTypeData typeData = {displayName, location, message, rangeType, detailType, data};
QmlEventData eventData = {-1, startTime, duration, ndata1, ndata2, ndata3, ndata4, ndata5};
QHash<QmlEventTypeData, int>::Iterator it = d->eventTypeIds.find(typeData);
if (it != d->eventTypeIds.end()) {
eventData.typeIndex = it.value();
} else {
eventData.typeIndex = d->eventTypes.size();
d->eventTypeIds[typeData] = eventData.typeIndex;
d->eventTypes.append(typeData);
}
d->eventList.append(eventData);
d->modelManager->modelProxyCountUpdated(d->modelId, startTime,
d->modelManager->estimatedProfilingTime() * 2);
}
QString QmlProfilerDataModel::getHashString(const QmlProfilerDataModel::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.message << 8) | event.rangeType),
QString::number(event.detailType));
}
qint64 QmlProfilerDataModel::lastTimeMark() const
{
Q_D(const QmlProfilerDataModel);
......@@ -237,9 +265,9 @@ qint64 QmlProfilerDataModel::lastTimeMark() const
void QmlProfilerDataModel::detailsChanged(int requestId, const QString &newString)
{
Q_D(QmlProfilerDataModel);
QTC_ASSERT(requestId < d->eventList.count(), return);
QTC_ASSERT(requestId < d->eventTypes.count(), return);
QmlEventData *event = &d->eventList[requestId];
QmlEventTypeData *event = &d->eventTypes[requestId];
event->data = newString;
}
......
......@@ -39,15 +39,19 @@ class QMLPROFILER_EXPORT QmlProfilerDataModel : public QmlProfilerBaseModel
{
Q_OBJECT
public:
struct QmlEventData {
struct QmlEventTypeData {
QString displayName;
QmlDebug::QmlEventLocation location;
QmlDebug::Message message;
QmlDebug::RangeType rangeType;
int detailType; // can be EventType, BindingType, PixmapEventType or SceneGraphFrameType
QString data;
};
struct QmlEventData {
int typeIndex;
qint64 startTime;
qint64 duration;
QString data;
QmlDebug::QmlEventLocation location;
qint64 numericData1;
qint64 numericData2;
qint64 numericData3;
......@@ -58,6 +62,7 @@ public:
explicit QmlProfilerDataModel(Utils::FileInProjectFinder *fileFinder, QmlProfilerModelManager *parent = 0);
const QVector<QmlEventData> &getEvents() const;
const QVector<QmlEventTypeData> &getEventTypes() const;
int count() const;
virtual void clear();
virtual bool isEmpty() const;
......@@ -66,7 +71,6 @@ public:
qint64 startTime, qint64 duration, const QString &data,
const QmlDebug::QmlEventLocation &location, qint64 ndata1, qint64 ndata2,
qint64 ndata3, qint64 ndata4, qint64 ndata5);
static QString getHashString(const QmlProfilerDataModel::QmlEventData &event);
qint64 lastTimeMark() const;
protected slots:
......
......@@ -51,7 +51,7 @@ public:
QmlProfilerEventsModelProxyPrivate(QmlProfilerEventsModelProxy *qq) : q(qq) {}
~QmlProfilerEventsModelProxyPrivate() {}
QHash<QString, QmlProfilerEventsModelProxy::QmlEventStats> data;
QHash<int, QmlProfilerEventsModelProxy::QmlEventStats> data;
QmlProfilerModelManager *modelManager;
QmlProfilerEventsModelProxy *q;
......@@ -59,7 +59,7 @@ public:
int modelId;
QList<QmlDebug::RangeType> acceptedTypes;
QSet<QString> eventsInBindingLoop;
QSet<int> eventsInBindingLoop;
};
QmlProfilerEventsModelProxy::QmlProfilerEventsModelProxy(QmlProfilerModelManager *modelManager, QObject *parent)
......@@ -93,9 +93,14 @@ bool QmlProfilerEventsModelProxy::eventTypeAccepted(QmlDebug::RangeType type) co
return d->acceptedTypes.contains(type);
}
const QList<QmlProfilerEventsModelProxy::QmlEventStats> QmlProfilerEventsModelProxy::getData() const
const QHash<int, QmlProfilerEventsModelProxy::QmlEventStats> &QmlProfilerEventsModelProxy::getData() const
{
return d->data.values();
return d->data;
}
const QVector<QmlProfilerDataModel::QmlEventTypeData> &QmlProfilerEventsModelProxy::getTypes() const
{
return d->modelManager->qmlModel()->getEventTypes();
}
void QmlProfilerEventsModelProxy::clear()
......@@ -118,7 +123,7 @@ void QmlProfilerEventsModelProxy::dataChanged()
clear();
}
QSet<QString> QmlProfilerEventsModelProxy::eventsInBindingLoop() const
const QSet<int> &QmlProfilerEventsModelProxy::eventsInBindingLoop() const
{
return d->eventsInBindingLoop;
}
......@@ -129,22 +134,24 @@ void QmlProfilerEventsModelProxy::loadData(qint64 rangeStart, qint64 rangeEnd)
qint64 qmlTime = 0;
qint64 lastEndTime = 0;
QHash <QString, QVector<qint64> > durations;
QHash <int, QVector<qint64> > durations;
const bool checkRanges = (rangeStart != -1) && (rangeEnd != -1);
const QVector<QmlProfilerDataModel::QmlEventData> eventList
const QVector<QmlProfilerDataModel::QmlEventData> &eventList
= d->modelManager->qmlModel()->getEvents();
const QVector<QmlProfilerDataModel::QmlEventTypeData> &typesList
= d->modelManager->qmlModel()->getEventTypes();
// used by binding loop detection
typedef QPair<QString, const QmlProfilerDataModel::QmlEventData*> CallStackEntry;
QStack<CallStackEntry> callStack;
callStack.push(CallStackEntry(QString(), 0)); // artificial root
QStack<const QmlProfilerDataModel::QmlEventData*> callStack;
callStack.push(0); // artificial root
for (int i = 0; i < eventList.size(); ++i) {
const QmlProfilerDataModel::QmlEventData *event = &eventList[i];
const QmlProfilerDataModel::QmlEventTypeData *type = &typesList[event->typeIndex];
if (!d->acceptedTypes.contains(event->rangeType))
if (!d->acceptedTypes.contains(type->rangeType))
continue;
if (checkRanges) {
......@@ -153,46 +160,18 @@ void QmlProfilerEventsModelProxy::loadData(qint64 rangeStart, qint64 rangeEnd)
continue;
}
// put event in hash
QString hash = QmlProfilerDataModel::getHashString(*event);
if (!d->data.contains(hash)) {
QmlEventStats stats = {
event->displayName,
hash,
event->data,
event->location,
event->message,
event->rangeType,
event->detailType,
event->duration,
1, //calls
event->duration, //minTime
event->duration, // maxTime
0, //timePerCall
0, //percentOfTime
0, //medianTime
false //isBindingLoop
};
// update stats
QmlEventStats *stats = &d->data[event->typeIndex];
d->data.insert(hash, stats);
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.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);
}
// for median computing
durations[event->typeIndex].append(event->duration);
// qml time computation
if (event->startTime > lastEndTime) { // assume parent event if starts before last end
......@@ -204,25 +183,22 @@ void QmlProfilerEventsModelProxy::loadData(qint64 rangeStart, qint64 rangeEnd)
//
// binding loop detection
//
const QmlProfilerDataModel::QmlEventData *potentialParent = callStack.top().second;
const QmlProfilerDataModel::QmlEventData *potentialParent = callStack.top();
while (potentialParent
&& !(potentialParent->startTime + potentialParent->duration > event->startTime)) {
callStack.pop();
potentialParent = callStack.top().second;
potentialParent = callStack.top();
}
// 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);
if (callStack.at(ii)->typeIndex == event->typeIndex) {
d->eventsInBindingLoop.insert(event->typeIndex);
break;
}
}
CallStackEntry newEntry(hash, event);
callStack.push(newEntry);
callStack.push(event);
d->modelManager->modelProxyCountUpdated(d->modelId, i, eventList.count()*2);
}
......@@ -230,12 +206,13 @@ void QmlProfilerEventsModelProxy::loadData(qint64 rangeStart, qint64 rangeEnd)
// post-process: calc mean time, median time, percentoftime
int i = d->data.size();
int total = i * 2;
foreach (const QString &hash, d->data.keys()) {
QmlEventStats* stats = &d->data[hash];
for (QHash<int, QmlEventStats>::iterator it = d->data.begin(); it != d->data.end(); ++it) {
QmlEventStats* stats = &it.value();
if (stats->calls > 0)
stats->timePerCall = stats->duration / (double)stats->calls;
QVector<qint64> eventDurations = durations.value(hash);
QVector<qint64> eventDurations = durations[it.key()];
if (!eventDurations.isEmpty()) {
qSort(eventDurations);
stats->medianTime = eventDurations.at(eventDurations.count()/2);
......@@ -246,32 +223,17 @@ void QmlProfilerEventsModelProxy::loadData(qint64 rangeStart, qint64 rangeEnd)
}
// 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);
foreach (int typeIndex, d->eventsInBindingLoop)
d->data[typeIndex].isBindingLoop = true;
// insert root event
QmlEventStats rootEvent = {
rootEventName, //event.displayName,
rootEventName, // hash
tr("Main Program"), //event.details,
rootEventLocation, // location
QmlDebug::MaximumMessage,
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);
QmlEventStats rootEvent;
rootEvent.duration = rootEvent.minTime = rootEvent.maxTime = rootEvent.timePerCall
= rootEvent.medianTime = qmlTime + 1;
rootEvent.calls = 1;
rootEvent.percentOfTime = 100.0;
d->data.insert(-1, rootEvent);
d->modelManager->modelProxyCountUpdated(d->modelId, 1, 1);
emit dataAvailable();
......@@ -303,11 +265,20 @@ QmlProfilerEventRelativesModelProxy::~QmlProfilerEventRelativesModelProxy()
{
}
const QmlProfilerEventRelativesModelProxy::QmlEventRelativesMap QmlProfilerEventRelativesModelProxy::getData(const QString &hash) const
const QmlProfilerEventRelativesModelProxy::QmlEventRelativesMap &QmlProfilerEventRelativesModelProxy::getData(int typeId) const
{
if (m_data.contains(hash))
return m_data[hash];
return QmlEventRelativesMap();
QHash <int, QmlEventRelativesMap>::ConstIterator it = m_data.find(typeId);
if (it != m_data.end()) {
return it.value();
} else {
static const QmlEventRelativesMap emptyMap;
return emptyMap;
}
}
const QVector<QmlProfilerDataModel::QmlEventTypeData> &QmlProfilerEventRelativesModelProxy::getTypes() const
{
return m_modelManager->qmlModel()->getEventTypes();
}
int QmlProfilerEventRelativesModelProxy::count() const
......@@ -345,35 +316,20 @@ void QmlProfilerEventParentsModelProxy::loadData()
if (simpleModel->isEmpty())
return;
QHash<QString, QmlProfilerDataModel::QmlEventData> cachedEvents;
QString rootEventName = tr("<program>");
QmlProfilerDataModel::QmlEventData rootEvent = {
rootEventName,
QmlDebug::MaximumMessage,
QmlDebug::Binding,
0,
0,
0,
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();
const QSet<int> &eventsInBindingLoop = m_eventsModel->eventsInBindingLoop();
// compute parent-child relationship and call count
QHash<int, QString> lastParent;
//for (int index = fromIndex; index <= toIndex; index++) {
QHash<int, int> lastParent;
const QVector<QmlProfilerDataModel::QmlEventData> eventList = simpleModel->getEvents();
const QVector<QmlProfilerDataModel::QmlEventTypeData> typesList = simpleModel->getEventTypes();
foreach (const QmlProfilerDataModel::QmlEventData &event, eventList) {
// whitelist
if (!m_eventsModel->eventTypeAccepted(event.rangeType))
if (!m_eventsModel->eventTypeAccepted(typesList[event.typeIndex].rangeType))
continue;
// level computation
......@@ -385,40 +341,26 @@ void QmlProfilerEventParentsModelProxy::loadData()
}
endtimesPerLevel[level] = event.startTime + event.duration;
QString parentHash = rootEventName;
QString eventHash = QmlProfilerDataModel::getHashString(event);
// save in cache
if (!cachedEvents.contains(eventHash))
cachedEvents.insert(eventHash, event);
int parentTypeIndex = -1;
if (level > QmlDebug::Constants::QML_MIN_LEVEL && lastParent.contains(level-1))
parentHash = lastParent[level-1];
QmlProfilerDataModel::QmlEventData *parentEvent = &(cachedEvents[parentHash]);
parentTypeIndex = lastParent[level-1];
// 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;
QmlEventRelativesMap &relativesMap = m_data[event.typeIndex];
QmlEventRelativesMap::Iterator it = relativesMap.find(parentTypeIndex);
if (it != relativesMap.end()) {
it.value().calls++;
it.value().duration += event.duration;
} else {
m_data[eventHash].insert(parentHash, QmlEventRelativesData());
QmlEventRelativesData *parent = &(m_data[eventHash][parentHash]);
parent->displayName = parentEvent->displayName;
parent->rangeType = parentEvent->rangeType;
parent->duration = event.duration;
parent->calls = 1;
parent->details = parentEvent->data;
parent->isBindingLoop = eventsInBindingLoop.contains(parentHash);
QmlEventRelativesData parent {
event.duration,
1,
eventsInBindingLoop.contains(parentTypeIndex)
};
relativesMap.insert(parentTypeIndex, parent);
}
// now lastparent is a string with the hash
lastParent[level] = eventHash;
// now lastparent is the new type
lastParent[level] = event.typeIndex;
}
}
......@@ -439,21 +381,20 @@ void QmlProfilerEventChildrenModelProxy::loadData()
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();
const QSet<int> &eventsInBindingLoop = m_eventsModel-&