已合并
【UBSE】process_mem 在途迁移量按远端numa实际占用计算, OOM检测常开快速轮询, 清理失败UT #1466
2401_87013476创建于 16 天前
【UBSE】process_mem 在途迁移量按远端numa实际占用计算, OOM检测常开快速轮询, 清理失败UT #1466
已合并
2401_87013476创建于 16 天前
15 个文件变更+413-678
@@ -4,29 +4,36 @@ ubse.plugin.name=process_mem
4ubse.plugin.pkg=libprocess_mem.so4ubse.plugin.pkg=libprocess_mem.so
5 5 
6[process_mem]6[process_mem]
7-# 采集周期, /proc 的间隔 (可配范围 1-3600s)7+# Collect cycle: interval between /proc scans (valid range 1-3600s, fallback default 5)
8collect_process_interval=58collect_process_interval=5
9-# 默认 on, 跳过 root 进程9+# Default on: root processes (uid 0) are skipped when matching managed pids
10filter_root_process=true10filter_root_process=true
11-# 决策周期, 借用/归还的执行间隔 (可配范围 1-3600s)11+# Decision cycle: interval between borrow rounds (timeout check / borrow / passive return),
12+# valid range 1-3600s, fallback default 5
12schedule_interval=513schedule_interval=5
13-# 节点空闲触发借用阈值 (f), 由容量规划推导 f = S k×d×r, 可配范围 1-4096 GB, 越界回退默认 15,14+# Free-memory threshold that triggers borrowing: borrow starts when total free of all local NUMA nodes
14-# 建议不超过本机最大内存, 且大于 emergency_free_threshold15+# drops below f, derived from capacity planning as f = S - k x d x r (valid range 1-4096 GB,
16+# fallback default 15; should not exceed total local memory and should be greater than
17+# emergency_free_threshold)
15pressing_free_threshold=1518pressing_free_threshold=15
16-# 借用超时, 128MB 粒度的耗时 (ms), 可配范围 1-1800000 (30 分钟), 越界回退默认 100019+# Borrow timeout per 128MB block (ms), valid range 1-1800000 (30 min), fallback default 1000
17borrow.timeout=100020borrow.timeout=1000
18-# 是否必须同平面借用 (true=必须, 严格过滤; false=优先, 评分加权)21+# Whether borrowing must stay on the same plane: true=must (strict filtering),
22+# false=prefer (soft, score-weighted)
19borrow.must_same_plane=false23borrow.must_same_plane=false
20 24 
21-# 主动归还 (可选, 均有默认值)25+# Active return (optional, defaults apply)
22[process_mem.return]26[process_mem.return]
23-# 连续快速检测周期数: 仅紧急 fast 轮询(200ms)期间记录节点 free, n 次结算一次取最小值,27+# Number of consecutive fast-poll cycles: node free is sampled only during emergency fast polling (200ms);
24-# 窗口最小 free > pressing_free_threshold 时触发主动归还 (默认 300×200ms=60s 观察期), 可配范围 1-4096, 越界回退默认 30028+# every n samples the window minimum is settled, and active return is triggered when the window minimum
29+# exceeds pressing_free_threshold (default 300 x 200ms = 60s observation window);
30+# valid range 1-4096, fallback default 300
25observe_cycles=30031observe_cycles=300
26 32 
27-# OOM 快速检测 (可选, 均有默认值)33+# OOM fast detection (optional, defaults apply)
28[process_mem.oom]34[process_mem.oom]
29-# 紧急状态轮询间隔 (ms), 可配范围 50-60000, 越界回退默认 20035+# Emergency fast-poll interval (ms), valid range 50-60000, fallback default 200
30collect_node_interval=20036collect_node_interval=200
31-# 紧急借用/转嫁阈值 (GB), 可配范围 1-4096, 越界回退默认 5, 建议小于 pressing_free_threshold37+# Emergency borrow/pass-off threshold (GB), valid range 1-4096, fallback default 5;
38+# should be less than pressing_free_threshold
32emergency_free_threshold=539emergency_free_threshold=5
@@ -98,12 +98,12 @@ api.timeout=1800
98# obmm.memory.offline.timeout=10098# obmm.memory.offline.timeout=100
99# When set to true, OS SEI degradation is enabled on the borrowing node after the first memory borrow. Any invalid value will be considered as false.99# When set to true, OS SEI degradation is enabled on the borrowing node after the first memory borrow. Any invalid value will be considered as false.
100sei.enable=false100sei.enable=false
101-# Phase 3 process_mem: Max lend capacity per node, in GB. The scheduler checks that101+# Scheduler config (reuse UBSE mem scheduler): per-node lending limit in GB, the total amount
102-# lender.totalLent + borrowAmount nodeMaxLend before selecting a lender.102+# a node can lend out as a lender. The scheduler checks that
103-# 调度器配置 (reuse UBSE mem scheduler): 节点最大借出量 (GB), lender 借出总上限103+# lender.totalLent + borrowAmount <= node_lending_limit before selecting a lender.
104-# 可配范围 0-65535, 0 或不配置 = 不限制, 越界回退 0 (不限制)104+# Valid range 0-65535; 0 or unset means unlimited, out of range falls back to 0 (unlimited).
105-# 默认不限制(注释), 按需由部署方显式开启105+# Disabled by default (commented out); enable explicitly per deployment.
106-# scheduler.node_max_lend_gb=0106+# scheduler.node_lending_limit=0
107 107 
108[ubse.urma]108[ubse.urma]
109# Topology mode used when UBSE pushes topology to URMA UVS in CLOS networking.109# Topology mode used when UBSE pushes topology to URMA UVS in CLOS networking.
@@ -120,7 +120,7 @@ section取值:[ubse.memory]
120| 12 | scheduler.mode | 内存调度模式,控制 socket NUMA 选择策略。 | 默认值:free-priority<br>取值范围:free-priority / reliability-priority / performance-priority<br>如果取值超过范围,则取默认值free-priority。 | - **free-priority**:优先选择空闲率最高的节点。<br>- **reliability-priority**:优先复用已有借用关系并均衡各节点的借出数。<br>- **performance-priority**:均衡各节点带宽利用率,兼顾时延、内存利用率和可靠性。<br>- 当不配置时,检查已弃用的 `lender.balance`,兼容方式如下:<br>lender.balance=true 时取值 reliability-priority,lender.balance=false 取值 free-priority。 |120| 12 | scheduler.mode | 内存调度模式,控制 socket NUMA 选择策略。 | 默认值:free-priority<br>取值范围:free-priority / reliability-priority / performance-priority<br>如果取值超过范围,则取默认值free-priority。 | - **free-priority**:优先选择空闲率最高的节点。<br>- **reliability-priority**:优先复用已有借用关系并均衡各节点的借出数。<br>- **performance-priority**:均衡各节点带宽利用率,兼顾时延、内存利用率和可靠性。<br>- 当不配置时,检查已弃用的 `lender.balance`,兼容方式如下:<br>lender.balance=true 时取值 reliability-priority,lender.balance=false 取值 free-priority。 |
121| 13 | bandwidth.tolerance | performance-priority 模式下带宽均衡评分的分带宽度(容忍度)阈值。 | 默认值:2 × block.size <br>单位:MB<br>取值小于block.size视为非法,取默认值2 × block.size。 | 对每个候选借出 socket 模拟本次借用后,计算候选集合借出量 spread(最大借出 − 最小借出);分带值 D = spread / tolerance(向下取整),D=0 即借入后差值 < tolerance,此时带宽分最优;所有候选按 D 做 min-max 归一化得到带宽分。建议 tolerance 与典型单次借用大小配置为同量级。 |121| 13 | bandwidth.tolerance | performance-priority 模式下带宽均衡评分的分带宽度(容忍度)阈值。 | 默认值:2 × block.size <br>单位:MB<br>取值小于block.size视为非法,取默认值2 × block.size。 | 对每个候选借出 socket 模拟本次借用后,计算候选集合借出量 spread(最大借出 − 最小借出);分带值 D = spread / tolerance(向下取整),D=0 即借入后差值 < tolerance,此时带宽分最优;所有候选按 D 做 min-max 归一化得到带宽分。建议 tolerance 与典型单次借用大小配置为同量级。 |
122| 14 | sei.enable | 控制内存借用时是否启用OS的SEI降级功能。 | 默认值:false<br>取值范围:[true,false]<br>如果取值超过范围或非法,则取默认值false。 | - 仅在借入节点生效。<br>- 依赖 /etc/sudoers.d/ubse-sei 授予 ubse 用户 sysctl 提权权限。<br>- sei.enable=true 时,首次借用内存后自动执行 sysctl -w kernel.arm64_sync_sei=1 开启OS的SEI降级功能;末次归还后自动关闭OS的SEI降级功能。<br>- sei.enable=false时不会触发OS的SEI降级功能,UB断链导致的内存错误可能触发 OS Panic。 |122| 14 | sei.enable | 控制内存借用时是否启用OS的SEI降级功能。 | 默认值:false<br>取值范围:[true,false]<br>如果取值超过范围或非法,则取默认值false。 | - 仅在借入节点生效。<br>- 依赖 /etc/sudoers.d/ubse-sei 授予 ubse 用户 sysctl 提权权限。<br>- sei.enable=true 时,首次借用内存后自动执行 sysctl -w kernel.arm64_sync_sei=1 开启OS的SEI降级功能;末次归还后自动关闭OS的SEI降级功能。<br>- sei.enable=false时不会触发OS的SEI降级功能,UB断链导致的内存错误可能触发 OS Panic。 |
123-| 15 | scheduler.node_max_lend_gb | 调度器节点最大借出量,即节点作为 lender 的借出总量上限。 | 默认值:用#注释(不限制)<br>单位:GB<br>取值范围:[0, 65535]<br>配置为 0 或不配置时表示不限制,取值超过范围则回退为 0(不限制)。 | - 调度器在选择 lender 前校验 `lender.totalLent + 本次借出量 ≤ node_max_lend_gb`,超出上限的节点不会被选中。<br>- 所有节点的配置需保持一致。<br>- 默认不限制,按需由部署方显式开启。 |123+| 15 | scheduler.node_lending_limit | 调度器节点最大借出量,即节点作为 lender 的借出总量上限。 | 默认值:用#注释(不限制)<br>单位:GB<br>取值范围:[0, 65535]<br>配置为 0 或不配置时表示不限制,取值超过范围则回退为 0(不限制)。 | - 调度器在选择 lender 前校验 `lender.totalLent + 本次借出量 ≤ node_lending_limit`,超出上限的节点不会被选中。<br>- 所有节点的配置需保持一致。<br>- 默认不限制,按需由部署方显式开启。 |
124 124 
125## URMA配置说明125## URMA配置说明
126 126 
@@ -207,6 +207,11 @@ uint32_t SetProcMemConfig(const api::server::UbseIpcMessage& request, const api:
207 }207 }
208 return SendPidSetResponse(0, "No running process matches name: " + newConfig.identifier, context.requestId);208 return SendPidSetResponse(0, "No running process matches name: " + newConfig.identifier, context.requestId);
209 }209 }
210+ if (ret == UBSE_ERR_ACCESS_DENIED) {
211+ return SendPidSetResponse(
212+ 0, "PID " + newConfig.identifier + " is a root process, rejected by filter_root_process=true",
213+ context.requestId);
214+ }
210 if (ret == UBSE_ERR_INVALID_ARG) {215 if (ret == UBSE_ERR_INVALID_ARG) {
211 return SendPidSetResponse(0, "Invalid process-mem config (size/ratio/name out of range)", context.requestId);216 return SendPidSetResponse(0, "Invalid process-mem config (size/ratio/name out of range)", context.requestId);
212 }217 }
@@ -36,6 +36,69 @@ using namespace process_mem::manager;
36 36 
37const size_t DEFAULT_PAGE_SIZE = 4096;37const size_t DEFAULT_PAGE_SIZE = 4096;
38 38 
39+namespace {
40+constexpr uint32_t kMaxNumaNum = 256;
41+ 
42+// 远端借用会以新 NUMA 形式出现在本节点, /sys/.../node<N>/remote 属性标识(1=远端)
43+std::optional<bool> ReadNumaRemoteAttr(uint32_t numaId)
44+{
45+ std::ifstream file("/sys/devices/system/node/node" + std::to_string(numaId) + "/remote");
46+ if (!file.is_open()) {
47+ return std::nullopt;
48+ }
49+ uint64_t attr = 0;
50+ if (!(file >> attr)) {
51+ return std::nullopt;
52+ }
53+ return attr == 1;
54+}
55+ 
56+std::optional<uint64_t> ReadNumaMemKbField(uint32_t numaId, const std::string& field)
57+{
58+ std::ifstream file("/sys/devices/system/node/node" + std::to_string(numaId) + "/meminfo");
59+ if (!file.is_open()) {
60+ return std::nullopt;
61+ }
62+ std::string line;
63+ while (std::getline(file, line)) {
64+ if (line.find(field) == std::string::npos) {
65+ continue;
66+ }
67+ std::vector<std::string> tokens;
68+ std::istringstream iss(line);
69+ std::string token;
70+ while (iss >> token) {
71+ tokens.push_back(token);
72+ }
73+ // 行格式: "Node 0 MemFree: <kB> kB", 取倒数第二个字段
74+ if (tokens.size() < 5 || tokens.back() != "kB") {
75+ return std::nullopt;
76+ }
77+ try {
78+ return std::stoull(tokens[tokens.size() - 2]);
79+ } catch (...) {
80+ return std::nullopt;
81+ }
82+ }
83+ return std::nullopt;
84+}
85+ 
86+std::optional<uint64_t> ReadNumaMemFreeKb(uint32_t numaId)
87+{
88+ return ReadNumaMemKbField(numaId, "MemFree");
89+}
90+ 
91+std::optional<uint64_t> ReadNumaMemUsedKb(uint32_t numaId)
92+{
93+ auto totalKb = ReadNumaMemKbField(numaId, "MemTotal");
94+ auto freeKb = ReadNumaMemKbField(numaId, "MemFree");
95+ if (!totalKb.has_value() || !freeKb.has_value() || *totalKb < *freeKb) {
96+ return std::nullopt;
97+ }
98+ return *totalKb - *freeKb;
99+}
100+} // namespace
101+ 
39std::vector<pid_t> GetChildrenPidsFallback(pid_t parentPid)102std::vector<pid_t> GetChildrenPidsFallback(pid_t parentPid)
40{103{
41 std::vector<pid_t> children;104 std::vector<pid_t> children;
@@ -548,6 +611,8 @@ void ProcessMemPidCollect::DoCollectRound(uint64_t roundNum)
548 PidCollectInfoMap results;611 PidCollectInfoMap results;
549 CollectVmRssDispatch(results, roundNum);612 CollectVmRssDispatch(results, roundNum);
550 613 
614+ CollectNodeFreeMemory(roundNum);
615+ 
551 decltype(vmRssHandlers_) handlerCopy;616 decltype(vmRssHandlers_) handlerCopy;
552 std::shared_lock<std::shared_mutex> lock(vmRssHandlersMutex_);617 std::shared_lock<std::shared_mutex> lock(vmRssHandlersMutex_);
553 handlerCopy = vmRssHandlers_;618 handlerCopy = vmRssHandlers_;
@@ -581,6 +646,66 @@ void ProcessMemPidCollect::CollectVmRssDispatch(PidCollectInfoMap& results, uint
581 CollectVmRss(results, roundNum);646 CollectVmRss(results, roundNum);
582}647}
583 648 
649+void ProcessMemPidCollect::CollectNodeFreeMemory(uint64_t roundNum)
650+{
651+ uint64_t localFreeKb = 0;
652+ uint64_t remoteUsedKb = 0;
653+ bool foundAny = false;
654+ for (uint32_t numaId = 0; numaId < kMaxNumaNum; ++numaId) {
655+ auto isRemote = ReadNumaRemoteAttr(numaId);
656+ if (!isRemote.has_value()) {
657+ continue; // 跳过缺失节点, 兼容非连续编号
658+ }
659+ if (*isRemote) {
660+ auto usedKb = ReadNumaMemUsedKb(numaId);
661+ if (!usedKb.has_value()) {
662+ UBSE_LOG_WARN << "[process_mem] collect round=" << roundNum << " read remote numa=" << numaId
663+ << " MemUsed failed, clear snapshot";
664+ std::lock_guard<std::mutex> lock(numaSnapshotMutex_);
665+ localNumaFreeKbSnapshot_ = std::nullopt;
666+ remoteNumaUsedKbSnapshot_ = std::nullopt;
667+ return;
668+ }
669+ remoteUsedKb += *usedKb;
670+ continue;
671+ }
672+ auto freeKb = ReadNumaMemFreeKb(numaId);
673+ if (!freeKb.has_value()) {
674+ UBSE_LOG_WARN << "[process_mem] collect round=" << roundNum << " read local numa=" << numaId
675+ << " MemFree failed, clear snapshot";
676+ std::lock_guard<std::mutex> lock(numaSnapshotMutex_);
677+ localNumaFreeKbSnapshot_ = std::nullopt;
678+ remoteNumaUsedKbSnapshot_ = std::nullopt;
679+ return;
680+ }
681+ localFreeKb += *freeKb;
682+ foundAny = true;
683+ }
684+ if (!foundAny) {
685+ std::lock_guard<std::mutex> lock(numaSnapshotMutex_);
686+ localNumaFreeKbSnapshot_ = std::nullopt;
687+ remoteNumaUsedKbSnapshot_ = std::nullopt;
688+ return;
689+ }
690+ std::lock_guard<std::mutex> lock(numaSnapshotMutex_);
691+ localNumaFreeKbSnapshot_ = localFreeKb;
692+ remoteNumaUsedKbSnapshot_ = remoteUsedKb;
693+ UBSE_LOG_DEBUG << "[process_mem] collect round=" << roundNum << " local numa free=" << localFreeKb
694+ << "kB remote numa used=" << remoteUsedKb << "kB";
695+}
696+ 
697+std::optional<uint64_t> ProcessMemPidCollect::GetLocalNumaFreeKb()
698+{
699+ std::lock_guard<std::mutex> lock(numaSnapshotMutex_);
700+ return localNumaFreeKbSnapshot_;
701+}
702+ 
703+std::optional<uint64_t> ProcessMemPidCollect::GetRemoteNumaUsedKb()
704+{
705+ std::lock_guard<std::mutex> lock(numaSnapshotMutex_);
706+ return remoteNumaUsedKbSnapshot_;
707+}
708+ 
584uint32_t ProcessMemPidCollect::RegisterVmRssCollectHandler(const std::string& name, VmRssCollectHandler handler)709uint32_t ProcessMemPidCollect::RegisterVmRssCollectHandler(const std::string& name, VmRssCollectHandler handler)
585{710{
586 std::unique_lock<std::shared_mutex> lock(vmRssHandlersMutex_);711 std::unique_lock<std::shared_mutex> lock(vmRssHandlersMutex_);
@@ -17,6 +17,7 @@
17#include <chrono>17#include <chrono>
18#include <cstdint>18#include <cstdint>
19#include <functional>19#include <functional>
20+#include <mutex>
20#include <optional>21#include <optional>
21#include <set>22#include <set>
22#include <shared_mutex>23#include <shared_mutex>
@@ -59,6 +60,10 @@ public:
59 60 
60 uint32_t CollectProcessNumaMemDistribution(pid_t pid, std::unordered_map<uint32_t, size_t>& numaMemDistribution);61 uint32_t CollectProcessNumaMemDistribution(pid_t pid, std::unordered_map<uint32_t, size_t>& numaMemDistribution);
61 62 
63+ std::optional<uint64_t> GetLocalNumaFreeKb();
64+ 
65+ std::optional<uint64_t> GetRemoteNumaUsedKb();
66+ 
62#ifdef UB_ENVIRONMENT67#ifdef UB_ENVIRONMENT
63 void SetCollectVmRssOverride(std::function<void(PidCollectInfoMap&, uint64_t)> fn);68 void SetCollectVmRssOverride(std::function<void(PidCollectInfoMap&, uint64_t)> fn);
64#endif69#endif
@@ -78,6 +83,8 @@ private:
78 83 
79 void CollectChildProcesses(uint64_t roundNum, const std::set<pid_t>& curPids);84 void CollectChildProcesses(uint64_t roundNum, const std::set<pid_t>& curPids);
80 85 
86+ void CollectNodeFreeMemory(uint64_t roundNum);
87+ 
81 std::set<pid_t> lastPidSet_{};88 std::set<pid_t> lastPidSet_{};
82 89 
83 ubse::task_executor::UbseTaskExecutorPtr collectExecutor_{};90 ubse::task_executor::UbseTaskExecutorPtr collectExecutor_{};
@@ -93,6 +100,10 @@ private:
93 std::unordered_map<std::string, VmRssCollectHandler> vmRssHandlers_{};100 std::unordered_map<std::string, VmRssCollectHandler> vmRssHandlers_{};
94 std::shared_mutex vmRssHandlersMutex_{};101 std::shared_mutex vmRssHandlersMutex_{};
95 102 
103+ std::optional<uint64_t> localNumaFreeKbSnapshot_{};
104+ std::optional<uint64_t> remoteNumaUsedKbSnapshot_{};
105+ std::mutex numaSnapshotMutex_{};
106+ 
96#ifdef UB_ENVIRONMENT107#ifdef UB_ENVIRONMENT
97 std::function<void(PidCollectInfoMap&, uint64_t)> collectVmRssOverride_{};108 std::function<void(PidCollectInfoMap&, uint64_t)> collectVmRssOverride_{};
98#endif109#endif
@@ -43,7 +43,6 @@ constexpr uint64_t BYTES_PER_GB = 1073741824;
43constexpr uint64_t BYTES_PER_MB = 1048576;43constexpr uint64_t BYTES_PER_MB = 1048576;
44constexpr uint64_t MB_128 = 128;44constexpr uint64_t MB_128 = 128;
45constexpr uint64_t RETURN_HEADROOM_BYTES = 5 * BYTES_PER_GB;45constexpr uint64_t RETURN_HEADROOM_BYTES = 5 * BYTES_PER_GB;
46-constexpr uint64_t NORMAL_POLL_INTERVAL_MS = 1000;
47constexpr uint32_t kBorrowHighWatermark = 100;46constexpr uint32_t kBorrowHighWatermark = 100;
48constexpr uint32_t kMaxReturnRetry = 100;47constexpr uint32_t kMaxReturnRetry = 100;
49constexpr int64_t kEmergencyBroadcastIntervalMs = 2000;48constexpr int64_t kEmergencyBroadcastIntervalMs = 2000;
@@ -271,7 +270,8 @@ void ProcessMemPidDecision::LoadConfig()
271 const std::string samePlaneKey = "borrow.must_same_plane";270 const std::string samePlaneKey = "borrow.must_same_plane";
272 bool mustSamePlane = false;271 bool mustSamePlane = false;
273 ubse::config::UbseGetBool(section, samePlaneKey, mustSamePlane);272 ubse::config::UbseGetBool(section, samePlaneKey, mustSamePlane);
274- // 配置语义: true=必须同平面(严格过滤); UbseMemBorrower.samePlanePrefer 语义相反: true=软优先273+ // Config semantics: true=must be same plane (strict filtering);
274+ // UbseMemBorrower.samePlanePrefer is the opposite: true=soft preference
275 samePlanePrefer_ = !mustSamePlane;275 samePlanePrefer_ = !mustSamePlane;
276}276}
277 277 
@@ -394,8 +394,8 @@ void ProcessMemPidDecision::RunBorrowRound(uint64_t roundNum)
394 }394 }
395 395 
396 uint64_t rawShortage = shortage;396 uint64_t rawShortage = shortage;
397- uint64_t pendingBorrow = GetPendingBorrowTotal();397+ uint64_t pendingMigrate = GetPendingMigrateTotal();
398- shortage = (shortage > pendingBorrow) ? (shortage - pendingBorrow) : 0;398+ shortage = (shortage > pendingMigrate) ? (shortage - pendingMigrate) : 0;
399 399 
400 uint64_t initialShortage = shortage;400 uint64_t initialShortage = shortage;
401 401 
@@ -405,7 +405,7 @@ void ProcessMemPidDecision::RunBorrowRound(uint64_t roundNum)
405 auto candidates = BuildCandidates(roundNum);405 auto candidates = BuildCandidates(roundNum);
406 if (candidates.empty()) {406 if (candidates.empty()) {
407 UBSE_LOG_INFO << "[process_mem] borrow round=" << roundNum << " shortage_gb=" << BytesToGb(rawShortage)407 UBSE_LOG_INFO << "[process_mem] borrow round=" << roundNum << " shortage_gb=" << BytesToGb(rawShortage)
408- << " pending_gb=" << BytesToGb(pendingBorrow)408+ << " pending_migrate_gb=" << BytesToGb(pendingMigrate)
409 << " borrowed_gb=0 slots=0 remaining_shortage_gb=" << BytesToGb(shortage)409 << " borrowed_gb=0 slots=0 remaining_shortage_gb=" << BytesToGb(shortage)
410 << " dur_ms=0 (no candidates)";410 << " dur_ms=0 (no candidates)";
411 return;411 return;
@@ -418,7 +418,7 @@ void ProcessMemPidDecision::RunBorrowRound(uint64_t roundNum)
418 uint64_t borrowed = (shortage <= initialShortage) ? (initialShortage - shortage) : 0;418 uint64_t borrowed = (shortage <= initialShortage) ? (initialShortage - shortage) : 0;
419 419 
420 UBSE_LOG_INFO << "[process_mem] borrow round=" << roundNum << " shortage_gb=" << BytesToGb(rawShortage)420 UBSE_LOG_INFO << "[process_mem] borrow round=" << roundNum << " shortage_gb=" << BytesToGb(rawShortage)
421- << " pending_gb=" << BytesToGb(pendingBorrow) << " borrowed_gb=" << BytesToGb(borrowed)421+ << " pending_migrate_gb=" << BytesToGb(pendingMigrate) << " borrowed_gb=" << BytesToGb(borrowed)
422 << " remaining_shortage_gb=" << BytesToGb(shortage) << " dur_ms=" << durMs;422 << " remaining_shortage_gb=" << BytesToGb(shortage) << " dur_ms=" << durMs;
423}423}
424 424 
@@ -450,7 +450,7 @@ bool ProcessMemPidDecision::CheckNodeFreeMemory(uint64_t& outShortage)
450 auto freeBytes = GetNodeFreeBytes();450 auto freeBytes = GetNodeFreeBytes();
451 if (!freeBytes.has_value()) {451 if (!freeBytes.has_value()) {
452 UBSE_LOG_WARN << "[process_mem] borrow round=" << roundNumber_.load()452 UBSE_LOG_WARN << "[process_mem] borrow round=" << roundNumber_.load()
453- << " read memAvailable failed, skip round decision";453+ << " read local numa free failed, skip round decision";
454 return false;454 return false;
455 }455 }
456 uint64_t totalFree = *freeBytes;456 uint64_t totalFree = *freeBytes;
@@ -471,22 +471,36 @@ bool ProcessMemPidDecision::CheckNodeFreeMemory(uint64_t& outShortage)
471 return true;471 return true;
472}472}
473 473 
474-uint64_t ProcessMemPidDecision::GetPendingBorrowTotal() const474+uint64_t ProcessMemPidDecision::GetPendingMigrateTotal() const
475{475{
476- uint64_t total = 0;476+ // 预期远端总占用 = 借用账本中已下发的迁移量, 归还侧暂不参与扣减
477+ uint64_t expected = 0;
477 auto snapshot = ProcessMemPidInfoManager::GetInstance().GetManagedPidCacheSnapshot();478 auto snapshot = ProcessMemPidInfoManager::GetInstance().GetManagedPidCacheSnapshot();
478 for (const auto& [pid, entry] : snapshot) {479 for (const auto& [pid, entry] : snapshot) {
479 for (const auto& s : entry.borrow.slots) {480 for (const auto& s : entry.borrow.slots) {
480- if (s.status == def::BorrowSlotStatus::BORROWING) {481+ if (s.status == def::BorrowSlotStatus::BORROWING || s.status == def::BorrowSlotStatus::COMPLETED) {
481- total += s.migratedBytes;482+ expected += s.migratedBytes;
482 }483 }
483 }484 }
484 }485 }
485- return total;486+ // 真实远端占用来自采集快照; 两者差值即为在途迁移量(smap 尚未落地的部分)
487+ auto usedKb = ReadRemoteNumaUsedKb();
488+ if (!usedKb.has_value()) {
489+ return 0;
490+ }
491+ uint64_t actual = *usedKb * 1024;
492+ return (expected > actual) ? (expected - actual) : 0;
493+}
494+ 
495+uint64_t ProcessMemPidDecision::GetUbseBlockSizeBytes()
496+{
497+ auto curNodeInfo = ubse::nodeController::UbseNodeController::GetInstance().GetCurNode();
498+ uint64_t blockSizeMb = (curNodeInfo.blockSize == 0) ? MB_128 : curNodeInfo.blockSize;
499+ return blockSizeMb * BYTES_PER_MB;
486}500}
487 501 
488CandidateSkip AppendBorrowCandidate(pid_t pid, const def::ManagedPidEntry& entry,502CandidateSkip AppendBorrowCandidate(pid_t pid, const def::ManagedPidEntry& entry,
489- std::vector<def::BorrowCandidate>& candidates)503+ std::vector<def::BorrowCandidate>& candidates, uint64_t blockSizeBytes)
490{504{
491 if (entry.maxMemory == 0 || entry.remoteRatio <= 0.0) {505 if (entry.maxMemory == 0 || entry.remoteRatio <= 0.0) {
492 return CandidateSkip::CAN_MIGRATE;506 return CandidateSkip::CAN_MIGRATE;
@@ -503,6 +517,8 @@ CandidateSkip AppendBorrowCandidate(pid_t pid, const def::ManagedPidEntry& entry
503 uint64_t targetRemote = static_cast<uint64_t>(entry.vmRss * entry.remoteRatio);517 uint64_t targetRemote = static_cast<uint64_t>(entry.vmRss * entry.remoteRatio);
504 uint64_t canMigrate = (targetRemote > entry.borrow.currentRemote) ? (targetRemote - entry.borrow.currentRemote) : 0;518 uint64_t canMigrate = (targetRemote > entry.borrow.currentRemote) ? (targetRemote - entry.borrow.currentRemote) : 0;
505 canMigrate = (canMigrate > pendingBorrow) ? (canMigrate - pendingBorrow) : 0;519 canMigrate = (canMigrate > pendingBorrow) ? (canMigrate - pendingBorrow) : 0;
520+ // 分配量按 ubse blockSize 粒度向下取整; ==0 保护用于满足 G.INT.03 静态检查(检查器不做跨函数分析)
521+ canMigrate = (blockSizeBytes == 0) ? canMigrate : (canMigrate / blockSizeBytes) * blockSizeBytes;
506 if (canMigrate == 0) {522 if (canMigrate == 0) {
507 return CandidateSkip::CAN_MIGRATE;523 return CandidateSkip::CAN_MIGRATE;
508 }524 }
@@ -537,9 +553,10 @@ std::vector<def::BorrowCandidate> ProcessMemPidDecision::BuildCandidates(uint64_
537 553 
538 std::vector<def::BorrowCandidate> candidates;554 std::vector<def::BorrowCandidate> candidates;
539 size_t skipCanMigrate = 0;555 size_t skipCanMigrate = 0;
556+ uint64_t blockSizeBytes = GetUbseBlockSizeBytes();
540 557 
541 for (const auto& [pid, entry] : cacheSnapshot) {558 for (const auto& [pid, entry] : cacheSnapshot) {
542- auto skip = AppendBorrowCandidate(pid, entry, candidates);559+ auto skip = AppendBorrowCandidate(pid, entry, candidates, blockSizeBytes);
543 if (skip == CandidateSkip::CAN_MIGRATE) {560 if (skip == CandidateSkip::CAN_MIGRATE) {
544 ++skipCanMigrate;561 ++skipCanMigrate;
545 }562 }
@@ -571,7 +588,7 @@ std::vector<def::BorrowCandidate> ProcessMemPidDecision::BuildCandidates(uint64_
571 588 
572 UBSE_LOG_DEBUG << "[process_mem] borrow round=" << roundNum589 UBSE_LOG_DEBUG << "[process_mem] borrow round=" << roundNum
573 << " step=candidates total_managed=" << cacheSnapshot.size() << " eligible=" << candidates.size()590 << " step=candidates total_managed=" << cacheSnapshot.size() << " eligible=" << candidates.size()
574- << " skip_canmigrate=" << skipCanMigrate;591+ << " skip_canmigrate=" << skipCanMigrate << " block_size_mb=" << (blockSizeBytes / BYTES_PER_MB);
575 592 
576 LogBorrowCandidates(roundNum, candidates);593 LogBorrowCandidates(roundNum, candidates);
577 return candidates;594 return candidates;
@@ -611,13 +628,19 @@ void ProcessMemPidDecision::ExecuteBorrowRound(const std::vector<def::BorrowCand
611 uint64_t roundNum, bool emergency)628 uint64_t roundNum, bool emergency)
612{629{
613 auto& infoMgr = ProcessMemPidInfoManager::GetInstance();630 auto& infoMgr = ProcessMemPidInfoManager::GetInstance();
631+ uint64_t blockSizeBytes = GetUbseBlockSizeBytes();
wangwenlong7
wangwenlong7wangwenlong716 天前

检视意见ID: S3-04

[S3-04] ExecuteBorrowRound 与 AppendBorrowCandidate 中 blockSizeBytes == 0 分支为死代码

  • 严重程度: S3 提示
  • 问题类型: 可维护性
  • 文件: src/addons/process_mem/process_mem_pid_decision.cpp:631
  • 问题简述: GetUbseBlockSizeBytes() 已保证返回值不小于 128MB,下游 blockSizeBytes == 0 判断永远为假

详细描述: GetUbseBlockSizeBytescurNodeInfo.blockSize == 0 时回退到 MB_128,而 UbseNodeInfo::blockSize 默认值为 128(见 src/include/ubse_node_controller.h:201)。因此 GetUbseBlockSizeBytes() 永远返回不小于 128MB 的值,下游两处 (blockSizeBytes == 0) ? amount : ... 三元判断永远走 : 后分支,构成死代码,让读者误以为 0 是合法输入。建议直接删除该三元判断以提升可读性,或在 GetUbseBlockSizeBytes 处显式断言非 0。

// 原始代码
uint64_t amount = std::min(candidate.canMigrate, shortage);
amount = (blockSizeBytes == 0) ? amount : ((amount + blockSizeBytes - 1) / blockSizeBytes) * blockSizeBytes;
if (amount == 0) {
    break;
}

修改方案: GetUbseBlockSizeBytes 已保证非 0,直接做向上取整即可,删除冗余三元判断。

// 修改后代码
uint64_t amount = std::min(candidate.canMigrate, shortage);
amount = ((amount + blockSizeBytes - 1) / blockSizeBytes) * blockSizeBytes;
if (amount == 0) {
    break;
}
likedislike
614 632 
615 for (const auto& candidate : candidates) {633 for (const auto& candidate : candidates) {
616 if (shortage == 0) {634 if (shortage == 0) {
617 break;635 break;
618 }636 }
619 637 
638+ // 向上取整到 blockSize: 缺口不足一个block时仍按整块迁移, 只要pid内存够就多迁一部分, shortage允许多减;
620 uint64_t amount = std::min(candidate.canMigrate, shortage);639 uint64_t amount = std::min(candidate.canMigrate, shortage);
640+ amount = (blockSizeBytes == 0) ? amount : ((amount + blockSizeBytes - 1) / blockSizeBytes) * blockSizeBytes;
641+ if (amount == 0) {
642+ break;
643+ }
621 644 
622 int srcNumaId = CollectSrcNuma(candidate.pid, roundNum);645 int srcNumaId = CollectSrcNuma(candidate.pid, roundNum);
623 646 
@@ -642,7 +665,7 @@ void ProcessMemPidDecision::ExecuteBorrowRound(const std::vector<def::BorrowCand
642 TraceContext::Clear();665 TraceContext::Clear();
643 });666 });
644 667 
645- shortage -= amount;668+ shortage = (amount >= shortage) ? 0 : (shortage - amount);
646 }669 }
647}670}
648 671 
@@ -689,8 +712,8 @@ def::AtomicMigrateResult ProcessMemPidDecision::CommitBorrowAndMigrate(
689 return;712 return;
690 }713 }
691 }714 }
692- BuildMigrateTargets(newBorrow, increments, numaTargets);715+ BuildMigrateTargets(newBorrow, increments, numaTargets, pid, debtId);
693- if (numaTargets.empty() || RmrsMigrateToNumas(pid, numaTargets) != 0) {716+ if (numaTargets.empty() || RmrsMigrateToNumas(pid, debtId, numaTargets) != 0) {
694 result = def::AtomicMigrateResult::kFail;717 result = def::AtomicMigrateResult::kFail;
695 return;718 return;
696 }719 }
@@ -811,7 +834,8 @@ bool ProcessMemPidDecision::CreateNumaDebt(pid_t pid, uint64_t need, int srcNuma
811 834 
812void ProcessMemPidDecision::BuildMigrateTargets(const def::BorrowState& borrow,835void ProcessMemPidDecision::BuildMigrateTargets(const def::BorrowState& borrow,
813 const std::map<int, uint64_t>& increments,836 const std::map<int, uint64_t>& increments,
814- std::vector<std::pair<int, uint64_t>>& numaTargets)837+ std::vector<std::pair<int, uint64_t>>& numaTargets, pid_t pid,
838+ const std::string& debtId)
815{839{
816 numaTargets.clear();840 numaTargets.clear();
817 constexpr uint64_t pageSizeBytes = 4 * 1024;841 constexpr uint64_t pageSizeBytes = 4 * 1024;
@@ -829,7 +853,8 @@ void ProcessMemPidDecision::BuildMigrateTargets(const def::BorrowState& borrow,
829 }853 }
830 numaTargets.emplace_back(numaId, alignedBytes);854 numaTargets.emplace_back(numaId, alignedBytes);
831 auto incIt = increments.find(numaId);855 auto incIt = increments.find(numaId);
832- UBSE_LOG_INFO << "[process_mem] numa_target_migrate numa=" << numaId << " target_gb=" << BytesToGbDouble(target)856+ UBSE_LOG_INFO << "[process_mem] numa_target_migrate pid=" << pid << " debt_id=" << debtId << " numa=" << numaId
857+ << " target_gb=" << BytesToGbDouble(target)
833 << " increment_gb=" << BytesToGbDouble(incIt != increments.end() ? incIt->second : 0);858 << " increment_gb=" << BytesToGbDouble(incIt != increments.end() ? incIt->second : 0);
834 }859 }
835}860}
@@ -879,15 +904,17 @@ void ProcessMemPidDecision::AsyncBorrowAndMigrate(const std::string& debtId, pid
879 << " numa_target_migrate";904 << " numa_target_migrate";
880}905}
881 906 
882-int ProcessMemPidDecision::RmrsMigrateToNumas(pid_t pid, const std::vector<std::pair<int, uint64_t>>& numaTargets)907+int ProcessMemPidDecision::RmrsMigrateToNumas(pid_t pid, const std::string& debtId,
908+ const std::vector<std::pair<int, uint64_t>>& numaTargets)
883{909{
884 std::vector<mempooling::smap::MigrateOutPayload> payloads{};910 std::vector<mempooling::smap::MigrateOutPayload> payloads{};
885 mempooling::smap::MigrateOutPayload payload{};911 mempooling::smap::MigrateOutPayload payload{};
886 constexpr size_t kMaxInner = static_cast<size_t>(mempooling::smap::REMOTE_NUMA_NUM);912 constexpr size_t kMaxInner = static_cast<size_t>(mempooling::smap::REMOTE_NUMA_NUM);
887 size_t innerCount = std::min(numaTargets.size(), kMaxInner);913 size_t innerCount = std::min(numaTargets.size(), kMaxInner);
888 if (numaTargets.size() > kMaxInner) {914 if (numaTargets.size() > kMaxInner) {
889- UBSE_LOG_WARN << "[process_mem] borrow pid=" << pid << " numa_targets=" << numaTargets.size()915+ UBSE_LOG_WARN << "[process_mem] borrow pid=" << pid << " debt_id=" << debtId
890- << " exceed inner capacity " << kMaxInner << ", truncated";916+ << " numa_targets=" << numaTargets.size() << " exceed inner capacity " << kMaxInner
917+ << ", truncated";
891 }918 }
892 for (size_t i = 0; i < innerCount; ++i) {919 for (size_t i = 0; i < innerCount; ++i) {
893 mempooling::smap::MigrateOutPayloadInner inner{};920 mempooling::smap::MigrateOutPayloadInner inner{};
@@ -895,14 +922,16 @@ int ProcessMemPidDecision::RmrsMigrateToNumas(pid_t pid, const std::vector<std::
895 inner.memSize = numaTargets[i].second / 1024; // smap 期望 KB, 输入为字节922 inner.memSize = numaTargets[i].second / 1024; // smap 期望 KB, 输入为字节
896 inner.destNid = numaTargets[i].first;923 inner.destNid = numaTargets[i].first;
897 payload.inner[i] = inner;924 payload.inner[i] = inner;
898- UBSE_LOG_INFO << "[process_mem] migrate_out_dispatch pid=" << pid << " dest_numa=" << inner.destNid925+ UBSE_LOG_INFO << "[process_mem] migrate_out_dispatch pid=" << pid << " debt_id=" << debtId
899- << " mem_size_kb=" << inner.memSize << " src_bytes=" << numaTargets[i].second;926+ << " dest_numa=" << inner.destNid << " mem_size_kb=" << inner.memSize
927+ << " src_bytes=" << numaTargets[i].second;
900 }928 }
901 payload.count = static_cast<int>(innerCount);929 payload.count = static_cast<int>(innerCount);
902 payload.pid = pid;930 payload.pid = pid;
903 payloads.push_back(payload);931 payloads.push_back(payload);
904 if (!pid::bridge::ProcessMemPidBridge::rmrsMigrateOut) {932 if (!pid::bridge::ProcessMemPidBridge::rmrsMigrateOut) {
905- UBSE_LOG_ERROR << "[process_mem] borrow pid=" << pid << " rmrsMigrateOut not loaded, skip migrate";933+ UBSE_LOG_ERROR << "[process_mem] borrow pid=" << pid << " debt_id=" << debtId
934+ << " rmrsMigrateOut not loaded, skip migrate";
906 return -1;935 return -1;
907 }936 }
908 return pid::bridge::ProcessMemPidBridge::rmrsMigrateOut(payloads, 0);937 return pid::bridge::ProcessMemPidBridge::rmrsMigrateOut(payloads, 0);
@@ -976,32 +1005,27 @@ bool ProcessMemPidDecision::CheckPidTimeoutSlots(pid_t pid, def::BorrowState& bo
976 1005 
977std::optional<uint64_t> ProcessMemPidDecision::GetNodeFreeBytes()1006std::optional<uint64_t> ProcessMemPidDecision::GetNodeFreeBytes()
978{1007{
979- auto memAvailableKb = ReadMemAvailableKb();1008+ auto freeKb = ReadLocalNumaFreeKb();
980- if (!memAvailableKb.has_value()) {1009+ if (!freeKb.has_value()) {
981 return std::nullopt;1010 return std::nullopt;
982 }1011 }
983- return *memAvailableKb * 1024;1012+ return *freeKb * 1024;
984}1013}
985 1014 
986-std::optional<uint64_t> ProcessMemPidDecision::ReadMemAvailableKb()1015+std::optional<uint64_t> ProcessMemPidDecision::ReadLocalNumaFreeKb()
987{1016{
988- if (memAvailableReader) {1017+ if (localNumaFreeKbReader) {
989- return memAvailableReader();1018+ return localNumaFreeKbReader();
990 }1019 }
991- std::ifstream meminfo("/proc/meminfo");1020+ return collect::ProcessMemPidCollect::GetInstance().GetLocalNumaFreeKb();
992- std::string line;1021+}
993- while (std::getline(meminfo, line)) {1022+ 
994- if (line.rfind("MemAvailable:", 0) == 0) {1023+std::optional<uint64_t> ProcessMemPidDecision::ReadRemoteNumaUsedKb() const
995- std::istringstream iss(line);1024+{
996- std::string key;1025+ if (remoteNumaUsedKbReader) {
997- uint64_t kb = 0;1026+ return remoteNumaUsedKbReader();
998- if (iss >> key >> kb) {
999- return kb;
1000- }
1001- return std::nullopt;
1002- }
1003 }1027 }
1004- return std::nullopt;1028+ return collect::ProcessMemPidCollect::GetInstance().GetRemoteNumaUsedKb();
1005}1029}
1006 1030 
1007void ProcessMemPidDecision::BroadcastPassiveReturn(uint64_t roundNum, uint64_t nodeFree, bool emergency)1031void ProcessMemPidDecision::BroadcastPassiveReturn(uint64_t roundNum, uint64_t nodeFree, bool emergency)
@@ -1114,8 +1138,8 @@ void ProcessMemPidDecision::RunReturnDebt(pid_t pid, def::ReturnRequestItem item
1114 }1138 }
1115 1139 
1116 ProcessMemPidInfoManager::GetInstance().UpdateManagedPidSlotReturnStatus(pid, item.name, def::ReturnStatus::NONE);1140 ProcessMemPidInfoManager::GetInstance().UpdateManagedPidSlotReturnStatus(pid, item.name, def::ReturnStatus::NONE);
1117- UBSE_LOG_DEBUG << "[process_mem] return " << ReturnSceneToString(scene) << " debt_id=" << item.name1141+ UBSE_LOG_DEBUG << "[process_mem] return " << ReturnSceneToString(scene) << " pid=" << pid
1118- << " failed (ret=" << ret << "), retry later";1142+ << " debt_id=" << item.name << " failed (ret=" << ret << "), retry later";
1119 uint32_t retryCount = 0;1143 uint32_t retryCount = 0;
1120 RetryReturnEnqueue(retryCount, [this, pid, item, scene]() { return EnqueueReturnDebt(pid, item, scene); });1144 RetryReturnEnqueue(retryCount, [this, pid, item, scene]() { return EnqueueReturnDebt(pid, item, scene); });
1121}1145}
@@ -1126,11 +1150,12 @@ uint32_t ProcessMemPidDecision::DoReturnDebtOnce(pid_t pid, const def::ReturnReq
1126 if (scene == ReturnScene::TIMEOUT) {1150 if (scene == ReturnScene::TIMEOUT) {
1127 auto delRet = pid::bridge::ProcessMemPidBridge::MemoryReturn(item.name);1151 auto delRet = pid::bridge::ProcessMemPidBridge::MemoryReturn(item.name);
1128 if (delRet == UBSE_OK || delRet == UBSE_ERR_NOT_EXIST) {1152 if (delRet == UBSE_OK || delRet == UBSE_ERR_NOT_EXIST) {
1129- UBSE_LOG_INFO << "[process_mem] return timeout debt_id=" << item.name1153+ UBSE_LOG_INFO << "[process_mem] return timeout pid=" << pid << " debt_id=" << item.name
1130 << " amount_gb=" << BytesToGbDouble(item.size) << " ubse_return_ok ubse_ret=" << delRet;1154 << " amount_gb=" << BytesToGbDouble(item.size) << " ubse_return_ok ubse_ret=" << delRet;
1131 return UBSE_OK;1155 return UBSE_OK;
1132 }1156 }
1133- UBSE_LOG_WARN << "[process_mem] return timeout debt_id=" << item.name << " ubse delete failed ret=" << delRet;1157+ UBSE_LOG_WARN << "[process_mem] return timeout pid=" << pid << " debt_id=" << item.name
1158+ << " ubse delete failed ret=" << delRet;
1134 return delRet;1159 return delRet;
1135 }1160 }
1136 ProcessMemPidInfoManager::GetInstance().UpdateManagedPidSlotReturnStatus(pid, item.name,1161 ProcessMemPidInfoManager::GetInstance().UpdateManagedPidSlotReturnStatus(pid, item.name,
@@ -1142,8 +1167,8 @@ uint32_t ProcessMemPidDecision::DoReturnDebtOnce(pid_t pid, const def::ReturnReq
1142 }1167 }
1143 auto nodeFreeOpt = GetNodeFreeBytes();1168 auto nodeFreeOpt = GetNodeFreeBytes();
1144 if (!nodeFreeOpt.has_value()) {1169 if (!nodeFreeOpt.has_value()) {
1145- UBSE_LOG_WARN << "[process_mem] return " << ReturnSceneToString(scene) << " debt_id=" << item.name1170+ UBSE_LOG_WARN << "[process_mem] return " << ReturnSceneToString(scene) << " pid=" << pid
1146- << " read memAvailable failed, retry later";1171+ << " debt_id=" << item.name << " read local numa free failed, retry later";
1147 return UBSE_ERROR;1172 return UBSE_ERROR;
1148 }1173 }
1149 return ReturnOneDebt(item, *nodeFreeOpt);1174 return ReturnOneDebt(item, *nodeFreeOpt);
@@ -1232,7 +1257,7 @@ bool ProcessMemPidDecision::ReturnOnePidDebt(pid_t pid, const ubse::mem::control
1232 auto ret = ReturnDebtUnified(pid, item, ReturnScene::EXITED);1257 auto ret = ReturnDebtUnified(pid, item, ReturnScene::EXITED);
1233 if (ret != UBSE_OK) {1258 if (ret != UBSE_OK) {
1234 ++failCount;1259 ++failCount;
1235- UBSE_LOG_WARN << "[process_mem] return exited debt_id=" << debt.name << " failed ret=" << ret;1260+ UBSE_LOG_WARN << "[process_mem] return exited pid=" << pid << " debt_id=" << debt.name << " failed ret=" << ret;
1236 return false;1261 return false;
1237 }1262 }
1238 ++freedCount;1263 ++freedCount;
@@ -1541,6 +1566,10 @@ uint32_t ProcessMemPidDecision::ReconcileLedgerWithCache()
1541 if (slot.returnStatus == def::ReturnStatus::RETURNING) {1566 if (slot.returnStatus == def::ReturnStatus::RETURNING) {
1542 continue; // 归还流程收尾,避免并发双删1567 continue; // 归还流程收尾,避免并发双删
1543 }1568 }
1569+ // 借用中: 债务可能尚未创建/账本尚未可见, 误删会中止借入, 由超时检查兜底清理
1570+ if (slot.status == def::BorrowSlotStatus::BORROWING) {
1571+ continue;
1572+ }
1544 if (ledgerNames.count(slot.debtId) == 0) {1573 if (ledgerNames.count(slot.debtId) == 0) {
1545 vanished.push_back(slot.debtId);1574 vanished.push_back(slot.debtId);
1546 }1575 }
@@ -1624,8 +1653,8 @@ void ProcessMemPidDecision::ReconcileApplyChanges(const std::vector<pid_t>& affe
1624 continue;1653 continue;
1625 }1654 }
1626 std::vector<std::pair<int, uint64_t>> numaTargets;1655 std::vector<std::pair<int, uint64_t>> numaTargets;
1627- BuildMigrateTargets(it->second.borrow, {}, numaTargets);1656+ BuildMigrateTargets(it->second.borrow, {}, numaTargets, pid, "");
1628- int ret = RmrsMigrateToNumas(pid, numaTargets);1657+ int ret = RmrsMigrateToNumas(pid, "", numaTargets);
1629 UBSE_LOG_INFO << "[process_mem] reconcile: pid=" << pid << " migrate_reissued targets=" << numaTargets.size()1658 UBSE_LOG_INFO << "[process_mem] reconcile: pid=" << pid << " migrate_reissued targets=" << numaTargets.size()
1630 << " ret=" << ret;1659 << " ret=" << ret;
1631 }1660 }
@@ -1687,7 +1716,7 @@ uint32_t ProcessMemPidDecision::ReturnOneDebt(const def::ReturnRequestItem& item
1687 }1716 }
1688 }1717 }
1689 if (migrateBytes != item.size) {1718 if (migrateBytes != item.size) {
1690- UBSE_LOG_INFO << "[process_mem] return passive debt_id=" << item.name1719+ UBSE_LOG_INFO << "[process_mem] return passive pid=" << pid << " debt_id=" << item.name
1691 << " migrate_size_gb=" << BytesToGbDouble(migrateBytes)1720 << " migrate_size_gb=" << BytesToGbDouble(migrateBytes)
1692 << " debt_size_gb=" << BytesToGbDouble(item.size);1721 << " debt_size_gb=" << BytesToGbDouble(item.size);
1693 }1722 }
@@ -1699,32 +1728,34 @@ uint32_t ProcessMemPidDecision::ReturnDebtByUbse(pid_t pid, const def::ReturnReq
1699{1728{
1700 auto delRet = pid::bridge::ProcessMemPidBridge::MemoryReturn(item.name);1729 auto delRet = pid::bridge::ProcessMemPidBridge::MemoryReturn(item.name);
1701 if (delRet == UBSE_OK || delRet == UBSE_ERR_NOT_EXIST) {1730 if (delRet == UBSE_OK || delRet == UBSE_ERR_NOT_EXIST) {
1702- UBSE_LOG_INFO << "[process_mem] return debt_id=" << item.name << " amount_gb=" << BytesToGbDouble(item.size)1731+ UBSE_LOG_INFO << "[process_mem] return pid=" << pid << " debt_id=" << item.name
1703- << " ubse_return_ok ubse_ret=" << delRet;1732+ << " amount_gb=" << BytesToGbDouble(item.size) << " ubse_return_ok ubse_ret=" << delRet;
1704 ProcessMemPidInfoManager::GetInstance().UpdateManagedPidSlotReturned(pid, item.name, item.size);1733 ProcessMemPidInfoManager::GetInstance().UpdateManagedPidSlotReturned(pid, item.name, item.size);
1705 return UBSE_OK;1734 return UBSE_OK;
1706 }1735 }
1707- UBSE_LOG_WARN << "[process_mem] return debt_id=" << item.name << " ubse delete failed ret=" << delRet;1736+ UBSE_LOG_WARN << "[process_mem] return pid=" << pid << " debt_id=" << item.name
1737+ << " ubse delete failed ret=" << delRet;
1708 return delRet;1738 return delRet;
1709}1739}
1710 1740 
1711uint32_t ProcessMemPidDecision::ReturnDebtUnified(pid_t pid, const def::ReturnRequestItem& item, ReturnScene scene)1741uint32_t ProcessMemPidDecision::ReturnDebtUnified(pid_t pid, const def::ReturnRequestItem& item, ReturnScene scene)
1712{1742{
1713 if (!pid::bridge::ProcessMemPidBridge::rmrsFreeWithMigrate) {1743 if (!pid::bridge::ProcessMemPidBridge::rmrsFreeWithMigrate) {
1714- UBSE_LOG_WARN << "[process_mem] return " << ReturnSceneToString(scene) << " debt_id=" << item.name1744+ UBSE_LOG_WARN << "[process_mem] return " << ReturnSceneToString(scene) << " pid=" << pid
1715- << " rmrsFreeWithMigrate not loaded, fallback to ubse return";1745+ << " debt_id=" << item.name << " rmrsFreeWithMigrate not loaded, fallback to ubse return";
1716 return ReturnDebtByUbse(pid, item);1746 return ReturnDebtByUbse(pid, item);
1717 }1747 }
1718 // 优先 rmrsFree: 完整归还(借用块数据同 numa 搬迁到 spare + 释放块 + 删 ubse 债务)1748 // 优先 rmrsFree: 完整归还(借用块数据同 numa 搬迁到 spare + 释放块 + 删 ubse 债务)
1719 auto freeRet = pid::bridge::ProcessMemPidBridge::rmrsFreeWithMigrate(item.name);1749 auto freeRet = pid::bridge::ProcessMemPidBridge::rmrsFreeWithMigrate(item.name);
1720 if (freeRet == MEM_POOLING_HANDLING_FAULT) {1750 if (freeRet == MEM_POOLING_HANDLING_FAULT) {
1721 // 远端 numa 处于故障处理(reboot/BMC 下电/OS panic)中, 故障处理完成锁释放后可重试1751 // 远端 numa 处于故障处理(reboot/BMC 下电/OS panic)中, 故障处理完成锁释放后可重试
1722- UBSE_LOG_WARN << "[process_mem] return " << ReturnSceneToString(scene) << " debt_id=" << item.name1752+ UBSE_LOG_WARN << "[process_mem] return " << ReturnSceneToString(scene) << " pid=" << pid
1723- << " rmrs free blocked by fault handling ret=" << freeRet << ", retry later";1753+ << " debt_id=" << item.name << " rmrs free blocked by fault handling ret=" << freeRet
1754+ << ", retry later";
1724 return freeRet;1755 return freeRet;
1725 }1756 }
1726 // 其余错误码(含成功/进程删除/迁移失败等)统一 fallback 到 ubse 内存归还接口收尾(幂等)1757 // 其余错误码(含成功/进程删除/迁移失败等)统一 fallback 到 ubse 内存归还接口收尾(幂等)
1727- UBSE_LOG_INFO << "[process_mem] return " << ReturnSceneToString(scene) << " debt_id=" << item.name1758+ UBSE_LOG_INFO << "[process_mem] return " << ReturnSceneToString(scene) << " pid=" << pid << " debt_id=" << item.name
1728 << " rmrs_free_ret=" << freeRet << ", fallback to ubse return";1759 << " rmrs_free_ret=" << freeRet << ", fallback to ubse return";
1729 return ReturnDebtByUbse(pid, item);1760 return ReturnDebtByUbse(pid, item);
1730}1761}
@@ -1734,12 +1765,12 @@ uint32_t ProcessMemPidDecision::ReturnDebtToLocal(pid_t pid, const def::ReturnRe
1734{1765{
1735 auto backRet = ReturnDebtUnified(pid, item, scene);1766 auto backRet = ReturnDebtUnified(pid, item, scene);
1736 if (backRet != UBSE_OK) {1767 if (backRet != UBSE_OK) {
1737- UBSE_LOG_WARN << "[process_mem] return " << ReturnSceneToString(scene) << " debt_id=" << item.name1768+ UBSE_LOG_WARN << "[process_mem] return " << ReturnSceneToString(scene) << " pid=" << pid
1738- << " migrate failed ret=" << backRet;1769+ << " debt_id=" << item.name << " migrate failed ret=" << backRet;
1739 return backRet;1770 return backRet;
1740 }1771 }
1741 1772 
1742- UBSE_LOG_INFO << "[process_mem] return " << ReturnSceneToString(scene) << " debt_id=" << item.name1773+ UBSE_LOG_INFO << "[process_mem] return " << ReturnSceneToString(scene) << " pid=" << pid << " debt_id=" << item.name
1743 << " amount_gb=" << BytesToGbDouble(item.size) << " rmrs_free (本地充裕)";1774 << " amount_gb=" << BytesToGbDouble(item.size) << " rmrs_free (本地充裕)";
1744 nodeFree += item.size;1775 nodeFree += item.size;
1745 return UBSE_OK;1776 return UBSE_OK;
@@ -1813,12 +1844,12 @@ uint32_t ProcessMemPidDecision::MigrateDebtToReplacement(const def::ReturnReques
1813 int migrateRet = MigrateDebtRemoteToRemote(pid, ctx.oldRemoteNuma, newRemoteNuma, migrateBytes);1844 int migrateRet = MigrateDebtRemoteToRemote(pid, ctx.oldRemoteNuma, newRemoteNuma, migrateBytes);
1814 if (migrateRet != 0) {1845 if (migrateRet != 0) {
1815 auto delRet = pid::bridge::ProcessMemPidBridge::MemoryReturn(newDebtId);1846 auto delRet = pid::bridge::ProcessMemPidBridge::MemoryReturn(newDebtId);
1816- UBSE_LOG_WARN << "[process_mem] return passive debt_id=" << item.name1847+ UBSE_LOG_WARN << "[process_mem] return passive pid=" << pid << " debt_id=" << item.name
1817 << " remote_to_remote failed ret=" << migrateRet << " keep old lender"1848 << " remote_to_remote failed ret=" << migrateRet << " keep old lender"
1818 << " new_debt_id=" << newDebtId << " ubse_return ubse_ret=" << delRet;1849 << " new_debt_id=" << newDebtId << " ubse_return ubse_ret=" << delRet;
1819 return UBSE_ERROR;1850 return UBSE_ERROR;
1820 }1851 }
1821- UBSE_LOG_INFO << "[process_mem] return passive debt_id=" << item.name1852+ UBSE_LOG_INFO << "[process_mem] return passive pid=" << pid << " debt_id=" << item.name
1822 << " remote_to_remote migrated ok old_remote_numa=" << ctx.oldRemoteNuma1853 << " remote_to_remote migrated ok old_remote_numa=" << ctx.oldRemoteNuma
1823 << " new_remote_numa=" << newRemoteNuma << " new_debt_id=" << newDebtId1854 << " new_remote_numa=" << newRemoteNuma << " new_debt_id=" << newDebtId
1824 << " migrate_gb=" << BytesToGbDouble(migrateBytes);1855 << " migrate_gb=" << BytesToGbDouble(migrateBytes);
@@ -1992,56 +2023,33 @@ uint32_t ProcessMemPidDecision::OomPollOnce()
1992{2023{
1993 OomRearrangeMigrated();2024 OomRearrangeMigrated();
1994 uint64_t roundNum = oomRoundNumber_.fetch_add(1) + 1;2025 uint64_t roundNum = oomRoundNumber_.fetch_add(1) + 1;
1995- auto memAvailableOpt = ReadMemAvailableKb();
1996 auto nodeFreeOpt = GetNodeFreeBytes();2026 auto nodeFreeOpt = GetNodeFreeBytes();
1997- if (!memAvailableOpt.has_value() || !nodeFreeOpt.has_value()) {2027+ if (!nodeFreeOpt.has_value()) {
1998- UBSE_LOG_WARN << "[process_mem] oom detector: read memAvailable failed, skip round";2028+ UBSE_LOG_WARN << "[process_mem] oom detector oom_round=" << roundNum
1999- return NORMAL_POLL_INTERVAL_MS;2029+ << ": read local numa free failed, skip round";
2030+ return fastPollIntervalMs_;
2000 }2031 }
2001- uint64_t memAvailableBytes = *memAvailableOpt * 1024;
2002- uint64_t detectThresholdBytes = freeMemoryThresholdBytes_ * 3;
2003 uint64_t nodeFree = *nodeFreeOpt;2032 uint64_t nodeFree = *nodeFreeOpt;
2004 2033 
2005- if (memAvailableBytes < detectThresholdBytes) {2034+ UpdateOomFastWindow(roundNum, nodeFree);
2006- if (!oomFastPoll_) {
2007- oomFastWindowCount_ = 0;
2008- oomFastWindowMin_ = 0;
2009- }
2010- oomFastPoll_ = true;
2011- UBSE_LOG_INFO << "[process_mem] oom detector: fast poll enabled (MemAvailable="
2012- << BytesToGbDouble(memAvailableBytes) << "GB < detect=" << BytesToGbDouble(detectThresholdBytes)
2013- << "GB)";
2014 2035 
2015- UpdateOomFastWindow(nodeFree);2036+ if (nodeFree < emergencyThresholdBytes_) {
2016- 2037+ OomEmergencyBorrow(roundNum, nodeFree);
2017- if (memAvailableBytes < emergencyThresholdBytes_) {2038+ auto now = std::chrono::steady_clock::now();
2018- OomEmergencyBorrow(roundNum, nodeFree);2039+ auto sinceLast = std::chrono::duration_cast<std::chrono::milliseconds>(now - lastEmergencyBroadcast_).count();
2019- auto now = std::chrono::steady_clock::now();2040+ if (sinceLast >= kEmergencyBroadcastIntervalMs) {
2020- auto sinceLast =2041+ BroadcastPassiveReturn(roundNum, nodeFree, true);
2021- std::chrono::duration_cast<std::chrono::milliseconds>(now - lastEmergencyBroadcast_).count();2042+ lastEmergencyBroadcast_ = now;
2022- if (sinceLast >= kEmergencyBroadcastIntervalMs) {
2023- BroadcastPassiveReturn(roundNum, nodeFree, true);
2024- lastEmergencyBroadcast_ = now;
2025- }
2026 }2043 }
2027- } else {
2028- oomFastPoll_ = false;
2029- oomFastWindowCount_ = 0;
2030- oomFastWindowMin_ = 0;
2031- UBSE_LOG_INFO << "[process_mem] oom detector: fast poll disabled (MemAvailable="
2032- << BytesToGbDouble(memAvailableBytes) << "GB >= detect=" << BytesToGbDouble(detectThresholdBytes)
2033- << "GB)";
2034 }2044 }
2035 2045 
2036- UBSE_LOG_DEBUG << "[process_mem] oom detector heartbeat: MemAvailable=" << BytesToGbDouble(memAvailableBytes)2046+ UBSE_LOG_DEBUG << "[process_mem] oom detector heartbeat oom_round=" << roundNum
2037- << "GB detect=" << BytesToGbDouble(detectThresholdBytes)2047+ << " LocalNumaFree=" << BytesToGbDouble(nodeFree)
2038- << "GB emergency=" << BytesToGbDouble(emergencyThresholdBytes_)2048+ << "GB emergency=" << BytesToGbDouble(emergencyThresholdBytes_) << "GB";
2039- << "GB poll=" << (oomFastPoll_ ? "fast" : "normal");2049+ return fastPollIntervalMs_;
2040- 
2041- return oomFastPoll_ ? fastPollIntervalMs_ : NORMAL_POLL_INTERVAL_MS;
2042}2050}
2043 2051 
2044-void ProcessMemPidDecision::UpdateOomFastWindow(uint64_t nodeFree)2052+void ProcessMemPidDecision::UpdateOomFastWindow(uint64_t roundNum, uint64_t nodeFree)
2045{2053{
2046 if (oomFastWindowCount_ == 0) {2054 if (oomFastWindowCount_ == 0) {
2047 oomFastWindowMin_ = nodeFree;2055 oomFastWindowMin_ = nodeFree;
@@ -2050,8 +2058,8 @@ void ProcessMemPidDecision::UpdateOomFastWindow(uint64_t nodeFree)
2050 }2058 }
2051 ++oomFastWindowCount_;2059 ++oomFastWindowCount_;
2052 if (oomFastWindowCount_ >= observeCycles_) {2060 if (oomFastWindowCount_ >= observeCycles_) {
2053- UBSE_LOG_DEBUG << "[process_mem] oom active window settle: samples=" << oomFastWindowCount_2061+ UBSE_LOG_DEBUG << "[process_mem] oom active window settle oom_round=" << roundNum
2054- << " min_gb=" << BytesToGbDouble(oomFastWindowMin_)2062+ << " samples=" << oomFastWindowCount_ << " min_gb=" << BytesToGbDouble(oomFastWindowMin_)
2055 << " threshold_gb=" << BytesToGbDouble(freeMemoryThresholdBytes_);2063 << " threshold_gb=" << BytesToGbDouble(freeMemoryThresholdBytes_);
2056 if (oomFastWindowMin_ > freeMemoryThresholdBytes_) {2064 if (oomFastWindowMin_ > freeMemoryThresholdBytes_) {
2057 OomActiveReturn(oomFastWindowMin_);2065 OomActiveReturn(oomFastWindowMin_);
@@ -2067,8 +2075,8 @@ void ProcessMemPidDecision::OomEmergencyBorrow(uint64_t roundNum, uint64_t nodeF
2067 return;2075 return;
2068 }2076 }
2069 uint64_t shortage = freeMemoryThresholdBytes_ - nodeFree;2077 uint64_t shortage = freeMemoryThresholdBytes_ - nodeFree;
2070- uint64_t pendingBorrow = GetPendingBorrowTotal();2078+ uint64_t pendingMigrate = GetPendingMigrateTotal();
2071- shortage = (shortage > pendingBorrow) ? (shortage - pendingBorrow) : 0;2079+ shortage = (shortage > pendingMigrate) ? (shortage - pendingMigrate) : 0;
2072 2080 
2073 auto candidates = BuildCandidates(roundNum);2081 auto candidates = BuildCandidates(roundNum);
2074 if (candidates.empty()) {2082 if (candidates.empty()) {
@@ -72,7 +72,8 @@ public:
72 72 
73 uint32_t ReconcileLedgerWithCache();73 uint32_t ReconcileLedgerWithCache();
74 74 
75- inline static std::function<std::optional<uint64_t>()> memAvailableReader;75+ inline static std::function<std::optional<uint64_t>()> localNumaFreeKbReader;
76+ inline static std::function<std::optional<uint64_t>()> remoteNumaUsedKbReader;
76 77 
77private:78private:
78 uint32_t OnDecisionTimer();79 uint32_t OnDecisionTimer();
@@ -88,14 +89,16 @@ private:
88 89 
89 void CheckTimeouts(uint64_t roundNum);90 void CheckTimeouts(uint64_t roundNum);
90 91 
91- uint64_t GetPendingBorrowTotal() const;92+ uint64_t GetPendingMigrateTotal() const;
93+ 
94+ uint64_t GetUbseBlockSizeBytes();
92 95 
93 std::string RecordPendingBorrow(pid_t pid, uint64_t amount, int srcNumaId, uint64_t roundNum);96 std::string RecordPendingBorrow(pid_t pid, uint64_t amount, int srcNumaId, uint64_t roundNum);
94 97 
95 void AsyncBorrowAndMigrate(const std::string& debtId, pid_t pid, uint64_t amount, int srcNumaId, uint64_t roundNum);98 void AsyncBorrowAndMigrate(const std::string& debtId, pid_t pid, uint64_t amount, int srcNumaId, uint64_t roundNum);
96 99 
97 void BuildMigrateTargets(const def::BorrowState& borrow, const std::map<int, uint64_t>& increments,100 void BuildMigrateTargets(const def::BorrowState& borrow, const std::map<int, uint64_t>& increments,
98- std::vector<std::pair<int, uint64_t>>& numaTargets);101+ std::vector<std::pair<int, uint64_t>>& numaTargets, pid_t pid, const std::string& debtId);
99 102 
100 def::AtomicMigrateResult CommitBorrowAndMigrate(pid_t pid, const std::string& debtId,103 def::AtomicMigrateResult CommitBorrowAndMigrate(pid_t pid, const std::string& debtId,
101 const CreatedDebtInfo& created,104 const CreatedDebtInfo& created,
@@ -108,7 +111,9 @@ private:
108 111 
109 std::optional<uint64_t> GetNodeFreeBytes();112 std::optional<uint64_t> GetNodeFreeBytes();
110 113 
111- std::optional<uint64_t> ReadMemAvailableKb();114+ std::optional<uint64_t> ReadLocalNumaFreeKb();
115+ 
116+ std::optional<uint64_t> ReadRemoteNumaUsedKb() const;
112 117 
113 bool EnqueueReturnDebt(pid_t pid, const def::ReturnRequestItem& item, ReturnScene scene);118 bool EnqueueReturnDebt(pid_t pid, const def::ReturnRequestItem& item, ReturnScene scene);
114 119 
@@ -169,7 +174,8 @@ private:
169 ubse::mem::controller::UbseMemBorrower& borrower);174 ubse::mem::controller::UbseMemBorrower& borrower);
170 bool CreateNumaDebt(pid_t pid, uint64_t need, int srcNumaId, const std::string& debtId, uint64_t roundNum,175 bool CreateNumaDebt(pid_t pid, uint64_t need, int srcNumaId, const std::string& debtId, uint64_t roundNum,
171 CreatedDebtInfo& out);176 CreatedDebtInfo& out);
172- int RmrsMigrateToNumas(pid_t pid, const std::vector<std::pair<int, uint64_t>>& numaTargets);177+ int RmrsMigrateToNumas(pid_t pid, const std::string& debtId,
178+ const std::vector<std::pair<int, uint64_t>>& numaTargets);
173 179 
174 bool CheckPidTimeoutSlots(pid_t pid, def::BorrowState& borrow, uint64_t roundNum);180 bool CheckPidTimeoutSlots(pid_t pid, def::BorrowState& borrow, uint64_t roundNum);
175 uint32_t DoReturnDebtOnce(pid_t pid, const def::ReturnRequestItem& item, ReturnScene scene);181 uint32_t DoReturnDebtOnce(pid_t pid, const def::ReturnRequestItem& item, ReturnScene scene);
@@ -192,7 +198,7 @@ private:
192 uint32_t DeleteOldReturnDebt(const std::string& debtId);198 uint32_t DeleteOldReturnDebt(const std::string& debtId);
193 std::vector<std::string> BuildReplacementCandidates(const std::string& oldLenderNodeId);199 std::vector<std::string> BuildReplacementCandidates(const std::string& oldLenderNodeId);
194 200 
195- void UpdateOomFastWindow(uint64_t nodeFree);201+ void UpdateOomFastWindow(uint64_t roundNum, uint64_t nodeFree);
196 void CollectActiveReturnDebts(const std::map<pid_t, def::ManagedPidEntry>& snapshot,202 void CollectActiveReturnDebts(const std::map<pid_t, def::ManagedPidEntry>& snapshot,
197 std::vector<std::pair<pid_t, def::ReturnRequestItem>>& debts, uint64_t& totalRemote);203 std::vector<std::pair<pid_t, def::ReturnRequestItem>>& debts, uint64_t& totalRemote);
198 204 
@@ -223,7 +229,6 @@ private:
223 std::mutex oomMutex_{};229 std::mutex oomMutex_{};
224 std::condition_variable oomCv_{};230 std::condition_variable oomCv_{};
225 bool oomRunning_{false};231 bool oomRunning_{false};
226- bool oomFastPoll_{false};
227 uint32_t oomFastWindowCount_{0};232 uint32_t oomFastWindowCount_{0};
228 uint64_t oomFastWindowMin_{0};233 uint64_t oomFastWindowMin_{0};
229 std::chrono::steady_clock::time_point lastEmergencyBroadcast_{};234 std::chrono::steady_clock::time_point lastEmergencyBroadcast_{};
@@ -284,7 +284,7 @@ uint32_t ProcessMemPidInfoManager::ValidateProcMemTarget(const def::ProcessMemNe
284 if (IsRootPid(pid)) {284 if (IsRootPid(pid)) {
285 UBSE_LOG_ERROR << "SetProcMemConfig: PID " << pid285 UBSE_LOG_ERROR << "SetProcMemConfig: PID " << pid
286 << " is a root process, not allowed (filter_root_process=true)";286 << " is a root process, not allowed (filter_root_process=true)";
287- return UBSE_ERR_INVALID_ARG;287+ return UBSE_ERR_ACCESS_DENIED;
288 }288 }
289 289 
290 outStartTime = startTime;290 outStartTime = startTime;
@@ -73,9 +73,9 @@ UbseResult CollectNodeBaseInfo(UbseNodeInfo& ubseNodeInfo)
73 << ", will use default value: true";73 << ", will use default value: true";
74 ubseNodeInfo.isLender = true;74 ubseNodeInfo.isLender = true;
75 }75 }
76- // 读取 node_max_lend_gb 配置,各节点独立上报,由 master scheduler 汇聚76+ // Read the per-node lending limit. Each node reports its own value, aggregated on the master scheduler side.
77- // 范围 [0, 65535] GB0 = 不限制;越界回退 0(不限制)77+ // Range [0, 65535] GB, 0 = unlimited; out of range falls back to 0 (unlimited)
78- const std::string nodeMaxLendKey = "scheduler.node_max_lend_gb";78+ const std::string nodeMaxLendKey = "scheduler.node_lending_limit";
79 uint32_t nodeMaxLendGb = 0;79 uint32_t nodeMaxLendGb = 0;
80 ret = confModule->GetConf<uint32_t>(IS_LENDER_SECTION, nodeMaxLendKey, nodeMaxLendGb);80 ret = confModule->GetConf<uint32_t>(IS_LENDER_SECTION, nodeMaxLendKey, nodeMaxLendGb);
81 constexpr uint32_t maxNodeMaxLendGb = 65535;81 constexpr uint32_t maxNodeMaxLendGb = 65535;
@@ -199,7 +199,7 @@ struct UbseNodeInfo {
199 UbseAllocator allocator{UbseAllocator::BUDDY_HIGHMEM}; // 使用不同的内存类型余量借用决策199 UbseAllocator allocator{UbseAllocator::BUDDY_HIGHMEM}; // 使用不同的内存类型余量借用决策
200 uint32_t pmdMapping{100}; // 控制每个numa上能导出的内存总量,单位%200 uint32_t pmdMapping{100}; // 控制每个numa上能导出的内存总量,单位%
201 uint32_t blockSize{128}; // 芯片表项内存拆分粒度大小,单位M201 uint32_t blockSize{128}; // 芯片表项内存拆分粒度大小,单位M
202- uint32_t nodeMaxLendGb{0}; // process_mem 节点最大可借出量,单位GB,每个节点独立配置202+ uint32_t nodeMaxLendGb{0}; // per-node max lending amount in GB, configured independently on each node
203 uint32_t exportTotalTimes{1024}; // 单个socket的总导出次数,一个节点上的两个socket配置相同203 uint32_t exportTotalTimes{1024}; // 单个socket的总导出次数,一个节点上的两个socket配置相同
204 UbseNodeSysSentryState sysSentryState{204 UbseNodeSysSentryState sysSentryState{
205 UbseNodeSysSentryState::UBSE_NODE_SYSSENTRY_UNKNOWN}; // 本节点sysSentry服务状态205 UbseNodeSysSentryState::UBSE_NODE_SYSSENTRY_UNKNOWN}; // 本节点sysSentry服务状态
@@ -0,0 +1,21 @@
1+/*
2+ * Copyright (c) Huawei Technologies Co., Ltd. 2026-2026. All rights reserved.
3+ * ubs-engine is licensed under Mulan PSL v2.
4+ * You can use this software according to the terms and conditions of the Mulan PSL v2.
5+ * You may obtain a copy of Mulan PSL v2 at:
6+ * http://license.coscl.org.cn/MulanPSL2
7+ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
8+ * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
9+ * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
10+ * See the Mulan PSL v2 for more details.
11+ */
12+ 
13+#include "ubse_election.h"
14+ 
15+namespace ubse::election {
16+uint32_t UbseGetMasterNodeId(std::string& masterNodeId)
17+{
18+ masterNodeId = "NODE11347";
19+ return 0;
20+}
21+} // namespace ubse::election
@@ -125,6 +125,23 @@ TEST_F(TestProcessMemPidCollect, GetChildrenPidsReturnsVector)
125 EXPECT_TRUE(children.empty() || !children.empty());125 EXPECT_TRUE(children.empty() || !children.empty());
126}126}
127 127 
128+TEST_F(TestProcessMemPidCollect, CollectNodeFreeMemorySnapshot)
129+{
130+ auto& collector = ProcessMemPidCollect::GetInstance();
131+ std::ifstream node0Remote("/sys/devices/system/node/node0/remote");
132+ bool remoteAttrExists = node0Remote.is_open();
133+ node0Remote.close();
134+ 
135+ collector.CollectNodeFreeMemory(1);
136+ auto freeKb = collector.GetLocalNumaFreeKb();
137+ if (remoteAttrExists) {
138+ ASSERT_TRUE(freeKb.has_value());
139+ EXPECT_GT(*freeKb, 0u);
140+ } else {
141+ EXPECT_FALSE(freeKb.has_value());
142+ }
143+}
144+ 
128TEST_F(TestProcessMemPidCollect, CollectProcessNumaMemDistributionInvalidPid)145TEST_F(TestProcessMemPidCollect, CollectProcessNumaMemDistributionInvalidPid)
129{146{
130 auto& collector = ProcessMemPidCollect::GetInstance();147 auto& collector = ProcessMemPidCollect::GetInstance();
@@ -161,63 +178,4 @@ TEST_F(TestProcessMemPidCollect, GetChildrenPidsFallbackForKnownPid)
161 EXPECT_TRUE(children.empty() || !children.empty());178 EXPECT_TRUE(children.empty() || !children.empty());
162}179}
163 180 
164-std::string ReadComm(pid_t pid)
165-{
166- std::ifstream commFile("/proc/" + std::to_string(pid) + "/comm");
167- std::string comm;
168- std::getline(commFile, comm);
169- return comm;
170-}
171- 
172-TEST_F(TestProcessMemPidCollect, HandleNewPidsByNameSkipsRootProcess)
173-{
174- ASSERT_EQ(GetProcUid(1), 0);
175- 
176- auto& mgr = ProcessMemPidInfoManager::GetInstance();
177- std::string comm = ReadComm(1);
178- ASSERT_FALSE(comm.empty());
179- 
180- def::ProcessMemNewConfigInfo config{};
181- config.isPid = false;
182- config.identifier = comm;
183- config.maxMemory = 1073741824;
184- config.remoteRatio = 0.5;
185- ASSERT_EQ(mgr.SetProcMemConfig(config), UBSE_OK);
186- 
187- ProcessMemPidCollect::GetInstance().HandleNewPidsByName({1}, 1);
188- 
189- auto snapshot = mgr.GetManagedPidCacheSnapshot();
190- EXPECT_EQ(snapshot.find(1), snapshot.end());
191- EXPECT_EQ(mgr.GetProcMemConfig(true, "1").maxMemory, 0u);
192- 
193- EXPECT_EQ(mgr.RemoveProcMemConfig(false, comm), UBSE_OK);
194-}
195- 
196-TEST_F(TestProcessMemPidCollect, HandleNewPidsByNameManagesNonRootProcess)
197-{
198- if (getuid() == 0) {
199- GTEST_SKIP() << "本进程为 root,无法作为非 root 正例";
200- }
201- auto& mgr = ProcessMemPidInfoManager::GetInstance();
202- std::string comm = ReadComm(getpid());
203- ASSERT_FALSE(comm.empty());
204- 
205- def::ProcessMemNewConfigInfo config{};
206- config.isPid = false;
207- config.identifier = comm;
208- config.maxMemory = 1073741824;
209- config.remoteRatio = 0.5;
210- ASSERT_EQ(mgr.SetProcMemConfig(config), UBSE_OK);
211- 
212- ProcessMemPidCollect::GetInstance().HandleNewPidsByName({getpid()}, 1);
213- 
214- auto snapshot = mgr.GetManagedPidCacheSnapshot();
215- auto it = snapshot.find(getpid());
216- ASSERT_NE(it, snapshot.end());
217- EXPECT_EQ(it->second.sources & static_cast<uint8_t>(def::ConfigSource::NAME_CONFIG),
218- static_cast<uint8_t>(def::ConfigSource::NAME_CONFIG));
219- 
220- mgr.RemoveManagedPidEntry(getpid());
221- EXPECT_EQ(mgr.RemoveProcMemConfig(false, comm), UBSE_OK);
222-}
223} // namespace ubse::ut::process_mem181} // namespace ubse::ut::process_mem
@@ -92,10 +92,10 @@ void AddManagedPid(pid_t pid, uint64_t maxGb, double ratio, uint64_t vmRssGb, co
92 }92 }
93}93}
94 94 
95-// nodeFree 口径统一为 MemAvailable 后,mock 直接驱动 memAvailableReader95+// nodeFree 口径统一为本地 NUMA 空闲总和后,mock 直接驱动 localNumaFreeKbReader
96void MockNodeFreeBytes(uint64_t bytes)96void MockNodeFreeBytes(uint64_t bytes)
97{97{
98- ProcessMemPidDecision::memAvailableReader = [bytes]() {98+ ProcessMemPidDecision::localNumaFreeKbReader = [bytes]() {
99 return bytes / 1024;99 return bytes / 1024;
100 };100 };
101}101}
@@ -140,9 +140,9 @@ void TestProcessMemPidDecision::SetUp()
140 decision.fastPollIntervalMs_ = 200;140 decision.fastPollIntervalMs_ = 200;
141 decision.observeCycles_ = 6;141 decision.observeCycles_ = 6;
142 decision.returnRetryIntervalMs_ = 1;142 decision.returnRetryIntervalMs_ = 1;
143- decision.oomFastPoll_ = false;
144 decision.oomFastWindowCount_ = 0;143 decision.oomFastWindowCount_ = 0;
145 decision.oomFastWindowMin_ = 0;144 decision.oomFastWindowMin_ = 0;
145+ decision.lastEmergencyBroadcast_ = std::chrono::steady_clock::now() - std::chrono::seconds(10);
146 if (decision.borrowExecutor_ == nullptr || !decision.borrowExecutor_->IsStart()) {146 if (decision.borrowExecutor_ == nullptr || !decision.borrowExecutor_->IsStart()) {
147 decision.borrowExecutor_ = ubse::task_executor::UbseTaskExecutor::Create("ProcMemDecisionTest", 2, 64);147 decision.borrowExecutor_ = ubse::task_executor::UbseTaskExecutor::Create("ProcMemDecisionTest", 2, 64);
148 decision.borrowExecutor_->Start();148 decision.borrowExecutor_->Start();
@@ -151,6 +151,19 @@ void TestProcessMemPidDecision::SetUp()
151 decision.returnExecutor_ = ubse::task_executor::UbseTaskExecutor::Create("ProcMemReturnTest", 2, 64);151 decision.returnExecutor_ = ubse::task_executor::UbseTaskExecutor::Create("ProcMemReturnTest", 2, 64);
152 decision.returnExecutor_->Start();152 decision.returnExecutor_->Start();
153 }153 }
154+ // UT 无远端 numa sysfs: mock 实际占用量 = 已完成 slot 之和, 使 pending 语义等价于旧的在途借用口径
155+ ProcessMemPidDecision::remoteNumaUsedKbReader = []() -> std::optional<uint64_t> {
156+ uint64_t actualBytes = 0;
157+ auto snapshot = ProcessMemPidInfoManager::GetInstance().GetManagedPidCacheSnapshot();
158+ for (const auto& [pid, entry] : snapshot) {
159+ for (const auto& s : entry.borrow.slots) {
160+ if (s.status == BorrowSlotStatus::COMPLETED) {
161+ actualBytes += s.migratedBytes;
162+ }
163+ }
164+ }
165+ return actualBytes / 1024;
166+ };
154}167}
155 168 
156void TestProcessMemPidDecision::TearDown()169void TestProcessMemPidDecision::TearDown()
@@ -161,7 +174,8 @@ void TestProcessMemPidDecision::TearDown()
161 ProcessMemPidBridge::rmrsFreeWithMigrate = {};174 ProcessMemPidBridge::rmrsFreeWithMigrate = {};
162 ProcessMemPidBridge::rmrsRemoteToRemote = {};175 ProcessMemPidBridge::rmrsRemoteToRemote = {};
163 ProcessMemPidBridge::rmrsProcessConfigQuery = {};176 ProcessMemPidBridge::rmrsProcessConfigQuery = {};
164- ProcessMemPidDecision::memAvailableReader = nullptr;177+ ProcessMemPidDecision::localNumaFreeKbReader = nullptr;
178+ ProcessMemPidDecision::remoteNumaUsedKbReader = nullptr;
165 ProcessMemPidCollect::GetInstance().SetCollectVmRssOverride(nullptr);179 ProcessMemPidCollect::GetInstance().SetCollectVmRssOverride(nullptr);
166 180 
167 auto& mgr = ProcessMemPidInfoManager::GetInstance();181 auto& mgr = ProcessMemPidInfoManager::GetInstance();
@@ -184,39 +198,6 @@ TEST_F(TestProcessMemPidDecision, PassiveReturnNoBroadcastWhenFreeAboveThreshold
184 EXPECT_TRUE(ubse::com::MockGetRpcSendRecords().empty());198 EXPECT_TRUE(ubse::com::MockGetRpcSendRecords().empty());
185}199}
186 200 
187-TEST_F(TestProcessMemPidDecision, PassiveReturnBroadcastToAllBorrowers)
188-{
189- MockNodeFree(10);
190- auto debts = MakeLentDebts({"NODE1", "NODE2", "NODE3"});
191- ubse::mem::controller::UbseNumaMemoryDebtInfo otherDebt;
192- otherDebt.name = "debt-other";
193- otherDebt.borrowNodeId = "NODE9";
194- otherDebt.lentNodeId = "NODE2";
195- otherDebt.size = 1 * GB;
196- debts.push_back(otherDebt);
197- ubse::mem::controller::UbseNumaMemoryDebtInfo selfDebt;
198- selfDebt.name = "debt-self";
199- selfDebt.borrowNodeId = "NODE0";
200- selfDebt.lentNodeId = "NODE0";
201- selfDebt.size = 1 * GB;
202- debts.push_back(selfDebt);
203- ubse::mem::controller::MockSetDebtInfos(debts);
204- 
205- ProcessMemPidDecision::GetInstance().OnDecisionTimer();
206- 
207- auto records = ubse::com::MockGetRpcSendRecords();
208- ASSERT_EQ(records.size(), 3u);
209- std::set<std::string> addresses;
210- for (const auto& record : records) {
211- addresses.insert(record.address);
212- auto items = DecodeItems(record.payload);
213- ASSERT_EQ(items.size(), 1u);
214- EXPECT_EQ(items[0].name, "debt-" + record.address);
215- EXPECT_EQ(items[0].size, 1 * GB);
216- }
217- EXPECT_EQ(addresses, (std::set<std::string>{"NODE1", "NODE2", "NODE3"}));
218-}
219- 
220TEST_F(TestProcessMemPidDecision, PassiveReturnStopsWhenFreeRecovered)201TEST_F(TestProcessMemPidDecision, PassiveReturnStopsWhenFreeRecovered)
221{202{
222 MockNodeFree(10);203 MockNodeFree(10);
@@ -257,167 +238,6 @@ TEST_F(TestProcessMemPidDecision, BorrowerDirectReturn)
257 EXPECT_EQ(it->second.processStatus, ProcessStatus::IDLE);238 EXPECT_EQ(it->second.processStatus, ProcessStatus::IDLE);
258}239}
259 240 
260-TEST_F(TestProcessMemPidDecision, BorrowerRemoteToRemote)
261-{
262- MockNodeFree(10);
263- ubse::mem::controller::MockSetDebtInfos({MakeDebt("debt-x", "NODE0", 5, 1001)});
264- AddManagedPid(1001, 10, 0.5, 4, MakeBorrow(1, "debt-x"));
265- 
266- std::vector<mempooling::smap::MigrateEscapeMsg> migrates;
267- ProcessMemPidBridge::rmrsRemoteToRemote = [&migrates](const mempooling::smap::MigrateEscapeMsg& msg) {
268- migrates.push_back(msg);
269- return 0;
270- };
271- 
272- ReturnRequestItem item{"debt-x", 1 * GB};
273- EXPECT_EQ(ProcessMemPidDecision::GetInstance().HandleReturnRequest({item}), UBSE_OK);
274- 
275- ASSERT_EQ(migrates.size(), 1u);
276- EXPECT_EQ(migrates[0].count, 1);
277- EXPECT_EQ(migrates[0].payload[0].pid, 1001);
278- EXPECT_EQ(migrates[0].payload[0].srcNid, 5);
279- EXPECT_EQ(migrates[0].payload[0].destNid, 3);
280- EXPECT_EQ(migrates[0].payload[0].memSize, 1 * 1024 * 1024);
281- EXPECT_EQ(migrates[0].payload[0].migrateMode, mempooling::smap::MIG_MEMSIZE_MODE);
282- EXPECT_FALSE(ubse::mem::controller::MockGetLastNumaCreateName().empty());
283- EXPECT_EQ(ubse::mem::controller::MockGetLastNumaDeleteName(), "debt-x");
284- 
285- auto snapshot = ProcessMemPidInfoManager::GetInstance().GetManagedPidCacheSnapshot();
286- auto it = snapshot.find(1001);
287- ASSERT_NE(it, snapshot.end());
288- EXPECT_EQ(it->second.borrow.currentRemote, 1 * GB);
289- ASSERT_EQ(it->second.borrow.slots.size(), 1u);
290- EXPECT_EQ(it->second.borrow.slots[0].debtId, ubse::mem::controller::MockGetLastNumaCreateName());
291- EXPECT_EQ(it->second.borrow.slots[0].remoteNumaId, 3);
292- EXPECT_EQ(it->second.borrow.slots[0].capacity, 1 * GB);
293- EXPECT_EQ(it->second.borrow.slots[0].migratedBytes, 1 * GB);
294- EXPECT_EQ(it->second.borrow.slots[0].returnStatus, ReturnStatus::NONE);
295- EXPECT_EQ(it->second.processStatus, ProcessStatus::BORROWED);
296-}
297- 
298-TEST_F(TestProcessMemPidDecision, BorrowerCreateFailRetriesUntilSuccess)
299-{
300- MockNodeFree(10);
301- ubse::mem::controller::MockSetDebtInfos({MakeDebt("debt-x", "NODE0", 5, 1001)});
302- AddManagedPid(1001, 10, 0.5, 4, MakeBorrow(1, "debt-x"));
303- ubse::mem::controller::MockSetNumaCreateErrorOnce(UBSE_ERROR);
304- 
305- std::vector<mempooling::smap::MigrateEscapeMsg> migrates;
306- ProcessMemPidBridge::rmrsRemoteToRemote = [&migrates](const mempooling::smap::MigrateEscapeMsg& msg) {
307- migrates.push_back(msg);
308- return 0;
309- };
310- 
311- ReturnRequestItem item{"debt-x", 1 * GB};
312- ProcessMemPidDecision::GetInstance().HandleReturnRequest({item});
313- 
314- ASSERT_EQ(migrates.size(), 1u);
315- EXPECT_EQ(migrates[0].payload[0].pid, 1001);
316- EXPECT_EQ(migrates[0].payload[0].srcNid, 5);
317- EXPECT_EQ(ubse::mem::controller::MockGetLastNumaDeleteName(), "debt-x");
318- 
319- auto snapshot = ProcessMemPidInfoManager::GetInstance().GetManagedPidCacheSnapshot();
320- auto it = snapshot.find(1001);
321- ASSERT_NE(it, snapshot.end());
322- EXPECT_EQ(it->second.borrow.currentRemote, 1 * GB);
323- ASSERT_EQ(it->second.borrow.slots.size(), 1u);
324- EXPECT_EQ(it->second.borrow.slots[0].debtId, ubse::mem::controller::MockGetLastNumaCreateName());
325- EXPECT_EQ(it->second.borrow.slots[0].remoteNumaId, 3);
326- EXPECT_EQ(it->second.borrow.slots[0].capacity, 1 * GB);
327- EXPECT_EQ(it->second.borrow.slots[0].migratedBytes, 1 * GB);
328- EXPECT_EQ(it->second.borrow.slots[0].returnStatus, ReturnStatus::NONE);
329- EXPECT_EQ(it->second.processStatus, ProcessStatus::BORROWED);
330-}
331- 
332-TEST_F(TestProcessMemPidDecision, BorrowerRemoteToRemoteRetriesUntilSuccess)
333-{
334- MockNodeFree(10);
335- ubse::mem::controller::MockSetDebtInfos({MakeDebt("debt-x", "NODE0", 5, 1001)});
336- AddManagedPid(1001, 10, 0.5, 4, MakeBorrow(1, "debt-x"));
337- 
338- std::vector<mempooling::smap::MigrateEscapeMsg> migrates;
339- int migrateCalls = 0;
340- ProcessMemPidBridge::rmrsRemoteToRemote = [&migrates,
341- &migrateCalls](const mempooling::smap::MigrateEscapeMsg& msg) {
342- migrates.push_back(msg);
343- return (migrateCalls++ == 0) ? -1 : 0;
344- };
345- 
346- ReturnRequestItem item{"debt-x", 1 * GB};
347- ProcessMemPidDecision::GetInstance().HandleReturnRequest({item});
348- 
349- ASSERT_EQ(migrates.size(), 2u);
350- EXPECT_EQ(ubse::mem::controller::MockGetLastNumaDeleteName(), "debt-x");
351- 
352- auto snapshot = ProcessMemPidInfoManager::GetInstance().GetManagedPidCacheSnapshot();
353- auto it = snapshot.find(1001);
354- ASSERT_NE(it, snapshot.end());
355- EXPECT_EQ(it->second.borrow.currentRemote, 1 * GB);
356- ASSERT_EQ(it->second.borrow.slots.size(), 1u);
357- EXPECT_EQ(it->second.borrow.slots[0].debtId, ubse::mem::controller::MockGetLastNumaCreateName());
358- EXPECT_EQ(it->second.borrow.slots[0].remoteNumaId, 3);
359- EXPECT_EQ(it->second.borrow.slots[0].capacity, 1 * GB);
360- EXPECT_EQ(it->second.borrow.slots[0].migratedBytes, 1 * GB);
361- EXPECT_EQ(it->second.borrow.slots[0].returnStatus, ReturnStatus::NONE);
362- EXPECT_EQ(it->second.processStatus, ProcessStatus::BORROWED);
363-}
364- 
365-TEST_F(TestProcessMemPidDecision, ReturnDebtEnqueuedAsync)
366-{
367- MockNodeFree(10);
368- ubse::mem::controller::MockSetDebtInfos(
369- {MakeDebt("debt-a", "NODE1", 5, 1001), MakeDebt("debt-b", "NODE2", 6, 1001)});
370- BorrowState borrow = MakeBorrow(1, "debt-a");
371- borrow.slots.push_back(MakeBorrow(1, "debt-b").slots[0]);
372- borrow.currentRemote = 2 * GB;
373- AddManagedPid(1001, 10, 0.5, 4, borrow);
374- 
375- std::mutex gateMutex;
376- std::condition_variable gateCv;
377- bool gateOpen = false;
378- std::atomic<bool> migrateStarted{false};
379- ProcessMemPidBridge::rmrsRemoteToRemote = [&](const mempooling::smap::MigrateEscapeMsg&) {
380- migrateStarted = true;
381- std::unique_lock<std::mutex> lock(gateMutex);
382- gateCv.wait(lock, [&] { return gateOpen; });
383- return 0;
384- };
385- 
386- ubse::task_executor::MockSetExecutorAsync(true);
387- ReturnRequestItem itemA{"debt-a", 1 * GB};
388- ReturnRequestItem itemB{"debt-b", 1 * GB};
389- EXPECT_EQ(ProcessMemPidDecision::GetInstance().HandleReturnRequest({itemA, itemB}), UBSE_OK);
390- 
391- auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(5);
392- while (!migrateStarted && std::chrono::steady_clock::now() < deadline) {
393- std::this_thread::sleep_for(std::chrono::milliseconds(1));
394- }
395- ASSERT_TRUE(migrateStarted);
396- auto snapshot = ProcessMemPidInfoManager::GetInstance().GetManagedPidCacheSnapshot();
397- EXPECT_EQ(snapshot.at(1001).borrow.currentRemote, 2 * GB);
398- 
399- {
400- std::lock_guard<std::mutex> lock(gateMutex);
401- gateOpen = true;
402- }
403- gateCv.notify_all();
404- ubse::task_executor::MockWaitExecutorIdle();
405- 
406- snapshot = ProcessMemPidInfoManager::GetInstance().GetManagedPidCacheSnapshot();
407- auto it = snapshot.find(1001);
408- ASSERT_NE(it, snapshot.end());
409- EXPECT_EQ(it->second.borrow.currentRemote, 2 * GB);
410- ASSERT_EQ(it->second.borrow.slots.size(), 2u);
411- EXPECT_NE(it->second.borrow.slots[0].debtId, "debt-a");
412- EXPECT_NE(it->second.borrow.slots[1].debtId, "debt-b");
413- EXPECT_EQ(it->second.borrow.slots[0].returnStatus, ReturnStatus::NONE);
414- EXPECT_EQ(it->second.borrow.slots[1].returnStatus, ReturnStatus::NONE);
415- EXPECT_EQ(it->second.processStatus, ProcessStatus::BORROWED);
416- EXPECT_EQ(ubse::mem::controller::MockGetNumaDeleteCallCount(), 2u);
417- 
418- ubse::task_executor::MockSetExecutorAsync(false);
419-}
420- 
421TEST_F(TestProcessMemPidDecision, TimeoutReturnEnqueuedAndSlotRemoved)241TEST_F(TestProcessMemPidDecision, TimeoutReturnEnqueuedAndSlotRemoved)
422{242{
423 const pid_t pid = 1001;243 const pid_t pid = 1001;
@@ -490,26 +310,6 @@ TEST_F(TestProcessMemPidDecision, PidExitedNoDebtNoFree)
490 EXPECT_TRUE(freedDebts.empty());310 EXPECT_TRUE(freedDebts.empty());
491}311}
492 312 
493-TEST_F(TestProcessMemPidDecision, PidExitedFreeFailureRetriesUntilSuccess)
494-{
495- ubse::mem::controller::MockSetDebtInfos(
496- {MakeDebt("debt-a", "NODE1", 5, 1001), MakeDebt("debt-b", "NODE2", 6, 1001)});
497- 
498- std::vector<std::string> freedDebts;
499- int debtAFailCalls = 0;
500- ProcessMemPidBridge::rmrsFreeWithMigrate = [&freedDebts, &debtAFailCalls](const std::string& name) {
501- if (name == "debt-a" && debtAFailCalls++ == 0) {
502- return UBSE_ERROR;
503- }
504- freedDebts.push_back(name);
505- return UBSE_OK;
506- };
507- 
508- EXPECT_EQ(ProcessMemPidDecision::GetInstance().HandlePidExited(1001), UBSE_OK);
509- std::set<std::string> freedSet(freedDebts.begin(), freedDebts.end());
510- EXPECT_EQ(freedSet, std::set<std::string>({"debt-a", "debt-b"}));
511-}
512- 
513TEST_F(TestProcessMemPidDecision, PidExitedUbsedDeleteThenRmrsReturn)313TEST_F(TestProcessMemPidDecision, PidExitedUbsedDeleteThenRmrsReturn)
514{314{
515 ubse::mem::controller::MockSetDebtInfos({MakeDebt("debt-a", "NODE1", 5, 1001)});315 ubse::mem::controller::MockSetDebtInfos({MakeDebt("debt-a", "NODE1", 5, 1001)});
@@ -527,42 +327,6 @@ TEST_F(TestProcessMemPidDecision, PidExitedUbsedDeleteThenRmrsReturn)
527 EXPECT_EQ(freedDebts[0], "debt-a");327 EXPECT_EQ(freedDebts[0], "debt-a");
528}328}
529 329 
530-TEST_F(TestProcessMemPidDecision, PidExitedUbseDeleteFailRetriesUntilSuccess)
531-{
532- ubse::mem::controller::MockSetDebtInfos({MakeDebt("debt-a", "NODE1", 5, 1001)});
533- ubse::mem::controller::MockSetNumaDeleteErrorOnce(UBSE_ERR_TIMEOUT);
534- 
535- std::vector<std::string> freedDebts;
536- ProcessMemPidBridge::rmrsFreeWithMigrate = [&freedDebts](const std::string& name) {
537- freedDebts.push_back(name);
538- return 0;
539- };
540- 
541- EXPECT_EQ(ProcessMemPidDecision::GetInstance().HandlePidExited(1001), UBSE_OK);
542- 
543- EXPECT_GE(ubse::mem::controller::MockGetNumaDeleteCallCount(), 2u);
544- ASSERT_EQ(freedDebts.size(), 1u);
545- EXPECT_EQ(freedDebts[0], "debt-a");
546-}
547- 
548-TEST_F(TestProcessMemPidDecision, RemovePidAliveProcessUsesRmrsDirectly)
549-{
550- pid_t pid = getpid();
551- ubse::mem::controller::MockSetDebtInfos({MakeDebt("debt-self", "NODE1", 5, static_cast<int32_t>(pid))});
552- 
553- std::vector<std::string> freedDebts;
554- ProcessMemPidBridge::rmrsFreeWithMigrate = [&freedDebts](const std::string& name) {
555- freedDebts.push_back(name);
556- return 0;
557- };
558- 
559- EXPECT_TRUE(ProcessMemPidDecision::GetInstance().EnqueuePidReturn(pid, ReturnScene::EXITED));
560- 
561- ASSERT_EQ(freedDebts.size(), 1u);
562- EXPECT_EQ(freedDebts[0], "debt-self");
563- EXPECT_TRUE(ubse::mem::controller::MockGetLastNumaDeleteName().empty());
564-}
565- 
566TEST_F(TestProcessMemPidDecision, PidReturnEnqueuedAsync)330TEST_F(TestProcessMemPidDecision, PidReturnEnqueuedAsync)
567{331{
568 ubse::mem::controller::MockSetDebtInfos(332 ubse::mem::controller::MockSetDebtInfos(
@@ -609,35 +373,34 @@ TEST_F(TestProcessMemPidDecision, PidReturnEnqueuedAsync)
609 ubse::task_executor::MockSetExecutorAsync(false);373 ubse::task_executor::MockSetExecutorAsync(false);
610}374}
611 375 
612-TEST_F(TestProcessMemPidDecision, OomDetectorNormalPoll)376+TEST_F(TestProcessMemPidDecision, OomDetectorAlwaysFastPoll)
613{377{
614- ProcessMemPidDecision::memAvailableReader = []() {378+ ProcessMemPidDecision::localNumaFreeKbReader = []() {
615 return 200 * KB_PER_GB;379 return 200 * KB_PER_GB;
616 };380 };
617 381 
618 auto& decision = ProcessMemPidDecision::GetInstance();382 auto& decision = ProcessMemPidDecision::GetInstance();
619- EXPECT_EQ(decision.OomPollOnce(), 1000u);383+ EXPECT_EQ(decision.OomPollOnce(), 200u);
620- EXPECT_FALSE(decision.oomFastPoll_);384+ EXPECT_EQ(decision.oomFastWindowCount_, 1u);
621- EXPECT_EQ(decision.oomFastWindowCount_, 0u);385+ EXPECT_EQ(decision.oomFastWindowMin_, 200 * GB);
622}386}
623 387 
624TEST_F(TestProcessMemPidDecision, OomDetectorFastPoll)388TEST_F(TestProcessMemPidDecision, OomDetectorFastPoll)
625{389{
626- ProcessMemPidDecision::memAvailableReader = []() {390+ ProcessMemPidDecision::localNumaFreeKbReader = []() {
627 return 100 * KB_PER_GB;391 return 100 * KB_PER_GB;
628 };392 };
629 MockNodeFree(10);393 MockNodeFree(10);
630 394 
631 auto& decision = ProcessMemPidDecision::GetInstance();395 auto& decision = ProcessMemPidDecision::GetInstance();
632 EXPECT_EQ(decision.OomPollOnce(), 200u);396 EXPECT_EQ(decision.OomPollOnce(), 200u);
633- EXPECT_TRUE(decision.oomFastPoll_);
634 EXPECT_EQ(decision.oomFastWindowCount_, 1u);397 EXPECT_EQ(decision.oomFastWindowCount_, 1u);
635 EXPECT_EQ(decision.oomFastWindowMin_, 10 * GB);398 EXPECT_EQ(decision.oomFastWindowMin_, 10 * GB);
636}399}
637 400 
638TEST_F(TestProcessMemPidDecision, OomEmergencyBorrow)401TEST_F(TestProcessMemPidDecision, OomEmergencyBorrow)
639{402{
640- ProcessMemPidDecision::memAvailableReader = []() {403+ ProcessMemPidDecision::localNumaFreeKbReader = []() {
641 return 3 * KB_PER_GB;404 return 3 * KB_PER_GB;
642 };405 };
643 AddManagedPid(1001, 10, 0.5, 4);406 AddManagedPid(1001, 10, 0.5, 4);
@@ -661,7 +424,7 @@ TEST_F(TestProcessMemPidDecision, OomEmergencyBorrow)
661 424 
662TEST_F(TestProcessMemPidDecision, OomEmergencyAllowsExistingDebt)425TEST_F(TestProcessMemPidDecision, OomEmergencyAllowsExistingDebt)
663{426{
664- ProcessMemPidDecision::memAvailableReader = []() {427+ ProcessMemPidDecision::localNumaFreeKbReader = []() {
665 return 3 * KB_PER_GB;428 return 3 * KB_PER_GB;
666 };429 };
667 AddManagedPid(1001, 10, 0.5, 4, MakeBorrow(1, "debt-x"));430 AddManagedPid(1001, 10, 0.5, 4, MakeBorrow(1, "debt-x"));
@@ -685,7 +448,7 @@ TEST_F(TestProcessMemPidDecision, OomEmergencyAllowsExistingDebt)
685 448 
686TEST_F(TestProcessMemPidDecision, OomLenderEmergencyNotify)449TEST_F(TestProcessMemPidDecision, OomLenderEmergencyNotify)
687{450{
688- ProcessMemPidDecision::memAvailableReader = []() {451+ ProcessMemPidDecision::localNumaFreeKbReader = []() {
689 return 3 * KB_PER_GB;452 return 3 * KB_PER_GB;
690 };453 };
691 ubse::mem::controller::MockSetDebtInfos(MakeLentDebts({"NODE1", "NODE2"}));454 ubse::mem::controller::MockSetDebtInfos(MakeLentDebts({"NODE1", "NODE2"}));
@@ -697,7 +460,7 @@ TEST_F(TestProcessMemPidDecision, OomLenderEmergencyNotify)
697 460 
698TEST_F(TestProcessMemPidDecision, OomActiveReturnTrigger)461TEST_F(TestProcessMemPidDecision, OomActiveReturnTrigger)
699{462{
700- ProcessMemPidDecision::memAvailableReader = []() {463+ ProcessMemPidDecision::localNumaFreeKbReader = []() {
701 return 100 * KB_PER_GB;464 return 100 * KB_PER_GB;
702 };465 };
703 MockNodeFree(60);466 MockNodeFree(60);
@@ -728,7 +491,7 @@ TEST_F(TestProcessMemPidDecision, OomActiveReturnTrigger)
728 491 
729TEST_F(TestProcessMemPidDecision, OomActiveCounterReset)492TEST_F(TestProcessMemPidDecision, OomActiveCounterReset)
730{493{
731- ProcessMemPidDecision::memAvailableReader = []() {494+ ProcessMemPidDecision::localNumaFreeKbReader = []() {
732 return 100 * KB_PER_GB;495 return 100 * KB_PER_GB;
733 };496 };
734 MockNodeFree(60);497 MockNodeFree(60);
@@ -751,7 +514,7 @@ TEST_F(TestProcessMemPidDecision, OomActiveCounterReset)
751 514 
752TEST_F(TestProcessMemPidDecision, OomActiveReturnSkipOversizeDebt)515TEST_F(TestProcessMemPidDecision, OomActiveReturnSkipOversizeDebt)
753{516{
754- ProcessMemPidDecision::memAvailableReader = []() {517+ ProcessMemPidDecision::localNumaFreeKbReader = []() {
755 return 100 * KB_PER_GB;518 return 100 * KB_PER_GB;
756 };519 };
757 MockNodeFree(55);520 MockNodeFree(55);
@@ -777,7 +540,7 @@ TEST_F(TestProcessMemPidDecision, OomActiveReturnSkipOversizeDebt)
777 540 
778TEST_F(TestProcessMemPidDecision, OomActiveReturnDeleteFailRetriesUntilSuccess)541TEST_F(TestProcessMemPidDecision, OomActiveReturnDeleteFailRetriesUntilSuccess)
779{542{
780- ProcessMemPidDecision::memAvailableReader = []() {543+ ProcessMemPidDecision::localNumaFreeKbReader = []() {
781 return 100 * KB_PER_GB;544 return 100 * KB_PER_GB;
782 };545 };
783 MockNodeFree(60);546 MockNodeFree(60);
@@ -805,7 +568,7 @@ TEST_F(TestProcessMemPidDecision, OomActiveReturnDeleteFailRetriesUntilSuccess)
805 568 
806TEST_F(TestProcessMemPidDecision, OomActiveReturnNotExistCleansLedger)569TEST_F(TestProcessMemPidDecision, OomActiveReturnNotExistCleansLedger)
807{570{
808- ProcessMemPidDecision::memAvailableReader = []() {571+ ProcessMemPidDecision::localNumaFreeKbReader = []() {
809 return 100 * KB_PER_GB;572 return 100 * KB_PER_GB;
810 };573 };
811 MockNodeFree(60);574 MockNodeFree(60);
@@ -898,7 +661,7 @@ TEST_F(TestProcessMemPidDecision, OomRearrangeMigratedBigFirst)
898 661 
899TEST_F(TestProcessMemPidDecision, OomActiveReturnUsesMigratedBytes)662TEST_F(TestProcessMemPidDecision, OomActiveReturnUsesMigratedBytes)
900{663{
901- ProcessMemPidDecision::memAvailableReader = []() {664+ ProcessMemPidDecision::localNumaFreeKbReader = []() {
902 return 100 * KB_PER_GB;665 return 100 * KB_PER_GB;
903 };666 };
904 MockNodeFree(55);667 MockNodeFree(55);
@@ -976,40 +739,6 @@ ubse::mem::controller::UbseNumaMemoryImportDebtInfo MakeImportDebt(const std::st
976 return debt;739 return debt;
977}740}
978 741 
979-TEST_F(TestProcessMemPidDecision, RecoverBorrowBindToExistingPid)
980-{
981- pid_t pid = getpid();
982- AddManagedPid(pid, 10, 0.5, 2);
983- auto startTime = ProcessMemPidConfigManager::GetExactStartTime(pid);
984- ASSERT_NE(startTime, 0u);
985- 
986- ubse::mem::controller::MockSetImportDebtInfos(
987- {MakeImportDebt("debt-recover", 1 * GB, 5, pid, static_cast<int64_t>(startTime))});
988- 
989- std::vector<std::string> freedDebts;
990- ProcessMemPidBridge::rmrsFreeWithMigrate = [&freedDebts](const std::string& name) {
991- freedDebts.push_back(name);
992- return 0;
993- };
994- 
995- ProcessMemPidDecision::GetInstance().RecoverBorrowFromObmm();
996- 
997- EXPECT_TRUE(freedDebts.empty());
998- auto snapshot = ProcessMemPidInfoManager::GetInstance().GetManagedPidCacheSnapshot();
999- auto it = snapshot.find(pid);
1000- ASSERT_NE(it, snapshot.end());
1001- EXPECT_EQ(it->second.processStatus, ProcessStatus::BORROWED);
1002- ASSERT_EQ(it->second.borrow.slots.size(), 1u);
1003- const auto& slot = it->second.borrow.slots[0];
1004- EXPECT_EQ(slot.debtId, "debt-recover");
1005- EXPECT_EQ(slot.capacity, 1 * GB);
1006- EXPECT_EQ(slot.status, BorrowSlotStatus::COMPLETED);
1007- EXPECT_EQ(slot.remoteNumaId, 5);
1008- EXPECT_EQ(slot.srcNumaId, 1);
1009- EXPECT_EQ(slot.migratedBytes, 0u);
1010- EXPECT_EQ(it->second.borrow.currentRemote, 0u);
1011-}
1012- 
1013TEST_F(TestProcessMemPidDecision, RecoverBorrowOrphanReleasedWhenPidMissing)742TEST_F(TestProcessMemPidDecision, RecoverBorrowOrphanReleasedWhenPidMissing)
1014{743{
1015 ubse::mem::controller::MockSetImportDebtInfos({MakeImportDebt("debt-orphan", 1 * GB, 5, 1001, 123456)});744 ubse::mem::controller::MockSetImportDebtInfos({MakeImportDebt("debt-orphan", 1 * GB, 5, 1001, 123456)});
@@ -1349,7 +1078,7 @@ TEST_F(TestProcessMemPidDecision, MultiCycleBorrowingLedgerVisibleAndNoDuplicate
1349 EXPECT_EQ(it->second.borrow.slots[0].status, BorrowSlotStatus::BORROWING);1078 EXPECT_EQ(it->second.borrow.slots[0].status, BorrowSlotStatus::BORROWING);
1350 EXPECT_EQ(it->second.processStatus, ProcessStatus::BORROWING);1079 EXPECT_EQ(it->second.processStatus, ProcessStatus::BORROWING);
1351 std::string firstDebtId = it->second.borrow.slots[0].debtId;1080 std::string firstDebtId = it->second.borrow.slots[0].debtId;
1352- EXPECT_EQ(decision.GetPendingBorrowTotal(), 10 * GB);1081+ EXPECT_EQ(decision.GetPendingMigrateTotal(), 10 * GB);
1353 1082 
1354 std::thread round2Thread([&decision]() { decision.OnDecisionTimer(); });1083 std::thread round2Thread([&decision]() { decision.OnDecisionTimer(); });
1355 round2Thread.join();1084 round2Thread.join();
@@ -1360,7 +1089,7 @@ TEST_F(TestProcessMemPidDecision, MultiCycleBorrowingLedgerVisibleAndNoDuplicate
1360 EXPECT_EQ(it->second.borrow.slots[0].debtId, firstDebtId);1089 EXPECT_EQ(it->second.borrow.slots[0].debtId, firstDebtId);
1361 EXPECT_EQ(it->second.borrow.slots[0].status, BorrowSlotStatus::BORROWING);1090 EXPECT_EQ(it->second.borrow.slots[0].status, BorrowSlotStatus::BORROWING);
1362 EXPECT_EQ(ubse::mem::controller::MockGetLastNumaCreateName(), firstDebtId);1091 EXPECT_EQ(ubse::mem::controller::MockGetLastNumaCreateName(), firstDebtId);
1363- EXPECT_EQ(decision.GetPendingBorrowTotal(), 10 * GB);1092+ EXPECT_EQ(decision.GetPendingMigrateTotal(), 10 * GB);
1364 1093 
1365 ubse::mem::controller::MockReleaseNumaCreate();1094 ubse::mem::controller::MockReleaseNumaCreate();
1366 ubse::task_executor::MockWaitExecutorIdle();1095 ubse::task_executor::MockWaitExecutorIdle();
@@ -1375,7 +1104,7 @@ TEST_F(TestProcessMemPidDecision, MultiCycleBorrowingLedgerVisibleAndNoDuplicate
1375 EXPECT_EQ(it->second.borrow.slots[0].status, BorrowSlotStatus::COMPLETED);1104 EXPECT_EQ(it->second.borrow.slots[0].status, BorrowSlotStatus::COMPLETED);
1376 EXPECT_EQ(it->second.borrow.currentRemote, 10 * GB);1105 EXPECT_EQ(it->second.borrow.currentRemote, 10 * GB);
1377 EXPECT_EQ(it->second.processStatus, ProcessStatus::BORROWED);1106 EXPECT_EQ(it->second.processStatus, ProcessStatus::BORROWED);
1378- EXPECT_EQ(decision.GetPendingBorrowTotal(), 0u);1107+ EXPECT_EQ(decision.GetPendingMigrateTotal(), 0u);
1379 1108 
1380 std::thread round3Thread([&decision]() { decision.OnDecisionTimer(); });1109 std::thread round3Thread([&decision]() { decision.OnDecisionTimer(); });
1381 round3Thread.join();1110 round3Thread.join();
@@ -1413,7 +1142,7 @@ TEST_F(TestProcessMemPidDecision, NodeFreeChangeConsidersInFlightBorrow)
1413 EXPECT_EQ(snapshot.at(pid).borrow.slots[0].migratedBytes, 10 * GB);1142 EXPECT_EQ(snapshot.at(pid).borrow.slots[0].migratedBytes, 10 * GB);
1414 EXPECT_EQ(snapshot.at(pid).borrow.slots[0].status, BorrowSlotStatus::BORROWING);1143 EXPECT_EQ(snapshot.at(pid).borrow.slots[0].status, BorrowSlotStatus::BORROWING);
1415 std::string firstDebtId = snapshot.at(pid).borrow.slots[0].debtId;1144 std::string firstDebtId = snapshot.at(pid).borrow.slots[0].debtId;
1416- EXPECT_EQ(decision.GetPendingBorrowTotal(), 10 * GB);1145+ EXPECT_EQ(decision.GetPendingMigrateTotal(), 10 * GB);
1417 1146 
1418 MockNodeFree(35);1147 MockNodeFree(35);
1419 std::thread round2Thread([&decision]() { decision.OnDecisionTimer(); });1148 std::thread round2Thread([&decision]() { decision.OnDecisionTimer(); });
@@ -1434,7 +1163,7 @@ TEST_F(TestProcessMemPidDecision, NodeFreeChangeConsidersInFlightBorrow)
1434 EXPECT_EQ(snapshot.at(pid).borrow.slots[1].migratedBytes, 5 * GB);1163 EXPECT_EQ(snapshot.at(pid).borrow.slots[1].migratedBytes, 5 * GB);
1435 EXPECT_EQ(snapshot.at(pid).borrow.slots[1].status, BorrowSlotStatus::BORROWING);1164 EXPECT_EQ(snapshot.at(pid).borrow.slots[1].status, BorrowSlotStatus::BORROWING);
1436 EXPECT_FALSE(snapshot.at(pid).borrow.slots[1].debtId.empty());1165 EXPECT_FALSE(snapshot.at(pid).borrow.slots[1].debtId.empty());
1437- EXPECT_EQ(decision.GetPendingBorrowTotal(), 15 * GB);1166+ EXPECT_EQ(decision.GetPendingMigrateTotal(), 15 * GB);
1438 1167 
1439 ubse::mem::controller::MockReleaseNumaCreate();1168 ubse::mem::controller::MockReleaseNumaCreate();
1440 deadline = std::chrono::steady_clock::now() + std::chrono::seconds(5);1169 deadline = std::chrono::steady_clock::now() + std::chrono::seconds(5);
@@ -1452,7 +1181,7 @@ TEST_F(TestProcessMemPidDecision, NodeFreeChangeConsidersInFlightBorrow)
1452 EXPECT_EQ(snapshot.at(pid).borrow.slots[0].status, BorrowSlotStatus::COMPLETED);1181 EXPECT_EQ(snapshot.at(pid).borrow.slots[0].status, BorrowSlotStatus::COMPLETED);
1453 EXPECT_EQ(snapshot.at(pid).borrow.slots[1].status, BorrowSlotStatus::COMPLETED);1182 EXPECT_EQ(snapshot.at(pid).borrow.slots[1].status, BorrowSlotStatus::COMPLETED);
1454 EXPECT_EQ(snapshot.at(pid).borrow.currentRemote, 15 * GB);1183 EXPECT_EQ(snapshot.at(pid).borrow.currentRemote, 15 * GB);
1455- EXPECT_EQ(decision.GetPendingBorrowTotal(), 0u);1184+ EXPECT_EQ(decision.GetPendingMigrateTotal(), 0u);
1456 1185 
1457 MockNodeFree(60);1186 MockNodeFree(60);
1458 std::thread round3Thread([&decision]() { decision.OnDecisionTimer(); });1187 std::thread round3Thread([&decision]() { decision.OnDecisionTimer(); });
@@ -1461,11 +1190,36 @@ TEST_F(TestProcessMemPidDecision, NodeFreeChangeConsidersInFlightBorrow)
1461 snapshot = infoMgr.GetManagedPidCacheSnapshot();1190 snapshot = infoMgr.GetManagedPidCacheSnapshot();
1462 ASSERT_EQ(snapshot.at(pid).borrow.slots.size(), 2u);1191 ASSERT_EQ(snapshot.at(pid).borrow.slots.size(), 2u);
1463 EXPECT_EQ(snapshot.at(pid).borrow.currentRemote, 15 * GB);1192 EXPECT_EQ(snapshot.at(pid).borrow.currentRemote, 15 * GB);
1464- EXPECT_EQ(decision.GetPendingBorrowTotal(), 0u);1193+ EXPECT_EQ(decision.GetPendingMigrateTotal(), 0u);
1465 1194 
1466 ubse::task_executor::MockSetExecutorAsync(false);1195 ubse::task_executor::MockSetExecutorAsync(false);
1467}1196}
1468 1197 
1198+TEST_F(TestProcessMemPidDecision, BorrowAmountRoundedUpToBlockSize)
1199+{
1200+ const pid_t pid = 15970;
1201+ constexpr uint64_t maxGb = 32;
1202+ constexpr double ratio = 0.5;
1203+ AddManagedPid(pid, maxGb, ratio, 40);
1204+ 
1205+ // mock blockSize=256MB: 缺口 4.625GB 非对齐, 向上取整到 4.75GB, shortage 允许多减
1206+ constexpr uint64_t shortageBytes = 4 * GB + GB / 2 + GB / 8;
1207+ constexpr uint64_t expectedBytes = 4 * GB + GB / 2 + GB / 4;
1208+ ubse::mem::controller::MockSetNumaCreateDesc(3, 7, expectedBytes);
1209+ MockNodeFreeBytes(THRESHOLD_BYTES - shortageBytes);
1210+ 
1211+ auto& decision = ProcessMemPidDecision::GetInstance();
1212+ std::thread roundThread([&decision]() { decision.OnDecisionTimer(); });
1213+ roundThread.join();
1214+ ubse::task_executor::MockWaitExecutorIdle();
1215+ 
1216+ auto snapshot = ProcessMemPidInfoManager::GetInstance().GetManagedPidCacheSnapshot();
1217+ ASSERT_EQ(snapshot.at(pid).borrow.slots.size(), 1u);
1218+ EXPECT_EQ(snapshot.at(pid).borrow.slots[0].migratedBytes, expectedBytes);
1219+ EXPECT_EQ(snapshot.at(pid).borrow.slots[0].capacity, expectedBytes);
1220+ EXPECT_EQ(ubse::mem::controller::MockGetLastNumaCreateSize(), expectedBytes);
1221+}
1222+ 
1469TEST_F(TestProcessMemPidDecision, NextCycleBorrowsFullAmountNoReuse)1223TEST_F(TestProcessMemPidDecision, NextCycleBorrowsFullAmountNoReuse)
1470{1224{
1471 const pid_t pid = 15970;1225 const pid_t pid = 15970;
@@ -1514,7 +1268,7 @@ TEST_F(TestProcessMemPidDecision, NextCycleBorrowsFullAmountNoReuse)
1514 EXPECT_FALSE(snapshot.at(pid).borrow.slots[1].debtId.empty());1268 EXPECT_FALSE(snapshot.at(pid).borrow.slots[1].debtId.empty());
1515 EXPECT_NE(snapshot.at(pid).borrow.slots[1].debtId, firstDebtId);1269 EXPECT_NE(snapshot.at(pid).borrow.slots[1].debtId, firstDebtId);
1516 EXPECT_EQ(snapshot.at(pid).processStatus, ProcessStatus::BORROWING);1270 EXPECT_EQ(snapshot.at(pid).processStatus, ProcessStatus::BORROWING);
1517- EXPECT_EQ(decision.GetPendingBorrowTotal(), 4 * GB + GB / 2);1271+ EXPECT_EQ(decision.GetPendingMigrateTotal(), 4 * GB + GB / 2);
1518 1272 
1519 ubse::mem::controller::MockReleaseNumaCreate();1273 ubse::mem::controller::MockReleaseNumaCreate();
1520 ubse::task_executor::MockWaitExecutorIdle();1274 ubse::task_executor::MockWaitExecutorIdle();
@@ -1530,7 +1284,7 @@ TEST_F(TestProcessMemPidDecision, NextCycleBorrowsFullAmountNoReuse)
1530 EXPECT_EQ(snapshot.at(pid).borrow.slots[1].remoteNumaId, 9);1284 EXPECT_EQ(snapshot.at(pid).borrow.slots[1].remoteNumaId, 9);
1531 EXPECT_EQ(snapshot.at(pid).borrow.slots[1].status, BorrowSlotStatus::COMPLETED);1285 EXPECT_EQ(snapshot.at(pid).borrow.slots[1].status, BorrowSlotStatus::COMPLETED);
1532 EXPECT_EQ(snapshot.at(pid).borrow.currentRemote, gb2_5 + 4 * GB + GB / 2);1286 EXPECT_EQ(snapshot.at(pid).borrow.currentRemote, gb2_5 + 4 * GB + GB / 2);
1533- EXPECT_EQ(decision.GetPendingBorrowTotal(), 0u);1287+ EXPECT_EQ(decision.GetPendingMigrateTotal(), 0u);
1534 EXPECT_EQ(snapshot.at(pid).processStatus, ProcessStatus::BORROWED);1288 EXPECT_EQ(snapshot.at(pid).processStatus, ProcessStatus::BORROWED);
1535 1289 
1536 ubse::task_executor::MockSetExecutorAsync(false);1290 ubse::task_executor::MockSetExecutorAsync(false);
@@ -1615,78 +1369,12 @@ TEST_F(TestProcessMemPidDecision, BorrowingSlotExcludedFromMigrateTargets)
1615 ASSERT_EQ(calls.size(), 1u);1369 ASSERT_EQ(calls.size(), 1u);
1616 EXPECT_EQ(calls[0].first, 9);1370 EXPECT_EQ(calls[0].first, 9);
1617 EXPECT_EQ(calls[0].second, gb2 / 1024);1371 EXPECT_EQ(calls[0].second, gb2 / 1024);
1618- EXPECT_EQ(decision.GetPendingBorrowTotal(), gb2_5);1372+ EXPECT_EQ(decision.GetPendingMigrateTotal(), gb2_5);
1619 EXPECT_EQ(snapshot.at(pid).processStatus, ProcessStatus::BORROWING);1373 EXPECT_EQ(snapshot.at(pid).processStatus, ProcessStatus::BORROWING);
1620 1374 
1621 ubse::task_executor::MockSetExecutorAsync(false);1375 ubse::task_executor::MockSetExecutorAsync(false);
1622}1376}
1623 1377 
1624-TEST_F(TestProcessMemPidDecision, CollectCycleRebalanceAfterMaxOvercommit)
1625-{
1626- const pid_t pid = getpid();
1627- constexpr uint64_t maxGb = 32;
1628- constexpr double ratio = 0.5;
1629- auto& decision = ProcessMemPidDecision::GetInstance();
1630- auto& mgr = ProcessMemPidInfoManager::GetInstance();
1631- 
1632- mgr.Init();
1633- AddManagedPid(pid, maxGb, ratio, 40);
1634- 
1635- ubse::mem::controller::MockSetNumaCreateDesc(3, 7, 5 * GB);
1636- MockNodeFree(45);
1637- std::thread round1Thread([&decision]() { decision.OnDecisionTimer(); });
1638- round1Thread.join();
1639- ubse::task_executor::MockWaitExecutorIdle();
1640- 
1641- ubse::mem::controller::MockSetNumaCreateDesc(9, 9, 15 * GB);
1642- MockNodeFree(35);
1643- std::thread round2Thread([&decision]() { decision.OnDecisionTimer(); });
1644- round2Thread.join();
1645- ubse::task_executor::MockWaitExecutorIdle();
1646- 
1647- auto snapshot = mgr.GetManagedPidCacheSnapshot();
1648- ASSERT_EQ(snapshot.at(pid).borrow.slots.size(), 2u);
1649- EXPECT_EQ(snapshot.at(pid).borrow.currentRemote, 20 * GB);
1650- EXPECT_EQ(ubse::smap::MockGetMigrateTargetKb(pid, 3), 5 * GB / 1024);
1651- EXPECT_EQ(ubse::smap::MockGetMigrateTargetKb(pid, 9), 15 * GB / 1024);
1652- 
1653- ProcessMemPidCollect::GetInstance().SetCollectVmRssOverride([pid](PidCollectInfoMap& results, uint64_t) {
1654- results.entries[pid] = {3 * GB / 1024, false};
1655- });
1656- 
1657- ASSERT_EQ(ProcessMemPidCollect::GetInstance().CycleCollectNumaInfo(), UBSE_OK);
1658- auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(5);
1659- while (std::chrono::steady_clock::now() < deadline) {
1660- auto snap = mgr.GetManagedPidCacheSnapshot();
1661- if (snap.at(pid).borrow.currentRemote < 20 * GB) {
1662- break;
1663- }
1664- std::this_thread::sleep_for(std::chrono::milliseconds(1));
1665- }
1666- mgr.UnInit();
1667- 
1668- snapshot = mgr.GetManagedPidCacheSnapshot();
1669- ASSERT_EQ(snapshot.at(pid).borrow.slots.size(), 2u);
1670- ASSERT_LT(snapshot.at(pid).borrow.currentRemote, 20 * GB);
1671- EXPECT_EQ(snapshot.at(pid).vmRss, 3 * GB);
1672- uint64_t expectedRemote = static_cast<uint64_t>(snapshot.at(pid).vmRss * ratio);
1673- EXPECT_EQ(expectedRemote, 3 * GB / 2);
1674- EXPECT_EQ(snapshot.at(pid).borrow.currentRemote, expectedRemote);
1675- EXPECT_EQ(snapshot.at(pid).borrow.slots[0].migratedBytes, 0u);
1676- EXPECT_EQ(snapshot.at(pid).borrow.slots[1].migratedBytes, expectedRemote);
1677- EXPECT_EQ(ubse::smap::MockGetMigrateTargetKb(pid, 3), 0u);
1678- uint64_t expectedTarget9Kb = expectedRemote / 1024 / 4 * 4;
1679- EXPECT_EQ(ubse::smap::MockGetMigrateTargetKb(pid, 9), expectedTarget9Kb);
1680- auto calls = ubse::smap::MockGetMigrateCalls();
1681- ASSERT_EQ(calls.size(), 4u);
1682- EXPECT_EQ(calls[1].first, 3);
1683- EXPECT_EQ(calls[1].second, 5 * GB / 1024);
1684- EXPECT_EQ(calls[2].first, 9);
1685- EXPECT_EQ(calls[2].second, 15 * GB / 1024);
1686- EXPECT_EQ(calls[3].first, 9);
1687- EXPECT_EQ(calls[3].second, expectedTarget9Kb);
1688-}
1689- 
1690TEST_F(TestProcessMemPidDecision, BorrowingPidEligibleWithChangingNodeFree)1378TEST_F(TestProcessMemPidDecision, BorrowingPidEligibleWithChangingNodeFree)
1691{1379{
1692 const pid_t pidA = 15970;1380 const pid_t pidA = 15970;
@@ -1716,7 +1404,7 @@ TEST_F(TestProcessMemPidDecision, BorrowingPidEligibleWithChangingNodeFree)
1716 snapshot = ProcessMemPidInfoManager::GetInstance().GetManagedPidCacheSnapshot();1404 snapshot = ProcessMemPidInfoManager::GetInstance().GetManagedPidCacheSnapshot();
1717 ASSERT_EQ(snapshot.at(pidA).borrow.slots.size(), 1u);1405 ASSERT_EQ(snapshot.at(pidA).borrow.slots.size(), 1u);
1718 EXPECT_EQ(snapshot.at(pidA).borrow.slots[0].status, BorrowSlotStatus::BORROWING);1406 EXPECT_EQ(snapshot.at(pidA).borrow.slots[0].status, BorrowSlotStatus::BORROWING);
1719- EXPECT_EQ(decision.GetPendingBorrowTotal(), 10 * GB);1407+ EXPECT_EQ(decision.GetPendingMigrateTotal(), 10 * GB);
1720 1408 
1721 auto candidates = decision.BuildCandidates(2);1409 auto candidates = decision.BuildCandidates(2);
1722 ASSERT_EQ(candidates.size(), 1u);1410 ASSERT_EQ(candidates.size(), 1u);
@@ -1792,6 +1480,6 @@ TEST_F(TestProcessMemPidDecision, ShortageDeductsPendingBorrow)
1792 ASSERT_EQ(snapshot.at(pidA).borrow.slots.size(), 1u);1480 ASSERT_EQ(snapshot.at(pidA).borrow.slots.size(), 1u);
1793 EXPECT_EQ(snapshot.at(pidA).borrow.slots[0].debtId, "debt-a");1481 EXPECT_EQ(snapshot.at(pidA).borrow.slots[0].debtId, "debt-a");
1794 EXPECT_EQ(snapshot.at(pidA).borrow.slots[0].status, BorrowSlotStatus::BORROWING);1482 EXPECT_EQ(snapshot.at(pidA).borrow.slots[0].status, BorrowSlotStatus::BORROWING);
1795- EXPECT_EQ(ProcessMemPidDecision::GetInstance().GetPendingBorrowTotal(), 15 * GB);1483+ EXPECT_EQ(ProcessMemPidDecision::GetInstance().GetPendingMigrateTotal(), 15 * GB);
1796}1484}
1797} // namespace ubse::ut::process_mem1485} // namespace ubse::ut::process_mem
@@ -1205,97 +1205,4 @@ TEST_F(TestProcessMemPidInfoManager, RemovePidThenNameLifecycleWithMockPids)
1205 ubse::mem::controller::MockClearDebtInfos();1205 ubse::mem::controller::MockClearDebtInfos();
1206}1206}
1207 1207 
1208-TEST_F(TestProcessMemPidInfoManager, RemovePidReBorrowThenRemoveNameWithMockPids)
1209-{
1210- auto& mgr = ProcessMemPidInfoManager::GetInstance();
1211- InjectMockPidLifecycleState(MockPidLifecycleKeys());
1212- mgr.RefreshProcMemConfigCache();
1213- mgr.RebuildManagedPidCache();
1214- 
1215- ubse::mem::controller::MockSetDebtInfos(
1216- {MakeDebtInfo("debt-A", MOCK_PID_A), MakeDebtInfo("debt-B", MOCK_PID_B), MakeDebtInfo("debt-C", MOCK_PID_C)});
1217- std::vector<std::string> freedDebts;
1218- ::process_mem::pid::bridge::ProcessMemPidBridge::rmrsFreeWithMigrate =
1219- [&freedDebts](const std::string& name) -> uint32_t {
1220- freedDebts.push_back(name);
1221- return UBSE_OK;
1222- };
1223- 
1224- auto snapshot = mgr.GetManagedPidCacheSnapshot();
1225- ASSERT_EQ(snapshot.size(), 3u);
1226- 
1227- EXPECT_EQ(mgr.RemoveProcMemConfig(true, std::to_string(MOCK_PID_A)), UBSE_OK);
1228- ASSERT_EQ(freedDebts.size(), 1u);
1229- EXPECT_EQ(freedDebts[0], "debt-A");
1230- snapshot = mgr.GetManagedPidCacheSnapshot();
1231- EXPECT_EQ(snapshot.find(MOCK_PID_A), snapshot.end());
1232- ASSERT_EQ(snapshot.size(), 2u);
1233- 
1234- freedDebts.clear();
1235- InjectMockPidLifecycleState({MOCK_NAME, std::to_string(MOCK_PID_C)});
1236- EXPECT_EQ(mgr.RemoveProcMemConfig(true, std::to_string(MOCK_PID_B)), UBSE_OK);
1237- ASSERT_EQ(freedDebts.size(), 1u);
1238- EXPECT_EQ(freedDebts[0], "debt-B");
1239- EXPECT_EQ(mgr.GetProcMemConfig(true, std::to_string(MOCK_PID_B)).maxMemory, 0u);
1240- snapshot = mgr.GetManagedPidCacheSnapshot();
1241- EXPECT_EQ(snapshot.find(MOCK_PID_B), snapshot.end());
1242- ASSERT_NE(snapshot.find(MOCK_PID_C), snapshot.end());
1243- 
1244- InjectMockPidLifecycleState({std::to_string(MOCK_PID_B), MOCK_NAME, std::to_string(MOCK_PID_C)});
1245- mgr.RebuildManagedPidCache();
1246- snapshot = mgr.GetManagedPidCacheSnapshot();
1247- ASSERT_NE(snapshot.find(MOCK_PID_B), snapshot.end());
1248- EXPECT_TRUE(snapshot.at(MOCK_PID_B).sources & static_cast<uint8_t>(ConfigSource::NAME_CONFIG));
1249- 
1250- auto& decision = ProcessMemPidDecision::GetInstance();
1251- if (decision.borrowExecutor_ == nullptr || !decision.borrowExecutor_->IsStart()) {
1252- decision.borrowExecutor_ = ubse::task_executor::UbseTaskExecutor::Create("ProcMemInfoMgrLifecycle", 2, 64);
1253- decision.borrowExecutor_->Start();
1254- }
1255- if (decision.returnExecutor_ == nullptr || !decision.returnExecutor_->IsStart()) {
1256- decision.returnExecutor_ = ubse::task_executor::UbseTaskExecutor::Create("ProcMemInfoMgrReturn", 2, 64);
1257- decision.returnExecutor_->Start();
1258- }
1259- decision.stopping_ = false;
1260- decision.freeMemoryThresholdBytes_ = 50 * 1073741824ull;
1261- decision.emergencyThresholdBytes_ = 5 * 1073741824ull;
1262- constexpr uint64_t need = 1 * 1073741824ull;
1263- ubse::mem::controller::MockSetNumaCreateDesc(3, 7, need);
1264- collect::PidCollectInfoMap collectMap;
1265- collectMap.entries[MOCK_PID_B].vmRssKb = 2 * 1024 * 1024;
1266- mgr.UpdateManagedPidVmRssBatch(collectMap);
1267- ubse::mem::controller::UbseNodeNumaInfo nodeInfo{};
1268- nodeInfo.nodeId = "NODE0";
1269- nodeInfo.numaId = 0;
1270- nodeInfo.memFree = 45 * 1073741824ull;
1271- ubse::mem::controller::MockSetNodeNumaInfos({nodeInfo});
1272- decision.OnDecisionTimer();
1273- 
1274- snapshot = mgr.GetManagedPidCacheSnapshot();
1275- ASSERT_EQ(snapshot.at(MOCK_PID_B).borrow.slots.size(), 1u);
1276- EXPECT_EQ(snapshot.at(MOCK_PID_B).borrow.slots[0].status, BorrowSlotStatus::COMPLETED);
1277- EXPECT_EQ(snapshot.at(MOCK_PID_B).borrow.slots[0].capacity, need);
1278- EXPECT_EQ(snapshot.at(MOCK_PID_B).borrow.currentRemote, need);
1279- 
1280- auto newDebtBName = ubse::mem::controller::MockGetLastNumaCreateName();
1281- ASSERT_FALSE(newDebtBName.empty());
1282- ubse::mem::controller::MockSetDebtInfos(
1283- {MakeDebtInfo(newDebtBName, MOCK_PID_B), MakeDebtInfo("debt-C", MOCK_PID_C)});
1284- 
1285- freedDebts.clear();
1286- EXPECT_EQ(mgr.RemoveProcMemConfig(false, MOCK_NAME), UBSE_OK);
1287- ASSERT_EQ(freedDebts.size(), 2u);
1288- EXPECT_EQ(freedDebts[0], newDebtBName);
1289- EXPECT_EQ(freedDebts[1], "debt-C");
1290- EXPECT_EQ(mgr.GetProcMemConfig(false, MOCK_NAME).maxMemory, 0u);
1291- InjectMockPidLifecycleState({});
1292- mgr.RebuildManagedPidCache();
1293- snapshot = mgr.GetManagedPidCacheSnapshot();
1294- EXPECT_TRUE(snapshot.empty());
1295- 
1296- ::process_mem::pid::bridge::ProcessMemPidBridge::rmrsFreeWithMigrate = [](const std::string&) -> uint32_t {
1297- return UBSE_OK;
1298- };
1299- ubse::mem::controller::MockClearDebtInfos();
1300-}
1301} // namespace ubse::ut::process_mem1208} // namespace ubse::ut::process_mem