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"