utils.js 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360
  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. var _ = require('lodash');
  17. var util = require('util');
  18. var P = require('bluebird');
  19. var child_process = require('child_process');
  20. var uuid = require('node-uuid');
  21. // Add some usefull promise methods
  22. exports.extendPromise = function(P){
  23. // This is designed for large array
  24. // The original map with concurrency option does not release memory
  25. P.mapLimit = function(items, fn, limit){
  26. if(!limit){
  27. limit = 1000;
  28. }
  29. var groups = [];
  30. var group = [];
  31. items.forEach(function(item){
  32. group.push(item);
  33. if(group.length >= limit){
  34. groups.push(group);
  35. group = [];
  36. }
  37. });
  38. if(group.length > 0){
  39. groups.push(group);
  40. }
  41. var results = [];
  42. var promise = P.resolve();
  43. groups.forEach(function(group){
  44. promise = promise.then(function(){
  45. return P.map(group, fn)
  46. .then(function(ret){
  47. ret.forEach(function(item){
  48. results.push(item);
  49. });
  50. });
  51. });
  52. });
  53. return promise.thenReturn(results);
  54. };
  55. P.mapSeries = function(items, fn){
  56. var results = [];
  57. return P.each(items, function(item){
  58. return P.try(function(){
  59. return fn(item);
  60. })
  61. .then(function(ret){
  62. results.push(ret);
  63. });
  64. })
  65. .thenReturn(results);
  66. };
  67. };
  68. exports.uuid = function(){
  69. return uuid.v4();
  70. };
  71. exports.isEmpty = function(obj){
  72. for(var key in obj){
  73. return false;
  74. }
  75. return true;
  76. };
  77. exports.getObjPath = function(obj, path){
  78. var current = obj;
  79. path.split('.').forEach(function(field){
  80. if(!!current){
  81. current = current[field];
  82. }
  83. });
  84. return current;
  85. };
  86. exports.setObjPath = function(obj, path, value){
  87. if(typeof(obj) !== 'object'){
  88. throw new Error('not object');
  89. }
  90. var current = obj;
  91. var fields = path.split('.');
  92. var finalField = fields.pop();
  93. fields.forEach(function(field){
  94. if(!current.hasOwnProperty(field)){
  95. current[field] = {};
  96. }
  97. current = current[field];
  98. if(typeof(current) !== 'object'){
  99. throw new Error('field ' + path + ' exists and not a object');
  100. }
  101. });
  102. current[finalField] = value;
  103. };
  104. exports.deleteObjPath = function(obj, path){
  105. if(typeof(obj) !== 'object'){
  106. throw new Error('not object');
  107. }
  108. var current = obj;
  109. var fields = path.split('.');
  110. var finalField = fields.pop();
  111. fields.forEach(function(field){
  112. if(!!current){
  113. current = current[field];
  114. }
  115. });
  116. if(current !== undefined){
  117. delete current[finalField];
  118. }
  119. };
  120. exports.clone = function(obj){
  121. return JSON.parse(JSON.stringify(obj));
  122. };
  123. exports.isDict = function(obj){
  124. return typeof(obj) === 'object' && obj !== null && !Array.isArray(obj);
  125. };
  126. // escape '$' and '.' in field name
  127. // '$abc.def\\g' => '\\u0024abc\\u002edef\\\\g'
  128. exports.escapeField = function(str){
  129. return str.replace(/\\/g, '\\\\').replace(/\$/g, '\\u0024').replace(/\./g, '\\u002e');
  130. };
  131. exports.unescapeField = function(str){
  132. return str.replace(/\\u002e/g, '.').replace(/\\u0024/g, '$').replace(/\\\\/g, '\\');
  133. };
  134. // Async foreach for mongo's cursor
  135. exports.mongoForEach = function(itor, func){
  136. var deferred = P.defer();
  137. var next = function(err){
  138. if(err){
  139. return deferred.reject(err);
  140. }
  141. // async iterator with .next(cb)
  142. itor.next(function(err, value){
  143. if(err){
  144. return deferred.reject(err);
  145. }
  146. if(value === null){
  147. return deferred.resolve();
  148. }
  149. P.try(function(){
  150. return func(value);
  151. })
  152. .nodeify(next);
  153. });
  154. };
  155. next();
  156. return deferred.promise;
  157. };
  158. exports.remoteExec = function(ip, cmd, opts){
  159. ip = ip || '127.0.0.1';
  160. opts = opts || {};
  161. var user = opts.user || process.env.USER;
  162. var successCodes = opts.successCodes || [0];
  163. var child = null;
  164. // localhost with current user
  165. if((ip === '127.0.0.1' || ip.toLowerCase() === 'localhost') && user === process.env.USER){
  166. cmd = cmd.replace(/\\/g, '/');
  167. var pos = cmd.indexOf('node.exe');
  168. if (pos != -1) {
  169. cmd = cmd.substring(pos, cmd.length);
  170. }
  171. child = child_process.spawn('bash', ['-c', cmd]);
  172. }
  173. // run remote via ssh
  174. else{
  175. child = child_process.spawn('ssh', ['-o StrictHostKeyChecking=no', user + '@' + ip, 'bash -c \'' + cmd + '\'']);
  176. }
  177. var deferred = P.defer();
  178. var stdout = '', stderr = '';
  179. child.stdout.on('data', function(data){
  180. stdout += data;
  181. });
  182. child.stderr.on('data', function(data){
  183. stderr += data;
  184. });
  185. child.on('exit', function(code, signal){
  186. if(successCodes.indexOf(code) !== -1){
  187. deferred.resolve(stdout);
  188. }
  189. else{
  190. deferred.reject(new Error(util.format('remoteExec return code %s on %s@%s - %s\n%s', code, user, ip, cmd, stderr)));
  191. }
  192. });
  193. return deferred.promise;
  194. };
  195. exports.waitUntil = function(fn, checkInterval){
  196. if(!checkInterval){
  197. checkInterval = 100;
  198. }
  199. var deferred = P.defer();
  200. var check = function(){
  201. if(fn()){
  202. deferred.resolve();
  203. }
  204. else{
  205. setTimeout(check, checkInterval);
  206. }
  207. };
  208. check();
  209. return deferred.promise;
  210. };
  211. exports.rateCounter = function(opts){
  212. opts = opts || {};
  213. var perserveSeconds = opts.perserveSeconds || 3600;
  214. var sampleSeconds = opts.sampleSeconds || 5;
  215. var counts = {};
  216. var cleanInterval = null;
  217. var getCurrentSlot = function(){
  218. return Math.floor(Date.now() / 1000 / sampleSeconds);
  219. };
  220. var beginSlot = getCurrentSlot();
  221. var counter = {
  222. inc : function(){
  223. var slotNow = getCurrentSlot();
  224. if(!counts.hasOwnProperty(slotNow)){
  225. counts[slotNow] = 0;
  226. }
  227. counts[slotNow]++;
  228. },
  229. reset : function(){
  230. counts = {};
  231. beginSlot = getCurrentSlot();
  232. },
  233. clean : function(){
  234. var slotNow = getCurrentSlot();
  235. Object.keys(counts).forEach(function(slot){
  236. if(slot < slotNow - Math.floor(perserveSeconds / sampleSeconds)){
  237. delete counts[slot];
  238. }
  239. });
  240. },
  241. rate : function(lastSeconds){
  242. var slotNow = getCurrentSlot();
  243. var total = 0;
  244. var startSlot = slotNow - Math.floor(lastSeconds / sampleSeconds);
  245. if(startSlot < beginSlot){
  246. startSlot = beginSlot;
  247. }
  248. for(var slot = startSlot; slot < slotNow; slot++){
  249. total += counts[slot] || 0;
  250. }
  251. return total / ((slotNow - startSlot) * sampleSeconds);
  252. },
  253. stop : function(){
  254. clearInterval(cleanInterval);
  255. },
  256. counts : function(){
  257. return counts;
  258. }
  259. };
  260. cleanInterval = setInterval(function(){
  261. counter.clean();
  262. }, sampleSeconds * 1000);
  263. return counter;
  264. };
  265. exports.hrtimer = function(autoStart){
  266. var total = 0;
  267. var starttime = null;
  268. var timer = {
  269. start : function(){
  270. if(starttime){
  271. return;
  272. }
  273. starttime = process.hrtime();
  274. },
  275. stop : function(){
  276. if(!starttime){
  277. return;
  278. }
  279. var timedelta = process.hrtime(starttime);
  280. total += timedelta[0] * 1000 + timedelta[1] / 1000000;
  281. return total;
  282. },
  283. total : function(){
  284. return total; //in ms
  285. },
  286. };
  287. if(autoStart){
  288. timer.start();
  289. }
  290. return timer;
  291. };
  292. exports.timeCounter = function(){
  293. var counts = {};
  294. return {
  295. add : function(name, time){
  296. if(!counts.hasOwnProperty(name)){
  297. counts[name] = [0, 0, 0]; // total, count, average
  298. }
  299. var count = counts[name];
  300. count[0] += time;
  301. count[1]++;
  302. count[2] = count[0] / count[1];
  303. },
  304. reset : function(){
  305. counts = {};
  306. },
  307. getCounts : function(){
  308. return counts;
  309. },
  310. };
  311. };
  312. // trick v8 to not use hidden class
  313. // https://github.com/joyent/node/issues/25661
  314. exports.forceHashMap = function(){
  315. var obj = {k : 1};
  316. delete obj.k;
  317. return obj;
  318. };