// Copyright 2015 The MemDB Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or // implied. See the License for the specific language governing // permissions and limitations under the License. See the AUTHORS file // for names of contributors. 'use strict'; var _ = require('lodash'); var util = require('util'); var P = require('bluebird'); var child_process = require('child_process'); var uuid = require('node-uuid'); // Add some usefull promise methods exports.extendPromise = function(P){ // This is designed for large array // The original map with concurrency option does not release memory P.mapLimit = function(items, fn, limit){ if(!limit){ limit = 1000; } var groups = []; var group = []; items.forEach(function(item){ group.push(item); if(group.length >= limit){ groups.push(group); group = []; } }); if(group.length > 0){ groups.push(group); } var results = []; var promise = P.resolve(); groups.forEach(function(group){ promise = promise.then(function(){ return P.map(group, fn) .then(function(ret){ ret.forEach(function(item){ results.push(item); }); }); }); }); return promise.thenReturn(results); }; P.mapSeries = function(items, fn){ var results = []; return P.each(items, function(item){ return P.try(function(){ return fn(item); }) .then(function(ret){ results.push(ret); }); }) .thenReturn(results); }; }; exports.uuid = function(){ return uuid.v4(); }; exports.isEmpty = function(obj){ for(var key in obj){ return false; } return true; }; exports.getObjPath = function(obj, path){ var current = obj; path.split('.').forEach(function(field){ if(!!current){ current = current[field]; } }); return current; }; exports.setObjPath = function(obj, path, value){ if(typeof(obj) !== 'object'){ throw new Error('not object'); } var current = obj; var fields = path.split('.'); var finalField = fields.pop(); fields.forEach(function(field){ if(!current.hasOwnProperty(field)){ current[field] = {}; } current = current[field]; if(typeof(current) !== 'object'){ throw new Error('field ' + path + ' exists and not a object'); } }); current[finalField] = value; }; exports.deleteObjPath = function(obj, path){ if(typeof(obj) !== 'object'){ throw new Error('not object'); } var current = obj; var fields = path.split('.'); var finalField = fields.pop(); fields.forEach(function(field){ if(!!current){ current = current[field]; } }); if(current !== undefined){ delete current[finalField]; } }; exports.clone = function(obj){ return JSON.parse(JSON.stringify(obj)); }; exports.isDict = function(obj){ return typeof(obj) === 'object' && obj !== null && !Array.isArray(obj); }; // escape '$' and '.' in field name // '$abc.def\\g' => '\\u0024abc\\u002edef\\\\g' exports.escapeField = function(str){ return str.replace(/\\/g, '\\\\').replace(/\$/g, '\\u0024').replace(/\./g, '\\u002e'); }; exports.unescapeField = function(str){ return str.replace(/\\u002e/g, '.').replace(/\\u0024/g, '$').replace(/\\\\/g, '\\'); }; // Async foreach for mongo's cursor exports.mongoForEach = function(itor, func){ var deferred = P.defer(); var next = function(err){ if(err){ return deferred.reject(err); } // async iterator with .next(cb) itor.next(function(err, value){ if(err){ return deferred.reject(err); } if(value === null){ return deferred.resolve(); } P.try(function(){ return func(value); }) .nodeify(next); }); }; next(); return deferred.promise; }; exports.remoteExec = function(ip, cmd, opts){ ip = ip || '127.0.0.1'; opts = opts || {}; var user = opts.user || process.env.USER; var successCodes = opts.successCodes || [0]; var child = null; // localhost with current user if((ip === '127.0.0.1' || ip.toLowerCase() === 'localhost') && user === process.env.USER){ cmd = cmd.replace(/\\/g, '/'); var pos = cmd.indexOf('node.exe'); if (pos != -1) { cmd = cmd.substring(pos, cmd.length); } child = child_process.spawn('bash', ['-c', cmd]); } // run remote via ssh else{ child = child_process.spawn('ssh', ['-o StrictHostKeyChecking=no', user + '@' + ip, 'bash -c \'' + cmd + '\'']); } var deferred = P.defer(); var stdout = '', stderr = ''; child.stdout.on('data', function(data){ stdout += data; }); child.stderr.on('data', function(data){ stderr += data; }); child.on('exit', function(code, signal){ if(successCodes.indexOf(code) !== -1){ deferred.resolve(stdout); } else{ deferred.reject(new Error(util.format('remoteExec return code %s on %s@%s - %s\n%s', code, user, ip, cmd, stderr))); } }); return deferred.promise; }; exports.waitUntil = function(fn, checkInterval){ if(!checkInterval){ checkInterval = 100; } var deferred = P.defer(); var check = function(){ if(fn()){ deferred.resolve(); } else{ setTimeout(check, checkInterval); } }; check(); return deferred.promise; }; exports.rateCounter = function(opts){ opts = opts || {}; var perserveSeconds = opts.perserveSeconds || 3600; var sampleSeconds = opts.sampleSeconds || 5; var counts = {}; var cleanInterval = null; var getCurrentSlot = function(){ return Math.floor(Date.now() / 1000 / sampleSeconds); }; var beginSlot = getCurrentSlot(); var counter = { inc : function(){ var slotNow = getCurrentSlot(); if(!counts.hasOwnProperty(slotNow)){ counts[slotNow] = 0; } counts[slotNow]++; }, reset : function(){ counts = {}; beginSlot = getCurrentSlot(); }, clean : function(){ var slotNow = getCurrentSlot(); Object.keys(counts).forEach(function(slot){ if(slot < slotNow - Math.floor(perserveSeconds / sampleSeconds)){ delete counts[slot]; } }); }, rate : function(lastSeconds){ var slotNow = getCurrentSlot(); var total = 0; var startSlot = slotNow - Math.floor(lastSeconds / sampleSeconds); if(startSlot < beginSlot){ startSlot = beginSlot; } for(var slot = startSlot; slot < slotNow; slot++){ total += counts[slot] || 0; } return total / ((slotNow - startSlot) * sampleSeconds); }, stop : function(){ clearInterval(cleanInterval); }, counts : function(){ return counts; } }; cleanInterval = setInterval(function(){ counter.clean(); }, sampleSeconds * 1000); return counter; }; exports.hrtimer = function(autoStart){ var total = 0; var starttime = null; var timer = { start : function(){ if(starttime){ return; } starttime = process.hrtime(); }, stop : function(){ if(!starttime){ return; } var timedelta = process.hrtime(starttime); total += timedelta[0] * 1000 + timedelta[1] / 1000000; return total; }, total : function(){ return total; //in ms }, }; if(autoStart){ timer.start(); } return timer; }; exports.timeCounter = function(){ var counts = {}; return { add : function(name, time){ if(!counts.hasOwnProperty(name)){ counts[name] = [0, 0, 0]; // total, count, average } var count = counts[name]; count[0] += time; count[1]++; count[2] = count[0] / count[1]; }, reset : function(){ counts = {}; }, getCounts : function(){ return counts; }, }; }; // trick v8 to not use hidden class // https://github.com/joyent/node/issues/25661 exports.forceHashMap = function(){ var obj = {k : 1}; delete obj.k; return obj; };