Add support for Response Times
Showing
2 changed files
with
127 additions
and
30 deletions
... | @@ -50,33 +50,40 @@ | ... | @@ -50,33 +50,40 @@ |
50 | <div class="container"> | 50 | <div class="container"> |
51 | <div class="stats-column"> | 51 | <div class="stats-column"> |
52 | <h5>CPU Usage</h5> | 52 | <h5>CPU Usage</h5> |
53 | <h1 id="cpuStat">0.03%</h1> | 53 | <h1 id="cpuStat">- %</h1> |
54 | </div> | 54 | </div> |
55 | <canvas id="cpuChart" width="400" height="100"></canvas> | 55 | <canvas id="cpuChart" width="400" height="100"></canvas> |
56 | </div> | 56 | </div> |
57 | <div class="container"> | 57 | <div class="container"> |
58 | <div class="stats-column"> | 58 | <div class="stats-column"> |
59 | <h5>Memory Usage</h5> | 59 | <h5>Memory Usage</h5> |
60 | <h1 id="memStat">0.03%</h1> | 60 | <h1 id="memStat">- %</h1> |
61 | </div> | 61 | </div> |
62 | <canvas id="memChart" width="400" height="100"></canvas> | 62 | <canvas id="memChart" width="400" height="100"></canvas> |
63 | </div> | 63 | </div> |
64 | <div class="container"> | 64 | <div class="container"> |
65 | <div class="stats-column"> | 65 | <div class="stats-column"> |
66 | <h5>Requests per second</h5> | 66 | <h5>Requests per second</h5> |
67 | <h1 id="rpsStat">1.45</h1> | 67 | <h1 id="rpsStat">-</h1> |
68 | </div> | 68 | </div> |
69 | <canvas id="rpsChart" width="400" height="100"></canvas> | 69 | <canvas id="rpsChart" width="400" height="100"></canvas> |
70 | </div> | 70 | </div> |
71 | <div class="Status Codes"> | 71 | <div class="container"> |
72 | <div class="stats-column"> | ||
73 | <h5>Response Time Mean</h5> | ||
74 | <h1 id="responseTimeStat">- ms</h1> | ||
75 | </div> | ||
76 | <canvas id="responseTimeChart" width="400" height="100"></canvas> | ||
77 | </div> | ||
78 | <div class="container"> | ||
72 | <div class="stats-column"> | 79 | <div class="stats-column"> |
73 | <h5>Response status codes</h5> | 80 | <h5>Response status codes</h5> |
74 | <h1 id="codeStat">OK</h1> | 81 | <h1 id="statusCodeStat">-</h1> |
75 | </div> | 82 | </div> |
76 | <canvas id="codeChart" width="400" height="100"></canvas> | 83 | <canvas id="statusCodeChart" width="400" height="100"></canvas> |
77 | </div> | 84 | </div> |
78 | <div class="footer"> | 85 | <div class="footer"> |
79 | <p>Powered by Socket.io</p> | 86 | <p>Powered by Socket.io & Chart.js</p> |
80 | </div> | 87 | </div> |
81 | </div> | 88 | </div> |
82 | <script> | 89 | <script> |
... | @@ -95,12 +102,35 @@ | ... | @@ -95,12 +102,35 @@ |
95 | lineTension: 0.3, | 102 | lineTension: 0.3, |
96 | pointRadius: 0, | 103 | pointRadius: 0, |
97 | }]; | 104 | }]; |
105 | |||
98 | var memDataset = [{ | 106 | var memDataset = [{ |
99 | label: 'Memory Usage in MB', | 107 | label: 'Memory Usage in MB', |
100 | data: [], | 108 | data: [], |
101 | lineTension: 0.6, | 109 | lineTension: 0.6, |
102 | pointRadius: 0, | 110 | pointRadius: 0, |
103 | }]; | 111 | }]; |
112 | |||
113 | var responseTimeDataset = [{ | ||
114 | label: 'Mean Response time', | ||
115 | data: [], | ||
116 | lineTension: 0.6, | ||
117 | pointRadius: 0, | ||
118 | }]; | ||
119 | |||
120 | var rpsDataset = [{ | ||
121 | label: 'Requests per second', | ||
122 | data: [], | ||
123 | lineTension: 0.6, | ||
124 | pointRadius: 0, | ||
125 | }]; | ||
126 | |||
127 | var statusCodesDataset = [{ | ||
128 | label: 'Memory Usage in MB', | ||
129 | data: [], | ||
130 | lineTension: 0.6, | ||
131 | pointRadius: 0, | ||
132 | }]; | ||
133 | |||
104 | var defaultOptions = { | 134 | var defaultOptions = { |
105 | scales: { | 135 | scales: { |
106 | yAxes: [{ | 136 | yAxes: [{ |
... | @@ -124,8 +154,14 @@ | ... | @@ -124,8 +154,14 @@ |
124 | }; | 154 | }; |
125 | 155 | ||
126 | var cpuStat = document.getElementById('cpuStat'); | 156 | var cpuStat = document.getElementById('cpuStat'); |
157 | var memStat = document.getElementById('memStat'); | ||
158 | var responseTimeStat = document.getElementById('responseTimeStat'); | ||
159 | var rpsStat = document.getElementById('rpsStat'); | ||
127 | var cpuChartCtx = document.getElementById("cpuChart"); | 160 | var cpuChartCtx = document.getElementById("cpuChart"); |
128 | var memChartCtx = document.getElementById("memChart"); | 161 | var memChartCtx = document.getElementById("memChart"); |
162 | var rpsChartCtx = document.getElementById("rpsChart"); | ||
163 | var responseTimeChartCtx = document.getElementById("responseTimeChart"); | ||
164 | var statusCodeChartCtx = document.getElementById("statusCodeChart"); | ||
129 | var cpuChart = new Chart(cpuChartCtx, { | 165 | var cpuChart = new Chart(cpuChartCtx, { |
130 | type: 'line', | 166 | type: 'line', |
131 | data: { | 167 | data: { |
... | @@ -144,17 +180,55 @@ | ... | @@ -144,17 +180,55 @@ |
144 | options: defaultOptions | 180 | options: defaultOptions |
145 | }); | 181 | }); |
146 | 182 | ||
183 | var rpsChart = new Chart(rpsChartCtx, { | ||
184 | type: 'line', | ||
185 | data: { | ||
186 | labels: labels, | ||
187 | datasets: responseTimeDataset, | ||
188 | }, | ||
189 | options: defaultOptions | ||
190 | }); | ||
191 | |||
192 | var responseTimeChart = new Chart(responseTimeChartCtx, { | ||
193 | type: 'line', | ||
194 | data: { | ||
195 | labels: labels, | ||
196 | datasets: responseTimeDataset, | ||
197 | }, | ||
198 | options: defaultOptions | ||
199 | }); | ||
200 | |||
201 | var statusCodeChart = new Chart(statusCodeChartCtx, { | ||
202 | type: 'line', | ||
203 | data: { | ||
204 | labels: labels, | ||
205 | datasets: statusCodesDataset, | ||
206 | }, | ||
207 | options: defaultOptions | ||
208 | }); | ||
209 | |||
147 | socket.on('stats', function (data) { | 210 | socket.on('stats', function (data) { |
148 | console.log(data); | 211 | console.log(data); |
149 | cpuStat.textContent = data.osStats[data.osStats.length - 1].cpu.toFixed(1) + '%'; | 212 | cpuStat.textContent = data.osStats[data.osStats.length - 1].cpu.toFixed(1) + '%'; |
150 | cpuChart.data.datasets[0].data = data.osStats.map((point) => point.cpu); | 213 | cpuChart.data.datasets[0].data = data.osStats.map(function(point) { return point.cpu; }); |
151 | cpuChart.data.labels = data.osStats.map((point) => point.timestamp); | 214 | cpuChart.data.labels = data.osStats.map(function(point) { return point.timestamp; }); |
152 | cpuChart.update(); | 215 | cpuChart.update(); |
153 | 216 | ||
154 | memStat.textContent = data.osStats[data.osStats.length - 1].memory.toFixed(1) + 'MB'; | 217 | memStat.textContent = data.osStats[data.osStats.length - 1].memory.toFixed(1) + 'MB'; |
155 | memChart.data.datasets[0].data = data.osStats.map((point) => point.memory); | 218 | memChart.data.datasets[0].data = data.osStats.map(function(point) { return point.memory; }); |
156 | memChart.data.labels = data.osStats.map((point) => point.timestamp); | 219 | memChart.data.labels = data.osStats.map(function(point) { return point.timestamp; }); |
157 | memChart.update(); | 220 | memChart.update(); |
221 | |||
222 | if (data.responses.length > 0) { | ||
223 | responseTimeStat.textContent = data.responses[data.responses.length - 1].mean.toFixed(1) + 'ms'; | ||
224 | responseTimeChart.data.datasets[0].data = data.responses.map(function (point) { | ||
225 | return point.mean; | ||
226 | }); | ||
227 | responseTimeChart.data.labels = data.responses.map(function (point) { | ||
228 | return point.timestamp; | ||
229 | }); | ||
230 | responseTimeChart.update(); | ||
231 | } | ||
158 | }); | 232 | }); |
159 | </script> | 233 | </script> |
160 | </body> | 234 | </body> | ... | ... |
... | @@ -4,33 +4,35 @@ | ... | @@ -4,33 +4,35 @@ |
4 | const path = require('path'); | 4 | const path = require('path'); |
5 | const onHeaders = require('on-headers'); | 5 | const onHeaders = require('on-headers'); |
6 | const pidusage = require('pidusage'); | 6 | const pidusage = require('pidusage'); |
7 | const responseTimes = []; | ||
8 | const osStats = []; | ||
9 | 7 | ||
10 | const defaultConfig = { | 8 | const defaultConfig = { |
11 | socketPort: 41338, | 9 | socketPort: 41338, |
12 | path: '/status', | 10 | path: '/status', |
11 | spans: [{ | ||
13 | interval: 1, | 12 | interval: 1, |
14 | retention: 100, | 13 | retention: 60 |
14 | }] | ||
15 | }; | 15 | }; |
16 | 16 | ||
17 | const gatherOsMetrics = (io, config) => { | 17 | const gatherOsMetrics = (io, span) => { |
18 | pidusage.stat(process.pid, (err, stat) => { | 18 | pidusage.stat(process.pid, (err, stat) => { |
19 | stat.timestamp = Date.now(); | 19 | stat.timestamp = Date.now(); |
20 | |||
20 | // Convert from B to MB | 21 | // Convert from B to MB |
21 | stat.memory = stat.memory / 1024 / 1024; | 22 | stat.memory = stat.memory / 1024 / 1024; |
22 | 23 | ||
23 | osStats.push(stat); | 24 | span.osStats.push(stat); |
24 | if (osStats.length >= config.retention) osStats.shift(); | 25 | if (span.osStats.length >= span.retention) span.osStats.shift(); |
26 | if (span.responses[0] && span.responses[0].timestamp + (span.interval * span.retention * 1000) < Date.now()) span.responses.shift(); | ||
25 | 27 | ||
26 | sendMetrics(io); | 28 | sendMetrics(io, span); |
27 | }); | 29 | }); |
28 | }; | 30 | }; |
29 | 31 | ||
30 | const sendMetrics = (io) => { | 32 | const sendMetrics = (io, span) => { |
31 | io.emit('stats', { | 33 | io.emit('stats', { |
32 | osStats, | 34 | osStats: span.osStats, |
33 | responseTimes | 35 | responses: span.responses, |
34 | }); | 36 | }); |
35 | }; | 37 | }; |
36 | 38 | ||
... | @@ -47,13 +49,21 @@ | ... | @@ -47,13 +49,21 @@ |
47 | config.socketPort = defaultConfig.socketPort; | 49 | config.socketPort = defaultConfig.socketPort; |
48 | } | 50 | } |
49 | 51 | ||
50 | if (config.interval === undefined || !config instanceof Number) { | 52 | if (config.spans === undefined || !config instanceof Array) { |
51 | config.interval = defaultConfig.interval; | 53 | config.spans = defaultConfig.span; |
52 | } | 54 | } |
53 | 55 | ||
54 | const io = require('socket.io')(config.socketPort); | 56 | const io = require('socket.io')(config.socketPort); |
55 | 57 | ||
56 | setInterval(() => gatherOsMetrics(io, config), config.interval * 1000); | 58 | io.on('connection', (socket) => { |
59 | console.log('User connected! ' + socket); | ||
60 | }); | ||
61 | |||
62 | config.spans.forEach((span) => { | ||
63 | span.osStats = []; | ||
64 | span.responses = []; | ||
65 | setInterval(() => gatherOsMetrics(io, span), span.interval * 1000); | ||
66 | }); | ||
57 | 67 | ||
58 | return (req, res, next) => { | 68 | return (req, res, next) => { |
59 | const startTime = process.hrtime(); | 69 | const startTime = process.hrtime(); |
... | @@ -61,14 +71,27 @@ | ... | @@ -61,14 +71,27 @@ |
61 | res.sendFile(path.join(__dirname + '/index.html')); | 71 | res.sendFile(path.join(__dirname + '/index.html')); |
62 | } else { | 72 | } else { |
63 | onHeaders(res, () => { | 73 | onHeaders(res, () => { |
64 | var diff = process.hrtime(startTime); | 74 | const diff = process.hrtime(startTime); |
65 | var responseTime = diff[0] * 1e3 + diff[1] * 1e-6; | 75 | const responseTime = diff[0] * 1e3 + diff[1] * 1e-6; |
66 | 76 | const category = Math.floor(res.statusCode / 100); | |
67 | responseTimes.push({ | 77 | |
68 | endpoint: req.path, | 78 | config.spans.forEach((span) => { |
69 | responseTime, | 79 | if (span.responses[span.responses.length - 1] !== undefined && |
80 | span.responses[span.responses.length - 1].timestamp / 1000 + span.interval > Date.now() / 1000) { | ||
81 | span.responses[span.responses.length - 1][category]++; | ||
82 | span.responses[span.responses.length - 1].count++; | ||
83 | } else { | ||
84 | span.responses.push({ | ||
85 | '2': category === 2 ? 1 : 0, | ||
86 | '3': category === 3 ? 1 : 0, | ||
87 | '4': category === 4 ? 1 : 0, | ||
88 | '5': category === 5 ? 1 : 0, | ||
89 | count: 1, | ||
90 | mean: responseTime, | ||
70 | timestamp: Date.now() | 91 | timestamp: Date.now() |
71 | }); | 92 | }); |
93 | } | ||
94 | }); | ||
72 | }); | 95 | }); |
73 | 96 | ||
74 | next(); | 97 | next(); | ... | ... |
-
Please register or sign in to post a comment