fix: 暴露内存回收真实结果
This commit is contained in:
@@ -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` 清空器,也不能为了估算全量临时目录而长时间阻塞。
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user