tests/benchmarks/qnetworkreply/tst_qnetworkreply.cpp
changeset 3 41300fa6a67c
parent 0 1918ee327afb
child 4 3b1da2848fc7
equal deleted inserted replaced
2:56cd8111b7f7 3:41300fa6a67c
    44 #include <qtest.h>
    44 #include <qtest.h>
    45 #include <QtTest/QtTest>
    45 #include <QtTest/QtTest>
    46 #include <QtNetwork/qnetworkreply.h>
    46 #include <QtNetwork/qnetworkreply.h>
    47 #include <QtNetwork/qnetworkrequest.h>
    47 #include <QtNetwork/qnetworkrequest.h>
    48 #include <QtNetwork/qnetworkaccessmanager.h>
    48 #include <QtNetwork/qnetworkaccessmanager.h>
       
    49 #include <QtNetwork/qtcpsocket.h>
       
    50 #include <QtNetwork/qtcpserver.h>
    49 #include "../../auto/network-settings.h"
    51 #include "../../auto/network-settings.h"
    50 
    52 
       
    53 
       
    54 class TimedSender: public QThread
       
    55 {
       
    56     Q_OBJECT
       
    57     qint64 totalBytes;
       
    58     QSemaphore ready;
       
    59     QByteArray dataToSend;
       
    60     QTcpSocket *client;
       
    61     int timeout;
       
    62     int port;
       
    63 public:
       
    64     int transferRate;
       
    65     TimedSender(int ms)
       
    66         : totalBytes(0), timeout(ms), port(-1), transferRate(-1)
       
    67     {
       
    68         dataToSend = QByteArray(16*1024, '@');
       
    69         start();
       
    70         ready.acquire();
       
    71     }
       
    72 
       
    73     inline int serverPort() const { return port; }
       
    74 
       
    75 private slots:
       
    76     void writeMore()
       
    77     {
       
    78         while (client->bytesToWrite() < 128 * 1024) {
       
    79             writePacket(dataToSend);
       
    80         }
       
    81     }
       
    82 
       
    83 protected:
       
    84     void run()
       
    85     {
       
    86         QTcpServer server;
       
    87         server.listen();
       
    88         port = server.serverPort();
       
    89         ready.release();
       
    90 
       
    91         server.waitForNewConnection(-1);
       
    92         client = server.nextPendingConnection();
       
    93 
       
    94         writeMore();
       
    95         connect(client, SIGNAL(bytesWritten(qint64)), SLOT(writeMore()), Qt::DirectConnection);
       
    96 
       
    97         QEventLoop eventLoop;
       
    98         QTimer::singleShot(timeout, &eventLoop, SLOT(quit()));
       
    99 
       
   100         QTime timer;
       
   101         timer.start();
       
   102         eventLoop.exec();
       
   103         disconnect(client, SIGNAL(bytesWritten(qint64)), this, 0);
       
   104 
       
   105         // wait for the connection to shut down
       
   106         client->disconnectFromHost();
       
   107         if (!client->waitForDisconnected(10000))
       
   108             return;
       
   109 
       
   110         transferRate = totalBytes * 1000 / timer.elapsed();
       
   111         qDebug() << "TimedSender::run" << "receive rate:" << (transferRate / 1024) << "kB/s in"
       
   112                  << timer.elapsed() << "ms";
       
   113     }
       
   114 
       
   115     void writePacket(const QByteArray &array)
       
   116     {
       
   117         client->write(array);
       
   118         totalBytes += array.size();
       
   119     }
       
   120 };
       
   121 
       
   122 
       
   123 class QNetworkReplyPtr: public QSharedPointer<QNetworkReply>
       
   124 {
       
   125 public:
       
   126     inline QNetworkReplyPtr(QNetworkReply *ptr = 0)
       
   127         : QSharedPointer<QNetworkReply>(ptr)
       
   128     { }
       
   129 
       
   130     inline operator QNetworkReply *() const { return data(); }
       
   131 };
       
   132 
       
   133 
       
   134 class DataReader: public QObject
       
   135 {
       
   136     Q_OBJECT
       
   137 public:
       
   138     qint64 totalBytes;
       
   139     QByteArray data;
       
   140     QIODevice *device;
       
   141     bool accumulate;
       
   142     DataReader(QIODevice *dev, bool acc = true) : totalBytes(0), device(dev), accumulate(acc)
       
   143     {
       
   144         connect(device, SIGNAL(readyRead()), SLOT(doRead()));
       
   145     }
       
   146 
       
   147 public slots:
       
   148     void doRead()
       
   149     {
       
   150         QByteArray buffer;
       
   151         buffer.resize(device->bytesAvailable());
       
   152         qint64 bytesRead = device->read(buffer.data(), device->bytesAvailable());
       
   153         if (bytesRead == -1) {
       
   154             QTestEventLoop::instance().exitLoop();
       
   155             return;
       
   156         }
       
   157         buffer.truncate(bytesRead);
       
   158         totalBytes += bytesRead;
       
   159 
       
   160         if (accumulate)
       
   161             data += buffer;
       
   162     }
       
   163 };
       
   164 
       
   165 class ThreadedDataReader: public QThread
       
   166 {
       
   167     Q_OBJECT
       
   168     // used to make the constructor only return after the tcp server started listening
       
   169     QSemaphore ready;
       
   170     QTcpSocket *client;
       
   171     int timeout;
       
   172     int port;
       
   173 public:
       
   174     qint64 transferRate;
       
   175     ThreadedDataReader()
       
   176         : port(-1), transferRate(-1)
       
   177     {
       
   178         start();
       
   179         ready.acquire();
       
   180     }
       
   181 
       
   182     inline int serverPort() const { return port; }
       
   183 
       
   184 protected:
       
   185     void run()
       
   186     {
       
   187         QTcpServer server;
       
   188         server.listen();
       
   189         port = server.serverPort();
       
   190         ready.release();
       
   191 
       
   192         server.waitForNewConnection(-1);
       
   193         client = server.nextPendingConnection();
       
   194 
       
   195         QEventLoop eventLoop;
       
   196         DataReader reader(client, false);
       
   197         QObject::connect(client, SIGNAL(disconnected()), &eventLoop, SLOT(quit()));
       
   198 
       
   199         QTime timer;
       
   200         timer.start();
       
   201         eventLoop.exec();
       
   202         qint64 elapsed = timer.elapsed();
       
   203 
       
   204         transferRate = reader.totalBytes * 1000 / elapsed;
       
   205         qDebug() << "ThreadedDataReader::run" << "send rate:" << (transferRate / 1024) << "kB/s in" << elapsed << "msec";
       
   206     }
       
   207 };
       
   208 
       
   209 class DataGenerator: public QIODevice
       
   210 {
       
   211     Q_OBJECT
       
   212     enum { Idle, Started, Stopped } state;
       
   213 public:
       
   214     DataGenerator() : state(Idle)
       
   215     { open(ReadOnly); }
       
   216 
       
   217     virtual bool isSequential() const { return true; }
       
   218     virtual qint64 bytesAvailable() const { return state == Started ? 1024*1024 : 0; }
       
   219 
       
   220 public slots:
       
   221     void start() { state = Started; emit readyRead(); }
       
   222     void stop() { state = Stopped; emit readyRead(); }
       
   223 
       
   224 protected:
       
   225     virtual qint64 readData(char *data, qint64 maxlen)
       
   226     {
       
   227         if (state == Stopped)
       
   228             return -1;          // EOF
       
   229 
       
   230         // return as many bytes as are wanted
       
   231         memset(data, '@', maxlen);
       
   232         return maxlen;
       
   233     }
       
   234     virtual qint64 writeData(const char *, qint64)
       
   235     { return -1; }
       
   236 };
       
   237 
       
   238 class ThreadedDataReaderHttpServer: public QThread
       
   239 {
       
   240     Q_OBJECT
       
   241     // used to make the constructor only return after the tcp server started listening
       
   242     QSemaphore ready;
       
   243     QTcpSocket *client;
       
   244     int timeout;
       
   245     int port;
       
   246 public:
       
   247     qint64 transferRate;
       
   248     ThreadedDataReaderHttpServer()
       
   249         : port(-1), transferRate(-1)
       
   250     {
       
   251         start();
       
   252         ready.acquire();
       
   253     }
       
   254 
       
   255     inline int serverPort() const { return port; }
       
   256 
       
   257 protected:
       
   258     void run()
       
   259     {
       
   260         QTcpServer server;
       
   261         server.listen();
       
   262         port = server.serverPort();
       
   263         ready.release();
       
   264 
       
   265         server.waitForNewConnection(-1);
       
   266         client = server.nextPendingConnection();
       
   267         client->write("HTTP/1.0 200 OK\r\n");
       
   268         client->write("Content-length: 0\r\n");
       
   269         client->write("\r\n");
       
   270         client->flush();
       
   271 
       
   272         QCoreApplication::processEvents();
       
   273 
       
   274         QEventLoop eventLoop;
       
   275         DataReader reader(client, false);
       
   276         QObject::connect(client, SIGNAL(disconnected()), &eventLoop, SLOT(quit()));
       
   277 
       
   278         QTime timer;
       
   279         timer.start();
       
   280         eventLoop.exec();
       
   281         qint64 elapsed = timer.elapsed();
       
   282 
       
   283         transferRate = reader.totalBytes * 1000 / elapsed;
       
   284         qDebug() << "ThreadedDataReaderHttpServer::run" << "send rate:" << (transferRate / 1024) << "kB/s in" << elapsed << "msec";
       
   285     }
       
   286 };
       
   287 
       
   288 
       
   289 class FixedSizeDataGenerator : public QIODevice
       
   290 {
       
   291     Q_OBJECT
       
   292     enum { Idle, Started, Stopped } state;
       
   293 public:
       
   294     FixedSizeDataGenerator(qint64 size) : state(Idle)
       
   295     { open(ReadOnly | Unbuffered);
       
   296       toBeGeneratedTotalCount = toBeGeneratedCount = size;
       
   297     }
       
   298 
       
   299     virtual qint64 bytesAvailable() const
       
   300     {
       
   301         return state == Started ? toBeGeneratedCount + QIODevice::bytesAvailable() : 0;
       
   302     }
       
   303 
       
   304     virtual bool isSequential() const{
       
   305         return false;
       
   306     }
       
   307 
       
   308     virtual bool reset() const{
       
   309         return false;
       
   310     }
       
   311 
       
   312     qint64 size() const {
       
   313         return toBeGeneratedTotalCount;
       
   314     }
       
   315 
       
   316 public slots:
       
   317     void start() { state = Started; emit readyRead(); }
       
   318 
       
   319 protected:
       
   320     virtual qint64 readData(char *data, qint64 maxlen)
       
   321     {
       
   322         memset(data, '@', maxlen);
       
   323 
       
   324         if (toBeGeneratedCount <= 0) {
       
   325             return -1;
       
   326         }
       
   327 
       
   328         qint64 n = qMin(maxlen, toBeGeneratedCount);
       
   329         toBeGeneratedCount -= n;
       
   330 
       
   331         if (toBeGeneratedCount <= 0) {
       
   332             // make sure this is a queued connection!
       
   333             emit readChannelFinished();
       
   334         }
       
   335 
       
   336         return n;
       
   337     }
       
   338     virtual qint64 writeData(const char *, qint64)
       
   339     { return -1; }
       
   340 
       
   341     qint64 toBeGeneratedCount;
       
   342     qint64 toBeGeneratedTotalCount;
       
   343 };
       
   344 
       
   345 class HttpDownloadPerformanceServer : QObject {
       
   346     Q_OBJECT;
       
   347     qint64 dataSize;
       
   348     qint64 dataSent;
       
   349     QTcpServer server;
       
   350     QTcpSocket *client;
       
   351     bool serverSendsContentLength;
       
   352     bool chunkedEncoding;
       
   353 
       
   354 public:
       
   355     HttpDownloadPerformanceServer (qint64 ds, bool sscl, bool ce) : dataSize(ds), dataSent(0),
       
   356     client(0), serverSendsContentLength(sscl), chunkedEncoding(ce) {
       
   357         server.listen();
       
   358         connect(&server, SIGNAL(newConnection()), this, SLOT(newConnectionSlot()));
       
   359     }
       
   360 
       
   361     int serverPort() {
       
   362         return server.serverPort();
       
   363     }
       
   364 
       
   365 public slots:
       
   366 
       
   367     void newConnectionSlot() {
       
   368         client = server.nextPendingConnection();
       
   369         client->setParent(this);
       
   370         connect(client, SIGNAL(readyRead()), this, SLOT(readyReadSlot()));
       
   371         connect(client, SIGNAL(bytesWritten(qint64)), this, SLOT(bytesWrittenSlot(qint64)));
       
   372     }
       
   373 
       
   374     void readyReadSlot() {
       
   375         client->readAll();
       
   376         client->write("HTTP/1.0 200 OK\n");
       
   377         if (serverSendsContentLength)
       
   378             client->write(QString("Content-Length: " + QString::number(dataSize) + "\n").toAscii());
       
   379         if (chunkedEncoding)
       
   380             client->write(QString("Transfer-Encoding: chunked\n").toAscii());
       
   381         client->write("Connection: close\n\n");
       
   382     }
       
   383 
       
   384     void bytesWrittenSlot(qint64 amount) {
       
   385         Q_UNUSED(amount);
       
   386         if (dataSent == dataSize && client) {
       
   387             // close eventually
       
   388 
       
   389             // chunked encoding: we have to send a last "empty" chunk
       
   390             if (chunkedEncoding)
       
   391                 client->write(QString("0\r\n\r\n").toAscii());
       
   392 
       
   393             client->disconnectFromHost();
       
   394             server.close();
       
   395             client = 0;
       
   396             return;
       
   397         }
       
   398 
       
   399         // send data
       
   400         if (client && client->bytesToWrite() < 100*1024 && dataSent < dataSize) {
       
   401             qint64 amount = qMin(qint64(16*1024), dataSize - dataSent);
       
   402             QByteArray data(amount, '@');
       
   403 
       
   404             if (chunkedEncoding) {
       
   405                 client->write(QString(QString("%1").arg(amount,0,16).toUpper() + "\r\n").toAscii());
       
   406                 client->write(data.constData(), amount);
       
   407                 client->write(QString("\r\n").toAscii());
       
   408             } else {
       
   409                 client->write(data.constData(), amount);
       
   410             }
       
   411 
       
   412             dataSent += amount;
       
   413         }
       
   414     }
       
   415 };
       
   416 
       
   417 class HttpDownloadPerformanceClient : QObject {
       
   418     Q_OBJECT;
       
   419     QIODevice *device;
       
   420     public:
       
   421     HttpDownloadPerformanceClient (QIODevice *dev) : device(dev){
       
   422         connect(dev, SIGNAL(readyRead()), this, SLOT(readyReadSlot()));
       
   423     }
       
   424 
       
   425     public slots:
       
   426     void readyReadSlot() {
       
   427         device->readAll();
       
   428     }
       
   429 
       
   430 };
       
   431 
       
   432 
       
   433 
       
   434 
    51 class tst_qnetworkreply : public QObject
   435 class tst_qnetworkreply : public QObject
    52 {
   436 {
    53     Q_OBJECT
   437     Q_OBJECT
       
   438 
       
   439     QNetworkAccessManager manager;
    54 private slots:
   440 private slots:
    55     void httpLatency();
   441     void httpLatency();
    56 
   442 
    57 #ifndef QT_NO_OPENSSL
   443 #ifndef QT_NO_OPENSSL
    58     void echoPerformance_data();
   444     void echoPerformance_data();
    59     void echoPerformance();
   445     void echoPerformance();
    60 #endif
   446 #endif
       
   447 
       
   448     void downloadPerformance();
       
   449     void uploadPerformance();
       
   450     void performanceControlRate();
       
   451     void httpUploadPerformance();
       
   452     void httpDownloadPerformance_data();
       
   453     void httpDownloadPerformance();
       
   454 
    61 };
   455 };
    62 
   456 
    63 void tst_qnetworkreply::httpLatency()
   457 void tst_qnetworkreply::httpLatency()
    64 {
   458 {
    65     QNetworkAccessManager manager;
   459     QNetworkAccessManager manager;
   105         delete reply;
   499         delete reply;
   106     }
   500     }
   107 }
   501 }
   108 #endif
   502 #endif
   109 
   503 
       
   504 void tst_qnetworkreply::downloadPerformance()
       
   505 {
       
   506     // unlike the above function, this one tries to send as fast as possible
       
   507     // and measures how fast it was.
       
   508     TimedSender sender(5000);
       
   509     QNetworkRequest request("debugpipe://127.0.0.1:" + QString::number(sender.serverPort()) + "/?bare=1");
       
   510     QNetworkReplyPtr reply = manager.get(request);
       
   511     DataReader reader(reply, false);
       
   512 
       
   513     QTime loopTime;
       
   514     connect(reply, SIGNAL(finished()), &QTestEventLoop::instance(), SLOT(exitLoop()));
       
   515     loopTime.start();
       
   516     QTestEventLoop::instance().enterLoop(40);
       
   517     int elapsedTime = loopTime.elapsed();
       
   518     sender.wait();
       
   519 
       
   520     qint64 receivedBytes = reader.totalBytes;
       
   521     qDebug() << "tst_QNetworkReply::downloadPerformance" << "receive rate:" << (receivedBytes * 1000 / elapsedTime / 1024) << "kB/s and"
       
   522              << elapsedTime << "ms";
       
   523 }
       
   524 
       
   525 void tst_qnetworkreply::uploadPerformance()
       
   526 {
       
   527       ThreadedDataReader reader;
       
   528       DataGenerator generator;
       
   529 
       
   530 
       
   531       QNetworkRequest request("debugpipe://127.0.0.1:" + QString::number(reader.serverPort()) + "/?bare=1");
       
   532       QNetworkReplyPtr reply = manager.put(request, &generator);
       
   533       generator.start();
       
   534       connect(&reader, SIGNAL(finished()), &QTestEventLoop::instance(), SLOT(exitLoop()));
       
   535       QTimer::singleShot(5000, &generator, SLOT(stop()));
       
   536 
       
   537       QTestEventLoop::instance().enterLoop(30);
       
   538       QCOMPARE(reply->error(), QNetworkReply::NoError);
       
   539       QVERIFY(!QTestEventLoop::instance().timeout());
       
   540 }
       
   541 
       
   542 void tst_qnetworkreply::httpUploadPerformance()
       
   543 {
       
   544 #ifdef Q_OS_SYMBIAN
       
   545       // SHow some mercy for non-desktop platform/s
       
   546       enum {UploadSize = 4*1024*1024}; // 4 MB
       
   547 #else
       
   548       enum {UploadSize = 128*1024*1024}; // 128 MB
       
   549 #endif
       
   550       ThreadedDataReaderHttpServer reader;
       
   551       FixedSizeDataGenerator generator(UploadSize);
       
   552 
       
   553       QNetworkRequest request(QUrl("http://127.0.0.1:" + QString::number(reader.serverPort()) + "/?bare=1"));
       
   554       request.setHeader(QNetworkRequest::ContentLengthHeader,UploadSize);
       
   555 
       
   556       QNetworkReplyPtr reply = manager.put(request, &generator);
       
   557 
       
   558       connect(reply, SIGNAL(finished()), &QTestEventLoop::instance(), SLOT(exitLoop()));
       
   559 
       
   560       QTime time;
       
   561       generator.start();
       
   562       time.start();
       
   563       QTestEventLoop::instance().enterLoop(40);
       
   564       QCOMPARE(reply->error(), QNetworkReply::NoError);
       
   565       QVERIFY(!QTestEventLoop::instance().timeout());
       
   566 
       
   567       qint64 elapsed = time.elapsed();
       
   568       qDebug() << "tst_QNetworkReply::httpUploadPerformance" << elapsed << "msec, "
       
   569               << ((UploadSize/1024.0)/(elapsed/1000.0)) << " kB/sec";
       
   570 
       
   571       reader.exit();
       
   572       reader.wait();
       
   573 }
       
   574 
       
   575 
       
   576 void tst_qnetworkreply::performanceControlRate()
       
   577 {
       
   578     // this is a control comparison for the other two above
       
   579     // it does the same thing, but instead bypasses the QNetworkAccess system
       
   580     qDebug() << "The following are the maximum transfer rates that we can get in this system"
       
   581         " (bypassing QNetworkAccess)";
       
   582 
       
   583     TimedSender sender(5000);
       
   584     QTcpSocket sink;
       
   585     sink.connectToHost("127.0.0.1", sender.serverPort());
       
   586     DataReader reader(&sink, false);
       
   587 
       
   588     QTime loopTime;
       
   589     connect(&sink, SIGNAL(disconnected()), &QTestEventLoop::instance(), SLOT(exitLoop()));
       
   590     loopTime.start();
       
   591     QTestEventLoop::instance().enterLoop(40);
       
   592     int elapsedTime = loopTime.elapsed();
       
   593     sender.wait();
       
   594 
       
   595     qint64 receivedBytes = reader.totalBytes;
       
   596     qDebug() << "tst_QNetworkReply::performanceControlRate" << "receive rate:" << (receivedBytes * 1000 / elapsedTime / 1024) << "kB/s and"
       
   597              << elapsedTime << "ms";
       
   598 }
       
   599 
       
   600 void tst_qnetworkreply::httpDownloadPerformance_data()
       
   601 {
       
   602     QTest::addColumn<bool>("serverSendsContentLength");
       
   603     QTest::addColumn<bool>("chunkedEncoding");
       
   604 
       
   605     QTest::newRow("Server sends no Content-Length") << false << false;
       
   606     QTest::newRow("Server sends Content-Length")     << true << false;
       
   607     QTest::newRow("Server uses chunked encoding")     << false << true;
       
   608 
       
   609 }
       
   610 
       
   611 void tst_qnetworkreply::httpDownloadPerformance()
       
   612 {
       
   613     QFETCH(bool, serverSendsContentLength);
       
   614     QFETCH(bool, chunkedEncoding);
       
   615 #ifdef Q_OS_SYMBIAN
       
   616     // Show some mercy to non-desktop platform/s
       
   617     enum {UploadSize = 4*1024*1024}; // 4 MB
       
   618 #else
       
   619     enum {UploadSize = 128*1024*1024}; // 128 MB
       
   620 #endif
       
   621     HttpDownloadPerformanceServer server(UploadSize, serverSendsContentLength, chunkedEncoding);
       
   622 
       
   623     QNetworkRequest request(QUrl("http://127.0.0.1:" + QString::number(server.serverPort()) + "/?bare=1"));
       
   624     QNetworkReplyPtr reply = manager.get(request);
       
   625 
       
   626     connect(reply, SIGNAL(finished()), &QTestEventLoop::instance(), SLOT(exitLoop()), Qt::QueuedConnection);
       
   627     HttpDownloadPerformanceClient client(reply);
       
   628 
       
   629     QTime time;
       
   630     time.start();
       
   631     QTestEventLoop::instance().enterLoop(40);
       
   632     QCOMPARE(reply->error(), QNetworkReply::NoError);
       
   633     QVERIFY(!QTestEventLoop::instance().timeout());
       
   634 
       
   635     qint64 elapsed = time.elapsed();
       
   636     qDebug() << "tst_QNetworkReply::httpDownloadPerformance" << elapsed << "msec, "
       
   637             << ((UploadSize/1024.0)/(elapsed/1000.0)) << " kB/sec";
       
   638 }
       
   639 
   110 QTEST_MAIN(tst_qnetworkreply)
   640 QTEST_MAIN(tst_qnetworkreply)
   111 
   641 
   112 #include "tst_qnetworkreply.moc"
   642 #include "tst_qnetworkreply.moc"