express-status-monitor
Advanced tools
Comparing version 0.1.5 to 0.1.6
@@ -12,4 +12,3 @@ { | ||
"import/no-extraneous-dependencies": [ | ||
"error", | ||
{ | ||
"error", { | ||
"peerDependencies": true | ||
@@ -20,2 +19,2 @@ } | ||
} | ||
} | ||
} |
/* eslint no-console: "off" */ | ||
const socketIoPort = 2222; | ||
const express = require('express'); | ||
// This is optional. If your server uses socket.io already, pass it to config as `webserver` along with it's port. | ||
const socketio = require('socket.io')(socketIoPort); | ||
const app = express(); | ||
const port = process.env.PORT || 3000; | ||
app.use(require('../index')({ path: '/' })); | ||
app.use(require('../index')({ | ||
path: '/', | ||
// Use existing socket.io instance. | ||
// websocket: socketio, | ||
// Pass socket.io instance port down to config. | ||
// Use only if you're passing your own instance. | ||
// port: socketIoPort, | ||
})); | ||
app.use(require('express-favicon-short-circuit')); | ||
@@ -10,0 +22,0 @@ |
const request = require('request'); | ||
const requestUrl = 'http://localhost:3000/return-status/'; | ||
const port = 3000; | ||
const requestUrl = `http://localhost:${port}/return-status/`; | ||
const interval = 50; | ||
@@ -9,2 +11,3 @@ | ||
request.get(`${requestUrl}${code}`); | ||
makeDummyCall(); | ||
@@ -11,0 +14,0 @@ }, interval); |
@@ -1,1 +0,1 @@ | ||
module.exports = require('./src/middleware-wrapper'); | ||
module.exports = require('./src/middleware-wrapper'); |
{ | ||
"name": "express-status-monitor", | ||
"version": "0.1.5", | ||
"version": "0.1.6", | ||
"description": "Realtime Monitoring for Express-based Node applications", | ||
@@ -46,15 +46,27 @@ "main": "index.js", | ||
"on-headers": "^1.0.1", | ||
"pidusage": "^1.0.8", | ||
"socket.io": "^1.4.8" | ||
"pidusage": "^1.1.0", | ||
"socket.io": "^1.5.1" | ||
}, | ||
"scripts": { | ||
"coverage": "istanbul cover _mocha test -- --recursive", | ||
"test": "mocha --recursive" | ||
"test": "mocha --recursive", | ||
"snyk-protect": "snyk protect", | ||
"prepublish": "npm run snyk-protect", | ||
"example": "cd examples && npm start", | ||
"eslint": "eslint ." | ||
}, | ||
"devDependencies": { | ||
"bithound": "^1.7.0", | ||
"chai": "^3.5.0", | ||
"eslint": "^3.9.1", | ||
"eslint-config-airbnb": "^12.0.0", | ||
"eslint-plugin-import": "^1.16.0", | ||
"eslint-plugin-jsx-a11y": "^2.2.3", | ||
"eslint-plugin-react": "^6.5.0", | ||
"istanbul": "^0.4.5", | ||
"mocha": "^3.0.2", | ||
"sinon": "^1.17.5" | ||
} | ||
"sinon": "^1.17.5", | ||
"snyk": "^1.19.1" | ||
}, | ||
"snyk": true | ||
} |
# express-status-monitor | ||
[![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/express-status-monitor/Lobby/?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) | ||
[![express-status-monitor on npm](https://img.shields.io/npm/v/express-status-monitor.svg)](https://www.npmjs.com/express-status-monitor) | ||
[![npm](https://img.shields.io/npm/dt/express-status-monitor.svg)](https://img.shields.io/npm/dt/express-status-monitor.svg) | ||
[![bitHound Overall Score](https://www.bithound.io/github/RafalWilinski/express-status-monitor/badges/score.svg)](https://www.bithound.io/github/RafalWilinski/express-status-monitor) | ||
[![CircleCI](https://img.shields.io/circleci/project/github/RafalWilinski/express-status-monitor/master.svg)](https://circleci.com/gh/RafalWilinski/express-status-monitor) | ||
Simple, self-hosted module based on Socket.io and Chart.js to report realtime server metrics for Express-based node servers. | ||
@@ -38,2 +44,3 @@ | ||
path: '/status', | ||
websocket: existingSocketIoInstance, | ||
spans: [{ | ||
@@ -73,2 +80,6 @@ interval: 1, // Every second | ||
## Using module with socket.io in project | ||
If you're using socket.io in your project, this module could break your project because this module by default will spawn its own socket.io instance. To mitigate that, fill websocket parameter with your main socket.io instance as well as port parameter. | ||
## Tests and coverage | ||
@@ -75,0 +86,0 @@ |
@@ -18,2 +18,4 @@ module.exports = { | ||
], | ||
port: null, | ||
websocket: null, | ||
}; |
module.exports = (statusCode, startTime, spans) => { | ||
const diff = process.hrtime(startTime); | ||
const responseTime = diff[0] * 1e3 + diff[1] * 1e-6; | ||
const responseTime = ((diff[0] * 1e3) + diff[1]) * 1e-6; | ||
const category = Math.floor(statusCode / 100); | ||
@@ -8,6 +8,6 @@ | ||
const last = span.responses[span.responses.length - 1]; | ||
if (last !== undefined && last.timestamp / 1000 + span.interval > Date.now() / 1000) { | ||
last[category]++; | ||
last.count++; | ||
last.mean = last.mean + ((responseTime - last.mean) / last.count); | ||
if (last !== undefined && (last.timestamp / 1000) + span.interval > Date.now() / 1000) { | ||
last[category] += 1; | ||
last.count += 1; | ||
last.mean += ((responseTime - last.mean) / last.count); | ||
} else { | ||
@@ -14,0 +14,0 @@ span.responses.push({ |
module.exports = (io, span) => { | ||
io.emit('stats', { | ||
io.emit('esm_stats', { | ||
os: span.os[span.os.length - 2], | ||
@@ -4,0 +4,0 @@ responses: span.responses[span.responses.length - 2], |
/* eslint strict: "off" */ | ||
'use strict'; | ||
@@ -9,14 +10,18 @@ | ||
module.exports = (server, spans) => { | ||
module.exports = (server, config) => { | ||
if (io === null || io === undefined) { | ||
io = socketIo(server); | ||
if (config.websocket !== null) { | ||
io = config.websocket; | ||
} else { | ||
io = socketIo(server); | ||
} | ||
io.on('connection', (socket) => { | ||
socket.emit('start', spans); | ||
socket.on('change', () => { | ||
socket.emit('start', spans); | ||
socket.emit('esm_start', config.spans); | ||
socket.on('esm_change', () => { | ||
socket.emit('esm_start', config.spans); | ||
}); | ||
}); | ||
spans.forEach((span) => { | ||
config.spans.forEach((span) => { | ||
span.os = []; | ||
@@ -23,0 +28,0 @@ span.responses = []; |
@@ -14,3 +14,7 @@ const defaultConfig = require('./default-config'); | ||
config.port = (typeof config.port === 'number') ? config.port : defaultConfig.port; | ||
config.websocket = (typeof config.websocket === 'object') ? config.websocket : defaultConfig.websocket; | ||
return config; | ||
}; |
@@ -15,2 +15,3 @@ const fs = require('fs'); | ||
.replace(/{{title}}/g, config.title) | ||
.replace(/{{port}}/g, config.port) | ||
.replace(/{{script}}/g, fs.readFileSync(path.join(__dirname, '/public/javascripts/app.js'))) | ||
@@ -20,3 +21,3 @@ .replace(/{{style}}/g, fs.readFileSync(path.join(__dirname, '/public/stylesheets/style.css'))); | ||
return (req, res, next) => { | ||
socketIoInit(req.socket.server, config.spans); | ||
socketIoInit(req.socket.server, config); | ||
@@ -27,3 +28,3 @@ const startTime = process.hrtime(); | ||
} else { | ||
onHeaders(res, () => { onHeadersListener(res.statusCode, startTime, config.spans) }); | ||
onHeaders(res, () => { onHeadersListener(res.statusCode, startTime, config.spans); }); | ||
next(); | ||
@@ -30,0 +31,0 @@ } |
@@ -0,1 +1,7 @@ | ||
/* | ||
eslint-disable no-plusplus, no-var, strict, vars-on-top, prefer-template, | ||
func-names, prefer-arrow-callback, no-loop-func | ||
*/ | ||
/* global Chart, location, document, port, parseInt, io */ | ||
'use strict'; | ||
@@ -6,7 +12,7 @@ | ||
Chart.defaults.global.legend.display = false; | ||
Chart.defaults.global.elements.line.backgroundColor = "rgba(0,0,0,0)"; | ||
Chart.defaults.global.elements.line.borderColor = "rgba(0,0,0,0.9)"; | ||
Chart.defaults.global.elements.line.backgroundColor = 'rgba(0,0,0,0)'; | ||
Chart.defaults.global.elements.line.borderColor = 'rgba(0,0,0,0.9)'; | ||
Chart.defaults.global.elements.line.borderWidth = 2; | ||
var socket = io(location.protocol + '//' + location.hostname + ':' + location.port); | ||
var socket = io(location.protocol + '//' + location.hostname + ':' + (port || location.port)); | ||
var defaultSpan = 0; | ||
@@ -20,3 +26,3 @@ var spans = []; | ||
lineTension: 0.2, | ||
pointRadius: 0 | ||
pointRadius: 0, | ||
}; | ||
@@ -28,4 +34,4 @@ | ||
ticks: { | ||
beginAtZero: true | ||
} | ||
beginAtZero: true, | ||
}, | ||
}], | ||
@@ -35,15 +41,15 @@ xAxes: [{ | ||
time: { | ||
unitStepSize: 30 | ||
unitStepSize: 30, | ||
}, | ||
gridLines: { | ||
display: false | ||
} | ||
}] | ||
display: false, | ||
}, | ||
}], | ||
}, | ||
tooltips: { | ||
enabled: false | ||
enabled: false, | ||
}, | ||
responsive: true, | ||
maintainAspectRatio: false, | ||
animation: false | ||
animation: false, | ||
}; | ||
@@ -58,3 +64,3 @@ | ||
}, | ||
options: defaultOptions | ||
options: defaultOptions, | ||
}); | ||
@@ -72,3 +78,2 @@ }; | ||
var rpsDataset = [Object.create(defaultDataset)]; | ||
var statusCodesDataset = [Object.create(defaultDataset)]; | ||
@@ -81,8 +86,8 @@ var cpuStat = document.getElementById('cpuStat'); | ||
var cpuChartCtx = document.getElementById("cpuChart"); | ||
var memChartCtx = document.getElementById("memChart"); | ||
var loadChartCtx = document.getElementById("loadChart"); | ||
var responseTimeChartCtx = document.getElementById("responseTimeChart"); | ||
var rpsChartCtx = document.getElementById("rpsChart"); | ||
var statusCodesChartCtx = document.getElementById("statusCodesChart"); | ||
var cpuChartCtx = document.getElementById('cpuChart'); | ||
var memChartCtx = document.getElementById('memChart'); | ||
var loadChartCtx = document.getElementById('loadChart'); | ||
var responseTimeChartCtx = document.getElementById('responseTimeChart'); | ||
var rpsChartCtx = document.getElementById('rpsChart'); | ||
var statusCodesChartCtx = document.getElementById('statusCodesChart'); | ||
@@ -102,9 +107,9 @@ var cpuChart = createChart(cpuChartCtx, cpuDataset); | ||
Object.create(defaultDataset), | ||
Object.create(defaultDataset) | ||
] | ||
Object.create(defaultDataset), | ||
], | ||
}, | ||
options: defaultOptions | ||
options: defaultOptions, | ||
}); | ||
statusCodesChart.data.datasets.forEach(function(dataset, index) { | ||
statusCodesChart.data.datasets.forEach(function (dataset, index) { | ||
dataset.borderColor = statusCodesColors[index]; | ||
@@ -117,3 +122,3 @@ }); | ||
e.target.classList.add('active'); | ||
defaultSpan = parseInt(e.target.id); | ||
defaultSpan = parseInt(e.target.id, 10); | ||
@@ -125,6 +130,6 @@ var otherSpans = document.getElementsByTagName('span'); | ||
socket.emit('change'); | ||
socket.emit('esm_change'); | ||
}; | ||
socket.on('start', function (data) { | ||
socket.on('esm_start', function (data) { | ||
// Remove last element of Array because it contains malformed responses data. | ||
@@ -179,5 +184,5 @@ // To keep consistency we also remove os data. | ||
for(var i = 0; i < 4; i++) { | ||
for (var i = 0; i < 4; i++) { | ||
statusCodesChart.data.datasets[i].data = data[defaultSpan].responses.map(function (point) { | ||
return point[i+2]; | ||
return point[i + 2]; | ||
}); | ||
@@ -188,7 +193,8 @@ } | ||
if (data[defaultSpan].responses.length >= 2) { | ||
var deltaTime = lastResponseMetric.timestamp - data[defaultSpan].responses[data[defaultSpan].responses.length - 2].timestamp; | ||
var deltaTime = lastResponseMetric.timestamp - | ||
data[defaultSpan].responses[data[defaultSpan].responses.length - 2].timestamp; | ||
if (deltaTime < 1) deltaTime = 1000; | ||
rpsStat.textContent = (lastResponseMetric.count / deltaTime * 1000).toFixed(2); | ||
rpsStat.textContent = ((lastResponseMetric.count / deltaTime) * 1000).toFixed(2); | ||
rpsChart.data.datasets[0].data = data[defaultSpan].responses.map(function (point) { | ||
return point.count / deltaTime * 1000; | ||
return (point.count / deltaTime) * 1000; | ||
}); | ||
@@ -207,7 +213,7 @@ rpsChart.data.labels = data[defaultSpan].responses.map(addTimestamp); | ||
retention: span.retention, | ||
interval: span.interval | ||
interval: span.interval, | ||
}); | ||
var spanNode = document.createElement('span'); | ||
var textNode = document.createTextNode((span.retention * span.interval) / 60 + "M"); | ||
var textNode = document.createTextNode(((span.retention * span.interval) / 60) + 'M'); | ||
spanNode.appendChild(textNode); | ||
@@ -222,4 +228,5 @@ spanNode.setAttribute('id', index); | ||
socket.on('stats', function (data) { | ||
if (data.retention === spans[defaultSpan].retention && data.interval === spans[defaultSpan].interval) { | ||
socket.on('esm_stats', function (data) { | ||
if (data.retention === spans[defaultSpan].retention && | ||
data.interval === spans[defaultSpan].interval) { | ||
var os = data.os; | ||
@@ -259,4 +266,4 @@ var responses = data.responses; | ||
if (deltaTime < 1) deltaTime = 1000; | ||
rpsStat.textContent = (responses.count / deltaTime * 1000).toFixed(2); | ||
rpsChart.data.datasets[0].data.push(responses.count / deltaTime * 1000); | ||
rpsStat.textContent = ((responses.count / deltaTime) * 1000).toFixed(2); | ||
rpsChart.data.datasets[0].data.push((responses.count / deltaTime) * 1000); | ||
rpsChart.data.labels.push(responses.timestamp); | ||
@@ -266,4 +273,4 @@ } | ||
if (responses) { | ||
for(var i = 0; i < 4; i++) { | ||
statusCodesChart.data.datasets[i].data.push(data.responses[i+2]); | ||
for (var i = 0; i < 4; i++) { | ||
statusCodesChart.data.datasets[i].data.push(data.responses[i + 2]); | ||
} | ||
@@ -275,3 +282,3 @@ statusCodesChart.data.labels.push(data.responses.timestamp); | ||
if (spans[defaultSpan].retention < chart.data.labels.length) { | ||
chart.data.datasets.forEach(function(dataset) { | ||
chart.data.datasets.forEach(function (dataset) { | ||
dataset.data.shift(); | ||
@@ -285,2 +292,2 @@ }); | ||
} | ||
}); | ||
}); |
@@ -17,3 +17,3 @@ const chai = require('chai'); | ||
sinon.assert.calledWith(io.emit, 'stats'); | ||
sinon.assert.calledWith(io.emit, 'esm_stats'); | ||
}); | ||
@@ -20,0 +20,0 @@ }); |
@@ -20,3 +20,3 @@ const chai = require('chai'); | ||
socketIoInit({}, spans); | ||
socketIoInit({}, defaultConfig); | ||
@@ -23,0 +23,0 @@ spans.forEach((span) => { |
@@ -0,1 +1,2 @@ | ||
/* eslint-disable no-unused-expressions */ | ||
const chai = require('chai'); | ||
@@ -24,6 +25,14 @@ | ||
}); | ||
it('then port === null', () => { | ||
chai.expect(config.port).to.be.null; | ||
}); | ||
it('then websocket === null', () => { | ||
chai.expect(config.websocket).to.be.null; | ||
}); | ||
}); | ||
describe('when config is invalid', () => { | ||
const config = validate({ title: true, path: false, spans: 'not-an-array' }); | ||
const config = validate({ title: true, path: false, spans: 'not-an-array', port: 'abc', websocket: false }); | ||
@@ -41,6 +50,14 @@ it(`then title === ${defaultConfig.title}`, () => { | ||
}); | ||
it('then port === null', () => { | ||
chai.expect(config.port).to.be.null; | ||
}); | ||
it('then websocket === null', () => { | ||
chai.expect(config.websocket).to.be.null; | ||
}); | ||
}); | ||
describe('when config is valid', () => { | ||
const customConfig = { title: 'Custom title', path: '/custom-path', spans: [{}, {}, {}] } | ||
const customConfig = { title: 'Custom title', path: '/custom-path', spans: [{}, {}, {}], port: 9999, websocket: {} }; | ||
const config = validate(customConfig); | ||
@@ -59,4 +76,12 @@ | ||
}); | ||
it('then websocket === {}', () => { | ||
config.websocket.should.deep.equal({}); | ||
}); | ||
it(`then port === ${customConfig.port}`, () => { | ||
config.port.should.equal(customConfig.port); | ||
}); | ||
}); | ||
}); | ||
}); |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
32675
28
702
94
11
Updatedpidusage@^1.1.0
Updatedsocket.io@^1.5.1