analytics-node-agent 1.0.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.
@@ -0,0 +1,4 @@
1
+ {
2
+ "java.configuration.updateBuildConfiguration": "interactive",
3
+ "window.title": "Assessment Analytics Node Agent"
4
+ }
package/index.js ADDED
@@ -0,0 +1,241 @@
1
+ import express from 'express'
2
+ import si from 'systeminformation'
3
+
4
+ const app = express()
5
+ const PORT = process.env.PORT || 9100
6
+
7
+ // Force every response to be plain JSON — avoids Content-Type issues on Linux
8
+ app.use((req, res, next) => {
9
+ res.setHeader('Content-Type', 'application/json')
10
+ next()
11
+ })
12
+
13
+ // Strips systeminformation's non-plain objects → guaranteed POJO for axios / Mongoose
14
+ const toPlain = obj => JSON.parse(JSON.stringify(obj ?? null))
15
+
16
+ // ── Cache static CPU info at startup (never changes) ──────────────────────────
17
+ let cpuStaticCache = null
18
+ let memLayoutCache = null // also static — physical DIMM slots never change
19
+
20
+ async function getCpuStatic() {
21
+ if (cpuStaticCache) return cpuStaticCache
22
+ try {
23
+ const [cpu, cpuCache] = await Promise.all([
24
+ si.cpu(),
25
+ si.cpuCache(),
26
+ ])
27
+ cpuStaticCache = toPlain({
28
+ manufacturer: cpu.manufacturer ?? null,
29
+ brand: cpu.brand ?? null,
30
+ vendor: cpu.vendor ?? null,
31
+ family: cpu.family ?? null,
32
+ model: cpu.model ?? null,
33
+ stepping: cpu.stepping ?? null,
34
+ revision: cpu.revision ?? null,
35
+ speed: cpu.speed ?? null,
36
+ speedMin: cpu.speedMin ?? null,
37
+ speedMax: cpu.speedMax ?? null,
38
+ cores: cpu.cores ?? null,
39
+ physicalCores: cpu.physicalCores ?? null,
40
+ processors: cpu.processors ?? null,
41
+ socket: cpu.socket ?? null,
42
+ flags: cpu.flags ?? null,
43
+ virtualization: cpu.virtualization ?? false,
44
+ cache: {
45
+ l1d: cpuCache.l1d ?? null,
46
+ l1i: cpuCache.l1i ?? null,
47
+ l2: cpuCache.l2 ?? null,
48
+ l3: cpuCache.l3 ?? null,
49
+ },
50
+ })
51
+ } catch (err) {
52
+ console.warn('[agent] CPU static info failed:', err.message)
53
+ cpuStaticCache = null
54
+ }
55
+ return cpuStaticCache
56
+ }
57
+
58
+ async function getMemLayout() {
59
+ if (memLayoutCache) return memLayoutCache
60
+ try {
61
+ const layout = await si.memLayout()
62
+ memLayoutCache = toPlain(
63
+ (Array.isArray(layout) ? layout : []).map(slot => ({
64
+ size: slot.size ?? null,
65
+ bank: slot.bank ?? null,
66
+ type: slot.type ?? null,
67
+ clockSpeed: slot.clockSpeed ?? null,
68
+ formFactor: slot.formFactor ?? null,
69
+ manufacturer: slot.manufacturer ?? null,
70
+ partNum: slot.partNum ?? null,
71
+ serialNum: slot.serialNum ?? null,
72
+ voltageConfigured: slot.voltageConfigured ?? null,
73
+ }))
74
+ )
75
+ } catch (err) {
76
+ console.warn('[agent] memLayout failed:', err.message)
77
+ memLayoutCache = []
78
+ }
79
+ return memLayoutCache
80
+ }
81
+
82
+ // Warm up caches on startup
83
+ getCpuStatic()
84
+ getMemLayout()
85
+
86
+ // ── Health ────────────────────────────────────────────────────────────────────
87
+ app.get('/health', (req, res) => {
88
+ res.end(JSON.stringify({ status: 'ok', timestamp: new Date().toISOString() }))
89
+ })
90
+
91
+ // ── CPU Static Info ───────────────────────────────────────────────────────────
92
+ app.get('/cpu-info', async (req, res) => {
93
+ try {
94
+ const info = await getCpuStatic()
95
+ if (!info) return res.status(503).end(JSON.stringify({ error: 'CPU info unavailable' }))
96
+ res.end(JSON.stringify({ cpuInfo: info }))
97
+ } catch (err) {
98
+ res.status(500).end(JSON.stringify({ error: err.message }))
99
+ }
100
+ })
101
+
102
+ // ── System (CPU load + RAM + Network) ────────────────────────────────────────
103
+ app.get('/system', async (req, res) => {
104
+ try {
105
+ const [cpuLoad, mem, net] = await Promise.all([
106
+ si.currentLoad(),
107
+ si.mem(),
108
+ si.networkStats(),
109
+ ])
110
+
111
+ // memLayout is cached — no extra I/O on every poll
112
+ const layout = await getMemLayout()
113
+
114
+ const cpu = toPlain({
115
+ currentLoad: parseFloat((cpuLoad.currentLoad ?? 0).toFixed(2)),
116
+ avgLoad: parseFloat((cpuLoad.avgLoad ?? 0).toFixed(2)),
117
+ currentLoadUser: parseFloat((cpuLoad.currentLoadUser ?? 0).toFixed(2)),
118
+ currentLoadSystem: parseFloat((cpuLoad.currentLoadSystem ?? 0).toFixed(2)),
119
+ currentLoadIdle: parseFloat((cpuLoad.currentLoadIdle ?? 0).toFixed(2)),
120
+ currentLoadIrq: parseFloat((cpuLoad.currentLoadIrq ?? 0).toFixed(2)),
121
+ cores: (cpuLoad.cpus ?? []).map((core, i) => ({
122
+ core: i,
123
+ currentLoad: parseFloat((core.load ?? 0).toFixed(2)),
124
+ currentLoadUser: parseFloat((core.loadUser ?? 0).toFixed(2)),
125
+ currentLoadSystem: parseFloat((core.loadSystem ?? 0).toFixed(2)),
126
+ currentLoadIdle: parseFloat((core.loadIdle ?? 0).toFixed(2)),
127
+ currentLoadIrq: parseFloat((core.loadIrq ?? 0).toFixed(2)),
128
+ })),
129
+ })
130
+
131
+ const ram = toPlain({
132
+ total: mem.total ?? null,
133
+ used: mem.active ?? null,
134
+ free: mem.available ?? null,
135
+ buffcache: mem.buffcache ?? null,
136
+ usedPercent: parseFloat(((mem.active / mem.total) * 100).toFixed(2)),
137
+ swapTotal: mem.swaptotal ?? null,
138
+ swapUsed: mem.swapused ?? null,
139
+ swapFree: mem.swapfree ?? null,
140
+ swapPercent: mem.swaptotal > 0
141
+ ? parseFloat(((mem.swapused / mem.swaptotal) * 100).toFixed(2))
142
+ : 0,
143
+ layout, // already a plain array from cache
144
+ })
145
+
146
+ const network = toPlain(
147
+ (Array.isArray(net) ? net : []).map(n => ({
148
+ iface: n.iface ?? null,
149
+ operstate: n.operstate ?? null,
150
+ rx_bytes: n.rx_bytes ?? null,
151
+ tx_bytes: n.tx_bytes ?? null,
152
+ rx_sec: Math.max(0, n.rx_sec ?? 0),
153
+ tx_sec: Math.max(0, n.tx_sec ?? 0),
154
+ rx_dropped: n.rx_dropped ?? null,
155
+ tx_dropped: n.tx_dropped ?? null,
156
+ rx_errors: n.rx_errors ?? null,
157
+ tx_errors: n.tx_errors ?? null,
158
+ ms: n.ms ?? null,
159
+ }))
160
+ )
161
+
162
+ res.end(JSON.stringify({ cpu, ram, network }))
163
+ } catch (err) {
164
+ res.status(500).end(JSON.stringify({ error: err.message }))
165
+ }
166
+ })
167
+
168
+ // ── Disk (filesystem + block devices + I/O stats) ────────────────────────────
169
+ app.get('/disk', async (req, res) => {
170
+ try {
171
+ const [fsSizes, fsStats, blockDevices, diskIO] = await Promise.all([
172
+ si.fsSize(),
173
+ si.fsStats(),
174
+ si.blockDevices(),
175
+ si.disksIO(),
176
+ ])
177
+
178
+ // Normalise fsStats → always an array
179
+ const fsStatsArray = Array.isArray(fsStats)
180
+ ? fsStats
181
+ : (fsStats && typeof fsStats === 'object' ? [fsStats] : [])
182
+
183
+ const disk = toPlain(
184
+ (Array.isArray(fsSizes) ? fsSizes : [])
185
+ .filter(d => d.size > 0)
186
+ .map(d => {
187
+ const stat = fsStatsArray.find(s => s.fs === d.fs) ?? null
188
+ return {
189
+ fs: d.fs ?? null,
190
+ mount: d.mount ?? null,
191
+ type: d.type ?? null,
192
+ size: d.size ?? null,
193
+ used: d.used ?? null,
194
+ available: d.available ?? null,
195
+ usePercent: parseFloat((d.use ?? 0).toFixed(2)),
196
+ rx_sec: stat ? Math.max(0, stat.rx_sec ?? 0) : null,
197
+ wx_sec: stat ? Math.max(0, stat.wx_sec ?? 0) : null,
198
+ tx_sec: stat ? Math.max(0, stat.tx_sec ?? 0) : null,
199
+ }
200
+ })
201
+ )
202
+
203
+ const blockDevs = toPlain(
204
+ (Array.isArray(blockDevices) ? blockDevices : [])
205
+ .filter(d => d.type === 'disk' || d.type === 'part')
206
+ .map(d => ({
207
+ name: d.name ?? null,
208
+ type: d.type ?? null,
209
+ fstype: d.fstype ?? null,
210
+ mount: d.mount ?? null,
211
+ size: d.size ?? null,
212
+ physical: d.physical ?? null,
213
+ removable: d.removable ?? false,
214
+ protocol: d.protocol ?? null,
215
+ label: d.label ?? null,
216
+ }))
217
+ )
218
+
219
+ const ioStats = toPlain([{
220
+ rIO: diskIO?.rIO ?? null,
221
+ wIO: diskIO?.wIO ?? null,
222
+ tIO: diskIO?.tIO ?? null,
223
+ rIO_sec: diskIO?.rIO_sec ?? null,
224
+ wIO_sec: diskIO?.wIO_sec ?? null,
225
+ tIO_sec: diskIO?.tIO_sec ?? null,
226
+ rBytesPerSec: diskIO?.rBytesPerSec ?? null,
227
+ wBytesPerSec: diskIO?.wBytesPerSec ?? null,
228
+ tBytesPerSec: diskIO?.tBytesPerSec ?? null,
229
+ ms: diskIO?.ms ?? null,
230
+ }])
231
+
232
+ res.end(JSON.stringify({ disk, blockDevices: blockDevs, diskIO: ioStats }))
233
+ } catch (err) {
234
+ res.status(500).end(JSON.stringify({ error: err.message }))
235
+ }
236
+ })
237
+
238
+ // ── Start ─────────────────────────────────────────────────────────────────────
239
+ app.listen(PORT, () => {
240
+ console.log(`✅ node-agent running on port ${PORT}`)
241
+ })
package/nodemon.json ADDED
@@ -0,0 +1,9 @@
1
+ {
2
+ "watch": [
3
+ "index.js"
4
+ ],
5
+ "ext": "js",
6
+ "ignore": [
7
+ "node_modules/"
8
+ ]
9
+ }
package/package.json ADDED
@@ -0,0 +1,18 @@
1
+
2
+ {
3
+ "name": "analytics-node-agent",
4
+ "version": "1.0.0",
5
+ "type": "module",
6
+ "main": "index.js",
7
+ "scripts": {
8
+ "start": "node index.js",
9
+ "dev": "nodemon index.js"
10
+ },
11
+ "dependencies": {
12
+ "express": "^4.18.2",
13
+ "systeminformation": "^5.22.0"
14
+ },
15
+ "devDependencies": {
16
+ "nodemon": "^3.0.0"
17
+ }
18
+ }