1/****************************************************************************
2**
3** Copyright (C) 2016 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the QtQml module of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:LGPL$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** GNU Lesser General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU Lesser
19** General Public License version 3 as published by the Free Software
20** Foundation and appearing in the file LICENSE.LGPL3 included in the
21** packaging of this file. Please review the following information to
22** ensure the GNU Lesser General Public License version 3 requirements
23** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24**
25** GNU General Public License Usage
26** Alternatively, this file may be used under the terms of the GNU
27** General Public License version 2.0 or (at your option) the GNU General
28** Public license version 3 or any later version approved by the KDE Free
29** Qt Foundation. The licenses are as published by the Free Software
30** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31** included in the packaging of this file. Please review the following
32** information to ensure the GNU General Public License requirements will
33** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34** https://www.gnu.org/licenses/gpl-3.0.html.
35**
36** $QT_END_LICENSE$
37**
38****************************************************************************/
39
40#include "qqmlprofilerservice.h"
41#include "qv4profileradapter.h"
42#include "qqmlprofileradapter.h"
43#include "qqmlprofilerservicefactory.h"
44
45#include <private/qjsengine_p.h>
46#include <private/qqmldebugpluginmanager_p.h>
47
48#include <QtCore/qurl.h>
49#include <QtCore/qtimer.h>
50#include <QtCore/qthread.h>
51#include <QtCore/qcoreapplication.h>
52
53#include <algorithm>
54
55QT_BEGIN_NAMESPACE
56
57Q_QML_DEBUG_PLUGIN_LOADER(QQmlAbstractProfilerAdapter)
58
59QQmlProfilerServiceImpl::QQmlProfilerServiceImpl(QObject *parent) :
60 QQmlConfigurableDebugService<QQmlProfilerService>(1, parent),
61 m_waitingForStop(false), m_globalEnabled(false), m_globalFeatures(0)
62{
63 m_timer.start();
64 QQmlAbstractProfilerAdapter *quickAdapter =
65 loadQQmlAbstractProfilerAdapter(key: QLatin1String("QQuickProfilerAdapter"));
66 if (quickAdapter) {
67 addGlobalProfiler(profiler: quickAdapter);
68 quickAdapter->setService(this);
69 }
70}
71
72QQmlProfilerServiceImpl::~QQmlProfilerServiceImpl()
73{
74 // No need to lock here. If any engine or global profiler is still trying to register at this
75 // point we have a nasty bug anyway.
76 qDeleteAll(c: m_engineProfilers);
77 qDeleteAll(c: m_globalProfilers);
78}
79
80void QQmlProfilerServiceImpl::dataReady(QQmlAbstractProfilerAdapter *profiler)
81{
82 QMutexLocker lock(&m_configMutex);
83 bool dataComplete = true;
84 for (QMultiMap<qint64, QQmlAbstractProfilerAdapter *>::iterator i(m_startTimes.begin()); i != m_startTimes.end();) {
85 if (i.value() == profiler) {
86 m_startTimes.erase(it: i++);
87 } else {
88 if (i.key() == -1)
89 dataComplete = false;
90 ++i;
91 }
92 }
93 m_startTimes.insert(key: 0, value: profiler);
94 if (dataComplete) {
95 QList<QJSEngine *> enginesToRelease;
96 for (QJSEngine *engine : qAsConst(t&: m_stoppingEngines)) {
97 const auto range = qAsConst(t&: m_engineProfilers).equal_range(key: engine);
98 const auto startTimesEnd = m_startTimes.cend();
99 for (auto it = range.first; it != range.second; ++it) {
100 if (std::find(first: m_startTimes.cbegin(), last: startTimesEnd, val: *it) != startTimesEnd) {
101 enginesToRelease.append(t: engine);
102 break;
103 }
104 }
105 }
106 sendMessages();
107 for (QJSEngine *engine : qAsConst(t&: enginesToRelease)) {
108 m_stoppingEngines.removeOne(t: engine);
109 emit detachedFromEngine(engine);
110 }
111 }
112}
113
114void QQmlProfilerServiceImpl::engineAboutToBeAdded(QJSEngine *engine)
115{
116 Q_ASSERT_X(QThread::currentThread() == engine->thread(), Q_FUNC_INFO,
117 "QML profilers have to be added from the engine thread");
118
119 QMutexLocker lock(&m_configMutex);
120 if (QQmlEngine *qmlEngine = qobject_cast<QQmlEngine *>(object: engine)) {
121 QQmlEnginePrivate *enginePrivate = QQmlEnginePrivate::get(e: qmlEngine);
122 QQmlProfilerAdapter *qmlAdapter = new QQmlProfilerAdapter(this, enginePrivate);
123 addEngineProfiler(profiler: qmlAdapter, engine);
124 QQmlProfilerAdapter *compileAdapter
125 = new QQmlProfilerAdapter(this, &(enginePrivate->typeLoader));
126 addEngineProfiler(profiler: compileAdapter, engine);
127 }
128 QV4ProfilerAdapter *v4Adapter = new QV4ProfilerAdapter(this, engine->handle());
129 addEngineProfiler(profiler: v4Adapter, engine);
130 QQmlConfigurableDebugService<QQmlProfilerService>::engineAboutToBeAdded(engine);
131}
132
133void QQmlProfilerServiceImpl::engineAdded(QJSEngine *engine)
134{
135 Q_ASSERT_X(QThread::currentThread() == engine->thread(), Q_FUNC_INFO,
136 "QML profilers have to be added from the engine thread");
137
138 QMutexLocker lock(&m_configMutex);
139
140 if (m_globalEnabled)
141 startProfiling(engine, features: m_globalFeatures);
142
143 const auto range = qAsConst(t&: m_engineProfilers).equal_range(key: engine);
144 for (auto it = range.first; it != range.second; ++it)
145 (*it)->stopWaiting();
146}
147
148void QQmlProfilerServiceImpl::engineAboutToBeRemoved(QJSEngine *engine)
149{
150 Q_ASSERT_X(QThread::currentThread() == engine->thread(), Q_FUNC_INFO,
151 "QML profilers have to be removed from the engine thread");
152
153 QMutexLocker lock(&m_configMutex);
154 bool isRunning = false;
155 const auto range = qAsConst(t&: m_engineProfilers).equal_range(key: engine);
156 for (auto it = range.first; it != range.second; ++it) {
157 QQmlAbstractProfilerAdapter *profiler = *it;
158 if (profiler->isRunning())
159 isRunning = true;
160 profiler->startWaiting();
161 }
162 if (isRunning) {
163 m_stoppingEngines.append(t: engine);
164 stopProfiling(engine);
165 } else {
166 emit detachedFromEngine(engine);
167 }
168}
169
170void QQmlProfilerServiceImpl::engineRemoved(QJSEngine *engine)
171{
172 Q_ASSERT_X(QThread::currentThread() == engine->thread(), Q_FUNC_INFO,
173 "QML profilers have to be removed from the engine thread");
174
175 QMutexLocker lock(&m_configMutex);
176 const auto range = qAsConst(t&: m_engineProfilers).equal_range(key: engine);
177 for (auto it = range.first; it != range.second; ++it) {
178 QQmlAbstractProfilerAdapter *profiler = *it;
179 removeProfilerFromStartTimes(profiler);
180 delete profiler;
181 }
182 m_engineProfilers.remove(key: engine);
183}
184
185void QQmlProfilerServiceImpl::addEngineProfiler(QQmlAbstractProfilerAdapter *profiler, QJSEngine *engine)
186{
187 profiler->moveToThread(thread: thread());
188 profiler->synchronize(t: m_timer);
189 m_engineProfilers.insert(key: engine, value: profiler);
190}
191
192void QQmlProfilerServiceImpl::addGlobalProfiler(QQmlAbstractProfilerAdapter *profiler)
193{
194 QMutexLocker lock(&m_configMutex);
195 profiler->synchronize(t: m_timer);
196 m_globalProfilers.append(t: profiler);
197 // Global profiler, not connected to a specific engine.
198 // Global profilers are started whenever any engine profiler is started and stopped when
199 // all engine profilers are stopped.
200 quint64 features = 0;
201 for (QQmlAbstractProfilerAdapter *engineProfiler : qAsConst(t&: m_engineProfilers))
202 features |= engineProfiler->features();
203
204 if (features != 0)
205 profiler->startProfiling(features);
206}
207
208void QQmlProfilerServiceImpl::removeGlobalProfiler(QQmlAbstractProfilerAdapter *profiler)
209{
210 QMutexLocker lock(&m_configMutex);
211 removeProfilerFromStartTimes(profiler);
212 m_globalProfilers.removeOne(t: profiler);
213}
214
215void QQmlProfilerServiceImpl::removeProfilerFromStartTimes(const QQmlAbstractProfilerAdapter *profiler)
216{
217 for (QMultiMap<qint64, QQmlAbstractProfilerAdapter *>::iterator i(m_startTimes.begin());
218 i != m_startTimes.end();) {
219 if (i.value() == profiler) {
220 m_startTimes.erase(it: i++);
221 break;
222 } else {
223 ++i;
224 }
225 }
226}
227
228/*!
229 * Start profiling the given \a engine. If \a engine is 0, start all engine profilers that aren't
230 * currently running.
231 *
232 * If any engine profiler is started like that also start all global profilers.
233 */
234void QQmlProfilerServiceImpl::startProfiling(QJSEngine *engine, quint64 features)
235{
236 QMutexLocker lock(&m_configMutex);
237
238 if (features & static_cast<quint64>(1) << ProfileDebugMessages) {
239 if (QDebugMessageService *messageService =
240 QQmlDebugConnector::instance()->service<QDebugMessageService>())
241 messageService->synchronizeTime(otherTimer: m_timer);
242 }
243
244 QQmlDebugPacket d;
245
246 d << m_timer.nsecsElapsed() << static_cast<qint32>(Event) << static_cast<qint32>(StartTrace);
247 bool startedAny = false;
248 if (engine != nullptr) {
249 const auto range = qAsConst(t&: m_engineProfilers).equal_range(key: engine);
250 for (auto it = range.first; it != range.second; ++it) {
251 QQmlAbstractProfilerAdapter *profiler = *it;
252 if (!profiler->isRunning()) {
253 profiler->startProfiling(features);
254 startedAny = true;
255 }
256 }
257 if (startedAny)
258 d << idForObject(engine);
259 } else {
260 m_globalEnabled = true;
261 m_globalFeatures = features;
262
263 QSet<QJSEngine *> engines;
264 for (QMultiHash<QJSEngine *, QQmlAbstractProfilerAdapter *>::iterator i(m_engineProfilers.begin());
265 i != m_engineProfilers.end(); ++i) {
266 if (!i.value()->isRunning()) {
267 engines << i.key();
268 i.value()->startProfiling(features);
269 startedAny = true;
270 }
271 }
272 for (QJSEngine *profiledEngine : qAsConst(t&: engines))
273 d << idForObject(profiledEngine);
274 }
275
276 if (startedAny) {
277 for (QQmlAbstractProfilerAdapter *profiler : qAsConst(t&: m_globalProfilers)) {
278 if (!profiler->isRunning())
279 profiler->startProfiling(features);
280 }
281
282 emit startFlushTimer();
283 emit messageToClient(name: name(), message: d.data());
284 }
285}
286
287/*!
288 * Stop profiling the given \a engine. If \a engine is 0, stop all currently running engine
289 * profilers.
290 *
291 * If afterwards no more engine profilers are running, also stop all global profilers. Otherwise
292 * only make them report their data.
293 */
294void QQmlProfilerServiceImpl::stopProfiling(QJSEngine *engine)
295{
296 QMutexLocker lock(&m_configMutex);
297 QList<QQmlAbstractProfilerAdapter *> stopping;
298 QList<QQmlAbstractProfilerAdapter *> reporting;
299
300 if (engine == nullptr)
301 m_globalEnabled = false;
302
303 bool stillRunning = false;
304 for (QMultiHash<QJSEngine *, QQmlAbstractProfilerAdapter *>::iterator i(m_engineProfilers.begin());
305 i != m_engineProfilers.end(); ++i) {
306 if (i.value()->isRunning()) {
307 m_startTimes.insert(key: -1, value: i.value());
308 if (engine == nullptr || i.key() == engine) {
309 stopping << i.value();
310 } else {
311 reporting << i.value();
312 stillRunning = true;
313 }
314 }
315 }
316
317 if (stopping.isEmpty())
318 return;
319
320 for (QQmlAbstractProfilerAdapter *profiler : qAsConst(t&: m_globalProfilers)) {
321 if (!profiler->isRunning())
322 continue;
323 m_startTimes.insert(key: -1, value: profiler);
324 if (stillRunning) {
325 reporting << profiler;
326 } else {
327 stopping << profiler;
328 }
329 }
330
331 emit stopFlushTimer();
332 m_waitingForStop = true;
333
334 for (QQmlAbstractProfilerAdapter *profiler : qAsConst(t&: reporting))
335 profiler->reportData();
336
337 for (QQmlAbstractProfilerAdapter *profiler : qAsConst(t&: stopping))
338 profiler->stopProfiling();
339}
340
341/*
342 Send the queued up messages.
343*/
344void QQmlProfilerServiceImpl::sendMessages()
345{
346 QList<QByteArray> messages;
347
348 QQmlDebugPacket traceEnd;
349 if (m_waitingForStop) {
350 traceEnd << m_timer.nsecsElapsed() << static_cast<qint32>(Event)
351 << static_cast<qint32>(EndTrace);
352
353 QSet<QJSEngine *> seen;
354 for (QQmlAbstractProfilerAdapter *profiler : qAsConst(t&: m_startTimes)) {
355 for (QMultiHash<QJSEngine *, QQmlAbstractProfilerAdapter *>::iterator i(m_engineProfilers.begin());
356 i != m_engineProfilers.end(); ++i) {
357 if (i.value() == profiler && !seen.contains(value: i.key())) {
358 seen << i.key();
359 traceEnd << idForObject(i.key());
360 }
361 }
362 }
363 }
364
365 while (!m_startTimes.empty()) {
366 QQmlAbstractProfilerAdapter *first = m_startTimes.begin().value();
367 m_startTimes.erase(it: m_startTimes.begin());
368 qint64 next = first->sendMessages(until: m_startTimes.isEmpty() ?
369 std::numeric_limits<qint64>::max() :
370 m_startTimes.begin().key(), messages);
371 if (next != -1)
372 m_startTimes.insert(key: next, value: first);
373
374 if (messages.length() >= QQmlAbstractProfilerAdapter::s_numMessagesPerBatch) {
375 emit messagesToClient(name: name(), messages);
376 messages.clear();
377 }
378 }
379
380 bool stillRunning = false;
381 for (const QQmlAbstractProfilerAdapter *profiler : qAsConst(t&: m_engineProfilers)) {
382 if (profiler->isRunning()) {
383 stillRunning = true;
384 break;
385 }
386 }
387
388 if (m_waitingForStop) {
389 // EndTrace can be sent multiple times, as it's engine specific.
390 messages << traceEnd.data();
391
392 if (!stillRunning) {
393 // Complete is only sent once, when no engines are running anymore.
394 QQmlDebugPacket ds;
395 ds << static_cast<qint64>(-1) << static_cast<qint32>(Complete);
396 messages << ds.data();
397 m_waitingForStop = false;
398 }
399 }
400
401 emit messagesToClient(name: name(), messages);
402
403 // Restart flushing if any profilers are still running
404 if (stillRunning)
405 emit startFlushTimer();
406}
407
408void QQmlProfilerServiceImpl::stateAboutToBeChanged(QQmlDebugService::State newState)
409{
410 QMutexLocker lock(&m_configMutex);
411
412 if (state() == newState)
413 return;
414
415 // Stop all profiling and send the data before we get disabled.
416 if (newState != Enabled) {
417 for (auto it = m_engineProfilers.keyBegin(), end = m_engineProfilers.keyEnd();
418 it != end; ++it) {
419 stopProfiling(engine: *it);
420 }
421 }
422}
423
424void QQmlProfilerServiceImpl::messageReceived(const QByteArray &message)
425{
426 QMutexLocker lock(&m_configMutex);
427
428 QQmlDebugPacket stream(message);
429
430 int engineId = -1;
431 quint64 features = std::numeric_limits<quint64>::max();
432 bool enabled;
433 quint32 flushInterval = 0;
434 stream >> enabled;
435 if (!stream.atEnd())
436 stream >> engineId;
437 if (!stream.atEnd())
438 stream >> features;
439 if (!stream.atEnd()) {
440 stream >> flushInterval;
441 m_flushTimer.setInterval(
442 static_cast<int>(qMin(a: flushInterval,
443 b: static_cast<quint32>(std::numeric_limits<int>::max()))));
444 auto timerStart = static_cast<void(QTimer::*)()>(&QTimer::start);
445 if (flushInterval > 0) {
446 connect(sender: &m_flushTimer, signal: &QTimer::timeout, receiver: this, slot: &QQmlProfilerServiceImpl::flush);
447 connect(sender: this, signal: &QQmlProfilerServiceImpl::startFlushTimer, receiver: &m_flushTimer, slot: timerStart);
448 connect(sender: this, signal: &QQmlProfilerServiceImpl::stopFlushTimer, receiver: &m_flushTimer, slot: &QTimer::stop);
449 } else {
450 disconnect(sender: &m_flushTimer, signal: &QTimer::timeout, receiver: this, slot: &QQmlProfilerServiceImpl::flush);
451 disconnect(sender: this, signal: &QQmlProfilerServiceImpl::startFlushTimer, receiver: &m_flushTimer, slot: timerStart);
452 disconnect(sender: this, signal: &QQmlProfilerServiceImpl::stopFlushTimer,
453 receiver: &m_flushTimer, slot: &QTimer::stop);
454 }
455 }
456
457 bool useMessageTypes = false;
458 if (!stream.atEnd())
459 stream >> useMessageTypes;
460
461 // If engineId == -1 objectForId() and then the cast will return 0.
462 if (enabled && useMessageTypes) // If the client doesn't support message types don't profile.
463 startProfiling(engine: qobject_cast<QJSEngine *>(object: objectForId(id: engineId)), features);
464 else if (!enabled) // On stopProfiling the client doesn't repeat useMessageTypes.
465 stopProfiling(engine: qobject_cast<QJSEngine *>(object: objectForId(id: engineId)));
466
467 stopWaiting();
468}
469
470void QQmlProfilerServiceImpl::flush()
471{
472 QMutexLocker lock(&m_configMutex);
473 QList<QQmlAbstractProfilerAdapter *> reporting;
474
475 for (QQmlAbstractProfilerAdapter *profiler : qAsConst(t&: m_engineProfilers)) {
476 if (profiler->isRunning()) {
477 m_startTimes.insert(key: -1, value: profiler);
478 reporting.append(t: profiler);
479 }
480 }
481
482 for (QQmlAbstractProfilerAdapter *profiler : qAsConst(t&: m_globalProfilers)) {
483 if (profiler->isRunning()) {
484 m_startTimes.insert(key: -1, value: profiler);
485 reporting.append(t: profiler);
486 }
487 }
488
489 for (QQmlAbstractProfilerAdapter *profiler : qAsConst(t&: reporting))
490 profiler->reportData();
491}
492
493QT_END_NAMESPACE
494
495#include "moc_qqmlprofilerservice.cpp"
496

source code of qtdeclarative/src/plugins/qmltooling/qmldbg_profiler/qqmlprofilerservice.cpp