diff --git a/examples/index.js b/examples/index.js index 42f9997..1aad381 100644 --- a/examples/index.js +++ b/examples/index.js @@ -3,10 +3,14 @@ const express = require('express'); const app = express(); +const port = process.env.PORT || 3000; app.use(require('../index')({ path: '/' })); app.use(require('express-favicon-short-circuit')); -app.listen(3000, () => { - console.log('listening on http://0.0.0.0:3000'); +// Example route throwing requested status code +app.get('/return-status/:statusCode', (req, res) => res.sendStatus(req.params.statusCode)); + +app.listen(port, () => { + console.log(`Listening on http://0.0.0.0:${port}`); }); diff --git a/examples/package.json b/examples/package.json index 4143826..8863bba 100644 --- a/examples/package.json +++ b/examples/package.json @@ -14,10 +14,12 @@ "license": "MIT", "dependencies": { "express": "^4.14.0", - "express-favicon-short-circuit": "^1.1.0" + "express-favicon-short-circuit": "^1.1.0", + "request": "^2.74.0" }, "scripts": { - "start": "node index.js" + "start": "node index.js", + "benchmark": "node tester.js" }, "repository": { "type": "git", diff --git a/examples/tester.js b/examples/tester.js new file mode 100644 index 0000000..1aa0bb3 --- /dev/null +++ b/examples/tester.js @@ -0,0 +1,12 @@ +const request = require('request'); + +const requestUrl = 'http://localhost:3000/return-status/'; +const interval = 50; + +const makeDummyCall = () => setTimeout(() => { + const code = 200 + Math.random() * 399; + request.get(`${requestUrl}${code}`); + makeDummyCall(); +}, interval); + +makeDummyCall(); diff --git a/package.json b/package.json index f25be63..6a8cb31 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "express-status-monitor", - "version": "0.1.2", + "version": "0.1.3", "description": "Realtime Monitoring for Express-based Node applications", "main": "index.js", "keywords": [ diff --git a/src/public/index.html b/src/public/index.html index 783de47..f9cba41 100644 --- a/src/public/index.html +++ b/src/public/index.html @@ -60,6 +60,18 @@

-

+
+
+
Status Codes
+
2xx
+
3xx
+
4xx
+
5xx
+
+
+ +
+
diff --git a/src/public/javascripts/app.js b/src/public/javascripts/app.js index 7e464f3..5190bdc 100644 --- a/src/public/javascripts/app.js +++ b/src/public/javascripts/app.js @@ -10,6 +10,7 @@ Chart.defaults.global.elements.line.borderWidth = 2; var socket = io(location.protocol + '//' + location.hostname + ':' + location.port); var defaultSpan = 0; var spans = []; +var statusCodesColors = ['#75D701', '#47b8e0', '#ffc952', '#E53A40']; var defaultDataset = { label: '', @@ -48,7 +49,7 @@ var createChart = function (ctx, dataset) { type: 'line', data: { labels: [], - datasets: dataset + datasets: dataset, }, options: defaultOptions }); @@ -63,6 +64,7 @@ var memDataset = [Object.create(defaultDataset)]; var loadDataset = [Object.create(defaultDataset)]; var responseTimeDataset = [Object.create(defaultDataset)]; var rpsDataset = [Object.create(defaultDataset)]; +var statusCodesDataset = [Object.create(defaultDataset)]; var cpuStat = document.getElementById('cpuStat'); var memStat = document.getElementById('memStat'); @@ -75,14 +77,32 @@ 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 cpuChart = createChart(cpuChartCtx, cpuDataset); var memChart = createChart(memChartCtx, memDataset); var loadChart = createChart(loadChartCtx, loadDataset); var responseTimeChart = createChart(responseTimeChartCtx, responseTimeDataset); var rpsChart = createChart(rpsChartCtx, rpsDataset); +var statusCodesChart = new Chart(statusCodesChartCtx, { + type: 'line', + data: { + labels: [], + datasets: [ + Object.create(defaultDataset), + Object.create(defaultDataset), + Object.create(defaultDataset), + Object.create(defaultDataset) + ] + }, + options: defaultOptions +}); + +statusCodesChart.data.datasets.forEach(function(dataset, index) { + dataset.borderColor = statusCodesColors[index]; +}); -var charts = [cpuChart, memChart, loadChart, responseTimeChart, rpsChart]; +var charts = [cpuChart, memChart, loadChart, responseTimeChart, rpsChart, statusCodesChart]; var onSpanChange = function (e) { e.target.classList.add('active'); @@ -146,8 +166,16 @@ socket.on('start', function (data) { }); responseTimeChart.data.labels = data[defaultSpan].responses.map(addTimestamp); + for(var i = 0; i < 4; i++) { + statusCodesChart.data.datasets[i].data = data[defaultSpan].responses.map(function (point) { + return point[i+2]; + }); + } + statusCodesChart.data.labels = data[defaultSpan].responses.map(addTimestamp); + if (data[defaultSpan].responses.length >= 2) { 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); rpsChart.data.datasets[0].data = data[defaultSpan].responses.map(function (point) { return point.count / deltaTime * 1000; @@ -181,6 +209,7 @@ socket.on('start', function (data) { socket.on('stats', function (data) { if (data.retention === spans[defaultSpan].retention && data.interval === spans[defaultSpan].interval) { var os = data.os; + var responses = data.responses; cpuStat.textContent = '0.0%'; if (os) { @@ -203,8 +232,6 @@ socket.on('stats', function (data) { loadChart.data.labels.push(os.timestamp); } - var responses = data.responses; - responseTimeStat.textContent = '0.00ms'; if (responses) { responseTimeStat.textContent = responses.mean.toFixed(2) + 'ms'; @@ -214,17 +241,27 @@ socket.on('stats', function (data) { if (responses) { var deltaTime = responses.timestamp - rpsChart.data.labels[rpsChart.data.labels.length - 1]; + if (deltaTime < 1) 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); } + if (responses) { + for(var i = 0; i < 4; i++) { + statusCodesChart.data.datasets[i].data.push(data.responses[i+2]); + } + statusCodesChart.data.labels.push(data.responses.timestamp); + } + charts.forEach(function (chart) { if (spans[defaultSpan].retention < chart.data.labels.length) { - chart.data.datasets[0].data.shift(); + chart.data.datasets.forEach(function(dataset) { + dataset.data.shift(); + }); + chart.data.labels.shift(); } - chart.update(); }); } diff --git a/src/public/stylesheets/style.css b/src/public/stylesheets/style.css index 8c80bb3..b3e4eef 100644 --- a/src/public/stylesheets/style.css +++ b/src/public/stylesheets/style.css @@ -1,70 +1,103 @@ * { - font-family: Helvetica Neue, Helvetica, Arial, sans-serif; + font-family: Helvetica Neue, Helvetica, Arial, sans-serif; } h1 { - font-size: 3em; - color: #222222; - margin: 0; + font-size: 3em; + color: #222; + margin: 0; } h5 { - margin: 0; - color: #888888; + margin: 0; + color: #888; +} + +h6 { + margin: 0; } p { - font-size: 0.7em; - color: #888888; + font-size: 0.7em; + color: #888; } span { - cursor: pointer; - font-size: 10px; - margin-left: 5px; - border: 1px solid #DDD; - padding: 3px 10px 4px 10px; + cursor: pointer; + font-size: 10px; + margin-left: 5px; + border: 1px solid #DDD; + padding: 3px 10px 4px 10px; } canvas { - width: 400px; - height: 100px; + width: 400px; + height: 100px; } .content { - width: 600px; - margin: auto; + width: 600px; + margin: auto; } .active { - background: #eeeeee; + background: #eeeeee; } .stats-column { - flex: 0 0 200px; + flex: 0 0 200px; } .container { - display: flex; - flex-direction: row; - margin-top: 20px; - height: 100px; + display: flex; + flex-direction: row; + margin-top: 20px; + height: 100px; } .chart-container { - width: 400px; - height: 100px; + width: 400px; + height: 100px; } .footer { - position: fixed; - margin: auto; - text-align: center; - left: 0; - right: 0; - bottom: 0; + position: fixed; + margin: auto; + text-align: center; + left: 0; + right: 0; + bottom: 0; } .span-controls { - float: right; + float: right; +} + +.status-code { + margin-top: 2px; +} + +.status-code:before { + content: ''; + display: inline-block; + width: 8px; + height: 8px; + border-radius: 8px; + margin-right: 10px; +} + +.status-code-2xx:before { + background-color: #75D701; +} + +.status-code-3xx:before { + background-color: #47b8e0; +} + +.status-code-4xx:before { + background-color: #ffc952; +} + +.status-code-5xx:before { + background-color: #E53A40; }