utils.js.org 9.1 KB

/ 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){ 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; };