File indexing completed on 2024-05-12 05:26:26
0001 #include <QTest> 0002 0003 #include "dummy_generated.h" 0004 0005 #include "hawd/dataset.h" 0006 #include "hawd/formatter.h" 0007 #include "common/storage.h" 0008 #include "common/storage/key.h" 0009 #include "common/log.h" 0010 0011 #include <definitions.h> 0012 #include <fstream> 0013 0014 #include <QDebug> 0015 #include <QString> 0016 #include <QTime> 0017 0018 using namespace Sink::ApplicationDomain::Buffer; 0019 using namespace flatbuffers; 0020 0021 static QByteArray createEntity() 0022 { 0023 static const size_t attachmentSize = 1024 * 2; // 2KB 0024 static uint8_t rawData[attachmentSize]; 0025 static FlatBufferBuilder fbb; 0026 fbb.Clear(); 0027 { 0028 uint8_t *rawDataPtr = Q_NULLPTR; 0029 auto summary = fbb.CreateString("summary"); 0030 auto data = fbb.CreateUninitializedVector<uint8_t>(attachmentSize, &rawDataPtr); 0031 DummyBuilder builder(fbb); 0032 builder.add_summary(summary); 0033 builder.add_attachment(data); 0034 FinishDummyBuffer(fbb, builder.Finish()); 0035 memcpy((void *)rawDataPtr, rawData, attachmentSize); 0036 } 0037 0038 return QByteArray::fromRawData(reinterpret_cast<const char *>(fbb.GetBufferPointer()), fbb.GetSize()); 0039 } 0040 0041 /** 0042 * Benchmark the storage implementation. 0043 */ 0044 class StorageBenchmark : public QObject 0045 { 0046 Q_OBJECT 0047 private: 0048 // This should point to a directory on disk and not a ramdisk (since we're measuring performance) 0049 QString testDataPath; 0050 QString dbName; 0051 QString filePath; 0052 const int count = 50000; 0053 0054 private slots: 0055 void initTestCase() 0056 { 0057 // testDataPath = Sink::storageLocation() + "/testdb"; 0058 testDataPath = "./testdb"; 0059 dbName = "test"; 0060 filePath = testDataPath + "buffer.fb"; 0061 } 0062 0063 void cleanupTestCase() 0064 { 0065 Sink::Storage::DataStore store(testDataPath, dbName); 0066 store.removeFromDisk(); 0067 } 0068 0069 void testWriteRead() 0070 { 0071 auto entity = createEntity(); 0072 0073 QScopedPointer<Sink::Storage::DataStore> store(new Sink::Storage::DataStore(testDataPath, dbName, Sink::Storage::DataStore::ReadWrite)); 0074 0075 const char *keyPrefix = "key"; 0076 0077 QTime time; 0078 time.start(); 0079 // Test db write time 0080 { 0081 auto transaction = store->createTransaction(Sink::Storage::DataStore::ReadWrite); 0082 for (int i = 0; i < count; i++) { 0083 transaction.openDatabase().write(keyPrefix + QByteArray::number(i), entity); 0084 if ((i % 10000) == 0) { 0085 transaction.commit(); 0086 transaction = store->createTransaction(Sink::Storage::DataStore::ReadWrite); 0087 } 0088 } 0089 transaction.commit(); 0090 } 0091 qreal dbWriteDuration = time.restart(); 0092 qreal dbWriteOpsPerMs = count / dbWriteDuration; 0093 0094 // Test file write time 0095 { 0096 std::ofstream myfile; 0097 myfile.open(filePath.toStdString()); 0098 for (int i = 0; i < count; i++) { 0099 myfile << entity.toStdString(); 0100 } 0101 myfile.close(); 0102 } 0103 qreal fileWriteDuration = time.restart(); 0104 qreal fileWriteOpsPerMs = count / fileWriteDuration; 0105 0106 // Db read time 0107 { 0108 auto transaction = store->createTransaction(Sink::Storage::DataStore::ReadOnly); 0109 auto db = transaction.openDatabase(); 0110 for (int i = 0; i < count; i++) { 0111 db.scan(keyPrefix + QByteArray::number(i), [](const QByteArray &key, const QByteArray &value) -> bool { return true; }); 0112 } 0113 } 0114 qreal readDuration = time.restart(); 0115 qreal readOpsPerMs = count / readDuration; 0116 0117 HAWD::Dataset dataset("storage_readwrite", m_hawdState); 0118 HAWD::Dataset::Row row = dataset.row(); 0119 row.setValue("rows", count); 0120 row.setValue("dbWrite", dbWriteOpsPerMs); 0121 row.setValue("fileWrite", fileWriteOpsPerMs); 0122 row.setValue("dbRead", readOpsPerMs); 0123 dataset.insertRow(row); 0124 HAWD::Formatter::print(dataset); 0125 0126 { 0127 Sink::Storage::DataStore store(testDataPath, dbName); 0128 QFileInfo fileInfo(filePath); 0129 0130 HAWD::Dataset dataset("storage_sizes", m_hawdState); 0131 HAWD::Dataset::Row row = dataset.row(); 0132 row.setValue("rows", count); 0133 row.setValue("dbSize", store.diskUsage() / 1024); 0134 row.setValue("fileSize", fileInfo.size() / 1024); 0135 dataset.insertRow(row); 0136 HAWD::Formatter::print(dataset); 0137 } 0138 } 0139 0140 void testScan() 0141 { 0142 QScopedPointer<Sink::Storage::DataStore> store(new Sink::Storage::DataStore(testDataPath, dbName, Sink::Storage::DataStore::ReadOnly)); 0143 0144 QBENCHMARK { 0145 int hit = 0; 0146 store->createTransaction(Sink::Storage::DataStore::ReadOnly) 0147 .openDatabase() 0148 .scan("", [&](const QByteArray &key, const QByteArray &value) -> bool { 0149 if (key == "key10000") { 0150 // qDebug() << "hit"; 0151 hit++; 0152 } 0153 return true; 0154 }); 0155 QCOMPARE(hit, 1); 0156 } 0157 } 0158 0159 void testKeyLookup() 0160 { 0161 QScopedPointer<Sink::Storage::DataStore> store(new Sink::Storage::DataStore(testDataPath, dbName, Sink::Storage::DataStore::ReadOnly)); 0162 auto transaction = store->createTransaction(Sink::Storage::DataStore::ReadOnly); 0163 auto db = transaction.openDatabase(); 0164 0165 QBENCHMARK { 0166 int hit = 0; 0167 db.scan("key40000", [&](const QByteArray &key, const QByteArray &value) -> bool { 0168 hit++; 0169 return true; 0170 }); 0171 QCOMPARE(hit, 1); 0172 } 0173 } 0174 0175 void testFindLatest() 0176 { 0177 QScopedPointer<Sink::Storage::DataStore> store(new Sink::Storage::DataStore(testDataPath, dbName, Sink::Storage::DataStore::ReadOnly)); 0178 auto transaction = store->createTransaction(Sink::Storage::DataStore::ReadOnly); 0179 auto db = transaction.openDatabase(); 0180 0181 QBENCHMARK { 0182 int hit = 0; 0183 db.findLatest("key40000", [&](const QByteArray &key, const QByteArray &value) -> bool { 0184 hit++; 0185 return true; 0186 }); 0187 QCOMPARE(hit, 1); 0188 } 0189 } 0190 0191 void testBufferCreation() 0192 { 0193 HAWD::Dataset dataset("buffer_creation", m_hawdState); 0194 HAWD::Dataset::Row row = dataset.row(); 0195 0196 QTime time; 0197 time.start(); 0198 0199 for (int i = 0; i < count; i++) { 0200 auto entity = createEntity(); 0201 Q_UNUSED(entity); 0202 } 0203 0204 qreal bufferDuration = time.restart(); 0205 qreal opsPerMs = count / bufferDuration; 0206 row.setValue("numBuffers", count); 0207 row.setValue("time", bufferDuration); 0208 row.setValue("ops", opsPerMs); 0209 dataset.insertRow(row); 0210 HAWD::Formatter::print(dataset); 0211 } 0212 0213 /* 0214 * This benchmark is supposed to roughly emulate the workload we get during a large sync. 0215 * 0216 * Observed behavior is that without a concurrent read-only transaction free pages are mostly reused, 0217 * but commits start to take longer and longer. 0218 * With a concurrent read-only transaction free pages are kept from being reused and commits a greatly sped up. 0219 * 0220 * NOTE: At least in podman there seems to be a massive performance difference whether we use a path within the home directory, 0221 * or in the bind-mounted build directory. This is probably because the home directory is a fuse-overlayfs, and the build directory is 0222 * just the underlying ext4 volume. I suppose the overlayfs is not suitable for this specific workload. 0223 */ 0224 void testWrite() 0225 { 0226 Sink::Storage::DataStore(testDataPath, dbName).removeFromDisk(); 0227 0228 using namespace Sink::Storage; 0229 0230 auto entity = createEntity(); 0231 qWarning() << "entity size " << entity.size(); 0232 Sink::Storage::DataStore store(testDataPath, dbName, Sink::Storage::DataStore::ReadWrite); 0233 0234 QTime time; 0235 time.start(); 0236 // Test db write time 0237 { 0238 //This read-only transaction will make commits fast but cause a quick buildup of free pages. 0239 // auto rotransaction = store.createTransaction(Sink::Storage::DataStore::ReadOnly); 0240 auto transaction = store.createTransaction(Sink::Storage::DataStore::ReadWrite); 0241 for (int i = 0; i < count; i++) { 0242 time.start(); 0243 const auto identifier = Identifier::fromDisplayByteArray(Sink::Storage::DataStore::generateUid()); 0244 const qint64 newRevision = Sink::Storage::DataStore::maxRevision(transaction) + 1; 0245 const auto key = Sink::Storage::Key(identifier, newRevision); 0246 0247 DataStore::mainDatabase(transaction, "mail") 0248 .write(newRevision, entity, 0249 [&](const DataStore::Error &error) { qWarning() << "Failed to write entity" << identifier << newRevision; }); 0250 0251 DataStore::setMaxRevision(transaction, newRevision); 0252 DataStore::recordRevision(transaction, newRevision, identifier, "mail"); 0253 DataStore::recordUid(transaction, identifier, "mail"); 0254 0255 auto db = transaction.openDatabase("mail.index.messageId" , std::function<void(const Sink::Storage::DataStore::Error &)>(), Sink::Storage::AllowDuplicates); 0256 db.write("messageid", key.toInternalByteArray(), [&] (const Sink::Storage::DataStore::Error &error) { 0257 qWarning() << "Error while writing value" << error; 0258 }); 0259 0260 if ((i % 100) == 0) { 0261 transaction.commit(); 0262 qWarning() << "Commit " << i << time.elapsed() << "[ms]"; 0263 transaction = store.createTransaction(Sink::Storage::DataStore::ReadWrite); 0264 // rotransaction = store.createTransaction(Sink::Storage::DataStore::ReadOnly); 0265 qWarning() << "Free pages " << store.createTransaction(Sink::Storage::DataStore::ReadOnly).stat(false).freePages; 0266 } 0267 } 0268 transaction.commit(); 0269 } 0270 qreal dbWriteDuration = time.elapsed(); 0271 qreal dbWriteOpsPerMs = count / dbWriteDuration; 0272 { 0273 auto transaction = store.createTransaction(Sink::Storage::DataStore::ReadOnly); 0274 auto stat = transaction.stat(false); 0275 qWarning() << "Write duration " << dbWriteDuration; 0276 qWarning() << "Write per ms " << dbWriteOpsPerMs; 0277 qWarning() << "free " << stat.freePages; 0278 qWarning() << "total " << stat.totalPages; 0279 0280 QList<QByteArray> databases = transaction.getDatabaseNames(); 0281 for (const auto &databaseName : databases) { 0282 auto db = transaction.openDatabase(databaseName); 0283 qint64 size = db.getSize() / 1024; 0284 qWarning() << QString("%1: %2 [kb]").arg(QString(databaseName)).arg(size); 0285 } 0286 } 0287 } 0288 0289 private: 0290 HAWD::State m_hawdState; 0291 }; 0292 0293 QTEST_MAIN(StorageBenchmark) 0294 #include "storagebenchmark.moc"