smart-home-engine 1.35.1 → 1.36.1

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.
@@ -1,4 +1,4 @@
1
- import{m as O}from"./monaco-langs-DKJI_o9J.js";import{t as I}from"./index-DxSKkp8l.js";/*!-----------------------------------------------------------------------------
1
+ import{m as O}from"./monaco-langs-DKJI_o9J.js";import{t as I}from"./index-Crt_IAIl.js";/*!-----------------------------------------------------------------------------
2
2
  * Copyright (c) Microsoft Corporation. All rights reserved.
3
3
  * Version: 0.52.2(404545bded1df6ffa41ea0af4e8ddb219018c6c1)
4
4
  * Released under the MIT license
@@ -172,10 +172,10 @@
172
172
  }
173
173
  })();
174
174
  </script>
175
- <script type="module" crossorigin src="/assets/index-DxSKkp8l.js"></script>
175
+ <script type="module" crossorigin src="/assets/index-Crt_IAIl.js"></script>
176
176
  <link rel="modulepreload" crossorigin href="/assets/monaco-langs-DKJI_o9J.js">
177
177
  <link rel="stylesheet" crossorigin href="/assets/monaco-langs-DyX1CsEw.css">
178
- <link rel="stylesheet" crossorigin href="/assets/index-ji2CpixA.css">
178
+ <link rel="stylesheet" crossorigin href="/assets/index-hiXQxo8M.css">
179
179
  </head>
180
180
  <body>
181
181
  <div id="app"></div>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "smart-home-engine",
3
- "version": "1.35.1",
3
+ "version": "1.36.1",
4
4
  "description": "Node.js based script runner for use in MQTT based Smart Home environments",
5
5
  "main": "src/index.js",
6
6
  "scripts": {
@@ -16,7 +16,8 @@
16
16
  # a wrapper or symlink /usr/local/bin/<adapter>.
17
17
  #
18
18
  # she-servicectl version
19
- # she-servicectl list (adapters = template units + npm packages with mqttInterfaces)
19
+ # she-servicectl list (adapters = template units + npm packages with mqttInterfaces; instances carry
20
+ # systemd's MemoryCurrent / CPUUsageNSec for the dashboard)
20
21
  # she-servicectl unit <adapter> <instance> start|stop|restart|enable|disable|status
21
22
  # she-servicectl logs <adapter> <instance> [-n N] [--follow]
22
23
  # she-servicectl env <adapter> <instance> read|write (write: file on stdin)
@@ -40,7 +41,7 @@
40
41
 
41
42
  set -eu
42
43
 
43
- VERSION=9
44
+ VERSION=10
44
45
  BROKER_ENV=/etc/mqtt-interfaces/broker.env
45
46
  UNIT_DIR=/etc/systemd/system
46
47
 
@@ -314,15 +315,20 @@ cmd_list() {
314
315
  valid_instance "$i" || continue
315
316
  [ $first -eq 1 ] || printf ','
316
317
  first=0
317
- show=$(systemctl show -p ActiveState -p SubState -p UnitFileState -p ExecMainStartTimestamp -p NRestarts "$a@$i.service" 2>/dev/null || true)
318
+ show=$(systemctl show -p ActiveState -p SubState -p UnitFileState -p ExecMainStartTimestamp -p NRestarts -p MainPID -p MemoryCurrent -p CPUUsageNSec "$a@$i.service" 2>/dev/null || true)
318
319
  active=$(printf '%s\n' "$show" | sed -n 's/^ActiveState=//p')
320
+ pid=$(printf '%s\n' "$show" | sed -n 's/^MainPID=//p')
321
+ mem=$(printf '%s\n' "$show" | sed -n 's/^MemoryCurrent=//p')
322
+ cpuns=$(printf '%s\n' "$show" | sed -n 's/^CPUUsageNSec=//p')
323
+ case "$mem" in *[!0-9]*|'') mem=null ;; esac
324
+ case "$cpuns" in *[!0-9]*|'') cpuns=null ;; esac
319
325
  sub=$(printf '%s\n' "$show" | sed -n 's/^SubState=//p')
320
326
  uf=$(printf '%s\n' "$show" | sed -n 's/^UnitFileState=//p')
321
327
  since=$(printf '%s\n' "$show" | sed -n 's/^ExecMainStartTimestamp=//p')
322
328
  nr=$(printf '%s\n' "$show" | sed -n 's/^NRestarts=//p')
323
- printf '{"adapter":"%s","instance":"%s","active":"%s","sub":"%s","unitFile":"%s","since":"%s","restarts":%s}' \
329
+ printf '{"adapter":"%s","instance":"%s","active":"%s","sub":"%s","unitFile":"%s","since":"%s","restarts":%s,"pid":%s,"memory":%s,"cpuNs":%s}' \
324
330
  "$a" "$i" "$(json_escape "$active")" "$(json_escape "$sub")" "$(json_escape "$uf")" \
325
- "$(json_escape "$since")" "${nr:-0}"
331
+ "$(json_escape "$since")" "${nr:-0}" "${pid:-0}" "$mem" "$cpuns"
326
332
  done
327
333
  done
328
334
  printf ']}\n'
@@ -26,7 +26,7 @@ const { execFile, spawn } = require('child_process');
26
26
  const sshDeploy = require('./ssh-deploy');
27
27
 
28
28
  const HELPER = '/usr/local/bin/she-servicectl';
29
- const HELPER_VERSION = 9; // must match VERSION in service/she-servicectl
29
+ const HELPER_VERSION = 10; // must match VERSION in service/she-servicectl
30
30
  /** Default identity for services.hosts[].ssh — one key for all managed hosts (I5). */
