server.dart 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239
  1. import 'dart:async' show Future;
  2. import 'dart:io';
  3. import 'dart:json' as json;
  4. import 'dart:math' show Random;
  5. import 'package:args/args.dart' show ArgParser;
  6. import 'package:mustache/mustache.dart' as mustache;
  7. import 'package:postgresql/postgresql.dart' as pg;
  8. import 'package:postgresql/postgresql_pool.dart' as pgpool;
  9. import 'package:yaml/yaml.dart' as yaml;
  10. /// Starts a new HTTP server that implements the tests to be benchmarked. The
  11. /// address and port for incoming connections is configurable via command line
  12. /// arguments, as is the number of database connections to be maintained in the
  13. /// connection pool.
  14. main() {
  15. var parser = new ArgParser();
  16. parser.addOption('address', abbr: 'a', defaultsTo: '0.0.0.0');
  17. parser.addOption('port', abbr: 'p', defaultsTo: '8080');
  18. parser.addOption('dbconnections', abbr: 'd', defaultsTo: '256');
  19. var arguments = parser.parse(new Options().arguments);
  20. _startServer(
  21. arguments['address'],
  22. int.parse(arguments['port']),
  23. int.parse(arguments['dbconnections']));
  24. }
  25. /// The entity used in the database query and update tests.
  26. class World {
  27. int id;
  28. int randomNumber;
  29. World(this.id, this.randomNumber);
  30. toJson() => { 'id': id, 'randomNumber': randomNumber };
  31. }
  32. /// The entity used in the fortunes test.
  33. class Fortune implements Comparable<Fortune> {
  34. int id;
  35. String message;
  36. Fortune(this.id, this.message);
  37. compareTo(Fortune other) => message.compareTo(other.message);
  38. }
  39. /// The number of rows in the world entity table.
  40. const _WORLD_TABLE_SIZE = 10000;
  41. /// A random number generator.
  42. final _RANDOM = new Random();
  43. /// The 'text/html; charset=utf-8' content type.
  44. final _TYPE_HTML = new ContentType('text', 'html', charset: 'utf-8');
  45. /// The 'application/json' content type.
  46. final _TYPE_JSON = new ContentType('application', 'json');
  47. /// The 'text/html; charset=utf-8' content type.
  48. final _TYPE_TEXT = new ContentType('text', 'plain', charset: 'utf-8');
  49. /// The PostgreSQL connection pool used by all the tests that require database
  50. /// connectivity.
  51. var _connectionPool;
  52. /// The mustache template which is rendered in the fortunes test.
  53. var _fortunesTemplate;
  54. /// Starts a benchmark server, which listens for connections from
  55. /// '[address] : [port]' and maintains [dbConnections] connections to the
  56. /// database.
  57. _startServer(address, port, dbConnections) {
  58. Future.wait([
  59. new File('postgresql.yaml').readAsString().then((config) {
  60. _connectionPool = new pgpool.Pool(
  61. new pg.Settings.fromMap(yaml.loadYaml(config)).toUri(),
  62. min: dbConnections,
  63. max: dbConnections);
  64. return _connectionPool.start();
  65. }),
  66. new File('fortunes.mustache').readAsString().then((template) {
  67. _fortunesTemplate = mustache.parse(template);
  68. })
  69. ]).then((_) {
  70. HttpServer.bind(address, port).then((server) {
  71. server.listen((request) {
  72. switch (request.uri.path) {
  73. case '/':
  74. _jsonTest(request);
  75. break;
  76. case '/db':
  77. _dbTest(request);
  78. break;
  79. case '/fortunes':
  80. _fortunesTest(request);
  81. break;
  82. case '/update':
  83. _updateTest(request);
  84. break;
  85. case '/plaintext':
  86. _plaintextTest(request);
  87. break;
  88. default:
  89. _sendResponse(request, HttpStatus.NOT_FOUND);
  90. break;
  91. }
  92. });
  93. });
  94. });
  95. }
  96. /// Returns the given [text] parsed as a base 10 integer. If the text is null
  97. /// or is an otherwise invalid representation of a base 10 integer, zero is
  98. /// returned.
  99. _parseInt(text) =>
  100. (text == null) ? 0 : int.parse(text, radix: 10, onError: ((_) => 0));
  101. /// Completes the given [request] by writing the [response] with the given
  102. /// [statusCode] and [type].
  103. _sendResponse(request, statusCode, [ type, response ]) {
  104. request.response.statusCode = statusCode;
  105. request.response.headers.add(HttpHeaders.SERVER, 'dart');
  106. request.response.headers.date = new DateTime.now();
  107. //
  108. // Prevent GZIP encoding, because it is disallowed in the rules for these
  109. // benchmark tests.
  110. //
  111. request.response.headers.add(HttpHeaders.CONTENT_ENCODING, '');
  112. if (type != null) {
  113. request.response.headers.contentType = type;
  114. }
  115. if (response != null) {
  116. request.response.write(response);
  117. }
  118. request.response.close();
  119. }
  120. /// Completes the given [request] by writing the [response] as HTML.
  121. _sendHtml(request, response) {
  122. _sendResponse(request, HttpStatus.OK, _TYPE_HTML, response);
  123. }
  124. /// Completes the given [request] by writing the [response] as JSON.
  125. _sendJson(request, response) {
  126. _sendResponse(request, HttpStatus.OK, _TYPE_JSON, json.stringify(response));
  127. }
  128. /// Completes the given [request] by writing the [response] as plain text.
  129. _sendText(request, response) {
  130. _sendResponse(request, HttpStatus.OK, _TYPE_TEXT, response);
  131. }
  132. /// Responds with the JSON test to the [request].
  133. _jsonTest(request) {
  134. _sendJson(request, { 'message': 'Hello, World!' });
  135. }
  136. /// Responds with the database query test to the [request].
  137. _dbTest(request) {
  138. var queries = _parseInt(request.uri.queryParameters['queries']).clamp(1, 500);
  139. var worlds = new List<World>(queries);
  140. Future.wait(new List.generate(queries, (index) {
  141. return _connectionPool.connect().then((connection) {
  142. return connection.query(
  143. 'SELECT id, randomNumber FROM world WHERE id = @id;',
  144. { 'id': _RANDOM.nextInt(_WORLD_TABLE_SIZE) + 1 })
  145. .toList()
  146. .then((rows) {
  147. //
  148. // The benchmark's constraints tell us there is exactly one row.
  149. //
  150. var row = rows[0];
  151. worlds[index] = new World(row[0], row[1]);
  152. })
  153. .whenComplete(() { connection.close(); });
  154. });
  155. }, growable: false)).then((_) { _sendJson(request, worlds); });
  156. }
  157. /// Responds with the fortunes test to the [request].
  158. _fortunesTest(request) {
  159. var fortunes = [];
  160. _connectionPool.connect().then((connection) {
  161. return connection.query('SELECT id, message FROM fortune;')
  162. .toList()
  163. .then((rows) {
  164. for (var row in rows) {
  165. fortunes.add(new Fortune(row[0], row[1]));
  166. }
  167. })
  168. .whenComplete(() { connection.close(); });
  169. }).then((_) {
  170. fortunes.add(new Fortune(0, 'Additional fortune added at request time.'));
  171. fortunes.sort();
  172. _sendHtml(request, _fortunesTemplate.renderString({
  173. 'fortunes': fortunes.map((fortune) => {
  174. 'id': fortune.id, 'message': fortune.message
  175. }).toList()
  176. }));
  177. });
  178. }
  179. /// Responds with the updates test to the [request].
  180. _updateTest(request) {
  181. var queries = _parseInt(request.uri.queryParameters['queries']).clamp(1, 500);
  182. var worlds = new List<World>(queries);
  183. Future.wait(new List.generate(queries, (index) {
  184. return _connectionPool.connect().then((connection) {
  185. return connection.query(
  186. 'SELECT id, randomNumber FROM world WHERE id = @id;',
  187. { 'id': _RANDOM.nextInt(_WORLD_TABLE_SIZE) + 1 })
  188. .toList()
  189. .then((rows) {
  190. //
  191. // The benchmark's constraints tell us there is exactly one row.
  192. //
  193. var row = rows[0];
  194. worlds[index] = new World(row[0], row[1]);
  195. })
  196. .whenComplete(() { connection.close(); });
  197. });
  198. }, growable: false)).then((_) {
  199. Future.wait(new List.generate(queries, (int index) {
  200. var world = worlds[index];
  201. world.randomNumber = _RANDOM.nextInt(_WORLD_TABLE_SIZE) + 1;
  202. return _connectionPool.connect().then((connection) {
  203. return connection.execute(
  204. 'UPDATE world SET randomNumber = @randomNumber WHERE id = @id;',
  205. { 'randomNumber': world.randomNumber, 'id': world.id })
  206. .whenComplete(() { connection.close(); });
  207. });
  208. }, growable: false)).then((_) { _sendJson(request, worlds); });
  209. });
  210. }
  211. /// Responds with the plaintext test to the [request].
  212. _plaintextTest(request) {
  213. _sendText(request, 'Hello, World!');
  214. }