File indexing completed on 2024-05-12 05:26:22

0001 #include <QTest>
0002 
0003 #include <QString>
0004 
0005 #include "testimplementations.h"
0006 
0007 #include <common/facade.h>
0008 #include <common/domainadaptor.h>
0009 #include <common/resultprovider.h>
0010 #include <common/definitions.h>
0011 #include <common/query.h>
0012 #include <common/store.h>
0013 
0014 #include "hawd/dataset.h"
0015 #include "hawd/formatter.h"
0016 
0017 #include <iostream>
0018 
0019 #include "event_generated.h"
0020 #include "getrssusage.h"
0021 #include "utils.h"
0022 #include "test.h"
0023 
0024 /**
0025  * Benchmark read performance of the facade implementation.
0026  *
0027  * The memory used should grow linearly with the number of retrieved entities.
0028  * The memory used should be independent from the database size, after accounting for the memory mapped db.
0029  */
0030 class DatabasePopulationAndFacadeQueryBenchmark : public QObject
0031 {
0032     Q_OBJECT
0033 
0034     QByteArray identifier;
0035     QList<double> mRssGrowthPerEntity;
0036     QList<double> mTimePerEntity;
0037     HAWD::State mHawdState;
0038 
0039     void populateDatabase(double count)
0040     {
0041         Sink::Storage::DataStore(Sink::storageLocation(), "identifier", Sink::Storage::DataStore::ReadWrite).removeFromDisk();
0042         // Setup
0043         auto domainTypeAdaptorFactory = QSharedPointer<TestEventAdaptorFactory>::create();
0044         {
0045             Sink::Storage::DataStore storage(Sink::storageLocation(), identifier, Sink::Storage::DataStore::ReadWrite);
0046             auto transaction = storage.createTransaction(Sink::Storage::DataStore::ReadWrite);
0047             auto db = Sink::Storage::DataStore::mainDatabase(transaction, "event");
0048 
0049             int bufferSizeTotal = 0;
0050             int keysSizeTotal = 0;
0051             QByteArray attachment;
0052             attachment.fill('c', 1000);
0053             for (int i = 0; i < count; i++) {
0054                 auto domainObject = Sink::ApplicationDomain::Event::Ptr::create();
0055                 domainObject->setProperty("uid", "uid");
0056                 domainObject->setProperty("summary", QString("summary%1").arg(i));
0057                 domainObject->setProperty("attachment", attachment);
0058                 flatbuffers::FlatBufferBuilder fbb;
0059                 domainTypeAdaptorFactory->createBuffer(*domainObject, fbb);
0060                 const auto buffer = QByteArray::fromRawData(reinterpret_cast<const char *>(fbb.GetBufferPointer()), fbb.GetSize());
0061                 const auto key = Sink::Storage::DataStore::generateUid();
0062                 db.write(key, buffer);
0063                 bufferSizeTotal += buffer.size();
0064                 keysSizeTotal += key.size();
0065             }
0066             transaction.commit();
0067 
0068             transaction = storage.createTransaction(Sink::Storage::DataStore::ReadOnly);
0069             db = Sink::Storage::DataStore::mainDatabase(transaction, "event");
0070 
0071             auto dataSizeTotal = count * (QByteArray("uid").size() + QByteArray("summary").size() + attachment.size());
0072             auto size = db.getSize();
0073             auto onDisk = storage.diskUsage();
0074             auto writeAmplification = static_cast<double>(onDisk) / static_cast<double>(bufferSizeTotal);
0075             std::cout << "Database size [kb]: " << size / 1024 << std::endl;
0076             std::cout << "On disk [kb]: " << onDisk / 1024 << std::endl;
0077             std::cout << "Buffer size total [kb]: " << bufferSizeTotal / 1024 << std::endl;
0078             std::cout << "Key size total [kb]: " << keysSizeTotal / 1024 << std::endl;
0079             std::cout << "Data size total [kb]: " << dataSizeTotal / 1024 << std::endl;
0080             std::cout << "Write amplification: " << writeAmplification << std::endl;
0081 
0082             // The buffer has an overhead, but with a reasonable attachment size it should be relatively small
0083             // A write amplification of 2 should be the worst case
0084             QVERIFY(writeAmplification < 2);
0085         }
0086     }
0087 
0088     void testLoad(int count)
0089     {
0090         const auto startingRss = static_cast<double>(getCurrentRSS());
0091 
0092         Sink::Query query;
0093         query.requestedProperties << "uid"
0094                                   << "summary";
0095 
0096         // Benchmark
0097         QTime time;
0098         time.start();
0099 
0100         auto resultSet = QSharedPointer<Sink::ResultProvider<Sink::ApplicationDomain::Event::Ptr>>::create();
0101         auto resourceAccess = QSharedPointer<TestResourceAccess>::create();
0102 
0103         QMap<QByteArray, DomainTypeAdaptorFactoryInterface::Ptr> factories;
0104         factories.insert("event", QSharedPointer<TestEventAdaptorFactory>::create());
0105         Sink::ResourceContext context{identifier, "test", factories};
0106         context.mResourceAccess = resourceAccess;
0107         TestResourceFacade facade(context);
0108 
0109         auto ret = facade.load(query, Sink::Log::Context{"benchmark"});
0110         ret.first.exec().waitForFinished();
0111         auto emitter = ret.second;
0112         QList<Sink::ApplicationDomain::Event::Ptr> list;
0113         emitter->onAdded([&list](const Sink::ApplicationDomain::Event::Ptr &event) { list << event; });
0114         bool done = false;
0115         emitter->onInitialResultSetComplete([&done](bool) { done = true; });
0116         emitter->fetch();
0117         QUICK_TRY_VERIFY(done);
0118         QCOMPARE(list.size(), count);
0119 
0120         const double elapsed = time.elapsed();
0121 
0122         const double finalRss = static_cast<double>(getCurrentRSS());
0123         const double rssGrowth = finalRss - startingRss;
0124         // Since the database is memory mapped it is attributted to the resident set size.
0125         const double rssWithoutDb = finalRss - static_cast<double>(Sink::Storage::DataStore(Sink::storageLocation(), identifier, Sink::Storage::DataStore::ReadWrite).diskUsage());
0126         const double peakRss = static_cast<double>(getPeakRSS());
0127         // How much peak deviates from final rss in percent (should be around 0)
0128         const double percentageRssError = static_cast<double>(peakRss - finalRss) * 100.0 / static_cast<double>(finalRss);
0129         const double rssGrowthPerEntity = rssGrowth / static_cast<double>(count);
0130 
0131         std::cout << "Loaded " << list.size() << " results." << std::endl;
0132         std::cout << "The query took [ms]: " << elapsed << std::endl;
0133         std::cout << "Current Rss usage [kb]: " << finalRss / 1024 << std::endl;
0134         std::cout << "Peak Rss usage [kb]: " << peakRss / 1024 << std::endl;
0135         std::cout << "Rss growth [kb]: " << rssGrowth / 1024 << std::endl;
0136         std::cout << "Rss growth per entity [byte]: " << rssGrowthPerEntity << std::endl;
0137         std::cout << "Rss without db [kb]: " << rssWithoutDb / 1024 << std::endl;
0138         std::cout << "Percentage error: " << percentageRssError << std::endl;
0139 
0140         HAWD::Dataset dataset("facade_query", mHawdState);
0141         HAWD::Dataset::Row row = dataset.row();
0142         row.setValue("rows", list.size());
0143         row.setValue("queryResultPerMs", (qreal)list.size() / elapsed);
0144         dataset.insertRow(row);
0145         HAWD::Formatter::print(dataset);
0146 
0147         mTimePerEntity << elapsed / static_cast<double>(count);
0148         mRssGrowthPerEntity << rssGrowthPerEntity;
0149 
0150         QVERIFY(percentageRssError < 10);
0151         // TODO This is much more than it should it seems, although adding the attachment results in pretty exactly a 1k increase,
0152         // so it doesn't look like that memory is being duplicated.
0153         QVERIFY(rssGrowthPerEntity < 5000);
0154 
0155         // Print memory layout, RSS is what is in memory
0156         // std::system("exec pmap -x \"$PPID\"");
0157         // std::system("top -p \"$PPID\" -b -n 1");
0158     }
0159 
0160 private slots:
0161 
0162     void init()
0163     {
0164         identifier = "identifier";
0165     }
0166 
0167     void test1k()
0168     {
0169         populateDatabase(1000);
0170         testLoad(1000);
0171     }
0172 
0173     void test2k()
0174     {
0175         populateDatabase(2000);
0176         testLoad(2000);
0177     }
0178 
0179     void test5k()
0180     {
0181         populateDatabase(5000);
0182         testLoad(5000);
0183     }
0184 
0185     // void test10k()
0186     // {
0187     //     populateDatabase(10000);
0188     //     testLoad(10000);
0189     // }
0190 
0191     void ensureUsedMemoryRemainsStable()
0192     {
0193         auto rssStandardDeviation = sqrt(variance(mRssGrowthPerEntity));
0194         auto timeStandardDeviation = sqrt(variance(mTimePerEntity));
0195         std::cout << "Rss standard deviation " << rssStandardDeviation << std::endl;
0196         std::cout << "Rss max difference [byte]" << maxDifference(mRssGrowthPerEntity) << std::endl;
0197         std::cout << "Time standard deviation " << timeStandardDeviation << std::endl;
0198         std::cout << "Time max difference [ms]" << maxDifference(mTimePerEntity) << std::endl;
0199         QVERIFY(rssStandardDeviation < 1000);
0200         QVERIFY(timeStandardDeviation < 1);
0201     }
0202 };
0203 
0204 QTEST_MAIN(DatabasePopulationAndFacadeQueryBenchmark)
0205 #include "databasepopulationandfacadequerybenchmark.moc"