31
31
  const DEFAULT_SERVICES_IDENTITY = '~/.she/ssh/services_id_ed25519';
32
32
  /** The helper as shipped with this she version (copied to remote hosts by the deploy route). */
@@ -69,6 +69,21 @@ function parseInfo(val) {
69
69
  return v;
70
70
  }
71
71
 
72
+ /** `<name>/maintenance/stats` payload (core 0.8+): numbers only, anything else → null. */
73
+ function parseStats(v) {
74
+ if (typeof v === 'string') {
75
+ try {
76
+ v = JSON.parse(v);
77
+ } catch {
78
+ return null;
79
+ }
80
+ }
81
+ if (!v || typeof v !== 'object' || Array.isArray(v)) return null;
82
+ const out = {};
83
+ for (const k of ['rss', 'heapUsed', 'heapTotal', 'cpu', 'eventLoopLag', 'uptime', 'ts']) if (typeof v[k] === 'number' && Number.isFinite(v[k])) out[k] = v[k];
84
+ return typeof out.rss === 'number' ? out : null;
85
+ }
86
+
72
87
  /**
73
88
  * Derive the service inventory from the retained MQTT state.
74
89
  *
@@ -80,11 +95,17 @@ function analyzeServices(entries, opts = {}) {
80
95
  const now = opts.now ?? Date.now();
81
96
  const infos = new Map(); // instance → {info, ts}
82
97
  const conns = new Map(); // instance → {connected, ts, lc}
98
+ const stats = new Map(); // instance → {stats, ts} (<name>/maintenance/stats, core 0.8+)
83
99
  const topics = new Set();
84
100
 
85
101
  for (const [topic, obj] of entries) {
86
102
  topics.add(topic);
87
103
  const slash = topic.indexOf('/');
104
+ if (slash > 0 && topic.slice(slash) === '/maintenance/stats') {
105
+ const st = parseStats(obj && obj.val);
106
+ if (st) stats.set(topic.slice(0, slash), { stats: st, ts: obj.ts });
107
+ continue;
108
+ }
88
109
  if (slash <= 0 || topic.indexOf('/', slash + 1) !== -1) continue; // exactly two levels
89
110
  const instance = topic.slice(0, slash);
90
111
  const leaf = topic.slice(slash + 1);
@@ -129,6 +150,7 @@ function analyzeServices(entries, opts = {}) {
129
150
  connectedTs: c ? c.ts : null,
130
151
  connectedLc: c ? (c.lc ?? c.ts) : null,
131
152
  infoTs: i ? i.ts : null,
153
+ stats: stats.has(instance) && connected !== null && connected > 0 ? { ...stats.get(instance).stats, receivedTs: stats.get(instance).ts } : null,
132
154
  statusTopics,
133
155
  info,
134
156
  });
@@ -556,6 +556,19 @@ router.get('/hosts', async (req, res) => {
556
556
  res.json({ hosts: await promise, cached: false });
557
557
  });
558
558
 
559
+ /** CPUUsageNSec samples per host + unit → cpu share (% of one core) between two listings. */
560
+ const _cpuSamples = new Map();
561
+ function withCpuShare(hostName, instances, now = Date.now()) {
562
+ return (instances || []).map((i) => {
563
+ if (typeof i.cpuNs !== 'number') return i;
564
+ const key = hostName + '|' + i.adapter + '@' + i.instance;
565
+ const prev = _cpuSamples.get(key);
566
+ _cpuSamples.set(key, { cpuNs: i.cpuNs, ts: now });
567
+ if (!prev || now - prev.ts < 1000 || i.cpuNs < prev.cpuNs) return { ...i, cpu: null };
568
+ return { ...i, cpu: Math.round(((i.cpuNs - prev.cpuNs) / ((now - prev.ts) * 1e6)) * 1000) / 10 };
569
+ });
570
+ }
571
+
559
572
  async function listHosts(req) {
560
573
  return Promise.all(
561
574
  hostEntries(req).map(async ({ cfg, driver }) => {
@@ -581,7 +594,7 @@ async function listHosts(req) {
581
594
  }
582
595
  }),
583
596
  );
584
- return { ...base, ok: true, hostname: base.hostname || list.hostname, ...list, adapters };
597
+ return { ...base, ok: true, hostname: base.hostname || list.hostname, ...list, instances: withCpuShare(cfg.name, list.instances), adapters };
585
598
  } catch (err) {
586
599
  return { ...base, ok: false, code: err.code || 'ERROR', error: err.message };
587
600
  }