summaryrefslogtreecommitdiffstats
path: root/tests
diff options
context:
space:
mode:
authorChristian Mollekopf <chrigi_1@fastmail.fm>2015-12-22 09:23:27 +0100
committerChristian Mollekopf <chrigi_1@fastmail.fm>2015-12-22 09:23:27 +0100
commit8e6671fe6d6519f1fecf37338a3263a5b88a00d1 (patch)
tree7ae0f08aef08566176611476bea8a85ae2707025 /tests
parent042d8fb10552d0bcc92647338d9d763357f35880 (diff)
downloadsink-8e6671fe6d6519f1fecf37338a3263a5b88a00d1.tar.gz
sink-8e6671fe6d6519f1fecf37338a3263a5b88a00d1.zip
An automated test that keeps memory usage in check.
And ensures we scale linearly with the number of entities.
Diffstat (limited to 'tests')
-rw-r--r--tests/databasepopulationandfacadequerybenchmark.cpp220
-rw-r--r--tests/genericfacadebenchmark.cpp79
-rw-r--r--tests/getrssusage.cpp124
-rw-r--r--tests/getrssusage.h39
4 files changed, 383 insertions, 79 deletions
diff --git a/tests/databasepopulationandfacadequerybenchmark.cpp b/tests/databasepopulationandfacadequerybenchmark.cpp
new file mode 100644
index 0000000..9a06710
--- /dev/null
+++ b/tests/databasepopulationandfacadequerybenchmark.cpp
@@ -0,0 +1,220 @@
1#include <QtTest>
2
3#include <QString>
4
5#include "testimplementations.h"
6
7#include <common/facade.h>
8#include <common/domainadaptor.h>
9#include <common/resultprovider.h>
10#include <common/synclistresult.h>
11#include <common/definitions.h>
12#include <common/query.h>
13#include <common/clientapi.h>
14
15#include <iostream>
16#include <math.h>
17
18#include "event_generated.h"
19#include "getrssusage.h"
20
21/**
22 * Benchmark read performance of the facade implementation.
23 *
24 * The memory used should grow linearly with the number of retrieved entities.
25 * The memory used should be independent from the database size, after accounting for the memory mapped db.
26 */
27class DatabasePopulationAndFacadeQueryBenchmark : public QObject
28{
29 Q_OBJECT
30
31 QByteArray identifier;
32 QList<double> mRssGrowthPerEntity;
33 QList<double> mTimePerEntity;
34
35 void populateDatabase(int count)
36 {
37 Akonadi2::Storage(Akonadi2::storageLocation(), "identifier", Akonadi2::Storage::ReadWrite).removeFromDisk();
38 //Setup
39 auto domainTypeAdaptorFactory = QSharedPointer<TestEventAdaptorFactory>::create();
40 {
41 Akonadi2::Storage storage(Akonadi2::storageLocation(), identifier, Akonadi2::Storage::ReadWrite);
42 auto transaction = storage.createTransaction(Akonadi2::Storage::ReadWrite);
43 auto db = transaction.openDatabase("event.main");
44
45 int bufferSizeTotal = 0;
46 int keysSizeTotal = 0;
47 QByteArray attachment;
48 attachment.fill('c', 1000);
49 for (int i = 0; i < count; i++) {
50 auto domainObject = Akonadi2::ApplicationDomain::Event::Ptr::create();
51 domainObject->setProperty("uid", "uid");
52 domainObject->setProperty("summary", "summary");
53 domainObject->setProperty("attachment", attachment);
54 flatbuffers::FlatBufferBuilder fbb;
55 domainTypeAdaptorFactory->createBuffer(*domainObject, fbb);
56 const auto buffer = QByteArray::fromRawData(reinterpret_cast<const char*>(fbb.GetBufferPointer()), fbb.GetSize());
57 const auto key = QString::number(i).toLatin1();
58 db.write(key, buffer);
59 bufferSizeTotal += buffer.size();
60 keysSizeTotal += key.size();
61 }
62 transaction.commit();
63
64 auto dataSizeTotal = count * (QByteArray("uid").size() + QByteArray("summary").size() + attachment.size());
65 auto size = db.getSize();
66 auto onDisk = storage.diskUsage();
67 auto writeAmplification = static_cast<double>(onDisk) / static_cast<double>(bufferSizeTotal);
68 std::cout << "Database size [kb]: " << size/1024 << std::endl;
69 std::cout << "On disk [kb]: " << onDisk/1024 << std::endl;
70 std::cout << "Buffer size total [kb]: " << bufferSizeTotal/1024 << std::endl;
71 std::cout << "Key size total [kb]: " << keysSizeTotal/1024 << std::endl;
72 std::cout << "Data size total [kb]: " << dataSizeTotal/1024 << std::endl;
73 std::cout << "Write amplification: " << writeAmplification << std::endl;
74
75 //The buffer has an overhead, but with a reasonable attachment size it should be relatively small
76 //A write amplification of 2 should be the worst case
77 QVERIFY(writeAmplification < 2);
78 }
79 }
80
81 void testLoad(int count)
82 {
83 const auto startingRss = getCurrentRSS();
84
85 Akonadi2::Query query;
86 query.liveQuery = false;
87 query.requestedProperties << "uid" << "summary";
88
89 //Benchmark
90 QTime time;
91 time.start();
92
93 auto resultSet = QSharedPointer<Akonadi2::ResultProvider<Akonadi2::ApplicationDomain::Event::Ptr> >::create();
94 auto resourceAccess = QSharedPointer<TestResourceAccess>::create();
95 TestResourceFacade facade(identifier, resourceAccess);
96
97 auto ret = facade.load(query);
98 ret.first.exec().waitForFinished();
99 auto emitter = ret.second;
100 QList<Akonadi2::ApplicationDomain::Event::Ptr> list;
101 emitter->onAdded([&list](const Akonadi2::ApplicationDomain::Event::Ptr &event) {
102 list << event;
103 });
104 bool done = false;
105 emitter->onInitialResultSetComplete([&done](const Akonadi2::ApplicationDomain::Event::Ptr &event) {
106 done = true;
107 });
108 emitter->fetch(Akonadi2::ApplicationDomain::Event::Ptr());
109 QTRY_VERIFY(done);
110 QCOMPARE(list.size(), count);
111
112 const auto elapsed = time.elapsed();
113
114 const auto finalRss = getCurrentRSS();
115 const auto rssGrowth = finalRss - startingRss;
116 //Since the database is memory mapped it is attributted to the resident set size.
117 const auto rssWithoutDb = finalRss - Akonadi2::Storage(Akonadi2::storageLocation(), identifier, Akonadi2::Storage::ReadWrite).diskUsage();
118 const auto peakRss = getPeakRSS();
119 //How much peak deviates from final rss in percent (should be around 0)
120 const auto percentageRssError = static_cast<double>(peakRss - finalRss)*100.0/static_cast<double>(finalRss);
121 auto rssGrowthPerEntity = rssGrowth/count;
122
123 std::cout << "Loaded " << list.size() << "results." << std::endl;
124 std::cout << "The query took [ms]: " << elapsed << std::endl;
125 std::cout << "Current Rss usage [kb]: " << finalRss/1024 << std::endl;
126 std::cout << "Peak Rss usage [kb]: " << peakRss/1024 << std::endl;
127 std::cout << "Rss growth [kb]: " << rssGrowth/1024 << std::endl;
128 std::cout << "Rss growth per entity [byte]: " << rssGrowthPerEntity << std::endl;
129 std::cout << "Rss without db [kb]: " << rssWithoutDb/1024 << std::endl;
130 std::cout << "Percentage error: " << percentageRssError << std::endl;
131
132 mTimePerEntity << static_cast<double>(elapsed)/static_cast<double>(count);
133 mRssGrowthPerEntity << rssGrowthPerEntity;
134
135 QVERIFY(percentageRssError < 10);
136 //This shouldn't include the attachment (but currently does)
137 QEXPECT_FAIL("", "We're loading the attachment", Continue);
138 QVERIFY(rssGrowthPerEntity < 2000);
139
140 // Print memory layout, RSS is what is in memory
141 // std::system("exec pmap -x \"$PPID\"");
142 // std::system("top -p \"$PPID\" -b -n 1");
143 }
144
145private Q_SLOTS:
146
147 void init()
148 {
149 identifier = "identifier";
150 }
151
152 void test1k()
153 {
154 populateDatabase(1000);
155 testLoad(1000);
156 }
157
158 void test2k()
159 {
160 populateDatabase(2000);
161 testLoad(2000);
162 }
163
164 void test5k()
165 {
166 populateDatabase(5000);
167 testLoad(5000);
168 }
169
170 // void test10k()
171 // {
172 // populateDatabase(10000);
173 // testLoad(10000);
174 // }
175
176 static double variance(const QList<double> &values)
177 {
178 double mean = 0;
179 for (auto value : values) {
180 mean += value;
181 }
182 mean = mean / static_cast<double>(values.size());
183 double variance = 0;
184 for (auto value : values) {
185 variance += pow(static_cast<double>(value) - mean, 2);
186 }
187 variance = variance / static_cast<double>(values.size() - 1);
188 return variance;
189 }
190
191 static double maxDifference(const QList<double> &values)
192 {
193 auto max = values.first();
194 auto min = values.first();
195 for (auto value : values) {
196 if (value > max) {
197 max = value;
198 }
199 if (value < min) {
200 min = value;
201 }
202 }
203 return max - min;
204 }
205
206 void ensureUsedMemoryRemainsStable()
207 {
208 auto rssStandardDeviation = sqrt(variance(mRssGrowthPerEntity));
209 auto timeStandardDeviation = sqrt(variance(mTimePerEntity));
210 std::cout << "Rss standard deviation " << rssStandardDeviation << std::endl;
211 std::cout << "Rss max difference [byte]" << maxDifference(mRssGrowthPerEntity) << std::endl;
212 std::cout << "Time standard deviation " << timeStandardDeviation << std::endl;
213 std::cout << "Time max difference [ms]" << maxDifference(mTimePerEntity) << std::endl;
214 QVERIFY(rssStandardDeviation < 500);
215 QVERIFY(timeStandardDeviation < 1);
216 }
217};
218
219QTEST_MAIN(DatabasePopulationAndFacadeQueryBenchmark)
220#include "databasepopulationandfacadequerybenchmark.moc"
diff --git a/tests/genericfacadebenchmark.cpp b/tests/genericfacadebenchmark.cpp
deleted file mode 100644
index 703acd1..0000000
--- a/tests/genericfacadebenchmark.cpp
+++ /dev/null
@@ -1,79 +0,0 @@
1#include <QtTest>
2
3#include <QString>
4
5#include "testimplementations.h"
6
7#include <common/facade.h>
8#include <common/domainadaptor.h>
9#include <common/resultprovider.h>
10#include <common/synclistresult.h>
11#include <common/definitions.h>
12
13#include "event_generated.h"
14
15
16
17/**
18 * Benchmark read performance of the generic facade implementation.
19 */
20class GenericFacadeBenchmark : public QObject
21{
22 Q_OBJECT
23private Q_SLOTS:
24
25 void initTestCase()
26 {
27 Akonadi2::Storage store(Akonadi2::storageLocation(), "identifier", Akonadi2::Storage::ReadWrite);
28 store.removeFromDisk();
29 }
30
31 void testLoad()
32 {
33 const QByteArray identifier = "identifier";
34 const int count = 100000;
35
36 //Setup
37 auto domainTypeAdaptorFactory = QSharedPointer<TestEventAdaptorFactory>::create();
38 {
39 Akonadi2::Storage storage(Akonadi2::storageLocation(), identifier, Akonadi2::Storage::ReadWrite);
40 auto transaction = storage.createTransaction(Akonadi2::Storage::ReadWrite);
41 auto db = transaction.openDatabase();
42 for (int i = 0; i < count; i++) {
43 auto domainObject = Akonadi2::ApplicationDomain::Event::Ptr::create();
44 domainObject->setProperty("uid", "uid");
45 domainObject->setProperty("summary", "summary");
46
47 flatbuffers::FlatBufferBuilder fbb;
48 domainTypeAdaptorFactory->createBuffer(*domainObject, fbb);
49 db.write(QString::number(i).toLatin1(), QByteArray::fromRawData(reinterpret_cast<const char*>(fbb.GetBufferPointer()), fbb.GetSize()));
50 }
51 }
52
53 Akonadi2::Query query;
54 query.liveQuery = false;
55
56 //Benchmark
57 QBENCHMARK {
58 auto resultSet = QSharedPointer<Akonadi2::ResultProvider<Akonadi2::ApplicationDomain::Event::Ptr> >::create();
59 auto resourceAccess = QSharedPointer<TestResourceAccess>::create();
60 TestResourceFacade facade(identifier, resourceAccess);
61
62 async::SyncListResult<Akonadi2::ApplicationDomain::Event::Ptr> result(resultSet->emitter());
63
64 facade.load(query, *resultSet).exec().waitForFinished();
65 resultSet->initialResultSetComplete();
66
67 //We have to wait for the events that deliver the results to be processed by the eventloop
68 result.exec();
69
70 QCOMPARE(result.size(), count);
71 }
72
73 // Print memory layout, RSS is what is in memory
74 // std::system("exec pmap -x \"$PPID\"");
75 }
76};
77
78QTEST_MAIN(GenericFacadeBenchmark)
79#include "genericfacadebenchmark.moc"
diff --git a/tests/getrssusage.cpp b/tests/getrssusage.cpp
new file mode 100644
index 0000000..020c7d6
--- /dev/null
+++ b/tests/getrssusage.cpp
@@ -0,0 +1,124 @@
1/*
2 * Author: David Robert Nadeau
3 * Site: http://NadeauSoftware.com/
4 * License: Creative Commons Attribution 3.0 Unported License
5 * http://creativecommons.org/licenses/by/3.0/deed.en_US
6 *
7 * This file has been adapted to match the coding style of the rest of the codebase.
8 *
9 * On windows link against psapi.lib, for the rest the standard library is sufficient.
10 */
11#include "getrssusage.h"
12
13#if defined(_WIN32)
14#include <windows.h>
15#include <psapi.h>
16
17#elif defined(__unix__) || defined(__unix) || defined(unix) || (defined(__APPLE__) && defined(__MACH__))
18#include <unistd.h>
19#include <sys/resource.h>
20
21#if defined(__APPLE__) && defined(__MACH__)
22#include <mach/mach.h>
23
24#elif (defined(_AIX) || defined(__TOS__AIX__)) || (defined(__sun__) || defined(__sun) || defined(sun) && (defined(__SVR4) || defined(__svr4__)))
25#include <fcntl.h>
26#include <procfs.h>
27
28#elif defined(__linux__) || defined(__linux) || defined(linux) || defined(__gnu_linux__)
29#include <stdio.h>
30
31#endif
32
33#else
34#error "Cannot define getPeakRSS( ) or getCurrentRSS( ) for an unknown OS."
35#endif
36
37
38/**
39 * Returns the peak (maximum so far) resident set size (physical
40 * memory use) measured in bytes, or zero if the value cannot be
41 * determined on this OS.
42 */
43size_t getPeakRSS( )
44{
45#if defined(_WIN32)
46 /* Windows -------------------------------------------------- */
47 PROCESS_MEMORY_COUNTERS info;
48 GetProcessMemoryInfo( GetCurrentProcess( ), &info, sizeof(info) );
49 return (size_t)info.PeakWorkingSetSize;
50
51#elif (defined(_AIX) || defined(__TOS__AIX__)) || (defined(__sun__) || defined(__sun) || defined(sun) && (defined(__SVR4) || defined(__svr4__)))
52 /* AIX and Solaris ------------------------------------------ */
53 struct psinfo psinfo;
54 int fd = -1;
55 if ( (fd = open( "/proc/self/psinfo", O_RDONLY )) == -1 )
56 return (size_t)0L; /* Can't open? */
57 if ( read( fd, &psinfo, sizeof(psinfo) ) != sizeof(psinfo) )
58 {
59 close( fd );
60 return (size_t)0L; /* Can't read? */
61 }
62 close( fd );
63 return (size_t)(psinfo.pr_rssize * 1024L);
64
65#elif defined(__unix__) || defined(__unix) || defined(unix) || (defined(__APPLE__) && defined(__MACH__))
66 /* BSD, Linux, and OSX -------------------------------------- */
67 struct rusage rusage;
68 getrusage( RUSAGE_SELF, &rusage );
69#if defined(__APPLE__) && defined(__MACH__)
70 return (size_t)rusage.ru_maxrss;
71#else
72 return (size_t)(rusage.ru_maxrss * 1024L);
73#endif
74
75#else
76 /* Unknown OS ----------------------------------------------- */
77 return (size_t)0L; /* Unsupported. */
78#endif
79}
80
81
82
83
84
85/**
86 * Returns the current resident set size (physical memory use) measured
87 * in bytes, or zero if the value cannot be determined on this OS.
88 */
89size_t getCurrentRSS( )
90{
91#if defined(_WIN32)
92 /* Windows -------------------------------------------------- */
93 PROCESS_MEMORY_COUNTERS info;
94 GetProcessMemoryInfo( GetCurrentProcess( ), &info, sizeof(info) );
95 return (size_t)info.WorkingSetSize;
96
97#elif defined(__APPLE__) && defined(__MACH__)
98 /* OSX ------------------------------------------------------ */
99 struct mach_task_basic_info info;
100 mach_msg_type_number_t infoCount = MACH_TASK_BASIC_INFO_COUNT;
101 if ( task_info( mach_task_self( ), MACH_TASK_BASIC_INFO,
102 (task_info_t)&info, &infoCount ) != KERN_SUCCESS )
103 return (size_t)0L; /* Can't access? */
104 return (size_t)info.resident_size;
105
106#elif defined(__linux__) || defined(__linux) || defined(linux) || defined(__gnu_linux__)
107 /* Linux ---------------------------------------------------- */
108 long rss = 0L;
109 FILE* fp = NULL;
110 if ( (fp = fopen( "/proc/self/statm", "r" )) == NULL )
111 return (size_t)0L; /* Can't open? */
112 if ( fscanf( fp, "%*s%ld", &rss ) != 1 )
113 {
114 fclose( fp );
115 return (size_t)0L; /* Can't read? */
116 }
117 fclose( fp );
118 return (size_t)rss * (size_t)sysconf( _SC_PAGESIZE);
119
120#else
121 /* AIX, BSD, Solaris, and Unknown OS ------------------------ */
122 return (size_t)0L; /* Unsupported. */
123#endif
124}
diff --git a/tests/getrssusage.h b/tests/getrssusage.h
new file mode 100644
index 0000000..e47b14c
--- /dev/null
+++ b/tests/getrssusage.h
@@ -0,0 +1,39 @@
1/*
2 * Author: David Robert Nadeau
3 * Site: http://NadeauSoftware.com/
4 * License: Creative Commons Attribution 3.0 Unported License
5 * http://creativecommons.org/licenses/by/3.0/deed.en_US
6 *
7 * This file has been adapted to match the coding style of the rest of the codebase.
8 *
9 * On windows link against psapi.lib, for the rest the standard library is sufficient.
10 */
11
12#pragma once
13
14#if defined(_WIN32)
15#include <windows.h>
16#include <psapi.h>
17
18#elif defined(__unix__) || defined(__unix) || defined(unix) || (defined(__APPLE__) && defined(__MACH__))
19#include <unistd.h>
20#include <sys/resource.h>
21
22#if defined(__APPLE__) && defined(__MACH__)
23#include <mach/mach.h>
24
25#elif (defined(_AIX) || defined(__TOS__AIX__)) || (defined(__sun__) || defined(__sun) || defined(sun) && (defined(__SVR4) || defined(__svr4__)))
26#include <fcntl.h>
27#include <procfs.h>
28
29#elif defined(__linux__) || defined(__linux) || defined(linux) || defined(__gnu_linux__)
30#include <stdio.h>
31
32#endif
33
34#else
35#error "Cannot define getPeakRSS( ) or getCurrentRSS( ) for an unknown OS."
36#endif
37
38size_t getCurrentRSS();
39size_t getPeakRSS();