diff options
-rw-r--r-- | src/qml/debugger/debugger.pri | 7 | ||||
-rw-r--r-- | src/qml/debugger/qqmlabstractprofileradapter.cpp | 109 | ||||
-rw-r--r-- | src/qml/debugger/qqmlabstractprofileradapter_p.h | 108 | ||||
-rw-r--r-- | src/qml/debugger/qqmlprofilerdefinitions_p.h | 133 | ||||
-rw-r--r-- | src/qml/debugger/qqmlprofilerservice.cpp | 287 | ||||
-rw-r--r-- | src/qml/debugger/qqmlprofilerservice_p.h | 111 | ||||
-rw-r--r-- | src/qml/qml/v8/qqmlbuiltinfunctions.cpp | 28 | ||||
-rw-r--r-- | tests/auto/qml/debugger/qqmlprofilerservice/tst_qqmlprofilerservice.cpp | 10 | ||||
-rw-r--r-- | tests/auto/qml/qqmlconsole/tst_qqmlconsole.cpp | 2 |
9 files changed, 656 insertions, 139 deletions
diff --git a/src/qml/debugger/debugger.pri b/src/qml/debugger/debugger.pri index e9f1d6f5fe..2f9844ae7a 100644 --- a/src/qml/debugger/debugger.pri +++ b/src/qml/debugger/debugger.pri @@ -8,7 +8,8 @@ SOURCES += \ $$PWD/qdebugmessageservice.cpp \ $$PWD/qv4debugservice.cpp \ $$PWD/qqmlconfigurabledebugservice.cpp \ - $$PWD/qqmlenginecontrolservice.cpp + $$PWD/qqmlenginecontrolservice.cpp \ + $$PWD/qqmlabstractprofileradapter.cpp HEADERS += \ $$PWD/qqmldebugservice_p.h \ @@ -26,4 +27,6 @@ HEADERS += \ $$PWD/qv4debugservice_p.h \ $$PWD/qqmlconfigurabledebugservice_p.h \ $$PWD/qqmlconfigurabledebugservice_p_p.h \ - $$PWD/qqmlenginecontrolservice_p.h + $$PWD/qqmlenginecontrolservice_p.h \ + $$PWD/qqmlprofilerdefinitions_p.h \ + $$PWD/qqmlabstractprofileradapter_p.h diff --git a/src/qml/debugger/qqmlabstractprofileradapter.cpp b/src/qml/debugger/qqmlabstractprofileradapter.cpp new file mode 100644 index 0000000000..b5962657b1 --- /dev/null +++ b/src/qml/debugger/qqmlabstractprofileradapter.cpp @@ -0,0 +1,109 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the QtQml module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** 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. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qqmlabstractprofileradapter_p.h" + +QT_BEGIN_NAMESPACE + +/*! + * \class QQmlAbstractProfilerAdapter + * Abstract base class for all adapters between profilers and the QQmlProfilerService. Adapters have + * to retrieve profiler-specific data and convert it to the format sent over the wire. Adapters must + * live in the QDebugServer thread but the actual profilers can live in different threads. The + * recommended way to deal with this is passing the profiling data through a signal/slot connection. + */ + +/*! + * \fn void QQmlAbstractProfilerAdapter::dataReady(QQmlAbstractProfilerAdapter *) + * Signals that data has been extracted from the profiler and is readily available in the adapter. + * The primary data representation is in satellite's format. It should be transformed and deleted + * on the fly with sendMessages. + */ + +/*! + * \fn void QQmlAbstractProfilerAdapter::dataRequested() + * Signals that data has been requested by the \c QQmlProfilerService. This signal should be + * connected to a slot in the profiler and the profiler should then transfer its currently available + * profiling data to the adapter as soon as possible. + */ + +/*! + * \fn qint64 QQmlAbstractProfilerAdapter::sendMessages(qint64 until, QList<QByteArray> &messages) + * Append the messages up to the timestamp \a until, chronologically sorted, to \a messages. Keep + * track of the messages already sent and with each subsequent call to this method start with the + * first one not yet sent. Messages that have been sent can be deleted. When new data from the + * profiler arrives the information about the last sent message must be reset. Return the timestamp + * of the next message after \a until or \c -1 if there is no such message. + * The profiler service keeps a list of adapters, sorted by time of next message and keeps querying + * the first one to send messages up to the time of the second one. Like that we get chronologically + * sorted messages and can occasionally post the messages to exploit parallelism and save memory. + */ + +/*! + * \fn qint64 QQmlAbstractProfilerAdapter::startProfiling() + * Emits either \c profilingEnabled() or \c profilingEnabledWhileWaiting(), depending on \c waiting. + * If the profiler's thread is waiting for an initial start signal we can emit the signal over a + * \c Qt::DirectConnection to avoid the delay of the event loop. + */ +void QQmlAbstractProfilerAdapter::startProfiling() +{ + if (waiting) + emit profilingEnabledWhileWaiting(); + else + emit profilingEnabled(); + running = true; +} + +/*! + * \fn qint64 QQmlAbstractProfilerAdapter::stopProfiling() + * Emits either \c profilingDisabled() or \c profilingDisabledWhileWaiting(), depending on + * \c waiting. If the profiler's thread is waiting for an initial start signal we can emit the + * signal over a \c Qt::DirectConnection to avoid the delay of the event loop. + */ +void QQmlAbstractProfilerAdapter::stopProfiling() { + if (waiting) + emit profilingDisabledWhileWaiting(); + else + emit profilingDisabled(); + running = false; +} + +QT_END_NAMESPACE diff --git a/src/qml/debugger/qqmlabstractprofileradapter_p.h b/src/qml/debugger/qqmlabstractprofileradapter_p.h new file mode 100644 index 0000000000..89b241e8d6 --- /dev/null +++ b/src/qml/debugger/qqmlabstractprofileradapter_p.h @@ -0,0 +1,108 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the QtQml module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** 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. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QQMLABSTRACTPROFILERADAPTER_P_H +#define QQMLABSTRACTPROFILERADAPTER_P_H + +#include <private/qtqmlglobal_p.h> +#include <private/qqmlprofilerdefinitions_p.h> + +#include <QtCore/QObject> +#include <QtCore/QElapsedTimer> + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +QT_BEGIN_NAMESPACE + +class QQmlProfilerService; +class Q_QML_PRIVATE_EXPORT QQmlAbstractProfilerAdapter : public QObject, public QQmlProfilerDefinitions { + Q_OBJECT + +public: + QQmlAbstractProfilerAdapter(QQmlProfilerService *service) : + service(service), waiting(true), running(false) {} + virtual ~QQmlAbstractProfilerAdapter() {} + + virtual qint64 sendMessages(qint64 until, QList<QByteArray> &messages) = 0; + + void startProfiling(); + + void stopProfiling(); + + void reportData() { emit dataRequested(); } + + void stopWaiting() { waiting = false; } + void startWaiting() { waiting = true; } + + bool isRunning() const { return running; } + + void synchronize(const QElapsedTimer &t) { emit referenceTimeKnown(t); } + +signals: + void profilingEnabled(); + void profilingEnabledWhileWaiting(); + + void profilingDisabled(); + void profilingDisabledWhileWaiting(); + + void dataRequested(); + void referenceTimeKnown(const QElapsedTimer &timer); + +protected: + QQmlProfilerService *service; + +private: + bool waiting; + bool running; +}; + +QT_END_NAMESPACE + +#endif // QQMLABSTRACTPROFILERADAPTER_P_H diff --git a/src/qml/debugger/qqmlprofilerdefinitions_p.h b/src/qml/debugger/qqmlprofilerdefinitions_p.h new file mode 100644 index 0000000000..9452260ce4 --- /dev/null +++ b/src/qml/debugger/qqmlprofilerdefinitions_p.h @@ -0,0 +1,133 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the QtQml module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** 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. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QQMLPROFILERDEFINITIONS_P_H +#define QQMLPROFILERDEFINITIONS_P_H + +#include <private/qtqmlglobal_p.h> + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +QT_BEGIN_NAMESPACE + +struct QQmlProfilerDefinitions { + enum Message { + Event, + RangeStart, + RangeData, + RangeLocation, + RangeEnd, + Complete, // end of transmission + PixmapCacheEvent, + SceneGraphFrame, + + MaximumMessage + }; + + enum EventType { + FramePaint, + Mouse, + Key, + AnimationFrame, + EndTrace, + StartTrace, + + MaximumEventType + }; + + enum RangeType { + Painting, + Compiling, + Creating, + Binding, //running a binding + HandlingSignal, //running a signal handler + Javascript, + + MaximumRangeType + }; + + enum BindingType { + QmlBinding, + V8Binding, + V4Binding, + + MaximumBindingType + }; + + enum PixmapEventType { + PixmapSizeKnown, + PixmapReferenceCountChanged, + PixmapCacheCountChanged, + PixmapLoadingStarted, + PixmapLoadingFinished, + PixmapLoadingError, + + MaximumPixmapEventType + }; + + enum SceneGraphFrameType { + SceneGraphRendererFrame, + SceneGraphAdaptationLayerFrame, + SceneGraphContextFrame, + SceneGraphRenderLoopFrame, + SceneGraphTexturePrepare, + SceneGraphTextureDeletion, + SceneGraphPolishAndSync, + SceneGraphWindowsRenderShow, + SceneGraphWindowsAnimations, + SceneGraphWindowsPolishFrame, + + MaximumSceneGraphFrameType + }; +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/qml/debugger/qqmlprofilerservice.cpp b/src/qml/debugger/qqmlprofilerservice.cpp index a9ef5ff6b1..cb84d8201f 100644 --- a/src/qml/debugger/qqmlprofilerservice.cpp +++ b/src/qml/debugger/qqmlprofilerservice.cpp @@ -40,6 +40,8 @@ ****************************************************************************/ #include "qqmlprofilerservice_p.h" +#include "qqmldebugserver_p.h" +#include <private/qqmlengine_p.h> #include <QtCore/qdatastream.h> #include <QtCore/qurl.h> @@ -146,12 +148,52 @@ QQmlProfilerService::QQmlProfilerService() // case we might miss the callback registration. if (state() == Enabled) QUnifiedTimer::instance()->registerProfilerCallback(&animationTimerCallback); + + // If there is no debug server it doesn't matter as we'll never get enabled anyway. + if (QQmlDebugServer::instance() != 0) + moveToThread(QQmlDebugServer::instance()->thread()); } QQmlProfilerService::~QQmlProfilerService() { + // No need to lock here. If any engine or global profiler is still trying to register at this + // point we have a nasty bug anyway. enabled = false; m_instance = 0; + qDeleteAll(m_engineProfilers.keys()); + qDeleteAll(m_globalProfilers); +} + +void QQmlProfilerService::dataReady(QQmlAbstractProfilerAdapter *profiler) +{ + QMutexLocker lock(configMutex()); + bool dataComplete = true; + for (QMultiMap<qint64, QQmlAbstractProfilerAdapter *>::iterator i(m_startTimes.begin()); i != m_startTimes.end();) { + if (i.value() == profiler) { + m_startTimes.erase(i++); + } else { + if (i.key() == -1) + dataComplete = false; + ++i; + } + } + m_startTimes.insert(0, profiler); + if (dataComplete) { + QList<QQmlEngine *> enginesToRelease; + foreach (QQmlEngine *engine, m_stoppingEngines) { + foreach (QQmlAbstractProfilerAdapter *engineProfiler, m_engineProfilers.values(engine)) { + if (m_startTimes.values().contains(engineProfiler)) { + enginesToRelease.append(engine); + break; + } + } + } + sendMessages(); + foreach (QQmlEngine *engine, enginesToRelease) { + m_stoppingEngines.removeOne(engine); + emit detachedFromEngine(engine); + } + } } QQmlProfilerService *QQmlProfilerService::instance() @@ -161,61 +203,214 @@ QQmlProfilerService *QQmlProfilerService::instance() return m_instance; } -bool QQmlProfilerService::startProfiling() +void QQmlProfilerService::engineAboutToBeAdded(QQmlEngine *engine) +{ + Q_ASSERT_X(QThread::currentThread() != thread(), Q_FUNC_INFO, "QML profilers have to be added from the engine thread"); + + QMutexLocker lock(configMutex()); + QQmlProfiler *qmlAdapter = new QQmlProfiler(this); + addEngineProfiler(qmlAdapter, engine); + QQmlConfigurableDebugService::engineAboutToBeAdded(engine); +} + +void QQmlProfilerService::engineAdded(QQmlEngine *engine) +{ + Q_ASSERT_X(QThread::currentThread() != thread(), Q_FUNC_INFO, "QML profilers have to be added from the engine thread"); + + QMutexLocker lock(configMutex()); + foreach (QQmlAbstractProfilerAdapter *profiler, m_engineProfilers.values(engine)) + profiler->stopWaiting(); +} + +void QQmlProfilerService::engineAboutToBeRemoved(QQmlEngine *engine) { - return profilerInstance()->startProfilingImpl(); + Q_ASSERT_X(QThread::currentThread() != thread(), Q_FUNC_INFO, "QML profilers have to be removed from the engine thread"); + + QMutexLocker lock(configMutex()); + bool isRunning = false; + foreach (QQmlAbstractProfilerAdapter *profiler, m_engineProfilers.values(engine)) { + if (profiler->isRunning()) + isRunning = true; + profiler->startWaiting(); + } + if (isRunning) { + m_stoppingEngines.append(engine); + stopProfiling(engine); + } else { + emit detachedFromEngine(engine); + } } -bool QQmlProfilerService::stopProfiling() +void QQmlProfilerService::engineRemoved(QQmlEngine *engine) { - return profilerInstance()->stopProfilingImpl(); + Q_ASSERT_X(QThread::currentThread() != thread(), Q_FUNC_INFO, "QML profilers have to be removed from the engine thread"); + + QMutexLocker lock(configMutex()); + foreach (QQmlAbstractProfilerAdapter *profiler, m_engineProfilers.values(engine)) + delete profiler; + m_engineProfilers.remove(engine); } -void QQmlProfilerService::sendProfilingData() +void QQmlProfilerService::addEngineProfiler(QQmlAbstractProfilerAdapter *profiler, QQmlEngine *engine) { - profilerInstance()->sendMessages(); + profiler->moveToThread(thread()); + profiler->synchronize(m_timer); + m_engineProfilers.insert(engine, profiler); } -bool QQmlProfilerService::startProfilingImpl() +void QQmlProfilerService::addGlobalProfiler(QQmlAbstractProfilerAdapter *profiler) { - if (QQmlDebugService::isDebuggingEnabled() && !enabled) { - enabled = true; - QList<QByteArray> messages; - QQmlProfilerData(m_timer.nsecsElapsed(), 1 << Event, 1 << StartTrace).toByteArrays(messages); - QQmlDebugService::sendMessages(messages); - return true; - } else { - return false; + QMutexLocker lock(configMutex()); + profiler->synchronize(m_timer); + m_globalProfilers.append(profiler); + // Global profiler, not connected to a specific engine. + // Global profilers are started whenever any engine profiler is started and stopped when + // all engine profilers are stopped. + foreach (QQmlAbstractProfilerAdapter *engineProfiler, m_engineProfilers) { + if (engineProfiler->isRunning()) { + profiler->startProfiling(); + break; + } } } -bool QQmlProfilerService::stopProfilingImpl() +void QQmlProfilerService::removeGlobalProfiler(QQmlAbstractProfilerAdapter *profiler) { - if (enabled) { - enabled = false; - // We cannot use instance here as this is called from the debugger thread. - // It may be called before the QML engine (and the profiler) is ready. - processMessage(QQmlProfilerData(m_timer.nsecsElapsed(), 1 << Event, 1 << EndTrace)); - return true; - } else { - return false; + QMutexLocker lock(configMutex()); + for (QMultiMap<qint64, QQmlAbstractProfilerAdapter *>::iterator i(m_startTimes.begin()); i != m_startTimes.end();) { + if (i.value() == profiler) + m_startTimes.erase(i++); + else + ++i; + } + m_globalProfilers.removeOne(profiler); + delete profiler; +} + +void QQmlProfilerService::startProfiling(QQmlEngine *engine) +{ + QMutexLocker lock(configMutex()); + + QByteArray message; + QQmlDebugStream d(&message, QIODevice::WriteOnly); + d << m_timer.nsecsElapsed() << (int)Event << (int)StartTrace << idForObject(engine); + foreach (QQmlAbstractProfilerAdapter *profiler, m_engineProfilers.values(engine)) { + profiler->startProfiling(); + } + if (!m_engineProfilers.values(engine).empty()) { + foreach (QQmlAbstractProfilerAdapter *profiler, m_globalProfilers) { + if (!profiler->isRunning()) + profiler->startProfiling(); + } + } + + QQmlDebugService::sendMessage(message); +} + +void QQmlProfilerService::stopProfiling(QQmlEngine *engine) +{ + QMutexLocker lock(configMutex()); + + bool stillRunning = false; + m_startTimes.clear(); + for (QMultiHash<QQmlEngine *, QQmlAbstractProfilerAdapter *>::iterator i(m_engineProfilers.begin()); + i != m_engineProfilers.end(); ++i) { + if (i.value()->isRunning()) { + if (i.key() == engine) { + m_startTimes.insert(-1, i.value()); + i.value()->stopProfiling(); + } else { + stillRunning = true; + } + } + } + foreach (QQmlAbstractProfilerAdapter *profiler, m_globalProfilers) { + if (!profiler->isRunning()) + continue; + m_startTimes.insert(-1, profiler); + if (stillRunning) { + profiler->reportData(); + } else { + profiler->stopProfiling(); + } + } +} + +QQmlProfiler::QQmlProfiler(QQmlProfilerService *service) : + QQmlAbstractProfilerAdapter(service), next(0) +{ + connect(this, SIGNAL(profilingEnabled()), this, SLOT(startProfiling())); + connect(this, SIGNAL(profilingEnabledWhileWaiting()), this, SLOT(startProfiling()), + Qt::DirectConnection); + connect(this, SIGNAL(profilingDisabled()), this, SLOT(stopProfiling())); + connect(this, SIGNAL(profilingDisabledWhileWaiting()), this, SLOT(stopProfiling()), + Qt::DirectConnection); +} + +qint64 QQmlProfiler::sendMessages(qint64 until, QList<QByteArray> &messages) +{ + QMutexLocker lock(&QQmlProfilerService::instance()->m_dataMutex); + QVector<QQmlProfilerData> *data = &(QQmlProfilerService::instance()->m_data); + while (next < data->size() && data->at(next).time <= until) { + data->at(next++).toByteArrays(messages); + } + return next < data->size() ? data->at(next).time : -1; +} + +void QQmlProfiler::startProfiling() +{ + if (!QQmlProfilerService::enabled) { + next = 0; + service->m_data.clear(); + QQmlProfilerService::enabled = true; } } +void QQmlProfiler::stopProfiling() +{ + next = 0; + QQmlProfilerService::enabled = false; + service->dataReady(this); +} + /* - Send the messages queued up by processMessage + Send the queued up messages. */ void QQmlProfilerService::sendMessages() { - QMutexLocker locker(&m_dataMutex); - QList<QByteArray> messages; - for (int i = 0; i < m_data.count(); ++i) - m_data.at(i).toByteArrays(messages); - m_data.clear(); - //indicate completion QByteArray data; + QQmlDebugStream traceEnd(&data, QIODevice::WriteOnly); + traceEnd << m_timer.nsecsElapsed() << (int)Event << (int)EndTrace; + + QSet<QQmlEngine *> seen; + foreach (QQmlAbstractProfilerAdapter *profiler, m_startTimes) { + for (QMultiHash<QQmlEngine *, QQmlAbstractProfilerAdapter *>::iterator i(m_engineProfilers.begin()); + i != m_engineProfilers.end(); ++i) { + if (i.value() == profiler && !seen.contains(i.key())) { + seen << i.key(); + traceEnd << idForObject(i.key()); + } + } + } + + while (!m_startTimes.empty()) { + QQmlAbstractProfilerAdapter *first = m_startTimes.begin().value(); + m_startTimes.erase(m_startTimes.begin()); + if (!m_startTimes.empty()) { + qint64 next = first->sendMessages(m_startTimes.begin().key(), messages); + if (next != -1) + m_startTimes.insert(next, first); + } else { + first->sendMessages(std::numeric_limits<qint64>::max(), messages); + } + } + + //indicate completion + messages << data; + data.clear(); + QQmlDebugStream ds(&data, QIODevice::WriteOnly); ds << (qint64)-1 << (int)Complete; messages << data; @@ -230,9 +425,10 @@ void QQmlProfilerService::stateAboutToBeChanged(QQmlDebugService::State newState if (state() == newState) return; - if (newState != Enabled && enabled) { - stopProfilingImpl(); - sendMessages(); + // Stop all profiling and send the data before we get disabled. + if (newState != Enabled) { + foreach (QQmlEngine *engine, m_engineProfilers.keys()) + stopProfiling(engine); } } @@ -243,14 +439,27 @@ void QQmlProfilerService::messageReceived(const QByteArray &message) QByteArray rwData = message; QQmlDebugStream stream(&rwData, QIODevice::ReadOnly); + int engineId = -1; bool enabled; stream >> enabled; - - if (enabled) { - startProfilingImpl(); + if (!stream.atEnd()) + stream >> engineId; + + // The second time around there will be specific engineIds. + // We only have to wait after the first, empty start message. + if (engineId == -1) { + // Wait until no engine registers within RegisterTimeout anymore. + foreach (QQmlEngine *engine, m_engineProfilers.keys().toSet()) { + if (enabled) + startProfiling(engine); + else + stopProfiling(engine); + } } else { - if (stopProfilingImpl()) - sendMessages(); + if (enabled) + startProfiling(qobject_cast<QQmlEngine *>(objectForId(engineId))); + else + stopProfiling(qobject_cast<QQmlEngine *>(objectForId(engineId))); } stopWaiting(); diff --git a/src/qml/debugger/qqmlprofilerservice_p.h b/src/qml/debugger/qqmlprofilerservice_p.h index b688141730..4549814a02 100644 --- a/src/qml/debugger/qqmlprofilerservice_p.h +++ b/src/qml/debugger/qqmlprofilerservice_p.h @@ -54,6 +54,9 @@ // #include "qqmlconfigurabledebugservice_p.h" +#include "qqmlprofilerdefinitions_p.h" +#include "qqmlabstractprofileradapter_p.h" + #include <private/qqmlboundsignal_p.h> // this contains QUnifiedTimer #include <private/qabstractanimation_p.h> @@ -163,81 +166,22 @@ class QUrl; class QQmlEngine; -class Q_QML_PRIVATE_EXPORT QQmlProfilerService : public QQmlConfigurableDebugService +class Q_QML_PRIVATE_EXPORT QQmlProfilerService : public QQmlConfigurableDebugService, public QQmlProfilerDefinitions { + Q_OBJECT public: - enum Message { - Event, - RangeStart, - RangeData, - RangeLocation, - RangeEnd, - Complete, // end of transmission - PixmapCacheEvent, - SceneGraphFrame, - - MaximumMessage - }; - - enum EventType { - FramePaint, - Mouse, - Key, - AnimationFrame, - EndTrace, - StartTrace, - - MaximumEventType - }; - - enum RangeType { - Painting, - Compiling, - Creating, - Binding, //running a binding - HandlingSignal, //running a signal handler - - MaximumRangeType - }; - - enum BindingType { - QmlBinding, - V8Binding, - V4Binding, - - MaximumBindingType - }; - - enum PixmapEventType { - PixmapSizeKnown, - PixmapReferenceCountChanged, - PixmapCacheCountChanged, - PixmapLoadingStarted, - PixmapLoadingFinished, - PixmapLoadingError, - - MaximumPixmapEventType - }; - - enum SceneGraphFrameType { - SceneGraphRendererFrame, - SceneGraphAdaptationLayerFrame, - SceneGraphContextFrame, - SceneGraphRenderLoopFrame, - SceneGraphTexturePrepare, - SceneGraphTextureDeletion, - SceneGraphPolishAndSync, - SceneGraphWindowsRenderShow, - SceneGraphWindowsAnimations, - SceneGraphWindowsPolishFrame, - - MaximumSceneGraphFrameType - }; static QQmlProfilerService *instance(); + void engineAboutToBeAdded(QQmlEngine *engine); + void engineAboutToBeRemoved(QQmlEngine *engine); + void engineAdded(QQmlEngine *engine); + void engineRemoved(QQmlEngine *engine); - static bool startProfiling(); - static bool stopProfiling(); + void addGlobalProfiler(QQmlAbstractProfilerAdapter *profiler); + void removeGlobalProfiler(QQmlAbstractProfilerAdapter *profiler); + + void startProfiling(QQmlEngine *engine); + void stopProfiling(QQmlEngine *engine); template<EventType DetailType> static void addEvent() @@ -290,18 +234,16 @@ public: qint64 timestamp() {return m_timer.nsecsElapsed();} - static void sendProfilingData(); - QQmlProfilerService(); ~QQmlProfilerService(); + void dataReady(QQmlAbstractProfilerAdapter *profiler); + protected: virtual void stateAboutToBeChanged(State state); virtual void messageReceived(const QByteArray &); private: - bool startProfilingImpl(); - bool stopProfilingImpl(); static void startBinding(const QString &fileName, int line, int column, BindingType bindingType) { @@ -357,6 +299,7 @@ private: } void sendMessages(); + void addEngineProfiler(QQmlAbstractProfilerAdapter *profiler, QQmlEngine *engine); void processMessage(const QQmlProfilerData &message) { @@ -373,12 +316,32 @@ private: QVector<QQmlProfilerData> m_data; QMutex m_dataMutex; + QList<QQmlAbstractProfilerAdapter *> m_globalProfilers; + QMultiHash<QQmlEngine *, QQmlAbstractProfilerAdapter *> m_engineProfilers; + QList<QQmlEngine *> m_stoppingEngines; + QMultiMap<qint64, QQmlAbstractProfilerAdapter *> m_startTimes; + static QQmlProfilerService *m_instance; friend struct QQmlBindingProfiler; friend struct QQmlHandlingSignalProfiler; friend struct QQmlVmeProfiler; friend struct QQmlCompilingProfiler; + friend class QQmlProfiler; +}; + +// Temporary shim around QQmlProfilerService to make it look like a QQmlAbstractProfilerAdapter. +class QQmlProfiler : public QQmlAbstractProfilerAdapter { + Q_OBJECT +public: + QQmlProfiler(QQmlProfilerService *service); + qint64 sendMessages(qint64 until, QList<QByteArray> &messages); + +public slots: + void startProfiling(); + void stopProfiling(); +private: + int next; }; // diff --git a/src/qml/qml/v8/qqmlbuiltinfunctions.cpp b/src/qml/qml/v8/qqmlbuiltinfunctions.cpp index 26ef86836c..fbc41201fb 100644 --- a/src/qml/qml/v8/qqmlbuiltinfunctions.cpp +++ b/src/qml/qml/v8/qqmlbuiltinfunctions.cpp @@ -1422,10 +1422,6 @@ QV4::ReturnedValue ConsoleObject::method_log(CallContext *ctx) QV4::ReturnedValue ConsoleObject::method_profile(CallContext *ctx) { - //DeclarativeDebugTrace cannot handle nested profiling - //although v8 can handle several profiling at once, - //we do not allow that. Hence, we pass an empty(default) title - QString title; QV4::ExecutionEngine *v4 = ctx->engine; QV4::StackFrame frame = v4->currentStackFrame(); @@ -1434,12 +1430,9 @@ QV4::ReturnedValue ConsoleObject::method_profile(CallContext *ctx) QMessageLogger logger(baSource.constData(), frame.line, baFunction.constData()); if (!QQmlDebugService::isDebuggingEnabled()) { logger.warning("Cannot start profiling because debug service is disabled. Start with -qmljsdebugger=port:XXXXX."); - } else if (QQmlProfilerService::startProfiling()) { - QV4ProfilerService::instance()->startProfiling(title); - - logger.debug("Profiling started."); } else { - logger.warning("Profiling is already in progress. First, end current profiling session."); + QQmlProfilerService::instance()->startProfiling(v4->v8Engine->engine()); + logger.debug("Profiling started."); } return QV4::Encode::undefined(); @@ -1447,11 +1440,6 @@ QV4::ReturnedValue ConsoleObject::method_profile(CallContext *ctx) QV4::ReturnedValue ConsoleObject::method_profileEnd(CallContext *ctx) { - //DeclarativeDebugTrace cannot handle nested profiling - //although v8 can handle several profiling at once, - //we do not allow that. Hence, we pass an empty(default) title - QString title; - QV4::ExecutionEngine *v4 = ctx->engine; QV4::StackFrame frame = v4->currentStackFrame(); @@ -1459,15 +1447,11 @@ QV4::ReturnedValue ConsoleObject::method_profileEnd(CallContext *ctx) const QByteArray baFunction = frame.function.toUtf8(); QMessageLogger logger(baSource.constData(), frame.line, baFunction.constData()); - if (QQmlProfilerService::stopProfiling()) { - QV4ProfilerService *profiler = QV4ProfilerService::instance(); - profiler->stopProfiling(title); - QQmlProfilerService::sendProfilingData(); - profiler->sendProfilingData(); - - logger.debug("Profiling ended."); + if (!QQmlDebugService::isDebuggingEnabled()) { + logger.warning("Ignoring console.profileEnd(): the debug service is disabled."); } else { - logger.warning("Profiling was not started."); + QQmlProfilerService::instance()->stopProfiling(v4->v8Engine->engine()); + logger.debug("Profiling ended."); } return QV4::Encode::undefined(); diff --git a/tests/auto/qml/debugger/qqmlprofilerservice/tst_qqmlprofilerservice.cpp b/tests/auto/qml/debugger/qqmlprofilerservice/tst_qqmlprofilerservice.cpp index 14a6178b09..d05dfa8496 100644 --- a/tests/auto/qml/debugger/qqmlprofilerservice/tst_qqmlprofilerservice.cpp +++ b/tests/auto/qml/debugger/qqmlprofilerservice/tst_qqmlprofilerservice.cpp @@ -100,6 +100,7 @@ public: Creating, Binding, //running a binding HandlingSignal, //running a signal handler + Javascript, MaximumRangeType }; @@ -214,9 +215,16 @@ void QQmlProfilerClient::messageReceived(const QByteArray &message) case QQmlProfilerClient::FramePaint: case QQmlProfilerClient::Mouse: case QQmlProfilerClient::Key: - case QQmlProfilerClient::StartTrace: + break; case QQmlProfilerClient::EndTrace: + case QQmlProfilerClient::StartTrace: { + int engineId = -1; + if (!stream.atEnd()) { + stream >> engineId; + QVERIFY(engineId >= 0); + } break; + } default: { QString failMsg = QString("Unknown event type:") + data.detailType; QFAIL(qPrintable(failMsg)); diff --git a/tests/auto/qml/qqmlconsole/tst_qqmlconsole.cpp b/tests/auto/qml/qqmlconsole/tst_qqmlconsole.cpp index a37d705284..43aa82e1e4 100644 --- a/tests/auto/qml/qqmlconsole/tst_qqmlconsole.cpp +++ b/tests/auto/qml/qqmlconsole/tst_qqmlconsole.cpp @@ -116,7 +116,7 @@ void tst_qqmlconsole::profiling() // profiling() QTest::ignoreMessage(QtWarningMsg, "Cannot start profiling because debug service is disabled. Start with -qmljsdebugger=port:XXXXX."); - QTest::ignoreMessage(QtWarningMsg, "Profiling was not started."); + QTest::ignoreMessage(QtWarningMsg, "Ignoring console.profileEnd(): the debug service is disabled."); QQmlComponent component(&engine, testUrl); QObject *object = component.create(); |