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

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