@chrysb/alphaclaw 0.3.2-beta.2 → 0.3.2-beta.4

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.
@@ -361,12 +361,12 @@ export const WatchdogTab = ({
361
361
  onToggle=${() => setMemoryExpanded(true)}
362
362
  />
363
363
  <${ResourceBar}
364
- label="Disk"
364
+ label=${`Disk${r.disk?.path ? ` (${r.disk.path})` : ""}`}
365
365
  percent=${r.disk?.percent}
366
366
  detail=${`${formatBytes(r.disk?.usedBytes)} / ${formatBytes(r.disk?.totalBytes)}`}
367
367
  />
368
368
  <${ResourceBar}
369
- label=${`CPU${r.cpu?.cores ? ` (${r.cpu.cores} core${r.cpu.cores > 1 ? "s" : ""})` : ""}`}
369
+ label=${`CPU${r.cpu?.cores ? ` (${r.cpu.cores} vCPU)` : ""}`}
370
370
  percent=${r.cpu?.percent}
371
371
  detail=${r.cpu?.percent != null
372
372
  ? `${r.cpu.percent}%`
@@ -1,6 +1,7 @@
1
1
  const os = require("os");
2
2
  const fs = require("fs");
3
3
  const { execSync } = require("child_process");
4
+ const { kRootDir } = require("./constants");
4
5
 
5
6
  const readCgroupFile = (filePath) => {
6
7
  try {
@@ -10,17 +11,54 @@ const readCgroupFile = (filePath) => {
10
11
  }
11
12
  };
12
13
 
14
+ const readFirstCgroupFile = (paths) => {
15
+ for (const filePath of paths) {
16
+ const value = readCgroupFile(filePath);
17
+ if (value != null) return value;
18
+ }
19
+ return null;
20
+ };
21
+
22
+ const countCpuSet = (cpuSet) => {
23
+ if (!cpuSet) return null;
24
+ let count = 0;
25
+ const parts = String(cpuSet)
26
+ .split(",")
27
+ .map((part) => part.trim())
28
+ .filter(Boolean);
29
+ for (const part of parts) {
30
+ const [startRaw, endRaw] = part.split("-");
31
+ const start = Number.parseInt(startRaw, 10);
32
+ const end = endRaw == null ? start : Number.parseInt(endRaw, 10);
33
+ if (Number.isNaN(start) || Number.isNaN(end)) continue;
34
+ count += Math.max(0, end - start + 1);
35
+ }
36
+ return count > 0 ? count : null;
37
+ };
38
+
13
39
  const parseCgroupMemory = () => {
14
- const current = readCgroupFile("/sys/fs/cgroup/memory.current");
15
- const max = readCgroupFile("/sys/fs/cgroup/memory.max");
40
+ const current = readFirstCgroupFile([
41
+ "/sys/fs/cgroup/memory.current",
42
+ "/sys/fs/cgroup/memory/memory.usage_in_bytes",
43
+ ]);
44
+ const max = readFirstCgroupFile([
45
+ "/sys/fs/cgroup/memory.max",
46
+ "/sys/fs/cgroup/memory/memory.limit_in_bytes",
47
+ ]);
16
48
  if (!current) return null;
17
49
  const usedBytes = Number.parseInt(current, 10);
18
50
  if (Number.isNaN(usedBytes)) return null;
19
- const limitBytes =
51
+ const parsedLimit =
20
52
  max && max !== "max" ? Number.parseInt(max, 10) : null;
53
+ const limitBytes = Number.isNaN(parsedLimit) ? null : parsedLimit;
54
+ // Cgroup v1 uses huge sentinel values to mean "no limit".
55
+ const unlimited =
56
+ limitBytes == null ||
57
+ limitBytes <= 0 ||
58
+ limitBytes >= 9_000_000_000_000_000_000;
21
59
  return {
22
60
  usedBytes,
23
- totalBytes: Number.isNaN(limitBytes) ? null : limitBytes,
61
+ totalBytes: unlimited ? null : limitBytes,
24
62
  };
25
63
  };
26
64
 
@@ -40,6 +78,54 @@ const parseCgroupCpu = () => {
40
78
  };
41
79
  };
42
80
 
81
+ const parseCgroupCpuV1 = () => {
82
+ const usageNs = readFirstCgroupFile([
83
+ "/sys/fs/cgroup/cpuacct/cpuacct.usage",
84
+ "/sys/fs/cgroup/cpu/cpuacct.usage",
85
+ ]);
86
+ if (!usageNs) return null;
87
+ const usageNsParsed = Number.parseInt(usageNs, 10);
88
+ if (Number.isNaN(usageNsParsed)) return null;
89
+ return {
90
+ usageUsec: Math.floor(usageNsParsed / 1000),
91
+ userUsec: null,
92
+ systemUsec: null,
93
+ };
94
+ };
95
+
96
+ const getAllocatedCpuCores = () => {
97
+ const cpuMax = readCgroupFile("/sys/fs/cgroup/cpu.max");
98
+ if (cpuMax) {
99
+ const [quotaRaw, periodRaw] = cpuMax.split(/\s+/);
100
+ const quota = Number.parseInt(quotaRaw, 10);
101
+ const period = Number.parseInt(periodRaw, 10);
102
+ if (quotaRaw !== "max" && !Number.isNaN(quota) && !Number.isNaN(period) && period > 0) {
103
+ return quota / period;
104
+ }
105
+ }
106
+
107
+ const quotaV1 = readFirstCgroupFile([
108
+ "/sys/fs/cgroup/cpu/cpu.cfs_quota_us",
109
+ "/sys/fs/cgroup/cpuacct/cpu.cfs_quota_us",
110
+ ]);
111
+ const periodV1 = readFirstCgroupFile([
112
+ "/sys/fs/cgroup/cpu/cpu.cfs_period_us",
113
+ "/sys/fs/cgroup/cpuacct/cpu.cfs_period_us",
114
+ ]);
115
+ if (quotaV1 && periodV1) {
116
+ const quota = Number.parseInt(quotaV1, 10);
117
+ const period = Number.parseInt(periodV1, 10);
118
+ if (!Number.isNaN(quota) && !Number.isNaN(period) && quota > 0 && period > 0) {
119
+ return quota / period;
120
+ }
121
+ }
122
+
123
+ const cpuSet =
124
+ readCgroupFile("/sys/fs/cgroup/cpuset.cpus.effective") ||
125
+ readCgroupFile("/sys/fs/cgroup/cpuset.cpus");
126
+ return countCpuSet(cpuSet);
127
+ };
128
+
43
129
  const readProcStatus = (pid) => {
44
130
  try {
45
131
  const status = fs.readFileSync(`/proc/${pid}/status`, "utf8");
@@ -76,27 +162,38 @@ const getProcessUsage = (pid) => {
76
162
  return null;
77
163
  };
78
164
 
165
+ const readDiskUsage = () => {
166
+ const paths = [kRootDir, "/data", "/"];
167
+ for (const diskPath of paths) {
168
+ try {
169
+ const stat = fs.statfsSync(diskPath);
170
+ return {
171
+ usedBytes: stat.bsize * (stat.blocks - stat.bfree),
172
+ totalBytes: stat.bsize * stat.blocks,
173
+ path: diskPath,
174
+ };
175
+ } catch {
176
+ // Try next path.
177
+ }
178
+ }
179
+ return { usedBytes: null, totalBytes: null, path: null };
180
+ };
181
+
79
182
  let prevCpuSnapshot = null;
80
183
  let prevCpuSnapshotAt = 0;
81
184
 
82
185
  const getSystemResources = ({ gatewayPid = null } = {}) => {
186
+ const hostCores = os.cpus().length || 1;
187
+ const allocatedCores = getAllocatedCpuCores() || hostCores;
83
188
  const cgroupMem = parseCgroupMemory();
84
189
  const mem = {
85
190
  usedBytes: cgroupMem?.usedBytes ?? process.memoryUsage().rss,
86
191
  totalBytes: cgroupMem?.totalBytes ?? os.totalmem(),
87
192
  };
88
193
 
89
- let diskUsedBytes = null;
90
- let diskTotalBytes = null;
91
- try {
92
- const stat = fs.statfsSync("/");
93
- diskTotalBytes = stat.bsize * stat.blocks;
94
- diskUsedBytes = stat.bsize * (stat.blocks - stat.bfree);
95
- } catch {
96
- // statfsSync unavailable
97
- }
194
+ const diskUsage = readDiskUsage();
98
195
 
99
- const cgroupCpu = parseCgroupCpu();
196
+ const cgroupCpu = parseCgroupCpu() || parseCgroupCpuV1();
100
197
  let cpuPercent = null;
101
198
  if (cgroupCpu?.usageUsec != null) {
102
199
  const now = Date.now();
@@ -105,15 +202,15 @@ const getSystemResources = ({ gatewayPid = null } = {}) => {
105
202
  if (elapsedMs > 0) {
106
203
  const usageDeltaUs = cgroupCpu.usageUsec - prevCpuSnapshot.usageUsec;
107
204
  const elapsedUs = elapsedMs * 1000;
108
- cpuPercent = Math.min(100, Math.max(0, (usageDeltaUs / elapsedUs) * 100));
205
+ const rawPercent = (usageDeltaUs / elapsedUs) * 100;
206
+ cpuPercent = Math.min(100, Math.max(0, rawPercent / allocatedCores));
109
207
  }
110
208
  }
111
209
  prevCpuSnapshot = cgroupCpu;
112
210
  prevCpuSnapshotAt = now;
113
211
  } else {
114
212
  const load = os.loadavg();
115
- const cpus = os.cpus().length || 1;
116
- cpuPercent = Math.min(100, Math.max(0, (load[0] / cpus) * 100));
213
+ cpuPercent = Math.min(100, Math.max(0, (load[0] / allocatedCores) * 100));
117
214
  }
118
215
 
119
216
  const alphaclawRss = process.memoryUsage().rss;
@@ -129,15 +226,17 @@ const getSystemResources = ({ gatewayPid = null } = {}) => {
129
226
  : null,
130
227
  },
131
228
  disk: {
132
- usedBytes: diskUsedBytes,
133
- totalBytes: diskTotalBytes,
134
- percent: diskTotalBytes
135
- ? Math.round((diskUsedBytes / diskTotalBytes) * 1000) / 10
229
+ usedBytes: diskUsage.usedBytes,
230
+ totalBytes: diskUsage.totalBytes,
231
+ path: diskUsage.path,
232
+ percent: diskUsage.totalBytes
233
+ ? Math.round((diskUsage.usedBytes / diskUsage.totalBytes) * 1000) / 10
136
234
  : null,
137
235
  },
138
236
  cpu: {
139
237
  percent: cpuPercent != null ? Math.round(cpuPercent * 10) / 10 : null,
140
- cores: os.cpus().length,
238
+ cores: Math.round(allocatedCores * 10) / 10,
239
+ hostCores,
141
240
  },
142
241
  processes: {
143
242
  alphaclaw: { rssBytes: alphaclawRss },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@chrysb/alphaclaw",
3
- "version": "0.3.2-beta.2",
3
+ "version": "0.3.2-beta.4",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },