diff --git a/README.md b/README.md index 26f89d1..5609bfc 100644 --- a/README.md +++ b/README.md @@ -61,6 +61,8 @@ chartVisibility: { cpu: true, mem: true, load: true, + eventLoop: true, + heap: true, responseTime: true, rps: true, statusCodes: true diff --git a/package-lock.json b/package-lock.json index 2e74897..d1e9b9b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1336,6 +1336,14 @@ "tslib": "^1.10.0" } }, + "event-loop-stats": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/event-loop-stats/-/event-loop-stats-1.2.0.tgz", + "integrity": "sha512-h/leAlXqoEf+D9w1dnFG0srR5vfIq59rLm9PHzcl3/GwFppd+UR46UMuLdp/mvJvuA+MjSd/dNShmuM2/dPFFw==", + "requires": { + "nan": "^2.14.0" + } + }, "execa": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/execa/-/execa-0.7.0.tgz", @@ -2475,6 +2483,11 @@ "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", "dev": true }, + "nan": { + "version": "2.14.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.0.tgz", + "integrity": "sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg==" + }, "natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", @@ -4925,4 +4938,4 @@ "integrity": "sha1-AI4G2AlDIMNy28L47XagymyKxBk=" } } -} \ No newline at end of file +} diff --git a/package.json b/package.json index 58d58af..e5efdc8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "express-status-monitor", - "version": "1.2.11", + "version": "1.3.0", "description": "Realtime Monitoring for Express-based Node applications", "main": "index.js", "keywords": [ @@ -59,6 +59,7 @@ "dependencies": { "axios": "0.19.2", "debug": "4.1.1", + "event-loop-stats": "1.2.0", "handlebars": "^4.7.6", "on-headers": "1.0.2", "pidusage": "2.0.18", diff --git a/src/helpers/default-config.js b/src/helpers/default-config.js index 98f1bd4..563acf6 100644 --- a/src/helpers/default-config.js +++ b/src/helpers/default-config.js @@ -24,10 +24,12 @@ module.exports = { cpu: true, mem: true, load: true, + heap: true, + eventLoop: true, responseTime: true, rps: true, statusCodes: true, }, ignoreStartsWith: '/admin', - healthChecks: [] + healthChecks: [], }; diff --git a/src/helpers/gather-os-metrics.js b/src/helpers/gather-os-metrics.js index f6920f3..f1b8b8b 100644 --- a/src/helpers/gather-os-metrics.js +++ b/src/helpers/gather-os-metrics.js @@ -1,5 +1,7 @@ const pidusage = require('pidusage'); const os = require('os'); +const v8 = require('v8'); +const eventLoopStats = require('event-loop-stats'); const sendMetrics = require('./send-metrics'); const debug = require('debug')('express-status-monitor'); @@ -26,9 +28,11 @@ module.exports = (io, span) => { stat.memory = stat.memory / 1024 / 1024; stat.load = os.loadavg(); stat.timestamp = Date.now(); + stat.heap = v8.getHeapStatistics(); + stat.loop = eventLoopStats.sense(); span.os.push(stat); - if (!span.responses[0] || last.timestamp + (span.interval * 1000) < Date.now()) { + if (!span.responses[0] || last.timestamp + span.interval * 1000 < Date.now()) { span.responses.push(defaultResponse); } diff --git a/src/helpers/socket-io-init.js b/src/helpers/socket-io-init.js index e060e04..7e67977 100644 --- a/src/helpers/socket-io-init.js +++ b/src/helpers/socket-io-init.js @@ -12,7 +12,7 @@ const addSocketEvents = (socket, config) => { socket.on('esm_change', () => { socket.emit('esm_start', config.spans); }); -} +}; module.exports = (server, config) => { if (io === null || io === undefined) { @@ -22,10 +22,11 @@ module.exports = (server, config) => { io = socketIo(server); } - io.on('connection', socket => { + io.on('connection', (socket) => { if (config.authorize) { - config.authorize(socket) - .then(authorized => { + config + .authorize(socket) + .then((authorized) => { if (!authorized) socket.disconnect('unauthorized'); else addSocketEvents(socket, config); }) @@ -35,7 +36,7 @@ module.exports = (server, config) => { } }); - config.spans.forEach(span => { + config.spans.forEach((span) => { span.os = []; span.responses = []; const interval = setInterval(() => gatherOsMetrics(io, span), span.interval * 1000); diff --git a/src/public/index.html b/src/public/index.html index 5c8ed73..26ed909 100644 --- a/src/public/index.html +++ b/src/public/index.html @@ -1,97 +1,114 @@ - - {{title}} - - - - - -
-
- {{title}} -
-
-
-
-
-
CPU Usage
-

- %

-
-
- -
-
-
-
-
Memory Usage
-

- %

-
-
- -
-
-
-
-
One Minute Load Avg
-

-

-
-
- -
-
-
-
-
Response Time
-

-

-
-
- -
-
-
-
-
Requests per Second
-

-

-
-
- -
-
-
-
-
Status Codes
-
2xx
-
3xx
-
4xx
-
5xx
-
-
- -
-
-
- {{#each healthCheckResults}} -
-
-
{{path}}
+ + {{title}} + + + + + +
+
+ {{title}} +
+
+
+
+
CPU Usage
+

- %

+
+
+
-
-

{{status}}

+
+
+
+
Memory Usage
+

- %

+
+
+ +
+
+
+
+
Heap Usage
+

- %

+
+
+ +
+
+
+
+
One Minute Load Avg
+

-

+
+
+ +
+
+
+
+
Spent in Event Loop
+

ms

+
+
+ +
+
+
+
+
Response Time
+

-

+
+
+ +
+
+
+
+
Requests per Second
+

-

+
+
+ +
+
+
+
+
Status Codes
+
2xx
+
3xx
+
4xx
+
5xx
+
+
+ +
+
+
+ {{#each healthCheckResults}} +
+
+
{{path}}
+
+
+

{{status}}

+
+ {{/each}} +
+
- {{/each}} -
- -
- - + + diff --git a/src/public/javascripts/app.js b/src/public/javascripts/app.js index 6baa930..d699a10 100644 --- a/src/public/javascripts/app.js +++ b/src/public/javascripts/app.js @@ -13,7 +13,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 + ':' + (port || location.port), { path: socketPath }); +var socket = io(location.protocol + '//' + location.hostname + ':' + (port || location.port), { + path: socketPath, +}); var defaultSpan = 0; var spans = []; var statusCodesColors = ['#75D701', '#47b8e0', '#ffc952', '#E53A40']; @@ -27,20 +29,24 @@ var defaultDataset = { var defaultOptions = { scales: { - yAxes: [{ - ticks: { - beginAtZero: true, - }, - }], - xAxes: [{ - type: 'time', - time: { - unitStepSize: 30, + yAxes: [ + { + ticks: { + beginAtZero: true, + }, }, - gridLines: { - display: false, + ], + xAxes: [ + { + type: 'time', + time: { + unitStepSize: 30, + }, + gridLines: { + display: false, + }, }, - }], + ], }, tooltips: { enabled: false, @@ -68,24 +74,32 @@ var addTimestamp = function (point) { var cpuDataset = [Object.create(defaultDataset)]; var memDataset = [Object.create(defaultDataset)]; var loadDataset = [Object.create(defaultDataset)]; +var heapDataset = [Object.create(defaultDataset)]; +var eventLoopDataset = [Object.create(defaultDataset)]; var responseTimeDataset = [Object.create(defaultDataset)]; var rpsDataset = [Object.create(defaultDataset)]; var cpuStat = document.getElementById('cpuStat'); var memStat = document.getElementById('memStat'); var loadStat = document.getElementById('loadStat'); +var heapStat = document.getElementById('heapStat'); +var eventLoopStat = document.getElementById('eventLoopStat'); var responseTimeStat = document.getElementById('responseTimeStat'); var rpsStat = document.getElementById('rpsStat'); var cpuChartCtx = document.getElementById('cpuChart'); var memChartCtx = document.getElementById('memChart'); var loadChartCtx = document.getElementById('loadChart'); +var heapChartCtx = document.getElementById('heapChart'); +var eventLoopChartCtx = document.getElementById('eventLoopChart'); var responseTimeChartCtx = document.getElementById('responseTimeChart'); var rpsChartCtx = document.getElementById('rpsChart'); var statusCodesChartCtx = document.getElementById('statusCodesChart'); var cpuChart = createChart(cpuChartCtx, cpuDataset); var memChart = createChart(memChartCtx, memDataset); +var heapChart = createChart(heapChartCtx, heapDataset); +var eventLoopChart = createChart(eventLoopChartCtx, eventLoopDataset); var loadChart = createChart(loadChartCtx, loadDataset); var responseTimeChart = createChart(responseTimeChartCtx, responseTimeDataset); var rpsChart = createChart(rpsChartCtx, rpsDataset); @@ -107,7 +121,16 @@ statusCodesChart.data.datasets.forEach(function (dataset, index) { dataset.borderColor = statusCodesColors[index]; }); -var charts = [cpuChart, memChart, loadChart, responseTimeChart, rpsChart, statusCodesChart]; +var charts = [ + cpuChart, + memChart, + loadChart, + responseTimeChart, + rpsChart, + statusCodesChart, + heapChart, + eventLoopChart, +]; var onSpanChange = function (e) { e.target.classList.add('active'); @@ -160,6 +183,16 @@ socket.on('esm_start', function (data) { }); loadChart.data.labels = data[defaultSpan].os.map(addTimestamp); + heapChart.data.datasets[0].data = data[defaultSpan].os.map(function (point) { + return point.heap.used_heap_size / 1024 / 1024; + }); + heapChart.data.labels = data[defaultSpan].os.map(addTimestamp); + + eventLoopChart.data.datasets[0].data = data[defaultSpan].os.map(function (point) { + return point.loop.sum; + }); + eventLoopChart.data.labels = data[defaultSpan].os.map(addTimestamp); + var lastResponseMetric = data[defaultSpan].responses[data[defaultSpan].responses.length - 1]; responseTimeStat.textContent = '0.00ms'; @@ -180,7 +213,8 @@ socket.on('esm_start', function (data) { statusCodesChart.data.labels = data[defaultSpan].responses.map(addTimestamp); if (data[defaultSpan].responses.length >= 2) { - var deltaTime = lastResponseMetric.timestamp - + var deltaTime = + lastResponseMetric.timestamp - data[defaultSpan].responses[data[defaultSpan].responses.length - 2].timestamp; if (deltaTime < 1) deltaTime = 1000; @@ -205,7 +239,7 @@ socket.on('esm_start', function (data) { }); 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); spanNode.setAttribute('id', index); @@ -217,8 +251,12 @@ socket.on('esm_start', function (data) { }); socket.on('esm_stats', function (data) { - if (data.retention === spans[defaultSpan].retention && - data.interval === spans[defaultSpan].interval) { + console.log(data); + + if ( + data.retention === spans[defaultSpan].retention && + data.interval === spans[defaultSpan].interval + ) { var os = data.os; var responses = data.responses; @@ -243,6 +281,20 @@ socket.on('esm_stats', function (data) { loadChart.data.labels.push(os.timestamp); } + heapStat.textContent = '0'; + if (os) { + heapStat.textContent = (os.heap.used_heap_size / 1024 / 1024).toFixed(1) + 'MB'; + heapChart.data.datasets[0].data.push(os.heap.used_heap_size / 1024 / 1024); + heapChart.data.labels.push(os.timestamp); + } + + eventLoopStat.textContent = '0'; + if (os) { + eventLoopStat.textContent = os.loop.sum.toFixed(2) + 'ms'; + eventLoopChart.data.datasets[0].data.push(os.loop.sum); + eventLoopChart.data.labels.push(os.timestamp); + } + responseTimeStat.textContent = '0.00ms'; if (responses) { responseTimeStat.textContent = responses.mean.toFixed(2) + 'ms';