@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
|
|
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}
|
|
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 =
|
|
15
|
-
|
|
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
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
133
|
-
totalBytes:
|
|
134
|
-
|
|
135
|
-
|
|
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:
|
|
238
|
+
cores: Math.round(allocatedCores * 10) / 10,
|
|
239
|
+
hostCores,
|
|
141
240
|
},
|
|
142
241
|
processes: {
|
|
143
242
|
alphaclaw: { rssBytes: alphaclawRss },
|