@chrysb/alphaclaw 0.3.2-beta.3 → 0.3.2
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}
|
|
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 =
|
|
16
|
-
|
|
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
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
238
|
+
cores: Math.round(allocatedCores * 10) / 10,
|
|
239
|
+
hostCores,
|
|
152
240
|
},
|
|
153
241
|
processes: {
|
|
154
242
|
alphaclaw: { rssBytes: alphaclawRss },
|
|
@@ -32,6 +32,24 @@ Changes committed ([abc1234](commit url)): <-- linked commit hash
|
|
|
32
32
|
• path/or/resource (new|edit|delete) — brief description
|
|
33
33
|
```
|
|
34
34
|
|
|
35
|
+
### Release Flow (Beta -> Production)
|
|
36
|
+
|
|
37
|
+
Use this release flow when promoting tested beta builds to production:
|
|
38
|
+
|
|
39
|
+
1. Ensure `main` is clean and synced, and tests pass.
|
|
40
|
+
2. Publish beta iterations as needed:
|
|
41
|
+
- `npm version prerelease --preid=beta`
|
|
42
|
+
- `git push && git push --tags`
|
|
43
|
+
- `npm publish --tag beta`
|
|
44
|
+
3. For beta template testing, pin exact beta in template `package.json` (for example `0.3.2-beta.4`) to avoid Docker layer cache reusing old installs.
|
|
45
|
+
4. When ready for production, publish a stable release version (for example `0.3.2`):
|
|
46
|
+
- `npm version 0.3.2`
|
|
47
|
+
- `git push && git push --tags`
|
|
48
|
+
- `npm publish` (publishes to `latest`)
|
|
49
|
+
5. Return templates to production channel:
|
|
50
|
+
- `@chrysb/alphaclaw: "latest"`
|
|
51
|
+
6. Optionally keep beta branch/tag flows active for next release cycle.
|
|
52
|
+
|
|
35
53
|
### Telegram Notice Format (AlphaClaw)
|
|
36
54
|
|
|
37
55
|
Use this format for any Telegram notices sent from AlphaClaw services (watchdog, system alerts, repair notices):
|