fix: 暴露内存回收真实结果

This commit is contained in:
pikastech
2026-07-21 09:20:02 +02:00
parent 5e62ff4657
commit 01ca1d3dad
4 changed files with 46 additions and 8 deletions
+4
View File
@@ -83,6 +83,10 @@ NC01 的内存压力 cgroup 回收规则:
- owning YAML 声明目标 cgroup、回收量、`minimumInactiveAnonBytes` 和 swap 保留线;
- plan 与执行前复核 `inactive_anon` 不低于 YAML 下限;
- 最坏情况下的换出量不会突破 YAML 声明的 `minimumSwapFreeBytesAfterReclaim`
- cgroup `reclaimBytes` 只表示内核请求上界,不是预期收益:
- plan 必须显示 `estimateKind=kernel-request-upper-bound`
- job 终态必须分别披露 cgroup 实际下降、`MemAvailable` 变化、`targetMet` 和剩余缺口;
- 内核请求成功但目标未达到时,结果必须为 `memory-target-not-met`,不得按请求量宣称已回收;
-`plan``run --confirm``status``memory` 的顺序复核实际结果。
默认 `/tmp` GC 只包含 allowlisted 诊断目录和已知低风险路径。非 allowlist 的 stale `/tmp` 一级子项必须显式 `--include-stale-tmp` 才能进入候选;扫描按 `--limit` 有界枚举候选,执行时仍只允许删除 `/tmp` 直接子项,并避开 X11/ICE/font socket、systemd private、tmux、ssh、vscode 等系统/session 前缀。该入口不能递归扩大成通用 `/tmp` 清空器,也不能为了估算全量临时目录而长时间阻塞。
+26 -5
View File
@@ -1526,15 +1526,22 @@ def summarize(candidates, returned, disk=None):
}
def summarize_memory_candidates(candidates, returned):
total_memory = sum(safe_int(item.get("expectedMemoryRssBytes")) for item in candidates)
returned_memory = sum(safe_int(item.get("expectedMemoryRssBytes")) for item in returned)
total_memory = sum(safe_int(item.get("expectedMemoryRssBytes")) for item in candidates if item.get("kind") != "cgroup-memory-reclaim")
returned_memory = sum(safe_int(item.get("expectedMemoryRssBytes")) for item in returned if item.get("kind") != "cgroup-memory-reclaim")
requested_memory = sum(safe_int(item.get("reclaimBytes")) for item in candidates if item.get("kind") == "cgroup-memory-reclaim")
returned_requested_memory = sum(safe_int(item.get("reclaimBytes")) for item in returned if item.get("kind") == "cgroup-memory-reclaim")
by_kind = {}
for item in candidates:
kind = item.get("kind") or "unknown"
bucket = by_kind.setdefault(kind, {"count": 0, "expectedMemoryRssBytes": 0})
bucket = by_kind.setdefault(kind, {"count": 0})
bucket["count"] += 1
bucket["expectedMemoryRssBytes"] += safe_int(item.get("expectedMemoryRssBytes"))
bucket["expectedMemoryRss"] = fmt_bytes(bucket["expectedMemoryRssBytes"])
if kind == "cgroup-memory-reclaim":
bucket["requestedMemoryReclaimBytes"] = safe_int(bucket.get("requestedMemoryReclaimBytes")) + safe_int(item.get("reclaimBytes"))
bucket["requestedMemoryReclaim"] = fmt_bytes(bucket["requestedMemoryReclaimBytes"])
bucket["estimateKind"] = "kernel-request-upper-bound"
else:
bucket["expectedMemoryRssBytes"] = safe_int(bucket.get("expectedMemoryRssBytes")) + safe_int(item.get("expectedMemoryRssBytes"))
bucket["expectedMemoryRss"] = fmt_bytes(bucket["expectedMemoryRssBytes"])
return {
"scope": "memory-pressure-only",
"candidateCount": len(candidates),
@@ -1543,6 +1550,11 @@ def summarize_memory_candidates(candidates, returned):
"expectedMemoryRss": fmt_bytes(total_memory),
"returnedExpectedMemoryRssBytes": returned_memory,
"returnedExpectedMemoryRss": fmt_bytes(returned_memory),
"requestedMemoryReclaimBytes": requested_memory,
"requestedMemoryReclaim": fmt_bytes(requested_memory),
"returnedRequestedMemoryReclaimBytes": returned_requested_memory,
"returnedRequestedMemoryReclaim": fmt_bytes(returned_requested_memory),
"cgroupEstimateKind": "kernel-request-upper-bound" if requested_memory > 0 else None,
"estimatedDiskBytes": 0,
"outcome": "memory-reclaim-candidates" if candidates else "no-memory-reclaim",
"byKind": by_kind,
@@ -2612,6 +2624,15 @@ def finish_memory_pressure_job(job_id, candidates, observed_at, worker_pid, work
- safe_int((memory_before.get("memory") or {}).get("availableBytes"))
) if memory_before.get("ok") and memory_after.get("ok") else None,
})
target_available = safe_int((MEMORY_CONFIG.get("cgroupReclaim") or {}).get("targetMemAvailableBytes"))
after_available = safe_int((memory_after.get("memory") or {}).get("availableBytes"))
target_met = target_available > 0 and after_available > target_available
summary.update({
"targetMemAvailableBytes": target_available,
"targetMet": target_met,
"targetGapBytes": max(0, target_available - after_available + 1) if target_available > 0 else None,
"outcome": "memory-target-met" if target_met else "memory-target-not-met",
})
payload = {
"ok": len(failed) == 0,
"action": "gc remote run",
+11 -1
View File
@@ -455,6 +455,9 @@ def collect_cgroup_reclaim_candidates():
"memoryCurrentBytes": current,
"memorySwapCurrentBytes": read_cgroup_integer(path, "memory.swap.current"),
"memoryStat": memory_stat,
"requestedMemoryReclaimBytes": reclaim_bytes,
"requestedMemoryReclaim": fmt_bytes(reclaim_bytes),
"estimateKind": "kernel-request-upper-bound",
"expectedMemoryRssBytes": reclaim_bytes,
"expectedMemoryRss": fmt_bytes(reclaim_bytes),
"estimatedDiskBytes": 0,
@@ -728,9 +731,16 @@ def execute_cgroup_reclaim_candidate(candidate):
cgroup_delta = max(0, cgroup_before - cgroup_after) if cgroup_before is not None and cgroup_after is not None else 0
host_delta = max(0, after_available - before_available) if after_available is not None else 0
succeeded = write_error is None or cgroup_delta > 0 or host_delta > 0
partial = max(cgroup_delta, host_delta) < safe_int(configured.get("reclaimBytes"))
if write_error and succeeded:
outcome = "partial-reclaim-after-kernel-error"
elif succeeded and partial:
outcome = "partial-reclaim"
else:
outcome = "reclaim-requested" if succeeded else "reclaim-request-failed"
return {
"status": "succeeded" if succeeded else "failed",
"outcome": "partial-reclaim-after-kernel-error" if write_error and succeeded else ("reclaim-requested" if succeeded else "reclaim-request-failed"),
"outcome": outcome,
"reclaimedMemoryBytes": max(cgroup_delta, host_delta),
"hostMemAvailableDeltaBytes": (after_available - before_available) if after_available is not None else None,
"cgroupMemoryDeltaBytes": cgroup_delta,
+5 -2
View File
@@ -400,8 +400,6 @@ function compactMemoryPressureCandidate(value: unknown): Record<string, unknown>
kind: candidate.kind,
reason: candidate.reason,
risk: candidate.risk,
expectedMemoryRssBytes: candidate.expectedMemoryRssBytes,
expectedMemoryRss: candidate.expectedMemoryRss,
action: { op: action.op, allowlist: action.allowlist },
};
if (candidate.kind === "cgroup-memory-reclaim") {
@@ -410,6 +408,9 @@ function compactMemoryPressureCandidate(value: unknown): Record<string, unknown>
configId: candidate.configId,
path: candidate.path,
reclaimBytes: candidate.reclaimBytes,
requestedMemoryReclaimBytes: candidate.requestedMemoryReclaimBytes,
requestedMemoryReclaim: candidate.requestedMemoryReclaim,
estimateKind: candidate.estimateKind,
swappiness: candidate.swappiness,
minimumInactiveAnonBytes: candidate.minimumInactiveAnonBytes,
targetMemAvailableBytes: candidate.targetMemAvailableBytes,
@@ -428,6 +429,8 @@ function compactMemoryPressureCandidate(value: unknown): Record<string, unknown>
&& positiveInteger(identity.startTicks) !== null);
return {
...base,
expectedMemoryRssBytes: candidate.expectedMemoryRssBytes,
expectedMemoryRss: candidate.expectedMemoryRss,
rootPid: candidate.rootPid,
sid: candidate.sid,
rootStartTicks: candidate.rootStartTicks,