ignore only node_modules in root

This commit is contained in:
Unitech 2015-07-24 19:47:36 +02:00
parent 5c7e06874a
commit da5964c03b
88 changed files with 5358 additions and 1 deletions

2
.gitignore vendored
View File

@ -1,4 +1,4 @@
node_modules
/node_modules
*.log
*.log
*.pid

View File

@ -0,0 +1,7 @@
node_modules
*.log
*.log
test/child
*.iml
.idea/**
sample/pmx-server-stats

View File

@ -0,0 +1,10 @@
language: node_js
branches:
only:
- master
- development
node_js:
- "iojs"
- "0.12"
- "0.11"
- "0.10"

View File

@ -0,0 +1,18 @@
# 0.2.27
- Remove debug message
- Rename module
- Auto instanciation
# 0.2.25
- Add ip address on each transaction
# 0.2.24
- Add unit option for Histogram and Meter
# 0.2.23
- Include Counter, Meter, Metric and Histogram

303
test/fixtures/module-fixture/node_modules/pmx/README.md generated vendored Normal file
View File

@ -0,0 +1,303 @@
# Driver for Keymetrics
![Keymetrics](https://keymetrics.io/assets/images/application-demo.png)
PMX is a module that allows you to create advanced interactions with Keymetrics.
With it you can:
- Trigger remote actions / functions
- Analyze custom metrics / variables (with utilities like Histogram/Counter/Metric/Meters)
- Report errors (uncaught exceptions and custom errors)
- Emit events
- Analyze HTTP latency
# Installation
![Build Status](https://api.travis-ci.org/keymetrics/pmx.png?branch=master)
Install PMX and add it to your package.json via:
```bash
$ npm install pmx --save
```
Then init the module to monitor HTTP, Errors and diverse metrics.
```javascript
var pmx = require('pmx').init(); // By default everything is enabled and ignore_routes is empty
```
Or choose what to monitor.
```javascript
var pmx = require('pmx').init({
http : true, // HTTP routes logging (default: true)
ignore_routes : [/socket\.io/, /notFound/], // Ignore http routes with this pattern (Default: [])
errors : true, // Exceptions loggin (default: true)
custom_probes : true, // Custom probes (default: true)
network : true, // Traffic usage monitoring (default: false)
ports : true // Shows which ports your app is listening on (default: false)
});
```
# Custom monitoring
## Emit Events
Emit events and get historical and statistics:
```javascript
var pmx = require('pmx');
pmx.emit('user:register', {
user : 'Alex registered',
email : 'thorustor@gmail.com'
});
```
## Custom Action
Trigger function from Keymetrics
### Long running
```javascript
var pmx = require('pmx');
pmx.action('db:clean', { comment : 'Description for this action' }, function(reply) {
clean.db(function() {
/**
* reply() must be called at the end of the action
*/
reply({success : true});
});
});
```
## Errors
Catch uncaught exceptions:
```javascript
var pmx = require('pmx').init();
```
Attach more data from errors that happens in Express:
```javascript
var pmx = require('pmx');
app.get('/' ...);
app.post(...);
app.use(pmx.expressErrorHandler());
```
Trigger custom errors:
```javascript
var pmx = require('pmx');
pmx.notify({ success : false });
pmx.notify('This is an error');
pmx.notify(new Error('This is an error'));
```
## TCP network usage monitoring
If you enable the flag `network: true` when you init pmx it will show network usage datas (download and upload) in realtime.
If you enable the flag `ports: true` when you init pmx it will show which ports your app is listenting on.
## HTTP latency analysis
Monitor routes, latency and codes. REST compliant.
```javascript
pmx.http(); // You must do this BEFORE any require('http')
```
Ignore some routes by passing a list of regular expressions.
```javascript
pmx.http({
http : true, // (Default: true)
ignore_routes : [/socket\.io/, /notFound/] // Ignore http routes with this pattern (Default: [])
});
```
This can also be done via pmx.init()
```javascript
pmx.init({
http : true, // (Default: true)
ignore_routes : [/socket\.io/, /notFound/] // Ignore http routes with this pattern (Default: [])
});
```
**This module is enabled by default if you called pmx with the init() function.**
## Measure
Measure critical segments of you code thanks to 4 kind of probes:
- Simple metrics: Values that can be read instantly
- Monitor variable value
- Counter: Things that increment or decrement
- Downloads being processed, user connected
- Meter: Things that are measured as events / interval
- Request per minute for a http server
- Histogram: Keeps a resevoir of statistically relevant values biased towards the last 5 minutes to explore their distribution
- Monitor the mean of execution of a query into database
#### Common options
- `name` : The probe name as is will be displayed on the **Keymetrics** dashboard
- `agg_type` : This param is optionnal, it can be `sum`, `max`, `min`, `avg` (default) or `none`. It will impact the way the probe data are aggregated within the **Keymetrics** backend. Use `none` if this is irrelevant (eg: constant or string value).
### Metric
Values that can be read instantly.
```javascript
var probe = pmx.probe();
var metric = probe.metric({
name : 'Realtime user',
agg_type: 'max',
value : function() {
return Object.keys(users).length;
}
});
```
### Counter
Things that increment or decrement.
```javascript
var probe = pmx.probe();
var counter = probe.counter({
name : 'Downloads',
agg_type: 'sum'
});
http.createServer(function(req, res) {
counter.inc();
req.on('end', function() {
counter.dec();
});
});
```
### Meter
Things that are measured as events / interval.
```javascript
var probe = pmx.probe();
var meter = probe.meter({
name : 'req/sec',
samples : 1,
timeframe : 60
});
http.createServer(function(req, res) {
meter.mark();
res.end({success:true});
});
```
#### Options
**samples** option is the rate unit. Defaults to **1** sec.
**timeframe** option is the timeframe over which events will be analyzed. Defaults to **60** sec.
### Histogram
Keeps a resevoir of statistically relevant values biased towards the last 5 minutes to explore their distribution.
```javascript
var probe = pmx.probe();
var histogram = probe.histogram({
name : 'latency',
measurement : 'mean'
});
var latency = 0;
setInterval(function() {
latency = Math.round(Math.random() * 100);
histogram.update(latency);
}, 100);
```
#### Options
**measurement** option can be:
- min: The lowest observed value.
- max: The highest observed value.
- sum: The sum of all observed values.
- variance: The variance of all observed values.
- mean: The average of all observed values.
- stddev: The stddev of all observed values.
- count: The number of observed values.
- median: 50% of all values in the resevoir are at or below this value.
- p75: See median, 75% percentile.
- p95: See median, 95% percentile.
- p99: See median, 99% percentile.
- p999: See median, 99.9% percentile.
## Expose data (JSON object)
```javascript
pmx.transpose('variable name', function() { return my_data });
// or
pmx.tranpose({
name : 'variable name',
value : function() { return my_data; }
});
```
## Modules
### Simple app
```
process.env.MODULE_DEBUG = true;
var pmx = require('pmx');
var conf = pmx.initModule();
```
# Beta
### Long running with data emitter (scoped action)
A scoped action is an action that can emit logs related to this action.
```javascript
var pmx = require('pmx');
pmx.scopedAction('scoped:action', function(options, res) {
var i = setInterval(function() {
// Emit progress data
if (error)
res.error('oops');
else
res.send('this is a chunk of data');
}, 1000);
setTimeout(function() {
clearInterval(i);
return res.end();
}, 8000);
});
```
# License
MIT

View File

@ -0,0 +1,21 @@
{
"name": "example-module",
"version": "0.3.21",
"description": "Keymetrics++ and PM2 adapter",
"main": "scoped-action.js",
"dependencies": {
},
"scripts": {
"test": "DEBUG='axm:*' mocha test/*.mocha.js"
},
"repository": {
"type": "git",
"url": "https://github.com/keymetrics/pmx.git"
},
"config" : {
"aconfig-var" : true,
"var2" : false
},
"author": "Keymetrics I/O",
"license": "MIT"
}

View File

@ -0,0 +1,70 @@
var pmx = require('..');
var conf = pmx.initModule({
widget : {
type : 'generic',
logo : 'https://app.keymetrics.io/img/logo/keymetrics-300.png',
// 0 = main element
// 1 = secondary
// 2 = main border
// 3 = secondary border
theme : ['#141A1F', '#222222', '#3ff', '#3ff'],
el : {
probes : true,
actions : true
},
block : {
actions : true,
issues : true,
meta : true
}
// Status
// Green / Yellow / Red
}
});
pmx.scopedAction('testo', function(data, emitter) {
var i = setInterval(function() {
emitter.send('datard');
}, 100);
setTimeout(function() {
emitter.end('end');
clearInterval(i);
}, 3000);
});
var spawn = require('child_process').spawn;
pmx.scopedAction('long running lsof', function(data, res) {
var child = spawn('lsof', []);
child.stdout.on('data', function(chunk) {
chunk.toString().split('\n').forEach(function(line) {
res.send(line);
});
});
child.stdout.on('end', function(chunk) {
res.end('end');
});
});
pmx.action('simple action', function(reply) {
return reply({success:true});
});
pmx.action('simple with arg', function(opts,reply) {
return reply(opts);
});

View File

@ -0,0 +1,2 @@
module.exports = exports = require("./lib");

View File

@ -0,0 +1,180 @@
var Counter = require('./utils/probes/Counter.js');
var Histogram = require('./utils/probes/Histogram.js');
var Meter = require('./utils/probes/Meter.js');
var Transport = require('./utils/transport.js');
var debug = require('debug')('axm:probe');
var Probe = {};
Probe._started = false;
Probe._var = {};
Probe.AVAILABLE_AGG_TYPES = ['avg', 'min', 'max', 'sum', 'none'];
Probe.AVAILABLE_MEASUREMENTS = [
'min',
'max',
'sum',
'count',
'variance',
'mean',
'stddev',
'median',
'p75',
'p95',
'p99',
'p999'
];
Probe.default_aggregation = 'avg';
function cookData(data) {
var cooked_data = {};
Object.keys(data).forEach(function(probe_name) {
var value = data[probe_name].value;
if (typeof(value) == 'function')
value = value();
else
value = value;
cooked_data[probe_name] = {
value: value
};
if (data[probe_name].agg_type &&
data[probe_name].agg_type != 'none')
cooked_data[probe_name].agg_type = data[probe_name].agg_type;
});
return cooked_data;
};
Probe.probe = function() {
if (Probe._started == false) {
Probe._started = true;
setInterval(function() {
Transport.send({
type : 'axm:monitor',
data : cookData(Probe._var)
});
}, 990);
}
return {
/**
* This reflect data to keymetrics
* pmx.transpose('prop name', fn)
*
* or
*
* pmx.transpose({
* name : 'variable name',
* data : function() { return value }
* });
*/
transpose : function(variable_name, reporter) {
if (typeof variable_name === 'object') {
reporter = variable_name.data;
variable_name = variable_name.name;
}
if (typeof reporter !== 'function') {
return console.error('[PMX] reporter is not a function');
}
Probe._var[variable_name] = {
value: reporter
};
},
metric : function(opts) {
var agg_type = opts.agg_type || Probe.default_aggregation;
if (!opts.name)
return console.error('[Probe][Metric] Name not defined');
if (typeof(opts.value) === 'undefined')
return console.error('[Probe][Metric] Value not defined');
if (Probe.AVAILABLE_AGG_TYPES.indexOf(agg_type) == -1)
return console.error("[Probe][Metric] Unknown agg_type: %s", agg_type);
if (opts.value)
Probe._var[opts.name] = {
value: opts.value,
agg_type: agg_type
};
return {
val : function() {
var value = Probe._var[opts.name].value;
if (typeof(value) == 'function')
value = value();
return value;
},
set : function(dt) { Probe._var[opts.name].value = dt }
}
},
histogram : function(opts) {
if (!opts.name)
return console.error('[Probe][Histogram] Name not defined');
opts.measurement = opts.measurement || 'mean';
opts.unit = opts.unit || '';
var agg_type = opts.agg_type || Probe.default_aggregation;
if (Probe.AVAILABLE_MEASUREMENTS.indexOf(opts.measurement) == -1)
return console.error('[Probe][Histogram] Measure type %s does not exists', opts.measurement);
if (Probe.AVAILABLE_AGG_TYPES.indexOf(agg_type) == -1)
return console.error("[Probe][Metric] Unknown agg_type: %s", agg_type);
var histogram = new Histogram(opts);
Probe._var[opts.name] = {
value: function() { return (Math.round(histogram.val() * 100) / 100) + '' + opts.unit },
agg_type: agg_type
};
return histogram;
},
meter : function(opts) {
var agg_type = opts.agg_type || Probe.default_aggregation;
if (!opts.name)
return console.error('[Probe][Meter] Name not defined');
if (Probe.AVAILABLE_AGG_TYPES.indexOf(agg_type) == -1)
return console.error("[Probe][Metric] Unknown agg_type: %s", agg_type);
opts.unit = opts.unit || '';
var meter = new Meter(opts);
Probe._var[opts.name] = {
value: function() { return meter.val() + '' + opts.unit },
agg_type: agg_type
};
return meter;
},
counter : function(opts) {
var agg_type = opts.agg_type || Probe.default_aggregation;
if (!opts.name)
return console.error('[Probe][Counter] Name not defined');
if (Probe.AVAILABLE_AGG_TYPES.indexOf(agg_type) == -1)
return console.error("[Probe][Metric] Unknown agg_type: %s", agg_type);
var counter = new Counter();
Probe._var[opts.name] = {
value: function() { return counter.val() },
agg_type: agg_type
};
return counter;
},
}
};
module.exports = Probe;

View File

@ -0,0 +1,147 @@
var domain = require('domain');
var debug = require('debug')('axm:events');
var Common = require('./common.js');
var Transport = require('./utils/transport.js');
var Actions = {};
Actions.action = function(action_name, opts, fn) {
if (!fn) {
fn = opts;
opts = null;
}
if (!action_name)
return console.error('[PMX] action.action_name is missing');
if (!fn)
return console.error('[PMX] emit.data is mission');
if (!process.send) {
debug('Process not running within PM2');
return false;
}
// Notify the action
Transport.send({
type : 'axm:action',
data : {
action_name : action_name,
opts : opts,
arity : fn.length
}
});
function reply(data) {
Transport.send({
type : 'axm:reply',
data : {
return : data,
action_name : action_name
}
});
}
process.on('message', function(data) {
if (!data) return false;
// In case 2 arguments has been set but no options has been transmitted
if (fn.length === 2 && typeof(data) === 'string' && data === action_name)
return fn({}, reply);
// In case 1 arguments has been set but options has been transmitted
if (fn.length === 1 && typeof(data) === 'object' && data.msg === action_name)
return fn(reply);
/**
* Classical call
*/
if (typeof(data) === 'string' && data === action_name)
return fn(reply);
/**
* If data is an object == v2 protocol
* Pass the opts as first argument
*/
if (typeof(data) === 'object' && data.msg === action_name)
return fn(data.opts, reply);
});
};
Actions.scopedAction = function(action_name, fn) {
if (!action_name)
return console.error('[PMX] action.action_name is missing');
if (!fn)
return console.error('[PMX] callback is missing');
if (!process.send) {
debug('Process not running within PM2');
return false;
}
// Notify the action
Transport.send({
type : 'axm:action',
data : {
action_name : action_name,
action_type : 'scoped'
}
});
process.on('message', function(data) {
if (!data
|| data.uuid === undefined
|| data.action_name === undefined)
return false;
if (data.action_name === action_name) {
var res = {
send : function(dt) {
Transport.send({
type : 'axm:scoped_action:stream',
data : {
data : dt,
uuid : data.uuid,
action_name : action_name
}
});
},
error : function(dt) {
Transport.send({
type : 'axm:scoped_action:error',
data : {
data : dt,
uuid : data.uuid,
action_name : action_name
}
});
},
end : function(dt) {
Transport.send({
type : 'axm:scoped_action:end',
data : {
data : dt,
uuid : data.uuid,
action_name : action_name
}
});
}
};
var d = domain.create();
d.on('error', function(err) {
res.error({error : err});
setTimeout(function() {
process.exit(1);
}, 300);
});
d.run(function() {
fn(data.opts || null, res);
});
}
});
};
module.exports = Actions;

View File

@ -0,0 +1,6 @@
var Common = module.exports = {};
Common.getDate = function getDate() {
return Math.round(Date.now() / 1000);
};

View File

@ -0,0 +1,26 @@
var debug = require('debug')('axm:events');
var Transport = require('./utils/transport.js');
var Common = require('./common.js');
var stringify = require('json-stringify-safe');
var Events = {};
Events.emit = function(name, data) {
if (!name)
return console.error('[AXM] emit.name is missing');
if (!data)
return console.error('[AXM] emit.data is missing');
var inflight_obj = JSON.parse(stringify(data));
inflight_obj.__name = name;
Transport.send({
type : 'human:event',
data : inflight_obj
}, true);
return false;
};
module.exports = Events;

View File

@ -0,0 +1,66 @@
var Events = require('./events.js');
var Actions = require('./actions.js');
var Notify = require('./notify.js');
var Transaction = require('./transaction.js');
var Network = require('./network.js');
var Monitor = require('./monitor.js');
var Profiling = require('./probes/profiling.js');
var Probe = require('./Probe.js');
var Pm2Module = require('./pm2_module.js');
var util = require('util');
var Export = {};
/**
* Flatten API
*/
util._extend(Export, Events);
util._extend(Export, Actions);
util._extend(Export, Notify);
util._extend(Export, Monitor);
util._extend(Export, Pm2Module);
util._extend(Export, Probe);
util._extend(Export, Transaction);
util._extend(Export, Network);
util._extend(Export, Profiling);
Export.init = function(opts) {
if (!opts) opts = {};
opts = util._extend({
http : true,
http_latency : 200,
http_code : 500,
ignore_routes : [],
profiling : true,
errors : true,
custom_probes : true,
network : false,
ports : false
}, opts);
if (opts.ports)
Export.catchPorts();
if (opts.network)
Export.catchTraffic();
Export.http(opts);
Export.catchAll(opts);
if (opts.profiling)
Profiling.v8Profiling(Export);
if (opts.custom_probes) {
// Event loop monitoring
require('./probes/pacemaker.js')(Export);
}
return this;
};
/**
* Export
*/
module.exports = Export;

View File

@ -0,0 +1,49 @@
var Transport = require('./utils/transport.js');
var debug = require('debug')('axm:monitor');
var Monitor = {};
function cookData(data) {
var cooked_data = {};
Object.keys(data).forEach(function(probe_name) {
if (typeof(data[probe_name]) == 'function')
cooked_data[probe_name] = data[probe_name]();
else
cooked_data[probe_name] = data[probe_name];
});
return cooked_data;
};
function enableProbes(custom_namespace) {
if (!custom_namespace)
custom_namespace = 'axm';
if (!global[custom_namespace])
global[custom_namespace] = {};
if (this.interval)
return global[custom_namespace];
this.interval = setInterval(function() {
Transport.send({
type : 'axm:monitor',
data : cookData(global[custom_namespace])
});
}, 990);
return global[custom_namespace];
};
function stopProbing() {
clearInterval(this.interval);
}
Monitor.enableProbes = enableProbes;
Monitor.enableProbe = enableProbes;
Monitor.stopProbe = stopProbing;
Monitor.stopProbes = stopProbing;
module.exports = Monitor;

View File

@ -0,0 +1,116 @@
var net_module = require('net');
var Probe = require('./Probe.js');
var Network = module.exports = {};
Network.catchPorts = function() {
var ports_list = [];
var opened_ports = 'N/A';
Probe.probe().metric({
name : 'Open ports',
value : function() { return opened_ports; }
});
var original_listen = net_module.Server.prototype.listen;
net_module.Server.prototype.listen = function() {
var port = parseInt(arguments[0]);
if (!isNaN(port) && ports_list.indexOf(port) === -1) {
ports_list.push(port);
opened_ports = ports_list.sort().join();
}
this.once('close', function() {
if (ports_list.indexOf(port) > -1) {
ports_list.splice(ports_list.indexOf(port), 1);
opened_ports = ports_list.sort().join();
}
});
return original_listen.apply(this, arguments);
};
};
Network.catchTraffic = function() {
var download = 0;
var upload = 0;
var up = '0 B/sec';
var down = '0 B/sec';
var filter = function(bytes) {
var to_fixed = 0;
if (bytes === 0)
;
else if (bytes < 1024)
to_fixed = 6;
else if (bytes < (1024 * 1024))
to_fixed = 3;
else
to_fixed = 2;
bytes = (bytes / (1024 * 1024)).toFixed(to_fixed);
var cut_zeros = 0;
for (var i = (bytes.length - 1); i > 0; --i) {
if (bytes[i] === '.') {
++cut_zeros;
break;
}
if (bytes[i] !== '0')
break;
++cut_zeros;
}
if (cut_zeros > 0)
bytes = bytes.slice(0, -(cut_zeros));
return (bytes + ' MB/s');
};
setInterval(function() {
up = filter(upload);
down = filter(download);
upload = 0;
download = 0;
}, 999);
Probe.probe().metric({
name : 'Network Download',
agg_type : 'sum',
value : function() { return down; }
});
Probe.probe().metric({
name : 'Network Upload',
agg_type : 'sum',
value : function() { return up; }
});
var original_write = net_module.Socket.prototype.write;
net_module.Socket.prototype.write = function(data) {
if (data.length)
upload += data.length;
return original_write.apply(this, arguments);
};
var original_read = net_module.Socket.prototype.read;
net_module.Socket.prototype.read = function() {
if (!this.monitored) {
this.monitored = true;
this.on('data', function(data) {
if (data.length)
download += data.length;
});
}
return original_read.apply(this, arguments);
};
};

View File

@ -0,0 +1,117 @@
var debug = require('debug')('axm:notify');
var util = require('util');
var Common = require('./common.js');
var Options = require('./pm2_module.js');
var Transport = require('./utils/transport.js');
var Notify = {};
var jsonize = function(err, filter, space) {
if (typeof(err) != 'object')
return err;
var plainObject = {};
Object.getOwnPropertyNames(err).forEach(function(key) {
plainObject[key] = err[key];
});
return plainObject;
};
Notify.catchAll = function(opts) {
if (opts === undefined)
opts = { errors : true };
Options.configureModule({
error : opts.errors
});
if (process.env.exec_mode == 'cluster_mode')
return false;
var catchException = function(err) {
//debug(err.stack || err);
Transport.send({
type : 'process:exception',
data : jsonize(err)
}, true);
console.error(err.stack || err);
process.exit(255);
};
if (opts.errors === true
&& util.inspect(process.listeners('uncaughtException')).length === 2) {
process.once('uncaughtException', catchException);
}
else if (opts.errors === false
&& util.inspect(process.listeners('uncaughtException')).length !== 2) {
process.removeAllListeners('uncaughtException');
}
};
Notify._interpretError = function(err) {
var s_err = {};
if (typeof(err) === 'string') {
// Simple string processing
s_err = new Error(err);
}
else if (!(err instanceof Error) && typeof(err) === 'object') {
// JSON processing
s_err = new Error(JSON.stringify(err));
s_err.data = err;
}
else if (err instanceof Error) {
// Error object type processing
s_err = err;
}
return jsonize(s_err);
};
Notify.notify = function(err) {
var ret_err = this._interpretError(err);
// full_err
//debug(ret_err);
Transport.send({
type : 'process:exception',
data : ret_err
}, true);
return ret_err;
};
Notify.expressErrorHandler = function() {
var self = this;
Options.configureModule({
error : true
});
return function errorHandler(err, req, res, next) {
if (res.statusCode < 400) res.statusCode = 500;
//debug(err.stack || err);
err.url = req.url;
err.component = req.url;
err.action = req.method;
err.params = req.body;
err.session = req.session;
Transport.send({
type : 'process:exception',
data : jsonize(err)
}, true);
return next(err);
};
};
module.exports = Notify;

View File

@ -0,0 +1,110 @@
var debug = require('debug')('axm:events');
var Transport = require('./utils/transport.js');
var path = require('path');
var fs = require('fs');
var util = require('util');
var Options = {};
Options.configureModule = function(opts) {
if (!this.running) {
this.running = true;
/* Avoid automatic exit of the script */
setInterval(function() {}, 1000);
}
Transport.send({
type : 'axm:option:configuration',
data : opts
}, false);
};
/**
* Load config and merge with data from package.json
*/
Options.loadConfig = Options.initModule = function(conf) {
var package_filepath = path.resolve(path.dirname(require.main.filename), 'package.json');
if (!conf)
conf = {};
if (!conf.module_conf)
conf.module_conf = {};
conf = util._extend(conf, {
errors : false,
latency : false,
versioning : false,
show_module_meta : false
});
/**
* Merge package.json metadata
*/
try {
var package_json = require(package_filepath);
conf.module_version = package_json.version;
conf.module_name = package_json.name;
conf.description = package_json.description;
if (package_json.config) {
conf = util._extend(conf, package_json.config);
conf.module_conf = package_json.config;
}
} catch(e) {
throw new Error('[PMX] package.json problem (not found or mal formated', e);
}
/**
* If custom variables has been set, merge with returned configuration
*/
try {
if (process.env[package_json.name]) {
conf = util._extend(conf, JSON.parse(process.env[package_json.name]));
conf.module_conf = util._extend(conf.module_conf, JSON.parse(process.env[package_json.name]));
}
} catch(e) {
console.error(e);
console.error('Ezssadrror while parsing configuration in environment (%s)', package_json.name);
}
Options.configureModule(conf);
return conf;
};
Options.getPID = function(file) {
if (typeof(file) === 'number')
return file;
return parseInt(fs.readFileSync(file).toString());
};
Options.resolvePidPaths = function(filepaths) {
if (typeof(filepaths) === 'number')
return filepaths;
function detect(filepaths) {
var content = '';
filepaths.some(function(filepath) {
try {
content = fs.readFileSync(filepath);
} catch(e) {
return false;
}
return true;
});
return content.toString().trim();
}
var ret = parseInt(detect(filepaths));
return isNaN(ret) ? null : ret;
};
module.exports = Options;

View File

@ -0,0 +1,19 @@
module.exports = function(pmx) {
var TIME_INTERVAL = 1000;
var oldTime = process.hrtime();
var histogram = pmx.probe().histogram({
name : 'Loop delay',
measurement : 'mean',
unit : 'ms'
});
setInterval(function() {
var newTime = process.hrtime();
var delay = (newTime[0] - oldTime[0]) * 1e3 + (newTime[1] - oldTime[1]) / 1e6 - TIME_INTERVAL;
oldTime = newTime;
histogram.update(delay);
}, TIME_INTERVAL);
};

View File

@ -0,0 +1,121 @@
var debug = require('debug')('axm:profiling');
var os = require('os');
var path = require('path');
var fs = require('fs');
var Options = require('../pm2_module.js');
var Profiling = module.exports = {};
Profiling.exposeProfiling = function(pmx, profiler_path) {
try {
var profiler = require(profiler_path);
} catch(e) {
debug('v8-profiler module not installed', e);
return false;
}
debug('v8-profiler sucessfully enabled');
/**
* Tell Keymetrics that profiling is possible
* (flag available in axm_options object)
*/
Options.configureModule({
heapdump : true
});
/**
* Heap snapshot
*/
pmx.action('km:heapdump', function(reply) {
var dump_file = path.join(os.tmpDir(), Date.now() + '.heapsnapshot');
var snapshot = profiler.takeSnapshot('km-heap-snapshot');
var buffer = '';
snapshot.serialize(
function iterator(data, length) {
buffer += data;
}, function complete() {
fs.writeFile(dump_file, buffer, function (err) {
debug('Heap dump file flushed (e=', err);
if (err) {
return reply({
success : false,
err : err
});
}
return reply({
success : true,
heapdump : true,
dump_file : dump_file
});
});
}
);
});
/**
* CPU profiling snapshot
*/
var ns_cpu_profiling = 'km-cpu-profiling';
var cpu_dump_file = path.join(os.tmpDir(), Date.now() + '.cpuprofile');
pmx.action('km:cpu:profiling:start', function(reply) {
profiler.startProfiling(ns_cpu_profiling);
return reply({ success : true });
});
pmx.action('km:cpu:profiling:stop', function(reply) {
var cpu = profiler.stopProfiling(ns_cpu_profiling);
fs.writeFile(cpu_dump_file, JSON.stringify(cpu), function(err) {
if (err) {
return reply({
success : false,
err : err
});
}
return reply({
success : true,
cpuprofile : true,
dump_file : cpu_dump_file
});
});
});
};
/**
* Discover v8-profiler
*/
Profiling.detectV8Profiler = function(cb) {
var require_paths = require.main.paths;
(function look_for_profiler(require_paths) {
if (!require_paths[0])
return cb(new Error('Module not found'));
var profiler_path = path.join(require_paths[0], 'v8-profiler');
fs.exists(profiler_path, function(exist) {
if (exist)
return cb(null, profiler_path);
require_paths.shift();
return look_for_profiler(require_paths);
});
return false;
})(require_paths);
};
Profiling.v8Profiling = function(pmx) {
Profiling.detectV8Profiler(function(err, profiler_path) {
if (err)
return false;
return Profiling.exposeProfiling(pmx, profiler_path);
});
};

View File

@ -0,0 +1,84 @@
var util = require('util');
var Proxy = require('./utils/proxy.js');
var SimpleHttpWrap = require('./wrapper/simple_http.js');
var Options = require('./pm2_module.js');
var debug = require('debug')('axm:patch');
var Transaction = module.exports = {};
Transaction.http = function(opts) {
var Module = require('module');
debug('Wrapping HTTP routes');
if (Array.isArray(opts)) {
var routes = JSON.parse(JSON.stringify(opts));
opts = {
http : true,
http_latency : 200,
http_code : 500,
ignore_routes : routes
};
}
opts = util._extend({
http : true,
http_latency : 200,
http_code : 500,
ignore_routes : [],
}, opts);
Proxy.wrap(Module, '_load', function(load) {
if (load.__axm_original) {
debug('HTTP routes have already been wrapped before');
Options.configureModule({
latency : opts.http
});
if (opts.http === false) {
return function(file) { return load.__axm_original.apply(this, arguments) };
} else {
return function(file) {
if (file === 'http' || file === 'https')
return SimpleHttpWrap(opts, load.__axm_original.apply(this, arguments));
else
return load.__axm_original.apply(this, arguments);
};
}
}
return function(file) {
if (opts.http &&
(file === 'http' || file === 'https')) {
debug('http module being required');
Options.configureModule({
latency : true
});
return SimpleHttpWrap(opts, load.apply(this, arguments));
}
else
return load.apply(this, arguments);
};
});
};
// Transaction.patch = function() {
// var Module = require('module');
// debug('Patching');
// Proxy.wrap(Module, '_load', function(load) {
// return function(file) {
// if (file == 'redis') {
// return RedisWrap(load.apply(this, arguments));
// }
// else if (file == 'http') {
// return HttpWrap(load.apply(this, arguments));
// }
// else
// return load.apply(this, arguments);
// };
// });
// };

View File

@ -0,0 +1,135 @@
// Hacked https://github.com/felixge/node-measured
// Based on http://en.wikipedia.org/wiki/Binary_Heap
// as well as http://eloquentjavascript.net/appendix2.html
module.exports = BinaryHeap;
function BinaryHeap(options) {
options = options || {};
this._elements = options.elements || [];
this._score = options.score || this._score;
}
BinaryHeap.prototype.add = function(/* elements */) {
for (var i = 0; i < arguments.length; i++) {
var element = arguments[i];
this._elements.push(element);
this._bubble(this._elements.length - 1);
}
};
BinaryHeap.prototype.first = function() {
return this._elements[0];
};
BinaryHeap.prototype.removeFirst = function() {
var root = this._elements[0];
var last = this._elements.pop();
if (this._elements.length > 0) {
this._elements[0] = last;
this._sink(0);
}
return root;
};
BinaryHeap.prototype.clone = function() {
return new BinaryHeap({
elements: this.toArray(),
score: this._score,
});
};
BinaryHeap.prototype.toSortedArray = function() {
var array = [];
var clone = this.clone();
while (true) {
var element = clone.removeFirst();
if (element === undefined) break;
array.push(element);
}
return array;
};
BinaryHeap.prototype.toArray = function() {
return [].concat(this._elements);
};
BinaryHeap.prototype.size = function() {
return this._elements.length;
};
BinaryHeap.prototype._bubble = function(bubbleIndex) {
var bubbleElement = this._elements[bubbleIndex];
var bubbleScore = this._score(bubbleElement);
while (bubbleIndex > 0) {
var parentIndex = this._parentIndex(bubbleIndex);
var parentElement = this._elements[parentIndex];
var parentScore = this._score(parentElement);
if (bubbleScore <= parentScore) break;
this._elements[parentIndex] = bubbleElement;
this._elements[bubbleIndex] = parentElement;
bubbleIndex = parentIndex;
}
};
BinaryHeap.prototype._sink = function(sinkIndex) {
var sinkElement = this._elements[sinkIndex];
var sinkScore = this._score(sinkElement);
var length = this._elements.length;
while (true) {
var swapIndex = null;
var swapScore = null;
var swapElement = null;
var childIndexes = this._childIndexes(sinkIndex);
for (var i = 0; i < childIndexes.length; i++) {
var childIndex = childIndexes[i];
if (childIndex >= length) break;
var childElement = this._elements[childIndex];
var childScore = this._score(childElement);
if (childScore > sinkScore) {
if (swapScore === null || swapScore < childScore) {
swapIndex = childIndex;
swapScore = childScore;
swapElement = childElement;
}
}
}
if (swapIndex === null) break;
this._elements[swapIndex] = sinkElement;
this._elements[sinkIndex] = swapElement;
sinkIndex = swapIndex;
}
};
BinaryHeap.prototype._parentIndex = function(index) {
return Math.floor((index - 1) / 2);
};
BinaryHeap.prototype._childIndexes = function(index) {
return [
2 * index + 1,
2 * index + 2,
];
return ;
};
BinaryHeap.prototype._score = function(element) {
return element.valueOf();
};

View File

@ -0,0 +1,101 @@
// Hacked https://github.com/felixge/node-measured
var BinaryHeap = require('./BinaryHeap');
var units = require('./units');
module.exports = ExponentiallyDecayingSample;
function ExponentiallyDecayingSample(options) {
options = options || {};
this._elements = new BinaryHeap({
score: function(element) {
return -element.priority;
}
});
this._rescaleInterval = options.rescaleInterval || ExponentiallyDecayingSample.RESCALE_INTERVAL;
this._alpha = options.alpha || ExponentiallyDecayingSample.ALPHA;
this._size = options.size || ExponentiallyDecayingSample.SIZE;
this._random = options.random || this._random;
this._landmark = null;
this._nextRescale = null;
}
ExponentiallyDecayingSample.RESCALE_INTERVAL = 1 * units.HOURS;
ExponentiallyDecayingSample.ALPHA = 0.015;
ExponentiallyDecayingSample.SIZE = 1028;
ExponentiallyDecayingSample.prototype.update = function(value, timestamp) {
var now = Date.now();
if (!this._landmark) {
this._landmark = now;
this._nextRescale = this._landmark + this._rescaleInterval;
}
timestamp = timestamp || now;
var newSize = this._elements.size() + 1;
var element = {
priority: this._priority(timestamp - this._landmark),
value: value
};
if (newSize <= this._size) {
this._elements.add(element);
} else if (element.priority > this._elements.first().priority) {
this._elements.removeFirst();
this._elements.add(element);
}
if (now >= this._nextRescale) this._rescale(now);
};
ExponentiallyDecayingSample.prototype.toSortedArray = function() {
return this._elements
.toSortedArray()
.map(function(element) {
return element.value;
});
};
ExponentiallyDecayingSample.prototype.toArray = function() {
return this._elements
.toArray()
.map(function(element) {
return element.value;
});
};
ExponentiallyDecayingSample.prototype._weight = function(age) {
// We divide by 1000 to not run into huge numbers before reaching a
// rescale event.
return Math.exp(this._alpha * (age / 1000));
};
ExponentiallyDecayingSample.prototype._priority = function(age) {
return this._weight(age) / this._random();
};
ExponentiallyDecayingSample.prototype._random = function() {
return Math.random();
};
ExponentiallyDecayingSample.prototype._rescale = function(now) {
now = now || Date.now();
var self = this;
var oldLandmark = this._landmark;
this._landmark = now || Date.now();
this._nextRescale = now + this._rescaleInterval;
var factor = self._priority(-(self._landmark - oldLandmark));
this._elements
.toArray()
.forEach(function(element) {
element.priority *= factor;
});
};

View File

@ -0,0 +1,31 @@
// Hacked https://github.com/felixge/node-measured
var units = require('./units');
module.exports = ExponentiallyWeightedMovingAverage;
function ExponentiallyWeightedMovingAverage(timePeriod, tickInterval) {
this._timePeriod = timePeriod || 1 * units.MINUTE;
this._tickInterval = tickInterval || ExponentiallyWeightedMovingAverage.TICK_INTERVAL;
this._alpha = 1 - Math.exp(-this._tickInterval / this._timePeriod);
this._count = 0;
this._rate = 0;
};
ExponentiallyWeightedMovingAverage.TICK_INTERVAL = 5 * units.SECONDS;
ExponentiallyWeightedMovingAverage.prototype.update = function(n) {
this._count += n;
};
ExponentiallyWeightedMovingAverage.prototype.tick = function() {
var instantRate = this._count / this._tickInterval;
this._count = 0;
this._rate += (this._alpha * (instantRate - this._rate));
};
ExponentiallyWeightedMovingAverage.prototype.rate = function(timeUnit) {
return (this._rate || 0) * timeUnit;
};

View File

@ -0,0 +1,115 @@
function FixedQueue( size, initialValues ){
// If there are no initial arguments, default it to
// an empty value so we can call the constructor in
// a uniform way.
initialValues = (initialValues || []);
// Create the fixed queue array value.
var queue = Array.apply( null, initialValues );
// Store the fixed size in the queue.
queue.fixedSize = size;
// Add the class methods to the queue. Some of these have
// to override the native Array methods in order to make
// sure the queue lenght is maintained.
queue.push = FixedQueue.push;
queue.splice = FixedQueue.splice;
queue.unshift = FixedQueue.unshift;
// Trim any initial excess from the queue.
FixedQueue.trimTail.call( queue );
// Return the new queue.
return( queue );
}
// I trim the queue down to the appropriate size, removing
// items from the beginning of the internal array.
FixedQueue.trimHead = function(){
// Check to see if any trimming needs to be performed.
if (this.length <= this.fixedSize){
// No trimming, return out.
return;
}
// Trim whatever is beyond the fixed size.
Array.prototype.splice.call(
this,
0,
(this.length - this.fixedSize)
);
};
// I trim the queue down to the appropriate size, removing
// items from the end of the internal array.
FixedQueue.trimTail = function(){
// Check to see if any trimming needs to be performed.
if (this.length <= this.fixedSize){
// No trimming, return out.
return;
}
// Trim whatever is beyond the fixed size.
Array.prototype.splice.call(
this,
this.fixedSize,
(this.length - this.fixedSize)
);
};
// I synthesize wrapper methods that call the native Array
// methods followed by a trimming method.
FixedQueue.wrapMethod = function( methodName, trimMethod ){
// Create a wrapper that calls the given method.
var wrapper = function(){
// Get the native Array method.
var method = Array.prototype[ methodName ];
// Call the native method first.
var result = method.apply( this, arguments );
// Trim the queue now that it's been augmented.
trimMethod.call( this );
// Return the original value.
return( result );
};
// Return the wrapper method.
return( wrapper );
};
// Wrap the native methods.
FixedQueue.push = FixedQueue.wrapMethod(
"push",
FixedQueue.trimHead
);
FixedQueue.splice = FixedQueue.wrapMethod(
"splice",
FixedQueue.trimTail
);
FixedQueue.unshift = FixedQueue.wrapMethod(
"unshift",
FixedQueue.trimTail
);

View File

@ -0,0 +1,26 @@
// Hacked from https://github.com/felixge/node-measured
module.exports = Counter;
function Counter(opts) {
opts = opts || {};
this._count = opts.count || 0;
}
Counter.prototype.val = function() {
return this._count;
};
Counter.prototype.inc = function(n) {
this._count += (n || 1);
};
Counter.prototype.dec = function(n) {
this._count -= (n || 1);
};
Counter.prototype.reset = function(count) {
this._count = count || 0;
};

View File

@ -0,0 +1,185 @@
// Hacked from https://github.com/felixge/node-measured
var EDS = require('../EDS.js');
function Histogram(opts) {
var self = this;
opts = opts || {};
this._measurement = opts.measurement;
this._call_fn = null;
var methods = {
min : this.getMin,
max : this.getMax,
sum : this.getSum,
count : this.getCount,
variance : this._calculateVariance,
mean : this._calculateMean,
stddev : this._calculateStddev
};
if (methods[this._measurement])
this._call_fn = methods[this._measurement];
else {
this._call_fn = function() {
var percentiles = this.percentiles([0.5, 0.75, 0.95, 0.99, 0.999]);
var medians = {
median : percentiles[0.5],
p75 : percentiles[0.75],
p95 : percentiles[0.95],
p99 : percentiles[0.99],
p999 : percentiles[0.999]
};
return medians[this._measurement];
}
}
this._sample = new EDS();
this._min = null;
this._max = null;
this._count = 0;
this._sum = 0;
// These are for the Welford algorithm for calculating running variance
// without floating-point doom.
this._varianceM = 0;
this._varianceS = 0;
}
Histogram.prototype.update = function(value) {
this._count++;
this._sum += value;
this._sample.update(value);
this._updateMin(value);
this._updateMax(value);
this._updateVariance(value);
};
Histogram.prototype.percentiles = function(percentiles) {
var values = this._sample
.toArray()
.sort(function(a, b) {
return (a === b)
? 0
: a - b;
});
var results = {};
for (var i = 0; i < percentiles.length; i++) {
var percentile = percentiles[i];
if (!values.length) {
results[percentile] = null;
continue;
}
var pos = percentile * (values.length + 1);
if (pos < 1) {
results[percentile] = values[0];
} else if (pos >= values.length) {
results[percentile] = values[values.length - 1];
} else {
var lower = values[Math.floor(pos) - 1];
var upper = values[Math.ceil(pos) - 1];
results[percentile] = lower + (pos - Math.floor(pos)) * (upper - lower);
}
}
return results;
};
Histogram.prototype.reset = function() {
this.constructor.call(this);
};
Histogram.prototype.val = function() {
if (typeof(this._call_fn) === 'function')
return this._call_fn();
else
return this._call_fn;
};
Histogram.prototype.getMin = function() {
return this._min;
};
Histogram.prototype.getMax = function() {
return this._max;
};
Histogram.prototype.getSum = function() {
return this._sum;
};
Histogram.prototype.getCount = function() {
return this._count;
};
Histogram.prototype.fullResults = function() {
var percentiles = this.percentiles([0.5, 0.75, 0.95, 0.99, 0.999]);
return {
min : this._min,
max : this._max,
sum : this._sum,
variance : this._calculateVariance(),
mean : this._calculateMean(),
stddev : this._calculateStddev(),
count : this._count,
median : percentiles[0.5],
p75 : percentiles[0.75],
p95 : percentiles[0.95],
p99 : percentiles[0.99],
p999 : percentiles[0.999]
};
};
Histogram.prototype._updateMin = function(value) {
if (this._min === null || value < this._min) {
this._min = value;
//console.log(value);
}
};
Histogram.prototype._updateMax = function(value) {
if (this._max === null || value > this._max) {
this._max = value;
}
};
Histogram.prototype._updateVariance = function(value) {
if (this._count === 1) return this._varianceM = value;
var oldM = this._varianceM;
this._varianceM += ((value - oldM) / this._count);
this._varianceS += ((value - oldM) * (value - this._varianceM));
};
Histogram.prototype._calculateMean = function() {
return (this._count === 0)
? 0
: this._sum / this._count;
};
Histogram.prototype._calculateVariance = function() {
return (this._count <= 1)
? null
: this._varianceS / (this._count - 1);
};
Histogram.prototype._calculateStddev = function() {
return (this._count < 1)
? null
: Math.sqrt(this._calculateVariance());
};
module.exports = Histogram;

View File

@ -0,0 +1,36 @@
// Hacked from https://github.com/felixge/node-measured
var units = require('../units');
var EWMA = require('../EWMA');
function Meter(opts) {
var self = this;
this._tickInterval = 5 * units.SECONDS;
this._samples = opts.samples || 1;
this._timeframe = opts.timeframe || 60;
this._rate = new EWMA(this._timeframe * units.SECONDS, this._tickInterval);
if (opts.debug && opts.debug == true)
return false;
this._interval = setInterval(function() {
self._rate.tick();
}, this._tickInterval);
}
Meter.RATE_UNIT = units.SECONDS;
Meter.prototype.mark = function(n) {
n = n || 1;
this._rate.update(n);
};
Meter.prototype.val = function() {
return Math.round(this._rate.rate(this._samples * Meter.RATE_UNIT) * 100 ) / 100;
};
module.exports = Meter;

View File

@ -0,0 +1,34 @@
var debug = require('debug')('axm:proxy');
// var cls = require('continuation-local-storage');
// var ns = cls.createNamespace('namespace');
var Proxy = module.exports = {
wrap : function(object, methods, hook) {
var self = this;
if (!Array.isArray(methods)) methods = [methods];
for (var i = 0; i < methods.length; ++i) {
debug('Wrapping method:', methods[i]);
var original = object[methods[i]];
if (!original) return debug('Method %s unknown', methods[i]);
if (original.__axm_original) {
debug('Already wrapped', methods[i]);
if (methods[i] != '_load')
return;
}
var hooked = hook(original);
if (original.__axm_original) {
hooked.__axm_original = original.__axm_original;
}
else {
hooked.__axm_original = original;
}
object[methods[i]] = hooked;
//debug('Method proxified');
}
}
};

View File

@ -0,0 +1,34 @@
var debug = require('debug')('axm:transport');
var stringify = require('json-stringify-safe');
var Transport = module.exports = {};
function ipcSend(args, print) {
/**
* For debug purpose
*/
if (process.env.MODULE_DEBUG)
console.log(args);
if (!process.send) {
var output = args.data;
delete output.__name;
return false;
}
try {
process.send(JSON.parse(stringify(args)));
} catch(e) {
console.error('Process disconnected from parent !');
console.error(e.stack || e);
process.exit(1);
}
};
Transport.send = function(args, print) {
if (!print) print = false;
ipcSend(args, print);
};

View File

@ -0,0 +1,9 @@
// Time units, as found in Java:
// see: http://download.oracle.com/javase/6/docs/api/java/util/concurrent/TimeUnit.html
exports.NANOSECONDS = 1 / (1000 * 1000);
exports.MICROSECONDS = 1 / 1000;
exports.MILLISECONDS = 1;
exports.SECONDS = 1000 * exports.MILLISECONDS;
exports.MINUTES = 60 * exports.SECONDS;
exports.HOURS = 60 * exports.MINUTES;
exports.DAYS = 24 * exports.HOURS;

View File

@ -0,0 +1,98 @@
var Proxy = require('../utils/proxy.js');
var Transport = require('../utils/transport.js');
var Probe = require('../Probe.js');
var gl_meter, gl_latency;
var HttpWrap = module.exports = function(opts, http) {
gl_meter = Probe.probe().meter({
name : 'HTTP',
seconds : 60,
unit : 'req/s'
});
gl_latency = Probe.probe().histogram({
measurement : 'mean',
name : 'pmx:http:latency',
unit : 'ms'
});
var ignoreRoutes = function(url) {
for (var i = 0; i < opts.ignore_routes.length; ++i) {
if (url.match(opts.ignore_routes[i]) != null) {
return true;
}
}
return false;
};
Proxy.wrap(http.Server.prototype, ['on', 'addListener'], function(addListener) {
return function(event, listener) {
var self = this;
var overloaded_function = function(request, response) {
gl_meter.mark();
var http_start = {
url : request.url,
method : request.method,
start : Date.now(),
ip : request.headers['x-forwarded-for'] ||
(request.connection ? request.connection.remoteAddress : false) ||
(request.socket ? request.socket.remoteAddress : false) ||
((request.connection && request.connection.socket) ? request.connection.socket.remoteAddress : false) || ''
};
response.once('finish', function() {
if (!ignoreRoutes(http_start.url))
gl_latency.update(Date.now() - http_start.start);
if (((Date.now() - http_start.start) >= opts.http_latency
|| response.statusCode >= opts.http_code)
&& !ignoreRoutes(http_start.url)) {
Transport.send({
type : 'http:transaction',
data : {
url : http_start.url,
method : http_start.method,
time : Date.now() - http_start.start,
code : response.statusCode,
ip : http_start.ip,
size : response.getHeader('Content-Length') || null
}
});
}
http_start = null;
});
};
if (!(event === 'request' && typeof listener === 'function'))
return addListener.apply(self, arguments);
if (self.__overloaded !== true) {
self.on('removeListener', function onRemoveListener() {
setTimeout(function() {
if (self.listeners('request').length === 1) {
self.removeListener('request', overloaded_function);
self.removeListener('removeListener', onRemoveListener);
self.__overloaded = false;
}
}, 200);
});
addListener.call(self, event, overloaded_function);
self.__overloaded = true;
}
return addListener.apply(self, arguments);
};
});
return http;
};

View File

@ -0,0 +1,3 @@
{
"laxbreak": true
}

View File

@ -0,0 +1,6 @@
support
test
examples
example
*.sock
dist

View File

@ -0,0 +1,195 @@
2.2.0 / 2015-05-09
==================
* package: update "ms" to v0.7.1 (#202, @dougwilson)
* README: add logging to file example (#193, @DanielOchoa)
* README: fixed a typo (#191, @amir-s)
* browser: expose `storage` (#190, @stephenmathieson)
* Makefile: add a `distclean` target (#189, @stephenmathieson)
2.1.3 / 2015-03-13
==================
* Updated stdout/stderr example (#186)
* Updated example/stdout.js to match debug current behaviour
* Renamed example/stderr.js to stdout.js
* Update Readme.md (#184)
* replace high intensity foreground color for bold (#182, #183)
2.1.2 / 2015-03-01
==================
* dist: recompile
* update "ms" to v0.7.0
* package: update "browserify" to v9.0.3
* component: fix "ms.js" repo location
* changed bower package name
* updated documentation about using debug in a browser
* fix: security error on safari (#167, #168, @yields)
2.1.1 / 2014-12-29
==================
* browser: use `typeof` to check for `console` existence
* browser: check for `console.log` truthiness (fix IE 8/9)
* browser: add support for Chrome apps
* Readme: added Windows usage remarks
* Add `bower.json` to properly support bower install
2.1.0 / 2014-10-15
==================
* node: implement `DEBUG_FD` env variable support
* package: update "browserify" to v6.1.0
* package: add "license" field to package.json (#135, @panuhorsmalahti)
2.0.0 / 2014-09-01
==================
* package: update "browserify" to v5.11.0
* node: use stderr rather than stdout for logging (#29, @stephenmathieson)
1.0.4 / 2014-07-15
==================
* dist: recompile
* example: remove `console.info()` log usage
* example: add "Content-Type" UTF-8 header to browser example
* browser: place %c marker after the space character
* browser: reset the "content" color via `color: inherit`
* browser: add colors support for Firefox >= v31
* debug: prefer an instance `log()` function over the global one (#119)
* Readme: update documentation about styled console logs for FF v31 (#116, @wryk)
1.0.3 / 2014-07-09
==================
* Add support for multiple wildcards in namespaces (#122, @seegno)
* browser: fix lint
1.0.2 / 2014-06-10
==================
* browser: update color palette (#113, @gscottolson)
* common: make console logging function configurable (#108, @timoxley)
* node: fix %o colors on old node <= 0.8.x
* Makefile: find node path using shell/which (#109, @timoxley)
1.0.1 / 2014-06-06
==================
* browser: use `removeItem()` to clear localStorage
* browser, node: don't set DEBUG if namespaces is undefined (#107, @leedm777)
* package: add "contributors" section
* node: fix comment typo
* README: list authors
1.0.0 / 2014-06-04
==================
* make ms diff be global, not be scope
* debug: ignore empty strings in enable()
* node: make DEBUG_COLORS able to disable coloring
* *: export the `colors` array
* npmignore: don't publish the `dist` dir
* Makefile: refactor to use browserify
* package: add "browserify" as a dev dependency
* Readme: add Web Inspector Colors section
* node: reset terminal color for the debug content
* node: map "%o" to `util.inspect()`
* browser: map "%j" to `JSON.stringify()`
* debug: add custom "formatters"
* debug: use "ms" module for humanizing the diff
* Readme: add "bash" syntax highlighting
* browser: add Firebug color support
* browser: add colors for WebKit browsers
* node: apply log to `console`
* rewrite: abstract common logic for Node & browsers
* add .jshintrc file
0.8.1 / 2014-04-14
==================
* package: re-add the "component" section
0.8.0 / 2014-03-30
==================
* add `enable()` method for nodejs. Closes #27
* change from stderr to stdout
* remove unnecessary index.js file
0.7.4 / 2013-11-13
==================
* remove "browserify" key from package.json (fixes something in browserify)
0.7.3 / 2013-10-30
==================
* fix: catch localStorage security error when cookies are blocked (Chrome)
* add debug(err) support. Closes #46
* add .browser prop to package.json. Closes #42
0.7.2 / 2013-02-06
==================
* fix package.json
* fix: Mobile Safari (private mode) is broken with debug
* fix: Use unicode to send escape character to shell instead of octal to work with strict mode javascript
0.7.1 / 2013-02-05
==================
* add repository URL to package.json
* add DEBUG_COLORED to force colored output
* add browserify support
* fix component. Closes #24
0.7.0 / 2012-05-04
==================
* Added .component to package.json
* Added debug.component.js build
0.6.0 / 2012-03-16
==================
* Added support for "-" prefix in DEBUG [Vinay Pulim]
* Added `.enabled` flag to the node version [TooTallNate]
0.5.0 / 2012-02-02
==================
* Added: humanize diffs. Closes #8
* Added `debug.disable()` to the CS variant
* Removed padding. Closes #10
* Fixed: persist client-side variant again. Closes #9
0.4.0 / 2012-02-01
==================
* Added browser variant support for older browsers [TooTallNate]
* Added `debug.enable('project:*')` to browser variant [TooTallNate]
* Added padding to diff (moved it to the right)
0.3.0 / 2012-01-26
==================
* Added millisecond diff when isatty, otherwise UTC string
0.2.0 / 2012-01-22
==================
* Added wildcard support
0.1.0 / 2011-12-02
==================
* Added: remove colors unless stderr isatty [TooTallNate]
0.0.1 / 2010-01-03
==================
* Initial release

View File

@ -0,0 +1,36 @@
# get Makefile directory name: http://stackoverflow.com/a/5982798/376773
THIS_MAKEFILE_PATH:=$(word $(words $(MAKEFILE_LIST)),$(MAKEFILE_LIST))
THIS_DIR:=$(shell cd $(dir $(THIS_MAKEFILE_PATH));pwd)
# BIN directory
BIN := $(THIS_DIR)/node_modules/.bin
# applications
NODE ?= $(shell which node)
NPM ?= $(NODE) $(shell which npm)
BROWSERIFY ?= $(NODE) $(BIN)/browserify
all: dist/debug.js
install: node_modules
clean:
@rm -rf dist
dist:
@mkdir -p $@
dist/debug.js: node_modules browser.js debug.js dist
@$(BROWSERIFY) \
--standalone debug \
. > $@
distclean: clean
@rm -rf node_modules
node_modules: package.json
@NODE_ENV= $(NPM) install
@touch node_modules
.PHONY: all install clean distclean

View File

@ -0,0 +1,188 @@
# debug
tiny node.js debugging utility modelled after node core's debugging technique.
## Installation
```bash
$ npm install debug
```
## Usage
With `debug` you simply invoke the exported function to generate your debug function, passing it a name which will determine if a noop function is returned, or a decorated `console.error`, so all of the `console` format string goodies you're used to work fine. A unique color is selected per-function for visibility.
Example _app.js_:
```js
var debug = require('debug')('http')
, http = require('http')
, name = 'My App';
// fake app
debug('booting %s', name);
http.createServer(function(req, res){
debug(req.method + ' ' + req.url);
res.end('hello\n');
}).listen(3000, function(){
debug('listening');
});
// fake worker of some kind
require('./worker');
```
Example _worker.js_:
```js
var debug = require('debug')('worker');
setInterval(function(){
debug('doing some work');
}, 1000);
```
The __DEBUG__ environment variable is then used to enable these based on space or comma-delimited names. Here are some examples:
![debug http and worker](http://f.cl.ly/items/18471z1H402O24072r1J/Screenshot.png)
![debug worker](http://f.cl.ly/items/1X413v1a3M0d3C2c1E0i/Screenshot.png)
#### Windows note
On Windows the environment variable is set using the `set` command.
```cmd
set DEBUG=*,-not_this
```
Then, run the program to be debugged as usual.
## Millisecond diff
When actively developing an application it can be useful to see when the time spent between one `debug()` call and the next. Suppose for example you invoke `debug()` before requesting a resource, and after as well, the "+NNNms" will show you how much time was spent between calls.
![](http://f.cl.ly/items/2i3h1d3t121M2Z1A3Q0N/Screenshot.png)
When stdout is not a TTY, `Date#toUTCString()` is used, making it more useful for logging the debug information as shown below:
![](http://f.cl.ly/items/112H3i0e0o0P0a2Q2r11/Screenshot.png)
## Conventions
If you're using this in one or more of your libraries, you _should_ use the name of your library so that developers may toggle debugging as desired without guessing names. If you have more than one debuggers you _should_ prefix them with your library name and use ":" to separate features. For example "bodyParser" from Connect would then be "connect:bodyParser".
## Wildcards
The `*` character may be used as a wildcard. Suppose for example your library has debuggers named "connect:bodyParser", "connect:compress", "connect:session", instead of listing all three with `DEBUG=connect:bodyParser,connect:compress,connect:session`, you may simply do `DEBUG=connect:*`, or to run everything using this module simply use `DEBUG=*`.
You can also exclude specific debuggers by prefixing them with a "-" character. For example, `DEBUG=*,-connect:*` would include all debuggers except those starting with "connect:".
## Browser support
Debug works in the browser as well, currently persisted by `localStorage`. Consider the situation shown below where you have `worker:a` and `worker:b`, and wish to debug both. Somewhere in the code on your page, include:
```js
window.myDebug = require("debug");
```
("debug" is a global object in the browser so we give this object a different name.) When your page is open in the browser, type the following in the console:
```js
myDebug.enable("worker:*")
```
Refresh the page. Debug output will continue to be sent to the console until it is disabled by typing `myDebug.disable()` in the console.
```js
a = debug('worker:a');
b = debug('worker:b');
setInterval(function(){
a('doing some work');
}, 1000);
setInterval(function(){
b('doing some work');
}, 1200);
```
#### Web Inspector Colors
Colors are also enabled on "Web Inspectors" that understand the `%c` formatting
option. These are WebKit web inspectors, Firefox ([since version
31](https://hacks.mozilla.org/2014/05/editable-box-model-multiple-selection-sublime-text-keys-much-more-firefox-developer-tools-episode-31/))
and the Firebug plugin for Firefox (any version).
Colored output looks something like:
![](https://cloud.githubusercontent.com/assets/71256/3139768/b98c5fd8-e8ef-11e3-862a-f7253b6f47c6.png)
### stderr vs stdout
You can set an alternative logging method per-namespace by overriding the `log` method on a per-namespace or globally:
Example _stdout.js_:
```js
var debug = require('debug');
var error = debug('app:error');
// by default stderr is used
error('goes to stderr!');
var log = debug('app:log');
// set this namespace to log via console.log
log.log = console.log.bind(console); // don't forget to bind to console!
log('goes to stdout');
error('still goes to stderr!');
// set all output to go via console.info
// overrides all per-namespace log settings
debug.log = console.info.bind(console);
error('now goes to stdout via console.info');
log('still goes to stdout, but via console.info now');
```
### Save debug output to a file
You can save all debug statements to a file by piping them.
Example:
```bash
$ DEBUG_FD=3 node your-app.js 3> whatever.log
```
## Authors
- TJ Holowaychuk
- Nathan Rajlich
## License
(The MIT License)
Copyright (c) 2014 TJ Holowaychuk &lt;tj@vision-media.ca&gt;
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
'Software'), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@ -0,0 +1,28 @@
{
"name": "visionmedia-debug",
"main": "dist/debug.js",
"version": "2.2.0",
"homepage": "https://github.com/visionmedia/debug",
"authors": [
"TJ Holowaychuk <tj@vision-media.ca>"
],
"description": "visionmedia-debug",
"moduleType": [
"amd",
"es6",
"globals",
"node"
],
"keywords": [
"visionmedia",
"debug"
],
"license": "MIT",
"ignore": [
"**/.*",
"node_modules",
"bower_components",
"test",
"tests"
]
}

View File

@ -0,0 +1,168 @@
/**
* This is the web browser implementation of `debug()`.
*
* Expose `debug()` as the module.
*/
exports = module.exports = require('./debug');
exports.log = log;
exports.formatArgs = formatArgs;
exports.save = save;
exports.load = load;
exports.useColors = useColors;
exports.storage = 'undefined' != typeof chrome
&& 'undefined' != typeof chrome.storage
? chrome.storage.local
: localstorage();
/**
* Colors.
*/
exports.colors = [
'lightseagreen',
'forestgreen',
'goldenrod',
'dodgerblue',
'darkorchid',
'crimson'
];
/**
* Currently only WebKit-based Web Inspectors, Firefox >= v31,
* and the Firebug extension (any Firefox version) are known
* to support "%c" CSS customizations.
*
* TODO: add a `localStorage` variable to explicitly enable/disable colors
*/
function useColors() {
// is webkit? http://stackoverflow.com/a/16459606/376773
return ('WebkitAppearance' in document.documentElement.style) ||
// is firebug? http://stackoverflow.com/a/398120/376773
(window.console && (console.firebug || (console.exception && console.table))) ||
// is firefox >= v31?
// https://developer.mozilla.org/en-US/docs/Tools/Web_Console#Styling_messages
(navigator.userAgent.toLowerCase().match(/firefox\/(\d+)/) && parseInt(RegExp.$1, 10) >= 31);
}
/**
* Map %j to `JSON.stringify()`, since no Web Inspectors do that by default.
*/
exports.formatters.j = function(v) {
return JSON.stringify(v);
};
/**
* Colorize log arguments if enabled.
*
* @api public
*/
function formatArgs() {
var args = arguments;
var useColors = this.useColors;
args[0] = (useColors ? '%c' : '')
+ this.namespace
+ (useColors ? ' %c' : ' ')
+ args[0]
+ (useColors ? '%c ' : ' ')
+ '+' + exports.humanize(this.diff);
if (!useColors) return args;
var c = 'color: ' + this.color;
args = [args[0], c, 'color: inherit'].concat(Array.prototype.slice.call(args, 1));
// the final "%c" is somewhat tricky, because there could be other
// arguments passed either before or after the %c, so we need to
// figure out the correct index to insert the CSS into
var index = 0;
var lastC = 0;
args[0].replace(/%[a-z%]/g, function(match) {
if ('%%' === match) return;
index++;
if ('%c' === match) {
// we only are interested in the *last* %c
// (the user may have provided their own)
lastC = index;
}
});
args.splice(lastC, 0, c);
return args;
}
/**
* Invokes `console.log()` when available.
* No-op when `console.log` is not a "function".
*
* @api public
*/
function log() {
// this hackery is required for IE8/9, where
// the `console.log` function doesn't have 'apply'
return 'object' === typeof console
&& console.log
&& Function.prototype.apply.call(console.log, console, arguments);
}
/**
* Save `namespaces`.
*
* @param {String} namespaces
* @api private
*/
function save(namespaces) {
try {
if (null == namespaces) {
exports.storage.removeItem('debug');
} else {
exports.storage.debug = namespaces;
}
} catch(e) {}
}
/**
* Load `namespaces`.
*
* @return {String} returns the previously persisted debug modes
* @api private
*/
function load() {
var r;
try {
r = exports.storage.debug;
} catch(e) {}
return r;
}
/**
* Enable namespaces listed in `localStorage.debug` initially.
*/
exports.enable(load());
/**
* Localstorage attempts to return the localstorage.
*
* This is necessary because safari throws
* when a user disables cookies/localstorage
* and you attempt to access it.
*
* @return {LocalStorage}
* @api private
*/
function localstorage(){
try {
return window.localStorage;
} catch (e) {}
}

View File

@ -0,0 +1,19 @@
{
"name": "debug",
"repo": "visionmedia/debug",
"description": "small debugging utility",
"version": "2.2.0",
"keywords": [
"debug",
"log",
"debugger"
],
"main": "browser.js",
"scripts": [
"browser.js",
"debug.js"
],
"dependencies": {
"rauchg/ms.js": "0.7.1"
}
}

View File

@ -0,0 +1,197 @@
/**
* This is the common logic for both the Node.js and web browser
* implementations of `debug()`.
*
* Expose `debug()` as the module.
*/
exports = module.exports = debug;
exports.coerce = coerce;
exports.disable = disable;
exports.enable = enable;
exports.enabled = enabled;
exports.humanize = require('ms');
/**
* The currently active debug mode names, and names to skip.
*/
exports.names = [];
exports.skips = [];
/**
* Map of special "%n" handling functions, for the debug "format" argument.
*
* Valid key names are a single, lowercased letter, i.e. "n".
*/
exports.formatters = {};
/**
* Previously assigned color.
*/
var prevColor = 0;
/**
* Previous log timestamp.
*/
var prevTime;
/**
* Select a color.
*
* @return {Number}
* @api private
*/
function selectColor() {
return exports.colors[prevColor++ % exports.colors.length];
}
/**
* Create a debugger with the given `namespace`.
*
* @param {String} namespace
* @return {Function}
* @api public
*/
function debug(namespace) {
// define the `disabled` version
function disabled() {
}
disabled.enabled = false;
// define the `enabled` version
function enabled() {
var self = enabled;
// set `diff` timestamp
var curr = +new Date();
var ms = curr - (prevTime || curr);
self.diff = ms;
self.prev = prevTime;
self.curr = curr;
prevTime = curr;
// add the `color` if not set
if (null == self.useColors) self.useColors = exports.useColors();
if (null == self.color && self.useColors) self.color = selectColor();
var args = Array.prototype.slice.call(arguments);
args[0] = exports.coerce(args[0]);
if ('string' !== typeof args[0]) {
// anything else let's inspect with %o
args = ['%o'].concat(args);
}
// apply any `formatters` transformations
var index = 0;
args[0] = args[0].replace(/%([a-z%])/g, function(match, format) {
// if we encounter an escaped % then don't increase the array index
if (match === '%%') return match;
index++;
var formatter = exports.formatters[format];
if ('function' === typeof formatter) {
var val = args[index];
match = formatter.call(self, val);
// now we need to remove `args[index]` since it's inlined in the `format`
args.splice(index, 1);
index--;
}
return match;
});
if ('function' === typeof exports.formatArgs) {
args = exports.formatArgs.apply(self, args);
}
var logFn = enabled.log || exports.log || console.log.bind(console);
logFn.apply(self, args);
}
enabled.enabled = true;
var fn = exports.enabled(namespace) ? enabled : disabled;
fn.namespace = namespace;
return fn;
}
/**
* Enables a debug mode by namespaces. This can include modes
* separated by a colon and wildcards.
*
* @param {String} namespaces
* @api public
*/
function enable(namespaces) {
exports.save(namespaces);
var split = (namespaces || '').split(/[\s,]+/);
var len = split.length;
for (var i = 0; i < len; i++) {
if (!split[i]) continue; // ignore empty strings
namespaces = split[i].replace(/\*/g, '.*?');
if (namespaces[0] === '-') {
exports.skips.push(new RegExp('^' + namespaces.substr(1) + '$'));
} else {
exports.names.push(new RegExp('^' + namespaces + '$'));
}
}
}
/**
* Disable debug output.
*
* @api public
*/
function disable() {
exports.enable('');
}
/**
* Returns true if the given mode name is enabled, false otherwise.
*
* @param {String} name
* @return {Boolean}
* @api public
*/
function enabled(name) {
var i, len;
for (i = 0, len = exports.skips.length; i < len; i++) {
if (exports.skips[i].test(name)) {
return false;
}
}
for (i = 0, len = exports.names.length; i < len; i++) {
if (exports.names[i].test(name)) {
return true;
}
}
return false;
}
/**
* Coerce `val`.
*
* @param {Mixed} val
* @return {Mixed}
* @api private
*/
function coerce(val) {
if (val instanceof Error) return val.stack || val.message;
return val;
}

View File

@ -0,0 +1,209 @@
/**
* Module dependencies.
*/
var tty = require('tty');
var util = require('util');
/**
* This is the Node.js implementation of `debug()`.
*
* Expose `debug()` as the module.
*/
exports = module.exports = require('./debug');
exports.log = log;
exports.formatArgs = formatArgs;
exports.save = save;
exports.load = load;
exports.useColors = useColors;
/**
* Colors.
*/
exports.colors = [6, 2, 3, 4, 5, 1];
/**
* The file descriptor to write the `debug()` calls to.
* Set the `DEBUG_FD` env variable to override with another value. i.e.:
*
* $ DEBUG_FD=3 node script.js 3>debug.log
*/
var fd = parseInt(process.env.DEBUG_FD, 10) || 2;
var stream = 1 === fd ? process.stdout :
2 === fd ? process.stderr :
createWritableStdioStream(fd);
/**
* Is stdout a TTY? Colored output is enabled when `true`.
*/
function useColors() {
var debugColors = (process.env.DEBUG_COLORS || '').trim().toLowerCase();
if (0 === debugColors.length) {
return tty.isatty(fd);
} else {
return '0' !== debugColors
&& 'no' !== debugColors
&& 'false' !== debugColors
&& 'disabled' !== debugColors;
}
}
/**
* Map %o to `util.inspect()`, since Node doesn't do that out of the box.
*/
var inspect = (4 === util.inspect.length ?
// node <= 0.8.x
function (v, colors) {
return util.inspect(v, void 0, void 0, colors);
} :
// node > 0.8.x
function (v, colors) {
return util.inspect(v, { colors: colors });
}
);
exports.formatters.o = function(v) {
return inspect(v, this.useColors)
.replace(/\s*\n\s*/g, ' ');
};
/**
* Adds ANSI color escape codes if enabled.
*
* @api public
*/
function formatArgs() {
var args = arguments;
var useColors = this.useColors;
var name = this.namespace;
if (useColors) {
var c = this.color;
args[0] = ' \u001b[3' + c + ';1m' + name + ' '
+ '\u001b[0m'
+ args[0] + '\u001b[3' + c + 'm'
+ ' +' + exports.humanize(this.diff) + '\u001b[0m';
} else {
args[0] = new Date().toUTCString()
+ ' ' + name + ' ' + args[0];
}
return args;
}
/**
* Invokes `console.error()` with the specified arguments.
*/
function log() {
return stream.write(util.format.apply(this, arguments) + '\n');
}
/**
* Save `namespaces`.
*
* @param {String} namespaces
* @api private
*/
function save(namespaces) {
if (null == namespaces) {
// If you set a process.env field to null or undefined, it gets cast to the
// string 'null' or 'undefined'. Just delete instead.
delete process.env.DEBUG;
} else {
process.env.DEBUG = namespaces;
}
}
/**
* Load `namespaces`.
*
* @return {String} returns the previously persisted debug modes
* @api private
*/
function load() {
return process.env.DEBUG;
}
/**
* Copied from `node/src/node.js`.
*
* XXX: It's lame that node doesn't expose this API out-of-the-box. It also
* relies on the undocumented `tty_wrap.guessHandleType()` which is also lame.
*/
function createWritableStdioStream (fd) {
var stream;
var tty_wrap = process.binding('tty_wrap');
// Note stream._type is used for test-module-load-list.js
switch (tty_wrap.guessHandleType(fd)) {
case 'TTY':
stream = new tty.WriteStream(fd);
stream._type = 'tty';
// Hack to have stream not keep the event loop alive.
// See https://github.com/joyent/node/issues/1726
if (stream._handle && stream._handle.unref) {
stream._handle.unref();
}
break;
case 'FILE':
var fs = require('fs');
stream = new fs.SyncWriteStream(fd, { autoClose: false });
stream._type = 'fs';
break;
case 'PIPE':
case 'TCP':
var net = require('net');
stream = new net.Socket({
fd: fd,
readable: false,
writable: true
});
// FIXME Should probably have an option in net.Socket to create a
// stream from an existing fd which is writable only. But for now
// we'll just add this hack and set the `readable` member to false.
// Test: ./node test/fixtures/echo.js < /etc/passwd
stream.readable = false;
stream.read = null;
stream._type = 'pipe';
// FIXME Hack to have stream not keep the event loop alive.
// See https://github.com/joyent/node/issues/1726
if (stream._handle && stream._handle.unref) {
stream._handle.unref();
}
break;
default:
// Probably an error on in uv_guess_handle()
throw new Error('Implement me. Unknown stream file type!');
}
// For supporting legacy API we put the FD here.
stream.fd = fd;
stream._isStdio = true;
return stream;
}
/**
* Enable namespaces listed in `process.env.DEBUG` initially.
*/
exports.enable(load());

View File

@ -0,0 +1,5 @@
node_modules
test
History.md
Makefile
component.json

View File

@ -0,0 +1,66 @@
0.7.1 / 2015-04-20
==================
* prevent extraordinary long inputs (@evilpacket)
* Fixed broken readme link
0.7.0 / 2014-11-24
==================
* add time abbreviations, updated tests and readme for the new units
* fix example in the readme.
* add LICENSE file
0.6.2 / 2013-12-05
==================
* Adding repository section to package.json to suppress warning from NPM.
0.6.1 / 2013-05-10
==================
* fix singularization [visionmedia]
0.6.0 / 2013-03-15
==================
* fix minutes
0.5.1 / 2013-02-24
==================
* add component namespace
0.5.0 / 2012-11-09
==================
* add short formatting as default and .long option
* add .license property to component.json
* add version to component.json
0.4.0 / 2012-10-22
==================
* add rounding to fix crazy decimals
0.3.0 / 2012-09-07
==================
* fix `ms(<String>)` [visionmedia]
0.2.0 / 2012-09-03
==================
* add component.json [visionmedia]
* add days support [visionmedia]
* add hours support [visionmedia]
* add minutes support [visionmedia]
* add seconds support [visionmedia]
* add ms string support [visionmedia]
* refactor tests to facilitate ms(number) [visionmedia]
0.1.0 / 2012-03-07
==================
* Initial release

View File

@ -0,0 +1,20 @@
(The MIT License)
Copyright (c) 2014 Guillermo Rauch <rauchg@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@ -0,0 +1,35 @@
# ms.js: miliseconds conversion utility
```js
ms('2 days') // 172800000
ms('1d') // 86400000
ms('10h') // 36000000
ms('2.5 hrs') // 9000000
ms('2h') // 7200000
ms('1m') // 60000
ms('5s') // 5000
ms('100') // 100
```
```js
ms(60000) // "1m"
ms(2 * 60000) // "2m"
ms(ms('10 hours')) // "10h"
```
```js
ms(60000, { long: true }) // "1 minute"
ms(2 * 60000, { long: true }) // "2 minutes"
ms(ms('10 hours'), { long: true }) // "10 hours"
```
- Node/Browser compatible. Published as [`ms`](https://www.npmjs.org/package/ms) in [NPM](http://nodejs.org/download).
- If a number is supplied to `ms`, a string with a unit is returned.
- If a string that contains the number is supplied, it returns it as
a number (e.g: it returns `100` for `'100'`).
- If you pass a string with a number and a valid unit, the number of
equivalent ms is returned.
## License
MIT

View File

@ -0,0 +1,125 @@
/**
* Helpers.
*/
var s = 1000;
var m = s * 60;
var h = m * 60;
var d = h * 24;
var y = d * 365.25;
/**
* Parse or format the given `val`.
*
* Options:
*
* - `long` verbose formatting [false]
*
* @param {String|Number} val
* @param {Object} options
* @return {String|Number}
* @api public
*/
module.exports = function(val, options){
options = options || {};
if ('string' == typeof val) return parse(val);
return options.long
? long(val)
: short(val);
};
/**
* Parse the given `str` and return milliseconds.
*
* @param {String} str
* @return {Number}
* @api private
*/
function parse(str) {
str = '' + str;
if (str.length > 10000) return;
var match = /^((?:\d+)?\.?\d+) *(milliseconds?|msecs?|ms|seconds?|secs?|s|minutes?|mins?|m|hours?|hrs?|h|days?|d|years?|yrs?|y)?$/i.exec(str);
if (!match) return;
var n = parseFloat(match[1]);
var type = (match[2] || 'ms').toLowerCase();
switch (type) {
case 'years':
case 'year':
case 'yrs':
case 'yr':
case 'y':
return n * y;
case 'days':
case 'day':
case 'd':
return n * d;
case 'hours':
case 'hour':
case 'hrs':
case 'hr':
case 'h':
return n * h;
case 'minutes':
case 'minute':
case 'mins':
case 'min':
case 'm':
return n * m;
case 'seconds':
case 'second':
case 'secs':
case 'sec':
case 's':
return n * s;
case 'milliseconds':
case 'millisecond':
case 'msecs':
case 'msec':
case 'ms':
return n;
}
}
/**
* Short format for `ms`.
*
* @param {Number} ms
* @return {String}
* @api private
*/
function short(ms) {
if (ms >= d) return Math.round(ms / d) + 'd';
if (ms >= h) return Math.round(ms / h) + 'h';
if (ms >= m) return Math.round(ms / m) + 'm';
if (ms >= s) return Math.round(ms / s) + 's';
return ms + 'ms';
}
/**
* Long format for `ms`.
*
* @param {Number} ms
* @return {String}
* @api private
*/
function long(ms) {
return plural(ms, d, 'day')
|| plural(ms, h, 'hour')
|| plural(ms, m, 'minute')
|| plural(ms, s, 'second')
|| ms + ' ms';
}
/**
* Pluralization helper.
*/
function plural(ms, n, name) {
if (ms < n) return;
if (ms < n * 1.5) return Math.floor(ms / n) + ' ' + name;
return Math.ceil(ms / n) + ' ' + name + 's';
}

View File

@ -0,0 +1,48 @@
{
"name": "ms",
"version": "0.7.1",
"description": "Tiny ms conversion utility",
"repository": {
"type": "git",
"url": "git://github.com/guille/ms.js.git"
},
"main": "./index",
"devDependencies": {
"mocha": "*",
"expect.js": "*",
"serve": "*"
},
"component": {
"scripts": {
"ms/index.js": "index.js"
}
},
"gitHead": "713dcf26d9e6fd9dbc95affe7eff9783b7f1b909",
"bugs": {
"url": "https://github.com/guille/ms.js/issues"
},
"homepage": "https://github.com/guille/ms.js",
"_id": "ms@0.7.1",
"scripts": {},
"_shasum": "9cd13c03adbff25b65effde7ce864ee952017098",
"_from": "ms@0.7.1",
"_npmVersion": "2.7.5",
"_nodeVersion": "0.12.2",
"_npmUser": {
"name": "rauchg",
"email": "rauchg@gmail.com"
},
"maintainers": [
{
"name": "rauchg",
"email": "rauchg@gmail.com"
}
],
"dist": {
"shasum": "9cd13c03adbff25b65effde7ce864ee952017098",
"tarball": "http://registry.npmjs.org/ms/-/ms-0.7.1.tgz"
},
"directories": {},
"_resolved": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz",
"readme": "ERROR: No README data found!"
}

View File

@ -0,0 +1,73 @@
{
"name": "debug",
"version": "2.2.0",
"repository": {
"type": "git",
"url": "git://github.com/visionmedia/debug.git"
},
"description": "small debugging utility",
"keywords": [
"debug",
"log",
"debugger"
],
"author": {
"name": "TJ Holowaychuk",
"email": "tj@vision-media.ca"
},
"contributors": [
{
"name": "Nathan Rajlich",
"email": "nathan@tootallnate.net",
"url": "http://n8.io"
}
],
"license": "MIT",
"dependencies": {
"ms": "0.7.1"
},
"devDependencies": {
"browserify": "9.0.3",
"mocha": "*"
},
"main": "./node.js",
"browser": "./browser.js",
"component": {
"scripts": {
"debug/index.js": "browser.js",
"debug/debug.js": "debug.js"
}
},
"gitHead": "b38458422b5aa8aa6d286b10dfe427e8a67e2b35",
"bugs": {
"url": "https://github.com/visionmedia/debug/issues"
},
"homepage": "https://github.com/visionmedia/debug",
"_id": "debug@2.2.0",
"scripts": {},
"_shasum": "f87057e995b1a1f6ae6a4960664137bc56f039da",
"_from": "debug@*",
"_npmVersion": "2.7.4",
"_nodeVersion": "0.12.2",
"_npmUser": {
"name": "tootallnate",
"email": "nathan@tootallnate.net"
},
"maintainers": [
{
"name": "tjholowaychuk",
"email": "tj@vision-media.ca"
},
{
"name": "tootallnate",
"email": "nathan@tootallnate.net"
}
],
"dist": {
"shasum": "f87057e995b1a1f6ae6a4960664137bc56f039da",
"tarball": "http://registry.npmjs.org/debug/-/debug-2.2.0.tgz"
},
"directories": {},
"_resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz",
"readme": "ERROR: No README data found!"
}

View File

@ -0,0 +1 @@
/*.tgz

View File

@ -0,0 +1,14 @@
## Unreleased
- Fixes stringify to only take ancestors into account when checking
circularity.
It previously assumed every visited object was circular which led to [false
positives][issue9].
Uses the tiny serializer I wrote for [Must.js][must] a year and a half ago.
- Fixes calling the `replacer` function in the proper context (`thisArg`).
- Fixes calling the `cycleReplacer` function in the proper context (`thisArg`).
- Speeds serializing by a factor of
Big-O(h-my-god-it-linearly-searched-every-object) it had ever seen. Searching
only the ancestors for a circular references speeds up things considerably.
[must]: https://github.com/moll/js-must
[issue9]: https://github.com/isaacs/json-stringify-safe/issues/9

View File

@ -0,0 +1,15 @@
The ISC License
Copyright (c) Isaac Z. Schlueter and Contributors
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

View File

@ -0,0 +1,35 @@
NODE_OPTS =
TEST_OPTS =
love:
@echo "Feel like makin' love."
test:
@node $(NODE_OPTS) ./node_modules/.bin/_mocha -R dot $(TEST_OPTS)
spec:
@node $(NODE_OPTS) ./node_modules/.bin/_mocha -R spec $(TEST_OPTS)
autotest:
@node $(NODE_OPTS) ./node_modules/.bin/_mocha -R dot --watch $(TEST_OPTS)
autospec:
@node $(NODE_OPTS) ./node_modules/.bin/_mocha -R spec --watch $(TEST_OPTS)
pack:
@file=$$(npm pack); echo "$$file"; tar tf "$$file"
publish:
npm publish
tag:
git tag "v$$(node -e 'console.log(require("./package").version)')"
clean:
rm -f *.tgz
npm prune --production
.PHONY: love
.PHONY: test spec autotest autospec
.PHONY: pack publish tag
.PHONY: clean

View File

@ -0,0 +1,52 @@
# json-stringify-safe
Like JSON.stringify, but doesn't throw on circular references.
## Usage
Takes the same arguments as `JSON.stringify`.
```javascript
var stringify = require('json-stringify-safe');
var circularObj = {};
circularObj.circularRef = circularObj;
circularObj.list = [ circularObj, circularObj ];
console.log(stringify(circularObj, null, 2));
```
Output:
```json
{
"circularRef": "[Circular]",
"list": [
"[Circular]",
"[Circular]"
]
}
```
## Details
```
stringify(obj, serializer, indent, decycler)
```
The first three arguments are the same as to JSON.stringify. The last
is an argument that's only used when the object has been seen already.
The default `decycler` function returns the string `'[Circular]'`.
If, for example, you pass in `function(k,v){}` (return nothing) then it
will prune cycles. If you pass in `function(k,v){ return {foo: 'bar'}}`,
then cyclical objects will always be represented as `{"foo":"bar"}` in
the result.
```
stringify.getSerialize(serializer, decycler)
```
Returns a serializer that can be used elsewhere. This is the actual
function that's passed to JSON.stringify.
**Note** that the function returned from `getSerialize` is stateful for now, so
do **not** use it more than once.

View File

@ -0,0 +1,68 @@
{
"name": "json-stringify-safe",
"version": "5.0.1",
"description": "Like JSON.stringify, but doesn't blow up on circular refs.",
"keywords": [
"json",
"stringify",
"circular",
"safe"
],
"homepage": "https://github.com/isaacs/json-stringify-safe",
"bugs": {
"url": "https://github.com/isaacs/json-stringify-safe/issues"
},
"author": {
"name": "Isaac Z. Schlueter",
"email": "i@izs.me",
"url": "http://blog.izs.me"
},
"contributors": [
{
"name": "Andri Möll",
"email": "andri@dot.ee",
"url": "http://themoll.com"
}
],
"license": "ISC",
"repository": {
"type": "git",
"url": "git://github.com/isaacs/json-stringify-safe.git"
},
"main": "stringify.js",
"scripts": {
"test": "node test.js"
},
"devDependencies": {
"mocha": ">= 2.1.0 < 3",
"must": ">= 0.12 < 0.13",
"sinon": ">= 1.12.2 < 2"
},
"gitHead": "3890dceab3ad14f8701e38ca74f38276abc76de5",
"_id": "json-stringify-safe@5.0.1",
"_shasum": "1296a2d58fd45f19a0f6ce01d65701e2c735b6eb",
"_from": "json-stringify-safe@*",
"_npmVersion": "2.10.0",
"_nodeVersion": "2.0.1",
"_npmUser": {
"name": "isaacs",
"email": "isaacs@npmjs.com"
},
"dist": {
"shasum": "1296a2d58fd45f19a0f6ce01d65701e2c735b6eb",
"tarball": "http://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz"
},
"maintainers": [
{
"name": "isaacs",
"email": "i@izs.me"
},
{
"name": "moll",
"email": "andri@dot.ee"
}
],
"directories": {},
"_resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz",
"readme": "ERROR: No README data found!"
}

View File

@ -0,0 +1,27 @@
exports = module.exports = stringify
exports.getSerialize = serializer
function stringify(obj, replacer, spaces, cycleReplacer) {
return JSON.stringify(obj, serializer(replacer, cycleReplacer), spaces)
}
function serializer(replacer, cycleReplacer) {
var stack = [], keys = []
if (cycleReplacer == null) cycleReplacer = function(key, value) {
if (stack[0] === value) return "[Circular ~]"
return "[Circular ~." + keys.slice(0, stack.indexOf(value)).join(".") + "]"
}
return function(key, value) {
if (stack.length > 0) {
var thisPos = stack.indexOf(this)
~thisPos ? stack.splice(thisPos + 1) : stack.push(this)
~thisPos ? keys.splice(thisPos, Infinity, key) : keys.push(key)
if (~stack.indexOf(value)) value = cycleReplacer.call(this, key, value)
}
else stack.push(value)
return replacer == null ? value : replacer.call(this, key, value)
}
}

View File

@ -0,0 +1,2 @@
--recursive
--require must

View File

@ -0,0 +1,246 @@
var Sinon = require("sinon")
var stringify = require("..")
function jsonify(obj) { return JSON.stringify(obj, null, 2) }
describe("Stringify", function() {
it("must stringify circular objects", function() {
var obj = {name: "Alice"}
obj.self = obj
var json = stringify(obj, null, 2)
json.must.eql(jsonify({name: "Alice", self: "[Circular ~]"}))
})
it("must stringify circular objects with intermediaries", function() {
var obj = {name: "Alice"}
obj.identity = {self: obj}
var json = stringify(obj, null, 2)
json.must.eql(jsonify({name: "Alice", identity: {self: "[Circular ~]"}}))
})
it("must stringify circular objects deeper", function() {
var obj = {name: "Alice", child: {name: "Bob"}}
obj.child.self = obj.child
stringify(obj, null, 2).must.eql(jsonify({
name: "Alice",
child: {name: "Bob", self: "[Circular ~.child]"}
}))
})
it("must stringify circular objects deeper with intermediaries", function() {
var obj = {name: "Alice", child: {name: "Bob"}}
obj.child.identity = {self: obj.child}
stringify(obj, null, 2).must.eql(jsonify({
name: "Alice",
child: {name: "Bob", identity: {self: "[Circular ~.child]"}}
}))
})
it("must stringify circular objects in an array", function() {
var obj = {name: "Alice"}
obj.self = [obj, obj]
stringify(obj, null, 2).must.eql(jsonify({
name: "Alice", self: ["[Circular ~]", "[Circular ~]"]
}))
})
it("must stringify circular objects deeper in an array", function() {
var obj = {name: "Alice", children: [{name: "Bob"}, {name: "Eve"}]}
obj.children[0].self = obj.children[0]
obj.children[1].self = obj.children[1]
stringify(obj, null, 2).must.eql(jsonify({
name: "Alice",
children: [
{name: "Bob", self: "[Circular ~.children.0]"},
{name: "Eve", self: "[Circular ~.children.1]"}
]
}))
})
it("must stringify circular arrays", function() {
var obj = []
obj.push(obj)
obj.push(obj)
var json = stringify(obj, null, 2)
json.must.eql(jsonify(["[Circular ~]", "[Circular ~]"]))
})
it("must stringify circular arrays with intermediaries", function() {
var obj = []
obj.push({name: "Alice", self: obj})
obj.push({name: "Bob", self: obj})
stringify(obj, null, 2).must.eql(jsonify([
{name: "Alice", self: "[Circular ~]"},
{name: "Bob", self: "[Circular ~]"}
]))
})
it("must stringify repeated objects in objects", function() {
var obj = {}
var alice = {name: "Alice"}
obj.alice1 = alice
obj.alice2 = alice
stringify(obj, null, 2).must.eql(jsonify({
alice1: {name: "Alice"},
alice2: {name: "Alice"}
}))
})
it("must stringify repeated objects in arrays", function() {
var alice = {name: "Alice"}
var obj = [alice, alice]
var json = stringify(obj, null, 2)
json.must.eql(jsonify([{name: "Alice"}, {name: "Alice"}]))
})
it("must call given decycler and use its output", function() {
var obj = {}
obj.a = obj
obj.b = obj
var decycle = Sinon.spy(function() { return decycle.callCount })
var json = stringify(obj, null, 2, decycle)
json.must.eql(jsonify({a: 1, b: 2}, null, 2))
decycle.callCount.must.equal(2)
decycle.thisValues[0].must.equal(obj)
decycle.args[0][0].must.equal("a")
decycle.args[0][1].must.equal(obj)
decycle.thisValues[1].must.equal(obj)
decycle.args[1][0].must.equal("b")
decycle.args[1][1].must.equal(obj)
})
it("must call replacer and use its output", function() {
var obj = {name: "Alice", child: {name: "Bob"}}
var replacer = Sinon.spy(bangString)
var json = stringify(obj, replacer, 2)
json.must.eql(jsonify({name: "Alice!", child: {name: "Bob!"}}))
replacer.callCount.must.equal(4)
replacer.args[0][0].must.equal("")
replacer.args[0][1].must.equal(obj)
replacer.thisValues[1].must.equal(obj)
replacer.args[1][0].must.equal("name")
replacer.args[1][1].must.equal("Alice")
replacer.thisValues[2].must.equal(obj)
replacer.args[2][0].must.equal("child")
replacer.args[2][1].must.equal(obj.child)
replacer.thisValues[3].must.equal(obj.child)
replacer.args[3][0].must.equal("name")
replacer.args[3][1].must.equal("Bob")
})
it("must call replacer after describing circular references", function() {
var obj = {name: "Alice"}
obj.self = obj
var replacer = Sinon.spy(bangString)
var json = stringify(obj, replacer, 2)
json.must.eql(jsonify({name: "Alice!", self: "[Circular ~]!"}))
replacer.callCount.must.equal(3)
replacer.args[0][0].must.equal("")
replacer.args[0][1].must.equal(obj)
replacer.thisValues[1].must.equal(obj)
replacer.args[1][0].must.equal("name")
replacer.args[1][1].must.equal("Alice")
replacer.thisValues[2].must.equal(obj)
replacer.args[2][0].must.equal("self")
replacer.args[2][1].must.equal("[Circular ~]")
})
it("must call given decycler and use its output for nested objects",
function() {
var obj = {}
obj.a = obj
obj.b = {self: obj}
var decycle = Sinon.spy(function() { return decycle.callCount })
var json = stringify(obj, null, 2, decycle)
json.must.eql(jsonify({a: 1, b: {self: 2}}))
decycle.callCount.must.equal(2)
decycle.args[0][0].must.equal("a")
decycle.args[0][1].must.equal(obj)
decycle.args[1][0].must.equal("self")
decycle.args[1][1].must.equal(obj)
})
it("must use decycler's output when it returned null", function() {
var obj = {a: "b"}
obj.self = obj
obj.selves = [obj, obj]
function decycle() { return null }
stringify(obj, null, 2, decycle).must.eql(jsonify({
a: "b",
self: null,
selves: [null, null]
}))
})
it("must use decycler's output when it returned undefined", function() {
var obj = {a: "b"}
obj.self = obj
obj.selves = [obj, obj]
function decycle() {}
stringify(obj, null, 2, decycle).must.eql(jsonify({
a: "b",
selves: [null, null]
}))
})
it("must throw given a decycler that returns a cycle", function() {
var obj = {}
obj.self = obj
var err
function identity(key, value) { return value }
try { stringify(obj, null, 2, identity) } catch (ex) { err = ex }
err.must.be.an.instanceof(TypeError)
})
describe(".getSerialize", function() {
it("must stringify circular objects", function() {
var obj = {a: "b"}
obj.circularRef = obj
obj.list = [obj, obj]
var json = JSON.stringify(obj, stringify.getSerialize(), 2)
json.must.eql(jsonify({
"a": "b",
"circularRef": "[Circular ~]",
"list": ["[Circular ~]", "[Circular ~]"]
}))
})
// This is the behavior as of Mar 3, 2015.
// The serializer function keeps state inside the returned function and
// so far I'm not sure how to not do that. JSON.stringify's replacer is not
// called _after_ serialization.
xit("must return a function that could be called twice", function() {
var obj = {name: "Alice"}
obj.self = obj
var json
var serializer = stringify.getSerialize()
json = JSON.stringify(obj, serializer, 2)
json.must.eql(jsonify({name: "Alice", self: "[Circular ~]"}))
json = JSON.stringify(obj, serializer, 2)
json.must.eql(jsonify({name: "Alice", self: "[Circular ~]"}))
})
})
})
function bangString(key, value) {
return typeof value == "string" ? value + "!" : value
}

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,164 @@
var pmx = require('..');
function forkApp(script) {
var app = require('child_process').fork(__dirname + (script || '/proc.mock.js'), []);
return app;
}
function forkAppWithOptions() {
var app = require('child_process').fork(__dirname + '/proc-option.mock.js', []);
return app;
}
describe('Action module', function() {
describe('Action without option', function() {
var app;
var action_name;
after(function() {
process.kill(app.pid);
});
it('should notify PM2 of a new action available', function(done) {
app = forkApp();
app.once('message', function(dt) {
dt.type.should.eql('axm:action');
dt.data.action_name.should.eql('test:nab');
dt.data.opts.comment.should.eql('This is a test');
dt.data.opts.display.should.eql(true);
action_name = dt.data.action_name;
done();
});
});
it('should trigger the action', function(done) {
app.once('message', function(dt) {
dt.type.should.eql('axm:reply');
dt.data.return.res.should.eql('hello moto');
done();
});
app.send(action_name);
});
it('should trigger the action via Object arity 1 (FALLBACK)', function(done) {
app.once('message', function(dt) {
dt.type.should.eql('axm:reply');
dt.data.return.res.should.eql('hello moto');
done();
});
app.send({msg : action_name, opts : { sisi : 'true' }});
});
it('should not trigger the action if wrong action name', function(done) {
app.once('message', function(dt) {
throw new Error('Should not be called');
});
app.send({
action_name : 'im unknown'
});
setTimeout(done, 200);
});
});
describe('Action with extra options (parameters)', function() {
var app;
var action_name;
after(function() {
process.kill(app.pid);
});
it('should notify PM2 of a new action available', function(done) {
app = forkAppWithOptions();
app.once('message', function(dt) {
dt.type.should.eql('axm:action');
dt.data.action_name.should.eql('test:with:options');
action_name = dt.data.action_name;
done();
});
});
it('should trigger the action without failing (2 args without option)', function(done) {
app.once('message', function(dt) {
dt.type.should.eql('axm:reply');
dt.data.return.res.should.eql('hello moto');
done();
});
app.send(action_name);
});
it('should trigger the action', function(done) {
app.once('message', function(dt) {
dt.type.should.eql('axm:reply');
dt.data.return.res.should.eql('hello moto');
dt.data.return.options.f1.should.eql('ab');
done();
});
app.send({ msg : action_name, opts : { f1 : 'ab', f2 : 'cd'}});
});
it('should not trigger the action if wrong action name', function(done) {
app.once('message', function(dt) {
throw new Error('Should not be called');
});
app.send('im unknown');
setTimeout(done, 200);
});
});
describe('Scoped Action (option, emitter, callback)', function() {
var app;
var action_name;
after(function() {
process.kill(app.pid);
});
it('should notify PM2 of a new action available', function(done) {
app = forkApp('/fixtures/scoped-action.fixture.js');
app.once('message', function(dt) {
dt.type.should.eql('axm:action');
dt.data.action_name.should.eql('scoped:action');
dt.data.action_type.should.eql('scoped');
action_name = dt.data.action_name;
done();
});
});
it('should stream data', function(done) {
app.once('message', function(dt) {
dt.type.should.eql('axm:scoped_action:stream');
dt.data.data.should.eql('data random');
done();
});
app.send({ action_name : action_name, uuid : 'Random nb'});
});
it('should trigger the action', function(done) {
app.on('message', function(dt) {
if (dt.type == 'axm:scoped_action:end')
done();
});
});
});
});

View File

@ -0,0 +1,101 @@
var axm = require('..');
var request = require('request');
var should = require('should');
var Plan = require('./helpers/plan');
function fork() {
return require('child_process').fork(__dirname + '/transaction/app.mock.auto.js', []);
}
describe('Automatic transaction', function() {
it('should have right properties', function(done) {
axm.should.have.property('http');
done();
});
var app;
after(function() {
process.kill(app.pid);
});
it('should receive configuration flag', function(done) {
app = fork();
app.once('message', function(data) {
data.type.should.eql('axm:option:configuration');
done();
});
});
it('should not log fast http request', function(done) {
var rcpt = function(data) {
if (data.type == 'axm:option:configuration')
return false;
if (data.type == 'axm:monitor')
return false;
return data.type.should.not.eql('http:transaction');
};
app.on('message', rcpt);
setTimeout(function() {
app.removeListener('message', rcpt);
return done();
}, 500);
setTimeout(function() {
request('http://127.0.0.1:9007/', function(req, res) {});
}, 100);
});
it('should not log ignored http request', function(done) {
var timer = setTimeout(function() {
app.removeListener('message', rcpt);
return done();
}, 1000);
var rcpt = function(data) {
if (data.type == 'axm:option:configuration')
return false;
if (data.type == 'axm:monitor')
return false;
return data.type.should.not.eql('http:transaction');
};
app.on('message', rcpt);
setTimeout(function() {
request('http://127.0.0.1:9007/socket.io/slow', function(req, res) {});
}, 100);
});
it('should log slow http request', function(done) {
var plan = new Plan(3, done);
app.on('message', function(data) {
if (data.type == 'axm:monitor') {
plan.ok(true);
if (Object.keys(data.data) < 3)
plan.ok(false);
}
if (data.type == 'http:transaction') {
data.data.should.have.properties('ip', 'time', 'url', 'method');
plan.ok(true);
}
});
setTimeout(function() {
request('http://127.0.0.1:9007/slow', function(req, res) {});
}, 100);
});
});

View File

@ -0,0 +1,38 @@
var axm = require('..');
function fork() {
return require('child_process').fork(__dirname + '/event.mock.js', []);
}
describe('Event', function() {
it('should have right property', function(done) {
axm.should.have.property('emit');
done();
});
describe('Event scenario', function() {
var app;
before(function() {
app = fork();
});
after(function() {
process.kill(app.pid);
});
it('should send right event data when called', function(done) {
app.once('message', function(data) {
data.type.should.eql('human:event');
data.data.user.should.eql('toto');
data.data.__name.should.eql('test');
data.data.subobj.subobj.a.should.eql('b');
done();
});
});
});
});

View File

@ -0,0 +1,13 @@
var axm = require('..');
setInterval(function() {
axm.emit('test', {
user : 'toto',
subobj : {
subobj : {
a : 'b'
}
}
});
}, 100);

View File

@ -0,0 +1,23 @@
var axm = require('../..');
var express = require('express');
var app = express();
var err = new Error('jajajja');
err.url = 'http://thd.com/';
axm.notify(err);
app.get('/', function(req, res){
res.send('Hello World');
});
app.get('/error', function(req, res, next){
next(new Error('toto'));
});
app.use(axm.expressErrorHandler());
app.listen(3001);

View File

@ -0,0 +1,14 @@
{
"name": "express",
"version": "0.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"dependencies" : {
"express" : "*"
},
"author": "",
"license": "ISC"
}

View File

@ -0,0 +1,45 @@
var axm = require('../..');
var probe = axm.probe();
var histogram = probe.histogram({
name : 'test',
measurement : 'p95',
agg_type: 'sum'
});
var a = 0;
setInterval(function() {
a = Math.round(Math.random() * 100);
histogram.update(a);
}, 100);
var h2 = probe.histogram({
name : 'mean',
measurement : 'mean',
unit : 'ms'
});
var b = 0;
setInterval(function() {
b = Math.round(Math.random() * 100);
h2.update(b);
}, 100);
var h3 = probe.histogram({
name : 'min',
measurement : 'min',
agg_type: 'min'
});
var c = 0;
setInterval(function() {
c = Math.round(Math.random() * 100);
h3.update(c);
}, 100);

View File

@ -0,0 +1,4 @@
var pmx = require('../../..');
var conf = pmx.initModule();

View File

@ -0,0 +1,14 @@
{
"name": "module",
"version": "1.0.0",
"description": "comment",
"main": "module.fixture.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"config" : {
"initial" : "init-val"
},
"author": "strzel",
"license": "ISC"
}

View File

@ -0,0 +1,20 @@
var axm = require('../..');
var obj = axm.enableProbes();
var i = 2;
obj.it_works = true;
obj.value = 20;
obj.i = i;
setTimeout(function() {
i = 4;
obj.it_works = false;
obj.value = 99;
setTimeout(function() {
axm.stopProbes();
}, 1100);
}, 1100);

View File

@ -0,0 +1,26 @@
var axm = require('../..');
var obj = axm.enableProbes();
var a = {
'aaa' : { 'ok' : true },
'bbb' : { 'ok' : false }
};
// Does not refresh because it copies the val
obj.count = Object.keys(a).length;
obj.countFn = function() {
return Object.keys(a).length;
};
setTimeout(function () {
a.ccc = 'test';
a.ddd = 'test';
setTimeout(function () {
axm.stopProbes();
}, 1100);
}, 1100);

View File

@ -0,0 +1,6 @@
var axm = require('../..');
setTimeout(function() {
axm.notify('hey');
}, 100);

View File

@ -0,0 +1,9 @@
var axm = require('../..');
axm.catchAll();
setTimeout(function() {
throw new Error('global error');
}, 200);

View File

@ -0,0 +1,118 @@
var axm = require('../..');
var probe = axm.probe();
var users = {
'alex' : 'ok',
'musta' : 'fa'
};
/**
* Monitor synchronous return of functions
*/
var rt_users = probe.metric({
name : 'Realtime user',
agg_type: 'max',
value : function() {
return Object.keys(users).length;
}
});
/**
* Monitor value
*/
var config_example = {
val : 'hey',
test : {
a : 'good',
sign : 'healthy'
}
}
var cheerio = probe.metric({
name : 'Cheerio',
value : function() {
return config_example;
}
});
/**
* Monitor value
*/
// probe.transpose('docker_config', config_example);
probe.transpose({
name : 'style_2_docker_config',
data : function doSomething() {
return config_example;
}
});
probe.transpose('style_1_docker_config', function doSomething() {
return config_example;
});
/**
* Meter for HTTP
*/
var meter = probe.meter({
name : 'req/min',
agg_type: 'min',
seconds : 60
});
var http = require('http');
http.createServer(function(req, res) {
meter.mark();
res.end('Thanks');
}).listen(3400);
/**
* Meter example
*/
var meter2 = probe.meter({
name : 'random',
unit : 'rd',
agg_type: 'sum',
seconds : 1
});
setInterval(function() {
meter2.mark(Math.random() * 100);
}, 10);
setTimeout(function() {
counter.inc();
config_example = { yes : true };
}, 1100);
/**
* Counter
*/
var counter = probe.counter({
name : 'Downloads',
agg_type: 'max'
});
counter.inc();
counter.dec();
counter.inc();
counter.inc();
// console.log(cheerio.val());
// setInterval(function() {
// console.log(counter.val());
// console.log(meter.val());
// console.log(meter2.val());
// console.log(rt_users.val());
// console.log(cheerio.val());
// }, 1500);

View File

@ -0,0 +1,15 @@
var pmx = require('../..');
pmx.scopedAction('scoped:action', function(opts, res) {
var i = setInterval(function() {
// Emit progress data
res.send('data random');
}, 100);
setTimeout(function() {
clearInterval(i);
res.end('end data');
}, 800);
});

View File

@ -0,0 +1 @@

View File

@ -0,0 +1,36 @@
var axm = require('../..');
var probe = axm.probe();
var config_example = {
val : 'hey',
test : {
a : 'good',
sign : 'healthy'
}
}
/**
* Monitor value
*/
// This is ompossible to do :( (refresh value by pointer):
//
// probe.transpose('docker_config', config_example);
probe.transpose({
name : 'style_2_docker_config',
data : function doSomething() {
return config_example;
}
});
probe.transpose('style_1_docker_config', function doSomething() {
return config_example;
});
setTimeout(function() {
config_example.val = 'new value';
}, 1100);

View File

@ -0,0 +1,23 @@
var assert = require('assert');
function Plan(count, done) {
this.done = done;
this.count = count;
}
Plan.prototype.ok = function(expression) {
assert(expression);
if (this.count === 0) {
assert(false, 'Too many assertions called');
} else {
this.count--;
}
if (this.count === 0) {
this.done();
}
};
module.exports = Plan;

View File

@ -0,0 +1,4 @@
--require should
--reporter spec
--timeout 30000000
--slow 300

View File

@ -0,0 +1,119 @@
var pmx = require('..');
var should = require('should');
function forkWithoutEnv() {
var app = require('child_process').fork(__dirname + '/fixtures/module/module.fixture.js', [], {
env : {
}
});
return app;
}
function forkWithSpecificVar() {
var app = require('child_process').fork(__dirname + '/fixtures/module/module.fixture.js', [], {
env : {
'module' : '{ "option1" : "value1", "option2" : "value2", "initial" : "over" }'
}
});
return app;
}
describe('PMX module', function() {
var app;
var action_name;
it('should emit a new action', function(done) {
// 1 - It should emit an action
app = forkWithoutEnv();
app.once('message', function(dt) {
/**
* Right event sent
*/
dt.type.should.eql('axm:option:configuration');
/**
* Options set
*/
dt.data.show_module_meta.should.exists;
dt.data.description.should.eql('comment');
dt.data.module_version.should.eql('1.0.0');
dt.data.module_name.should.eql('module');
/**
* Configuration succesfully passed
*/
dt.data.initial.should.eql('init-val');
/**
* Should configuration variable be mirrored into module_conf
* attribute (for keymetrics purposes)
*/
dt.data.module_conf.initial.should.eql('init-val');
done();
});
});
it('should emit a new action', function(done) {
// 1 - It should emit an action
app = forkWithSpecificVar();
app.once('message', function(dt) {
/**
* Right event sent
*/
dt.type.should.eql('axm:option:configuration');
/**
* Options set
*/
dt.data.show_module_meta.should.exists;
dt.data.description.should.eql('comment');
dt.data.module_version.should.eql('1.0.0');
dt.data.module_name.should.eql('module');
/**
* Configuration succesfully passed
*/
dt.data.option1.should.eql('value1');
dt.data.option2.should.eql('value2');
dt.data.initial.should.eql('over');
/**
* Should configuration variable be mirrored into module_conf
* attribute (for keymetrics purposes)
*/
dt.data.module_conf.option1.should.eql('value1');
dt.data.module_conf.option2.should.eql('value2');
dt.data.module_conf.initial.should.eql('over');
done();
});
});
it('should find existing file', function(done) {
var content = pmx.resolvePidPaths([
'asdasdsad',
'asdasd',
'lolilol',
__dirname + '/fixtures/file.pid'
]);
content.should.eql(1456);
done();
});
it('should return null', function(done) {
var content = pmx.resolvePidPaths([
'asdasdsad',
'asdasd',
'lolilol'
]);
should(content).be.null;
done();
});
});

View File

@ -0,0 +1,65 @@
var axm = require('..');
function fork() {
return require('child_process').fork(__dirname + '/fixtures/monitor.mock.js', []);
}
function forkMonitor2() {
return require('child_process').fork(__dirname + '/fixtures/monitor2.mock.js', []);
}
describe('Monitor', function() {
it('should have properties', function(done) {
axm.should.have.property('enableProbes');
done();
});
it('should send event when called', function(done) {
var app = fork();
app.once('message', function(pck) {
pck.type.should.eql('axm:monitor');
pck.data.it_works.should.eql(true);
pck.data.value.should.eql(20);
app.once('message', function(pck) {
pck.type.should.eql('axm:monitor');
pck.data.it_works.should.eql(false);
pck.data.value.should.eql(99);
pck.data.i.should.eql(2);
app.kill();
done();
});
});
});
it('should send right value with monitor2', function(done) {
var app = forkMonitor2();
app.once('message', function(pck) {
pck.type.should.eql('axm:monitor');
pck.data.count.should.eql(2);
pck.data.countFn.should.eql(2);
app.once('message', function(pck) {
pck.type.should.eql('axm:monitor');
pck.data.count.should.eql(2);
pck.data.countFn.should.eql(4);
app.kill();
done();
});
});
});
});

View File

@ -0,0 +1,90 @@
var axm = require('..');
var should = require('should');
function forkCatch() {
var app = require('child_process').fork(__dirname + '/fixtures/notify_catch_all.mock.js', []);
return app;
}
function forkNotify() {
var app = require('child_process').fork(__dirname + '/fixtures/notify.mock.js', []);
return app;
}
describe('Notify exceptions', function() {
it('should have the right properties', function(done) {
axm.should.have.property('catchAll');
axm.should.have.property('notify');
axm.should.have.property('expressErrorHandler');
done();
});
it('should process simple string error', function(done) {
var ret = axm._interpretError('this is a message');
should.exist(ret.stack);
should.exist(ret.message);
ret.message.should.eql('this is a message');
done();
});
it('should process JSON object', function(done) {
var ret = axm._interpretError({
line : 'ok',
env : 'sisi'
});
should.exist(ret.stack);
should.exist(ret.message);
ret.data.line.should.eql('ok');
ret.data.env.should.eql('sisi');
done();
});
it('should process simple string', function(done) {
var ret = axm._interpretError('Error');
should.exist(ret.stack);
should.exist(ret.message);
done();
});
it('should process error', function(done) {
var ret = axm._interpretError(new Error('error'));
should.exist(ret.stack);
should.exist(ret.message);
done();
});
it('should catchAll exception in fork mode', function(done) {
var app = forkCatch();
app.once('message', function(data) {
data.type.should.eql('axm:option:configuration');
app.once('message', function(data) {
data.type.should.eql('process:exception');
data.data.message.should.eql('global error');
process.kill(app.pid);
done();
});
});
});
it('should notify process about error', function(done) {
var app = forkNotify();
app.once('message', function(data) {
data.type.should.eql('process:exception');
data.data.message.should.eql('hey');
process.kill(app.pid);
done();
});
});
});

View File

@ -0,0 +1,18 @@
var pmx = require('..');
describe('PMX driver', function() {
it('should have the right properties', function(done) {
pmx.should.have.property('emit');
pmx.should.have.property('action');
done();
});
describe('Event module', function() {
it('should not hang if process not forked', function(done) {
pmx.emit('testo', { data : 'ok' });
done();
});
});
});

View File

@ -0,0 +1,83 @@
var axm = require('..');
function fork(script) {
var app = require('child_process').fork(__dirname + (script || '/fixtures/probe.fixture.js'), []);
return app;
}
function forkHistogram() {
var app = require('child_process').fork(__dirname + '/fixtures/histogram.fixture.js', []);
return app;
}
describe('Probe', function() {
it('should have the right properties', function(done) {
axm.should.have.property('probe');
var probe = axm.probe();
probe.should.have.property('meter');
probe.should.have.property('metric');
probe.should.have.property('histogram');
probe.should.have.property('counter');
done();
});
it('should fork app and receive data from probes', function(done) {
var app = fork();
app.on('message', function(pck) {
// Will iterate two times, metric change the value to false
pck.type.should.eql('axm:monitor');
pck.data.should.have.properties('req/min',
'Realtime user',
'random',
'Cheerio');
if (pck.data.random.value && pck.data.random.agg_type == 'sum' &&
pck.data.Cheerio.value.yes == true && pck.data.Cheerio.agg_type == 'avg' &&
pck.data.Downloads.value > 1 && pck.data.Downloads.agg_type == 'max') {
app.kill();
done();
}
});
});
it('should receive transposed data', function(done) {
var app = fork('/fixtures/transpose.fixture.js');
var pass = 0;
app.on('message', function(pck) {
// Will iterate two times, metric change the value to false
pck.type.should.eql('axm:monitor');
pck.data.should.have.properties('style_2_docker_config',
'style_1_docker_config');
if (pck.data.style_1_docker_config.value.val == 'new value' &&
pck.data.style_2_docker_config.value.val == 'new value') {
app.kill();
done();
}
});
});
it('should fork app and receive data', function(done) {
var app = forkHistogram();
app.on('message', function(pck) {
pck.type.should.eql('axm:monitor');
if (pck.data.mean && pck.data.mean.agg_type == 'avg' &&
pck.data.min && pck.data.min.agg_type == 'min' &&
pck.data.test && pck.data.test.agg_type == 'sum') {
app.kill();
done();
}
});
});
})

View File

@ -0,0 +1,8 @@
var axm = require('..');
axm.action('test:with:options', function(options, reply) {
console.log('CHILD: Action test called from external process');
reply({ res : 'hello moto', options : options});
});

View File

@ -0,0 +1,7 @@
var axm = require('..');
axm.action('test:nab', {comment : 'This is a test', display : true}, function(reply) {
console.log('CHILD: Action test called from external process');
reply({ res : 'hello moto'});
});

View File

@ -0,0 +1,47 @@
var pmx = require('..');
var Profiling = require('../lib/probes/profiling.js');
var should = require('should');
var shelljs = require('shelljs');
describe('Profiling', function() {
it('should have right properties', function(done) {
pmx.should.have.property('v8Profiling');
Profiling.should.have.property('detectV8Profiler');
Profiling.should.have.property('exposeProfiling');
Profiling.should.have.property('v8Profiling');
done();
});
it('should return error as v8-profiler not present', function(done) {
Profiling.detectV8Profiler(function(err, data) {
err.should.not.be.null;
should(data).be.undefined;
done();
});
});
describe.skip('V8-profiler', function() {
before(function(done) {
shelljs.exec('npm install v8-profiler', function() {
setTimeout(done, 10000);
});
});
after(function(done) {
shelljs.exec('npm uninstall v8-profiler', function() {
done();
});
});
it('should detect v8 profiler', function(done) {
Profiling.detectV8Profiler(function(err, data) {
console.log(arguments);
err.should.not.be.null;
should(data).be.undefined;
done();
});
});
});
});

View File

View File

@ -0,0 +1,37 @@
require('../..').init({
ignore_routes : [/\/socket\.io.*/]
});
var express = require('express');
var app = express();
app.get('/', function(req, res) {
res.send(202, {success:true});
});
app.get('/nothing', function(req, res) {
res.send('yes');
});
app.get('/slow', function(req, res) {
setTimeout(function() {
res.send('yes');
}, 700);
});
app.get('/socket.io/slow', function(req, res) {
setTimeout(function() {
res.send('yes');
}, 700);
});
app.get('/nothing2', function(req, res) {
setTimeout(function() {
res.send('yes');
}, 1000);
});
app.listen(9007);

View File

@ -0,0 +1,14 @@
{
"name": "transaction",
"version": "0.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"devDependencies" : {
"express" : "*"
},
"author": "",
"license": "ISC"
}