diff options
Diffstat (limited to 'tests')
-rw-r--r-- | tests/databasepopulationandfacadequerybenchmark.cpp | 220 | ||||
-rw-r--r-- | tests/genericfacadebenchmark.cpp | 79 | ||||
-rw-r--r-- | tests/getrssusage.cpp | 124 | ||||
-rw-r--r-- | tests/getrssusage.h | 39 |
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 | */ | ||
27 | class 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 | |||
145 | private 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 | |||
219 | QTEST_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 | */ | ||
20 | class GenericFacadeBenchmark : public QObject | ||
21 | { | ||
22 | Q_OBJECT | ||
23 | private 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 | |||
78 | QTEST_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 | */ | ||
43 | size_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 | */ | ||
89 | size_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 | |||
38 | size_t getCurrentRSS(); | ||
39 | size_t getPeakRSS(); | ||