@chrysb/alphaclaw 0.3.2-beta.3 → 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.
@@ -366,7 +366,7 @@ export const WatchdogTab = ({
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}%`
@@ -11,17 +11,54 @@ const readCgroupFile = (filePath) => {
11
11
  }
12
12
  };
13
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
+
14
39
  const parseCgroupMemory = () => {
15
- const current = readCgroupFile("/sys/fs/cgroup/memory.current");
16
- 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
+ ]);
17
48
  if (!current) return null;
18
49
  const usedBytes = Number.parseInt(current, 10);
19
50
  if (Number.isNaN(usedBytes)) return null;
20
- const limitBytes =
51
+ const parsedLimit =
21
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;
22
59
  return {
23
60
  usedBytes,
24
- totalBytes: Number.isNaN(limitBytes) ? null : limitBytes,
61
+ totalBytes: unlimited ? null : limitBytes,
25
62
  };
26
63
  };
27
64
 
@@ -41,6 +78,54 @@ const parseCgroupCpu = () => {
41
78
  };
42
79
  };
43
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
+
44
129
  const readProcStatus = (pid) => {
45
130
  try {
46
131
  const status = fs.readFileSync(`/proc/${pid}/status`, "utf8");
@@ -98,6 +183,8 @@ let prevCpuSnapshot = null;
98
183
  let prevCpuSnapshotAt = 0;
99
184
 
100
185
  const getSystemResources = ({ gatewayPid = null } = {}) => {
186
+ const hostCores = os.cpus().length || 1;
187
+ const allocatedCores = getAllocatedCpuCores() || hostCores;
101
188
  const cgroupMem = parseCgroupMemory();
102
189
  const mem = {
103
190
  usedBytes: cgroupMem?.usedBytes ?? process.memoryUsage().rss,
@@ -106,7 +193,7 @@ const getSystemResources = ({ gatewayPid = null } = {}) => {
106
193
 
107
194
  const diskUsage = readDiskUsage();
108
195
 
109
- const cgroupCpu = parseCgroupCpu();
196
+ const cgroupCpu = parseCgroupCpu() || parseCgroupCpuV1();
110
197
  let cpuPercent = null;
111
198
  if (cgroupCpu?.usageUsec != null) {
112
199
  const now = Date.now();
@@ -115,15 +202,15 @@ const getSystemResources = ({ gatewayPid = null } = {}) => {
115
202
  if (elapsedMs > 0) {
116
203
  const usageDeltaUs = cgroupCpu.usageUsec - prevCpuSnapshot.usageUsec;
117
204
  const elapsedUs = elapsedMs * 1000;
118
- 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));
119
207
  }
120
208
  }
121
209
  prevCpuSnapshot = cgroupCpu;
122
210
  prevCpuSnapshotAt = now;
123
211
  } else {
124
212
  const load = os.loadavg();
125
- const cpus = os.cpus().length || 1;
126
- cpuPercent = Math.min(100, Math.max(0, (load[0] / cpus) * 100));
213
+ cpuPercent = Math.min(100, Math.max(0, (load[0] / allocatedCores) * 100));
127
214
  }
128
215
 
129
216
  const alphaclawRss = process.memoryUsage().rss;
@@ -148,7 +235,8 @@ const getSystemResources = ({ gatewayPid = null } = {}) => {
148
235
  },
149
236
  cpu: {
150
237
  percent: cpuPercent != null ? Math.round(cpuPercent * 10) / 10 : null,
151
- cores: os.cpus().length,
238
+ cores: Math.round(allocatedCores * 10) / 10,
239
+ hostCores,
152
240
  },
153
241
  processes: {
154
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.3",
3
+ "version": "0.3.2-beta.4",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },