/**************************************************************************** ** ** 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 #include #include #include #include #include #include #include namespace QmlProfiler { namespace Internal { class QmlProfilerEventsModelProxy::QmlProfilerEventsModelProxyPrivate { public: QmlProfilerEventsModelProxyPrivate(QmlProfilerEventsModelProxy *qq) : q(qq) {} ~QmlProfilerEventsModelProxyPrivate() {} QHash data; QmlProfilerModelManager *modelManager; QmlProfilerEventsModelProxy *q; int modelId; QVector acceptedTypes; QSet 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::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 QmlProfilerEventsModelProxy::eventsInBindingLoop() const { return d->eventsInBindingLoop; } void QmlProfilerEventsModelProxy::loadData(qint64 rangeStart, qint64 rangeEnd) { clear(); qint64 qmlTime = 0; qint64 lastEndTime = 0; QHash > durations; const bool checkRanges = (rangeStart != -1) && (rangeEnd != -1); const QVector eventList = d->modelManager->simpleModel()->getEvents(); // used by binding loop detection typedef QPair CallStackEntry; QStack 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()); 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 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(""); 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 cachedEvents; QString rootEventName = tr(""); 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 endtimesPerLevel; int level = QmlDebug::Constants::QML_MIN_LEVEL; endtimesPerLevel[0] = 0; const QSet eventsInBindingLoop = m_eventsModel->eventsInBindingLoop(); // compute parent-child relationship and call count QHash lastParent; //for (int index = fromIndex; index <= toIndex; index++) { const QVector 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(""); // for level computation QHash endtimesPerLevel; int level = QmlDebug::Constants::QML_MIN_LEVEL; endtimesPerLevel[0] = 0; const QSet eventsInBindingLoop = m_eventsModel->eventsInBindingLoop(); // compute parent-child relationship and call count QHash lastParent; const QVector 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; } } } }