PerformanceTesting.m 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427
  1. //
  2. // PerformanceTesting.m
  3. // Benchmarking
  4. //
  5. // CocoaLumberjack Benchmarking
  6. //
  7. #import "PerformanceTesting.h"
  8. #import <CocoaLumberjack/CocoaLumberjack.h>
  9. #import "BaseNSLogging.h"
  10. #import "StaticLogging.h"
  11. #import "DynamicLogging.h"
  12. // Define the number of times each test is performed.
  13. // Due to various factors, the execution time of each test run may vary quite a bit.
  14. // Each test should be executed several times in order to arrive at a stable average.
  15. #define NUMBER_OF_RUNS 20
  16. /**
  17. * The idea behind the benchmark tests is simple:
  18. * How does the logging framework compare to basic NSLog statements?
  19. *
  20. * However, due to the complexity of the logging framework and its various configuration options,
  21. * it is more complicated than a single test. Thus the testing is broken up as follows:
  22. *
  23. * - 3 Suites, each representing a different configuration of the logging framework
  24. * - 5 Tests, run within each suite.
  25. *
  26. * The suites are described below in the configureLoggingForSuiteX methods.
  27. * The tests are described in the various logging files, such as StaticLogging or DynamicLogging.
  28. * Notice that these file are almost exactly the same.
  29. *
  30. * BaseNSLogging defines the log methods to use NSLog (the base we are comparing against).
  31. * StaticLogging uses a 'const' log level, meaning the compiler will prune log statements (in release mode).
  32. * DynamicLogging use a non-const log level, meaning each log statement will incur an integer comparison penalty.
  33. **/
  34. @implementation PerformanceTesting
  35. static NSTimeInterval base[5][3]; // [test][min,avg,max]
  36. static NSTimeInterval fmwk[3][2][5][3]; // [suite][file][test][min,avg,max]
  37. static DDFileLogger *fileLogger = nil;
  38. + (void)initialize
  39. {
  40. bzero(&base, sizeof(base));
  41. bzero(&fmwk, sizeof(fmwk));
  42. }
  43. + (DDFileLogger *)fileLogger
  44. {
  45. if (fileLogger == nil)
  46. {
  47. fileLogger = [[DDFileLogger alloc] init];
  48. fileLogger.maximumFileSize = (1024 * 1024 * 1); // 1 MB
  49. fileLogger.rollingFrequency = (60 * 60 * 24); // 24 Hours
  50. fileLogger.logFileManager.maximumNumberOfLogFiles = 4;
  51. }
  52. return fileLogger;
  53. }
  54. + (id<DDLogger>)ttyLogger
  55. {
  56. return [DDTTYLogger sharedInstance];
  57. }
  58. /**
  59. * Suite 1 - Logging to Console only.
  60. **/
  61. + (void)configureLoggingForSuite1
  62. {
  63. [DDLog removeAllLoggers];
  64. [DDLog addLogger:[DDOSLogger sharedInstance]];
  65. [DDLog addLogger:[self ttyLogger]];
  66. }
  67. /**
  68. * Suite 2 - Logging to File only.
  69. *
  70. * We attempt to configure the logging so it will be forced to roll the log files during the test.
  71. * Rolling the log files requires creating and opening a new file.
  72. * This could be a performance hit, so we want our benchmark to take this into account.
  73. **/
  74. + (void)configureLoggingForSuite2
  75. {
  76. [DDLog removeAllLoggers];
  77. [DDLog addLogger:[self fileLogger]];
  78. }
  79. /**
  80. * Suite 3 - Logging to Console & File.
  81. **/
  82. + (void)configureLoggingForSuite3
  83. {
  84. [DDLog removeAllLoggers];
  85. [DDLog addLogger:[DDOSLogger sharedInstance]];
  86. [DDLog addLogger:[self ttyLogger]];
  87. [DDLog addLogger:[self fileLogger]];
  88. }
  89. + (void)executeTestsWithBase:(BOOL)exeBase framework:(BOOL)exeFramework frameworkSuite:(int)suiteNum
  90. {
  91. if (!exeBase && !exeFramework) return;
  92. int sn = suiteNum - 1; // Zero-indexed for array
  93. int i, j, k;
  94. int start = exeBase ? 0 : 1;
  95. int finish = exeFramework ? 3 : 1;
  96. for (i = start; i < finish; i++)
  97. {
  98. Class class;
  99. switch (i)
  100. {
  101. case 0 : class = [BaseNSLogging class]; break;
  102. case 1 : class = [StaticLogging class]; break;
  103. default : class = [DynamicLogging class]; break;
  104. }
  105. for (j = 0; j < 5; j++)
  106. {
  107. NSTimeInterval min = DBL_MAX;
  108. NSTimeInterval max = DBL_MIN;
  109. NSTimeInterval total = 0.0;
  110. for (k = 0; k < NUMBER_OF_RUNS; k++)
  111. {
  112. @autoreleasepool {
  113. NSDate *startDate = [NSDate date];
  114. switch (j)
  115. {
  116. case 0 : [class performSelector:@selector(speedTest0)]; break;
  117. case 1 : [class performSelector:@selector(speedTest1)]; break;
  118. case 2 : [class performSelector:@selector(speedTest2)]; break;
  119. case 3 : [class performSelector:@selector(speedTest3)]; break;
  120. default : [class performSelector:@selector(speedTest4)]; break;
  121. }
  122. NSTimeInterval result = [startDate timeIntervalSinceNow] * -1.0;
  123. min = MIN(min, result);
  124. max = MAX(max, result);
  125. total += result;
  126. }
  127. [DDLog flushLog];
  128. }
  129. if (i == 0)
  130. {
  131. // Base
  132. base[j][0] = min;
  133. base[j][1] = total / (double)NUMBER_OF_RUNS;
  134. base[j][2] = max;
  135. }
  136. else
  137. {
  138. // Framework
  139. fmwk[sn][i-1][j][0] = min;
  140. fmwk[sn][i-1][j][1] = total / (double)NUMBER_OF_RUNS;
  141. fmwk[sn][i-1][j][2] = max;
  142. }
  143. }
  144. }
  145. }
  146. + (NSString *)printableResultsForSuite:(int)suiteNum
  147. {
  148. int sn = suiteNum - 1; // Zero-indexed for array
  149. NSMutableString *str = [NSMutableString stringWithCapacity:2000];
  150. [str appendFormat:@"Results are given as [min][avg][max] calculated over the course of %i runs.", NUMBER_OF_RUNS];
  151. [str appendString:@"\n\n"];
  152. [str appendString:@"Test 0:\n"];
  153. [str appendFormat:@"Execute %i log statements.\n", SPEED_TEST_0_COUNT];
  154. [str appendString:@"The log statement is above the log level threshold, and will not execute.\n"];
  155. [str appendString:@"The StaticLogging class will compile it out (in release mode).\n"];
  156. [str appendString:@"The DynamicLogging class will require a single integer comparison.\n"];
  157. [str appendString:@"\n"];
  158. [str appendFormat:@"BaseNSLogging :[%.4f][%.4f][%.4f]\n", base[0][0], base[0][1], base[0][2]];
  159. [str appendFormat:@"StaticLogging :[%.4f][%.4f][%.4f]\n", fmwk[sn][0][0][0], fmwk[sn][0][0][1], fmwk[sn][0][0][2]];
  160. [str appendFormat:@"DynamicLogging:[%.4f][%.4f][%.4f]\n", fmwk[sn][1][0][0], fmwk[sn][1][0][1], fmwk[sn][1][0][2]];
  161. [str appendString:@"\n\n\n"];
  162. [str appendString:@"Test 1:\n"];
  163. [str appendFormat:@"Execute %i log statements.\n", SPEED_TEST_1_COUNT];
  164. [str appendString:@"The log statement is at or below the log level threshold, and will execute.\n"];
  165. [str appendString:@"The logging framework will execute the statements Asynchronously.\n"];
  166. [str appendString:@"\n"];
  167. [str appendFormat:@"BaseNSLogging :[%.4f][%.4f][%.4f]\n", base[1][0], base[1][1], base[1][2]];
  168. [str appendFormat:@"StaticLogging :[%.4f][%.4f][%.4f]\n", fmwk[sn][0][1][0], fmwk[sn][0][1][1], fmwk[sn][0][1][2]];
  169. [str appendFormat:@"DynamicLogging:[%.4f][%.4f][%.4f]\n", fmwk[sn][1][1][0], fmwk[sn][1][1][1], fmwk[sn][1][1][2]];
  170. [str appendString:@"\n\n\n"];
  171. [str appendString:@"Test 2:\n"];
  172. [str appendFormat:@"Execute %i log statements.\n", SPEED_TEST_2_COUNT];
  173. [str appendString:@"The log statement is at or below the log level threshold, and will execute.\n"];
  174. [str appendString:@"The logging framework will execute the statements Synchronously.\n"];
  175. [str appendString:@"\n"];
  176. [str appendFormat:@"BaseNSLogging :[%.4f][%.4f][%.4f]\n", base[2][0], base[2][1], base[2][2]];
  177. [str appendFormat:@"StaticLogging :[%.4f][%.4f][%.4f]\n", fmwk[sn][0][2][0], fmwk[sn][0][2][1], fmwk[sn][0][2][2]];
  178. [str appendFormat:@"DynamicLogging:[%.4f][%.4f][%.4f]\n", fmwk[sn][1][2][0], fmwk[sn][1][2][1], fmwk[sn][1][2][2]];
  179. [str appendString:@"\n\n\n"];
  180. [str appendString:@"Test 3:"];
  181. [str appendFormat:@"Execute %i log statements per level.\n", SPEED_TEST_3_COUNT];
  182. [str appendString:@"This is designed to mimic what might happen in a regular application.\n"];
  183. [str appendString:@"25% will be above log level threshold and will be filtered out.\n"];
  184. [str appendString:@"50% will execute Asynchronously.\n"];
  185. [str appendString:@"25% will execute Synchronously.\n"];
  186. [str appendString:@"\n"];
  187. [str appendFormat:@"BaseNSLogging :[%.4f][%.4f][%.4f]\n", base[3][0], base[3][1], base[3][2]];
  188. [str appendFormat:@"StaticLogging :[%.4f][%.4f][%.4f]\n", fmwk[sn][0][3][0], fmwk[sn][0][3][1], fmwk[sn][0][3][2]];
  189. [str appendFormat:@"DynamicLogging:[%.4f][%.4f][%.4f]\n", fmwk[sn][1][3][0], fmwk[sn][1][3][1], fmwk[sn][1][3][2]];
  190. [str appendString:@"\n\n\n"];
  191. float total = 0.0F;
  192. total += SPEED_TEST_4_VERBOSE_COUNT;
  193. total += SPEED_TEST_4_INFO_COUNT;
  194. total += SPEED_TEST_4_WARN_COUNT;
  195. total += SPEED_TEST_4_ERROR_COUNT;
  196. float verbose = (float)SPEED_TEST_4_VERBOSE_COUNT / total * 100.0F;
  197. float info = (float)SPEED_TEST_4_INFO_COUNT / total * 100.0F;
  198. float warn = (float)SPEED_TEST_4_WARN_COUNT / total * 100.0F;
  199. float error = (float)SPEED_TEST_4_ERROR_COUNT / total * 100.0F;
  200. [str appendString:@"Test 4:\n"];
  201. [str appendString:@"Similar to test 3, designed to mimic a real application\n"];
  202. [str appendFormat:@"Execute %i log statements in total.\n", (int)total];
  203. [str appendFormat:@"%04.1f%% will be above log level threshold and will be filtered out.\n", (double)verbose];
  204. [str appendFormat:@"%04.1f%% will execute Asynchronously.\n", (double)(info + warn)];
  205. [str appendFormat:@"%04.1f%% will execute Synchronously.\n", (double)error];
  206. [str appendString:@"\n"];
  207. [str appendFormat:@"BaseNSLogging :[%.4f][%.4f][%.4f]\n", base[4][0], base[4][1], base[4][2]];
  208. [str appendFormat:@"StaticLogging :[%.4f][%.4f][%.4f]\n", fmwk[sn][0][4][0], fmwk[sn][0][4][1], fmwk[sn][0][4][2]];
  209. [str appendFormat:@"DynamicLogging:[%.4f][%.4f][%.4f]\n", fmwk[sn][1][4][0], fmwk[sn][1][4][1], fmwk[sn][1][4][2]];
  210. [str appendString:@"\n\n\n"];
  211. return str;
  212. }
  213. + (NSString *)csvResults
  214. {
  215. NSMutableString *str = [NSMutableString stringWithCapacity:1000];
  216. // What are we trying to do here?
  217. //
  218. // What we ultimately want is to compare the performance of the framework against the baseline.
  219. // This means we want to see the performance of the baseline for test 1,
  220. // and then right next to it we want to see the performance of the framework with each various configuration.
  221. //
  222. // So we want it to kinda look like this for Test 1:
  223. //
  224. // Base, [min], [avg], [max]
  225. // Suite 1 - Static, [min], [avg], [max]
  226. // Suite 1 - Dynamic, [min], [avg], [max]
  227. // Suite 2 - Static, [min], [avg], [max]
  228. // Suite 2 - Dynamic, [min], [avg], [max]
  229. // Suite 3 - Static, [min], [avg], [max]
  230. // Suite 3 - Dynamic, [min], [avg], [max]
  231. //
  232. // This will import into Excel just fine.
  233. // However, I couldn't get Excel to make a decent looking graph with the data.
  234. // Perhaps I'm just not familiar enough with Excel.
  235. // But I was able to download OmniGraphSketcher,
  236. // and figure out how to create an awesome looking graph in less than 15 minutes.
  237. // And thus OmniGraphSketcher wins for me.
  238. // The only catch is that it wants to import the data with numbers instead of names.
  239. // So I need to convert the output to look like this:
  240. //
  241. // 0, [min], [avg], [max]
  242. // 1, [min], [avg], [max]
  243. // 2, [min], [avg], [max]
  244. // 3, [min], [avg], [max]
  245. // 4, [min], [avg], [max]
  246. // 5, [min], [avg], [max]
  247. // 6, [min], [avg], [max]
  248. //
  249. // I can then import the data into OmniGraphSketcher, and rename the X-axis points.
  250. // static NSTimeInterval base[5][3]; // [test][min,avg,max]
  251. //
  252. // static NSTimeInterval fmwk[3][2][5][3]; // [suite][file][test][min,avg,max]
  253. int row = 0;
  254. int suite, file, test;
  255. for (test = 0; test < 5; test++)
  256. {
  257. [str appendFormat:@"%i, %.4f, %.4f, %.4f\n", row++, base[test][0], base[test][1], base[test][2]];
  258. for (suite = 0; suite < 3; suite++)
  259. {
  260. for (file = 0; file < 2; file++)
  261. {
  262. [str appendFormat:@"%i, %.4f, %.4f, %.4f\n", row++,
  263. fmwk[suite][file][test][0], fmwk[suite][file][test][1], fmwk[suite][file][test][2]];
  264. }
  265. }
  266. row += 3;
  267. }
  268. return str;
  269. }
  270. + (void)startPerformanceTests
  271. {
  272. BOOL runBase = YES;
  273. BOOL runSuite1 = YES;
  274. BOOL runSuite2 = YES;
  275. BOOL runSuite3 = YES;
  276. if (!runBase && !runSuite1 && !runSuite2 && !runSuite3)
  277. {
  278. // Nothing to do, all suites disabled
  279. return;
  280. }
  281. NSLog(@"Preparing to start performance tests...");
  282. NSLog(@"The results will be printed nicely when all logging has completed.\n\n");
  283. [NSThread sleepForTimeInterval:3.0];
  284. if (runBase)
  285. {
  286. [self executeTestsWithBase:YES framework:NO frameworkSuite:0];
  287. }
  288. NSString *printableResults1 = nil;
  289. NSString *printableResults2 = nil;
  290. NSString *printableResults3 = nil;
  291. if (runSuite1)
  292. {
  293. [self configureLoggingForSuite1];
  294. [self executeTestsWithBase:NO framework:YES frameworkSuite:1];
  295. printableResults1 = [self printableResultsForSuite:1];
  296. NSLog(@"\n\n\n\n");
  297. }
  298. if (runSuite2)
  299. {
  300. [self configureLoggingForSuite2];
  301. [self executeTestsWithBase:NO framework:YES frameworkSuite:2];
  302. printableResults2 = [self printableResultsForSuite:2];
  303. NSLog(@"\n\n\n\n");
  304. }
  305. if (runSuite3)
  306. {
  307. [self configureLoggingForSuite3];
  308. [self executeTestsWithBase:NO framework:YES frameworkSuite:3];
  309. printableResults3 = [self printableResultsForSuite:3];
  310. NSLog(@"\n\n\n\n");
  311. }
  312. if (runSuite1)
  313. {
  314. NSLog(@"======================================================================");
  315. NSLog(@"Benchmark Suite 1:");
  316. NSLog(@"Logging framework configured to log to console only.");
  317. NSLog(@"\n\n%@", printableResults1);
  318. NSLog(@"======================================================================");
  319. }
  320. if (runSuite2)
  321. {
  322. NSLog(@"======================================================================");
  323. NSLog(@"Benchmark Suite 2:");
  324. NSLog(@"Logging framework configured to log to file only.");
  325. NSLog(@"\n\n%@", printableResults2);
  326. NSLog(@"======================================================================");
  327. }
  328. if (runSuite3)
  329. {
  330. NSLog(@"======================================================================");
  331. NSLog(@"Benchmark Suite 3:");
  332. NSLog(@"Logging framework configured to log to console & file.");
  333. NSLog(@"\n\n%@", printableResults3);
  334. NSLog(@"======================================================================");
  335. }
  336. #if TARGET_OS_IPHONE
  337. NSString *csvResultsPath = [@"~/Documents/LumberjackBenchmark.csv" stringByExpandingTildeInPath];
  338. #else
  339. NSString *csvResultsPath = [@"~/Desktop/LumberjackBenchmark.csv" stringByExpandingTildeInPath];
  340. #endif
  341. if (![[NSFileManager defaultManager] fileExistsAtPath:csvResultsPath])
  342. {
  343. [[NSFileManager defaultManager] createFileAtPath:csvResultsPath contents:nil attributes:nil];
  344. }
  345. NSFileHandle *csvResultsFile = [NSFileHandle fileHandleForWritingAtPath:csvResultsPath];
  346. NSData *csvResults = [[self csvResults] dataUsingEncoding:NSUTF8StringEncoding];
  347. if (csvResults != nil)
  348. {
  349. [csvResultsFile writeData:csvResults];
  350. }
  351. NSLog(@"CSV results file written to:\n%@", csvResultsPath);
  352. }
  353. @end