node-runtime-monitor 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +60 -0
- package/index.js +58 -0
- package/package.json +29 -0
- package/src/collectors/cpu.js +7 -0
- package/src/collectors/event-loop.js +17 -0
- package/src/collectors/gc.js +45 -0
- package/src/collectors/memory.js +7 -0
- package/src/collectors/process.js +11 -0
- package/src/middlewares/metricsRoute.js +7 -0
- package/src/public/index.html +635 -0
- package/src/scheduler/startCollector.js +23 -0
- package/src/store/metricStore.js +10 -0
package/README.md
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# node-runtime-monitor
|
|
2
|
+
|
|
3
|
+
Lightweight Node.js runtime monitoring for Express apps.
|
|
4
|
+
|
|
5
|
+
It collects runtime stats (CPU, memory, process info, GC, and event loop behavior), exposes them as JSON, and provides a simple browser dashboard.
|
|
6
|
+
|
|
7
|
+
Zero external runtime dependencies in Node.js. The dashboard UI uses only Chart.js loaded from a CDN in `src/public/index.html`.
|
|
8
|
+
|
|
9
|
+
## Install
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npm install node-runtime-monitor
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Quick start
|
|
16
|
+
|
|
17
|
+
```js
|
|
18
|
+
const express = require('express');
|
|
19
|
+
const {
|
|
20
|
+
monitor,
|
|
21
|
+
metricsHandler,
|
|
22
|
+
dashboardHandler,
|
|
23
|
+
} = require('node-runtime-monitor');
|
|
24
|
+
|
|
25
|
+
const app = express();
|
|
26
|
+
|
|
27
|
+
// Start background metric collection
|
|
28
|
+
app.use(monitor({ interval: 5000 }));
|
|
29
|
+
|
|
30
|
+
// Raw metrics JSON
|
|
31
|
+
app.get('/status', metricsHandler);
|
|
32
|
+
|
|
33
|
+
// Dashboard UI
|
|
34
|
+
app.get('/status-dashboard', dashboardHandler({ metricsPath: '/status' }));
|
|
35
|
+
|
|
36
|
+
app.listen(3000, () => {
|
|
37
|
+
console.log('Server running on http://localhost:3000');
|
|
38
|
+
});
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## What this package exports
|
|
42
|
+
|
|
43
|
+
The package exports 3 functions:
|
|
44
|
+
|
|
45
|
+
- `monitor(options?)`: Starts metric collection (once) and returns an Express middleware.
|
|
46
|
+
- `options.interval` (number, optional): collection interval in milliseconds.
|
|
47
|
+
- Default: `5000`.
|
|
48
|
+
- `metricsHandler(req, res)`: Express route handler that returns all collected metrics as JSON.
|
|
49
|
+
- `dashboardHandler(options?)`: Returns an Express route handler that serves the built-in dashboard HTML.
|
|
50
|
+
- `options.metricsPath` (string, optional): endpoint the dashboard should call for metrics.
|
|
51
|
+
- Default: `/status`.
|
|
52
|
+
|
|
53
|
+
## Suggested routes
|
|
54
|
+
|
|
55
|
+
- JSON metrics: `/status`
|
|
56
|
+
- Dashboard: `/status-dashboard`
|
|
57
|
+
|
|
58
|
+
## License
|
|
59
|
+
|
|
60
|
+
MIT
|
package/index.js
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
const path = require('path');
|
|
2
|
+
const fs = require('fs');
|
|
3
|
+
|
|
4
|
+
const startCollector = require('./src/scheduler/startCollector');
|
|
5
|
+
const metricsRoute = require('./src/middlewares/metricsRoute');
|
|
6
|
+
const metricsStore = require('./src/store/metricStore');
|
|
7
|
+
|
|
8
|
+
let initialized = false;
|
|
9
|
+
|
|
10
|
+
function monitor(options = {}) {
|
|
11
|
+
|
|
12
|
+
if (!initialized) {
|
|
13
|
+
initialized = true;
|
|
14
|
+
|
|
15
|
+
const interval = options.interval || 5000;
|
|
16
|
+
startCollector(interval);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
return function (req, res, next) {
|
|
20
|
+
next();
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function metricsHandler(req, res) {
|
|
25
|
+
res.setHeader('Content-Type', 'application/json');
|
|
26
|
+
res.end(JSON.stringify(metricsStore));
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function dashboardHandler(options = {}) {
|
|
30
|
+
const metricsPath = options.metricsPath || '/status';
|
|
31
|
+
|
|
32
|
+
return function (req, res) {
|
|
33
|
+
const filePath = path.join(__dirname, 'src', 'public', 'index.html');
|
|
34
|
+
fs.readFile(filePath, 'utf-8', (err, html) => {
|
|
35
|
+
|
|
36
|
+
if (err) {
|
|
37
|
+
res.statusCode = 500;
|
|
38
|
+
return res.end('Failed to load dashboard');
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Inject metrics route
|
|
42
|
+
html = html.replace(
|
|
43
|
+
'__METRICS_PATH__',
|
|
44
|
+
metricsPath
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
res.setHeader('Content-Type', 'text/html');
|
|
48
|
+
|
|
49
|
+
res.end(html);
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
module.exports = {
|
|
55
|
+
monitor,
|
|
56
|
+
metricsHandler,
|
|
57
|
+
dashboardHandler
|
|
58
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "node-runtime-monitor",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Lightweight Node.js runtime monitoring and observability middleware using perf_hooks and native APIs.",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"type": "commonjs",
|
|
7
|
+
"keywords": [
|
|
8
|
+
"nodejs",
|
|
9
|
+
"monitoring",
|
|
10
|
+
"observability",
|
|
11
|
+
"metrics",
|
|
12
|
+
"performance",
|
|
13
|
+
"event-loop",
|
|
14
|
+
"gc",
|
|
15
|
+
"express",
|
|
16
|
+
"runtime-monitor",
|
|
17
|
+
"perf-hooks"
|
|
18
|
+
],
|
|
19
|
+
"homepage": "https://github.com/jhamayank02/node-runtime-monitor",
|
|
20
|
+
"repository": {
|
|
21
|
+
"type": "git",
|
|
22
|
+
"url": "git+https://github.com/jhamayank02/node-runtime-monitor.git"
|
|
23
|
+
},
|
|
24
|
+
"bugs": {
|
|
25
|
+
"url": "https://github.com/jhamayank02/node-runtime-monitor/issues"
|
|
26
|
+
},
|
|
27
|
+
"license": "MIT",
|
|
28
|
+
"author": "jhamayank02"
|
|
29
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
const { PerformanceObserver, performance, monitorEventLoopDelay } = require("perf_hooks");
|
|
2
|
+
|
|
3
|
+
const histogram = monitorEventLoopDelay({
|
|
4
|
+
resolution: 20
|
|
5
|
+
});
|
|
6
|
+
|
|
7
|
+
histogram.enable();
|
|
8
|
+
|
|
9
|
+
function getEventLoopMetrics() {
|
|
10
|
+
return {
|
|
11
|
+
mean: histogram.mean / 1e6,
|
|
12
|
+
max: histogram.max / 1e6,
|
|
13
|
+
p95: histogram.percentile(95) / 1e6
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
module.exports = getEventLoopMetrics;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
const { PerformanceObserver, constants } = require('perf_hooks');
|
|
2
|
+
const metricsStore = require('../store/metricStore');
|
|
3
|
+
|
|
4
|
+
const gcTypes = {
|
|
5
|
+
[constants.NODE_PERFORMANCE_GC_MAJOR]: 'major',
|
|
6
|
+
[constants.NODE_PERFORMANCE_GC_MINOR]: 'minor',
|
|
7
|
+
[constants.NODE_PERFORMANCE_GC_INCREMENTAL]: 'incremental',
|
|
8
|
+
[constants.NODE_PERFORMANCE_GC_WEAKCB]: 'weakcb'
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
let observer;
|
|
12
|
+
|
|
13
|
+
function startGcObserver() {
|
|
14
|
+
if (observer) {
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
observer = new PerformanceObserver((list) => {
|
|
19
|
+
const entries = list.getEntries();
|
|
20
|
+
|
|
21
|
+
entries.forEach((entry) => {
|
|
22
|
+
if(!metricsStore.gc){
|
|
23
|
+
metricsStore.gc = [];
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
metricsStore.gc.push({
|
|
27
|
+
duration: entry.duration,
|
|
28
|
+
kind: entry.kind,
|
|
29
|
+
type: gcTypes[entry.kind],
|
|
30
|
+
timestamp: Date.now()
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
// Keep only last 100 events
|
|
34
|
+
if (metricsStore.gc.length > 100) {
|
|
35
|
+
metricsStore.gc.shift();
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
observer.observe({
|
|
41
|
+
entryTypes: ['gc']
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
module.exports = startGcObserver;
|
|
@@ -0,0 +1,635 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
|
|
4
|
+
<head>
|
|
5
|
+
<meta charset="UTF-8" />
|
|
6
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
7
|
+
<title>Node Runtime Monitor</title>
|
|
8
|
+
<!-- Dashboard UI uses only Chart.js from CDN. No other external UI dependencies. -->
|
|
9
|
+
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/4.4.1/chart.umd.js"></script>
|
|
10
|
+
<style>
|
|
11
|
+
*,
|
|
12
|
+
*::before,
|
|
13
|
+
*::after {
|
|
14
|
+
box-sizing: border-box;
|
|
15
|
+
margin: 0;
|
|
16
|
+
padding: 0;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
body {
|
|
20
|
+
background: #0f1117;
|
|
21
|
+
color: #c9d1d9;
|
|
22
|
+
font-family: ui-monospace, 'SFMono-Regular', 'Cascadia Code', monospace;
|
|
23
|
+
font-size: 13px;
|
|
24
|
+
padding: 20px 24px;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
header {
|
|
28
|
+
display: flex;
|
|
29
|
+
align-items: center;
|
|
30
|
+
justify-content: space-between;
|
|
31
|
+
margin-bottom: 20px;
|
|
32
|
+
padding-bottom: 12px;
|
|
33
|
+
border-bottom: 1px solid #21262d;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
.header-left {
|
|
37
|
+
display: flex;
|
|
38
|
+
align-items: center;
|
|
39
|
+
gap: 12px;
|
|
40
|
+
color: #8b949e;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
.status-dot {
|
|
44
|
+
width: 7px;
|
|
45
|
+
height: 7px;
|
|
46
|
+
border-radius: 50%;
|
|
47
|
+
background: #3fb950;
|
|
48
|
+
flex-shrink: 0;
|
|
49
|
+
animation: blink 2s ease-in-out infinite;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
@keyframes blink {
|
|
53
|
+
|
|
54
|
+
0%,
|
|
55
|
+
100% {
|
|
56
|
+
opacity: 1;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
50% {
|
|
60
|
+
opacity: 0.35;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
.title {
|
|
65
|
+
color: #e6edf3;
|
|
66
|
+
font-weight: 600;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
.sep {
|
|
70
|
+
color: #30363d;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
.header-right {
|
|
74
|
+
color: #8b949e;
|
|
75
|
+
font-size: 12px;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
.error-bar {
|
|
79
|
+
display: none;
|
|
80
|
+
background: #161b22;
|
|
81
|
+
border: 1px solid #f85149;
|
|
82
|
+
border-radius: 4px;
|
|
83
|
+
padding: 6px 12px;
|
|
84
|
+
color: #f85149;
|
|
85
|
+
font-size: 12px;
|
|
86
|
+
margin-bottom: 16px;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
.error-bar.show {
|
|
90
|
+
display: block;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
.stats {
|
|
94
|
+
display: grid;
|
|
95
|
+
grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
|
|
96
|
+
gap: 8px;
|
|
97
|
+
margin-bottom: 16px;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
.stat {
|
|
101
|
+
background: #161b22;
|
|
102
|
+
border: 1px solid #21262d;
|
|
103
|
+
border-radius: 6px;
|
|
104
|
+
padding: 10px 12px;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
.stat .lbl {
|
|
108
|
+
font-size: 11px;
|
|
109
|
+
color: #8b949e;
|
|
110
|
+
margin-bottom: 5px;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
.stat .val {
|
|
114
|
+
font-size: 18px;
|
|
115
|
+
font-weight: 600;
|
|
116
|
+
color: #e6edf3;
|
|
117
|
+
line-height: 1;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
.stat .unit {
|
|
121
|
+
font-size: 11px;
|
|
122
|
+
color: #8b949e;
|
|
123
|
+
margin-left: 2px;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
.stat .sub {
|
|
127
|
+
font-size: 11px;
|
|
128
|
+
color: #8b949e;
|
|
129
|
+
margin-top: 4px;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
.charts {
|
|
133
|
+
display: grid;
|
|
134
|
+
grid-template-columns: 1fr 1fr;
|
|
135
|
+
gap: 8px;
|
|
136
|
+
margin-bottom: 16px;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
@media (max-width: 700px) {
|
|
140
|
+
.charts {
|
|
141
|
+
grid-template-columns: 1fr;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
.chart-box {
|
|
146
|
+
background: #161b22;
|
|
147
|
+
border: 1px solid #21262d;
|
|
148
|
+
border-radius: 6px;
|
|
149
|
+
padding: 12px;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
.box-title {
|
|
153
|
+
font-size: 11px;
|
|
154
|
+
color: #8b949e;
|
|
155
|
+
margin-bottom: 10px;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
.bottom {
|
|
159
|
+
display: grid;
|
|
160
|
+
grid-template-columns: 200px 1fr;
|
|
161
|
+
gap: 8px;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
@media (max-width: 700px) {
|
|
165
|
+
.bottom {
|
|
166
|
+
grid-template-columns: 1fr;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
.box {
|
|
171
|
+
background: #161b22;
|
|
172
|
+
border: 1px solid #21262d;
|
|
173
|
+
border-radius: 6px;
|
|
174
|
+
padding: 12px;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
.el-rows {
|
|
178
|
+
margin-top: 8px;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
.el-row {
|
|
182
|
+
display: flex;
|
|
183
|
+
justify-content: space-between;
|
|
184
|
+
align-items: center;
|
|
185
|
+
padding: 5px 0;
|
|
186
|
+
border-bottom: 1px solid #21262d;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
.el-row:last-child {
|
|
190
|
+
border-bottom: none;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
.el-row .k {
|
|
194
|
+
color: #8b949e;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
.el-row .v {
|
|
198
|
+
color: #e6edf3;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
table {
|
|
202
|
+
width: 100%;
|
|
203
|
+
border-collapse: collapse;
|
|
204
|
+
margin-top: 8px;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
th {
|
|
208
|
+
font-size: 11px;
|
|
209
|
+
color: #8b949e;
|
|
210
|
+
text-align: left;
|
|
211
|
+
padding: 0 0 6px;
|
|
212
|
+
border-bottom: 1px solid #21262d;
|
|
213
|
+
font-weight: 400;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
td {
|
|
217
|
+
padding: 5px 0;
|
|
218
|
+
border-bottom: 1px solid #161b22;
|
|
219
|
+
color: #8b949e;
|
|
220
|
+
font-size: 12px;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
td:first-child {
|
|
224
|
+
color: #e6edf3;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
tr:last-child td {
|
|
228
|
+
border-bottom: none;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
.badge {
|
|
232
|
+
font-size: 10px;
|
|
233
|
+
padding: 1px 6px;
|
|
234
|
+
border-radius: 3px;
|
|
235
|
+
font-weight: 600;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
.minor {
|
|
239
|
+
background: rgba(63, 185, 80, .15);
|
|
240
|
+
color: #3fb950;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
.major {
|
|
244
|
+
background: rgba(248, 130, 74, .15);
|
|
245
|
+
color: #f8824a;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
.incr {
|
|
249
|
+
background: rgba(88, 166, 255, .15);
|
|
250
|
+
color: #58a6ff;
|
|
251
|
+
}
|
|
252
|
+
</style>
|
|
253
|
+
</head>
|
|
254
|
+
|
|
255
|
+
<body>
|
|
256
|
+
|
|
257
|
+
<header>
|
|
258
|
+
<div class="header-left">
|
|
259
|
+
<div class="status-dot"></div>
|
|
260
|
+
<span class="title">node-runtime-monitor</span>
|
|
261
|
+
<span class="sep">|</span>
|
|
262
|
+
<span id="pidVal">pid —</span>
|
|
263
|
+
<span class="sep">|</span>
|
|
264
|
+
<span id="platformVal">—</span>
|
|
265
|
+
<span class="sep">|</span>
|
|
266
|
+
<span>up <span id="uptimeVal">—</span></span>
|
|
267
|
+
</div>
|
|
268
|
+
<div class="header-right">updated <span id="lastUpdate">—</span> · polling every 5s</div>
|
|
269
|
+
</header>
|
|
270
|
+
|
|
271
|
+
<div class="error-bar" id="errorBar">⚠ Server unreachable — waiting for metrics</div>
|
|
272
|
+
|
|
273
|
+
<div class="stats">
|
|
274
|
+
<div class="stat">
|
|
275
|
+
<div class="lbl">cpu user</div>
|
|
276
|
+
<div class="val"><span id="cpuUser">—</span><span class="unit">µs</span></div>
|
|
277
|
+
</div>
|
|
278
|
+
<div class="stat">
|
|
279
|
+
<div class="lbl">cpu system</div>
|
|
280
|
+
<div class="val"><span id="cpuSys">—</span><span class="unit">µs</span></div>
|
|
281
|
+
</div>
|
|
282
|
+
<div class="stat">
|
|
283
|
+
<div class="lbl">rss</div>
|
|
284
|
+
<div class="val"><span id="memRss">—</span><span class="unit">mb</span></div>
|
|
285
|
+
</div>
|
|
286
|
+
<div class="stat">
|
|
287
|
+
<div class="lbl">heap used</div>
|
|
288
|
+
<div class="val"><span id="heapUsed">—</span><span class="unit">mb</span></div>
|
|
289
|
+
<div class="sub">of <span id="heapTotal">—</span> mb total</div>
|
|
290
|
+
</div>
|
|
291
|
+
<div class="stat">
|
|
292
|
+
<div class="lbl">external</div>
|
|
293
|
+
<div class="val"><span id="memExt">—</span><span class="unit">mb</span></div>
|
|
294
|
+
</div>
|
|
295
|
+
</div>
|
|
296
|
+
|
|
297
|
+
<div class="charts">
|
|
298
|
+
<div class="chart-box">
|
|
299
|
+
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:10px;">
|
|
300
|
+
<div class="box-title" style="margin-bottom:0;">cpu (µs)</div>
|
|
301
|
+
<div style="display:flex;gap:12px;">
|
|
302
|
+
<span style="display:flex;align-items:center;gap:5px;font-size:11px;color:#8b949e;"><span
|
|
303
|
+
style="width:10px;height:2px;background:#3fb950;display:inline-block;border-radius:1px;"></span>user</span>
|
|
304
|
+
<span style="display:flex;align-items:center;gap:5px;font-size:11px;color:#8b949e;"><span
|
|
305
|
+
style="width:10px;height:2px;background:#58a6ff;display:inline-block;border-radius:1px;"></span>system</span>
|
|
306
|
+
</div>
|
|
307
|
+
</div>
|
|
308
|
+
<div style="position:relative;height:130px;">
|
|
309
|
+
<canvas id="cpuChart" role="img" aria-label="CPU usage over time"></canvas>
|
|
310
|
+
</div>
|
|
311
|
+
</div>
|
|
312
|
+
<div class="chart-box">
|
|
313
|
+
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:10px;">
|
|
314
|
+
<div class="box-title" style="margin-bottom:0;">memory (mb)</div>
|
|
315
|
+
<div style="display:flex;gap:12px;">
|
|
316
|
+
<span style="display:flex;align-items:center;gap:5px;font-size:11px;color:#8b949e;"><span
|
|
317
|
+
style="width:10px;height:2px;background:#f8824a;display:inline-block;border-radius:1px;"></span>rss</span>
|
|
318
|
+
<span style="display:flex;align-items:center;gap:5px;font-size:11px;color:#8b949e;"><span
|
|
319
|
+
style="width:10px;height:2px;background:#d2a8ff;display:inline-block;border-radius:1px;"></span>heap</span>
|
|
320
|
+
<span style="display:flex;align-items:center;gap:5px;font-size:11px;color:#8b949e;"><span
|
|
321
|
+
style="width:10px;height:2px;background:#ffa657;display:inline-block;border-radius:1px;"></span>ext</span>
|
|
322
|
+
</div>
|
|
323
|
+
</div>
|
|
324
|
+
<div style="position:relative;height:130px;">
|
|
325
|
+
<canvas id="memChart" role="img" aria-label="Memory usage over time"></canvas>
|
|
326
|
+
</div>
|
|
327
|
+
</div>
|
|
328
|
+
</div>
|
|
329
|
+
|
|
330
|
+
<div class="bottom">
|
|
331
|
+
<div class="box">
|
|
332
|
+
<div class="box-title">event loop lag</div>
|
|
333
|
+
<div class="el-rows">
|
|
334
|
+
<div class="el-row"><span class="k">mean</span><span class="v" id="elMean">—</span></div>
|
|
335
|
+
<div class="el-row"><span class="k">p95</span> <span class="v" id="elP95">—</span></div>
|
|
336
|
+
<div class="el-row"><span class="k">max</span> <span class="v" id="elMax">—</span></div>
|
|
337
|
+
</div>
|
|
338
|
+
</div>
|
|
339
|
+
<div class="box">
|
|
340
|
+
<div class="box-title">gc events</div>
|
|
341
|
+
<table style="width:100%;border-collapse:collapse;">
|
|
342
|
+
<thead>
|
|
343
|
+
<tr>
|
|
344
|
+
<th>type</th>
|
|
345
|
+
<th>duration</th>
|
|
346
|
+
<th>kind</th>
|
|
347
|
+
<th>time</th>
|
|
348
|
+
</tr>
|
|
349
|
+
</thead>
|
|
350
|
+
</table>
|
|
351
|
+
<div style="height:108px;overflow-y:auto;">
|
|
352
|
+
<table style="width:100%;border-collapse:collapse;">
|
|
353
|
+
<tbody id="gcBody">
|
|
354
|
+
<tr>
|
|
355
|
+
<td colspan="4" style="color:#8b949e">—</td>
|
|
356
|
+
</tr>
|
|
357
|
+
</tbody>
|
|
358
|
+
</table>
|
|
359
|
+
</div>
|
|
360
|
+
</div>
|
|
361
|
+
</div>
|
|
362
|
+
|
|
363
|
+
<!-- ── Glossary ───────────────────────────────────────────────────────── -->
|
|
364
|
+
<div style="margin-top:16px;background:#161b22;border:1px solid #21262d;border-radius:6px;overflow:hidden;">
|
|
365
|
+
<div style="padding:10px 14px;border-bottom:1px solid #21262d;font-size:11px;color:#8b949e;letter-spacing:.06em;">METRIC REFERENCE</div>
|
|
366
|
+
|
|
367
|
+
<div style="display:flex;align-items:baseline;gap:10px;padding:9px 14px;border-bottom:1px solid #21262d;">
|
|
368
|
+
<div style="width:180px;min-width:180px;display:flex;align-items:baseline;gap:6px;flex-wrap:wrap;">
|
|
369
|
+
<span style="color:#e6edf3;font-weight:600;font-size:12px;">cpu user</span>
|
|
370
|
+
<span style="font-size:10px;color:#6e7681;">µs</span>
|
|
371
|
+
</div>
|
|
372
|
+
<div style="font-size:12px;color:#8b949e;line-height:1.55;">Time the CPU spent running <em>your app's own code</em> — JS, business logic, JSON parsing, loops. High value = your code is doing heavy computation.</div>
|
|
373
|
+
</div>
|
|
374
|
+
|
|
375
|
+
<div style="display:flex;align-items:baseline;gap:10px;padding:9px 14px;border-bottom:1px solid #21262d;">
|
|
376
|
+
<div style="width:180px;min-width:180px;display:flex;align-items:baseline;gap:6px;flex-wrap:wrap;">
|
|
377
|
+
<span style="color:#e6edf3;font-weight:600;font-size:12px;">cpu system</span>
|
|
378
|
+
<span style="font-size:10px;color:#6e7681;">µs</span>
|
|
379
|
+
</div>
|
|
380
|
+
<div style="font-size:12px;color:#8b949e;line-height:1.55;">Time the OS kernel spent doing work <em>on behalf of your process</em> — file reads, network accepts, memory allocation. High value = heavy I/O or frequent syscalls.</div>
|
|
381
|
+
</div>
|
|
382
|
+
|
|
383
|
+
<div style="display:flex;align-items:baseline;gap:10px;padding:9px 14px;border-bottom:1px solid #21262d;">
|
|
384
|
+
<div style="width:180px;min-width:180px;display:flex;align-items:baseline;gap:6px;flex-wrap:wrap;">
|
|
385
|
+
<span style="color:#e6edf3;font-weight:600;font-size:12px;">rss</span>
|
|
386
|
+
<span style="font-size:10px;color:#6e7681;">mb · resident set size</span>
|
|
387
|
+
</div>
|
|
388
|
+
<div style="font-size:12px;color:#8b949e;line-height:1.55;">Total physical RAM the process occupies — JS heap + V8 engine + native addons + stack. What your OS task manager shows next to the node process.</div>
|
|
389
|
+
</div>
|
|
390
|
+
|
|
391
|
+
<div style="display:flex;align-items:baseline;gap:10px;padding:9px 14px;border-bottom:1px solid #21262d;">
|
|
392
|
+
<div style="width:180px;min-width:180px;display:flex;align-items:baseline;gap:6px;flex-wrap:wrap;">
|
|
393
|
+
<span style="color:#e6edf3;font-weight:600;font-size:12px;">heap used</span>
|
|
394
|
+
<span style="font-size:10px;color:#6e7681;">mb</span>
|
|
395
|
+
</div>
|
|
396
|
+
<div style="font-size:12px;color:#8b949e;line-height:1.55;">JS heap space actually filled with live objects — variables, closures, arrays, cached data. Drops when GC runs. If it only ever climbs and never comes back down, you have a memory leak.</div>
|
|
397
|
+
</div>
|
|
398
|
+
|
|
399
|
+
<div style="display:flex;align-items:baseline;gap:10px;padding:9px 14px;border-bottom:1px solid #21262d;">
|
|
400
|
+
<div style="width:180px;min-width:180px;display:flex;align-items:baseline;gap:6px;flex-wrap:wrap;">
|
|
401
|
+
<span style="color:#e6edf3;font-weight:600;font-size:12px;">heap total</span>
|
|
402
|
+
<span style="font-size:10px;color:#6e7681;">mb</span>
|
|
403
|
+
</div>
|
|
404
|
+
<div style="font-size:12px;color:#8b949e;line-height:1.55;">Total JS heap V8 has <em>reserved</em> from the OS — the container, not just the filled part. V8 grows this proactively to avoid asking the OS on every allocation. Heap used always sits inside heap total.</div>
|
|
405
|
+
</div>
|
|
406
|
+
|
|
407
|
+
<div style="display:flex;align-items:baseline;gap:10px;padding:9px 14px;border-bottom:1px solid #21262d;">
|
|
408
|
+
<div style="width:180px;min-width:180px;display:flex;align-items:baseline;gap:6px;flex-wrap:wrap;">
|
|
409
|
+
<span style="color:#e6edf3;font-weight:600;font-size:12px;">external</span>
|
|
410
|
+
<span style="font-size:10px;color:#6e7681;">mb · incl. arrayBuffers</span>
|
|
411
|
+
</div>
|
|
412
|
+
<div style="font-size:12px;color:#8b949e;line-height:1.55;">C++ memory controlled by JS but living <em>outside</em> the JS heap — mainly <code style="color:#c9d1d9;">Buffer</code>, <code style="color:#c9d1d9;">TypedArray</code>, TLS/zlib internals. Freed when the JS reference is GC'd.</div>
|
|
413
|
+
</div>
|
|
414
|
+
|
|
415
|
+
<div style="display:flex;align-items:baseline;gap:10px;padding:9px 14px;border-bottom:1px solid #21262d;">
|
|
416
|
+
<div style="width:180px;min-width:180px;display:flex;align-items:baseline;gap:6px;flex-wrap:wrap;">
|
|
417
|
+
<span style="color:#e6edf3;font-weight:600;font-size:12px;">event loop lag</span>
|
|
418
|
+
<span style="font-size:10px;color:#6e7681;">ms · mean / p95 / max</span>
|
|
419
|
+
</div>
|
|
420
|
+
<div style="font-size:12px;color:#8b949e;line-height:1.55;">How long the event loop is blocked between ticks. <strong style="color:#c9d1d9;">Mean</strong> = average. <strong style="color:#c9d1d9;">p95</strong> = 95% of ticks were faster than this. <strong style="color:#c9d1d9;">Max</strong> = worst single block. Above 10 ms means responses will feel slow.</div>
|
|
421
|
+
</div>
|
|
422
|
+
|
|
423
|
+
<div style="display:flex;align-items:baseline;gap:10px;padding:9px 14px;border-bottom:1px solid #21262d;">
|
|
424
|
+
<div style="width:180px;min-width:180px;display:flex;align-items:center;gap:6px;">
|
|
425
|
+
<span style="color:#e6edf3;font-weight:600;font-size:12px;">gc</span>
|
|
426
|
+
<span style="font-size:10px;background:rgba(63,185,80,.15);color:#3fb950;padding:1px 5px;border-radius:3px;font-weight:600;">minor</span>
|
|
427
|
+
</div>
|
|
428
|
+
<div style="font-size:12px;color:#8b949e;line-height:1.55;">Scavenge GC — fast collection of short-lived objects in the young generation of the heap. Happens frequently, very cheap (<5 ms). Completely normal.</div>
|
|
429
|
+
</div>
|
|
430
|
+
|
|
431
|
+
<div style="display:flex;align-items:baseline;gap:10px;padding:9px 14px;border-bottom:1px solid #21262d;">
|
|
432
|
+
<div style="width:180px;min-width:180px;display:flex;align-items:center;gap:6px;">
|
|
433
|
+
<span style="color:#e6edf3;font-weight:600;font-size:12px;">gc</span>
|
|
434
|
+
<span style="font-size:10px;background:rgba(248,130,74,.15);color:#f8824a;padding:1px 5px;border-radius:3px;font-weight:600;">major</span>
|
|
435
|
+
</div>
|
|
436
|
+
<div style="font-size:12px;color:#8b949e;line-height:1.55;">Mark-sweep GC — full collection across the whole heap including long-lived objects. Expensive (10–100 ms), pauses the event loop. Frequent major GCs signal memory pressure.</div>
|
|
437
|
+
</div>
|
|
438
|
+
|
|
439
|
+
<div style="display:flex;align-items:baseline;gap:10px;padding:9px 14px;">
|
|
440
|
+
<div style="width:180px;min-width:180px;display:flex;align-items:center;gap:6px;">
|
|
441
|
+
<span style="color:#e6edf3;font-weight:600;font-size:12px;">gc</span>
|
|
442
|
+
<span style="font-size:10px;background:rgba(88,166,255,.15);color:#58a6ff;padding:1px 5px;border-radius:3px;font-weight:600;">incremental</span>
|
|
443
|
+
</div>
|
|
444
|
+
<div style="font-size:12px;color:#8b949e;line-height:1.55;">Mark-sweep split into small steps interleaved with JS execution instead of one long pause. V8's way of spreading the cost of a major GC across multiple ticks to reduce latency spikes.</div>
|
|
445
|
+
</div>
|
|
446
|
+
|
|
447
|
+
</div>
|
|
448
|
+
|
|
449
|
+
<script>
|
|
450
|
+
const METRICS_URL = "__METRICS_PATH__";
|
|
451
|
+
const MAX_POINTS = 20;
|
|
452
|
+
const POLL_INTERVAL = 5000;
|
|
453
|
+
|
|
454
|
+
// Rolling data arrays shared by both charts
|
|
455
|
+
const labels = [];
|
|
456
|
+
const cpuUser = [], cpuSystem = [];
|
|
457
|
+
const rssData = [], heapData = [], extData = [];
|
|
458
|
+
|
|
459
|
+
// Tracks whether the arrays have been flood-filled on the first reading.
|
|
460
|
+
// Until filled, values are appended freely. Once filled, every new point
|
|
461
|
+
// shifts the oldest one out — keeping arrays at exactly MAX_POINTS.
|
|
462
|
+
let chartsFilled = false;
|
|
463
|
+
|
|
464
|
+
// ── Chart options ────────────────────────────────────────────────────
|
|
465
|
+
|
|
466
|
+
const chartDefaults = {
|
|
467
|
+
responsive: true,
|
|
468
|
+
maintainAspectRatio: false,
|
|
469
|
+
animation: { duration: 400 },
|
|
470
|
+
plugins: {
|
|
471
|
+
legend: { display: false },
|
|
472
|
+
tooltip: {
|
|
473
|
+
backgroundColor: '#1c2128',
|
|
474
|
+
borderColor: '#30363d',
|
|
475
|
+
borderWidth: 1,
|
|
476
|
+
titleColor: '#8b949e',
|
|
477
|
+
bodyColor: '#e6edf3',
|
|
478
|
+
titleFont: { family: 'monospace', size: 11 },
|
|
479
|
+
bodyFont: { family: 'monospace', size: 11 }
|
|
480
|
+
}
|
|
481
|
+
},
|
|
482
|
+
scales: {
|
|
483
|
+
x: {
|
|
484
|
+
grid: { color: '#21262d' },
|
|
485
|
+
ticks: { color: '#8b949e', font: { size: 10, family: 'monospace' }, maxTicksLimit: 6 }
|
|
486
|
+
},
|
|
487
|
+
y: {
|
|
488
|
+
grid: { color: '#21262d' },
|
|
489
|
+
ticks: { color: '#8b949e', font: { size: 10, family: 'monospace' } },
|
|
490
|
+
beginAtZero: true
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
};
|
|
494
|
+
|
|
495
|
+
const cpuChart = new Chart(document.getElementById('cpuChart'), {
|
|
496
|
+
type: 'line',
|
|
497
|
+
data: {
|
|
498
|
+
labels,
|
|
499
|
+
datasets: [
|
|
500
|
+
{ label: 'user', data: cpuUser, borderColor: '#3fb950', backgroundColor: 'rgba(63,185,80,.08)', borderWidth: 1.5, fill: true, tension: 0.4, pointRadius: 0 },
|
|
501
|
+
{ label: 'system', data: cpuSystem, borderColor: '#58a6ff', backgroundColor: 'rgba(88,166,255,.08)', borderWidth: 1.5, fill: true, tension: 0.4, pointRadius: 0 }
|
|
502
|
+
]
|
|
503
|
+
},
|
|
504
|
+
options: { ...chartDefaults }
|
|
505
|
+
});
|
|
506
|
+
|
|
507
|
+
const memChart = new Chart(document.getElementById('memChart'), {
|
|
508
|
+
type: 'line',
|
|
509
|
+
data: {
|
|
510
|
+
labels,
|
|
511
|
+
datasets: [
|
|
512
|
+
{ label: 'rss', data: rssData, borderColor: '#f8824a', borderWidth: 1.5, fill: false, tension: 0.4, pointRadius: 0 },
|
|
513
|
+
{ label: 'heap', data: heapData, borderColor: '#d2a8ff', borderWidth: 1.5, fill: false, tension: 0.4, pointRadius: 0 },
|
|
514
|
+
{ label: 'ext', data: extData, borderColor: '#ffa657', borderWidth: 1.5, fill: false, tension: 0.4, pointRadius: 0, borderDash: [4, 3] }
|
|
515
|
+
]
|
|
516
|
+
},
|
|
517
|
+
options: { ...chartDefaults }
|
|
518
|
+
});
|
|
519
|
+
|
|
520
|
+
// ── Helpers ──────────────────────────────────────────────────────────
|
|
521
|
+
|
|
522
|
+
// Push a value into a rolling array, evicting the oldest once full.
|
|
523
|
+
function pushRolling(arr, value) {
|
|
524
|
+
arr.push(value);
|
|
525
|
+
if (arr.length > MAX_POINTS) arr.shift();
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
// On the very first reading, flood-fill all arrays to MAX_POINTS with
|
|
529
|
+
// that value so the chart renders a stable full-width baseline immediately.
|
|
530
|
+
// After this every new point displaces exactly one old point.
|
|
531
|
+
function fillInitial(label, user, system, rss, heap, ext) {
|
|
532
|
+
for (let i = 0; i < MAX_POINTS; i++) {
|
|
533
|
+
labels.push(label);
|
|
534
|
+
cpuUser.push(user); cpuSystem.push(system);
|
|
535
|
+
rssData.push(rss); heapData.push(heap); extData.push(ext);
|
|
536
|
+
}
|
|
537
|
+
chartsFilled = true;
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
function toMb(bytes) { return (bytes / 1048576).toFixed(1); }
|
|
541
|
+
function fmtMs(value) { return value == null ? '—' : value.toFixed(3) + 'ms'; }
|
|
542
|
+
function currentTime() { return new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', second: '2-digit' }); }
|
|
543
|
+
|
|
544
|
+
function fmtUptime(seconds) {
|
|
545
|
+
const hours = Math.floor(seconds / 3600);
|
|
546
|
+
const minutes = Math.floor((seconds % 3600) / 60);
|
|
547
|
+
const secs = Math.floor(seconds % 60);
|
|
548
|
+
return hours ? `${hours}h ${minutes}m` : minutes ? `${minutes}m ${secs}s` : `${secs}s`;
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
function gcKindLabel(kind) {
|
|
552
|
+
const map = { 1: 'scavenge', 2: 'minor ms', 4: 'mark-sweep', 8: 'incremental', 15: 'all' };
|
|
553
|
+
return map[kind] ?? kind;
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
function gcTypeBadge(type) {
|
|
557
|
+
const map = { minor: 'minor', major: 'major', incremental: 'incr' };
|
|
558
|
+
return map[type] ?? 'minor';
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
// ── Render ───────────────────────────────────────────────────────────
|
|
562
|
+
|
|
563
|
+
function render(data) {
|
|
564
|
+
// Header
|
|
565
|
+
document.getElementById('pidVal').textContent = 'pid ' + data.process.pid;
|
|
566
|
+
document.getElementById('platformVal').textContent = data.process.platform;
|
|
567
|
+
document.getElementById('uptimeVal').textContent = fmtUptime(data.process.uptime);
|
|
568
|
+
document.getElementById('lastUpdate').textContent = currentTime();
|
|
569
|
+
|
|
570
|
+
// Stat tiles
|
|
571
|
+
document.getElementById('cpuUser').textContent = data.cpu.user.toLocaleString();
|
|
572
|
+
document.getElementById('cpuSys').textContent = data.cpu.system.toLocaleString();
|
|
573
|
+
document.getElementById('memRss').textContent = toMb(data.memory.rss);
|
|
574
|
+
document.getElementById('heapUsed').textContent = toMb(data.memory.heapUsed);
|
|
575
|
+
document.getElementById('heapTotal').textContent = toMb(data.memory.heapTotal);
|
|
576
|
+
document.getElementById('memExt').textContent = toMb(data.memory.external + (data.memory.arrayBuffers || 0));
|
|
577
|
+
|
|
578
|
+
// Event loop
|
|
579
|
+
document.getElementById('elMean').textContent = fmtMs(data.eventLoop.mean);
|
|
580
|
+
document.getElementById('elP95').textContent = fmtMs(data.eventLoop.p95);
|
|
581
|
+
document.getElementById('elMax').textContent = fmtMs(data.eventLoop.max);
|
|
582
|
+
|
|
583
|
+
// GC table — most recent 6 events, newest first
|
|
584
|
+
const gcEvents = data.gc || [];
|
|
585
|
+
document.getElementById('gcBody').innerHTML = gcEvents.length === 0
|
|
586
|
+
? '<tr><td colspan="4" style="color:#8b949e">no events</td></tr>'
|
|
587
|
+
: [...gcEvents].reverse().slice(0, 6).map(event => `
|
|
588
|
+
<tr>
|
|
589
|
+
<td><span class="badge ${gcTypeBadge(event.type)}">${event.type}</span></td>
|
|
590
|
+
<td>${event.duration.toFixed(3)}ms</td>
|
|
591
|
+
<td>${gcKindLabel(event.kind)}</td>
|
|
592
|
+
<td>${new Date(event.timestamp).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', second: '2-digit' })}</td>
|
|
593
|
+
</tr>`).join('');
|
|
594
|
+
|
|
595
|
+
// Chart data
|
|
596
|
+
const label = currentTime();
|
|
597
|
+
const user = data.cpu.user;
|
|
598
|
+
const system = data.cpu.system;
|
|
599
|
+
const rss = parseFloat(toMb(data.memory.rss));
|
|
600
|
+
const heap = parseFloat(toMb(data.memory.heapUsed));
|
|
601
|
+
const ext = parseFloat(toMb(data.memory.external));
|
|
602
|
+
|
|
603
|
+
if (!chartsFilled) {
|
|
604
|
+
// First reading: flood-fill so the axis scale is stable immediately.
|
|
605
|
+
fillInitial(label, user, system, rss, heap, ext);
|
|
606
|
+
} else {
|
|
607
|
+
// Steady state: slide one new point in, one old point out.
|
|
608
|
+
pushRolling(labels, label);
|
|
609
|
+
pushRolling(cpuUser, user); pushRolling(cpuSystem, system);
|
|
610
|
+
pushRolling(rssData, rss); pushRolling(heapData, heap); pushRolling(extData, ext);
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
cpuChart.update();
|
|
614
|
+
memChart.update();
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
// ── Polling ──────────────────────────────────────────────────────────
|
|
618
|
+
|
|
619
|
+
async function poll() {
|
|
620
|
+
try {
|
|
621
|
+
const response = await fetch(METRICS_URL, { cache: 'no-store', signal: AbortSignal.timeout(2500) });
|
|
622
|
+
if (!response.ok) throw new Error('bad response');
|
|
623
|
+
document.getElementById('errorBar').classList.remove('show');
|
|
624
|
+
render(await response.json());
|
|
625
|
+
} catch {
|
|
626
|
+
document.getElementById('errorBar').classList.add('show');
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
poll();
|
|
631
|
+
setInterval(poll, POLL_INTERVAL);
|
|
632
|
+
</script>
|
|
633
|
+
</body>
|
|
634
|
+
|
|
635
|
+
</html>
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
const getCpuMetrics = require('../collectors/cpu');
|
|
2
|
+
const getEventLoopMetrics = require('../collectors/event-loop');
|
|
3
|
+
const startGcObserver = require('../collectors/gc');
|
|
4
|
+
const getMemoryMetrics = require('../collectors/memory');
|
|
5
|
+
const getProcessMetrics = require('../collectors/process');
|
|
6
|
+
|
|
7
|
+
const metricsStore = require('../store/metricStore');
|
|
8
|
+
|
|
9
|
+
function collectMetrics() {
|
|
10
|
+
metricsStore.cpu = getCpuMetrics();
|
|
11
|
+
metricsStore.memory = getMemoryMetrics();
|
|
12
|
+
metricsStore.process = getProcessMetrics();
|
|
13
|
+
metricsStore.eventLoop = getEventLoopMetrics();
|
|
14
|
+
metricsStore.gc = startGcObserver();
|
|
15
|
+
metricsStore.timestamp = Date.now();
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function startCollector(interval = 3000) {
|
|
19
|
+
collectMetrics();
|
|
20
|
+
setInterval(collectMetrics, interval);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
module.exports = startCollector;
|