| /**************************************************************************** |
| ** |
| ** Copyright (C) 2017-2015 Ford Motor Company |
| ** Contact: https://www.qt.io/licensing/ |
| ** |
| ** This file is part of the QtRemoteObjects module of the Qt Toolkit. |
| ** |
| ** $QT_BEGIN_LICENSE:GPL-EXCEPT$ |
| ** 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 The Qt Company. For licensing terms |
| ** and conditions see https://www.qt.io/terms-conditions. For further |
| ** information use the contact form at https://www.qt.io/contact-us. |
| ** |
| ** GNU General Public License Usage |
| ** Alternatively, this file may be used under the terms of the GNU |
| ** General Public License version 3 as published by the Free Software |
| ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT |
| ** included in the packaging of this file. Please review the following |
| ** information to ensure the GNU General Public License requirements will |
| ** be met: https://www.gnu.org/licenses/gpl-3.0.html. |
| ** |
| ** $QT_END_LICENSE$ |
| ** |
| ****************************************************************************/ |
| |
| #include <QString> |
| #include <QDataStream> |
| #include <QLocalSocket> |
| #include <QLocalServer> |
| #include <QtTest> |
| #include <QtRemoteObjects/QAbstractItemModelReplica> |
| #include <QtRemoteObjects/QRemoteObjectNode> |
| #include "rep_localdatacenter_replica.h" |
| #include "rep_localdatacenter_source.h" |
| |
| class BenchmarksModel : public QAbstractListModel |
| { |
| // QAbstractItemModel interface |
| public: |
| int rowCount(const QModelIndex &parent) const override; |
| QVariant data(const QModelIndex &index, int role) const override; |
| QHash<int, QByteArray> roleNames() const override; |
| }; |
| |
| int BenchmarksModel::rowCount(const QModelIndex &parent) const |
| { |
| Q_UNUSED(parent); |
| return 100000; |
| } |
| |
| QVariant BenchmarksModel::data(const QModelIndex &index, int role) const |
| { |
| switch (role) { |
| case Qt::DisplayRole: |
| return QStringLiteral("Benchmark data %1").arg(index.row()); |
| case Qt::BackgroundRole: |
| return index.row() % 2 ? QStringLiteral("red") : QStringLiteral("green"); |
| } |
| return QVariant(); |
| } |
| |
| QHash<int, QByteArray> BenchmarksModel::roleNames() const |
| { |
| static QHash<int,QByteArray> roleNames = { |
| {Qt::DisplayRole, "_text"}, |
| {Qt::BackgroundRole, "_color"} |
| }; |
| return roleNames; |
| } |
| |
| class BenchmarksTest : public QObject |
| { |
| Q_OBJECT |
| |
| public: |
| BenchmarksTest(); |
| private: |
| QRemoteObjectHost m_basicServer; |
| QRemoteObjectNode m_basicClient; |
| QScopedPointer<LocalDataCenterSimpleSource> dataCenterLocal; |
| BenchmarksModel m_sourceModel; |
| |
| private Q_SLOTS: |
| void initTestCase(); |
| void benchPropertyChangesInt(); |
| void benchQDataStreamInt(); |
| void benchQLocalSocketInt(); |
| void benchQLocalSocketQDataStreamInt(); |
| void benchModelLinearAccess(); |
| void benchModelRandomAccess(); |
| }; |
| |
| BenchmarksTest::BenchmarksTest() |
| { |
| } |
| |
| void BenchmarksTest::initTestCase() { |
| m_basicServer.setHostUrl(QUrl(QStringLiteral("local:benchmark_replica"))); |
| dataCenterLocal.reset(new LocalDataCenterSimpleSource); |
| dataCenterLocal->setData1(5); |
| const bool remoted = m_basicServer.enableRemoting(dataCenterLocal.data()); |
| Q_ASSERT(remoted); |
| Q_UNUSED(remoted); |
| |
| m_basicClient.connectToNode(QUrl(QStringLiteral("local:benchmark_replica"))); |
| Q_ASSERT(m_basicClient.lastError() == QRemoteObjectNode::NoError); |
| |
| m_basicServer.enableRemoting(&m_sourceModel, QStringLiteral("BenchmarkRemoteModel"), |
| m_sourceModel.roleNames().keys().toVector()); |
| } |
| |
| void BenchmarksTest::benchPropertyChangesInt() |
| { |
| QScopedPointer<LocalDataCenterReplica> center; |
| center.reset(m_basicClient.acquire<LocalDataCenterReplica>()); |
| if (!center->isInitialized()) { |
| QEventLoop loop; |
| connect(center.data(), &LocalDataCenterReplica::initialized, &loop, &QEventLoop::quit); |
| loop.exec(); |
| } |
| QEventLoop loop; |
| int lastValue = 0; |
| connect(center.data(), &LocalDataCenterReplica::data1Changed, [&lastValue, ¢er, &loop]() { |
| const bool res = (lastValue++ == center->data1()); |
| Q_ASSERT(res); |
| Q_UNUSED(res); |
| if (lastValue == 50000) |
| loop.quit(); |
| }); |
| QBENCHMARK { |
| for (int i = 0; i < 50000; ++i) { |
| dataCenterLocal->setData1(i); |
| } |
| loop.exec(); |
| } |
| } |
| // This ONLY tests the optimal case of a non resizing QByteArray |
| void BenchmarksTest::benchQDataStreamInt() |
| { |
| QByteArray buffer; |
| QDataStream stream(&buffer, QIODevice::WriteOnly); |
| QDataStream rStream(&buffer, QIODevice::ReadOnly); |
| int readout = 0; |
| QBENCHMARK { |
| for (int i = 0; i < 50000; ++i) { |
| stream << i; |
| } |
| for (int i = 0; i < 50000; ++i) { |
| rStream >> readout; |
| Q_ASSERT(i == readout); |
| } |
| } |
| } |
| |
| void BenchmarksTest::benchQLocalSocketInt() |
| { |
| const QString socketName = QStringLiteral("benchLocalSocket"); |
| QLocalServer server; |
| QLocalServer::removeServer(socketName); |
| server.listen(socketName); |
| QLocalSocket client; |
| client.connectToServer(socketName); |
| QEventLoop loop; |
| QScopedPointer<QLocalSocket> serverSock; |
| if (!server.hasPendingConnections()) { |
| connect(&server, &QLocalServer::newConnection, &loop, &QEventLoop::quit); |
| loop.exec(); |
| } |
| Q_ASSERT(server.hasPendingConnections()); |
| serverSock.reset(server.nextPendingConnection()); |
| int lastValue = 0; |
| connect(&client, &QLocalSocket::readyRead, [&loop, &lastValue, &client]() { |
| int readout = 0; |
| while (client.bytesAvailable() && lastValue < 50000) { |
| client.read(reinterpret_cast<char*>(&readout), sizeof(int)); |
| const bool res = (lastValue++ == readout); |
| Q_ASSERT(res); |
| Q_UNUSED(res); |
| } |
| if (lastValue >= 50000) |
| loop.quit(); |
| }); |
| QBENCHMARK { |
| for (int i = 0; i < 50000; ++i) { |
| const int res = serverSock->write(reinterpret_cast<char*>(&i), sizeof(int)); |
| Q_ASSERT(res == sizeof(int)); |
| Q_UNUSED(res); |
| } |
| loop.exec(); |
| } |
| |
| #ifdef Q_OS_WIN |
| // Work-around QTBUG-38185: immediately close socket |
| client.abort(); |
| #endif |
| } |
| |
| void BenchmarksTest::benchQLocalSocketQDataStreamInt() |
| { |
| const QString socketName = QStringLiteral("benchLocalSocket"); |
| QLocalServer server; |
| QLocalServer::removeServer(socketName); |
| server.listen(socketName); |
| QLocalSocket client; |
| client.connectToServer(socketName); |
| QDataStream readStream(&client); |
| QEventLoop loop; |
| QScopedPointer<QLocalSocket> serverSock; |
| if (!server.hasPendingConnections()) { |
| connect(&server, &QLocalServer::newConnection, &loop, &QEventLoop::quit); |
| loop.exec(); |
| } |
| Q_ASSERT(server.hasPendingConnections()); |
| serverSock.reset(server.nextPendingConnection()); |
| QDataStream writeStream(serverSock.data()); |
| int lastValue = 0; |
| connect(&client, &QIODevice::readyRead, [&loop, &lastValue, &readStream]() { |
| int readout = 0; |
| while (readStream.device()->bytesAvailable() && lastValue < 50000) { |
| readStream >> readout; |
| const bool res = (lastValue++ == readout); |
| Q_ASSERT(res); |
| Q_UNUSED(res); |
| } |
| if (lastValue >= 50000) |
| loop.quit(); |
| }); |
| QBENCHMARK { |
| for (int i = 0; i < 50000; ++i) { |
| writeStream << i; |
| } |
| loop.exec(); |
| } |
| |
| #ifdef Q_OS_WIN |
| // Work-around QTBUG-38185: immediately close socket |
| client.abort(); |
| #endif |
| } |
| |
| void BenchmarksTest::benchModelLinearAccess() |
| { |
| // Simulate an user browse through item them stops. |
| // We're measuring the time needed needed to deliver the visible chunk of data |
| // which are the last 50 items |
| QBENCHMARK { |
| QRemoteObjectNode localClient; |
| localClient.connectToNode(QUrl(QStringLiteral("local:benchmark_replica"))); |
| QScopedPointer<QAbstractItemModelReplica> model(localClient.acquireModel(QStringLiteral("BenchmarkRemoteModel"))); |
| QEventLoop loop; |
| QHash<int, QPair<QString, QString>> dataToWait; |
| connect(model.data(), &QAbstractItemModelReplica::dataChanged, [&model, &loop, &dataToWait](const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector<int> &roles) { |
| for (int row = topLeft.row(); row <= bottomRight.row(); ++row) { |
| // we're assuming that the view will try use the sent data, |
| // therefore we're not optimizing the code |
| auto it = dataToWait.find(row); |
| if (it == dataToWait.end()) { |
| // simulate some work with the received data |
| QThread::usleep(10); |
| continue; |
| } |
| for (int role : roles) { |
| QVariant data = model->data(model->index(row, 0), role); |
| switch (role) { |
| case Qt::DisplayRole: |
| it->first = data.toString(); |
| break; |
| case Qt::BackgroundRole: |
| it->second = data.toString(); |
| break; |
| } |
| } |
| |
| if (it->first == QStringLiteral("Benchmark data %1").arg(row) && |
| it->second == (row % 2 ? QStringLiteral("red") : QStringLiteral("green"))) { |
| dataToWait.erase(it); |
| if (dataToWait.isEmpty()) |
| break; |
| } |
| } |
| if (dataToWait.isEmpty()) |
| loop.quit(); |
| }); |
| |
| auto beginBenchmark = [&model, &dataToWait] { |
| for (int row = 0; row < 1000; ++row) { |
| if (row >= 950) |
| dataToWait.insert(row, QPair<QString, QString>()); |
| model->data(model->index(row, 0), Qt::DisplayRole); |
| model->data(model->index(row, 0), Qt::BackgroundRole); |
| |
| // Views (e.g. QTreeView) are accessing other roles |
| model->data(model->index(row, 0), Qt::FontRole); |
| model->data(model->index(row, 0), Qt::DecorationRole); |
| model->data(model->index(row, 0), Qt::SizeHintRole); |
| } |
| |
| }; |
| connect(model.data(), &QAbstractItemModelReplica::initialized, [&model, &loop, &beginBenchmark] { |
| if (model->isInitialized()) { |
| beginBenchmark(); |
| } else { |
| Q_ASSERT(false); |
| loop.quit(); |
| } |
| }); |
| if (model->isInitialized()) |
| beginBenchmark(); |
| |
| QTimer::singleShot(5000, &loop, &QEventLoop::quit); |
| loop.exec(); |
| QVERIFY(dataToWait.isEmpty()); |
| } |
| } |
| |
| void BenchmarksTest::benchModelRandomAccess() |
| { |
| QBENCHMARK { |
| QRemoteObjectNode localClient; |
| localClient.connectToNode(QUrl(QStringLiteral("local:benchmark_replica"))); |
| QScopedPointer<QAbstractItemModelReplica> model(localClient.acquireModel(QStringLiteral("BenchmarkRemoteModel"))); |
| model->setRootCacheSize(5000); // we need to make room for all 5000 rows that we'll use |
| QEventLoop loop; |
| QHash<int, QPair<QString, QString>> dataToWait; |
| connect(model.data(), &QAbstractItemModelReplica::dataChanged, [&model, &loop, &dataToWait](const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector<int> &roles) { |
| for (int row = topLeft.row(); row <= bottomRight.row(); ++row) { |
| // we're assuming that the view will try use the sent data, |
| // therefore we're not optimizing the code |
| auto it = dataToWait.find(row); |
| if (it == dataToWait.end()) { |
| QThread::yieldCurrentThread(); // instead to wait |
| continue; |
| } |
| for (int role : roles) { |
| QVariant data = model->data(model->index(row, 0), role); |
| switch (role) { |
| case Qt::DisplayRole: |
| it->first = data.toString(); |
| break; |
| case Qt::BackgroundRole: |
| it->second = data.toString(); |
| break; |
| } |
| } |
| |
| if (it->first == QStringLiteral("Benchmark data %1").arg(row) && |
| it->second == (row % 2 ? QStringLiteral("red") : QStringLiteral("green"))) { |
| dataToWait.erase(it); |
| if (dataToWait.isEmpty()) |
| break; |
| } |
| } |
| if (dataToWait.isEmpty()) |
| loop.quit(); |
| }); |
| |
| auto beginBenchmark = [&model, &dataToWait] { |
| for (int chunck = 0; chunck < 100; ++chunck) { |
| int row = chunck * 950; |
| for (int r = 0; r < 50; ++r) { |
| dataToWait.insert(r + row, QPair<QString, QString>()); |
| model->data(model->index(r + row, 0), Qt::DisplayRole); |
| model->data(model->index(r + row, 0), Qt::BackgroundRole); |
| |
| // Views (e.g. QTreeView) are accessing other roles |
| model->data(model->index(r + row, 0), Qt::FontRole); |
| model->data(model->index(r + row, 0), Qt::DecorationRole); |
| model->data(model->index(r + row, 0), Qt::SizeHintRole); |
| } |
| } |
| |
| }; |
| connect(model.data(), &QAbstractItemModelReplica::initialized, [&model, &loop, &beginBenchmark] { |
| if (model->isInitialized()) { |
| beginBenchmark(); |
| } else { |
| Q_ASSERT(false); |
| loop.quit(); |
| } |
| }); |
| if (model->isInitialized()) |
| beginBenchmark(); |
| |
| QTimer::singleShot(5000, &loop, &QEventLoop::quit); |
| loop.exec(); |
| QVERIFY(dataToWait.isEmpty()); |
| } |
| } |
| |
| QTEST_MAIN(BenchmarksTest) |
| |
| #include "tst_benchmarkstest.moc" |