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 test suite of the Qt Toolkit. |
7 | ** |
8 | ** $QT_BEGIN_LICENSE:GPL-EXCEPT$ |
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 General Public License Usage |
18 | ** Alternatively, this file may be used under the terms of the GNU |
19 | ** General Public License version 3 as published by the Free Software |
20 | ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT |
21 | ** included in the packaging of this file. Please review the following |
22 | ** information to ensure the GNU General Public License requirements will |
23 | ** be met: https://www.gnu.org/licenses/gpl-3.0.html. |
24 | ** |
25 | ** $QT_END_LICENSE$ |
26 | ** |
27 | ****************************************************************************/ |
28 | |
29 | #include "debugutil_p.h" |
30 | #include "qqmldebugprocess_p.h" |
31 | #include "../../../shared/util.h" |
32 | |
33 | #include <private/qqmlprofilerclient_p.h> |
34 | #include <private/qqmldebugconnection_p.h> |
35 | |
36 | #include <QtTest/qtest.h> |
37 | #include <QtTest/qsignalspy.h> |
38 | #include <QtCore/qlibraryinfo.h> |
39 | |
40 | #include <QtGui/private/qguiapplication_p.h> |
41 | #include <QtGui/qpa/qplatformintegration.h> |
42 | |
43 | class QQmlProfilerTestClient : public QQmlProfilerEventReceiver |
44 | { |
45 | Q_OBJECT |
46 | |
47 | public: |
48 | QQmlProfilerTestClient(QQmlDebugConnection *connection) : |
49 | client(new QQmlProfilerClient(connection, this)) |
50 | { |
51 | connect(sender: client.data(), signal: &QQmlProfilerClient::traceStarted, |
52 | receiver: this, slot: &QQmlProfilerTestClient::startTrace); |
53 | connect(sender: client.data(), signal: &QQmlProfilerClient::traceFinished, |
54 | receiver: this, slot: &QQmlProfilerTestClient::endTrace); |
55 | } |
56 | |
57 | void startTrace(qint64 timestamp, const QList<int> &engineIds); |
58 | void endTrace(qint64 timestamp, const QList<int> &engineIds); |
59 | |
60 | QPointer<QQmlProfilerClient> client; // Owned by QQmlDebugTest |
61 | QVector<QQmlProfilerEventType> types; |
62 | |
63 | QVector<QQmlProfilerEvent> qmlMessages; |
64 | QVector<QQmlProfilerEvent> javascriptMessages; |
65 | QVector<QQmlProfilerEvent> jsHeapMessages; |
66 | QVector<QQmlProfilerEvent> asynchronousMessages; |
67 | QVector<QQmlProfilerEvent> pixmapMessages; |
68 | |
69 | int numLoadedEventTypes() const override; |
70 | void addEventType(const QQmlProfilerEventType &type) override; |
71 | void addEvent(const QQmlProfilerEvent &event) override; |
72 | |
73 | private: |
74 | qint64 lastTimestamp = -1; |
75 | }; |
76 | |
77 | void QQmlProfilerTestClient::startTrace(qint64 timestamp, const QList<int> &engineIds) |
78 | { |
79 | types.append(t: QQmlProfilerEventType(Event, MaximumRangeType, StartTrace)); |
80 | asynchronousMessages.append(t: QQmlProfilerEvent(timestamp, types.length() - 1, |
81 | engineIds.toVector())); |
82 | } |
83 | |
84 | void QQmlProfilerTestClient::endTrace(qint64 timestamp, const QList<int> &engineIds) |
85 | { |
86 | types.append(t: QQmlProfilerEventType(Event, MaximumRangeType, EndTrace)); |
87 | asynchronousMessages.append(t: QQmlProfilerEvent(timestamp, types.length() - 1, |
88 | engineIds.toVector())); |
89 | } |
90 | |
91 | int QQmlProfilerTestClient::numLoadedEventTypes() const |
92 | { |
93 | return types.length(); |
94 | } |
95 | |
96 | void QQmlProfilerTestClient::addEventType(const QQmlProfilerEventType &type) |
97 | { |
98 | types.append(t: type); |
99 | } |
100 | |
101 | void QQmlProfilerTestClient::addEvent(const QQmlProfilerEvent &event) |
102 | { |
103 | const int typeIndex = event.typeIndex(); |
104 | QVERIFY(typeIndex < types.length()); |
105 | |
106 | const QQmlProfilerEventType &type = types[typeIndex]; |
107 | |
108 | QVERIFY(event.timestamp() >= lastTimestamp); |
109 | lastTimestamp = event.timestamp(); |
110 | |
111 | switch (type.message()) { |
112 | case Event: { |
113 | switch (type.detailType()) { |
114 | case StartTrace: |
115 | QFAIL("StartTrace should not be passed on as event" ); |
116 | break; |
117 | case EndTrace: |
118 | QFAIL("EndTrace should not be passed on as event" ); |
119 | break; |
120 | case AnimationFrame: |
121 | asynchronousMessages.append(t: event); |
122 | break; |
123 | case Mouse: |
124 | case Key: |
125 | qmlMessages.append(t: event); |
126 | break; |
127 | default: |
128 | QFAIL(qPrintable(QString::fromLatin1("Event with unknown detailType %1 received at %2." ) |
129 | .arg(type.detailType()).arg(event.timestamp()))); |
130 | break; |
131 | } |
132 | break; |
133 | } |
134 | case RangeStart: |
135 | case RangeData: |
136 | case RangeLocation: |
137 | case RangeEnd: |
138 | QFAIL("Range stages are transmitted as part of events" ); |
139 | break; |
140 | case Complete: |
141 | QFAIL("Complete should not be passed on as event" ); |
142 | break; |
143 | case PixmapCacheEvent: |
144 | pixmapMessages.append(t: event); |
145 | break; |
146 | case SceneGraphFrame: |
147 | asynchronousMessages.append(t: event); |
148 | break; |
149 | case MemoryAllocation: |
150 | jsHeapMessages.append(t: event); |
151 | break; |
152 | case DebugMessage: |
153 | // Unhandled |
154 | break; |
155 | case MaximumMessage: |
156 | switch (type.rangeType()) { |
157 | case Painting: |
158 | QFAIL("QtQuick1 paint message received." ); |
159 | break; |
160 | case Compiling: |
161 | case Creating: |
162 | case Binding: |
163 | case HandlingSignal: |
164 | qmlMessages.append(t: event); |
165 | break; |
166 | case Javascript: |
167 | javascriptMessages.append(t: event); |
168 | break; |
169 | default: |
170 | QFAIL(qPrintable( |
171 | QString::fromLatin1("Unknown range event %1 received at %2." ) |
172 | .arg(type.rangeType()).arg(event.timestamp()))); |
173 | break; |
174 | } |
175 | break; |
176 | } |
177 | } |
178 | |
179 | class tst_QQmlProfilerService : public QQmlDebugTest |
180 | { |
181 | Q_OBJECT |
182 | |
183 | private: |
184 | enum MessageListType { |
185 | MessageListQML, |
186 | MessageListJavaScript, |
187 | MessageListJsHeap, |
188 | MessageListAsynchronous, |
189 | MessageListPixmap |
190 | }; |
191 | |
192 | enum CheckType { |
193 | CheckMessageType = 1 << 0, |
194 | CheckDetailType = 1 << 1, |
195 | CheckLine = 1 << 2, |
196 | CheckColumn = 1 << 3, |
197 | CheckDataEndsWith = 1 << 4, |
198 | CheckFileEndsWith = 1 << 5, |
199 | CheckNumbers = 1 << 6, |
200 | |
201 | CheckType = CheckMessageType | CheckDetailType | CheckLine | CheckColumn | CheckFileEndsWith |
202 | }; |
203 | |
204 | ConnectResult connectTo(bool block, const QString &file, bool recordFromStart = true, |
205 | uint flushInterval = 0, bool restrictServices = true, |
206 | const QString &executable |
207 | = QLibraryInfo::location(QLibraryInfo::BinariesPath) + "/qmlscene" ); |
208 | void checkProcessTerminated(); |
209 | void checkTraceReceived(); |
210 | void checkJsHeap(); |
211 | bool verify(MessageListType type, int expectedPosition, |
212 | const QQmlProfilerEventType &expected, quint32 checks, |
213 | const QVector<qint64> &expectedNumbers); |
214 | |
215 | QList<QQmlDebugClient *> createClients() override; |
216 | QScopedPointer<QQmlProfilerTestClient> m_client; |
217 | |
218 | private slots: |
219 | void cleanup() override; |
220 | |
221 | void connect_data(); |
222 | void connect(); |
223 | void pixmapCacheData(); |
224 | void scenegraphData(); |
225 | void profileOnExit(); |
226 | void controlFromJS(); |
227 | void signalSourceLocation(); |
228 | void javascript(); |
229 | void flushInterval(); |
230 | void translationBinding(); |
231 | void memory(); |
232 | void compile(); |
233 | void multiEngine(); |
234 | void batchOverflow(); |
235 | |
236 | private: |
237 | bool m_recordFromStart = true; |
238 | bool m_flushInterval = false; |
239 | bool m_isComplete = false; |
240 | |
241 | // Don't use ({...}) here as MSVC will interpret that as the "QVector(int size)" ctor. |
242 | const QVector<qint64> m_rangeStart = (QVector<qint64>() << RangeStart); |
243 | const QVector<qint64> m_rangeEnd = (QVector<qint64>() << RangeEnd); |
244 | }; |
245 | |
246 | #define VERIFY(type, position, expected, checks, numbers) \ |
247 | QVERIFY(verify(type, position, expected, checks, numbers)) |
248 | |
249 | QQmlDebugTest::ConnectResult tst_QQmlProfilerService::connectTo( |
250 | bool block, const QString &file, bool recordFromStart, uint flushInterval, |
251 | bool restrictServices, const QString &executable) |
252 | { |
253 | m_recordFromStart = recordFromStart; |
254 | m_flushInterval = flushInterval; |
255 | m_isComplete = false; |
256 | |
257 | // ### Still using qmlscene due to QTBUG-33377 |
258 | return QQmlDebugTest::connectTo( |
259 | executable, |
260 | services: restrictServices ? "CanvasFrameRate,EngineControl,DebugMessages" : QString(), |
261 | extraArgs: testFile(fileName: file), block); |
262 | } |
263 | |
264 | void tst_QQmlProfilerService::checkProcessTerminated() |
265 | { |
266 | // If the process ends before connect(), we get a non-success value from connect() |
267 | // That's not a problem as we will still receive the trace. We check that process has terminated |
268 | // cleanly here. |
269 | |
270 | // Wait for the process to finish by itself, if that hasn't happened already |
271 | QVERIFY(m_client); |
272 | QVERIFY(m_client->client); |
273 | QTRY_COMPARE(m_client->client->state(), QQmlDebugClient::NotConnected); |
274 | QVERIFY(m_process); |
275 | QVERIFY(m_process->exitStatus() != QProcess::CrashExit); |
276 | QTRY_COMPARE(m_process->exitStatus(), QProcess::NormalExit); |
277 | } |
278 | |
279 | void tst_QQmlProfilerService::checkTraceReceived() |
280 | { |
281 | QVERIFY(m_process->exitStatus() != QProcess::CrashExit); |
282 | QTRY_VERIFY2(m_isComplete, "No trace received in time." ); |
283 | |
284 | QVector<qint64> numbers; |
285 | |
286 | // must start with "StartTrace" |
287 | QQmlProfilerEventType expected(Event, MaximumRangeType, StartTrace); |
288 | VERIFY(MessageListAsynchronous, 0, expected, CheckMessageType | CheckDetailType, numbers); |
289 | |
290 | // must end with "EndTrace" |
291 | expected = QQmlProfilerEventType(Event, MaximumRangeType, EndTrace); |
292 | VERIFY(MessageListAsynchronous, m_client->asynchronousMessages.length() - 1, expected, |
293 | CheckMessageType | CheckDetailType, numbers); |
294 | } |
295 | |
296 | void tst_QQmlProfilerService::checkJsHeap() |
297 | { |
298 | QVERIFY(m_client); |
299 | QVERIFY2(m_client->jsHeapMessages.count() > 0, "no JavaScript heap messages received" ); |
300 | |
301 | bool seen_alloc = false; |
302 | bool seen_small = false; |
303 | bool seen_large = false; |
304 | qint64 allocated = 0; |
305 | qint64 used = 0; |
306 | qint64 lastTimestamp = -1; |
307 | foreach (const QQmlProfilerEvent &message, m_client->jsHeapMessages) { |
308 | const auto amount = message.number<qint64>(i: 0); |
309 | const QQmlProfilerEventType &type = m_client->types.at(i: message.typeIndex()); |
310 | switch (type.detailType()) { |
311 | case HeapPage: |
312 | allocated += amount; |
313 | seen_alloc = true; |
314 | break; |
315 | case SmallItem: |
316 | used += amount; |
317 | seen_small = true; |
318 | break; |
319 | case LargeItem: |
320 | allocated += amount; |
321 | used += amount; |
322 | seen_large = true; |
323 | break; |
324 | } |
325 | |
326 | QVERIFY(message.timestamp() >= lastTimestamp); |
327 | // The heap will only be consistent after all events of the same timestamp are processed. |
328 | if (lastTimestamp == -1) { |
329 | lastTimestamp = message.timestamp(); |
330 | continue; |
331 | } |
332 | |
333 | if (message.timestamp() == lastTimestamp) |
334 | continue; |
335 | |
336 | lastTimestamp = message.timestamp(); |
337 | |
338 | QVERIFY2(used >= 0, QString::fromLatin1("Negative memory usage seen: %1" ) |
339 | .arg(used).toUtf8().constData()); |
340 | |
341 | QVERIFY2(allocated >= 0, QString::fromLatin1("Negative memory allocation seen: %1" ) |
342 | .arg(allocated).toUtf8().constData()); |
343 | |
344 | QVERIFY2(used <= allocated, |
345 | QString::fromLatin1("More memory usage than allocation seen: %1 > %2" ) |
346 | .arg(used).arg(allocated).toUtf8().constData()); |
347 | } |
348 | |
349 | QVERIFY2(seen_alloc, "No heap allocation seen" ); |
350 | QVERIFY2(seen_small, "No small item seen" ); |
351 | QVERIFY2(seen_large, "No large item seen" ); |
352 | } |
353 | |
354 | bool tst_QQmlProfilerService::verify(tst_QQmlProfilerService::MessageListType type, |
355 | int expectedPosition, const QQmlProfilerEventType &expected, |
356 | quint32 checks, const QVector<qint64> &expectedNumbers) |
357 | { |
358 | if (!m_client) { |
359 | qWarning() << "No debug client available" ; |
360 | return false; |
361 | } |
362 | |
363 | const QVector<QQmlProfilerEvent> *target = nullptr; |
364 | switch (type) { |
365 | case MessageListQML: target = &(m_client->qmlMessages); break; |
366 | case MessageListJavaScript: target = &(m_client->javascriptMessages); break; |
367 | case MessageListJsHeap: target = &(m_client->jsHeapMessages); break; |
368 | case MessageListAsynchronous: target = &(m_client->asynchronousMessages); break; |
369 | case MessageListPixmap: target = &(m_client->pixmapMessages); break; |
370 | } |
371 | |
372 | if (!target) { |
373 | qWarning() << "Invalid MessageListType" << type; |
374 | return false; |
375 | } |
376 | |
377 | if (target->length() <= expectedPosition) { |
378 | qWarning() << "Not enough events. expected position:" << expectedPosition |
379 | << "length:" << target->length(); |
380 | return false; |
381 | } |
382 | |
383 | int position = expectedPosition; |
384 | qint64 timestamp = target->at(i: expectedPosition).timestamp(); |
385 | while (position > 0 && target->at(i: position - 1).timestamp() == timestamp) |
386 | --position; |
387 | |
388 | QStringList warnings; |
389 | |
390 | do { |
391 | const QQmlProfilerEvent &event = target->at(i: position); |
392 | const QQmlProfilerEventType &actual = m_client->types.at(i: event.typeIndex()); |
393 | if ((checks & CheckMessageType) && |
394 | (actual.message() != expected.message() |
395 | || actual.rangeType() != expected.rangeType())) { |
396 | warnings << QString::fromLatin1(str: "%1: unexpected messageType or rangeType. " |
397 | "actual: %2, %3 - expected: %4, %5" ) |
398 | .arg(a: position).arg(a: actual.message()).arg(a: actual.rangeType()) |
399 | .arg(a: expected.message()).arg(a: expected.rangeType()); |
400 | continue; |
401 | } |
402 | if ((checks & CheckDetailType) && actual.detailType() != expected.detailType()) { |
403 | warnings << QString::fromLatin1(str: "%1: unexpected detailType. actual: %2 - expected: %3" ) |
404 | .arg(a: position).arg(a: actual.detailType()).arg(a: expected.detailType()); |
405 | continue; |
406 | } |
407 | |
408 | const QQmlProfilerEventLocation expectedLocation = expected.location(); |
409 | const QQmlProfilerEventLocation actualLocation = actual.location(); |
410 | |
411 | if ((checks & CheckLine) && actualLocation.line() != expectedLocation.line()) { |
412 | warnings << QString::fromLatin1(str: "%1: unexpected line. actual: %2 - expected: %3" ) |
413 | .arg(a: position).arg(a: actualLocation.line()) |
414 | .arg(a: expectedLocation.line()); |
415 | continue; |
416 | } |
417 | if ((checks & CheckColumn) && actualLocation.column() != expectedLocation.column()) { |
418 | warnings << QString::fromLatin1(str: "%1: unexpected column. actual: %2 - expected: %3" ) |
419 | .arg(a: position).arg(a: actualLocation.column()) |
420 | .arg(a: expectedLocation.column()); |
421 | continue; |
422 | } |
423 | if ((checks & CheckFileEndsWith) && |
424 | !actualLocation.filename().endsWith(s: expectedLocation.filename())) { |
425 | warnings << QString::fromLatin1(str: "%1: unexpected fileName. actual: %2 - expected: %3" ) |
426 | .arg(a: position).arg(a: actualLocation.filename()) |
427 | .arg(a: expectedLocation.filename()); |
428 | continue; |
429 | } |
430 | |
431 | if ((checks & CheckDataEndsWith) && !actual.data().endsWith(s: expected.data())) { |
432 | warnings << QString::fromLatin1(str: "%1: unexpected detailData. actual: %2 - expected: %3" ) |
433 | .arg(a: position).arg(a: actual.data()).arg(a: expected.data()); |
434 | continue; |
435 | } |
436 | |
437 | if (checks & CheckNumbers) { |
438 | const QVector<qint64> actualNumbers = event.numbers<QVector<qint64>>(); |
439 | if (actualNumbers != expectedNumbers) { |
440 | |
441 | QStringList expectedList; |
442 | for (qint64 number : expectedNumbers) |
443 | expectedList.append(t: QString::number(number)); |
444 | QStringList actualList; |
445 | for (qint64 number : actualNumbers) |
446 | actualList.append(t: QString::number(number)); |
447 | |
448 | warnings << QString::fromLatin1( |
449 | str: "%1: unexpected numbers. actual [%2] - expected: [%3]" ) |
450 | .arg(a: position) |
451 | .arg(a: actualList.join(sep: QLatin1String(", " ))) |
452 | .arg(a: expectedList.join(sep: QLatin1String(", " ))); |
453 | continue; |
454 | } |
455 | } |
456 | |
457 | return true; |
458 | } while (++position < target->length() && target->at(i: position).timestamp() == timestamp); |
459 | |
460 | foreach (const QString &message, warnings) |
461 | qWarning() << message.toLocal8Bit().constData(); |
462 | |
463 | return false; |
464 | } |
465 | |
466 | QList<QQmlDebugClient *> tst_QQmlProfilerService::createClients() |
467 | { |
468 | m_client.reset(other: new QQmlProfilerTestClient(m_connection)); |
469 | m_client->client->setRecording(m_recordFromStart); |
470 | m_client->client->setFlushInterval(m_flushInterval); |
471 | QObject::connect(sender: m_client->client.data(), signal: &QQmlProfilerClient::complete, |
472 | context: this, slot: [this](){ m_isComplete = true; }); |
473 | return QList<QQmlDebugClient *>({m_client->client}); |
474 | } |
475 | |
476 | void tst_QQmlProfilerService::cleanup() |
477 | { |
478 | auto log = [this](const QQmlProfilerEvent &data, int i) { |
479 | const QQmlProfilerEventType &type = m_client->types.at(i: data.typeIndex()); |
480 | const QQmlProfilerEventLocation location = type.location(); |
481 | qDebug() << i << data.timestamp() << type.message() << type.rangeType() << type.detailType() |
482 | << location.filename() << location.line() << location.column() |
483 | << data.numbers<QVector<qint64>>(); |
484 | }; |
485 | |
486 | if (m_client && QTest::currentTestFailed()) { |
487 | qDebug() << "QML Messages:" << m_client->qmlMessages.count(); |
488 | int i = 0; |
489 | for (const QQmlProfilerEvent &data : qAsConst(t&: m_client->qmlMessages)) |
490 | log(data, i++); |
491 | |
492 | qDebug() << " " ; |
493 | qDebug() << "JavaScript Messages:" << m_client->javascriptMessages.count(); |
494 | i = 0; |
495 | |
496 | for (const QQmlProfilerEvent &data : qAsConst(t&: m_client->javascriptMessages)) |
497 | log(data, i++); |
498 | |
499 | qDebug() << " " ; |
500 | qDebug() << "Asynchronous Messages:" << m_client->asynchronousMessages.count(); |
501 | i = 0; |
502 | for (const QQmlProfilerEvent &data : qAsConst(t&: m_client->asynchronousMessages)) |
503 | log(data, i++); |
504 | |
505 | qDebug() << " " ; |
506 | qDebug() << "Pixmap Cache Messages:" << m_client->pixmapMessages.count(); |
507 | i = 0; |
508 | for (const QQmlProfilerEvent &data : qAsConst(t&: m_client->pixmapMessages)) |
509 | log(data, i++); |
510 | |
511 | qDebug() << " " ; |
512 | qDebug() << "Javascript Heap Messages:" << m_client->jsHeapMessages.count(); |
513 | i = 0; |
514 | for (const QQmlProfilerEvent &data : qAsConst(t&: m_client->jsHeapMessages)) |
515 | log(data, i++); |
516 | |
517 | qDebug() << " " ; |
518 | } |
519 | |
520 | m_client.reset(); |
521 | QQmlDebugTest::cleanup(); |
522 | } |
523 | |
524 | void tst_QQmlProfilerService::connect_data() |
525 | { |
526 | QTest::addColumn<bool>(name: "blockMode" ); |
527 | QTest::addColumn<bool>(name: "restrictMode" ); |
528 | QTest::addColumn<bool>(name: "traceEnabled" ); |
529 | QTest::newRow(dataTag: "normal/unrestricted/disabled" ) << false << false << false; |
530 | QTest::newRow(dataTag: "block/unrestricted/disabled" ) << true << false << false; |
531 | QTest::newRow(dataTag: "normal/restricted/disabled" ) << false << true << false; |
532 | QTest::newRow(dataTag: "block/restricted/disabled" ) << true << true << false; |
533 | QTest::newRow(dataTag: "normal/unrestricted/enabled" ) << false << false << true; |
534 | QTest::newRow(dataTag: "block/unrestricted/enabled" ) << true << false << true; |
535 | QTest::newRow(dataTag: "normal/restricted/enabled" ) << false << true << true; |
536 | QTest::newRow(dataTag: "block/restricted/enabled" ) << true << true << true; |
537 | } |
538 | |
539 | void tst_QQmlProfilerService::connect() |
540 | { |
541 | QFETCH(bool, blockMode); |
542 | QFETCH(bool, restrictMode); |
543 | QFETCH(bool, traceEnabled); |
544 | |
545 | QCOMPARE(connectTo(blockMode, "test.qml" , traceEnabled, 0, restrictMode), ConnectSuccess); |
546 | |
547 | if (!traceEnabled) |
548 | m_client->client->setRecording(true); |
549 | |
550 | QTRY_VERIFY(m_client->numLoadedEventTypes() > 0); |
551 | m_client->client->setRecording(false); |
552 | checkTraceReceived(); |
553 | checkJsHeap(); |
554 | } |
555 | |
556 | void tst_QQmlProfilerService::pixmapCacheData() |
557 | { |
558 | |
559 | QCOMPARE(connectTo(true, "pixmapCacheTest.qml" ), ConnectSuccess); |
560 | |
561 | // Don't wait for readyReadStandardOutput before the loop. It may have already arrived. |
562 | while (m_process->output().indexOf(s: QLatin1String("image loaded" )) == -1 && |
563 | m_process->output().indexOf(s: QLatin1String("image error" )) == -1) |
564 | QVERIFY(QQmlDebugTest::waitForSignal(m_process, SIGNAL(readyReadStandardOutput()))); |
565 | |
566 | m_client->client->setRecording(false); |
567 | |
568 | checkTraceReceived(); |
569 | checkJsHeap(); |
570 | |
571 | auto createType = [](PixmapEventType type) { |
572 | return QQmlProfilerEventType(PixmapCacheEvent, MaximumRangeType, type); |
573 | }; |
574 | |
575 | QVector<qint64> numbers; |
576 | |
577 | // image starting to load |
578 | VERIFY(MessageListPixmap, 0, createType(PixmapLoadingStarted), |
579 | CheckMessageType | CheckDetailType, numbers); |
580 | |
581 | // image size |
582 | numbers = QVector<qint64>({2, 2, 1}); |
583 | VERIFY(MessageListPixmap, 1, createType(PixmapSizeKnown), |
584 | CheckMessageType | CheckDetailType | CheckNumbers, numbers); |
585 | |
586 | // image loaded |
587 | VERIFY(MessageListPixmap, 2, createType(PixmapLoadingFinished), |
588 | CheckMessageType | CheckDetailType, numbers); |
589 | |
590 | // cache size |
591 | VERIFY(MessageListPixmap, 3, createType(PixmapCacheCountChanged), |
592 | CheckMessageType | CheckDetailType, numbers); |
593 | } |
594 | |
595 | void tst_QQmlProfilerService::scenegraphData() |
596 | { |
597 | QCOMPARE(connectTo(true, "scenegraphTest.qml" ), ConnectSuccess); |
598 | |
599 | while (!m_process->output().contains(s: QLatin1String("tick" ))) |
600 | QVERIFY(QQmlDebugTest::waitForSignal(m_process, SIGNAL(readyReadStandardOutput()))); |
601 | m_client->client->setRecording(false); |
602 | |
603 | checkTraceReceived(); |
604 | checkJsHeap(); |
605 | |
606 | // Check that at least one frame was rendered. |
607 | // There should be a SGContextFrame + SGRendererFrame + SGRenderLoopFrame sequence, |
608 | // but we can't be sure to get the SGRenderLoopFrame in the threaded renderer. |
609 | // |
610 | // Since the rendering happens in a different thread, there could be other unrelated events |
611 | // interleaved. Also, events could carry the same time stamps and be sorted in an unexpected way |
612 | // if the clocks are acting up. |
613 | qint64 contextFrameTime = -1; |
614 | qint64 renderFrameTime = -1; |
615 | #if QT_CONFIG(opengl) //Software renderer doesn't have context frames |
616 | if (QGuiApplicationPrivate::platformIntegration()->hasCapability(cap: QPlatformIntegration::OpenGL)) { |
617 | foreach (const QQmlProfilerEvent &msg, m_client->asynchronousMessages) { |
618 | const QQmlProfilerEventType &type = m_client->types.at(i: msg.typeIndex()); |
619 | if (type.message() == SceneGraphFrame) { |
620 | if (type.detailType() == SceneGraphContextFrame) { |
621 | contextFrameTime = msg.timestamp(); |
622 | break; |
623 | } |
624 | } |
625 | } |
626 | |
627 | QVERIFY(contextFrameTime != -1); |
628 | } |
629 | #endif |
630 | foreach (const QQmlProfilerEvent &msg, m_client->asynchronousMessages) { |
631 | const QQmlProfilerEventType &type = m_client->types.at(i: msg.typeIndex()); |
632 | if (type.detailType() == SceneGraphRendererFrame) { |
633 | QVERIFY(msg.timestamp() >= contextFrameTime); |
634 | renderFrameTime = msg.timestamp(); |
635 | break; |
636 | } |
637 | } |
638 | |
639 | QVERIFY(renderFrameTime != -1); |
640 | |
641 | foreach (const QQmlProfilerEvent &msg, m_client->asynchronousMessages) { |
642 | const QQmlProfilerEventType &type = m_client->types.at(i: msg.typeIndex()); |
643 | if (type.detailType() == SceneGraphRenderLoopFrame) { |
644 | if (msg.timestamp() >= contextFrameTime) { |
645 | // Make sure SceneGraphRenderLoopFrame is not between SceneGraphContextFrame and |
646 | // SceneGraphRendererFrame. A SceneGraphRenderLoopFrame before everything else is |
647 | // OK as the scene graph might decide to do an initial rendering. |
648 | QVERIFY(msg.timestamp() >= renderFrameTime); |
649 | break; |
650 | } |
651 | } |
652 | } |
653 | } |
654 | |
655 | void tst_QQmlProfilerService::profileOnExit() |
656 | { |
657 | QCOMPARE(connectTo(true, "exit.qml" ), ConnectSuccess); |
658 | checkProcessTerminated(); |
659 | |
660 | checkTraceReceived(); |
661 | checkJsHeap(); |
662 | } |
663 | |
664 | void tst_QQmlProfilerService::controlFromJS() |
665 | { |
666 | QCOMPARE(connectTo(true, "controlFromJS.qml" , false), ConnectSuccess); |
667 | |
668 | QTRY_VERIFY(m_client->numLoadedEventTypes() > 0); |
669 | m_client->client->setRecording(false); |
670 | checkTraceReceived(); |
671 | checkJsHeap(); |
672 | } |
673 | |
674 | void tst_QQmlProfilerService::signalSourceLocation() |
675 | { |
676 | QCOMPARE(connectTo(true, "signalSourceLocation.qml" ), ConnectSuccess); |
677 | |
678 | while (!(m_process->output().contains(s: QLatin1String("500" )))) |
679 | QVERIFY(QQmlDebugTest::waitForSignal(m_process, SIGNAL(readyReadStandardOutput()))); |
680 | m_client->client->setRecording(false); |
681 | checkTraceReceived(); |
682 | checkJsHeap(); |
683 | |
684 | auto createType = [](int line, int column) { |
685 | return QQmlProfilerEventType( |
686 | MaximumMessage, HandlingSignal, -1, |
687 | QQmlProfilerEventLocation(QLatin1String("signalSourceLocation.qml" ), line, |
688 | column)); |
689 | }; |
690 | |
691 | VERIFY(MessageListQML, 4, createType(8, 28), CheckType | CheckNumbers, m_rangeStart); |
692 | VERIFY(MessageListQML, 6, createType(7, 21), CheckType | CheckNumbers, m_rangeEnd); |
693 | } |
694 | |
695 | void tst_QQmlProfilerService::javascript() |
696 | { |
697 | QCOMPARE(connectTo(true, "javascript.qml" ), ConnectSuccess); |
698 | |
699 | while (!(m_process->output().contains(s: QLatin1String("done" )))) |
700 | QVERIFY(QQmlDebugTest::waitForSignal(m_process, SIGNAL(readyReadStandardOutput()))); |
701 | m_client->client->setRecording(false); |
702 | checkTraceReceived(); |
703 | checkJsHeap(); |
704 | |
705 | VERIFY(MessageListJavaScript, 2, QQmlProfilerEventType(MaximumMessage, Javascript), |
706 | CheckMessageType | CheckDetailType | CheckNumbers, m_rangeStart); |
707 | |
708 | VERIFY(MessageListJavaScript, 3, |
709 | QQmlProfilerEventType( |
710 | MaximumMessage, Javascript, -1, |
711 | QQmlProfilerEventLocation(QLatin1String("javascript.qml" ), 4, 5)), |
712 | CheckType | CheckNumbers, m_rangeStart); |
713 | |
714 | VERIFY(MessageListJavaScript, 4, QQmlProfilerEventType( |
715 | MaximumMessage, Javascript, -1, |
716 | QQmlProfilerEventLocation(), QLatin1String("something" )), |
717 | CheckMessageType | CheckDetailType | CheckDataEndsWith | CheckNumbers, m_rangeStart); |
718 | |
719 | VERIFY(MessageListJavaScript, 10, QQmlProfilerEventType(MaximumMessage, Javascript), |
720 | CheckMessageType | CheckDetailType | CheckNumbers, m_rangeEnd); |
721 | } |
722 | |
723 | void tst_QQmlProfilerService::flushInterval() |
724 | { |
725 | QCOMPARE(connectTo(true, "timer.qml" , true, 1), ConnectSuccess); |
726 | |
727 | // Make sure we get multiple messages |
728 | QTRY_VERIFY(m_client->qmlMessages.length() > 0); |
729 | QVERIFY(m_client->qmlMessages.length() < 100); |
730 | QTRY_VERIFY(m_client->qmlMessages.length() > 100); |
731 | |
732 | m_client->client->setRecording(false); |
733 | checkTraceReceived(); |
734 | checkJsHeap(); |
735 | } |
736 | |
737 | void tst_QQmlProfilerService::translationBinding() |
738 | { |
739 | QCOMPARE(connectTo(true, "qstr.qml" ), ConnectSuccess); |
740 | checkProcessTerminated(); |
741 | |
742 | checkTraceReceived(); |
743 | checkJsHeap(); |
744 | |
745 | const QQmlProfilerEventType type(MaximumMessage, Binding); |
746 | |
747 | VERIFY(MessageListQML, 4, type, CheckDetailType | CheckMessageType | CheckNumbers, |
748 | m_rangeStart); |
749 | VERIFY(MessageListQML, 5, type, CheckDetailType | CheckMessageType | CheckNumbers, |
750 | m_rangeEnd); |
751 | } |
752 | |
753 | void tst_QQmlProfilerService::memory() |
754 | { |
755 | QCOMPARE(connectTo(true, "memory.qml" ), ConnectSuccess); |
756 | checkProcessTerminated(); |
757 | |
758 | checkTraceReceived(); |
759 | checkJsHeap(); |
760 | |
761 | QVERIFY(m_client); |
762 | int smallItems = 0; |
763 | for (const auto& message : m_client->jsHeapMessages) { |
764 | const QQmlProfilerEventType &type = m_client->types[message.typeIndex()]; |
765 | if (type.detailType() == SmallItem) |
766 | ++smallItems; |
767 | } |
768 | |
769 | QVERIFY(smallItems > 5); |
770 | } |
771 | |
772 | static bool hasCompileEvents(const QVector<QQmlProfilerEventType> &types) |
773 | { |
774 | for (const QQmlProfilerEventType &type : types) { |
775 | if (type.message() == MaximumMessage && type.rangeType() == Compiling) |
776 | return true; |
777 | } |
778 | return false; |
779 | } |
780 | |
781 | void tst_QQmlProfilerService::compile() |
782 | { |
783 | // Flush interval so that we actually get the events before we stop recording. |
784 | connectTo(block: true, file: "test.qml" , recordFromStart: true, flushInterval: 100); |
785 | |
786 | QVERIFY(m_client); |
787 | |
788 | // We need to check specifically for compile events as we can otherwise stop recording after the |
789 | // StartTrace has arrived, but before it compiles anything. |
790 | QTRY_VERIFY(hasCompileEvents(m_client->types)); |
791 | m_client->client->setRecording(false); |
792 | |
793 | checkTraceReceived(); |
794 | checkJsHeap(); |
795 | |
796 | Message rangeStage = MaximumMessage; |
797 | for (const auto& message : m_client->qmlMessages) { |
798 | const QQmlProfilerEventType &type = m_client->types[message.typeIndex()]; |
799 | if (type.rangeType() == Compiling) { |
800 | switch (rangeStage) { |
801 | case MaximumMessage: |
802 | QCOMPARE(message.rangeStage(), RangeStart); |
803 | break; |
804 | case RangeStart: |
805 | QCOMPARE(message.rangeStage(), RangeEnd); |
806 | break; |
807 | default: |
808 | QFAIL("Wrong range stage" ); |
809 | } |
810 | rangeStage = message.rangeStage(); |
811 | QCOMPARE(type.message(), MaximumMessage); |
812 | QCOMPARE(type.location().filename(), testFileUrl("test.qml" ).toString()); |
813 | QCOMPARE(type.location().line(), 0); |
814 | QCOMPARE(type.location().column(), 0); |
815 | } |
816 | } |
817 | |
818 | QCOMPARE(rangeStage, RangeEnd); |
819 | } |
820 | |
821 | void tst_QQmlProfilerService::multiEngine() |
822 | { |
823 | QCOMPARE(connectTo(true, "quit.qml" , true, 0, false, debugJsServerPath("qqmlprofilerservice" )), |
824 | ConnectSuccess); |
825 | |
826 | QSignalSpy spy(m_client->client, SIGNAL(complete(qint64))); |
827 | |
828 | checkTraceReceived(); |
829 | checkJsHeap(); |
830 | |
831 | QTRY_COMPARE(m_process->state(), QProcess::NotRunning); |
832 | QCOMPARE(m_process->exitStatus(), QProcess::NormalExit); |
833 | |
834 | QCOMPARE(spy.count(), 1); |
835 | } |
836 | |
837 | void tst_QQmlProfilerService::batchOverflow() |
838 | { |
839 | // The trace client checks that the events are received in order. |
840 | QCOMPARE(connectTo(true, "batchOverflow.qml" ), ConnectSuccess); |
841 | checkProcessTerminated(); |
842 | checkTraceReceived(); |
843 | checkJsHeap(); |
844 | } |
845 | |
846 | QTEST_MAIN(tst_QQmlProfilerService) |
847 | |
848 | #include "tst_qqmlprofilerservice.moc" |
849 | |