From 1ea79c5916d6d98f4d15defcc238a319f759798b Mon Sep 17 00:00:00 2001 From: Christian Mollekopf Date: Tue, 22 Dec 2015 17:40:42 +0100 Subject: A benchmark for resource writing memory usage --- tests/CMakeLists.txt | 7 +- tests/dummyresourcewritebenchmark.cpp | 227 ++++++++++++++++++++++++++++++++++ 2 files changed, 232 insertions(+), 2 deletions(-) create mode 100644 tests/dummyresourcewritebenchmark.cpp diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 4b10b56..5d64511 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -12,7 +12,7 @@ add_definitions(-DTESTDATAPATH="${CMAKE_CURRENT_SOURCE_DIR}/data") macro(manual_tests) foreach(_testname ${ARGN}) - add_executable(${_testname} ${_testname}.cpp testimplementations.cpp) + add_executable(${_testname} ${_testname}.cpp testimplementations.cpp getrssusage.cpp) generate_flatbuffers(${_testname} calendar) qt5_use_modules(${_testname} Core Test Concurrent) target_link_libraries(${_testname} akonadi2common libhawd) @@ -21,7 +21,7 @@ endmacro(manual_tests) macro(auto_tests) foreach(_testname ${ARGN}) - add_executable(${_testname} ${_testname}.cpp testimplementations.cpp) + add_executable(${_testname} ${_testname}.cpp testimplementations.cpp getrssusage.cpp) generate_flatbuffers(${_testname} calendar) add_test(${_testname} ${_testname}) qt5_use_modules(${_testname} Core Test Concurrent) @@ -48,9 +48,12 @@ auto_tests ( resourcecommunicationtest pipelinetest querytest + databasepopulationandfacadequerybenchmark + dummyresourcewritebenchmark ) target_link_libraries(dummyresourcetest akonadi2_resource_dummy) target_link_libraries(dummyresourcebenchmark akonadi2_resource_dummy) +target_link_libraries(dummyresourcewritebenchmark akonadi2_resource_dummy) target_link_libraries(querytest akonadi2_resource_dummy) if (BUILD_MAILDIR) diff --git a/tests/dummyresourcewritebenchmark.cpp b/tests/dummyresourcewritebenchmark.cpp new file mode 100644 index 0000000..3e8f4c5 --- /dev/null +++ b/tests/dummyresourcewritebenchmark.cpp @@ -0,0 +1,227 @@ +#include + +#include + +#include + +#include "dummyresource/resourcefactory.h" +#include "dummyresource/domainadaptor.h" +#include "clientapi.h" +#include "commands.h" +#include "entitybuffer.h" +#include "pipeline.h" +#include "log.h" +#include "resourceconfig.h" +#include "definitions.h" + +#include "hawd/dataset.h" +#include "hawd/formatter.h" + +#include "event_generated.h" +#include "entity_generated.h" +#include "metadata_generated.h" +#include "createentity_generated.h" + +#include "getrssusage.h" + +static double variance(const QList &values) +{ + double mean = 0; + for (auto value : values) { + mean += value; + } + mean = mean / static_cast(values.size()); + double variance = 0; + for (auto value : values) { + variance += pow(static_cast(value) - mean, 2); + } + variance = variance / static_cast(values.size() - 1); + return variance; +} + +static double maxDifference(const QList &values) +{ + auto max = values.first(); + auto min = values.first(); + for (auto value : values) { + if (value > max) { + max = value; + } + if (value < min) { + min = value; + } + } + return max - min; +} + +static QByteArray createEntityBuffer(int &bufferSize) +{ + flatbuffers::FlatBufferBuilder eventFbb; + eventFbb.Clear(); + { + auto summary = eventFbb.CreateString("summary"); + Akonadi2::ApplicationDomain::Buffer::EventBuilder eventBuilder(eventFbb); + eventBuilder.add_summary(summary); + auto eventLocation = eventBuilder.Finish(); + Akonadi2::ApplicationDomain::Buffer::FinishEventBuffer(eventFbb, eventLocation); + } + + flatbuffers::FlatBufferBuilder localFbb; + { + auto uid = localFbb.CreateString("testuid"); + auto localBuilder = Akonadi2::ApplicationDomain::Buffer::EventBuilder(localFbb); + localBuilder.add_uid(uid); + auto location = localBuilder.Finish(); + Akonadi2::ApplicationDomain::Buffer::FinishEventBuffer(localFbb, location); + } + + flatbuffers::FlatBufferBuilder entityFbb; + Akonadi2::EntityBuffer::assembleEntityBuffer(entityFbb, 0, 0, eventFbb.GetBufferPointer(), eventFbb.GetSize(), localFbb.GetBufferPointer(), localFbb.GetSize()); + bufferSize = entityFbb.GetSize(); + + flatbuffers::FlatBufferBuilder fbb; + auto type = fbb.CreateString(Akonadi2::ApplicationDomain::getTypeName().toStdString().data()); + auto delta = fbb.CreateVector(entityFbb.GetBufferPointer(), entityFbb.GetSize()); + Akonadi2::Commands::CreateEntityBuilder builder(fbb); + builder.add_domainType(type); + builder.add_delta(delta); + auto location = builder.Finish(); + Akonadi2::Commands::FinishCreateEntityBuffer(fbb, location); + + return QByteArray(reinterpret_cast(fbb.GetBufferPointer()), fbb.GetSize()); +} + +/** + * Benchmark writing in the synchronizer process. + */ +class DummyResourceWriteBenchmark : public QObject +{ + Q_OBJECT + + QList mRssGrowthPerEntity; + QList mTimePerEntity; + + void writeInProcess(int num) + { + DummyResource::removeFromDisk("org.kde.dummy.instance1"); + + + QTime time; + time.start(); + + auto pipeline = QSharedPointer::create("org.kde.dummy.instance1"); + DummyResource resource("org.kde.dummy.instance1", pipeline); + + int bufferSize = 0; + auto command = createEntityBuffer(bufferSize); + + const auto startingRss = getCurrentRSS(); + for (int i = 0; i < num; i++) { + resource.processCommand(Akonadi2::Commands::CreateEntityCommand, command); + } + auto appendTime = time.elapsed(); + auto bufferSizeTotal = bufferSize * num; + + //Wait until all messages have been processed + resource.processAllMessages().exec().waitForFinished(); + + auto allProcessedTime = time.elapsed(); + + const auto finalRss = getCurrentRSS(); + const auto rssGrowth = finalRss - startingRss; + //Since the database is memory mapped it is attributted to the resident set size. + const auto rssWithoutDb = finalRss - DummyResource::diskUsage("org.kde.dummy.instance1"); + const auto peakRss = getPeakRSS(); + //How much peak deviates from final rss in percent + const auto percentageRssError = static_cast(peakRss - finalRss)*100.0/static_cast(finalRss); + auto rssGrowthPerEntity = rssGrowth/num; + std::cout << "Current Rss usage [kb]: " << finalRss/1024 << std::endl; + std::cout << "Peak Rss usage [kb]: " << peakRss/1024 << std::endl; + std::cout << "Rss growth [kb]: " << rssGrowth/1024 << std::endl; + std::cout << "Rss growth per entity [byte]: " << rssGrowthPerEntity << std::endl; + std::cout << "Rss without db [kb]: " << rssWithoutDb/1024 << std::endl; + std::cout << "Percentage peak rss error: " << percentageRssError << std::endl; + + auto onDisk = DummyResource::diskUsage("org.kde.dummy.instance1"); + auto writeAmplification = static_cast(onDisk) / static_cast(bufferSizeTotal); + std::cout << "On disk [kb]: " << onDisk/1024 << std::endl; + std::cout << "Buffer size total [kb]: " << bufferSizeTotal/1024 << std::endl; + std::cout << "Write amplification: " << writeAmplification << std::endl; + + + mTimePerEntity << static_cast(allProcessedTime)/static_cast(num); + mRssGrowthPerEntity << rssGrowthPerEntity; + + QVERIFY(percentageRssError < 10); + //TODO This is much more than it should it seems, although adding the attachment results in pretty exactly a 1k increase, + //so it doesn't look like that memory is being duplicated. + QVERIFY(rssGrowthPerEntity < 2500); + + // HAWD::Dataset dataset("dummy_write_in_process", m_hawdState); + // HAWD::Dataset::Row row = dataset.row(); + // + // row.setValue("rows", num); + // row.setValue("append", (qreal)num/appendTime); + // row.setValue("total", (qreal)num/allProcessedTime); + // dataset.insertRow(row); + // HAWD::Formatter::print(dataset); + + // Print memory layout, RSS is what is in memory + // std::system("exec pmap -x \"$PPID\""); + } + + +private Q_SLOTS: + void initTestCase() + { + Akonadi2::Log::setDebugOutputLevel(Akonadi2::Log::Warning); + } + + void cleanup() + { + } + + void test1k() + { + writeInProcess(1000); + } + + void test2k() + { + writeInProcess(2000); + } + + void test5k() + { + writeInProcess(5000); + } + + // void test20k() + // { + // writeInProcess(20000); + // } + // + void ensureUsedMemoryRemainsStable() + { + auto rssStandardDeviation = sqrt(variance(mRssGrowthPerEntity)); + auto timeStandardDeviation = sqrt(variance(mTimePerEntity)); + std::cout << "Rss standard deviation " << rssStandardDeviation << std::endl; + std::cout << "Rss max difference [byte]" << maxDifference(mRssGrowthPerEntity) << std::endl; + std::cout << "Time standard deviation " << timeStandardDeviation << std::endl; + std::cout << "Time max difference [ms]" << maxDifference(mTimePerEntity) << std::endl; + QVERIFY(rssStandardDeviation < 1000); + QVERIFY(timeStandardDeviation < 1); + } + + //This allows to run individual parts without doing a cleanup, but still cleaning up normally + void testCleanupForCompleteTest() + { + DummyResource::removeFromDisk("org.kde.dummy.instance1"); + } + +private: + HAWD::State m_hawdState; +}; + +QTEST_MAIN(DummyResourceWriteBenchmark) +#include "dummyresourcewritebenchmark.moc" -- cgit v1.2.3