diff options
author | Christian Mollekopf <chrigi_1@fastmail.fm> | 2015-12-22 17:40:42 +0100 |
---|---|---|
committer | Christian Mollekopf <chrigi_1@fastmail.fm> | 2015-12-22 17:40:42 +0100 |
commit | 1ea79c5916d6d98f4d15defcc238a319f759798b (patch) | |
tree | c1b68cd447453a939d77437047437874d829cbac /tests/dummyresourcewritebenchmark.cpp | |
parent | 501a9c3a441a4fbd5629a15e4056a52b54834baf (diff) | |
download | sink-1ea79c5916d6d98f4d15defcc238a319f759798b.tar.gz sink-1ea79c5916d6d98f4d15defcc238a319f759798b.zip |
A benchmark for resource writing memory usage
Diffstat (limited to 'tests/dummyresourcewritebenchmark.cpp')
-rw-r--r-- | tests/dummyresourcewritebenchmark.cpp | 227 |
1 files changed, 227 insertions, 0 deletions
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 @@ | |||
1 | #include <QtTest> | ||
2 | |||
3 | #include <QString> | ||
4 | |||
5 | #include <iostream> | ||
6 | |||
7 | #include "dummyresource/resourcefactory.h" | ||
8 | #include "dummyresource/domainadaptor.h" | ||
9 | #include "clientapi.h" | ||
10 | #include "commands.h" | ||
11 | #include "entitybuffer.h" | ||
12 | #include "pipeline.h" | ||
13 | #include "log.h" | ||
14 | #include "resourceconfig.h" | ||
15 | #include "definitions.h" | ||
16 | |||
17 | #include "hawd/dataset.h" | ||
18 | #include "hawd/formatter.h" | ||
19 | |||
20 | #include "event_generated.h" | ||
21 | #include "entity_generated.h" | ||
22 | #include "metadata_generated.h" | ||
23 | #include "createentity_generated.h" | ||
24 | |||
25 | #include "getrssusage.h" | ||
26 | |||
27 | static double variance(const QList<double> &values) | ||
28 | { | ||
29 | double mean = 0; | ||
30 | for (auto value : values) { | ||
31 | mean += value; | ||
32 | } | ||
33 | mean = mean / static_cast<double>(values.size()); | ||
34 | double variance = 0; | ||
35 | for (auto value : values) { | ||
36 | variance += pow(static_cast<double>(value) - mean, 2); | ||
37 | } | ||
38 | variance = variance / static_cast<double>(values.size() - 1); | ||
39 | return variance; | ||
40 | } | ||
41 | |||
42 | static double maxDifference(const QList<double> &values) | ||
43 | { | ||
44 | auto max = values.first(); | ||
45 | auto min = values.first(); | ||
46 | for (auto value : values) { | ||
47 | if (value > max) { | ||
48 | max = value; | ||
49 | } | ||
50 | if (value < min) { | ||
51 | min = value; | ||
52 | } | ||
53 | } | ||
54 | return max - min; | ||
55 | } | ||
56 | |||
57 | static QByteArray createEntityBuffer(int &bufferSize) | ||
58 | { | ||
59 | flatbuffers::FlatBufferBuilder eventFbb; | ||
60 | eventFbb.Clear(); | ||
61 | { | ||
62 | auto summary = eventFbb.CreateString("summary"); | ||
63 | Akonadi2::ApplicationDomain::Buffer::EventBuilder eventBuilder(eventFbb); | ||
64 | eventBuilder.add_summary(summary); | ||
65 | auto eventLocation = eventBuilder.Finish(); | ||
66 | Akonadi2::ApplicationDomain::Buffer::FinishEventBuffer(eventFbb, eventLocation); | ||
67 | } | ||
68 | |||
69 | flatbuffers::FlatBufferBuilder localFbb; | ||
70 | { | ||
71 | auto uid = localFbb.CreateString("testuid"); | ||
72 | auto localBuilder = Akonadi2::ApplicationDomain::Buffer::EventBuilder(localFbb); | ||
73 | localBuilder.add_uid(uid); | ||
74 | auto location = localBuilder.Finish(); | ||
75 | Akonadi2::ApplicationDomain::Buffer::FinishEventBuffer(localFbb, location); | ||
76 | } | ||
77 | |||
78 | flatbuffers::FlatBufferBuilder entityFbb; | ||
79 | Akonadi2::EntityBuffer::assembleEntityBuffer(entityFbb, 0, 0, eventFbb.GetBufferPointer(), eventFbb.GetSize(), localFbb.GetBufferPointer(), localFbb.GetSize()); | ||
80 | bufferSize = entityFbb.GetSize(); | ||
81 | |||
82 | flatbuffers::FlatBufferBuilder fbb; | ||
83 | auto type = fbb.CreateString(Akonadi2::ApplicationDomain::getTypeName<Akonadi2::ApplicationDomain::Event>().toStdString().data()); | ||
84 | auto delta = fbb.CreateVector<uint8_t>(entityFbb.GetBufferPointer(), entityFbb.GetSize()); | ||
85 | Akonadi2::Commands::CreateEntityBuilder builder(fbb); | ||
86 | builder.add_domainType(type); | ||
87 | builder.add_delta(delta); | ||
88 | auto location = builder.Finish(); | ||
89 | Akonadi2::Commands::FinishCreateEntityBuffer(fbb, location); | ||
90 | |||
91 | return QByteArray(reinterpret_cast<const char *>(fbb.GetBufferPointer()), fbb.GetSize()); | ||
92 | } | ||
93 | |||
94 | /** | ||
95 | * Benchmark writing in the synchronizer process. | ||
96 | */ | ||
97 | class DummyResourceWriteBenchmark : public QObject | ||
98 | { | ||
99 | Q_OBJECT | ||
100 | |||
101 | QList<double> mRssGrowthPerEntity; | ||
102 | QList<double> mTimePerEntity; | ||
103 | |||
104 | void writeInProcess(int num) | ||
105 | { | ||
106 | DummyResource::removeFromDisk("org.kde.dummy.instance1"); | ||
107 | |||
108 | |||
109 | QTime time; | ||
110 | time.start(); | ||
111 | |||
112 | auto pipeline = QSharedPointer<Akonadi2::Pipeline>::create("org.kde.dummy.instance1"); | ||
113 | DummyResource resource("org.kde.dummy.instance1", pipeline); | ||
114 | |||
115 | int bufferSize = 0; | ||
116 | auto command = createEntityBuffer(bufferSize); | ||
117 | |||
118 | const auto startingRss = getCurrentRSS(); | ||
119 | for (int i = 0; i < num; i++) { | ||
120 | resource.processCommand(Akonadi2::Commands::CreateEntityCommand, command); | ||
121 | } | ||
122 | auto appendTime = time.elapsed(); | ||
123 | auto bufferSizeTotal = bufferSize * num; | ||
124 | |||
125 | //Wait until all messages have been processed | ||
126 | resource.processAllMessages().exec().waitForFinished(); | ||
127 | |||
128 | auto allProcessedTime = time.elapsed(); | ||
129 | |||
130 | const auto finalRss = getCurrentRSS(); | ||
131 | const auto rssGrowth = finalRss - startingRss; | ||
132 | //Since the database is memory mapped it is attributted to the resident set size. | ||
133 | const auto rssWithoutDb = finalRss - DummyResource::diskUsage("org.kde.dummy.instance1"); | ||
134 | const auto peakRss = getPeakRSS(); | ||
135 | //How much peak deviates from final rss in percent | ||
136 | const auto percentageRssError = static_cast<double>(peakRss - finalRss)*100.0/static_cast<double>(finalRss); | ||
137 | auto rssGrowthPerEntity = rssGrowth/num; | ||
138 | std::cout << "Current Rss usage [kb]: " << finalRss/1024 << std::endl; | ||
139 | std::cout << "Peak Rss usage [kb]: " << peakRss/1024 << std::endl; | ||
140 | std::cout << "Rss growth [kb]: " << rssGrowth/1024 << std::endl; | ||
141 | std::cout << "Rss growth per entity [byte]: " << rssGrowthPerEntity << std::endl; | ||
142 | std::cout << "Rss without db [kb]: " << rssWithoutDb/1024 << std::endl; | ||
143 | std::cout << "Percentage peak rss error: " << percentageRssError << std::endl; | ||
144 | |||
145 | auto onDisk = DummyResource::diskUsage("org.kde.dummy.instance1"); | ||
146 | auto writeAmplification = static_cast<double>(onDisk) / static_cast<double>(bufferSizeTotal); | ||
147 | std::cout << "On disk [kb]: " << onDisk/1024 << std::endl; | ||
148 | std::cout << "Buffer size total [kb]: " << bufferSizeTotal/1024 << std::endl; | ||
149 | std::cout << "Write amplification: " << writeAmplification << std::endl; | ||
150 | |||
151 | |||
152 | mTimePerEntity << static_cast<double>(allProcessedTime)/static_cast<double>(num); | ||
153 | mRssGrowthPerEntity << rssGrowthPerEntity; | ||
154 | |||
155 | QVERIFY(percentageRssError < 10); | ||
156 | //TODO This is much more than it should it seems, although adding the attachment results in pretty exactly a 1k increase, | ||
157 | //so it doesn't look like that memory is being duplicated. | ||
158 | QVERIFY(rssGrowthPerEntity < 2500); | ||
159 | |||
160 | // HAWD::Dataset dataset("dummy_write_in_process", m_hawdState); | ||
161 | // HAWD::Dataset::Row row = dataset.row(); | ||
162 | // | ||
163 | // row.setValue("rows", num); | ||
164 | // row.setValue("append", (qreal)num/appendTime); | ||
165 | // row.setValue("total", (qreal)num/allProcessedTime); | ||
166 | // dataset.insertRow(row); | ||
167 | // HAWD::Formatter::print(dataset); | ||
168 | |||
169 | // Print memory layout, RSS is what is in memory | ||
170 | // std::system("exec pmap -x \"$PPID\""); | ||
171 | } | ||
172 | |||
173 | |||
174 | private Q_SLOTS: | ||
175 | void initTestCase() | ||
176 | { | ||
177 | Akonadi2::Log::setDebugOutputLevel(Akonadi2::Log::Warning); | ||
178 | } | ||
179 | |||
180 | void cleanup() | ||
181 | { | ||
182 | } | ||
183 | |||
184 | void test1k() | ||
185 | { | ||
186 | writeInProcess(1000); | ||
187 | } | ||
188 | |||
189 | void test2k() | ||
190 | { | ||
191 | writeInProcess(2000); | ||
192 | } | ||
193 | |||
194 | void test5k() | ||
195 | { | ||
196 | writeInProcess(5000); | ||
197 | } | ||
198 | |||
199 | // void test20k() | ||
200 | // { | ||
201 | // writeInProcess(20000); | ||
202 | // } | ||
203 | // | ||
204 | void ensureUsedMemoryRemainsStable() | ||
205 | { | ||
206 | auto rssStandardDeviation = sqrt(variance(mRssGrowthPerEntity)); | ||
207 | auto timeStandardDeviation = sqrt(variance(mTimePerEntity)); | ||
208 | std::cout << "Rss standard deviation " << rssStandardDeviation << std::endl; | ||
209 | std::cout << "Rss max difference [byte]" << maxDifference(mRssGrowthPerEntity) << std::endl; | ||
210 | std::cout << "Time standard deviation " << timeStandardDeviation << std::endl; | ||
211 | std::cout << "Time max difference [ms]" << maxDifference(mTimePerEntity) << std::endl; | ||
212 | QVERIFY(rssStandardDeviation < 1000); | ||
213 | QVERIFY(timeStandardDeviation < 1); | ||
214 | } | ||
215 | |||
216 | //This allows to run individual parts without doing a cleanup, but still cleaning up normally | ||
217 | void testCleanupForCompleteTest() | ||
218 | { | ||
219 | DummyResource::removeFromDisk("org.kde.dummy.instance1"); | ||
220 | } | ||
221 | |||
222 | private: | ||
223 | HAWD::State m_hawdState; | ||
224 | }; | ||
225 | |||
226 | QTEST_MAIN(DummyResourceWriteBenchmark) | ||
227 | #include "dummyresourcewritebenchmark.moc" | ||