The @pm2/io module comes along with PM2. It is the PM2 part responsible for gathering the metrics, reporting exceptions, exposing remote actions and every outside interaction with your application.
- Installation
- Expose Custom Metrics
- Expose Remote Actions
- Report Caught Exceptions
- Report Custom Events (deprecated)
- Predefined Metrics
- Configuration
- Entrypoint
- Development
With npm:
npm install @pm2/io --save
With yarn:
yarn add @pm2/io
@pm2/io allows you to gather metrics from your code to be reported in pm2 monit
or in the Keymetrics dashboard.
You can create a new custom metrics with the method metric()
of @pm2/io
.
const io = require('@pm2/io');
io.metric({
name: 'Realtime user',
});
This arguments are available:
- name: The metric name (required; string)
- type: The type of metric (default 'metric', string)
- agg_type: type of aggregation (default 'avg'; string: )
- unit: unit of the measure (default ''; string)
- historic: keep the history in PM2 Plus (default: true; boolean)
There are 4 different types of metrics:
- metric: To expose a variable's value
- counter: A discrete counter to be triggered manually to count a number of occurrence
- meter: To measure a frequency, a number of occurrences of a repeating event per unit of time
- histogram: To measure a statistic, a statistic on a metric over the last 5 minutes
The first type of metric, called metric
, allows to expose a variable's value. The variable can be exposed passively, with a function that gets called every second, or actively, with a method that you use to update the value.
const io = require('@pm2/io');
io.metric({
name: 'Realtime user',
value: function() {
return Object.keys(users).length;
}
});
In active mode, you need to create a probe and call the method set()
to update the value.
const Realtime_Value = io.metric({
name: 'Realtime Value'
});
Realtime_Value.set(23);
The second type of metric, called counter
, is a discrete counter that helps you count the number of occurrence of a particular event. The counter starts at 0 and can be incremented or decremented.
const io = require('@pm2/io');
const Current_req_processed = io.counter({
name: 'Current req processed',
type: 'counter',
});
http.createServer((req, res) => {
// Increment the counter, counter will eq 1
Current_req_processed.inc();
req.on('end', () => {
// Decrement the counter, counter will eq 0
Current_req_processed.dec();
});
});
The third type of metric, called meter
, compute the frequency of an event. Each time the event happens, you need to call the mark()
method. By default, the frequency is the number of events per second over the last minute.
const io = require('@pm2/io');
const reqsec = io.meter({
name: 'req/sec',
type: 'meter',
});
http.createServer((req, res) => {
reqsec.mark();
res.end({ success: true });
});
Additional options:
- samples: (optional)(default: 1) Rate unit. Defaults to 1 sec.
- timeframe: (optional)(default: 60) Timeframe over which the events will be analyzed. Defaults to 60 sec.
Collect values and provide statistic tools to explore their distribution over the last 5 minutes.
const io = require('@pm2/io');
const latency = io.histogram({
name: 'latency',
measurement: 'mean'
});
const latencyValue = 0;
setInterval(() => {
latencyValue = Math.round(Math.random() * 100);
latency.update(latencyValue);
}, 100);
Options are:
- measurement : default: mean; min, max, sum, count, variance, mean, stddev, median, p75, p95, p99, p99.
Remotely trigger functions from Keymetrics.
The function takes a function as a parameter (cb here) and need to be called once the job is finished.
Example:
const io = require('@pm2/io');
io.action('db:clean', (cb) => {
clean.db(() => {
/**
* cb() must be called at the end of the action
*/
cb({ success: true });
});
});
Scoped Actions are advanced remote actions that can be also triggered from Keymetrics.
Two arguments are passed to the function, data (optional data sent from Keymetrics) and res that allows to emit log data and to end the scoped action.
Example:
io.scopedAction('long running lsof', (data, res) => {
const child = spawn('lsof', []);
child.stdout.on('data', (chunk) => {
chunk.toString().split('\n').forEach(function(line) {
res.send(line); // This send log to Keymetrics to be saved (for tracking)
});
});
child.stdout.on('end', (chunk) => {
res.end('end'); // This end the scoped action
});
child.on('error', (e) => {
res.error(e); // This report an error to Keymetrics
});
});
By default, in the Issue tab, you are only alerted for uncaught exceptions. Any exception that you catch is not reported. You can manually report them with the notify()
method.
const io = require('@pm2/io');
io.notifyError(new Error('This is an error'));
const io = require('@pm2/io')
io.init({
metrics: {
eventLoopActive: true, // (default: true) Monitor active handles and active requests
eventLoopDelay: true, // (default: true) Get event loop's average delay
// Network monitoring at the application level
network: {
traffic: true, // (default: true) Allow application level network monitoring
ports: true // (default: false) Shows which ports your app is listening on
},
// Transaction Tracing system configuration
transaction: {
http: true, // (default: true) HTTP routes logging
tracing: { // (default: false) Enable transaction tracing
http_latency: 1, // (default: 200) minimum latency in milliseconds to take into account
ignore_routes: ['/foo'] // (default: empty) exclude some routes
}
},
deepMetrics: {
mongo: true, // (default: true) Mongo connections monitoring
mysql: true, // (default: true) MySQL connections monitoring
mqtt: true, // (default: true) Mqtt connections monitoring
socketio: true, // (default: true) WebSocket monitoring
redis: true, // (default: true) Redis monitoring
http: true, // (default: true) Http incoming requests monitoring
https: true, // (default: true) Https incoming requests monitoring
"http-outbound": true, // (default: true) Http outbound requests monitoring
"https-outbound": true // (default: true) Https outbound requests monitoring
},
v8: {
new_space: true, // (default: true) New objects space size
old_space: true, // (default: true) Old objects space size
map_space: true, // (default: true) Map space size
code_space: true, // (default: true) Executable space size
large_object_space: true, // (default: true) Large objects space size
total_physical_size: false, // (default: false) Physical heap size
total_heap_size: true, // (default: true) Heap size
total_available_size: false, // (default: false) Total available size for the heap
total_heap_size_executable: true, // (default: true) Executable heap size
used_heap_size: true, // (default: true) Used heap size
heap_size_limit: true, // (default: true) Heap size maximum size
malloced_memory: false, // (default: false) Allocated memory
peak_malloced_memory: false, // (default: false) Peak of allocated memory
does_zap_garbage: false, // (default: false) Zap garbage enable/disable
GC: {
totalHeapSize: true, // (default: true) GC heap size
totalHeapExecutableSize: true, // (default: true) GC executable heap size
usedHeapSize: true, // (default: true) GC used heap size
heapSizeLimit: false, // (default: false) GC heap size maximum size
totalPhysicalSize: false, // (default: false) GC heap physical size
totalAvailableSize: false, // (default: false) GC available size
mallocedMemory: false, // (default: false) GC allocated memory
peakMallocedMemory: false, // (default: false) GC peak of allocated memory
gcType: true, // (default: true) Type of GC (scavenge, mark/sweep/compact, ...)
gcPause: true // (default: true) Duration of pause (in milliseconds)
}
}
},
actions: {
eventLoopDump: false, // (default: false) Enable event loop dump action
profilingCpu: true, // (default: true) Enable CPU profiling actions
profilingHeap: true // (default: true) Enable Heap profiling actions
}
});
If you want to activate default options of a section :
const io = require('@pm2/io')
io.init({
metrics: {
v8: true
}
})
If you want to activate all options of a section :
const io = require('@pm2/io')
io.init({
metrics: {
v8: 'all'
}
})
If you want to customize a section :
const io = require('@pm2/io')
io.init({
metrics: {
v8: {
new_space: false, // don't take new_space into account
...
}
}
})
Note : if you do not disable the default options they will be kept (merged with your configuration)
const app = require('express')()
const io = require('@pm2/io')
class MyEntrypoint extends io.Entrypoint {
onStop (err, code, signal, cb) {
console.log(`Application stopped with code ${code} or signal ${signal} !`)
cb()
}
onStart (cb) {
const http = require('http').Server(app)
app.get('/ok', function(req, res) {
res.send('Ok')
});
http.listen(process.env.PORT || 3000, function(){
console.log('Server express', 'listening on port', http.address().port)
cb()
});
}
}
new MyEntrypoint()
You can write your own configuration like you do for @pm2/io, just add a conf() method into your entrypoint which returns a json object. Details of configuration can be found in this section : Configuration
const io = require('@pm2/io')
class MyEntrypoint extends io.Entrypoint {
...
conf() {
return {
...
}
}
}
new MyEntrypoint()
Entrypoint allow access to an instance of @pm2/io. So you can use all features described above by calling this.io.
const app = require('express')()
const io = require('@pm2/io')
class MyEntrypoint extends io.Entrypoint {
onStart (cb) {
const http = require('http').Server(app)
app.get('/ok', function(req, res) {
res.send('Ok')
});
const counter = this.io.counter('start')
http.listen(process.env.PORT || 3000, () => {
console.log('Server express', 'listening on port', http.address().port)
counter.inc()
cb()
});
}
}
new MyEntrypoint()
To auto rebuild on file change:
$ npm install
$ npm run watch
TODO
MIT