stress.js 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226
  1. // Copyright 2015 The MemDB Authors.
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
  12. // implied. See the License for the specific language governing
  13. // permissions and limitations under the License. See the AUTHORS file
  14. // for names of contributors.
  15. 'use strict';
  16. // run with node >= 0.12 with --harmony option
  17. // If logLevel >= INFO, change log4js.json - set appender type to fileSync (otherwise log buffer may eat out memory)
  18. var minimist = require('minimist');
  19. var crypto = require('crypto');
  20. var P = require('bluebird');
  21. var _ = require('lodash');
  22. var uuid = require('node-uuid');
  23. var env = require('./env');
  24. var memdb = require('../lib');
  25. var mongodb = P.promisifyAll(require('mongodb'));
  26. var logger = memdb.logger.getLogger('test', __filename);
  27. var isMaster = true;
  28. var concurrency = 1000;
  29. var areaCount = 200;
  30. var maxAreaPlayers = 10;
  31. var randomRoute = false; // turn on this can slow down performance
  32. var newPlayerIntervalValue = 10;
  33. var currentConcurrency = 0;
  34. var route = function(id){
  35. var shardIds = Object.keys(env.config.shards);
  36. if(id === null || id === undefined){
  37. return _.sample(shardIds);
  38. }
  39. var md5 = crypto.createHash('md5').update(String(id)).digest('hex');
  40. var hash = parseInt(md5.substr(0, 8), 16);
  41. var index = randomRoute ? _.random(shardIds.length - 1) : (hash % shardIds.length);
  42. return shardIds[index];
  43. };
  44. var playerThread = P.coroutine(function*(db, playerId){
  45. var Player = db.collection('player');
  46. var Area = db.collection('area');
  47. var playInArea = P.coroutine(function*(playerId, areaId){
  48. // join area
  49. var success = yield db.transaction(P.coroutine(function*(){
  50. var area = yield Area.find(areaId);
  51. if(!area){
  52. area = {_id : areaId, playerCount : 0};
  53. yield Area.insert(area);
  54. }
  55. if(area.playerCount >= maxAreaPlayers){
  56. return false;
  57. }
  58. yield Area.update(areaId, {$inc : {playerCount : 1}});
  59. yield Player.update(playerId, {$set : {areaId : areaId}});
  60. return true;
  61. }), route(areaId));
  62. if(!success){
  63. return;
  64. }
  65. for(var i=0; i<_.random(10); i++){
  66. yield P.delay(_.random(1000));
  67. // earn scores from other players in the area
  68. yield db.transaction(P.coroutine(function*(){
  69. var players = yield Player.find({areaId : areaId});
  70. if(players.length <= 1){
  71. return;
  72. }
  73. for(var i=0; i<players.length; i++){
  74. var player = players[i];
  75. if(player._id !== playerId){
  76. yield Player.update(player._id, {$set : {score : player.score - 1}});
  77. }
  78. else{
  79. yield Player.update(player._id, {$set : {score : player.score + players.length - 1}});
  80. }
  81. }
  82. }), route(areaId));
  83. }
  84. // quit area
  85. yield db.transaction(P.coroutine(function*(){
  86. yield Area.update(areaId, {$inc : {playerCount : -1}});
  87. yield Player.update(playerId, {$set : {areaId : null}});
  88. var area = yield Area.find(areaId);
  89. if(area.playerCount === 0){
  90. yield Area.remove(areaId);
  91. }
  92. }), route(areaId));
  93. });
  94. yield db.transaction(P.coroutine(function*(){
  95. yield Player.insert({_id : playerId, areaId : null, score : 0});
  96. }), route(playerId));
  97. for(var i=0; i<_.random(10); i++){
  98. yield P.delay(_.random(10 * 1000));
  99. var areaId = _.random(areaCount);
  100. yield playInArea(playerId, areaId);
  101. }
  102. // yield db.transaction(P.coroutine(function*(){
  103. // yield Player.remove(playerId);
  104. // }), route(playerId));
  105. });
  106. var checkConsistency = P.coroutine(function*(){
  107. var db = yield P.promisify(mongodb.MongoClient.connect)(env.config.backend.url);
  108. var playerCount = yield db.collection('player').countAsync();
  109. var areaCount = yield db.collection('area').countAsync();
  110. logger.warn('playerCount: %s, areaCount: %s', playerCount, areaCount);
  111. var ret = yield db.collection('player').aggregateAsync([{$group : {_id : null, total : {$sum :'$score'}}}]);
  112. if(ret[0].total !== 0){
  113. logger.fatal('consistency check FAIL');
  114. }
  115. else{
  116. logger.warn('consistency check PASS');
  117. }
  118. });
  119. var isShutingDown = false;
  120. var newPlayerInterval = null;
  121. var shutdown = P.coroutine(function*(){
  122. if(isShutingDown){
  123. return;
  124. }
  125. isShutingDown = true;
  126. try{
  127. clearInterval(newPlayerInterval);
  128. if(isMaster){
  129. env.stopCluster();
  130. yield checkConsistency();
  131. }
  132. }
  133. catch(e){
  134. logger.error(e.stack);
  135. }
  136. finally{
  137. memdb.logger.shutdown(process.exit);
  138. }
  139. });
  140. var main = P.coroutine(function*(opts){
  141. isMaster = !opts.slave;
  142. if(opts.hasOwnProperty('concurrency')){
  143. concurrency = parseInt(opts.concurrency);
  144. }
  145. if(opts.hasOwnProperty('areaCount')){
  146. areaCount = parseInt(opts.areaCount);
  147. }
  148. if(opts.hasOwnProperty('maxAreaPlayers')){
  149. maxAreaPlayers = parseInt(opts.maxAreaPlayers);
  150. }
  151. if(opts.hasOwnProperty('randomRoute')){
  152. randomRoute = !!opts.randomRoute;
  153. }
  154. if(isMaster){
  155. env.flushdb();
  156. env.startCluster();
  157. }
  158. var autoconn = yield memdb.autoConnect(env.config);
  159. newPlayerInterval = setInterval(P.coroutine(function*(){
  160. if(currentConcurrency >= concurrency){
  161. return;
  162. }
  163. currentConcurrency++;
  164. var playerId = uuid.v4();
  165. try{
  166. logger.warn('player %s start', playerId);
  167. logger.warn('current concurrency: %s', currentConcurrency);
  168. yield playerThread(autoconn, playerId);
  169. }
  170. catch(e){
  171. logger.error('player %s error: %s', playerId, e.stack);
  172. }
  173. finally{
  174. logger.warn('player %s finish', playerId);
  175. currentConcurrency--;
  176. }
  177. }), newPlayerIntervalValue);
  178. process.on('SIGTERM', shutdown);
  179. process.on('SIGINT', shutdown);
  180. });
  181. if (require.main === module) {
  182. var opts = minimist(process.argv.slice(2));
  183. main(opts);
  184. }