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 | |
55 | QT_BEGIN_NAMESPACE |
56 | |
57 | Q_QML_DEBUG_PLUGIN_LOADER(QQmlAbstractProfilerAdapter) |
58 | |
59 | QQmlProfilerServiceImpl::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 | |
72 | QQmlProfilerServiceImpl::~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 | |
80 | void 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 | |
114 | void 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 | |
133 | void 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 | |
148 | void 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 | |
170 | void 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 | |
185 | void 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 | |
192 | void 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 | |
208 | void QQmlProfilerServiceImpl::removeGlobalProfiler(QQmlAbstractProfilerAdapter *profiler) |
209 | { |
210 | QMutexLocker lock(&m_configMutex); |
211 | removeProfilerFromStartTimes(profiler); |
212 | m_globalProfilers.removeOne(t: profiler); |
213 | } |
214 | |
215 | void 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 | */ |
234 | void 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 | */ |
294 | void 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 | */ |
344 | void 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 | |
408 | void 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 | |
424 | void 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 | |
470 | void 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 | |
493 | QT_END_NAMESPACE |
494 | |
495 | #include "moc_qqmlprofilerservice.cpp" |
496 | |