已合并
process_mem: 新模型单元测试补齐与配置文档更新 #1431
process_mem: 新模型单元测试补齐与配置文档更新 #1431
已合并
wangwenlong7创建于 11 天前
50 个文件变更+8988-6349
@@ -3,8 +3,30 @@ ubse.plugin.name=process_mem
3# Name of the SO file on which the process_mem module depends. Fixed value: libprocess_mem.so.3# Name of the SO file on which the process_mem module depends. Fixed value: libprocess_mem.so.
4ubse.plugin.pkg=libprocess_mem.so4ubse.plugin.pkg=libprocess_mem.so
5 5 
6-[process_mem.pid]6+[process_mem]
7-# NUMA distribution collection interval, in seconds. The value range is [1, 3600]. Any invalid value will be reset to 10.7+# 采集周期, /proc 的间隔 (可配范围 1-3600s)
8-collect.interval=108+collect_process_interval=5
9-# Timeout threshold for CREATING memory borrow debts, in seconds. If a borrow remains in CREATING state beyond this timeout, the PID is reset. Any invalid value will be reset to 30.9+# 默认 on, 跳过 root 进程
10-borrow.timeout=1010+filter_root_process=true
11+# 决策周期, 借用/归还的执行间隔 (可配范围 1-3600s)
12+schedule_interval=5
13+# 节点空闲触发借用阈值 (f), 由容量规划推导 f = S − k×d×r, 可配范围 1-4096 GB, 越界回退默认 15,
14+# 建议不超过本机最大内存, 且大于 emergency_free_threshold
15+pressing_free_threshold=15
16+# 借用超时, 每 128MB 粒度的耗时 (ms), 可配范围 1-1800000 (30 分钟), 越界回退默认 1000
17+borrow.timeout=1000
18+# 是否必须同平面借用 (true=必须, 严格过滤; false=优先, 评分加权)
19+borrow.must_same_plane=false
20+ 
21+# 主动归还 (可选, 均有默认值)
22+[process_mem.return]
23+# 连续快速检测周期数: 仅紧急 fast 轮询(200ms)期间记录节点 free, 每 n 次结算一次取最小值,
24+# 窗口最小 free > pressing_free_threshold 时触发主动归还 (默认 300×200ms=60s 观察期), 可配范围 1-4096, 越界回退默认 300
25+observe_cycles=300
26+ 
27+# OOM 快速检测 (可选, 均有默认值)
28+[process_mem.oom]
29+# 紧急状态轮询间隔 (ms), 可配范围 50-60000, 越界回退默认 200
30+collect_node_interval=200
31+# 紧急借用/转嫁阈值 (GB), 可配范围 1-4096, 越界回退默认 5, 建议小于 pressing_free_threshold
32+emergency_free_threshold=5
@@ -94,6 +94,12 @@ api.timeout=1800
94# obmm.memory.offline.timeout=10094# obmm.memory.offline.timeout=100
95# 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.95# 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.
96sei.enable=false96sei.enable=false
97+# Phase 3 process_mem: Max lend capacity per node, in GB. The scheduler checks that
98+# lender.totalLent + borrowAmount ≤ nodeMaxLend before selecting a lender.
99+# 调度器配置 (reuse UBSE mem scheduler): 节点最大借出量 (GB), lender 借出总上限
100+# 可配范围 0-65535, 0 或不配置 = 不限制, 越界回退 0 (不限制)
101+# 默认不限制(注释), 按需由部署方显式开启
102+# scheduler.node_max_lend_gb=0
97 103 
98[ubse.urma]104[ubse.urma]
99# Topology mode used when UBSE pushes topology to URMA UVS in CLOS networking.105# Topology mode used when UBSE pushes topology to URMA UVS in CLOS networking.
@@ -856,7 +856,7 @@ ubsectl display process-mem -t <type>
856 856 
857| 参数名 | 说明 | 取值 |857| 参数名 | 说明 | 取值 |
858|------|------|------|858|------|------|------|
859-| -t<br/>--type | 必选,查询的进程内存配置信息类型 | config |859+| -t<br/>--type | 必选,查询的进程内存配置信息类型 | config:按配置条目展示<br/>proc_detail:按当前纳管进程展开展示 |
860 860 
861**约束限制**861**约束限制**
862 862 
@@ -865,24 +865,31 @@ ubsectl display process-mem -t <type>
865 865 
866**输出信息说明**866**输出信息说明**
867 867 
868-| 字段名 | 字段描述 | 字段取值 |868+| 字段名 | 字段描述 | 字段取值 |
869-| -------------------- | ----------------------- | -------- |869+| ----------- | ------------------------ | -------------- |
870-| pid | 进程ID | 整数 |870+| PID | 进程ID,配置条目未匹配到进程时为 N/A | 整数 / N/A |
871-| evictThreshold | 迁出阈值(%) | 整数 |871+| Name | 进程名 | 字符串 |
872-| targetEvictThreshold | 迁出比例(%) | 整数 |872+| Size | 进程望最大内存大小 | 如 1G、512M、1.5G |
873-| reclaimThreshold | 迁回阈值(%) | 整数 |873+| RemoteRatio | 远端内存占比上限 | 0.00-1.00 |
874-| totalMemoryUsage | 进程期望总内存大小 | 整数 |
875-| srcNuma | 本地NUMA节点ID(可选) | 整数 |
876 874 
877**示例**875**示例**
878 876 
879```bash877```bash
878+# 查看配置条目
880$ ubsectl display process-mem -t config879$ ubsectl display process-mem -t config
881---------------------------------------------------------------------------------------------------880---------------------------------------------------------------------------------------------------
882-pid evictThreshold targetEvictThreshold reclaimThreshold totalMemoryUsage srcNuma881+PID Name Size RemoteRatio
883---------------------------------------------------------------------------------------------------882---------------------------------------------------------------------------------------------------
884-1234 80 50 30 1073741824 1883+1234 N/A 1G 0.50
885-5678 80 50 30 1073741824 N/A884+N/A myapp 512M 0.30
885+---------------------------------------------------------------------------------------------------
886+ 
887+# 查看按当前纳管进程展开的详情(name 配置会展开为实际进程)
888+$ ubsectl display process-mem -t proc_detail
889+---------------------------------------------------------------------------------------------------
890+PID Name Size RemoteRatio
891+---------------------------------------------------------------------------------------------------
892+1234 myapp 1G 0.50
886---------------------------------------------------------------------------------------------------893---------------------------------------------------------------------------------------------------
887```894```
888 895 
@@ -890,50 +897,44 @@ pid evictThreshold targetEvictThreshold reclaimThreshold totalMemo
890 897 
891**描述**898**描述**
892 899 
893-修改指定进程的内存配置信息,包括迁出阈值、预迁出比例、迁回阈值以及进程的期望内存大小。当进程存在子进程时,子进程会继承父进程的内存使用配置。900+修改指定进程(或进程名匹配所有进程)的内存配置信息,包括期望最大内存大小与远端内存占比上限。当进程存在子进程时,子进程会继承父进程的内存使用配置。
894 901 
895**用法**902**用法**
896 903 
897```shell904```shell
898-ubsectl change process-mem -p <pid> -e <evict-thresh> -t <target-evict-thresh> -r <reclaim-thresh> -s <size> [--src-numa <numa-id>]905+ubsectl change process-mem (-p <pid> | -n <name>) -s <size> -r <remote-ratio>
899```906```
900 907 
901**输入参数说明**908**输入参数说明**
902 909 
903| 参数名 | 说明 | 取值 |910| 参数名 | 说明 | 取值 |
904|------|------|------|911|------|------|------|
905-| -p<br/>--pid | 选,目标进程的PID | 整数,范围 1-4194304 |912+| -p<br/>--pid | 与 -n 二,目标进程的PID | 整数,范围 1-4194304 |
906-| -e<br/>--evict-thresh | 选,迁出阈值(%)。当进程总内存使用超过此比例时触发迁出动作 | 整数范围 1-100 |913+| -n<br/>--name | 与 -p 二目标进程名(comm)匹配所有同名进程 | 字符串最长 15 字符,仅含字母/数字/点/冒号/下划线/连字符 |
907-| -t<br/>--target-evict-thresh | 必选,迁出比例(%)。迁出后远端内存占总内存的目标比例 | 数,范围 1-100 |914+| -s<br/>--size | 必选,进程望最大内存大小 | 格式为字+单位(B/K/M/G按1024换算),支持最多两位小数,范围 128M~32G,如 512M、1G、1.5G |
908-| -r<br/>--reclaim-thresh | 必选,迁回阈值(%)。当进程总内存使用低于此比例时,将远端内存全部迁回并释放 | 数,范围 1-100,需比--evict-thresh至少小5 |915+| -r<br/>--remote-ratio | 必选,远端内存占比上限 | 数,范围 0.0-1.0 |
909-| -s<br/>--size | 必选,进程期望总内存大小 | 格式为数字+单位(B/K/M/G),支持最多两位小数,范围 128M~32G,如 512M、1G、1.5G |
910-| -sn<br/>--src-numa | 可选,本地NUMA节点ID。同平面socket会被选为借出socket | 整数 |
911 916 
912**约束限制**917**约束限制**
913 918 
9141. ubsectl只能在root,ubse用户中运行9191. ubsectl只能在root,ubse用户中运行
9152. 需要目标节点的 process_mem 插件正常启动,否则配置无法生效9202. 需要目标节点的 process_mem 插件正常启动,否则配置无法生效
916-3. evict-thresh必须比reclaim-thresh至少大5避免震荡921+3. -p 与 -n 互斥必须二选一
9174. 当进程已有子进程时,子进程会继承该配置9224. 当进程已有子进程时,子进程会继承该配置
918 923 
919**输出信息说明**924**输出信息说明**
920 925 
921-Set successfully / 错误信息926+Set process-mem config successfully / 错误信息
922 927 
923**示例**928**示例**
924 929 
925```bash930```bash
926-# 配置PID为1234的进程,迁出阈值80%,预迁出比例50%,迁回阈值30%,期望内存1G931+# 配置PID为1234的进程,期望最大内存1G,远端占比50%
927-$ ubsectl change process-mem -p 1234 -e 80 -t 50 -r 30 -s 1G932+$ ubsectl change process-mem -p 1234 -s 1G -r 0.5
928-Set successfully933+Set process-mem config successfully
929 934 
930-# 指定本地NUMA节点935+# 按进程名配置(匹配当前节点所有同名进程)
931-$ ubsectl change process-mem -p 1234 -e 80 -t 50 -r 30 -s 1G --src-numa 0936+$ ubsectl change process-mem -n myapp -s 512M -r 0.3
932-Set successfully937+Set process-mem config successfully
933- 
934-# 参数校验失败
935-$ ubsectl change process-mem -p 1234 -e 80 -t 50 -r 76 -s 1G
936-evict-thresh must be at least 5 higher than reclaim-thresh to avoid oscillation
937```938```
938 939 
939### 移除进程内存配置940### 移除进程内存配置
@@ -945,19 +946,21 @@ evict-thresh must be at least 5 higher than reclaim-thresh to avoid oscillation
945**用法**946**用法**
946 947 
947```shell948```shell
948-ubsectl remove process-mem -p <pid>949+ubsectl remove process-mem (-p <pid> | -n <name>)
949```950```
950 951 
951**输入参数说明**952**输入参数说明**
952 953 
953| 参数名 | 说明 | 取值 |954| 参数名 | 说明 | 取值 |
954|------|------|------|955|------|------|------|
955-| -p<br/>--pid | 必选,目标进程的PID | 整数,范围 1-4194304 |956+| -p<br/>--pid | 与 -n 互斥,必选其一,目标进程的PID | 整数,范围 1-4194304 |
957+| -n<br/>--name | 与 -p 互斥,必选其一,目标进程名 | 字符串,匹配当前节点所有同名进程 |
956 958 
957**约束限制**959**约束限制**
958 960 
9591. ubsectl只能在root,ubse用户中运行9611. ubsectl只能在root,ubse用户中运行
9602. 需要目标节点的 process_mem 插件正常启动,否则配置移除操作无法执行9622. 需要目标节点的 process_mem 插件正常启动,否则配置移除操作无法执行
963+3. -p 与 -n 互斥,必须二选一
961 964 
962**输出信息说明**965**输出信息说明**
963 966 
@@ -966,8 +969,13 @@ Unset successfully
966**示例**969**示例**
967 970 
968```bash971```bash
972+# 移除PID为1234的进程配置
969$ ubsectl remove process-mem -p 1234973$ ubsectl remove process-mem -p 1234
970Unset successfully974Unset successfully
975+ 
976+# 按进程名移除配置(匹配当前节点所有同名进程)
977+$ ubsectl remove process-mem -n myapp
978+Unset successfully
971```979```
972 980 
973### 导入共享内存981### 导入共享内存
@@ -120,6 +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`(第 6 项),兼容方式如下:<br>&nbsp;&nbsp;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`(第 6 项),兼容方式如下:<br>&nbsp;&nbsp;lender.balance=true 时取值 reliability-priority,lender.balance=false 取值 free-priority。 |
121| 13 | bandwidth.tolerance | performance-priority 模式下带宽均衡的容忍度阈值。 | 默认值:2 × block.size(通常 256 MB)<br>单位:MB<br>如果取值小于block.size,则取默认值。 | 候选 socket 的当前借出量低于平均值至少该阈值时得分归零(最优),介于平均值与该阈值之间时得分线性插值在 (0,1] 范围。 |121| 13 | bandwidth.tolerance | performance-priority 模式下带宽均衡的容忍度阈值。 | 默认值:2 × block.size(通常 256 MB)<br>单位:MB<br>如果取值小于block.size,则取默认值。 | 候选 socket 的当前借出量低于平均值至少该阈值时得分归零(最优),介于平均值与该阈值之间时得分线性插值在 (0,1] 范围。 |
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 124 
124## URMA配置说明125## URMA配置说明
125 126 
@@ -20,6 +20,22 @@ _ubse_mem_current_type() {
20 fi20 fi
21}21}
22 22 
23+# process-mem: 补全运行中进程 PID(/proc 扫描)
24+_ubse_proc_pids() {
25+ compgen -W "$(ls /proc 2>/dev/null | grep -E '^[0-9]+$' | sort -n)" -- "$1"
26+}
27+ 
28+# process-mem: 补全运行中进程名(/proc/<pid>/comm,去重)
29+_ubse_proc_names() {
30+ local names=""
31+ for name in /proc/[0-9]*/comm; do
32+ local comm
33+ read -r comm < "$name" 2>/dev/null
34+ [ -n "$comm" ] && names="$names $comm"
35+ done
36+ compgen -W "$(echo $names | tr ' ' '\n' | sort -u | tr '\n' ' ')" -- "$1"
37+}
38+ 
23function _ubse_commond_completion() {39function _ubse_commond_completion() {
24 COMPREPLY=()40 COMPREPLY=()
25 41 
@@ -28,7 +44,7 @@ function _ubse_commond_completion() {
28 local prev=${COMP_WORDS[COMP_CWORD-2]}44 local prev=${COMP_WORDS[COMP_CWORD-2]}
29 45 
30commands='create display import delete check change remove detach attach'46commands='create display import delete check change remove detach attach'
31- display_types='topo memory cluster cert node urma urma-qos process-mem'47+ display_types='topo memory cluster node urma urma-qos process-mem'
32 create_types='memory urma-qos'48 create_types='memory urma-qos'
33 delete_types='memory urma-qos'49 delete_types='memory urma-qos'
34 check_types='memory'50 check_types='memory'
@@ -54,7 +70,7 @@ commands='create display import delete check change remove detach attach'
54 return 070 return 0
55 ;;71 ;;
56 'change'|'remove')72 'change'|'remove')
57- COMPREPLY=( $(compgen -W 'cert memory process-mem' -- ${cur}) )73+ COMPREPLY=( $(compgen -W 'cert process-mem' -- ${cur}) )
58 return 074 return 0
59 ;;75 ;;
60 'check')76 'check')
@@ -135,8 +151,8 @@ commands='create display import delete check change remove detach attach'
135 COMPREPLY=( $(compgen -W '--ca-crl-file' -- ${cur}) )151 COMPREPLY=( $(compgen -W '--ca-crl-file' -- ${cur}) )
136 return 0152 return 0
137 ;;153 ;;
138- 'memory'|'process-mem')154+ 'process-mem')
139- COMPREPLY=( $(compgen -W '--pid --evict-thresh --target-evict-thresh --reclaim-thresh --size --src-numa' -- ${cur}) )155+ COMPREPLY=( $(compgen -W '--pid --name --size --remote-ratio' -- ${cur}) )
140 return 0156 return 0
141 ;;157 ;;
142 '*')158 '*')
@@ -147,8 +163,8 @@ commands='create display import delete check change remove detach attach'
147 163 
148 'remove')164 'remove')
149 case ${COMP_WORDS[2]} in165 case ${COMP_WORDS[2]} in
150- 'memory'|'process-mem')166+ 'process-mem')
151- COMPREPLY=( $(compgen -W '--pid' -- ${cur}) )167+ COMPREPLY=( $(compgen -W '--pid --name' -- ${cur}) )
152 return 0168 return 0
153 ;;169 ;;
154 '*')170 '*')
@@ -274,8 +290,8 @@ commands='create display import delete check change remove detach attach'
274 COMPREPLY=( $(compgen -W '-l' -- ${cur}) )290 COMPREPLY=( $(compgen -W '-l' -- ${cur}) )
275 return 0291 return 0
276 ;;292 ;;
277- 'memory'|'process-mem')293+ 'process-mem')
278- COMPREPLY=( $(compgen -W '-p -e -t -r -s -sn' -- ${cur}) )294+ COMPREPLY=( $(compgen -W '-p -n -s -r' -- ${cur}) )
279 return 0295 return 0
280 ;;296 ;;
281 '*')297 '*')
@@ -286,8 +302,8 @@ commands='create display import delete check change remove detach attach'
286 302 
287 'remove')303 'remove')
288 case ${COMP_WORDS[2]} in304 case ${COMP_WORDS[2]} in
289- 'memory'|'process-mem')305+ 'process-mem')
290- COMPREPLY=( $(compgen -W '-p' -- ${cur}) )306+ COMPREPLY=( $(compgen -W '-p -n' -- ${cur}) )
291 return 0307 return 0
292 ;;308 ;;
293 '*')309 '*')
@@ -420,6 +436,33 @@ if [[ ${COMP_WORDS[0]} == *ubsectl ]] && \
420 return 0436 return 0
421 fi437 fi
422 438 
439+ # process-mem: change/remove 的 -p/--pid 补全运行中 PID
440+ if [[ ${COMP_WORDS[0]} == *ubsectl ]] && \
441+ [[ ${COMP_WORDS[1]} == change || ${COMP_WORDS[1]} == remove ]] && \
442+ [[ ${COMP_WORDS[2]} == process-mem ]] && \
443+ [[ "${cmd}" == '-p' || "${cmd}" == '--pid' ]]; then
444+ COMPREPLY=( $(_ubse_proc_pids "$cur") )
445+ return 0
446+ fi
447+ 
448+ # process-mem: change/remove 的 -n/--name 补全运行中进程名
449+ if [[ ${COMP_WORDS[0]} == *ubsectl ]] && \
450+ [[ ${COMP_WORDS[1]} == change || ${COMP_WORDS[1]} == remove ]] && \
451+ [[ ${COMP_WORDS[2]} == process-mem ]] && \
452+ [[ "${cmd}" == '-n' || "${cmd}" == '--name' ]]; then
453+ COMPREPLY=( $(_ubse_proc_names "$cur") )
454+ return 0
455+ fi
456+ 
457+ # process-mem: change 的 -r/--remote-ratio 补全常用比值(对应 -p 0 1 枚举模式)
458+ if [[ ${COMP_WORDS[0]} == *ubsectl ]] && \
459+ [[ ${COMP_WORDS[1]} == change ]] && \
460+ [[ ${COMP_WORDS[2]} == process-mem ]] && \
461+ [[ "${cmd}" == '-r' || "${cmd}" == '--remote-ratio' ]]; then
462+ COMPREPLY=( $(compgen -W '0.3 0.5 0.8 1.0' -- ${cur}) )
463+ return 0
464+ fi
465+ 
423}466}
424 467 
425complete -F _ubse_commond_completion ubsectl468complete -F _ubse_commond_completion ubsectl
@@ -4,6 +4,7 @@ set(PROCESS_MEM_SOURCES
4 process_mem_pid_info_manager.cpp4 process_mem_pid_info_manager.cpp
5 process_mem_pid_collect.cpp5 process_mem_pid_collect.cpp
6 process_mem_pid_config_manager.cpp6 process_mem_pid_config_manager.cpp
7+ process_mem_pid_decision.cpp
7 process_mem_plugin.cpp8 process_mem_plugin.cpp
8)9)
9add_library(process_mem SHARED ${PROCESS_MEM_SOURCES})10add_library(process_mem SHARED ${PROCESS_MEM_SOURCES})
@@ -11,7 +11,9 @@
11 */11 */
12#include "process_mem_pid_bridge.h"12#include "process_mem_pid_bridge.h"
13 13 
14-#include <set>14+#include <algorithm>
15+#include <map>
16+#include <unordered_map>
15 17 
16#include <dlfcn.h>18#include <dlfcn.h>
17#include <linux/capability.h>19#include <linux/capability.h>
@@ -28,51 +30,14 @@
28#include "ubse_node_controller.h"30#include "ubse_node_controller.h"
29#include "ubse_security_module.h"31#include "ubse_security_module.h"
30#include "ubse_serial_util.h"32#include "ubse_serial_util.h"
33+#include "process_mem_pid_collect.h"
31#include "process_mem_pid_config_manager.h"34#include "process_mem_pid_config_manager.h"
35+#include "process_mem_pid_decision.h"
32#include "process_mem_pid_info_manager.h"36#include "process_mem_pid_info_manager.h"
33namespace process_mem::pid::bridge {37namespace process_mem::pid::bridge {
34UBSE_DEFINE_THIS_MODULE("process_mem");38UBSE_DEFINE_THIS_MODULE("process_mem");
35-const std::string PROCESS_MEM_PERMISSION = "";39+// 具名权限对象, 便于在 [auth.role.default] 中按角色授权; 未授权时非内置用户 fail-closed
36- 40+const std::string PROCESS_MEM_PERMISSION = "process_mem";
37-uint32_t ProcessMemPidBridge::MemoryBorrow(def::ProcessMemPidInfo& pidInfo,
38- const ubse::mem::controller::UbseMemBorrower& borrower,
39- const MemoryBorrowRequest& request,
40- ubse::mem::controller::UbseMemNumaDesc& borrowInfo)
41-{
42- ubse::mem::controller::UbseMemNumaCreateOpt opt;
43- constexpr size_t highWatermark = 100;
44- opt.highWatermark = highWatermark;
45- opt.size = request.size;
46- if (memcpy_s(opt.usrInfo, ubse::mem::controller::UBSE_MAX_USR_INFO_LEN, request.usrInfo,
47- ubse::mem::controller::UBSE_MAX_USR_INFO_LEN) != EOK) {
48- UBSE_LOG_ERROR << "memcpy_s usrInfo to opt failed";
49- return UBSE_ERROR;
50- }
51- if (pidInfo.memBorrowInfo.exportSlotId == -1) {
52- auto errCode = UbseMemNumaCreate(request.name, borrower, opt, borrowInfo);
53- if (errCode != UBSE_OK) {
54- UBSE_LOG_ERROR << "UbseMemNumaCreate failed";
55- return errCode;
56- }
57- } else {
58- ubse::mem::controller::UbseMemNumaCandidateOpt candidateOpt{};
59- candidateOpt.highWatermark = highWatermark;
60- candidateOpt.size = request.size;
61- if (memcpy_s(candidateOpt.usrInfo, ubse::mem::controller::UBSE_MAX_USR_INFO_LEN, request.usrInfo,
62- ubse::mem::controller::UBSE_MAX_USR_INFO_LEN) != EOK) {
63- UBSE_LOG_ERROR << "memcpy_s usrInfo to candidateOpt failed";
64- return UBSE_ERROR;
65- }
66- candidateOpt.slotIds.push_back(std::to_string(pidInfo.memBorrowInfo.exportSlotId));
67- auto errCode = UbseMemNumaCreateWithCandidate(request.name, borrower, candidateOpt, borrowInfo);
68- if (errCode != UBSE_OK) {
69- UBSE_LOG_ERROR << "UbseMemNumaCreateWithCandidate failed";
70- return errCode;
71- }
72- }
73- UBSE_LOG_INFO << borrowInfo.name << "UbseMemory Borrow " << borrowInfo.size << " Success";
74- return UBSE_OK;
75-}
76 41 
77uint32_t ProcessMemPidBridge::MemoryReturn(const std::string& name)42uint32_t ProcessMemPidBridge::MemoryReturn(const std::string& name)
78{43{
@@ -90,49 +55,8 @@ uint32_t ProcessMemPidBridge::MemoryReturn(const std::string& name)
90 return UBSE_OK;55 return UBSE_OK;
91}56}
92 57 
93-void GetLentInfo(const ubse::mem::controller::UbseNumaMemoryDebtInfo& info,
94- const ubse::mem::controller::UbseMemNumaDesc& desc, uint32_t& socketId, uint64_t& numaId, bool& flag)
95-{
96- if (info.name == desc.name) {
97- if (info.borrowNodeId == std::to_string(desc.importNode.slotId)) {
98- auto socketList = info.lentSocketIdList;
99- auto lentNumaIdList = info.lentNumaIdList;
100- if (!info.lentSocketIdList.empty() || !info.lentNumaIdList.empty()) {
101- flag = true;
102- socketId = info.lentSocketIdList[0];
103- numaId = info.lentNumaIdList[0];
104- }
105- }
106- }
107-}
108- 
109-uint32_t ProcessMemPidBridge::GetRemoteNumaSocketInfo(const ubse::mem::controller::UbseMemNumaDesc& desc,
110- uint32_t& socketId, uint64_t& numaId)
111-{
112- std::vector<ubse::mem::controller::UbseNumaMemoryDebtInfo> debtInfo{};
113- auto ret = UbseGetNumaMemDebtInfoWithNode(std::to_string(desc.exportNode.slotId), debtInfo);
114- constexpr int retryTime = 3;
115- constexpr int retryIntervalSec = 2;
116- int remainRetry = retryTime;
117- while (ret != UBSE_OK && ret != UBSE_MEMCONTROLLER_ERROR_PAR_SUCCESS && remainRetry > 0) {
118- sleep(retryIntervalSec);
119- ret = UbseGetNumaMemDebtInfoWithNode(std::to_string(desc.exportNode.slotId), debtInfo);
120- --remainRetry;
121- }
122- auto flag = false;
123- for (const auto& info : debtInfo) {
124- GetLentInfo(info, desc, socketId, numaId, flag);
125- }
126- 
127- if (flag) {
128- return UBSE_OK;
129- }
130- return UBSE_ERROR;
131-}
132- 
133namespace {58namespace {
134- 59+ubse::com::UbseComEndpoint GetProcessMemReturnEndpoint(uint16_t opCode, const std::string& nodeId)
135-ubse::com::UbseComEndpoint GetProcessMemFaultComEndpoint(uint16_t opCode, const std::string& nodeId)
136{60{
137 return ubse::com::UbseComEndpoint{61 return ubse::com::UbseComEndpoint{
138 .moduleId = static_cast<uint16_t>(ubse::com::UbseModuleCode::UBSE_MEM_FAULT),62 .moduleId = static_cast<uint16_t>(ubse::com::UbseModuleCode::UBSE_MEM_FAULT),
@@ -140,151 +64,65 @@ ubse::com::UbseComEndpoint GetProcessMemFaultComEndpoint(uint16_t opCode, const
140 .address = nodeId,64 .address = nodeId,
141 };65 };
142}66}
143- 
144-uint32_t SendProcessMemNodeFaultToNode(const std::string& nodeId, const std::string& faultNodeId)
145-{
146- UBSE_LOG_INFO << "[PROCESS_MEM] Sending node fault notify to nodeId=" << nodeId << ", faultNodeId=" << faultNodeId;
147- 
148- ubse::serial::UbseSerialization output;
149- output << faultNodeId;
150- if (!output.Check()) {
151- UBSE_LOG_ERROR << "[PROCESS_MEM] Failed to serialize node fault notify message.";
152- return UBSE_ERROR_SERIALIZE_FAILED;
153- }
154- 
155- uint32_t remoteResult = UBSE_OK;
156- auto respHandler = [&remoteResult](void*, const UbseByteBuffer& respData, uint32_t statusCode) -> void {
157- if (statusCode != UBSE_OK) {
158- UBSE_LOG_ERROR << "[PROCESS_MEM] Node fault notify RPC failed, statusCode=" << statusCode;
159- remoteResult = statusCode;
160- return;
161- }
162- ubse::serial::UbseDeSerialization input(respData.data, respData.len);
163- input >> remoteResult;
164- if (!input.Check()) {
165- UBSE_LOG_ERROR << "[PROCESS_MEM] Failed to deserialize node fault notify response.";
166- remoteResult = UBSE_ERROR;
167- return;
168- }
169- UBSE_LOG_INFO << "[PROCESS_MEM] Node fault notify response, remoteResult=" << remoteResult;
170- };
171- 
172- auto endpoint = GetProcessMemFaultComEndpoint(
173- static_cast<uint16_t>(ubse::com::UbseMemFaultOpCode::UBSE_PROCESS_MEM_NODE_FAULT_NOTIFY), nodeId);
174- UbseByteBuffer request{.data = output.GetBuffer(), .len = output.GetLength(), .freeFunc = nullptr};
175- auto ret = ubse::com::UbseRpcSend(endpoint, request, nullptr, respHandler);
176- if (ret != UBSE_OK) {
177- UBSE_LOG_ERROR << "[PROCESS_MEM] Failed to send node fault notify to " << nodeId << ", ret=" << ret;
178- return ret;
179- }
180- return remoteResult;
181-}
182- 
183} // namespace67} // namespace
184 68 
185-uint32_t ProcessMemPidBridge::FaultHandler(ubse::ras::ALARM_FAULT_TYPE alarmFaultEvent, std::string faultInfo)69+uint32_t ProcessMemPidBridge::SendReturnRequestToNode(const std::string& nodeId,
70+ const std::vector<def::ReturnRequestItem>& items)
186{71{
187- const auto& lentNodeId = faultInfo;72+ ubse::serial::UbseSerialization output;
188- UBSE_LOG_INFO << "FaultHandler: event=" << alarmFaultEvent << ", lentNodeId=" << lentNodeId;73+ output << ubse::serial::array_len_insert(items.size());
189- 74+ for (const auto& item : items) {
190- // 1. 本地处理(如果Master自身也是借入节点,处理本地的PID)75+ output << item.name << item.size;
191- auto ret = manager::ProcessMemPidInfoManager::GetInstance().HandleNodeFaultEvent(lentNodeId);76+ }
192- if (ret != UBSE_OK) {77+ if (!output.Check()) {
193- UBSE_LOG_ERROR << "FaultHandler: HandleNodeFaultEvent failed, ret=" << ret;78+ UBSE_LOG_ERROR << "SendReturnRequestToNode: serialization failed, nodeId=" << nodeId;
194- return ret;79+ return UBSE_ERROR;
195 }80 }
196 81 
197- // 2. 通知其他借入节点处理故障82+ auto endpoint = GetProcessMemReturnEndpoint(
198- ret = NotifyBorrowNodesOnFault(lentNodeId);83+ static_cast<uint16_t>(ubse::com::UbseMemFaultOpCode::UBSE_PROCESS_MEM_RETURN_REQUEST), nodeId);
84+ UbseByteBuffer request{.data = output.GetBuffer(), .len = output.GetLength(), .freeFunc = nullptr};
85+ auto ret = ubse::com::UbseRpcSend(endpoint, request, nullptr, [](void*, const UbseByteBuffer&, uint32_t) {});
199 if (ret != UBSE_OK) {86 if (ret != UBSE_OK) {
200- UBSE_LOG_ERROR << "FaultHandler: NotifyBorrowNodesOnFault failed, ret=" << ret;87+ UBSE_LOG_ERROR << "SendReturnRequestToNode: rpc send failed to " << nodeId << ", ret=" << ret;
201- return ret;
202 }88 }
203- 89+ return ret;
204- return UBSE_OK;
205}90}
206 91 
207-uint32_t ProcessMemPidBridge::NotifyBorrowNodesOnFault(const std::string& lentNodeId)92+void ProcessMemPidBridge::ProcessMemReturnRequestHandler(const UbseByteBuffer& req, UbseByteBuffer& resp)
208{93{
209- // 查询该故障节点相关的全量账本信息,找出所有借入节点94+ ubse::serial::UbseDeSerialization deserializer{req.data, req.len};
210- std::vector<ubse::mem::controller::UbseNumaMemoryDebtInfo> debtInfos{};95+ ubse::serial::common_len itemCount = 0;
211- constexpr int retryTime = 3;96+ deserializer >> ubse::serial::array_len_capture(itemCount);
212- constexpr int retryIntervalSec = 2;97+ if (!deserializer.Check()) {
213- int remainRetry = retryTime;98+ UBSE_LOG_ERROR << "ProcessMemReturnRequestHandler: deserialize failed";
214- auto ret = UbseGetNumaMemDebtInfoWithNode(lentNodeId, debtInfos);
215- while (ret != UBSE_OK && ret != UBSE_MEMCONTROLLER_ERROR_PAR_SUCCESS && remainRetry > 0) {
216- UBSE_LOG_WARN << "[PROCESS_MEM] NotifyBorrowNodesOnFault: query debts retry, ret=" << ret
217- << ", remainRetry=" << remainRetry;
218- sleep(retryIntervalSec);
219- debtInfos.clear();
220- ret = UbseGetNumaMemDebtInfoWithNode(lentNodeId, debtInfos);
221- --remainRetry;
222- }
223- if (ret != UBSE_OK && ret != UBSE_MEMCONTROLLER_ERROR_PAR_SUCCESS) {
224- UBSE_LOG_WARN << "[PROCESS_MEM] NotifyBorrowNodesOnFault: query debts for node " << lentNodeId
225- << " failed after retry, ret=" << ret;
226- return ret;
227- }
228- UBSE_LOG_INFO << "[PROCESS_MEM] NotifyBorrowNodesOnFault: query debts done, ret=" << ret
229- << ", total=" << debtInfos.size();
230- 
231- // 获取当前节点ID,避免通知自身(自身已在FaultHandler中处理)
232- auto currentNodeId = ubse::nodeController::UbseNodeController::GetInstance().GetCurrentNodeId();
233- 
234- // 收集所有与该故障节点相关的借入节点(排除自身)
235- std::set<std::string> targetNodeIds;
236- for (const auto& debtInfo : debtInfos) {
237- if (debtInfo.lentNodeId != lentNodeId) {
238- continue;
239- }
240- if (!debtInfo.borrowNodeId.empty() && debtInfo.borrowNodeId != currentNodeId) {
241- targetNodeIds.insert(debtInfo.borrowNodeId);
242- }
243- }
244- 
245- if (targetNodeIds.empty()) {
246- UBSE_LOG_INFO << "[PROCESS_MEM] No remote borrow nodes to notify for lentNodeId=" << lentNodeId;
247- return UBSE_OK;
248- }
249- 
250- // 向每个借入节点发送故障通知
251- uint32_t firstError = UBSE_OK;
252- for (const auto& nodeId : targetNodeIds) {
253- auto sendRet = SendProcessMemNodeFaultToNode(nodeId, lentNodeId);
254- if (sendRet != UBSE_OK) {
255- UBSE_LOG_ERROR << "[PROCESS_MEM] Failed to notify borrow node " << nodeId << " about fault node "
256- << lentNodeId;
257- if (firstError == UBSE_OK) {
258- firstError = sendRet;
259- }
260- }
261- }
262- return firstError;
263-}
264- 
265-void ProcessMemPidBridge::ProcessMemNodeFaultNotifyHandler(const UbseByteBuffer& req, UbseByteBuffer& resp)
266-{
267- UBSE_LOG_INFO << "[PROCESS_MEM] Received node fault notify from master.";
268- 
269- ubse::serial::UbseDeSerialization input(req.data, req.len);
270- std::string faultNodeId;
271- input >> faultNodeId;
272- if (!input.Check()) {
273- UBSE_LOG_ERROR << "[PROCESS_MEM] Failed to deserialize node fault notify message.";
274 return;99 return;
275 }100 }
276- 101+ if (itemCount == 0) {
277- UBSE_LOG_INFO << "[PROCESS_MEM] Processing node fault notify, faultNodeId=" << faultNodeId;102+ UBSE_LOG_WARN << "ProcessMemReturnRequestHandler: empty item list, ignore";
278- auto ret = manager::ProcessMemPidInfoManager::GetInstance().HandleNodeFaultEvent(faultNodeId);103+ return;
279- UBSE_LOG_INFO << "[PROCESS_MEM] Node fault notify processing done, ret=" << ret;104+ }
280- 105+ constexpr ubse::serial::common_len MAX_RETURN_ITEMS = 4096;
281- ubse::serial::UbseSerialization output;106+ if (itemCount > MAX_RETURN_ITEMS) {
282- output << ret;107+ UBSE_LOG_ERROR << "ProcessMemReturnRequestHandler: invalid itemCount=" << itemCount
283- resp.data = output.GetBuffer(true);108+ << ", max=" << MAX_RETURN_ITEMS;
284- resp.len = output.GetLength();109+ return;
285- resp.freeFunc = [](uint8_t* data) {110+ }
286- delete[] data;111+ std::vector<def::ReturnRequestItem> items;
287- };112+ items.reserve(static_cast<size_t>(itemCount));
113+ for (ubse::serial::common_len i = 0; i < itemCount; ++i) {
114+ def::ReturnRequestItem item;
115+ deserializer >> item.name >> item.size;
116+ if (!deserializer.Check()) {
117+ UBSE_LOG_ERROR << "ProcessMemReturnRequestHandler: deserialize failed at item " << i;
118+ return;
119+ }
120+ items.push_back(std::move(item));
121+ }
122+ auto ret = decision::ProcessMemPidDecision::GetInstance().HandleReturnRequest(items);
123+ if (ret != UBSE_OK) {
124+ UBSE_LOG_ERROR << "ProcessMemReturnRequestHandler: HandleReturnRequest failed, ret=" << ret;
125+ }
288}126}
289 127 
290uint32_t SendPidSetResponse(int successCode, const std::string& errorMsg, uint64_t requestId)128uint32_t SendPidSetResponse(int successCode, const std::string& errorMsg, uint64_t requestId)
@@ -305,112 +143,198 @@ uint32_t SendPidSetResponse(int successCode, const std::string& errorMsg, uint64
305 return ret;143 return ret;
306}144}
307 145 
308-uint32_t ValidateSrcNumaIdOnCurrentNode(const def::ProcessMemPidConfigInfo& configInfo)146+namespace {
309-{147+bool DeserializeProcMemConfigRequest(const api::server::UbseIpcMessage& request,
310- if (!configInfo.srcNumaId.has_value()) {148+ def::ProcessMemNewConfigInfo& outConfig, const char* callerName)
311- return UBSE_OK;
312- }
313- auto curNodeInfo = ubse::nodeController::UbseNodeController::GetInstance().GetCurNode();
314- for (const auto& [numaLoc, _] : curNodeInfo.numaInfos) {
315- if (numaLoc.numaId == configInfo.srcNumaId.value()) {
316- return UBSE_OK;
317- }
318- }
319- UBSE_LOG_ERROR << "srcNumaId " << configInfo.srcNumaId.value() << " not found on current node";
320- return UBSE_ERR_NOT_EXIST;
321-}
322- 
323-uint32_t SendSetPidMapErrorResponse(uint32_t ret, uint64_t requestId)
324-{
325- const char* msg = nullptr;
326- if (ret == UBSE_ERR_NOT_EXIST) {
327- msg = "PID does not exist on current node";
328- } else if (ret == UBSE_ERR_INVALID_ARG) {
329- msg = "PID start time mismatch, process may have been restarted";
330- } else if (ret == UBSE_ERR_RESOURCE_BUSY) {
331- msg = "Cannot modify srcNumaId while borrow debts exist";
332- } else {
333- msg = "Set PID info failed";
334- }
335- UBSE_LOG_ERROR << "SetPidInfoMap failed, " << ubse::log::FormatRetCode(ret);
336- return SendPidSetResponse(0, msg, requestId);
337-}
338- 
339-uint32_t SetPidInfo(const api::server::UbseIpcMessage& request, const api::server::UbseRequestContext& context)
340{149{
341 ubse::serial::UbseDeSerialization deserializer{request.buffer, request.length};150 ubse::serial::UbseDeSerialization deserializer{request.buffer, request.length};
342- def::ProcessMemPidInfo pidInfo{};151+ auto ret = outConfig.Deserialize(deserializer);
343- auto ret = pidInfo.configInfo.DeserializeConfigInfo(deserializer);152+ if (ret != UBSE_OK || !deserializer.Check()) {
153+ UBSE_LOG_ERROR << callerName << ": deserialize failed";
154+ return false;
155+ }
156+ return true;
157+}
158+} // namespace
159+ 
160+uint32_t SetProcMemConfig(const api::server::UbseIpcMessage& request, const api::server::UbseRequestContext& context)
161+{
162+ def::ProcessMemNewConfigInfo newConfig{};
163+ if (!DeserializeProcMemConfigRequest(request, newConfig, "SetProcMemConfig")) {
164+ return SendPidSetResponse(0, "Invalid request", context.requestId);
165+ }
166+ 
167+ if (newConfig.identifier.empty()) {
168+ UBSE_LOG_ERROR << "SetProcMemConfig: identifier is empty";
169+ return SendPidSetResponse(0, "Identifier is empty", context.requestId);
170+ }
171+ 
172+ auto ret = manager::ProcessMemPidInfoManager::GetInstance().SetProcMemConfig(newConfig);
173+ if (ret == UBSE_ERR_NOT_EXIST) {
174+ if (newConfig.isPid) {
175+ return SendPidSetResponse(0, "PID does not exist on current node", context.requestId);
176+ }
177+ return SendPidSetResponse(0, "No running process matches name: " + newConfig.identifier, context.requestId);
178+ }
179+ if (ret == UBSE_ERR_INVALID_ARG) {
180+ return SendPidSetResponse(0, "Invalid process-mem config (size/ratio/name out of range)", context.requestId);
181+ }
344 if (ret != UBSE_OK) {182 if (ret != UBSE_OK) {
345- UBSE_LOG_ERROR << "DeSerialPidDefInfo failed, " << ubse::log::FormatRetCode(ret);183+ UBSE_LOG_ERROR << "SetProcMemConfig failed for " << newConfig.identifier
346- return ret;184+ << ", ret=" << ubse::log::FormatRetCode(ret);
347- }185+ return SendPidSetResponse(0, "Failed to set process-mem config", context.requestId);
348- auto retValidate = ValidateSrcNumaIdOnCurrentNode(pidInfo.configInfo);
349- if (retValidate != UBSE_OK) {
350- return SendPidSetResponse(
351- 0, "srcNumaId " + std::to_string(pidInfo.configInfo.srcNumaId.value()) + " not found on current node",
352- context.requestId);
353- }
354- pidInfo.startTime = manager::ProcessMemPidConfigManager::GetExactStartTime(pidInfo.configInfo.pid);
355- if (pidInfo.startTime == 0) {
356- UBSE_LOG_ERROR << "PID " << pidInfo.configInfo.pid << " does not exist";
357- return SendPidSetResponse(0, "PID does not exist", context.requestId);
358- }
359- ret = manager::ProcessMemPidInfoManager::GetInstance().SetPidInfoMap(pidInfo);
360- if (ret != UBSE_OK) {
361- return SendSetPidMapErrorResponse(ret, context.requestId);
362 }186 }
187+ 
188+ UBSE_LOG_INFO << "SetProcMemConfig: " << (newConfig.isPid ? "pid=" : "name=") << newConfig.identifier
189+ << ", maxMemory=" << newConfig.maxMemory << ", remoteRatio=" << newConfig.remoteRatio;
363 return SendPidSetResponse(1, "", context.requestId);190 return SendPidSetResponse(1, "", context.requestId);
364}191}
365 192 
366-uint32_t PidInfoPrint(const api::server::UbseIpcMessage& request, const api::server::UbseRequestContext& context)193+uint32_t RemoveProcMemConfig(const api::server::UbseIpcMessage& request, const api::server::UbseRequestContext& context)
367{194{
368- ubse::serial::UbseDeSerialization out{request.buffer, request.length};195+ def::ProcessMemNewConfigInfo removeTarget{};
369- std::vector<def::ProcessMemPidInfo> queryInfo{};196+ if (!DeserializeProcMemConfigRequest(request, removeTarget, "RemoveProcMemConfig")) {
370- manager::ProcessMemPidInfoManager::GetInstance().GetAllPidInfo(queryInfo);197+ return SendPidSetResponse(0, "Invalid request", context.requestId);
371- UBSE_LOG_INFO << "info size is " << queryInfo.size();198+ }
372- api::server::UbseIpcMessage response;199+ 
373- ubse::serial::UbseSerialization serializer;200+ auto ret = manager::ProcessMemPidInfoManager::GetInstance().RemoveProcMemConfig(removeTarget.isPid,
374- serializer << (ubse::serial::right_v<size_t>(queryInfo.size()));201+ removeTarget.identifier);
375- for (const auto& info : queryInfo) {202+ if (ret != UBSE_OK) {
376- auto ret = info.configInfo.SerializeConfigInfo(serializer);203+ UBSE_LOG_ERROR << "RemoveProcMemConfig failed for " << (removeTarget.isPid ? "pid=" : "name=")
377- if (ret != UBSE_OK) {204+ << removeTarget.identifier << ", ret=" << ubse::log::FormatRetCode(ret);
378- UBSE_LOG_ERROR << "SerialPidDefInfo failed, " << ubse::log::FormatRetCode(ret);205+ if (ret == UBSE_ERR_NOT_EXIST) {
379- auto ret = SendResponse(UBSE_OK, context.requestId, response);206+ if (removeTarget.isPid) {
380- if (ret != UBSE_OK) {207+ return SendPidSetResponse(0, "pid " + removeTarget.identifier + " is not managed by process_mem",
381- UBSE_LOG_ERROR << "Send response failed, " << ubse::log::FormatRetCode(ret);208+ context.requestId);
382- return ret;
383 }209 }
210+ return SendPidSetResponse(0, "name '" + removeTarget.identifier + "' is not configured", context.requestId);
211+ }
212+ return SendPidSetResponse(0, "Failed to remove process-mem config", context.requestId);
213+ }
214+ 
215+ UBSE_LOG_INFO << "RemoveProcMemConfig: " << (removeTarget.isPid ? "pid=" : "name=") << removeTarget.identifier
216+ << " removed successfully";
217+ return SendPidSetResponse(1, "", context.requestId);
218+}
219+ 
220+namespace {
221+ 
222+void SortConfigEntries(std::vector<def::ProcessMemDisplayEntry>& entries)
223+{
224+ std::sort(entries.begin(), entries.end(), [](const auto& a, const auto& b) {
225+ if ((a.pid > 0) != (b.pid > 0)) {
226+ return a.pid > 0;
227+ }
228+ if (a.pid > 0) {
229+ return a.pid < b.pid;
230+ }
231+ return a.name < b.name;
232+ });
233+}
234+ 
235+void SortPidEntries(std::vector<def::ProcessMemDisplayEntry>& entries)
236+{
237+ std::sort(entries.begin(), entries.end(), [](const auto& a, const auto& b) { return a.pid < b.pid; });
238+}
239+ 
240+uint32_t SendDisplayEntries(const api::server::UbseIpcMessage& request, const api::server::UbseRequestContext& context,
241+ const std::vector<def::ProcessMemDisplayEntry>& entries)
242+{
243+ api::server::UbseIpcMessage response{nullptr, 0};
244+ ubse::serial::UbseSerialization serializer;
245+ size_t entryCount = entries.size();
246+ serializer << entryCount;
247+ for (const auto& entry : entries) {
248+ auto serRet = entry.Serialize(serializer);
249+ if (serRet != UBSE_OK) {
250+ UBSE_LOG_ERROR << "DisplayProcMem: entry serialize failed for pid=" << entry.pid;
251+ return serRet;
384 }252 }
385 }253 }
386 254 
387 response.buffer = serializer.GetBuffer();255 response.buffer = serializer.GetBuffer();
388 if (!response.buffer) {256 if (!response.buffer) {
389- UBSE_LOG_ERROR << "Serialization response failed.";257+ UBSE_LOG_ERROR << "DisplayProcMem: serialization response failed.";
390 return UBSE_ERROR_NULLPTR;258 return UBSE_ERROR_NULLPTR;
391 }259 }
392 response.length = static_cast<uint32_t>(serializer.GetLength());260 response.length = static_cast<uint32_t>(serializer.GetLength());
393- auto ret = SendResponse(UBSE_OK, context.requestId, response);261+ auto sendRet = SendResponse(UBSE_OK, context.requestId, response);
394- if (ret != UBSE_OK) {262+ if (sendRet != UBSE_OK) {
395- UBSE_LOG_ERROR << "Send response failed, " << ubse::log::FormatRetCode(ret);263+ UBSE_LOG_ERROR << "DisplayProcMem: Send response failed, " << ubse::log::FormatRetCode(sendRet);
396 }264 }
397- return ret;265+ return sendRet;
398}266}
399 267 
400-uint32_t UnSetPidInfoPrint(const api::server::UbseIpcMessage& request, const api::server::UbseRequestContext& context)268+} // namespace
269+ 
270+void BuildConfigEntries(std::vector<def::ProcessMemDisplayEntry>& entries,
271+ const std::vector<def::ProcessMemNewConfigInfo>& newConfigs)
401{272{
402- ubse::serial::UbseDeSerialization out{request.buffer, request.length};273+ for (const auto& cfg : newConfigs) {
403- pid_t pid{};274+ if (cfg.isPid) {
404- out >> pid;275+ auto pidOpt = def::ParsePidFromIdentifier(cfg.identifier);
405- auto ret = manager::ProcessMemPidInfoManager::GetInstance().UnsetPidInfo(pid);276+ if (!pidOpt.has_value()) {
406- if (ret == UBSE_ERR_NOT_EXIST) {277+ UBSE_LOG_WARN << "BuildConfigEntries: skipping PID config with unparseable identifier="
407- UBSE_LOG_ERROR << "UnsetPidInfo failed, " << ubse::log::FormatRetCode(ret);278+ << cfg.identifier;
408- return SendPidSetResponse(0, "PID is not configured", context.requestId);279+ continue;
409- } else if (ret != UBSE_OK) {280+ }
410- UBSE_LOG_ERROR << "UnsetPidInfo failed, " << ubse::log::FormatRetCode(ret);281+ def::ProcessMemDisplayEntry entry;
411- return SendPidSetResponse(0, "Failed to unset PID info", context.requestId);282+ entry.pid = static_cast<int32_t>(pidOpt.value());
283+ entry.name = "N/A";
284+ entry.maxMemory = cfg.maxMemory;
285+ entry.remoteRatio = cfg.remoteRatio;
286+ entries.push_back(entry);
287+ } else {
288+ def::ProcessMemDisplayEntry entry;
289+ entry.pid = 0;
290+ entry.name = cfg.identifier;
291+ entry.maxMemory = cfg.maxMemory;
292+ entry.remoteRatio = cfg.remoteRatio;
293+ entries.push_back(entry);
294+ }
412 }295 }
413- return SendPidSetResponse(1, "", context.requestId);296+ SortConfigEntries(entries);
297+}
298+ 
299+void BuildProcDetailEntries(std::vector<def::ProcessMemDisplayEntry>& entries,
300+ const std::map<pid_t, def::ManagedPidEntry>& managedSnapshot)
301+{
302+ // 仅回显 process_mem 真正托管(匹配)到的进程, 取托管缓存而非配置/实时 /proc 扫描;
303+ // pid 配置命中时 name 强制 N/A (与旧行为一致), 否则显示实际匹配的配置名
304+ for (const auto& [pid, entry] : managedSnapshot) {
305+ def::ProcessMemDisplayEntry display;
306+ display.pid = static_cast<int32_t>(pid);
307+ bool hasPidConfig = (entry.sources & static_cast<uint8_t>(def::ConfigSource::PID_CONFIG)) != 0;
308+ display.name = hasPidConfig ? "N/A" : (entry.nameConfigName.empty() ? "N/A" : entry.nameConfigName);
309+ display.maxMemory = entry.maxMemory;
310+ display.remoteRatio = entry.remoteRatio;
311+ entries.push_back(display);
312+ }
313+ SortPidEntries(entries);
314+}
315+ 
316+uint32_t DisplayProcMemConfig(const api::server::UbseIpcMessage& request,
317+ const api::server::UbseRequestContext& context)
318+{
319+ std::vector<def::ProcessMemNewConfigInfo> newConfigs;
320+ manager::ProcessMemPidInfoManager::GetInstance().GetAllProcMemConfigs(newConfigs);
321+ 
322+ std::vector<def::ProcessMemDisplayEntry> entries;
323+ BuildConfigEntries(entries, newConfigs);
324+ 
325+ UBSE_LOG_INFO << "DisplayProcMemConfig: returning " << entries.size() << " config entries";
326+ return SendDisplayEntries(request, context, entries);
327+}
328+ 
329+uint32_t DisplayProcMemDetail(const api::server::UbseIpcMessage& request,
330+ const api::server::UbseRequestContext& context)
331+{
332+ std::vector<def::ProcessMemDisplayEntry> entries;
333+ auto managedSnapshot = manager::ProcessMemPidInfoManager::GetInstance().GetManagedPidCacheSnapshot();
334+ BuildProcDetailEntries(entries, managedSnapshot);
335+ 
336+ UBSE_LOG_INFO << "DisplayProcMemDetail: returning " << entries.size() << " managed entries";
337+ return SendDisplayEntries(request, context, entries);
414}338}
415 339 
416namespace {340namespace {
@@ -427,86 +351,85 @@ bool LoadMemPoolingLibrary()
427 reinterpret_cast<int (*)(const uint16_t, const std::vector<pid_t>&, int)>(dlsym(handle, "UBSRMRSRemove"));351 reinterpret_cast<int (*)(const uint16_t, const std::vector<pid_t>&, int)>(dlsym(handle, "UBSRMRSRemove"));
428 ProcessMemPidBridge::rmrsFreeWithMigrate =352 ProcessMemPidBridge::rmrsFreeWithMigrate =
429 reinterpret_cast<uint32_t (*)(const std::string)>(dlsym(handle, "UBSRMRSMemFreeWithMigrate"));353 reinterpret_cast<uint32_t (*)(const std::string)>(dlsym(handle, "UBSRMRSMemFreeWithMigrate"));
354+ ProcessMemPidBridge::rmrsRemoteToRemote =
355+ reinterpret_cast<int (*)(const mempooling::smap::MigrateEscapeMsg&)>(dlsym(handle, "UBSRMRSRemoteNumaMigrate"));
356+ ProcessMemPidBridge::rmrsProcessConfigQuery =
357+ reinterpret_cast<int (*)(int, mempooling::smap::ProcessPayload*, int, int*)>(
358+ dlsym(handle, "UBSRMRSProcessConfigQuery"));
430 if (!ProcessMemPidBridge::rmrsMigrateOut || !ProcessMemPidBridge::rmrsRemove) {359 if (!ProcessMemPidBridge::rmrsMigrateOut || !ProcessMemPidBridge::rmrsRemove) {
431 dlclose(handle);360 dlclose(handle);
432 ProcessMemPidBridge::memPoolingHandle = nullptr;361 ProcessMemPidBridge::memPoolingHandle = nullptr;
433 return false;362 return false;
434 }363 }
364+ if (!ProcessMemPidBridge::rmrsRemoteToRemote) {
365+ UBSE_LOG_WARN << "UBSRMRSRemoteNumaMigrate not exported by libmempooling.so, "
366+ "remote-to-remote return will be skipped";
367+ }
435 return true;368 return true;
436}369}
437 370 
438-void RegisterLocalStateHandler()
439-{
440- // 注册前先检查当前节点状态,避免状态已变更但回调未注册
441- auto curNode = ubse::nodeController::UbseNodeController::GetInstance().GetCurNode();
442- if (curNode.localState == ubse::nodeController::UbseNodeLocalState::UBSE_NODE_READY) {
443- process_mem::manager::ProcessMemPidInfoManager::GetInstance().RecoverAllDebtInfoData();
444- }
445- 
446- auto localStateHandler = [](const ubse::nodeController::UbseNodeInfo& node) -> uint32_t {
447- if (process_mem::manager::ProcessMemPidInfoManager::GetInstance().IsRecoverCompleted()) {
448- return UBSE_OK;
449- }
450- if (node.localState == ubse::nodeController::UbseNodeLocalState::UBSE_NODE_READY) {
451- process_mem::manager::ProcessMemPidInfoManager::GetInstance().RecoverAllDebtInfoData();
452- } else {
453- UBSE_LOG_INFO << "localStateHandler: state=" << static_cast<int>(node.localState)
454- << ", not READY yet, skip recovery";
455- }
456- return UBSE_OK;
457- };
458- ubse::nodeController::UbseNodeController::GetInstance().RegLocalStateNotifyHandler(localStateHandler);
459-}
460- 
461-void RegisterFaultHandlers()
462-{
463- ubse::ras::AlarmHandler panicHandler;
464- panicHandler.alarmFaultEvent = ubse::ras::ALARM_PANIC_EVENT;
465- panicHandler.name = "ProcessMemPanic";
466- panicHandler.handler = ProcessMemPidBridge::FaultHandler;
467- panicHandler.priority = ubse::ras::AlarmHandlerPriority::HIGH;
468- ubse::ras::RegisterAlarmFaultHandler(panicHandler);
469- 
470- ubse::ras::AlarmHandler rebootHandler;
471- rebootHandler.alarmFaultEvent = ubse::ras::ALARM_KERNEL_REBOOT_EVENT;
472- rebootHandler.name = "ProcessMemKernelReboot";
473- rebootHandler.handler = ProcessMemPidBridge::FaultHandler;
474- rebootHandler.priority = ubse::ras::AlarmHandlerPriority::HIGH;
475- ubse::ras::RegisterAlarmFaultHandler(rebootHandler);
476-}
477} // namespace371} // namespace
478 372 
373+uint32_t ProcessMemPidBridge::RegisterConfigIpcHandlers()
374+{
375+ auto ret =
376+ api::server::RegisterIpcHandler(UBSE_MEM, UBSE_MEM_CLI_PROC_MEM_SET, SetProcMemConfig, PROCESS_MEM_PERMISSION);
377+ if (ret != UBSE_OK) {
378+ UBSE_LOG_ERROR << "Register SetProcMemConfig IPC Handler failed, " << ubse::log::FormatRetCode(ret);
379+ return ret;
380+ }
381+ ret = api::server::RegisterIpcHandler(UBSE_MEM, UBSE_MEM_CLI_PROC_MEM_REMOVE, RemoveProcMemConfig,
382+ PROCESS_MEM_PERMISSION);
383+ if (ret != UBSE_OK) {
384+ UBSE_LOG_ERROR << "Register RemoveProcMemConfig IPC Handler failed, " << ubse::log::FormatRetCode(ret);
385+ return ret;
386+ }
387+ ret = api::server::RegisterIpcHandler(UBSE_MEM, UBSE_MEM_CLI_PROC_MEM_DISPLAY_CONFIG, DisplayProcMemConfig,
388+ PROCESS_MEM_PERMISSION);
389+ if (ret != UBSE_OK) {
390+ UBSE_LOG_ERROR << "Register DisplayProcMemConfig IPC Handler failed, " << ubse::log::FormatRetCode(ret);
391+ return ret;
392+ }
393+ ret = api::server::RegisterIpcHandler(UBSE_MEM, UBSE_MEM_CLI_PROC_MEM_DISPLAY_DETAIL, DisplayProcMemDetail,
394+ PROCESS_MEM_PERMISSION);
395+ if (ret != UBSE_OK) {
396+ UBSE_LOG_ERROR << "Register DisplayProcMemDetail IPC Handler failed, " << ubse::log::FormatRetCode(ret);
397+ return ret;
398+ }
399+ return UBSE_OK;
400+}
401+ 
479uint32_t ProcessMemPidBridge::Init()402uint32_t ProcessMemPidBridge::Init()
480{403{
404+ auto ret = RegisterConfigIpcHandlers();
405+ if (ret != UBSE_OK) {
406+ return ret;
407+ }
408+ 
409+ auto returnEndpoint = GetProcessMemReturnEndpoint(
410+ static_cast<uint16_t>(ubse::com::UbseMemFaultOpCode::UBSE_PROCESS_MEM_RETURN_REQUEST), "");
411+ auto rpcRet = ubse::com::UbseRegRpcService(returnEndpoint, ProcessMemPidBridge::ProcessMemReturnRequestHandler);
412+ if (rpcRet != UBSE_OK) {
413+ UBSE_LOG_ERROR << "Register ProcessMemReturnRequest RPC handler failed, " << ubse::log::FormatRetCode(rpcRet);
414+ return rpcRet;
415+ }
416+ 
481 if (!LoadMemPoolingLibrary()) {417 if (!LoadMemPoolingLibrary()) {
482- return UBSE_ERROR;418+ UBSE_LOG_WARN << "Failed to load libmempooling.so, memory borrowing/migration will be unavailable";
419+ process_mem::manager::ProcessMemPidInfoManager::GetInstance().RefreshProcMemConfigCache();
420+ return UBSE_OK;
483 }421 }
484 422 
485 std::vector<__u32> dacReadSearchCap = {CAP_DAC_READ_SEARCH};423 std::vector<__u32> dacReadSearchCap = {CAP_DAC_READ_SEARCH};
486 ubse::security::UbseSecurityModule::ModifyEffectiveCapabilities(dacReadSearchCap, true);424 ubse::security::UbseSecurityModule::ModifyEffectiveCapabilities(dacReadSearchCap, true);
487 425 
488- auto ret =
489- api::server::RegisterIpcHandler(UBSE_MEM, UBSE_MEM_CLI_PID_SET_THRESHOLD, SetPidInfo, PROCESS_MEM_PERMISSION);
490- ret |= api::server::RegisterIpcHandler(UBSE_MEM, UBSE_MEM_CLI_PRINT_PID_INFO, PidInfoPrint, PROCESS_MEM_PERMISSION);
491- ret |= api::server::RegisterIpcHandler(UBSE_MEM, UBSE_MEM_CLI_PID_UNSET, UnSetPidInfoPrint, PROCESS_MEM_PERMISSION);
492- if (ret != UBSE_OK) {
493- UBSE_LOG_ERROR << "Register IPC Handler failed, " << ubse::log::FormatRetCode(ret);
494- return ret;
495- }
496- 
497- // 注册process_mem节点故障通知RPC处理器(所有节点均需注册,用于接收Master的通知)
498- auto rpcEndpoint = GetProcessMemFaultComEndpoint(
499- static_cast<uint16_t>(ubse::com::UbseMemFaultOpCode::UBSE_PROCESS_MEM_NODE_FAULT_NOTIFY), "");
500- auto rpcRet = ubse::com::UbseRegRpcService(rpcEndpoint, ProcessMemPidBridge::ProcessMemNodeFaultNotifyHandler);
501- if (rpcRet != UBSE_OK) {
502- UBSE_LOG_ERROR << "[PROCESS_MEM] Register node fault notify RPC handler failed, "
503- << ubse::log::FormatRetCode(rpcRet);
504- return rpcRet;
505- }
506- 
507- RegisterFaultHandlers();
508 process_mem::manager::ProcessMemPidInfoManager::GetInstance().Init();426 process_mem::manager::ProcessMemPidInfoManager::GetInstance().Init();
509- RegisterLocalStateHandler();427+ 
428+ auto decisionRet = process_mem::decision::ProcessMemPidDecision::GetInstance().Init();
429+ if (decisionRet != UBSE_OK) {
430+ UBSE_LOG_ERROR << "ProcessMemPidDecision Init failed, " << ubse::log::FormatRetCode(decisionRet);
431+ }
432+ 
510 return UBSE_OK;433 return UBSE_OK;
511}434}
512 435 
@@ -515,6 +438,7 @@ uint32_t ProcessMemPidBridge::UnInit()
515 if (memPoolingHandle != nullptr) {438 if (memPoolingHandle != nullptr) {
516 dlclose(memPoolingHandle);439 dlclose(memPoolingHandle);
517 }440 }
441+ process_mem::decision::ProcessMemPidDecision::GetInstance().UnInit();
518 process_mem::manager::ProcessMemPidInfoManager::GetInstance().UnInit();442 process_mem::manager::ProcessMemPidInfoManager::GetInstance().UnInit();
519 return UBSE_OK;443 return UBSE_OK;
520}444}
@@ -16,7 +16,6 @@
16 16 
17#include "ubse_def.h"17#include "ubse_def.h"
18#include "ubse_mem_controller.h"18#include "ubse_mem_controller.h"
19-#include "ubse_ras.h"
20#include "mp_smap_module.h"19#include "mp_smap_module.h"
21#include "process_mem_pid_manager_def.h"20#include "process_mem_pid_manager_def.h"
22 21 
@@ -26,35 +25,29 @@ constexpr const char* MEMPOOLING_PATH = "/usr/lib64/libmempooling.so";
26using MigrateOut = std::function<int(const std::vector<mempooling::smap::MigrateOutPayload>&, int)>;25using MigrateOut = std::function<int(const std::vector<mempooling::smap::MigrateOutPayload>&, int)>;
27using Remove = std::function<int(const uint16_t, const std::vector<pid_t>&, int)>;26using Remove = std::function<int(const uint16_t, const std::vector<pid_t>&, int)>;
28using NoMigrateBack = std::function<uint32_t(const std::string&)>;27using NoMigrateBack = std::function<uint32_t(const std::string&)>;
29- 28+using RemoteToRemote = std::function<int(const mempooling::smap::MigrateEscapeMsg&)>;
30-struct MemoryBorrowRequest {29+using ProcessConfigQuery = std::function<int(int, mempooling::smap::ProcessPayload*, int, int*)>;
31- std::string name;
32- uint64_t size;
33- uint8_t usrInfo[ubse::mem::controller::UBSE_MAX_USR_INFO_LEN]{};
34-};
35 30 
36class ProcessMemPidBridge {31class ProcessMemPidBridge {
37public:32public:
38 static uint32_t Init();33 static uint32_t Init();
39 static uint32_t UnInit();34 static uint32_t UnInit();
40- static uint32_t MemoryBorrow(def::ProcessMemPidInfo& pidInfo,35+ 
41- const ubse::mem::controller::UbseMemBorrower& borrower,36+ static uint32_t RegisterConfigIpcHandlers();
42- const MemoryBorrowRequest& request,
43- ubse::mem::controller::UbseMemNumaDesc& borrowInfo);
44 37 
45 static uint32_t MemoryReturn(const std::string& name);38 static uint32_t MemoryReturn(const std::string& name);
46 39 
47- static uint32_t GetRemoteNumaSocketInfo(const ubse::mem::controller::UbseMemNumaDesc& desc, uint32_t& socketId,40+ static uint32_t SendReturnRequestToNode(const std::string& nodeId,
48- uint64_t& numaId);41+ const std::vector<def::ReturnRequestItem>& items);
42+ 
43+ static void ProcessMemReturnRequestHandler(const UbseByteBuffer& req, UbseByteBuffer& resp);
44+ 
49 inline static MigrateOut rmrsMigrateOut;45 inline static MigrateOut rmrsMigrateOut;
50 inline static Remove rmrsRemove;46 inline static Remove rmrsRemove;
51 inline static NoMigrateBack rmrsFreeWithMigrate;47 inline static NoMigrateBack rmrsFreeWithMigrate;
48+ inline static RemoteToRemote rmrsRemoteToRemote;
49+ inline static ProcessConfigQuery rmrsProcessConfigQuery;
52 inline static void* memPoolingHandle = nullptr;50 inline static void* memPoolingHandle = nullptr;
53- static uint32_t FaultHandler(ubse::ras::ALARM_FAULT_TYPE alarmFaultEvent, std::string faultInfo);
54- 
55- static void ProcessMemNodeFaultNotifyHandler(const UbseByteBuffer& req, UbseByteBuffer& resp);
56- 
57- static uint32_t NotifyBorrowNodesOnFault(const std::string& lentNodeId);
58};51};
59} // namespace process_mem::pid::bridge52} // namespace process_mem::pid::bridge
60#endif53#endif
@@ -12,8 +12,12 @@
12 12 
13#include "process_mem_pid_collect.h"13#include "process_mem_pid_collect.h"
14 14 
15+#include <dirent.h>
15#include <securec.h>16#include <securec.h>
17+#include <exception>
18+#include <filesystem>
16#include <fstream>19#include <fstream>
20+#include <sstream>
17#include <string>21#include <string>
18 22 
19#include "ubse_conf.h"23#include "ubse_conf.h"
@@ -21,6 +25,7 @@
21#include "ubse_logger.h"25#include "ubse_logger.h"
22#include "ubse_timer.h"26#include "ubse_timer.h"
23#include "process_mem_pid_config_manager.h"27#include "process_mem_pid_config_manager.h"
28+#include "process_mem_pid_decision.h"
24#include "process_mem_pid_info_manager.h"29#include "process_mem_pid_info_manager.h"
25 30 
26namespace process_mem::collect {31namespace process_mem::collect {
@@ -29,7 +34,6 @@ UBSE_DEFINE_THIS_MODULE("process_mem");
29using namespace ubse::timer;34using namespace ubse::timer;
30using namespace process_mem::manager;35using namespace process_mem::manager;
31 36 
32-const uint32_t PROCESS_MEM_COLLECT_INTERVAL = 10;
33const size_t DEFAULT_PAGE_SIZE = 4096;37const size_t DEFAULT_PAGE_SIZE = 4096;
34 38 
35std::vector<pid_t> GetChildrenPidsFallback(pid_t parentPid)39std::vector<pid_t> GetChildrenPidsFallback(pid_t parentPid)
@@ -54,7 +58,6 @@ std::vector<pid_t> GetChildrenPidsFallback(pid_t parentPid)
54 58 
55std::vector<pid_t> GetChildrenPids(pid_t parentPid)59std::vector<pid_t> GetChildrenPids(pid_t parentPid)
56{60{
57- // 优先尝试 /proc/pid/children(高效)
58 std::string path = "/proc/" + std::to_string(parentPid) + "/task/" + std::to_string(parentPid) + "/children";61 std::string path = "/proc/" + std::to_string(parentPid) + "/task/" + std::to_string(parentPid) + "/children";
59 std::ifstream file(path);62 std::ifstream file(path);
60 if (file.is_open()) {63 if (file.is_open()) {
@@ -69,117 +72,537 @@ std::vector<pid_t> GetChildrenPids(pid_t parentPid)
69 return children;72 return children;
70 }73 }
71 }74 }
72- // 回退到 pgrep
73 return GetChildrenPidsFallback(parentPid);75 return GetChildrenPidsFallback(parentPid);
74}76}
75 77 
76-uint32_t GetPidInfoByCollect(def::ProcessMemPidInfo& pidInfo, CollectInfoMap& pidCollectInfo)
77-{
78- std::unordered_map<uint32_t, size_t> numaDistribution{};
79- auto ret =
80- ProcessMemPidCollect::GetInstance().CollectProcessNumaMemDistribution(pidInfo.configInfo.pid, numaDistribution);
81- // 读取失败认为进程不存在
82- if (ret != UBSE_OK) {
83- UBSE_LOG_ERROR << "pid=" << std::to_string(pidInfo.configInfo.pid) << " collect failed=" << ret;
84- pidInfo.processStatus = def::ProcessStatus::INACTIVE;
85- return ret;
86- }
87- pidInfo.processStatus = def::ProcessStatus::IDLE;
88- pidCollectInfo[pidInfo.configInfo.pid] = numaDistribution;
89- return UBSE_OK;
90-}
91- 
92uint32_t ProcessMemPidCollect::Init()78uint32_t ProcessMemPidCollect::Init()
93{79{
94- const std::string section = "process_mem.pid";80+ const std::string section = "process_mem";
95- const std::string configKey = "collect.interval";81+ const std::string collectIntervalKey = "collect_process_interval";
96 uint32_t collectInterval = 0;82 uint32_t collectInterval = 0;
97- ubse::config::UbseGetUInt(section, configKey, collectInterval);83+ ubse::config::UbseGetUInt(section, collectIntervalKey, collectInterval);
84+ constexpr uint32_t defaultCollectInterval = 5;
98 constexpr uint32_t maxCollectInterval = 3600;85 constexpr uint32_t maxCollectInterval = 3600;
99- constexpr uint32_t defaultCollectInterval = 10;
100 if (collectInterval < 1 || collectInterval > maxCollectInterval) {86 if (collectInterval < 1 || collectInterval > maxCollectInterval) {
101 collectInterval = defaultCollectInterval;87 collectInterval = defaultCollectInterval;
102 }88 }
89+ 
90+ const std::string filterRootKey = "filter_root_process";
91+ filterRootProcesses_ = true;
92+ ubse::config::UbseGetBool(section, filterRootKey, filterRootProcesses_);
93+ 
94+ collectExecutor_ = ubse::task_executor::UbseTaskExecutor::Create("ProcMemCollect", 1, 16);
95+ if (collectExecutor_ == nullptr || !collectExecutor_->Start()) {
96+ UBSE_LOG_ERROR << "ProcMemCollect: collectExecutor start failed";
97+ return UBSE_ERROR;
98+ }
99+ 
103 UbseTimerHandlerRegister(100 UbseTimerHandlerRegister(
104 "numaDistributionCollectTimer", [this]() -> uint32_t { return this->CycleCollectNumaInfo(); }, collectInterval);101 "numaDistributionCollectTimer", [this]() -> uint32_t { return this->CycleCollectNumaInfo(); }, collectInterval);
102+ UBSE_LOG_INFO << "ProcessMemPidCollect Init: collect_interval=" << collectInterval
103+ << "s, filter_root=" << (filterRootProcesses_ ? "on" : "off");
105 return UBSE_OK;104 return UBSE_OK;
106}105}
107 106 
108void ProcessMemPidCollect::UnInit()107void ProcessMemPidCollect::UnInit()
109{108{
110 UbseTimerHandlerUnregister("numaDistributionCollectTimer");109 UbseTimerHandlerUnregister("numaDistributionCollectTimer");
110+ if (collectExecutor_ != nullptr) {
111+ collectExecutor_->Wait();
112+ collectExecutor_->Stop();
113+ }
111}114}
112 115 
113uint32_t ProcessMemPidCollect::CycleCollectNumaInfo()116uint32_t ProcessMemPidCollect::CycleCollectNumaInfo()
114{117{
115- CollectInfoMap pidCollectInfo{};118+ uint64_t roundNum = roundNumber_.fetch_add(1) + 1;
116- std::vector<def::ProcessMemPidInfo> pidInfos{};119+ 
117- ProcessMemPidInfoManager::GetInstance().GetAllPidInfo(pidInfos);120+ if (collectionRunning_.load(std::memory_order_acquire)) {
118- for (auto pidInfo : pidInfos) {121+ UBSE_LOG_INFO << "[process_mem] collect round=" << roundNum
119- bool isFaultPid = (pidInfo.processStatus == def::ProcessStatus::FAULT);122+ << " skip: backpressure (prev_round dur=" << prevRoundDurMs_.load() << "ms, not finished)";
120- if (GetPidInfoByCollect(pidInfo, pidCollectInfo) != UBSE_OK) {123+ return UBSE_OK;
121- ProcessMemPidInfoManager::GetInstance().UnsetPidInfo(pidInfo.configInfo.pid);
122- continue;
123- }
124- CollectChildPids(pidInfo.configInfo.pid, pidInfo.configInfo, pidCollectInfo);
125 }124 }
126- decltype(collectHandlers) handlerCopy;125+ 
127- {126+ bool prevFinished = prevRoundFinished_.load(std::memory_order_acquire);
128- std::shared_lock<std::shared_mutex> lock(collectHandlersMutex);127+ UBSE_LOG_DEBUG << "[process_mem] collect round=" << roundNum << " timer fired, dispatching task (prev round dur="
129- handlerCopy = collectHandlers;128+ << (prevFinished ? std::to_string(prevRoundDurMs_.load()) : "N/A")
130- }129+ << "ms, finished=" << (prevFinished ? "Y" : "N") << ")";
131- for (const auto& handler : handlerCopy) {130+ 
132- handler.second(pidCollectInfo);131+ collectionRunning_.store(true, std::memory_order_release);
132+ prevRoundFinished_.store(false, std::memory_order_release);
133+ 
134+ if (!collectExecutor_->Execute([this, roundNum]() { DoCollectRound(roundNum); })) {
135+ UBSE_LOG_ERROR << "[process_mem] collect round=" << roundNum << " enqueue failed";
136+ prevRoundFinished_.store(true, std::memory_order_release);
137+ collectionRunning_.store(false, std::memory_order_release);
133 }138 }
139+ 
134 return UBSE_OK;140 return UBSE_OK;
135}141}
136 142 
137-void ProcessMemPidCollect::CollectChildPids(pid_t parentPid, const def::ProcessMemPidConfigInfo& parentConfig,143+std::optional<std::set<pid_t>> ProcessMemPidCollect::ScanProcPids()
138- CollectInfoMap& pidCollectInfo)
139{144{
140- auto childrenPids = GetChildrenPids(parentPid);145+ std::set<pid_t> curPids;
141- for (auto pid : childrenPids) {146+ DIR* procDir = opendir("/proc");
142- def::ProcessMemPidInfo childPidInfo{};147+ if (procDir == nullptr) {
143- childPidInfo.ppid = parentPid;148+ UBSE_LOG_ERROR << "[process_mem] collect: opendir(/proc) failed, errno=" << errno;
144- childPidInfo.configInfo = parentConfig;149+ return std::nullopt;
145- childPidInfo.configInfo.pid = pid;150+ }
146- childPidInfo.startTime = ProcessMemPidConfigManager::GetExactStartTime(pid);
147 151 
148- auto existingInfo = ProcessMemPidInfoManager::GetInstance().GetPidInfoMap(pid);152+ struct dirent* entry;
149- bool alreadyConfigured =153+ while ((entry = readdir(procDir)) != nullptr) {
150- (existingInfo.configInfo.pid != -1 && existingInfo.startTime == childPidInfo.startTime);154+ if (entry->d_type != DT_DIR) {
151- 
152- if (GetPidInfoByCollect(childPidInfo, pidCollectInfo) != UBSE_OK) {
153- if (!alreadyConfigured) {
154- ProcessMemPidInfoManager::GetInstance().UnsetPidInfo(pid);
155- }
156 continue;155 continue;
157 }156 }
158- 157+ const char* name = entry->d_name;
159- if (!alreadyConfigured) {158+ if (name[0] < '0' || name[0] > '9') {
160- ProcessMemPidInfoManager::GetInstance().SetPidInfoMap(childPidInfo);159+ continue;
161 }160 }
161+ bool allDigits = true;
162+ for (const char* p = name; *p != '\0'; ++p) {
163+ if (*p < '0' || *p > '9') {
164+ allDigits = false;
165+ break;
166+ }
167+ }
168+ if (!allDigits) {
169+ continue;
170+ }
171+ pid_t pid = static_cast<pid_t>(strtol(name, nullptr, 10));
172+ if (pid > 0) {
173+ curPids.insert(pid);
174+ }
175+ }
176+ closedir(procDir);
177+ return curPids;
178+}
179+ 
180+static std::string ReadProcStatComm(pid_t pid)
181+{
182+ std::string path = "/proc/" + std::to_string(pid) + "/stat";
183+ std::ifstream file(path);
184+ if (!file.is_open()) {
185+ return {};
186+ }
187+ std::string line;
188+ if (!std::getline(file, line)) {
189+ return {};
190+ }
191+ auto start = line.find('(');
192+ auto end = line.rfind(')'); // comm 可含 '(' 或 ')',必须取最后一个右括号
193+ if (start == std::string::npos || end == std::string::npos || start >= end) {
194+ return {};
195+ }
196+ return line.substr(start + 1, end - start - 1);
197+}
198+ 
199+void HandleExitedPidEntry(ProcessMemPidInfoManager& infoMgr, pid_t pid, const def::ManagedPidEntry& entry,
200+ uint64_t roundNum)
201+{
202+ bool hasPidSource = (entry.sources & static_cast<uint8_t>(def::ConfigSource::PID_CONFIG)) != 0;
203+ bool hasNameSource = (entry.sources & static_cast<uint8_t>(def::ConfigSource::NAME_CONFIG)) != 0;
204+ std::string comm = entry.nameConfigName.empty() ? ReadProcStatComm(pid) : entry.nameConfigName;
205+ 
206+ if (hasPidSource && hasNameSource) {
207+ infoMgr.RemoveProcMemConfig(true, std::to_string(pid));
208+ infoMgr.RemovePidSourceFromManagedPid(pid);
209+ UBSE_LOG_DEBUG << "[process_mem] collect round=" << roundNum << " pid=" << pid << " comm=" << comm
210+ << " exit, removed pid-config from cache and storage"
211+ << " (kept name-config source)";
212+ } else if (hasPidSource) {
213+ infoMgr.RemoveProcMemConfig(true, std::to_string(pid));
214+ infoMgr.RemoveManagedPidEntry(pid);
215+ UBSE_LOG_DEBUG << "[process_mem] collect round=" << roundNum << " pid=" << pid << " comm=" << comm
216+ << " exit, removed from cache and storage (sources=pid-config)";
217+ } else if (hasNameSource) {
218+ infoMgr.RemoveManagedPidEntry(pid);
219+ UBSE_LOG_DEBUG << "[process_mem] collect round=" << roundNum << " pid=" << pid << " comm=" << comm
220+ << " exit, removed from cache (sources=name-config)";
221+ } else if (entry.isChild) {
222+ infoMgr.RemoveManagedPidEntry(pid);
223+ UBSE_LOG_DEBUG << "[process_mem] collect round=" << roundNum << " pid=" << pid << " comm=" << comm
224+ << " exit, removed from cache (sources=child)";
162 }225 }
163}226}
164 227 
165-uint32_t ProcessMemPidCollect::RegisterCollectHandler(const std::string& name,228+bool ShouldSkipNewPid(const std::map<pid_t, def::ManagedPidEntry>& snapshot, bool filterRoot, pid_t pid,
166- NumaMemDistributionCollectHandler handler)229+ size_t& skippedChild, size_t& skippedRoot)
167{230{
168- std::unique_lock<std::shared_mutex> lock(collectHandlersMutex);231+ if (filterRoot && GetProcUid(pid) == 0) {
232+ ++skippedRoot;
233+ return true;
234+ }
235+ auto existingIt = snapshot.find(pid);
236+ if (existingIt != snapshot.end() && existingIt->second.isChild) {
237+ ++skippedChild;
238+ return true;
239+ }
240+ return false;
241+}
242+ 
243+bool BuildNameConfigMap(ProcessMemPidInfoManager& infoMgr,
244+ std::unordered_map<std::string, def::ProcessMemNewConfigInfo>& nameToConfig)
245+{
246+ std::vector<def::ProcessMemNewConfigInfo> allConfigs;
247+ infoMgr.GetAllProcMemConfigs(allConfigs);
248+ 
249+ std::set<std::string> configNameSet;
250+ for (const auto& cfg : allConfigs) {
251+ if (!cfg.isPid) {
252+ configNameSet.insert(cfg.identifier);
253+ nameToConfig[cfg.identifier] = cfg;
254+ }
255+ }
256+ return !configNameSet.empty();
257+}
258+ 
259+bool MatchNewPidComm(ProcessMemPidInfoManager& infoMgr, pid_t pid,
260+ const std::unordered_map<std::string, def::ProcessMemNewConfigInfo>& nameToConfig,
261+ uint64_t roundNum)
262+{
263+ std::string comm = ReadProcStatComm(pid);
264+ if (comm.empty()) {
265+ return false;
266+ }
267+ auto cfgIt = nameToConfig.find(comm);
268+ if (cfgIt == nameToConfig.end()) {
269+ return false;
270+ }
271+ 
272+ UBSE_LOG_DEBUG << "[process_mem] collect round=" << roundNum << " pid=" << pid << " comm=" << comm
273+ << " discovered: NAME match";
274+ 
275+ infoMgr.AddNameSourceToManagedPid(pid, comm, cfgIt->second.maxMemory, cfgIt->second.remoteRatio);
276+ return true;
277+}
278+ 
279+bool ReadProcStatusVmRss(pid_t pid, uint64_t& vmRssKb, uint32_t& uid)
280+{
281+ std::string path = "/proc/" + std::to_string(pid) + "/status";
282+ std::ifstream file(path);
283+ if (!file.is_open()) {
284+ return false;
285+ }
286+ 
287+ bool foundVmRss = false;
288+ bool foundUid = false;
289+ std::string line;
290+ while (std::getline(file, line)) {
291+ if (line.compare(0, 6, "VmRSS:") == 0) {
292+ std::istringstream iss(line.substr(6));
293+ if (iss >> vmRssKb) {
294+ foundVmRss = true;
295+ }
296+ }
297+ if (line.compare(0, 4, "Uid:") == 0) {
298+ std::istringstream iss(line.substr(4));
299+ iss >> uid;
300+ foundUid = true;
301+ }
302+ if (foundVmRss && foundUid) {
303+ break;
304+ }
305+ }
306+ return foundVmRss;
307+}
308+ 
309+bool ComputePidDiff(const std::set<pid_t>& lastPidSet, const std::set<pid_t>& curPids, std::set<pid_t>& newPids,
310+ std::set<pid_t>& exitedPids)
311+{
312+ if (lastPidSet.empty()) {
313+ newPids = curPids;
314+ return true;
315+ }
316+ for (pid_t pid : curPids) {
317+ if (lastPidSet.find(pid) == lastPidSet.end()) {
318+ newPids.insert(pid);
319+ }
320+ }
321+ for (pid_t pid : lastPidSet) {
322+ if (curPids.find(pid) == curPids.end()) {
323+ exitedPids.insert(pid);
324+ }
325+ }
326+ return false;
327+}
328+ 
329+size_t DispatchVmRssCallbacks(const std::unordered_map<std::string, VmRssCollectHandler>& handlers,
330+ const PidCollectInfoMap& results)
331+{
332+ size_t callbackEntries = 0;
333+ for (const auto& handler : handlers) {
334+ for (const auto& [pid, entry] : results.entries) {
335+ if (!entry.rootFiltered) {
336+ ++callbackEntries;
337+ }
338+ }
339+ handler.second(results);
340+ }
341+ return callbackEntries;
342+}
343+ 
344+size_t LogRoundSummary(uint64_t roundNum, const PidCollectInfoMap& results, size_t managed, int64_t durMs,
345+ size_t prevManagedCount)
346+{
347+ size_t collected = 0;
348+ size_t skipRoot = 0;
349+ for (const auto& [pid, entry] : results.entries) {
350+ if (!entry.rootFiltered) {
351+ ++collected;
352+ } else {
353+ ++skipRoot;
354+ }
355+ }
356+ 
357+ UBSE_LOG_INFO << "[process_mem] collect round=" << roundNum << " managed=" << managed << " collected=" << collected
358+ << " skip(root=" << skipRoot << ") dur=" << durMs << "ms";
359+ 
360+ if (prevManagedCount > 0) {
361+ int64_t delta = static_cast<int64_t>(managed) - static_cast<int64_t>(prevManagedCount);
362+ int64_t absDelta = (delta < 0) ? -delta : delta;
363+ double percentChange = (prevManagedCount > 0) ?
364+ (static_cast<double>(absDelta) / static_cast<double>(prevManagedCount)) * 100.0 :
365+ 0.0;
366+ if (percentChange > 30.0 || absDelta > 5) {
367+ UBSE_LOG_INFO << "[process_mem] collect managedPidCache size changed: " << prevManagedCount << " -> "
368+ << managed << " (delta=" << delta << ")";
369+ }
370+ }
371+ return managed;
372+}
373+ 
374+void ProcessMemPidCollect::HandleExitedPids(const std::set<pid_t>& exitedPids, uint64_t roundNum)
375+{
376+ if (exitedPids.empty()) {
377+ return;
378+ }
379+ 
380+ auto& infoMgr = ProcessMemPidInfoManager::GetInstance();
381+ std::vector<std::pair<pid_t, def::ManagedPidEntry>> toProcess;
382+ auto cache = infoMgr.GetManagedPidCacheSnapshot();
383+ for (pid_t pid : exitedPids) {
384+ auto it = cache.find(pid);
385+ if (it != cache.end()) {
386+ toProcess.push_back({pid, it->second});
387+ }
388+ }
389+ 
390+ for (const auto& [pid, entry] : toProcess) {
391+ decision::ProcessMemPidDecision::GetInstance().HandlePidExited(pid);
392+ HandleExitedPidEntry(infoMgr, pid, entry, roundNum);
393+ }
394+}
395+ 
396+void ProcessMemPidCollect::HandleNewPidsByName(const std::set<pid_t>& newPids, uint64_t roundNum)
397+{
398+ if (newPids.empty()) {
399+ return;
400+ }
401+ 
402+ auto& infoMgr = ProcessMemPidInfoManager::GetInstance();
403+ 
404+ std::unordered_map<std::string, def::ProcessMemNewConfigInfo> nameToConfig;
405+ if (!BuildNameConfigMap(infoMgr, nameToConfig)) {
406+ return;
407+ }
408+ 
409+ UBSE_LOG_DEBUG << "[process_mem] collect round=" << roundNum << " step=name_match scan_new=" << newPids.size()
410+ << " name_count=" << nameToConfig.size();
411+ 
412+ size_t matched = 0;
413+ size_t skippedChild = 0;
414+ size_t skippedRoot = 0;
415+ auto snapshot = infoMgr.GetManagedPidCacheSnapshot();
416+ for (pid_t pid : newPids) {
417+ if (ShouldSkipNewPid(snapshot, filterRootProcesses_, pid, skippedChild, skippedRoot)) {
418+ continue;
419+ }
420+ if (MatchNewPidComm(infoMgr, pid, nameToConfig, roundNum)) {
421+ ++matched;
422+ }
423+ }
424+ 
425+ if (skippedChild > 0 || skippedRoot > 0) {
426+ UBSE_LOG_DEBUG << "[process_mem] collect round=" << roundNum
427+ << " step=name_match skipped_child=" << skippedChild << " skipped_root=" << skippedRoot;
428+ }
429+ 
430+ auto cache = infoMgr.GetManagedPidCacheSnapshot();
431+ UBSE_LOG_DEBUG << "[process_mem] collect round=" << roundNum << " step=name_match matched=" << matched
432+ << " total_managed=" << cache.size();
433+}
434+ 
435+void ProcessMemPidCollect::CollectVmRss(PidCollectInfoMap& results, uint64_t roundNum)
436+{
437+ auto& infoMgr = ProcessMemPidInfoManager::GetInstance();
438+ auto cache = infoMgr.GetManagedPidCacheSnapshot();
439+ 
440+ size_t passCount = 0;
441+ size_t skipRootCount = 0;
442+ 
443+ for (const auto& [pid, entry] : cache) {
444+ uint64_t vmRssKb = 0;
445+ uint32_t uid = 0;
446+ if (!ReadProcStatusVmRss(pid, vmRssKb, uid)) {
447+ continue;
448+ }
449+ 
450+ std::string comm = entry.nameConfigName.empty() ? ReadProcStatComm(pid) : entry.nameConfigName;
451+ 
452+ if (filterRootProcesses_ && uid == 0) {
453+ UBSE_LOG_DEBUG << "[process_mem] collect round=" << roundNum << " pid=" << pid << " comm=" << comm
454+ << " skip: root filtered (uid=" << uid << ")";
455+ ++skipRootCount;
456+ results.entries[pid] = {vmRssKb, true};
457+ continue;
458+ }
459+ 
460+ UBSE_LOG_DEBUG << "[process_mem] collect round=" << roundNum << " pid=" << pid << " comm=" << comm
461+ << " rss=" << vmRssKb;
462+ 
463+ ++passCount;
464+ results.entries[pid] = {vmRssKb, false};
465+ }
466+ 
467+ UBSE_LOG_DEBUG << "[process_mem] collect round=" << roundNum << " step=filter pass=" << passCount
468+ << " skip_root=" << skipRootCount;
469+}
470+ 
471+void ProcessMemPidCollect::CollectChildProcesses(uint64_t roundNum, const std::set<pid_t>& curPids)
472+{
473+ auto& infoMgr = ProcessMemPidInfoManager::GetInstance();
474+ auto snapshot = infoMgr.GetManagedPidCacheSnapshot();
475+ 
476+ size_t discovered = 0;
477+ size_t staleRemoved = 0;
478+ 
479+ std::set<pid_t> parentPids;
480+ for (const auto& [pid, entry] : snapshot) {
481+ if (!entry.isChild) {
482+ parentPids.insert(pid);
483+ }
484+ }
485+ 
486+ for (pid_t parentPid : parentPids) {
487+ auto children = GetChildrenPids(parentPid);
488+ auto parentIt = snapshot.find(parentPid);
489+ uint64_t maxMemory = (parentIt != snapshot.end()) ? parentIt->second.maxMemory : 0;
490+ double remoteRatio = (parentIt != snapshot.end()) ? parentIt->second.remoteRatio : 0.0;
491+ 
492+ for (pid_t childPid : children) {
493+ if (filterRootProcesses_ && GetProcUid(childPid) == 0) {
494+ continue;
495+ }
496+ infoMgr.AddChildSourceToManagedPid(childPid, parentPid, maxMemory, remoteRatio);
497+ ++discovered;
498+ }
499+ }
500+ 
501+ std::vector<pid_t> staleChildren;
502+ for (const auto& [pid, entry] : snapshot) {
503+ if (entry.isChild && curPids.find(pid) == curPids.end()) {
504+ staleChildren.push_back(pid);
505+ }
506+ }
507+ for (pid_t pid : staleChildren) {
508+ infoMgr.RemoveManagedPidEntry(pid);
509+ ++staleRemoved;
510+ }
511+ 
512+ if (discovered > 0 || staleRemoved > 0) {
513+ UBSE_LOG_DEBUG << "[process_mem] collect round=" << roundNum << " step=children discovered=" << discovered
514+ << " stale_removed=" << staleRemoved << " parent_count=" << parentPids.size();
515+ }
516+}
517+ 
518+void ProcessMemPidCollect::DoCollectRound(uint64_t roundNum)
519+{
520+ auto roundStart = std::chrono::steady_clock::now();
521+ 
522+ auto scanRet = ScanProcPids();
523+ if (!scanRet) {
524+ UBSE_LOG_ERROR << "[process_mem] collect round=" << roundNum << " /proc scan failed, skip round";
525+ prevRoundFinished_.store(true, std::memory_order_release);
526+ collectionRunning_.store(false, std::memory_order_release);
527+ return;
528+ }
529+ std::set<pid_t> curPids = std::move(*scanRet);
530+ 
531+ std::set<pid_t> newPids;
532+ std::set<pid_t> exitedPids;
533+ bool coldStart = ComputePidDiff(lastPidSet_, curPids, newPids, exitedPids);
534+ if (coldStart) {
535+ UBSE_LOG_INFO << "[process_mem] collect round=" << roundNum << " cold_start: lastPidSet empty, full scan of "
536+ << curPids.size() << " pids";
537+ }
538+ UBSE_LOG_DEBUG << "[process_mem] collect round=" << roundNum << " step=diff cur_pids=" << curPids.size()
539+ << " new=" << newPids.size() << " exited=" << exitedPids.size()
540+ << (coldStart ? " (cold_start)" : "");
541+ 
542+ HandleExitedPids(exitedPids, roundNum);
543+ 
544+ HandleNewPidsByName(newPids, roundNum);
545+ 
546+ CollectChildProcesses(roundNum, curPids);
547+ 
548+ PidCollectInfoMap results;
549+ CollectVmRssDispatch(results, roundNum);
550+ 
551+ decltype(vmRssHandlers_) handlerCopy;
552+ std::shared_lock<std::shared_mutex> lock(vmRssHandlersMutex_);
553+ handlerCopy = vmRssHandlers_;
554+ lock.unlock();
555+ size_t callbackEntries = DispatchVmRssCallbacks(handlerCopy, results);
556+ UBSE_LOG_DEBUG << "[process_mem] collect round=" << roundNum << " step=callback entries=" << callbackEntries;
557+ 
558+ lastPidSet_ = std::move(curPids);
559+ 
560+ prevRoundDurMs_.store(
561+ std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now() - roundStart).count(),
562+ std::memory_order_release);
563+ prevRoundFinished_.store(true, std::memory_order_release);
564+ collectionRunning_.store(false, std::memory_order_release);
565+ 
566+ prevManagedCount_ = LogRoundSummary(roundNum, results,
567+ ProcessMemPidInfoManager::GetInstance().GetManagedPidCacheSnapshot().size(),
568+ prevRoundDurMs_.load(), prevManagedCount_);
569+}
570+ 
571+void ProcessMemPidCollect::CollectVmRssDispatch(PidCollectInfoMap& results, uint64_t roundNum)
572+{
573+#ifdef UB_ENVIRONMENT
574+ if (collectVmRssOverride_) {
575+ collectVmRssOverride_(results, roundNum);
576+ return;
577+ }
578+#endif
579+ CollectVmRss(results, roundNum);
580+}
581+ 
582+uint32_t ProcessMemPidCollect::RegisterVmRssCollectHandler(const std::string& name, VmRssCollectHandler handler)
583+{
584+ std::unique_lock<std::shared_mutex> lock(vmRssHandlersMutex_);
169 if (!handler) {585 if (!handler) {
170- UBSE_LOG_ERROR << "handler=" << name << " is nullptr";586+ UBSE_LOG_ERROR << "VmRss handler=" << name << " is nullptr";
171 return UBSE_ERROR;587 return UBSE_ERROR;
172 }588 }
173- collectHandlers[name] = handler;589+ vmRssHandlers_[name] = handler;
174 return UBSE_OK;590 return UBSE_OK;
175}591}
176 592 
177-void ProcessMemPidCollect::UnRegisterCollectHandler(const std::string& name)593+void ProcessMemPidCollect::UnRegisterVmRssCollectHandler(const std::string& name)
178{594{
179- std::unique_lock<std::shared_mutex> lock(collectHandlersMutex);595+ std::unique_lock<std::shared_mutex> lock(vmRssHandlersMutex_);
180- collectHandlers.erase(name);596+ vmRssHandlers_.erase(name);
181}597}
182 598 
599+#ifdef UB_ENVIRONMENT
600+void ProcessMemPidCollect::SetCollectVmRssOverride(std::function<void(PidCollectInfoMap&, uint64_t)> fn)
601+{
602+ collectVmRssOverride_ = std::move(fn);
603+}
604+#endif
605+ 
183size_t GetPageSize()606size_t GetPageSize()
184{607{
185 long pageSize = sysconf(_SC_PAGESIZE);608 long pageSize = sysconf(_SC_PAGESIZE);
@@ -204,7 +627,6 @@ bool GetNumaInfoFromToken(const std::string& token, const size_t pageSize,
204 size_t pages = std::stoull(pagesStr);627 size_t pages = std::stoull(pagesStr);
205 numaMemDistribution[numa] += pages * pageSize;628 numaMemDistribution[numa] += pages * pageSize;
206 } catch (const std::exception& e) {629 } catch (const std::exception& e) {
207- // 忽略非法格式
208 return false;630 return false;
209 }631 }
210 }632 }
@@ -239,7 +661,60 @@ uint32_t ProcessMemPidCollect::CollectProcessNumaMemDistribution(
239 }661 }
240 file.close();662 file.close();
241 663 
242- for (const auto& [key, value] : numaMemDistribution) {}
243 return UBSE_OK;664 return UBSE_OK;
244}665}
666+ 
667+int32_t GetProcUid(pid_t pid)
668+{
669+ std::ifstream file("/proc/" + std::to_string(pid) + "/status");
670+ std::string line;
671+ while (std::getline(file, line)) {
672+ if (line.compare(0, 4, "Uid:") == 0) {
673+ std::istringstream iss(line.substr(4));
674+ uint32_t uid = 0;
675+ iss >> uid;
676+ return static_cast<int32_t>(uid);
677+ }
678+ }
679+ return -1;
680+}
681+ 
682+std::vector<PidNameEntry> FindPidsByName(const std::string& procName)
683+{
684+ std::vector<PidNameEntry> result;
685+ try {
686+ for (const auto& entry : std::filesystem::directory_iterator("/proc")) {
687+ if (!entry.is_directory()) {
688+ continue;
689+ }
690+ std::string pidStr = entry.path().filename().string();
691+ if (pidStr.empty() || !std::all_of(pidStr.begin(), pidStr.end(), ::isdigit)) {
692+ continue;
693+ }
694+ std::ifstream commFile(entry.path().string() + "/comm");
695+ std::string comm;
696+ if (!std::getline(commFile, comm) || comm != procName) {
697+ continue;
698+ }
699+ pid_t pid;
700+ try {
701+ pid = std::stoi(pidStr);
702+ } catch (const std::exception& e) {
703+ UBSE_LOG_DEBUG << "FindPidsByName: stoi failed for pidStr=" << pidStr << ": " << e.what();
704+ continue;
705+ }
706+ long startTime = manager::ProcessMemPidConfigManager::GetExactStartTime(pid);
707+ if (startTime != 0) {
708+ result.push_back({pid, startTime});
709+ } else {
710+ UBSE_LOG_DEBUG << "FindPidsByName: process died mid-scan, pid=" << pid << ", name=" << procName;
711+ }
712+ }
713+ } catch (const std::exception& e) {
714+ UBSE_LOG_ERROR << "FindPidsByName: /proc scan failed: " << e.what();
715+ }
716+ UBSE_LOG_DEBUG << "FindPidsByName: scanned /proc entries, matched " << result.size()
717+ << " PIDs for name=" << procName;
718+ return result;
719+}
245} // namespace process_mem::collect720} // namespace process_mem::collect
@@ -14,19 +14,30 @@
14#define PROCESS_MEM_PID_COLLECT_H14#define PROCESS_MEM_PID_COLLECT_H
15 15 
16#include <sys/types.h>16#include <sys/types.h>
17+#include <chrono>
17#include <cstdint>18#include <cstdint>
18#include <functional>19#include <functional>
20+#include <optional>
21+#include <set>
19#include <shared_mutex>22#include <shared_mutex>
20#include <string>23#include <string>
21#include <unordered_map>24#include <unordered_map>
22 25 
26+#include "ubse_thread_pool.h"
23#include "process_mem_pid_manager_def.h"27#include "process_mem_pid_manager_def.h"
24 28 
25namespace process_mem::collect {29namespace process_mem::collect {
26 30 
27-using CollectInfoMap = std::unordered_map<pid_t, std::unordered_map<uint32_t, size_t>>;31+struct PidCollectEntry {
28-// std::unordered_map<pid, std::unordered_map<numa, memSize>>, 每个numa上内存分布单位为 byte.32+ uint64_t vmRssKb{0};
29-using NumaMemDistributionCollectHandler = std::function<void(CollectInfoMap)>;33+ bool rootFiltered{false};
34+};
35+ 
36+struct PidCollectInfoMap {
37+ std::unordered_map<pid_t, PidCollectEntry> entries;
38+};
39+ 
40+using VmRssCollectHandler = std::function<void(const PidCollectInfoMap&)>;
30 41 
31class ProcessMemPidCollect {42class ProcessMemPidCollect {
32public:43public:
@@ -42,20 +53,60 @@ public:
42 53 
43 uint32_t CycleCollectNumaInfo();54 uint32_t CycleCollectNumaInfo();
44 55 
45- uint32_t RegisterCollectHandler(const std::string& name, NumaMemDistributionCollectHandler handler);56+ uint32_t RegisterVmRssCollectHandler(const std::string& name, VmRssCollectHandler handler);
46 57 
47- void UnRegisterCollectHandler(const std::string& name);58+ void UnRegisterVmRssCollectHandler(const std::string& name);
48 59 
49 uint32_t CollectProcessNumaMemDistribution(pid_t pid, std::unordered_map<uint32_t, size_t>& numaMemDistribution);60 uint32_t CollectProcessNumaMemDistribution(pid_t pid, std::unordered_map<uint32_t, size_t>& numaMemDistribution);
50 61 
51- void CollectChildPids(pid_t parentPid, const def::ProcessMemPidConfigInfo& parentConfig,62+#ifdef UB_ENVIRONMENT
52- CollectInfoMap& pidCollectInfo);63+ void SetCollectVmRssOverride(std::function<void(PidCollectInfoMap&, uint64_t)> fn);
64+#endif
53 65 
54private:66private:
55- std::unordered_map<std::string, NumaMemDistributionCollectHandler> collectHandlers{};67+ void DoCollectRound(uint64_t roundNum);
56 68 
57- std::shared_mutex collectHandlersMutex{};69+ std::optional<std::set<pid_t>> ScanProcPids();
70+ 
71+ void HandleExitedPids(const std::set<pid_t>& exitedPids, uint64_t roundNum);
72+ 
73+ void HandleNewPidsByName(const std::set<pid_t>& newPids, uint64_t roundNum);
74+ 
75+ void CollectVmRss(PidCollectInfoMap& results, uint64_t roundNum);
76+ 
77+ void CollectVmRssDispatch(PidCollectInfoMap& results, uint64_t roundNum);
78+ 
79+ void CollectChildProcesses(uint64_t roundNum, const std::set<pid_t>& curPids);
80+ 
81+ std::set<pid_t> lastPidSet_{};
82+ 
83+ ubse::task_executor::UbseTaskExecutorPtr collectExecutor_{};
84+ std::atomic<bool> collectionRunning_{false};
85+ std::atomic<uint64_t> roundNumber_{0};
86+ std::atomic<bool> prevRoundFinished_{true};
87+ std::atomic<int64_t> prevRoundDurMs_{0};
88+ 
89+ bool filterRootProcesses_{true};
90+ 
91+ size_t prevManagedCount_{0};
92+ 
93+ std::unordered_map<std::string, VmRssCollectHandler> vmRssHandlers_{};
94+ std::shared_mutex vmRssHandlersMutex_{};
95+ 
96+#ifdef UB_ENVIRONMENT
97+ std::function<void(PidCollectInfoMap&, uint64_t)> collectVmRssOverride_{};
98+#endif
58};99};
100+ 
101+struct PidNameEntry {
102+ pid_t pid;
103+ long startTime;
104+};
105+ 
106+std::vector<PidNameEntry> FindPidsByName(const std::string& procName);
107+ 
108+int32_t GetProcUid(pid_t pid);
109+ 
59} // namespace process_mem::collect110} // namespace process_mem::collect
60 111 
61-#endif // PROCESS_MEM_PID_COLLECT_H112+#endif
@@ -13,7 +13,6 @@
13 13 
14#include <cerrno>14#include <cerrno>
15#include <cstring>15#include <cstring>
16-#include <filesystem>
17 16 
18#include "ubse_error.h"17#include "ubse_error.h"
19#include "ubse_logger.h"18#include "ubse_logger.h"
@@ -22,76 +21,18 @@
22#include "ubse_storage_module.h"21#include "ubse_storage_module.h"
23namespace process_mem::manager {22namespace process_mem::manager {
24UBSE_DEFINE_THIS_MODULE("process_mem");23UBSE_DEFINE_THIS_MODULE("process_mem");
25-static const std::string PID_CONFIG_KEY_PREFIX = "pidConfig_";
26-void ProcessMemPidConfigManager::PersistPidConfigInfo(const def::ProcessMemPidInfo& pidInfo)
27-{
28- std::string key = std::to_string(pidInfo.configInfo.pid);
29- ubse::serial::UbseSerialization serializer;
30- auto ret = pidInfo.SerializePidInfo(serializer);
31- if (ret != UBSE_OK) {
32- UBSE_LOG_ERROR << "SerialPidDefInfo failed, " << ubse::log::FormatRetCode(ret);
33- return;
34- }
35- UbseByteBuffer byteBuffer{};
36- byteBuffer.data = serializer.GetBuffer();
37- byteBuffer.len = serializer.GetLength();
38- ret = ubse::storage::UbseStoragePutData(PID_CONFIG_KEY_PREFIX, key, &byteBuffer);
39- if (ret != UBSE_OK) {
40- UBSE_LOG_ERROR << "UbseStoragePutData failed, " << ubse::log::FormatRetCode(ret);
41- return;
42- }
43-}
44- 
45-void ProcessMemPidConfigManager::GetAllPersistedPidConfigInfo(std::vector<def::ProcessMemPidInfo>& pidInfos)
46-{
47- std::vector<std::string> fileNames{};
48- try {
49- // 1. 遍历目录
50- for (const auto& entry : std::filesystem::directory_iterator(ubse::storage::DB_STORE_DIR)) {
51- // 2. 过滤:只获取普通文件(排除文件夹)
52- if (entry.is_regular_file()) {
53- // 3. 获取文件名(不含路径)
54- std::string fileName = entry.path().filename().string();
55- if (fileName.size() < PID_CONFIG_KEY_PREFIX.size()) {
56- continue;
57- }
58- if (fileName.compare(0, PID_CONFIG_KEY_PREFIX.size(), PID_CONFIG_KEY_PREFIX) != 0) {
59- continue;
60- }
61- fileNames.push_back(fileName);
62- UBSE_LOG_INFO << "Get one pid file:" << fileName;
63- }
64- }
65- } catch (const std::exception& e) {
66- UBSE_LOG_ERROR << "Failed to read directory: " << ubse::storage::DB_STORE_DIR << ", error: " << e.what();
67- return;
68- }
69- 
70- // 遍历所有文件获取所有的pid
71- auto ret = UBSE_OK;
72- for (const auto& pidFileName : fileNames) {
73- auto pidStr = pidFileName.substr(PID_CONFIG_KEY_PREFIX.size());
74- ret = ubse::storage::UbseStorageQueryData(PID_CONFIG_KEY_PREFIX, pidStr, &pidInfos, QueryPidConfigCallback);
75- if (ret != UBSE_OK) {
76- UBSE_LOG_ERROR << "Failed to query pid config for pid: " << pidStr;
77- continue;
78- }
79- }
80- return;
81-}
82- 
83uint64_t ProcessMemPidConfigManager::GetExactStartTime(pid_t pid)24uint64_t ProcessMemPidConfigManager::GetExactStartTime(pid_t pid)
84{25{
85 std::string path = "/proc/" + std::to_string(pid) + "/stat";26 std::string path = "/proc/" + std::to_string(pid) + "/stat";
86 FILE* file = fopen(path.c_str(), "r");27 FILE* file = fopen(path.c_str(), "r");
87 if (!file) {28 if (!file) {
29+ UBSE_LOG_WARN << "GetExactStartTime: fopen failed for pid=" << pid << ", path=" << path << ", errno=" << errno
30+ << " (" << std::strerror(errno) << ")";
88 return 0;31 return 0;
89 }32 }
90 33 
91- char buf[1024];34+ unsigned long long starttime = 0;
92- unsigned long long starttime = 0; // 使用 unsigned long long 接收
93 35 
94- // 使用 %*[^)] 跳过带空格的进程名, 使用 %llu 匹配 64 位无符号整数
95 if (fscanf_s(file,36 if (fscanf_s(file,
96 "%*d %*[^)] %*c %*c %*d %*d %*d %*d %*d %*u %*u %*u %*u %*u %*lu %*lu %*d %*d %*d %*d %*d %*d %llu",37 "%*d %*[^)] %*c %*c %*d %*d %*d %*d %*d %*u %*u %*u %*u %*u %*lu %*lu %*d %*d %*d %*d %*d %*d %llu",
97 &starttime) == 1) {38 &starttime) == 1) {
@@ -101,59 +42,171 @@ uint64_t ProcessMemPidConfigManager::GetExactStartTime(pid_t pid)
101 return static_cast<uint64_t>(starttime);42 return static_cast<uint64_t>(starttime);
102 }43 }
103 44 
45+ UBSE_LOG_WARN << "GetExactStartTime: fscanf_s failed to parse stat for pid=" << pid << ", path=" << path
46+ << ", errno=" << errno << " (" << std::strerror(errno) << ")";
104 if (fclose(file) != 0) {47 if (fclose(file) != 0) {
105 UBSE_LOG_WARN << "fclose_failed errno=" << errno << " error=" << std::strerror(errno);48 UBSE_LOG_WARN << "fclose_failed errno=" << errno << " error=" << std::strerror(errno);
106 }49 }
107 return 0;50 return 0;
108}51}
109 52 
110-void ProcessMemPidConfigManager::QueryPidConfigCallback(const std::string& keyPrefix, const std::string& key,53+namespace {
111- const UbseByteBuffer& buff, void* ctx)54+const std::string& GetProcMemKeyPrefix(bool isPid)
55+{
56+ return isPid ? def::PROC_MEM_PID_KEY_PREFIX : def::PROC_MEM_NAME_KEY_PREFIX;
57+}
58+} // namespace
59+ 
60+uint32_t ProcessMemPidConfigManager::PersistProcMemConfig(const def::ProcessMemNewConfigInfo& config)
61+{
62+ ubse::serial::UbseSerialization serializer;
63+ auto ret = config.Serialize(serializer);
64+ if (ret != UBSE_OK) {
65+ UBSE_LOG_ERROR << "Serialize ProcMemConfig failed for identifier=" << config.identifier;
66+ return UBSE_ERROR;
67+ }
68+ UbseByteBuffer byteBuffer{};
69+ byteBuffer.data = serializer.GetBuffer();
70+ byteBuffer.len = serializer.GetLength();
71+ const auto& prefix = GetProcMemKeyPrefix(config.isPid);
72+ ret = ubse::storage::UbseStoragePutData(prefix, config.identifier, &byteBuffer);
73+ if (ret != UBSE_OK) {
74+ UBSE_LOG_ERROR << "UbseStoragePutData ProcMemConfig failed for prefix=" << prefix
75+ << ", identifier=" << config.identifier << ", ret=" << ubse::log::FormatRetCode(ret);
76+ return UBSE_ERROR;
77+ }
78+ UBSE_LOG_INFO << "PersistProcMemConfig: persisted prefix=" << prefix << ", identifier=" << config.identifier
79+ << ", maxMemory=" << config.maxMemory << ", remoteRatio=" << config.remoteRatio;
80+ return UBSE_OK;
81+}
82+ 
83+void ProcessMemPidConfigManager::GetAllPersistedProcMemConfigs(std::vector<def::ProcessMemNewConfigInfo>& configs)
84+{
85+ for (const auto& prefix : {def::PROC_MEM_PID_KEY_PREFIX, def::PROC_MEM_NAME_KEY_PREFIX}) {
86+ std::vector<std::string> keys;
87+ auto listRet = ubse::storage::UbseStorageListKeys(prefix, keys);
88+ if (listRet != UBSE_OK) {
89+ UBSE_LOG_WARN << "UbseStorageListKeys failed for prefix=" << prefix;
90+ continue;
91+ }
92+ for (const auto& key : keys) {
93+ std::vector<def::ProcessMemNewConfigInfo> tmpVec;
94+ auto ret = ubse::storage::UbseStorageQueryData(prefix, key, &tmpVec, QueryProcMemConfigCallback);
95+ if (ret == UBSE_OK && !tmpVec.empty()) {
96+ configs.push_back(tmpVec[0]);
97+ } else {
98+ UBSE_LOG_WARN << "UbseStorageQueryData failed for prefix=" << prefix << ", key=" << key
99+ << ", ret=" << ubse::log::FormatRetCode(ret);
100+ }
101+ }
102+ }
103+}
104+ 
105+uint32_t ProcessMemPidConfigManager::DeleteProcMemConfig(bool isPid, const std::string& identifier)
106+{
107+ const auto& prefix = GetProcMemKeyPrefix(isPid);
108+ auto ret = ubse::storage::UbseStorageDeleteData(prefix, identifier);
109+ if (ret != UBSE_OK) {
110+ UBSE_LOG_ERROR << "Failed to delete ProcMem config for prefix=" << prefix << ", identifier=" << identifier
111+ << ", ret=" << ubse::log::FormatRetCode(ret);
112+ return ret;
113+ }
114+ return UBSE_OK;
115+}
116+ 
117+uint32_t ProcessMemPidConfigManager::PersistFossilConfig(pid_t pid, const def::FossilPidConfigInfo& fossil)
118+{
119+ ubse::serial::UbseSerialization serializer;
120+ auto ret = fossil.Serialize(serializer);
121+ if (ret != UBSE_OK) {
122+ UBSE_LOG_ERROR << "Serialize FossilConfig failed for pid=" << pid;
123+ return UBSE_ERROR;
124+ }
125+ UbseByteBuffer byteBuffer{};
126+ byteBuffer.data = serializer.GetBuffer();
127+ byteBuffer.len = serializer.GetLength();
128+ const std::string key = std::to_string(pid);
129+ ret = ubse::storage::UbseStoragePutData(def::PROC_MEM_FOSSIL_KEY_PREFIX, key, &byteBuffer);
130+ if (ret != UBSE_OK) {
131+ UBSE_LOG_ERROR << "UbseStoragePutData FossilConfig failed for pid=" << pid
132+ << ", ret=" << ubse::log::FormatRetCode(ret);
133+ return UBSE_ERROR;
134+ }
135+ UBSE_LOG_INFO << "PersistFossilConfig: pid=" << pid << " name=" << fossil.name << " maxMemory=" << fossil.maxMemory
136+ << " remoteRatio=" << fossil.remoteRatio;
137+ return UBSE_OK;
138+}
139+ 
140+uint32_t ProcessMemPidConfigManager::DeleteFossilConfig(pid_t pid)
141+{
142+ auto ret = ubse::storage::UbseStorageDeleteData(def::PROC_MEM_FOSSIL_KEY_PREFIX, std::to_string(pid));
143+ if (ret != UBSE_OK) {
144+ UBSE_LOG_ERROR << "Failed to delete FossilConfig for pid=" << pid << ", ret=" << ubse::log::FormatRetCode(ret);
145+ return ret;
146+ }
147+ return UBSE_OK;
148+}
149+ 
150+void ProcessMemPidConfigManager::GetAllFossilConfigs(std::vector<std::pair<pid_t, def::FossilPidConfigInfo>>& fossils)
151+{
152+ std::vector<std::string> keys;
153+ auto listRet = ubse::storage::UbseStorageListKeys(def::PROC_MEM_FOSSIL_KEY_PREFIX, keys);
154+ if (listRet != UBSE_OK) {
155+ UBSE_LOG_WARN << "UbseStorageListKeys failed for prefix=" << def::PROC_MEM_FOSSIL_KEY_PREFIX;
156+ return;
157+ }
158+ for (const auto& key : keys) {
159+ std::vector<def::FossilPidConfigInfo> tmpVec;
160+ auto ret = ubse::storage::UbseStorageQueryData(def::PROC_MEM_FOSSIL_KEY_PREFIX, key, &tmpVec,
161+ QueryFossilConfigCallback);
162+ if (ret == UBSE_OK && !tmpVec.empty()) {
163+ auto pidOpt = def::ParsePidFromIdentifier(key);
164+ if (pidOpt.has_value()) {
165+ fossils.emplace_back(pidOpt.value(), tmpVec[0]);
166+ }
167+ } else {
168+ UBSE_LOG_WARN << "UbseStorageQueryData failed for prefix=" << def::PROC_MEM_FOSSIL_KEY_PREFIX
169+ << ", key=" << key << ", ret=" << ubse::log::FormatRetCode(ret);
170+ }
171+ }
172+}
173+ 
174+void ProcessMemPidConfigManager::QueryFossilConfigCallback(const std::string& keyPrefix, const std::string& key,
175+ const UbseByteBuffer& buff, void* ctx)
112{176{
113 if (buff.data == nullptr || buff.len == 0) {177 if (buff.data == nullptr || buff.len == 0) {
114- UBSE_LOG_ERROR << "QueryPidConfigCallback failed, invalid buffer";178+ UBSE_LOG_ERROR << "QueryFossilConfigCallback failed, invalid buffer for keyPrefix=" << keyPrefix
179+ << " key=" << key;
115 return;180 return;
116 }181 }
117- def::ProcessMemPidInfo pidInfo{};182+ def::FossilPidConfigInfo fossil{};
118 ubse::serial::UbseDeSerialization deserializer(buff.data, buff.len);183 ubse::serial::UbseDeSerialization deserializer(buff.data, buff.len);
119- auto ret = pidInfo.DeserializePidInfo(deserializer);184+ auto ret = fossil.Deserialize(deserializer);
120 if (ret != UBSE_OK) {185 if (ret != UBSE_OK) {
121- UBSE_LOG_ERROR << "DeSerialPidDefInfo failed, " << ubse::log::FormatRetCode(ret);186+ UBSE_LOG_ERROR << "Deserialize FossilConfig failed for keyPrefix=" << keyPrefix << " key=" << key;
122 return;187 return;
123 }188 }
124- auto pidInfos = static_cast<std::vector<def::ProcessMemPidInfo>*>(ctx);189+ auto fossils = static_cast<std::vector<def::FossilPidConfigInfo>*>(ctx);
125- pidInfos->push_back(pidInfo);190+ fossils->push_back(fossil);
126- return;
127}191}
128 192 
129-bool ProcessMemPidConfigManager::CheckPidConfigInfo(const def::ProcessMemPidInfo& pidInfo)193+void ProcessMemPidConfigManager::QueryProcMemConfigCallback(const std::string& keyPrefix, const std::string& key,
194+ const UbseByteBuffer& buff, void* ctx)
130{195{
131- auto exactStartTime = GetExactStartTime(pidInfo.configInfo.pid);196+ if (buff.data == nullptr || buff.len == 0) {
132- if (exactStartTime == 0) {197+ UBSE_LOG_ERROR << "QueryProcMemConfigCallback failed, invalid buffer for keyPrefix=" << keyPrefix
133- UBSE_LOG_ERROR << "Failed to get exact start time for pid: " << pidInfo.configInfo.pid;198+ << " key=" << key;
134- return false;199+ return;
135 }200 }
136- return exactStartTime == pidInfo.startTime;201+ def::ProcessMemNewConfigInfo config{};
137-}202+ ubse::serial::UbseDeSerialization deserializer(buff.data, buff.len);
138- 203+ auto ret = config.Deserialize(deserializer);
139-void ProcessMemPidConfigManager::DeletePidConfigInfo(pid_t pid)
140-{
141- auto pidStr = std::to_string(pid);
142- auto ret = ubse::storage::UbseStorageDeleteData(PID_CONFIG_KEY_PREFIX, pidStr);
143 if (ret != UBSE_OK) {204 if (ret != UBSE_OK) {
144- UBSE_LOG_ERROR << "Failed to delete pid config for pid: " << pidStr << ", " << ubse::log::FormatRetCode(ret);205+ UBSE_LOG_ERROR << "Deserialize ProcMemConfig failed for keyPrefix=" << keyPrefix << " key=" << key;
206+ return;
145 }207 }
146- UBSE_LOG_INFO << "Delete pid config for pid: " << pidStr;208+ auto configs = static_cast<std::vector<def::ProcessMemNewConfigInfo>*>(ctx);
147- return;209+ configs->push_back(config);
148}210}
149 211 
150-bool ProcessMemPidConfigManager::IsPidInfoExist(pid_t pid, uint64_t startTime)212+} // namespace process_mem::manager
151-{
152- auto exactStartTime = GetExactStartTime(pid);
153- if (exactStartTime == 0) {
154- return false;
155- }
156- return exactStartTime == startTime;
157-}
158- 
159-} // namespace process_mem::manager
@@ -16,20 +16,20 @@
16namespace process_mem::manager {16namespace process_mem::manager {
17class ProcessMemPidConfigManager {17class ProcessMemPidConfigManager {
18public:18public:
19- static void PersistPidConfigInfo(const def::ProcessMemPidInfo& pidInfo);
20- 
21- static void GetAllPersistedPidConfigInfo(std::vector<def::ProcessMemPidInfo>& pidConfigInfos);
22- 
23- static bool CheckPidConfigInfo(const def::ProcessMemPidInfo& pidInfo);
24- 
25 static uint64_t GetExactStartTime(pid_t pid);19 static uint64_t GetExactStartTime(pid_t pid);
26 20 
27- static void QueryPidConfigCallback(const std::string& keyPrefix, const std::string& key, const UbseByteBuffer& buff,21+ static uint32_t PersistProcMemConfig(const def::ProcessMemNewConfigInfo& config);
28- void* ctx);22+ static void GetAllPersistedProcMemConfigs(std::vector<def::ProcessMemNewConfigInfo>& configs);
23+ static uint32_t DeleteProcMemConfig(bool isPid, const std::string& identifier);
29 24 
30- static void DeletePidConfigInfo(pid_t pid);25+ static uint32_t PersistFossilConfig(pid_t pid, const def::FossilPidConfigInfo& fossil);
26+ static uint32_t DeleteFossilConfig(pid_t pid);
27+ static void GetAllFossilConfigs(std::vector<std::pair<pid_t, def::FossilPidConfigInfo>>& fossils);
31 28 
32- static bool IsPidInfoExist(pid_t pid, uint64_t startTime);29+ static void QueryProcMemConfigCallback(const std::string& keyPrefix, const std::string& key,
30+ const UbseByteBuffer& buff, void* ctx);
31+ static void QueryFossilConfigCallback(const std::string& keyPrefix, const std::string& key,
32+ const UbseByteBuffer& buff, void* ctx);
33};33};
34} // namespace process_mem::manager34} // namespace process_mem::manager
35-#endif35+#endif
@@ -0,0 +1,1916 @@
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+#include "process_mem_pid_decision.h"
13+ 
14+#include <algorithm>
15+#include <fstream>
16+#include <set>
17+#include <sstream>
18+#include <unordered_map>
19+ 
20+#include <securec.h>
21+ 
22+#include "ubse_conf.h"
23+#include "ubse_error.h"
24+#include "ubse_logger.h"
25+#include "ubse_mem_controller.h"
26+#include "ubse_node_controller.h"
27+#include "ubse_timer.h"
28+#include "process_mem_pid_bridge.h"
29+#include "process_mem_pid_collect.h"
30+#include "process_mem_pid_config_manager.h"
31+#include "process_mem_pid_info_manager.h"
32+#include "trace_context.h"
33+ 
34+namespace process_mem::decision {
35+UBSE_DEFINE_THIS_MODULE("process_mem");
36+ 
37+using namespace ubse::timer;
38+using namespace process_mem::manager;
39+ 
40+namespace {
41+constexpr uint64_t BYTES_PER_GB = 1073741824;
42+constexpr uint64_t BYTES_PER_MB = 1048576;
43+constexpr uint64_t MB_128 = 128;
44+constexpr uint64_t RETURN_HEADROOM_BYTES = 5 * BYTES_PER_GB;
45+constexpr uint64_t NORMAL_POLL_INTERVAL_MS = 1000;
46+constexpr uint32_t kBorrowHighWatermark = 100;
47+constexpr uint32_t kMaxReturnRetry = 100;
48+constexpr int64_t kEmergencyBroadcastIntervalMs = 2000;
49+ 
50+const char* ReturnSceneToString(ReturnScene scene)
51+{
52+ switch (scene) {
53+ case ReturnScene::TIMEOUT:
54+ return "timeout";
55+ case ReturnScene::ACTIVE:
56+ return "active";
57+ case ReturnScene::EXITED:
58+ return "exited";
59+ case ReturnScene::PASSIVE:
60+ return "passive";
61+ }
62+ return "unknown";
63+}
64+ 
65+uint64_t GbToBytes(uint64_t gb)
66+{
67+ return gb * BYTES_PER_GB;
68+}
69+uint64_t BytesToGb(uint64_t bytes)
70+{
71+ return bytes / BYTES_PER_GB;
72+}
73+double BytesToGbDouble(uint64_t bytes)
74+{
75+ return static_cast<double>(bytes) / BYTES_PER_GB;
76+}
77+ 
78+std::string GenerateBorrowName(pid_t pid)
79+{
80+ static std::atomic<uint32_t> nameSeq{0};
81+ auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch())
82+ .count();
83+ auto nodeId = ubse::nodeController::UbseNodeController::GetInstance().GetCurrentNodeId();
84+ std::string name =
85+ nodeId + "-" + std::to_string(pid) + "-" + std::to_string(ms) + "-" + std::to_string(nameSeq.fetch_add(1));
86+ constexpr size_t maxNameLen = 48;
87+ if (name.size() >= maxNameLen) {
88+ name.resize(maxNameLen - 1);
89+ }
90+ return name;
91+}
92+ 
93+enum class CandidateSkip
94+{
95+ NONE,
96+ CAN_MIGRATE
97+};
98+ 
99+def::ProcessStatus ComputeSlotStatus(const std::vector<def::BorrowSlot>& slots)
100+{
101+ bool hasActiveSlots = false;
102+ for (const auto& s : slots) {
103+ if (s.status == def::BorrowSlotStatus::BORROWING) {
104+ hasActiveSlots = true;
105+ break;
106+ }
107+ }
108+ return slots.empty() ? def::ProcessStatus::IDLE :
109+ (hasActiveSlots ? def::ProcessStatus::BORROWING : def::ProcessStatus::BORROWED);
110+}
111+ 
112+std::vector<const ubse::mem::controller::UbseNumaMemoryDebtInfo*> MatchPidDebts(
113+ pid_t pid, const std::vector<ubse::mem::controller::UbseNumaMemoryDebtInfo>& debts)
114+{
115+ std::vector<const ubse::mem::controller::UbseNumaMemoryDebtInfo*> matched;
116+ for (const auto& debt : debts) {
117+ def::ProcessMemUsrInfo usrInfo{};
118+ if (memcpy_s(&usrInfo, sizeof(usrInfo), debt.usrInfo, sizeof(usrInfo)) != EOK ||
119+ usrInfo.pid != static_cast<int32_t>(pid)) {
120+ continue;
121+ }
122+ matched.push_back(&debt);
123+ }
124+ return matched;
125+}
126+ 
127+bool IsBorrowBindable(pid_t pid, const def::ProcessMemUsrInfo& usrInfo,
128+ const std::map<pid_t, def::ManagedPidEntry>& snapshot)
129+{
130+ auto it = snapshot.find(pid);
131+ if (it == snapshot.end()) {
132+ return false;
133+ }
134+ auto curStartTime = ProcessMemPidConfigManager::GetExactStartTime(pid);
135+ return curStartTime != 0 && static_cast<uint64_t>(curStartTime) == static_cast<uint64_t>(usrInfo.startTime);
136+}
137+ 
138+void QuerySmapProcessConfigs(std::unordered_map<pid_t, std::unordered_map<int, uint64_t>>& smapMemKb,
139+ std::optional<std::set<int>>& failedNumas, size_t& queryFail)
140+{
141+ if (!pid::bridge::ProcessMemPidBridge::rmrsProcessConfigQuery) {
142+ UBSE_LOG_DEBUG << "[process_mem] recover config: UBSRMRSProcessConfigQuery unavailable";
143+ failedNumas = std::nullopt;
144+ return;
145+ }
146+ constexpr int kCapacity = 512;
147+ std::vector<ubse::mem::controller::UbseNumaMemoryImportDebtInfo> debts;
148+ if (ubse::mem::controller::UbseGetNumaMemImportDebtInfoWithLocalNode(debts) != UBSE_OK) {
149+ UBSE_LOG_WARN << "[process_mem] recover config: get import debts failed";
150+ failedNumas = std::nullopt;
151+ return;
152+ }
153+ std::set<int> remoteNumas;
154+ for (const auto& debt : debts) {
155+ remoteNumas.insert(static_cast<int>(debt.remoteNumaId));
156+ }
157+ for (int nid : remoteNumas) {
158+ std::vector<mempooling::smap::ProcessPayload> payloads(static_cast<size_t>(kCapacity));
159+ int realLen = 0;
160+ auto ret = pid::bridge::ProcessMemPidBridge::rmrsProcessConfigQuery(nid, payloads.data(), kCapacity, &realLen);
161+ if (ret != 0 || realLen <= 0) {
162+ if (ret != 0) {
163+ ++queryFail;
164+ if (!failedNumas.has_value()) {
165+ failedNumas = std::set<int>{};
166+ }
167+ failedNumas->insert(nid);
168+ }
169+ continue;
170+ }
171+ for (int i = 0; i < realLen; ++i) {
172+ const auto& p = payloads[static_cast<size_t>(i)];
173+ if (p.pid <= 0 || p.scanType != 1) {
174+ continue;
175+ }
176+ smapMemKb[p.pid][nid] += p.memSize;
177+ }
178+ }
179+}
180+ 
181+void RearrangePidMigrated(def::BorrowState& borrow)
182+{
183+ std::map<int, std::vector<size_t>> numaGroups;
184+ for (size_t i = 0; i < borrow.slots.size(); ++i) {
185+ const auto& s = borrow.slots[i];
186+ if (s.status != def::BorrowSlotStatus::COMPLETED || s.returnStatus == def::ReturnStatus::RETURNING) {
187+ continue;
188+ }
189+ numaGroups[s.remoteNumaId].push_back(i);
190+ }
191+ for (auto& [numaId, idxs] : numaGroups) {
192+ uint64_t expectedTotal = 0;
193+ for (size_t i : idxs) {
194+ expectedTotal += borrow.slots[i].migratedBytes;
195+ }
196+ std::sort(idxs.begin(), idxs.end(),
197+ [&borrow](size_t a, size_t b) { return borrow.slots[a].capacity > borrow.slots[b].capacity; });
198+ uint64_t remaining = expectedTotal;
199+ for (size_t i : idxs) {
200+ uint64_t fill = std::min(borrow.slots[i].capacity, remaining);
201+ borrow.slots[i].migratedBytes = fill;
202+ remaining -= fill;
203+ }
204+ }
205+}
206+} // namespace
207+ 
208+std::string ProcessMemPidDecision::GetCurrentNodeId()
209+{
210+ return ubse::nodeController::UbseNodeController::GetInstance().GetCurrentNodeId();
211+}
212+ 
213+void ProcessMemPidDecision::LoadConfig()
214+{
215+ const std::string section = "process_mem";
216+ 
217+ const std::string intervalKey = "schedule_interval";
218+ ubse::config::UbseGetUInt(section, intervalKey, processIntervalSec_);
219+ constexpr uint32_t defaultInterval = 5;
220+ constexpr uint32_t maxInterval = 3600;
221+ if (processIntervalSec_ < 1 || processIntervalSec_ > maxInterval) {
222+ processIntervalSec_ = defaultInterval;
223+ }
224+ 
225+ const std::string thresholdKey = "pressing_free_threshold";
226+ uint64_t thresholdGb = 15;
227+ ubse::config::UbseGetULong(section, thresholdKey, thresholdGb);
228+ constexpr uint64_t defaultThresholdGb = 15;
229+ constexpr uint64_t maxThresholdGb = 4096;
230+ if (thresholdGb < 1 || thresholdGb > maxThresholdGb) {
231+ thresholdGb = defaultThresholdGb;
232+ }
233+ freeMemoryThresholdBytes_ = GbToBytes(thresholdGb);
234+ 
235+ const std::string timeoutKey = "borrow.timeout";
236+ ubse::config::UbseGetUInt(section, timeoutKey, timeoutPer128MbMs_);
237+ constexpr uint32_t defaultTimeoutMs = 1000;
238+ constexpr uint32_t maxTimeoutMs = 1800000;
239+ if (timeoutPer128MbMs_ == 0 || timeoutPer128MbMs_ > maxTimeoutMs) {
240+ timeoutPer128MbMs_ = defaultTimeoutMs;
241+ }
242+ 
243+ const std::string samePlaneKey = "borrow.must_same_plane";
244+ bool mustSamePlane = false;
245+ ubse::config::UbseGetBool(section, samePlaneKey, mustSamePlane);
246+ // 配置语义: true=必须同平面(严格过滤); UbseMemBorrower.samePlanePrefer 语义相反: true=软优先
247+ samePlanePrefer_ = !mustSamePlane;
248+}
249+ 
250+void ProcessMemPidDecision::LoadOomReturnConfig()
251+{
252+ const std::string oomSection = "process_mem.oom";
253+ uint32_t fastPollMs = 200;
254+ ubse::config::UbseGetUInt(oomSection, "collect_node_interval", fastPollMs);
255+ constexpr uint32_t defaultFastPollMs = 200;
256+ constexpr uint32_t minFastPollMs = 50;
257+ constexpr uint32_t maxFastPollMs = 60000;
258+ if (fastPollMs < minFastPollMs || fastPollMs > maxFastPollMs) {
259+ fastPollMs = defaultFastPollMs;
260+ }
261+ fastPollIntervalMs_ = fastPollMs;
262+ uint32_t emergencyGb = 5;
263+ ubse::config::UbseGetUInt(oomSection, "emergency_free_threshold", emergencyGb);
264+ constexpr uint32_t defaultEmergencyGb = 5;
265+ constexpr uint32_t maxEmergencyGb = 4096;
266+ if (emergencyGb < 1 || emergencyGb > maxEmergencyGb) {
267+ emergencyGb = defaultEmergencyGb;
268+ }
269+ emergencyThresholdBytes_ = GbToBytes(emergencyGb);
270+ 
271+ const std::string returnSection = "process_mem.return";
272+ uint32_t observeCycles = 300;
273+ ubse::config::UbseGetUInt(returnSection, "observe_cycles", observeCycles);
274+ constexpr uint32_t defaultObserveCycles = 300;
275+ constexpr uint32_t maxObserveCycles = 4096;
276+ if (observeCycles < 1 || observeCycles > maxObserveCycles) {
277+ observeCycles = defaultObserveCycles;
278+ }
279+ observeCycles_ = observeCycles;
280+}
281+ 
282+bool ProcessMemPidDecision::StartExecutors()
283+{
284+ constexpr int borrowThreadNum = 8;
285+ constexpr int queSize = 256;
286+ borrowExecutor_ = ubse::task_executor::UbseTaskExecutor::Create("ProcMemDecision", borrowThreadNum, queSize);
287+ if (borrowExecutor_ == nullptr || !borrowExecutor_->Start()) {
288+ UBSE_LOG_ERROR << "[process_mem] decision: borrowExecutor start failed";
289+ return false;
290+ }
291+ 
292+ constexpr int returnThreadNum = 4;
293+ returnExecutor_ = ubse::task_executor::UbseTaskExecutor::Create("ProcMemReturn", returnThreadNum, queSize);
294+ if (returnExecutor_ == nullptr || !returnExecutor_->Start()) {
295+ UBSE_LOG_ERROR << "[process_mem] decision: returnExecutor start failed";
296+ return false;
297+ }
298+ return true;
299+}
300+ 
301+uint32_t ProcessMemPidDecision::Init()
302+{
303+ LoadConfig();
304+ LoadOomReturnConfig();
305+ 
306+ if (!StartExecutors()) {
307+ return UBSE_ERROR;
308+ }
309+ 
310+ RecoverBorrowFromObmm();
311+ ProcessMemPidInfoManager::GetInstance().CleanupStalePidConfigs();
312+ 
313+ if (UbseTimerHandlerRegister(
314+ "ProcessMemDecisionTimer", [this]() -> uint32_t { return this->OnDecisionTimer(); }, processIntervalSec_) !=
315+ UBSE_OK) {
316+ return UBSE_ERROR;
317+ }
318+ 
319+ {
320+ std::lock_guard<std::mutex> lock(oomMutex_);
321+ oomRunning_ = true;
322+ }
323+ oomThread_ = std::thread([this]() { OomLoop(); });
324+ 
325+ UBSE_LOG_INFO << "[process_mem] decision init: interval=" << processIntervalSec_
326+ << "s, free_mem_threshold_gb=" << BytesToGb(freeMemoryThresholdBytes_)
327+ << ", timeout_per_128mb_ms=" << timeoutPer128MbMs_ << ", same_plane_prefer=" << samePlanePrefer_
328+ << ", oom_fast_poll_ms=" << fastPollIntervalMs_
329+ << ", oom_emergency_gb=" << BytesToGb(emergencyThresholdBytes_)
330+ << ", return_observe_cycles=" << observeCycles_;
331+ return UBSE_OK;
332+}
333+ 
334+void ProcessMemPidDecision::UnInit()
335+{
336+ {
337+ std::lock_guard<std::mutex> lock(oomMutex_);
338+ oomRunning_ = false;
339+ }
340+ oomCv_.notify_all();
341+ if (oomThread_.joinable()) {
342+ oomThread_.join();
343+ }
344+ 
345+ stopping_ = true;
346+ UbseTimerHandlerUnregister("ProcessMemDecisionTimer");
347+ if (borrowExecutor_ != nullptr) {
348+ borrowExecutor_->Wait();
349+ borrowExecutor_->Stop();
350+ }
351+ if (returnExecutor_ != nullptr) {
352+ returnExecutor_->Wait();
353+ returnExecutor_->Stop();
354+ }
355+}
356+ 
357+void ProcessMemPidDecision::RunBorrowRound(uint64_t roundNum)
358+{
359+ auto roundStart = std::chrono::steady_clock::now();
360+ 
361+ CheckTimeouts(roundNum);
362+ 
363+ uint64_t shortage = 0;
364+ if (!CheckNodeFreeMemory(shortage)) {
365+ return;
366+ }
367+ 
368+ uint64_t rawShortage = shortage;
369+ uint64_t pendingBorrow = GetPendingBorrowTotal();
370+ shortage = (shortage > pendingBorrow) ? (shortage - pendingBorrow) : 0;
371+ 
372+ uint64_t initialShortage = shortage;
373+ 
374+ uint64_t nodeFree = freeMemoryThresholdBytes_ - rawShortage;
375+ BroadcastPassiveReturn(roundNum, nodeFree, false);
376+ 
377+ auto candidates = BuildCandidates(roundNum);
378+ if (candidates.empty()) {
379+ UBSE_LOG_INFO << "[process_mem] borrow round=" << roundNum << " shortage_gb=" << BytesToGb(rawShortage)
380+ << " pending_gb=" << BytesToGb(pendingBorrow)
381+ << " borrowed_gb=0 slots=0 remaining_shortage_gb=" << BytesToGb(shortage)
382+ << " dur_ms=0 (no candidates)";
383+ return;
384+ }
385+ 
386+ ExecuteBorrowRound(candidates, shortage, roundNum);
387+ 
388+ auto roundEnd = std::chrono::steady_clock::now();
389+ auto durMs = std::chrono::duration_cast<std::chrono::milliseconds>(roundEnd - roundStart).count();
390+ uint64_t borrowed = (shortage <= initialShortage) ? (initialShortage - shortage) : 0;
391+ 
392+ UBSE_LOG_INFO << "[process_mem] borrow round=" << roundNum << " shortage_gb=" << BytesToGb(rawShortage)
393+ << " pending_gb=" << BytesToGb(pendingBorrow) << " borrowed_gb=" << BytesToGb(borrowed)
394+ << " remaining_shortage_gb=" << BytesToGb(shortage) << " dur_ms=" << durMs;
395+}
396+ 
397+uint32_t ProcessMemPidDecision::OnDecisionTimer()
398+{
399+ uint64_t roundNum = roundNumber_.fetch_add(1) + 1;
400+ 
401+ bool expected = false;
402+ if (!cycleRunning_.compare_exchange_strong(expected, true, std::memory_order_acq_rel)) {
403+ UBSE_LOG_DEBUG << "[process_mem] borrow round=" << roundNum << " skip: backpressure (prev round running)";
404+ return UBSE_OK;
405+ }
406+ std::string traceId = TraceContext::GetTraceId();
407+ if (!borrowExecutor_->Execute([this, roundNum, traceId]() {
408+ TraceContext::SetTraceId(traceId);
409+ RunBorrowRound(roundNum);
410+ cycleRunning_.store(false, std::memory_order_release);
411+ TraceContext::Clear();
412+ })) {
413+ cycleRunning_.store(false, std::memory_order_release);
414+ UBSE_LOG_ERROR << "[process_mem] borrow round=" << roundNum << " enqueue failed";
415+ }
416+ 
417+ return UBSE_OK;
418+}
419+ 
420+bool ProcessMemPidDecision::CheckNodeFreeMemory(uint64_t& outShortage)
421+{
422+ auto freeBytes = GetNodeFreeBytes();
423+ if (!freeBytes.has_value()) {
424+ UBSE_LOG_WARN << "[process_mem] borrow round=" << roundNumber_.load()
425+ << " read memAvailable failed, skip round decision";
426+ return false;
427+ }
428+ uint64_t totalFree = *freeBytes;
429+ 
430+ uint64_t roundNum = roundNumber_.load();
431+ UBSE_LOG_DEBUG << "[process_mem] borrow round=" << roundNum
432+ << " step=node_check node_free_gb=" << BytesToGbDouble(totalFree)
433+ << " threshold_gb=" << BytesToGbDouble(freeMemoryThresholdBytes_) << " shortage_gb="
434+ << (totalFree >= freeMemoryThresholdBytes_ ?
435+ "0 (skip)" :
436+ std::to_string(BytesToGb(freeMemoryThresholdBytes_ - totalFree)));
437+ 
438+ if (totalFree >= freeMemoryThresholdBytes_) {
439+ return false;
440+ }
441+ 
442+ outShortage = freeMemoryThresholdBytes_ - totalFree;
443+ return true;
444+}
445+ 
446+uint64_t ProcessMemPidDecision::GetPendingBorrowTotal() const
447+{
448+ uint64_t total = 0;
449+ auto snapshot = ProcessMemPidInfoManager::GetInstance().GetManagedPidCacheSnapshot();
450+ for (const auto& [pid, entry] : snapshot) {
451+ for (const auto& s : entry.borrow.slots) {
452+ if (s.status == def::BorrowSlotStatus::BORROWING) {
453+ total += s.migratedBytes;
454+ }
455+ }
456+ }
457+ return total;
458+}
459+ 
460+CandidateSkip AppendBorrowCandidate(pid_t pid, const def::ManagedPidEntry& entry,
461+ std::vector<def::BorrowCandidate>& candidates)
462+{
463+ if (entry.maxMemory == 0 || entry.remoteRatio <= 0.0) {
464+ return CandidateSkip::CAN_MIGRATE;
465+ }
466+ 
467+ bool hasActiveBorrow = false;
468+ uint64_t pendingBorrow = 0;
469+ for (const auto& s : entry.borrow.slots) {
470+ if (s.status == def::BorrowSlotStatus::BORROWING) {
471+ hasActiveBorrow = true;
472+ pendingBorrow += s.migratedBytes;
473+ }
474+ }
475+ uint64_t targetRemote = static_cast<uint64_t>(entry.vmRss * entry.remoteRatio);
476+ uint64_t canMigrate = (targetRemote > entry.borrow.currentRemote) ? (targetRemote - entry.borrow.currentRemote) : 0;
477+ canMigrate = (canMigrate > pendingBorrow) ? (canMigrate - pendingBorrow) : 0;
478+ if (canMigrate == 0) {
479+ return CandidateSkip::CAN_MIGRATE;
480+ }
481+ 
482+ def::BorrowCandidate candidate;
483+ candidate.pid = pid;
484+ candidate.isChild = entry.isChild;
485+ candidate.hasActiveBorrow = hasActiveBorrow;
486+ candidate.lastMigrateTime = entry.lastMigrateTime;
487+ candidate.actual = entry.vmRss;
488+ candidate.maxMemory = entry.maxMemory;
489+ candidate.remoteRatio = entry.remoteRatio;
490+ candidate.currentRemote = entry.borrow.currentRemote;
491+ candidate.canMigrate = canMigrate;
492+ candidates.push_back(candidate);
493+ return CandidateSkip::NONE;
494+}
495+ 
496+void ProcessMemPidDecision::LogBorrowCandidates(uint64_t roundNum, const std::vector<def::BorrowCandidate>& candidates)
497+{
498+ for (const auto& c : candidates) {
499+ UBSE_LOG_DEBUG << "[process_mem] borrow round=" << roundNum << " candidate pid=" << c.pid
500+ << " actual_gb=" << BytesToGbDouble(c.actual) << " max_gb=" << BytesToGbDouble(c.maxMemory)
501+ << " ratio=" << c.remoteRatio << " cur_remote_gb=" << BytesToGbDouble(c.currentRemote)
502+ << " can_migrate_gb=" << BytesToGbDouble(c.canMigrate);
503+ }
504+}
505+ 
506+std::vector<def::BorrowCandidate> ProcessMemPidDecision::BuildCandidates(uint64_t roundNum)
507+{
508+ auto cacheSnapshot = ProcessMemPidInfoManager::GetInstance().GetManagedPidCacheSnapshot();
509+ 
510+ std::vector<def::BorrowCandidate> candidates;
511+ size_t skipCanMigrate = 0;
512+ 
513+ for (const auto& [pid, entry] : cacheSnapshot) {
514+ auto skip = AppendBorrowCandidate(pid, entry, candidates);
515+ if (skip == CandidateSkip::CAN_MIGRATE) {
516+ ++skipCanMigrate;
517+ }
518+ }
519+ 
520+ constexpr int64_t staleIntervalSec = 60;
521+ auto now = std::chrono::steady_clock::now();
522+ std::sort(candidates.begin(), candidates.end(), [now, staleIntervalSec](const auto& a, const auto& b) {
523+ if (a.hasActiveBorrow != b.hasActiveBorrow) {
524+ return !a.hasActiveBorrow;
525+ }
526+ bool aOverCap = a.actual > static_cast<uint64_t>(a.maxMemory * a.remoteRatio);
527+ bool bOverCap = b.actual > static_cast<uint64_t>(b.maxMemory * b.remoteRatio);
528+ if (aOverCap != bOverCap) {
529+ return aOverCap;
530+ }
531+ if (a.isChild != b.isChild) {
532+ return !a.isChild;
533+ }
534+ auto aStale = (a.lastMigrateTime.time_since_epoch().count() == 0) ||
535+ (now - a.lastMigrateTime) > std::chrono::seconds(staleIntervalSec);
536+ auto bStale = (b.lastMigrateTime.time_since_epoch().count() == 0) ||
537+ (now - b.lastMigrateTime) > std::chrono::seconds(staleIntervalSec);
538+ if (aStale != bStale) {
539+ return aStale;
540+ }
541+ return a.actual > b.actual;
542+ });
543+ 
544+ UBSE_LOG_DEBUG << "[process_mem] borrow round=" << roundNum
545+ << " step=candidates total_managed=" << cacheSnapshot.size() << " eligible=" << candidates.size()
546+ << " skip_canmigrate=" << skipCanMigrate;
547+ 
548+ LogBorrowCandidates(roundNum, candidates);
549+ return candidates;
550+}
551+ 
552+int ProcessMemPidDecision::CollectSrcNuma(pid_t pid, uint64_t roundNum)
553+{
554+ auto parseStart = std::chrono::steady_clock::now();
555+ 
556+ std::unordered_map<uint32_t, size_t> numaDistribution;
557+ auto ret = collect::ProcessMemPidCollect::GetInstance().CollectProcessNumaMemDistribution(pid, numaDistribution);
558+ if (ret != UBSE_OK || numaDistribution.empty()) {
559+ UBSE_LOG_DEBUG << "[process_mem] borrow round=" << roundNum << " step=numa pid=" << pid
560+ << " src_numa=-1 (no data)";
561+ return -1;
562+ }
563+ 
564+ int bestNuma = -1;
565+ size_t maxPages = 0;
566+ for (const auto& [numaId, memSize] : numaDistribution) {
567+ if (memSize > maxPages) {
568+ maxPages = memSize;
569+ bestNuma = static_cast<int>(numaId);
570+ }
571+ }
572+ 
573+ auto parseEnd = std::chrono::steady_clock::now();
574+ auto parseMs = std::chrono::duration_cast<std::chrono::milliseconds>(parseEnd - parseStart).count();
575+ 
576+ UBSE_LOG_DEBUG << "[process_mem] borrow round=" << roundNum << " step=numa pid=" << pid << " src_numa=" << bestNuma
577+ << " parse_ms=" << parseMs;
578+ 
579+ return bestNuma;
580+}
581+ 
582+void ProcessMemPidDecision::ExecuteBorrowRound(const std::vector<def::BorrowCandidate>& candidates, uint64_t& shortage,
583+ uint64_t roundNum, bool emergency)
584+{
585+ auto& infoMgr = ProcessMemPidInfoManager::GetInstance();
586+ 
587+ for (const auto& candidate : candidates) {
588+ if (shortage == 0) {
589+ break;
590+ }
591+ 
592+ uint64_t amount = std::min(candidate.canMigrate, shortage);
593+ 
594+ int srcNumaId = CollectSrcNuma(candidate.pid, roundNum);
595+ 
596+ std::string debtId = RecordPendingBorrow(candidate.pid, amount, srcNumaId, roundNum);
597+ if (debtId.empty()) {
598+ continue;
599+ }
600+ 
601+ if (emergency) {
602+ UBSE_LOG_INFO << "[process_mem] oom emergency_borrow pid=" << candidate.pid
603+ << " amount_gb=" << BytesToGbDouble(amount) << " (smap)";
604+ } else {
605+ UBSE_LOG_INFO << "[process_mem] borrow round=" << roundNum << " step=execute pid=" << candidate.pid
606+ << " amount_gb=" << BytesToGbDouble(amount) << " same_plane_prefer=" << samePlanePrefer_
607+ << " timeout_ms=" << ((amount / BYTES_PER_MB / MB_128 + 1) * timeoutPer128MbMs_);
608+ }
609+ 
610+ std::string traceId = TraceContext::GetTraceId();
611+ borrowExecutor_->Execute([this, debtId, pid = candidate.pid, amount, srcNumaId, roundNum, traceId]() {
612+ TraceContext::SetTraceId(traceId);
613+ AsyncBorrowAndMigrate(debtId, pid, amount, srcNumaId, roundNum);
614+ TraceContext::Clear();
615+ });
616+ 
617+ shortage -= amount;
618+ }
619+}
620+ 
621+std::string ProcessMemPidDecision::RecordPendingBorrow(pid_t pid, uint64_t amount, int srcNumaId, uint64_t roundNum)
622+{
623+ auto& infoMgr = ProcessMemPidInfoManager::GetInstance();
624+ def::BorrowSlot slot;
625+ slot.migratedBytes = amount;
626+ slot.srcNumaId = srcNumaId;
627+ slot.borrowTime = std::chrono::steady_clock::now();
628+ slot.status = def::BorrowSlotStatus::BORROWING;
629+ slot.debtId = GenerateBorrowName(pid);
630+ bool recorded = false;
631+ infoMgr.UpdateManagedPidBorrowStateAtomic(pid, [&](def::BorrowState& newBorrow, def::ProcessStatus& newStatus) {
632+ newBorrow.slots.push_back(slot);
633+ newStatus = def::ProcessStatus::BORROWING;
634+ recorded = true;
635+ });
636+ if (!recorded) {
637+ UBSE_LOG_WARN << "[process_mem] borrow round=" << roundNum << " pid=" << pid
638+ << " record_skipped reason=pid_not_managed";
639+ return "";
640+ }
641+ 
642+ UBSE_LOG_INFO << "[process_mem] borrow round=" << roundNum << " step=record pid=" << pid
643+ << " debt_id=" << slot.debtId << " amount_gb=" << BytesToGbDouble(amount)
644+ << " src_numa=" << srcNumaId;
645+ return slot.debtId;
646+}
647+ 
648+def::AtomicMigrateResult ProcessMemPidDecision::CommitBorrowAndMigrate(
649+ pid_t pid, const std::string& debtId, const CreatedDebtInfo& created, const std::map<int, uint64_t>& increments,
650+ std::vector<std::pair<int, uint64_t>>& numaTargets)
651+{
652+ def::AtomicMigrateResult result = def::AtomicMigrateResult::kFail;
653+ auto& infoMgr = ProcessMemPidInfoManager::GetInstance();
654+ infoMgr.UpdateManagedPidBorrowStateAtomic(pid, [&](def::BorrowState& newBorrow, def::ProcessStatus& newStatus) {
655+ if (created.capacity > 0) {
656+ auto slotIt = std::find_if(newBorrow.slots.begin(), newBorrow.slots.end(),
657+ [&debtId](const def::BorrowSlot& s) { return s.debtId == debtId; });
658+ if (slotIt == newBorrow.slots.end() || slotIt->status != def::BorrowSlotStatus::BORROWING ||
659+ slotIt->returnStatus != def::ReturnStatus::NONE) {
660+ result = def::AtomicMigrateResult::kVanish;
661+ return;
662+ }
663+ }
664+ BuildMigrateTargets(newBorrow, increments, numaTargets);
665+ if (numaTargets.empty() || RmrsMigrateToNumas(pid, numaTargets) != 0) {
666+ result = def::AtomicMigrateResult::kFail;
667+ return;
668+ }
669+ newBorrow.currentRemote += FlushBorrowIncrements(newBorrow, numaTargets, increments, debtId, created);
670+ newStatus = ComputeSlotStatus(newBorrow.slots);
671+ result = def::AtomicMigrateResult::kOk;
672+ });
673+ return result;
674+}
675+ 
676+void ProcessMemPidDecision::RemoveSlotFinalize(pid_t pid, const std::string& debtId)
677+{
678+ auto& infoMgr = ProcessMemPidInfoManager::GetInstance();
679+ infoMgr.UpdateManagedPidBorrowStateAtomic(pid, [&](def::BorrowState& newBorrow, def::ProcessStatus& newStatus) {
680+ auto slotIt = std::find_if(newBorrow.slots.begin(), newBorrow.slots.end(),
681+ [&debtId](const def::BorrowSlot& s) { return s.debtId == debtId; });
682+ if (slotIt != newBorrow.slots.end() && slotIt->status == def::BorrowSlotStatus::BORROWING) {
683+ newBorrow.slots.erase(slotIt);
684+ }
685+ newStatus = ComputeSlotStatus(newBorrow.slots);
686+ });
687+}
688+ 
689+uint64_t ProcessMemPidDecision::FlushBorrowIncrements(def::BorrowState& borrow,
690+ const std::vector<std::pair<int, uint64_t>>& numaTargets,
691+ const std::map<int, uint64_t>& increments,
692+ const std::string& ownDebtId, const CreatedDebtInfo& created)
693+{
694+ uint64_t delta = 0;
695+ for (auto& s : borrow.slots) {
696+ if (s.debtId != ownDebtId || s.status != def::BorrowSlotStatus::BORROWING) {
697+ continue;
698+ }
699+ s.remoteNumaId = created.remoteNumaId;
700+ s.exportSlotId = created.exportSlotId;
701+ s.capacity = created.capacity;
702+ }
703+ for (const auto& [numaId, memSizeKb] : numaTargets) {
704+ auto incIt = increments.find(numaId);
705+ if (incIt == increments.end() || incIt->second == 0) {
706+ continue;
707+ }
708+ uint64_t remaining = incIt->second;
709+ for (auto& s : borrow.slots) {
710+ if (s.debtId != ownDebtId || s.remoteNumaId != numaId || s.status != def::BorrowSlotStatus::BORROWING ||
711+ s.capacity == 0 || s.returnStatus == def::ReturnStatus::RETURNING) {
712+ continue;
713+ }
714+ uint64_t fill = std::min(s.capacity, remaining);
715+ s.migratedBytes = fill;
716+ s.status = def::BorrowSlotStatus::COMPLETED;
717+ delta += fill;
718+ break;
719+ }
720+ }
721+ return delta;
722+}
723+ 
724+bool ProcessMemPidDecision::BuildBorrower(pid_t pid, int srcNumaId, uint64_t need, uint64_t roundNum,
725+ ubse::mem::controller::UbseMemBorrower& borrower)
726+{
727+ borrower.nodeId = GetCurrentNodeId();
728+ if (borrower.nodeId.empty()) {
729+ UBSE_LOG_ERROR << "[process_mem] borrow round=" << roundNum << " pid=" << pid
730+ << " slot_failed amount_gb=" << BytesToGbDouble(need) << " reason=borrower_nodeid_empty";
731+ return false;
732+ }
733+ 
734+ auto curNodeInfo = ubse::nodeController::UbseNodeController::GetInstance().GetCurNode();
735+ for (const auto& [numaLoc, numaInfo] : curNodeInfo.numaInfos) {
736+ if (static_cast<int>(numaLoc.numaId) == srcNumaId) {
737+ borrower.affinitySocketId = numaInfo.socketId;
738+ break;
739+ }
740+ }
741+ borrower.samePlanePrefer = samePlanePrefer_;
742+ return true;
743+}
744+ 
745+bool ProcessMemPidDecision::CreateNumaDebt(pid_t pid, uint64_t need, int srcNumaId, const std::string& debtId,
746+ uint64_t roundNum, CreatedDebtInfo& out)
747+{
748+ def::ProcessMemUsrInfo usrInfo{};
749+ usrInfo.pid = static_cast<int32_t>(pid);
750+ usrInfo.srcNuma = static_cast<int32_t>(srcNumaId);
751+ 
752+ ubse::mem::controller::UbseMemBorrower borrower{};
753+ if (!BuildBorrower(pid, srcNumaId, need, roundNum, borrower)) {
754+ RemoveSlotFinalize(pid, debtId);
755+ return false;
756+ }
757+ 
758+ ubse::mem::controller::UbseMemNumaCreateOpt opt;
759+ opt.highWatermark = kBorrowHighWatermark;
760+ opt.size = need;
761+ if (memcpy_s(opt.usrInfo, ubse::mem::controller::UBSE_MAX_USR_INFO_LEN, &usrInfo, sizeof(usrInfo)) != EOK) {
762+ UBSE_LOG_ERROR << "[process_mem] borrow round=" << roundNum << " pid=" << pid
763+ << " slot_failed amount_gb=" << BytesToGbDouble(need) << " reason=memcpy_usrinfo";
764+ RemoveSlotFinalize(pid, debtId);
765+ return false;
766+ }
767+ 
768+ ubse::mem::controller::UbseMemNumaDesc desc{};
769+ auto ret = ubse::mem::controller::UbseMemNumaCreate(debtId, borrower, opt, desc);
770+ if (ret != UBSE_OK) {
771+ UBSE_LOG_ERROR << "[process_mem] borrow round=" << roundNum << " pid=" << pid
772+ << " slot_failed amount_gb=" << BytesToGbDouble(need) << " reason=create_failed ret=" << ret;
773+ RemoveSlotFinalize(pid, debtId);
774+ return false;
775+ }
776+ 
777+ out.remoteNumaId = static_cast<int>(desc.numaId);
778+ out.exportSlotId = static_cast<int32_t>(desc.exportNode.slotId);
779+ out.capacity = desc.size;
780+ return true;
781+}
782+ 
783+void ProcessMemPidDecision::BuildMigrateTargets(const def::BorrowState& borrow,
784+ const std::map<int, uint64_t>& increments,
785+ std::vector<std::pair<int, uint64_t>>& numaTargets)
786+{
787+ numaTargets.clear();
788+ constexpr uint64_t pageSizeKb = 4;
789+ std::map<int, uint64_t> targets;
790+ for (const auto& [numaId, migrated] : borrow.remoteNumaMigrated) {
791+ targets[numaId] += migrated;
792+ }
793+ for (const auto& [numaId, inc] : increments) {
794+ targets[numaId] += inc;
795+ }
796+ for (const auto& [numaId, target] : targets) {
797+ uint64_t memSizeKb = target / 1024 / pageSizeKb * pageSizeKb;
798+ if (memSizeKb == 0) {
799+ continue;
800+ }
801+ numaTargets.emplace_back(numaId, memSizeKb);
802+ auto incIt = increments.find(numaId);
803+ UBSE_LOG_INFO << "[process_mem] numa_target_migrate numa=" << numaId << " target_gb=" << BytesToGbDouble(target)
804+ << " increment_gb=" << BytesToGbDouble(incIt != increments.end() ? incIt->second : 0);
805+ }
806+}
807+ 
808+void ProcessMemPidDecision::FailBorrowAbort(pid_t pid, const std::string& debtId, uint64_t need, uint64_t roundNum)
809+{
810+ UBSE_LOG_ERROR << "[process_mem] borrow round=" << roundNum << " pid=" << pid
811+ << " slot_failed reason=numa_target_migrate_failed";
812+ if (need > 0) {
813+ pid::bridge::ProcessMemPidBridge::MemoryReturn(debtId);
814+ }
815+ RemoveSlotFinalize(pid, debtId);
816+}
817+ 
818+void ProcessMemPidDecision::AsyncBorrowAndMigrate(const std::string& debtId, pid_t pid, uint64_t amount, int srcNumaId,
819+ uint64_t roundNum)
820+{
821+ uint64_t need = amount;
822+ std::map<int, uint64_t> increments;
823+ 
824+ CreatedDebtInfo created;
825+ if (!CreateNumaDebt(pid, need, srcNumaId, debtId, roundNum, created)) {
826+ return;
827+ }
828+ increments[created.remoteNumaId] += need;
829+ UBSE_LOG_INFO << "[process_mem] borrow round=" << roundNum << " pid=" << pid << " ubse_created debt_id=" << debtId
830+ << " export_slot=" << created.exportSlotId << " remote_numa=" << created.remoteNumaId
831+ << " capacity_gb=" << BytesToGbDouble(created.capacity);
832+ 
833+ std::vector<std::pair<int, uint64_t>> numaTargets;
834+ def::AtomicMigrateResult migrateResult = CommitBorrowAndMigrate(pid, debtId, created, increments, numaTargets);
835+ if (migrateResult == def::AtomicMigrateResult::kVanish) {
836+ pid::bridge::ProcessMemPidBridge::MemoryReturn(debtId);
837+ UBSE_LOG_WARN << "[process_mem] borrow round=" << roundNum << " pid=" << pid
838+ << " slot_vanished debt_id=" << debtId
839+ << " (removed by timeout or taken over by return, ubse debt returned)";
840+ return;
841+ }
842+ if (migrateResult != def::AtomicMigrateResult::kOk) {
843+ FailBorrowAbort(pid, debtId, need, roundNum);
844+ return;
845+ }
846+ ProcessMemPidInfoManager::GetInstance().UpdateManagedPidLastMigrateTime(pid);
847+ UBSE_LOG_INFO << "[process_mem] borrow round=" << roundNum << " pid=" << pid << " slot_completed debt_id=" << debtId
848+ << " numa_target_migrate";
849+}
850+ 
851+int ProcessMemPidDecision::RmrsMigrateToNumas(pid_t pid, const std::vector<std::pair<int, uint64_t>>& numaTargets)
852+{
853+ std::vector<mempooling::smap::MigrateOutPayload> payloads{};
854+ mempooling::smap::MigrateOutPayload payload{};
855+ constexpr size_t kMaxInner = static_cast<size_t>(mempooling::smap::REMOTE_NUMA_NUM);
856+ size_t innerCount = std::min(numaTargets.size(), kMaxInner);
857+ if (numaTargets.size() > kMaxInner) {
858+ UBSE_LOG_WARN << "[process_mem] borrow pid=" << pid << " numa_targets=" << numaTargets.size()
859+ << " exceed inner capacity " << kMaxInner << ", truncated";
860+ }
861+ for (size_t i = 0; i < innerCount; ++i) {
862+ mempooling::smap::MigrateOutPayloadInner inner{};
863+ inner.migrateMode = mempooling::smap::MIG_MEMSIZE_MODE;
864+ inner.memSize = numaTargets[i].second;
865+ inner.destNid = numaTargets[i].first;
866+ payload.inner[i] = inner;
867+ }
868+ payload.count = static_cast<int>(innerCount);
869+ payload.pid = pid;
870+ payloads.push_back(payload);
871+ if (!pid::bridge::ProcessMemPidBridge::rmrsMigrateOut) {
872+ UBSE_LOG_ERROR << "[process_mem] borrow pid=" << pid << " rmrsMigrateOut not loaded, skip migrate";
873+ return -1;
874+ }
875+ return pid::bridge::ProcessMemPidBridge::rmrsMigrateOut(payloads, 0);
876+}
877+ 
878+void ProcessMemPidDecision::CheckTimeouts(uint64_t roundNum)
879+{
880+ auto& infoMgr = ProcessMemPidInfoManager::GetInstance();
881+ auto snapshot = infoMgr.GetManagedPidCacheSnapshot();
882+ 
883+ for (const auto& snapshotItem : snapshot) {
884+ pid_t pid = snapshotItem.first;
885+ const auto& entry = snapshotItem.second;
886+ if (entry.borrow.slots.empty()) {
887+ continue;
888+ }
889+ infoMgr.UpdateManagedPidBorrowStateAtomic(
890+ pid, [this, pid, roundNum](def::BorrowState& newBorrow, def::ProcessStatus& newStatus) {
891+ if (!CheckPidTimeoutSlots(pid, newBorrow, roundNum)) {
892+ return;
893+ }
894+ newBorrow.currentRemote = 0;
895+ for (const auto& s : newBorrow.slots) {
896+ if (s.status == def::BorrowSlotStatus::COMPLETED) {
897+ newBorrow.currentRemote += s.migratedBytes;
898+ }
899+ }
900+ newBorrow.slots.erase(
901+ std::remove_if(newBorrow.slots.begin(), newBorrow.slots.end(),
902+ [](const def::BorrowSlot& s) { return s.status == def::BorrowSlotStatus::FAILED; }),
903+ newBorrow.slots.end());
904+ newStatus = ComputeSlotStatus(newBorrow.slots);
905+ });
906+ }
907+}
908+ 
909+bool ProcessMemPidDecision::CheckPidTimeoutSlots(pid_t pid, def::BorrowState& borrow, uint64_t roundNum)
910+{
911+ bool hasChanges = false;
912+ for (auto& slot : borrow.slots) {
913+ if (slot.status != def::BorrowSlotStatus::BORROWING) {
914+ continue;
915+ }
916+ 
917+ auto now = std::chrono::steady_clock::now();
918+ auto elapsedMs = std::chrono::duration_cast<std::chrono::milliseconds>(now - slot.borrowTime).count();
919+ uint64_t blocks128Mb = (slot.migratedBytes / BYTES_PER_MB + MB_128 - 1) / MB_128;
920+ auto timeoutMs = static_cast<int64_t>(blocks128Mb * timeoutPer128MbMs_);
921+ 
922+ if (elapsedMs <= timeoutMs) {
923+ continue;
924+ }
925+ 
926+ UBSE_LOG_INFO << "[process_mem] borrow round=" << roundNum << " pid=" << pid
927+ << " slot_timeout migrated_gb=" << BytesToGbDouble(slot.migratedBytes)
928+ << " remote_numa=" << slot.remoteNumaId << " debt_id=" << slot.debtId
929+ << " elapsed_ms=" << elapsedMs << " timeout_ms=" << timeoutMs;
930+ 
931+ slot.status = def::BorrowSlotStatus::FAILED;
932+ hasChanges = true;
933+ 
934+ if (!EnqueueReturnDebt(pid, def::ReturnRequestItem{slot.debtId, slot.migratedBytes}, ReturnScene::TIMEOUT)) {
935+ pid::bridge::ProcessMemPidBridge::MemoryReturn(slot.debtId);
936+ }
937+ }
938+ return hasChanges;
939+}
940+ 
941+std::optional<uint64_t> ProcessMemPidDecision::GetNodeFreeBytes()
942+{
943+ auto memAvailableKb = ReadMemAvailableKb();
944+ if (!memAvailableKb.has_value()) {
945+ return std::nullopt;
946+ }
947+ return *memAvailableKb * 1024;
948+}
949+ 
950+std::optional<uint64_t> ProcessMemPidDecision::ReadMemAvailableKb()
951+{
952+ if (memAvailableReader) {
953+ return memAvailableReader();
954+ }
955+ std::ifstream meminfo("/proc/meminfo");
956+ std::string line;
957+ while (std::getline(meminfo, line)) {
958+ if (line.rfind("MemAvailable:", 0) == 0) {
959+ std::istringstream iss(line);
960+ std::string key;
961+ uint64_t kb = 0;
962+ if (iss >> key >> kb) {
963+ return kb;
964+ }
965+ return std::nullopt;
966+ }
967+ }
968+ return std::nullopt;
969+}
970+ 
971+void ProcessMemPidDecision::BroadcastPassiveReturn(uint64_t roundNum, uint64_t nodeFree, bool emergency)
972+{
973+ auto nodeId = GetCurrentNodeId();
974+ 
975+ std::vector<ubse::mem::controller::UbseNumaMemoryDebtInfo> debts;
976+ auto ret = ubse::mem::controller::UbseGetNumaMemDebtInfoWithNode(nodeId, debts);
977+ if (ret != UBSE_OK) {
978+ UBSE_LOG_ERROR << "[process_mem] return passive round=" << roundNum << " get own debts failed, ret=" << ret;
979+ return;
980+ }
981+ 
982+ std::unordered_map<std::string, std::vector<def::ReturnRequestItem>> groups;
983+ uint64_t lentDebtCount = 0;
984+ for (const auto& debt : debts) {
985+ if (debt.lentNodeId != nodeId) {
986+ continue;
987+ }
988+ ++lentDebtCount;
989+ if (debt.borrowNodeId.empty() || debt.borrowNodeId == nodeId) {
990+ continue;
991+ }
992+ groups[debt.borrowNodeId].push_back(def::ReturnRequestItem{debt.name, debt.size});
993+ }
994+ 
995+ for (const auto& [borrowerNode, items] : groups) {
996+ auto sendRet = pid::bridge::ProcessMemPidBridge::SendReturnRequestToNode(borrowerNode, items);
997+ if (sendRet != UBSE_OK) {
998+ UBSE_LOG_WARN << "[process_mem] return passive round=" << roundNum << " send failed to " << borrowerNode
999+ << ", ret=" << sendRet;
1000+ }
1001+ }
1002+ 
1003+ if (emergency) {
1004+ UBSE_LOG_INFO << "[process_mem] oom lender_emergency: broadcasting to " << groups.size()
1005+ << " borrowers, lent_debts=" << lentDebtCount;
1006+ } else {
1007+ UBSE_LOG_INFO << "[process_mem] return passive lender=" << nodeId << " broadcasting to " << groups.size()
1008+ << " borrowers (nodeFree=" << BytesToGbDouble(nodeFree)
1009+ << " < threshold=" << BytesToGbDouble(freeMemoryThresholdBytes_) << ")";
1010+ }
1011+}
1012+ 
1013+uint32_t ProcessMemPidDecision::HandleReturnRequest(const std::vector<def::ReturnRequestItem>& items)
1014+{
1015+ if (items.empty()) {
1016+ return UBSE_ERROR;
1017+ }
1018+ 
1019+ for (const auto& item : items) {
1020+ if (!EnqueueReturnDebt(0, item, ReturnScene::PASSIVE)) {
1021+ UBSE_LOG_WARN << "[process_mem] return passive debt_id=" << item.name
1022+ << " enqueue failed (stopping/queue full)";
1023+ }
1024+ }
1025+ return UBSE_OK;
1026+}
1027+ 
1028+bool ProcessMemPidDecision::EnqueueReturnDebt(pid_t pid, const def::ReturnRequestItem& item, ReturnScene scene)
1029+{
1030+ if (stopping_) {
1031+ return false;
1032+ }
1033+ std::string traceId = TraceContext::GetTraceId();
1034+ return returnExecutor_->Execute([this, pid, item, scene, traceId]() {
1035+ TraceContext::SetTraceId(traceId);
1036+ RunReturnDebt(pid, item, scene);
1037+ TraceContext::Clear();
1038+ });
1039+}
1040+ 
1041+void ProcessMemPidDecision::RunReturnDebt(pid_t pid, def::ReturnRequestItem item, ReturnScene scene)
1042+{
1043+ if (pid <= 0) {
1044+ pid = ResolveDebtPid(item.name);
1045+ }
1046+ 
1047+ uint32_t ret = UBSE_OK;
1048+ if (pid < 0) {
1049+ ret = UBSE_ERROR;
1050+ } else if (pid == 0) {
1051+ ProcessMemPidInfoManager::GetInstance().ResetSlotByDebtName(item.name);
1052+ UBSE_LOG_INFO << "[process_mem] return " << ReturnSceneToString(scene) << " debt_id=" << item.name
1053+ << " skip: debt not found locally";
1054+ return;
1055+ } else {
1056+ ret = DoReturnDebtOnce(pid, item, scene);
1057+ }
1058+ 
1059+ if (ret == UBSE_OK) {
1060+ return;
1061+ }
1062+ 
1063+ if (ret == UBSE_ERR_NOT_EXIST) {
1064+ ProcessMemPidInfoManager::GetInstance().UpdateManagedPidSlotReturnStatus(pid, item.name,
1065+ def::ReturnStatus::NONE);
1066+ return;
1067+ }
1068+ 
1069+ ProcessMemPidInfoManager::GetInstance().UpdateManagedPidSlotReturnStatus(pid, item.name, def::ReturnStatus::NONE);
1070+ UBSE_LOG_DEBUG << "[process_mem] return " << ReturnSceneToString(scene) << " debt_id=" << item.name
1071+ << " failed (ret=" << ret << "), retry later";
1072+ uint32_t retryCount = 0;
1073+ RetryReturnEnqueue(retryCount, [this, pid, item, scene]() { return EnqueueReturnDebt(pid, item, scene); });
1074+}
1075+ 
1076+uint32_t ProcessMemPidDecision::DoReturnDebtOnce(pid_t pid, const def::ReturnRequestItem& item, ReturnScene scene)
1077+{
1078+ std::lock_guard<std::mutex> pidLock(GetPidMutex(pid));
1079+ if (scene == ReturnScene::TIMEOUT) {
1080+ auto delRet = pid::bridge::ProcessMemPidBridge::MemoryReturn(item.name);
1081+ if (delRet == UBSE_OK || delRet == UBSE_ERR_NOT_EXIST) {
1082+ return UBSE_OK;
1083+ }
1084+ UBSE_LOG_WARN << "[process_mem] return timeout debt_id=" << item.name << " ubse delete failed ret=" << delRet;
1085+ return delRet;
1086+ }
1087+ ProcessMemPidInfoManager::GetInstance().UpdateManagedPidSlotReturnStatus(pid, item.name,
1088+ def::ReturnStatus::RETURNING);
1089+ if (item.size == 0) {
1090+ auto delRet = pid::bridge::ProcessMemPidBridge::MemoryReturn(item.name);
1091+ if (delRet == UBSE_OK || delRet == UBSE_ERR_NOT_EXIST) {
1092+ ProcessMemPidInfoManager::GetInstance().UpdateManagedPidSlotReturned(pid, item.name, 0);
1093+ return UBSE_OK;
1094+ }
1095+ UBSE_LOG_WARN << "[process_mem] return " << ReturnSceneToString(scene) << " debt_id=" << item.name
1096+ << " ubse delete failed ret=" << delRet;
1097+ return delRet;
1098+ }
1099+ if (scene == ReturnScene::ACTIVE) {
1100+ auto freeRet = pid::bridge::ProcessMemPidBridge::rmrsFreeWithMigrate(item.name);
1101+ if (freeRet != UBSE_OK) {
1102+ UBSE_LOG_WARN << "[process_mem] return " << ReturnSceneToString(scene) << " debt_id=" << item.name
1103+ << " migrate_back failed ret=" << freeRet;
1104+ return freeRet;
1105+ }
1106+ auto delRet = pid::bridge::ProcessMemPidBridge::MemoryReturn(item.name);
1107+ if (delRet == UBSE_OK || delRet == UBSE_ERR_NOT_EXIST) {
1108+ ProcessMemPidInfoManager::GetInstance().UpdateManagedPidSlotReturned(pid, item.name, item.size);
1109+ return UBSE_OK;
1110+ }
1111+ UBSE_LOG_WARN << "[process_mem] return " << ReturnSceneToString(scene) << " debt_id=" << item.name
1112+ << " ubse delete failed ret=" << delRet;
1113+ return delRet;
1114+ }
1115+ auto nodeFreeOpt = GetNodeFreeBytes();
1116+ if (!nodeFreeOpt.has_value()) {
1117+ UBSE_LOG_WARN << "[process_mem] return " << ReturnSceneToString(scene) << " debt_id=" << item.name
1118+ << " read memAvailable failed, retry later";
1119+ return UBSE_ERROR;
1120+ }
1121+ return ReturnOneDebt(item, *nodeFreeOpt);
1122+}
1123+ 
1124+pid_t ProcessMemPidDecision::ResolveDebtPid(const std::string& name)
1125+{
1126+ std::vector<ubse::mem::controller::UbseNumaMemoryDebtInfo> debts;
1127+ if (ubse::mem::controller::UbseGetNumaMemDebtInfoWithNode(GetCurrentNodeId(), debts) != UBSE_OK) {
1128+ return -1;
1129+ }
1130+ for (const auto& debt : debts) {
1131+ if (debt.name != name) {
1132+ continue;
1133+ }
1134+ def::ProcessMemUsrInfo usrInfo{};
1135+ if (memcpy_s(&usrInfo, sizeof(usrInfo), debt.usrInfo, sizeof(usrInfo)) == EOK && usrInfo.pid > 0) {
1136+ return static_cast<pid_t>(usrInfo.pid);
1137+ }
1138+ }
1139+ return 0;
1140+}
1141+ 
1142+std::mutex& ProcessMemPidDecision::GetPidMutex(pid_t pid)
1143+{
1144+ return pidMutexes_[static_cast<size_t>(pid) % kPidMutexNum];
1145+}
1146+ 
1147+bool ProcessMemPidDecision::EnqueuePidReturn(pid_t pid, ReturnScene scene)
1148+{
1149+ if (stopping_) {
1150+ return false;
1151+ }
1152+ return returnExecutor_->Execute([this, pid, scene]() { RunPidReturn(pid, scene); });
1153+}
1154+ 
1155+uint32_t ProcessMemPidDecision::DoPidReturnOnce(pid_t pid, ReturnScene scene)
1156+{
1157+ std::lock_guard<std::mutex> pidLock(GetPidMutex(pid));
1158+ auto nodeId = GetCurrentNodeId();
1159+ 
1160+ std::vector<ubse::mem::controller::UbseNumaMemoryDebtInfo> debts;
1161+ auto ret = ubse::mem::controller::UbseGetNumaMemDebtInfoWithNode(nodeId, debts);
1162+ if (ret != UBSE_OK) {
1163+ UBSE_LOG_WARN << "[process_mem] return " << ReturnSceneToString(scene) << " pid=" << pid
1164+ << " skip: get own debts failed, ret=" << ret;
1165+ return ret;
1166+ }
1167+ 
1168+ auto matched = MatchPidDebts(pid, debts);
1169+ if (matched.empty()) {
1170+ UBSE_LOG_INFO << "[process_mem] return " << ReturnSceneToString(scene) << " pid=" << pid << " no debts";
1171+ return UBSE_OK;
1172+ }
1173+ 
1174+ auto& infoMgr = ProcessMemPidInfoManager::GetInstance();
1175+ for (const auto* debt : matched) {
1176+ infoMgr.UpdateManagedPidSlotReturnStatus(pid, debt->name, def::ReturnStatus::RETURNING);
1177+ }
1178+ 
1179+ std::unordered_map<uint32_t, size_t> numaDistribution;
1180+ bool procExited = collect::ProcessMemPidCollect::GetInstance().CollectProcessNumaMemDistribution(
1181+ pid, numaDistribution) != UBSE_OK;
1182+ 
1183+ uint64_t freedBytes = 0;
1184+ uint32_t freedCount = 0;
1185+ uint32_t failCount = 0;
1186+ for (const auto* debt : matched) {
1187+ if (ReturnOnePidDebt(*debt, procExited, freedCount, freedBytes, failCount)) {
1188+ infoMgr.UpdateManagedPidSlotReturned(pid, debt->name, debt->size);
1189+ }
1190+ }
1191+ 
1192+ if (freedCount > 0 || failCount > 0) {
1193+ UBSE_LOG_INFO << "[process_mem] return " << ReturnSceneToString(scene) << " pid=" << pid
1194+ << " debts=" << (freedCount + failCount) << " freed=" << freedCount << " failed=" << failCount
1195+ << " amount_gb=" << BytesToGbDouble(freedBytes);
1196+ }
1197+ for (const auto* debt : matched) {
1198+ infoMgr.UpdateManagedPidSlotReturnStatus(pid, debt->name, def::ReturnStatus::NONE);
1199+ }
1200+ return (failCount > 0) ? UBSE_ERROR : UBSE_OK;
1201+}
1202+ 
1203+bool ProcessMemPidDecision::ReturnOnePidDebt(const ubse::mem::controller::UbseNumaMemoryDebtInfo& debt, bool procExited,
1204+ uint32_t& freedCount, uint64_t& freedBytes, uint32_t& failCount)
1205+{
1206+ if (procExited) {
1207+ auto delRet = pid::bridge::ProcessMemPidBridge::MemoryReturn(debt.name);
1208+ if (delRet != UBSE_OK && delRet != UBSE_ERR_NOT_EXIST) {
1209+ ++failCount;
1210+ UBSE_LOG_WARN << "[process_mem] return debt_id=" << debt.name << " ubse delete failed ret=" << delRet
1211+ << ", skip rmrs return";
1212+ return false;
1213+ }
1214+ }
1215+ if (!pid::bridge::ProcessMemPidBridge::rmrsFreeWithMigrate) {
1216+ ++failCount;
1217+ UBSE_LOG_WARN << "[process_mem] return debt_id=" << debt.name << " rmrsFreeWithMigrate not loaded, skip free";
1218+ return false;
1219+ }
1220+ auto freeRet = pid::bridge::ProcessMemPidBridge::rmrsFreeWithMigrate(debt.name);
1221+ if (freeRet != UBSE_OK) {
1222+ ++failCount;
1223+ UBSE_LOG_WARN << "[process_mem] return debt_id=" << debt.name << " free failed ret=" << freeRet;
1224+ return false;
1225+ }
1226+ ++freedCount;
1227+ freedBytes += debt.size;
1228+ return true;
1229+}
1230+ 
1231+void ProcessMemPidDecision::RunPidReturn(pid_t pid, ReturnScene scene)
1232+{
1233+ uint32_t ret = DoPidReturnOnce(pid, scene);
1234+ if (ret == UBSE_OK || ret == UBSE_ERR_NOT_EXIST) {
1235+ return;
1236+ }
1237+ UBSE_LOG_DEBUG << "[process_mem] return " << ReturnSceneToString(scene) << " pid=" << pid << " failed (ret=" << ret
1238+ << "), retry later";
1239+ uint32_t retryCount = 0;
1240+ RetryReturnEnqueue(retryCount, [this, pid, scene]() { return EnqueuePidReturn(pid, scene); });
1241+}
1242+ 
1243+uint32_t ProcessMemPidDecision::HandlePidExited(pid_t pid)
1244+{
1245+ if (EnqueuePidReturn(pid, ReturnScene::EXITED)) {
1246+ return UBSE_OK;
1247+ }
1248+ return UBSE_ERROR;
1249+}
1250+ 
1251+void ProcessMemPidDecision::RecoverBorrowFromObmm()
1252+{
1253+ std::vector<ubse::mem::controller::UbseNumaMemoryImportDebtInfo> debts;
1254+ auto ret = ubse::mem::controller::UbseGetNumaMemImportDebtInfoWithLocalNode(debts);
1255+ if (ret != UBSE_OK) {
1256+ UBSE_LOG_WARN << "[process_mem] recover borrow: get import debts failed, ret=" << ret;
1257+ return;
1258+ }
1259+ if (debts.empty()) {
1260+ return;
1261+ }
1262+ 
1263+ auto& infoMgr = ProcessMemPidInfoManager::GetInstance();
1264+ auto snapshot = infoMgr.GetManagedPidCacheSnapshot();
1265+ 
1266+ size_t recovered = 0;
1267+ size_t orphaned = 0;
1268+ size_t skipped = 0;
1269+ for (const auto& debt : debts) {
1270+ def::ProcessMemUsrInfo usrInfo{};
1271+ if (memcpy_s(&usrInfo, sizeof(usrInfo), debt.usrInfo, sizeof(usrInfo)) != EOK ||
1272+ usrInfo.pluginId != def::UsrInfoPluginType::PROCESS_MEM || usrInfo.pid <= 0) {
1273+ ++skipped;
1274+ continue;
1275+ }
1276+ pid_t pid = static_cast<pid_t>(usrInfo.pid);
1277+ if (IsBorrowBindable(pid, usrInfo, snapshot)) {
1278+ RecoverBindDebt(pid, debt);
1279+ ++recovered;
1280+ } else {
1281+ ++orphaned;
1282+ EnqueueOrphanReturn(debt.name);
1283+ }
1284+ }
1285+ UBSE_LOG_INFO << "[process_mem] recover borrow: debts=" << debts.size() << " recovered=" << recovered
1286+ << " orphaned=" << orphaned << " skipped=" << skipped;
1287+ 
1288+ RecoverSmapProcessConfig();
1289+}
1290+ 
1291+void ProcessMemPidDecision::RecoverBindDebt(pid_t pid, const ubse::mem::controller::UbseNumaMemoryImportDebtInfo& debt)
1292+{
1293+ def::ProcessMemUsrInfo usrInfo{};
1294+ if (memcpy_s(&usrInfo, sizeof(usrInfo), debt.usrInfo, sizeof(usrInfo)) != EOK) {
1295+ return;
1296+ }
1297+ def::BorrowSlot slot{};
1298+ slot.debtId = debt.name;
1299+ slot.migratedBytes = debt.size;
1300+ slot.capacity = debt.size;
1301+ slot.remoteNumaId = static_cast<int>(debt.remoteNumaId);
1302+ slot.srcNumaId = static_cast<int>(usrInfo.srcNuma);
1303+ slot.borrowTime = std::chrono::steady_clock::now();
1304+ slot.status = def::BorrowSlotStatus::COMPLETED;
1305+ ProcessMemPidInfoManager::GetInstance().UpdateManagedPidBorrowStateAtomic(
1306+ pid, [&slot](def::BorrowState& newBorrow, def::ProcessStatus& newStatus) {
1307+ newBorrow.slots.push_back(slot);
1308+ newStatus = def::ProcessStatus::BORROWED;
1309+ });
1310+ UBSE_LOG_INFO << "[process_mem] recover borrow: pid=" << pid << " debt=" << debt.name
1311+ << " amount_gb=" << BytesToGbDouble(debt.size) << " recovered (status=COMPLETED)";
1312+}
1313+ 
1314+bool ProcessMemPidDecision::EnqueueOrphanReturn(const std::string& debtName)
1315+{
1316+ if (stopping_) {
1317+ return false;
1318+ }
1319+ std::string traceId = TraceContext::GetTraceId();
1320+ return returnExecutor_->Execute([this, debtName, traceId]() {
1321+ TraceContext::SetTraceId(traceId);
1322+ RunOrphanReturn(debtName);
1323+ TraceContext::Clear();
1324+ });
1325+}
1326+ 
1327+void ProcessMemPidDecision::RunOrphanReturn(const std::string& debtName)
1328+{
1329+ auto ret = pid::bridge::ProcessMemPidBridge::MemoryReturn(debtName);
1330+ if (ret == UBSE_OK || ret == UBSE_ERR_NOT_EXIST) {
1331+ UBSE_LOG_INFO << "[process_mem] recover borrow: debt=" << debtName << " orphaned, force returned (ret=" << ret
1332+ << ")";
1333+ return;
1334+ }
1335+ 
1336+ UBSE_LOG_DEBUG << "[process_mem] recover borrow: debt=" << debtName << " orphaned, force return failed (ret=" << ret
1337+ << "), retry later";
1338+ uint32_t retryCount = 0;
1339+ RetryReturnEnqueue(retryCount, [this, debtName]() { return EnqueueOrphanReturn(debtName); });
1340+}
1341+ 
1342+void ProcessMemPidDecision::RetryReturnEnqueue(uint32_t& retryCount, const std::function<bool()>& enqueue)
1343+{
1344+ ++retryCount;
1345+ if (retryCount > kMaxReturnRetry) {
1346+ UBSE_LOG_WARN << "[process_mem] return retry exhausted (retries=" << retryCount << "), give up";
1347+ return;
1348+ }
1349+ std::this_thread::sleep_for(std::chrono::milliseconds(returnRetryIntervalMs_));
1350+ while (!enqueue() && !stopping_) {
1351+ std::this_thread::sleep_for(std::chrono::milliseconds(returnRetryIntervalMs_));
1352+ }
1353+}
1354+ 
1355+uint32_t ProcessMemPidDecision::RecoverSmapProcessConfig()
1356+{
1357+ std::unordered_map<pid_t, std::unordered_map<int, uint64_t>> smapMemKb;
1358+ std::optional<std::set<int>> failedNumas = std::set<int>{};
1359+ size_t queryFail = 0;
1360+ QuerySmapProcessConfigs(smapMemKb, failedNumas, queryFail);
1361+ 
1362+ auto& infoMgr = ProcessMemPidInfoManager::GetInstance();
1363+ auto snapshot = infoMgr.GetManagedPidCacheSnapshot();
1364+ 
1365+ if (!failedNumas.has_value()) {
1366+ UBSE_LOG_WARN << "[process_mem] recover config: smap query unavailable (plugin/import-debt), "
1367+ << "skip migratedBytes recover to avoid over-borrow";
1368+ return 0;
1369+ }
1370+ 
1371+ size_t filled = 0;
1372+ uint64_t filledBytes = 0;
1373+ for (const auto& [pid, state] : snapshot) {
1374+ if (state.borrow.slots.empty()) {
1375+ continue;
1376+ }
1377+ RecoverPidMigratedBytes(pid, smapMemKb, *failedNumas, filled, filledBytes);
1378+ }
1379+ 
1380+ size_t dirty = 0;
1381+ size_t smapEntries = 0;
1382+ ReportDirtySmapStates(smapMemKb, snapshot, dirty, smapEntries);
1383+ UBSE_LOG_INFO << "[process_mem] recover config: smap_numa_entries=" << smapEntries << " filled=" << filled
1384+ << " filled_gb=" << BytesToGbDouble(filledBytes) << " dirty=" << dirty << " query_fail=" << queryFail;
1385+ return static_cast<uint32_t>(dirty);
1386+}
1387+ 
1388+void ProcessMemPidDecision::RecoverPidMigratedBytes(
1389+ pid_t pid, const std::unordered_map<pid_t, std::unordered_map<int, uint64_t>>& smapMemKb,
1390+ const std::set<int>& failedNumas, size_t& filled, uint64_t& filledBytes)
1391+{
1392+ constexpr uint64_t kBytesPerKb = 1024;
1393+ static const std::unordered_map<int, uint64_t> kEmptyNumaMap;
1394+ auto numaIt = smapMemKb.find(pid);
1395+ const auto& numaMap = (numaIt != smapMemKb.end()) ? numaIt->second : kEmptyNumaMap;
1396+ 
1397+ size_t slotCount = 0;
1398+ uint64_t total = 0;
1399+ ProcessMemPidInfoManager::GetInstance().UpdateManagedPidBorrowStateAtomic(
1400+ pid, [&](def::BorrowState& newBorrow, def::ProcessStatus& newStatus) {
1401+ slotCount = newBorrow.slots.size();
1402+ std::unordered_map<int, uint64_t> remainingByNuma;
1403+ for (auto& slot : newBorrow.slots) {
1404+ if (failedNumas.count(slot.remoteNumaId) > 0) {
1405+ continue;
1406+ }
1407+ auto it = numaMap.find(slot.remoteNumaId);
1408+ if (it == numaMap.end() || it->second == 0) {
1409+ slot.migratedBytes = 0;
1410+ } else {
1411+ auto remIt = remainingByNuma.find(slot.remoteNumaId);
1412+ if (remIt == remainingByNuma.end()) {
1413+ remIt = remainingByNuma.emplace(slot.remoteNumaId, it->second * kBytesPerKb).first;
1414+ }
1415+ if (remIt->second >= slot.capacity) {
1416+ slot.migratedBytes = slot.capacity;
1417+ remIt->second -= slot.capacity;
1418+ } else {
1419+ slot.migratedBytes = remIt->second;
1420+ remIt->second = 0;
1421+ }
1422+ }
1423+ ++filled;
1424+ filledBytes += slot.migratedBytes;
1425+ }
1426+ for (const auto& slot : newBorrow.slots) {
1427+ total += slot.migratedBytes;
1428+ }
1429+ newBorrow.currentRemote = total;
1430+ newStatus = def::ProcessStatus::BORROWED;
1431+ });
1432+ UBSE_LOG_INFO << "[process_mem] recover config: pid=" << pid << " slots=" << slotCount
1433+ << " currentRemote_gb=" << BytesToGbDouble(total);
1434+}
1435+ 
1436+void ProcessMemPidDecision::ReportDirtySmapStates(
1437+ const std::unordered_map<pid_t, std::unordered_map<int, uint64_t>>& smapMemKb,
1438+ const std::map<pid_t, def::ManagedPidEntry>& snapshot, size_t& dirty, size_t& smapEntries)
1439+{
1440+ for (const auto& [pid, numaMap] : smapMemKb) {
1441+ smapEntries += numaMap.size();
1442+ uint64_t pidTotalKb = 0;
1443+ for (const auto& [numa, memKb] : numaMap) {
1444+ pidTotalKb += memKb;
1445+ }
1446+ auto it = snapshot.find(pid);
1447+ if (it == snapshot.end()) {
1448+ UBSE_LOG_WARN << "[process_mem] recover config: pid=" << pid << " smap mem_kb=" << pidTotalKb
1449+ << " not in managed cache (dirty state)";
1450+ ++dirty;
1451+ continue;
1452+ }
1453+ for (const auto& [numa, memKb] : numaMap) {
1454+ bool covered = false;
1455+ for (const auto& slot : it->second.borrow.slots) {
1456+ if (slot.remoteNumaId == numa) {
1457+ covered = true;
1458+ break;
1459+ }
1460+ }
1461+ if (!covered) {
1462+ UBSE_LOG_WARN << "[process_mem] recover config: pid=" << pid << " numa=" << numa
1463+ << " smap mem_kb=" << memKb << " but no slot (UBSE ledger lost?)";
1464+ ++dirty;
1465+ }
1466+ }
1467+ }
1468+}
1469+ 
1470+uint32_t ProcessMemPidDecision::ReturnOneDebt(const def::ReturnRequestItem& item, uint64_t& nodeFree)
1471+{
1472+ auto nodeId = GetCurrentNodeId();
1473+ 
1474+ std::vector<ubse::mem::controller::UbseNumaMemoryDebtInfo> debts;
1475+ auto ret = ubse::mem::controller::UbseGetNumaMemDebtInfoWithNode(nodeId, debts);
1476+ if (ret != UBSE_OK) {
1477+ UBSE_LOG_WARN << "[process_mem] return passive debt_id=" << item.name
1478+ << " skip: get own debts failed, ret=" << ret;
1479+ return ret;
1480+ }
1481+ 
1482+ const ubse::mem::controller::UbseNumaMemoryDebtInfo* matched = nullptr;
1483+ for (const auto& debt : debts) {
1484+ if (debt.name == item.name) {
1485+ matched = &debt;
1486+ break;
1487+ }
1488+ }
1489+ if (matched == nullptr) {
1490+ UBSE_LOG_WARN << "[process_mem] return passive debt_id=" << item.name << " skip: debt not found locally";
1491+ return UBSE_ERR_NOT_EXIST;
1492+ }
1493+ 
1494+ pid_t pid = 0;
1495+ int srcNuma = -1;
1496+ def::ProcessMemUsrInfo usrInfo{};
1497+ if (memcpy_s(&usrInfo, sizeof(usrInfo), matched->usrInfo, sizeof(usrInfo)) == EOK && usrInfo.pid > 0) {
1498+ pid = static_cast<pid_t>(usrInfo.pid);
1499+ srcNuma = usrInfo.srcNuma;
1500+ }
1501+ 
1502+ if (item.size == 0) {
1503+ return ReturnDebtToLocal(pid, item, nodeFree, ReturnScene::PASSIVE);
1504+ }
1505+ 
1506+ if (nodeFree >= freeMemoryThresholdBytes_ + RETURN_HEADROOM_BYTES) {
1507+ return ReturnDebtToLocal(pid, item, nodeFree, ReturnScene::PASSIVE);
1508+ }
1509+ 
1510+ return ReturnDebtRemoteToRemote(item, matched->lentNodeId, static_cast<int>(matched->remoteNumaId), pid, srcNuma);
1511+}
1512+ 
1513+uint32_t ProcessMemPidDecision::ReturnDebtToLocal(pid_t pid, const def::ReturnRequestItem& item, uint64_t& nodeFree,
1514+ ReturnScene scene)
1515+{
1516+ if (!pid::bridge::ProcessMemPidBridge::rmrsFreeWithMigrate) {
1517+ UBSE_LOG_WARN << "[process_mem] return " << ReturnSceneToString(scene) << " debt_id=" << item.name
1518+ << " skip: rmrsFreeWithMigrate unavailable";
1519+ return UBSE_ERROR;
1520+ }
1521+ auto freeRet = pid::bridge::ProcessMemPidBridge::rmrsFreeWithMigrate(item.name);
1522+ if (freeRet != UBSE_OK) {
1523+ UBSE_LOG_WARN << "[process_mem] return " << ReturnSceneToString(scene) << " debt_id=" << item.name
1524+ << " direct_return failed ret=" << freeRet;
1525+ return freeRet;
1526+ }
1527+ 
1528+ UBSE_LOG_INFO << "[process_mem] return " << ReturnSceneToString(scene) << " debt_id=" << item.name
1529+ << " amount_gb=" << BytesToGbDouble(item.size) << " direct_return (本地充裕)";
1530+ nodeFree += item.size;
1531+ if (pid > 0) {
1532+ ProcessMemPidInfoManager::GetInstance().UpdateManagedPidSlotReturned(pid, item.name, item.size);
1533+ }
1534+ return UBSE_OK;
1535+}
1536+ 
1537+uint32_t ProcessMemPidDecision::ReturnDebtRemoteToRemote(const def::ReturnRequestItem& item,
1538+ const std::string& oldLenderNodeId, int oldRemoteNuma,
1539+ pid_t pid, int srcNuma)
1540+{
1541+ if (pid <= 0) {
1542+ UBSE_LOG_INFO << "[process_mem] return passive debt_id=" << item.name << " skip: no pid in usrInfo";
1543+ return UBSE_ERR_NOT_EXIST;
1544+ }
1545+ if (!pid::bridge::ProcessMemPidBridge::rmrsRemoteToRemote) {
1546+ UBSE_LOG_INFO << "[process_mem] return passive debt_id=" << item.name << " skip: RemoteNumaMigrate unavailable";
1547+ return UBSE_ERROR;
1548+ }
1549+ if (oldRemoteNuma < 0) {
1550+ UBSE_LOG_INFO << "[process_mem] return passive debt_id=" << item.name
1551+ << " skip: invalid remote numa=" << oldRemoteNuma;
1552+ return UBSE_ERR_NOT_EXIST;
1553+ }
1554+ 
1555+ bool migrationDone = false;
1556+ {
1557+ std::lock_guard<std::mutex> lock(pendingOldDebtDeletesMutex_);
1558+ migrationDone = pendingOldDebtDeletes_.count(item.name) > 0;
1559+ }
1560+ 
1561+ std::string newDebtId;
1562+ int newRemoteNuma = -1;
1563+ if (!migrationDone) {
1564+ ReplacementDebtCtx ctx;
1565+ ctx.srcNuma = srcNuma;
1566+ ctx.oldRemoteNuma = oldRemoteNuma;
1567+ ctx.oldLenderNodeId = oldLenderNodeId;
1568+ auto migrateRet = MigrateDebtToReplacement(item, pid, ctx, newDebtId, newRemoteNuma);
1569+ if (migrateRet != UBSE_OK) {
1570+ return migrateRet;
1571+ }
1572+ }
1573+ 
1574+ auto delRet = DeleteOldReturnDebt(item.name);
1575+ if (delRet != UBSE_OK) {
1576+ UBSE_LOG_WARN << "[process_mem] return passive debt_id=" << item.name
1577+ << " old debt delete failed ret=" << delRet << ", keep slot for retry";
1578+ return delRet;
1579+ }
1580+ 
1581+ if (migrationDone) {
1582+ UBSE_LOG_INFO << "[process_mem] return passive debt_id=" << item.name
1583+ << " remote_to_remote: old debt delete retry succeeded";
1584+ } else {
1585+ UBSE_LOG_INFO << "[process_mem] return passive debt_id=" << item.name
1586+ << " amount_gb=" << BytesToGbDouble(item.size)
1587+ << " remote_to_remote: old_lender=" << oldLenderNodeId << " new_remote_numa=" << newRemoteNuma;
1588+ }
1589+ return UBSE_OK;
1590+}
1591+ 
1592+uint32_t ProcessMemPidDecision::MigrateDebtToReplacement(const def::ReturnRequestItem& item, pid_t pid,
1593+ const ReplacementDebtCtx& ctx, std::string& newDebtId,
1594+ int& newRemoteNuma)
1595+{
1596+ auto createRet = CreateReplacementDebt(item, pid, ctx, newDebtId, newRemoteNuma);
1597+ if (createRet != UBSE_OK) {
1598+ return createRet;
1599+ }
1600+ 
1601+ int migrateRet = MigrateDebtRemoteToRemote(pid, ctx.oldRemoteNuma, newRemoteNuma, item.size);
1602+ if (migrateRet != 0) {
1603+ pid::bridge::ProcessMemPidBridge::MemoryReturn(newDebtId);
1604+ UBSE_LOG_WARN << "[process_mem] return passive debt_id=" << item.name
1605+ << " remote_to_remote failed ret=" << migrateRet << " keep old lender";
1606+ return UBSE_ERROR;
1607+ }
1608+ 
1609+ ProcessMemPidInfoManager::GetInstance().UpdateManagedPidBorrowStateAtomic(
1610+ pid, [&](def::BorrowState& borrow, def::ProcessStatus&) {
1611+ auto slotIt = std::find_if(borrow.slots.begin(), borrow.slots.end(),
1612+ [&item](const def::BorrowSlot& s) { return s.debtId == item.name; });
1613+ if (slotIt != borrow.slots.end()) {
1614+ slotIt->debtId = newDebtId;
1615+ slotIt->remoteNumaId = newRemoteNuma;
1616+ slotIt->capacity = item.size;
1617+ slotIt->migratedBytes = item.size;
1618+ slotIt->returnStatus = def::ReturnStatus::NONE;
1619+ }
1620+ });
1621+ return UBSE_OK;
1622+}
1623+ 
1624+uint32_t ProcessMemPidDecision::DeleteOldReturnDebt(const std::string& debtId)
1625+{
1626+ ubse::mem::controller::UbseMemBorrower delBorrower{};
1627+ delBorrower.nodeId = GetCurrentNodeId();
1628+ auto delRet = ubse::mem::controller::UbseMemNumaDelete(debtId, delBorrower);
1629+ if (delRet != UBSE_OK && delRet != UBSE_ERR_NOT_EXIST) {
1630+ std::lock_guard<std::mutex> lock(pendingOldDebtDeletesMutex_);
1631+ pendingOldDebtDeletes_.insert(debtId);
1632+ return delRet;
1633+ }
1634+ 
1635+ std::lock_guard<std::mutex> lock(pendingOldDebtDeletesMutex_);
1636+ pendingOldDebtDeletes_.erase(debtId);
1637+ return UBSE_OK;
1638+}
1639+ 
1640+std::vector<std::string> ProcessMemPidDecision::BuildReplacementCandidates(const std::string& oldLenderNodeId)
1641+{
1642+ std::vector<std::string> candidates;
1643+ auto allNodes = ubse::nodeController::UbseNodeController::GetInstance().GetAllNodes();
1644+ for (const auto& nodeEntry : allNodes) {
1645+ if (nodeEntry.first == oldLenderNodeId) {
1646+ continue;
1647+ }
1648+ candidates.push_back(nodeEntry.first);
1649+ }
1650+ if (candidates.empty()) {
1651+ UBSE_LOG_WARN << "[process_mem] return passive: no replacement lender candidate "
1652+ << "(old lender node=" << oldLenderNodeId << ")";
1653+ }
1654+ return candidates;
1655+}
1656+ 
1657+uint32_t ProcessMemPidDecision::CreateReplacementDebt(const def::ReturnRequestItem& item, pid_t pid,
1658+ const ReplacementDebtCtx& ctx, std::string& newDebtId,
1659+ int& newRemoteNuma)
1660+{
1661+ newDebtId = GenerateBorrowName(pid);
1662+ ubse::mem::controller::UbseMemBorrower borrower{};
1663+ if (!BuildBorrower(pid, ctx.srcNuma, item.size, 0, borrower)) {
1664+ UBSE_LOG_WARN << "[process_mem] return passive debt_id=" << item.name << " skip: build borrower failed";
1665+ return UBSE_ERROR;
1666+ }
1667+ 
1668+ ubse::mem::controller::UbseMemNumaCandidateOpt opt;
1669+ opt.highWatermark = kBorrowHighWatermark;
1670+ opt.size = item.size;
1671+ // 替换债不能落到旧借出节点:被动归还要的就是把内存从旧借出方腾走,排除其候选
1672+ opt.slotIds = BuildReplacementCandidates(ctx.oldLenderNodeId);
1673+ if (opt.slotIds.empty()) {
1674+ UBSE_LOG_WARN << "[process_mem] return passive debt_id=" << item.name << " skip: no replacement lender";
1675+ return UBSE_ERROR;
1676+ }
1677+ def::ProcessMemUsrInfo usrInfo{};
1678+ usrInfo.pid = static_cast<int32_t>(pid);
1679+ usrInfo.srcNuma = static_cast<int32_t>(ctx.srcNuma);
1680+ if (memcpy_s(opt.usrInfo, ubse::mem::controller::UBSE_MAX_USR_INFO_LEN, &usrInfo, sizeof(usrInfo)) != EOK) {
1681+ UBSE_LOG_ERROR << "[process_mem] return passive debt_id=" << item.name << " skip: memcpy_usrinfo";
1682+ return UBSE_ERROR;
1683+ }
1684+ 
1685+ ubse::mem::controller::UbseMemNumaDesc desc{};
1686+ auto createRet = ubse::mem::controller::UbseMemNumaCreateWithCandidate(newDebtId, borrower, opt, desc);
1687+ if (createRet != UBSE_OK) {
1688+ UBSE_LOG_INFO << "[process_mem] return passive debt_id=" << item.name
1689+ << " skip: no available lender (ret=" << createRet << ")";
1690+ return createRet;
1691+ }
1692+ newRemoteNuma = static_cast<int>(desc.numaId);
1693+ return UBSE_OK;
1694+}
1695+ 
1696+int ProcessMemPidDecision::MigrateDebtRemoteToRemote(pid_t pid, int oldRemoteNuma, int newRemoteNuma,
1697+ uint64_t sizeBytes)
1698+{
1699+ mempooling::smap::MigrateEscapeMsg msg{};
1700+ msg.count = 1;
1701+ msg.payload[0].pid = pid;
1702+ msg.payload[0].srcNid = oldRemoteNuma;
1703+ msg.payload[0].destNid = newRemoteNuma;
1704+ uint64_t memSizeKb = sizeBytes / 1024;
1705+ constexpr uint64_t pageSizeKb = 4;
1706+ msg.payload[0].memSize = memSizeKb / pageSizeKb * pageSizeKb;
1707+ msg.payload[0].migrateMode = mempooling::smap::MIG_MEMSIZE_MODE;
1708+ return pid::bridge::ProcessMemPidBridge::rmrsRemoteToRemote(msg);
1709+}
1710+ 
1711+void ProcessMemPidDecision::OomLoop()
1712+{
1713+ std::unique_lock<std::mutex> lock(oomMutex_);
1714+ while (oomRunning_) {
1715+ uint32_t intervalMs = OomPollOnce();
1716+ if (!oomRunning_) {
1717+ break;
1718+ }
1719+ oomCv_.wait_for(lock, std::chrono::milliseconds(intervalMs));
1720+ }
1721+}
1722+ 
1723+void ProcessMemPidDecision::OomRearrangeMigrated()
1724+{
1725+ auto& infoMgr = ProcessMemPidInfoManager::GetInstance();
1726+ auto snapshot = infoMgr.GetManagedPidCacheSnapshot();
1727+ for (const auto& [pid, entry] : snapshot) {
1728+ bool hasCompleted = false;
1729+ for (const auto& s : entry.borrow.slots) {
1730+ if (s.status == def::BorrowSlotStatus::COMPLETED && s.returnStatus != def::ReturnStatus::RETURNING) {
1731+ hasCompleted = true;
1732+ break;
1733+ }
1734+ }
1735+ if (!hasCompleted) {
1736+ continue;
1737+ }
1738+ size_t changed = 0;
1739+ size_t emptySlots = 0;
1740+ size_t numaGroups = 0;
1741+ uint64_t spareBytes = 0;
1742+ infoMgr.UpdateManagedPidBorrowStateAtomic(pid, [&](def::BorrowState& borrow, def::ProcessStatus&) {
1743+ std::vector<size_t> before;
1744+ for (const auto& s : borrow.slots) {
1745+ before.push_back(s.migratedBytes);
1746+ }
1747+ RearrangePidMigrated(borrow);
1748+ std::map<int, size_t> groups;
1749+ for (size_t i = 0; i < borrow.slots.size(); ++i) {
1750+ const auto& s = borrow.slots[i];
1751+ if (s.status != def::BorrowSlotStatus::COMPLETED || s.returnStatus == def::ReturnStatus::RETURNING) {
1752+ continue;
1753+ }
1754+ groups[s.remoteNumaId]++;
1755+ if (s.migratedBytes == 0) {
1756+ ++emptySlots;
1757+ }
1758+ spareBytes += (s.capacity > s.migratedBytes) ? (s.capacity - s.migratedBytes) : 0;
1759+ if (i < before.size() && before[i] != s.migratedBytes) {
1760+ ++changed;
1761+ }
1762+ }
1763+ numaGroups = groups.size();
1764+ });
1765+ if (changed > 0 || emptySlots > 0) {
1766+ UBSE_LOG_DEBUG << "[process_mem] oom rearrange: pid=" << pid << " numa_groups=" << numaGroups
1767+ << " changed_slots=" << changed << " empty_slots=" << emptySlots
1768+ << " spare_gb=" << BytesToGbDouble(spareBytes);
1769+ }
1770+ }
1771+}
1772+ 
1773+uint32_t ProcessMemPidDecision::OomPollOnce()
1774+{
1775+ OomRearrangeMigrated();
1776+ uint64_t roundNum = oomRoundNumber_.fetch_add(1) + 1;
1777+ auto memAvailableOpt = ReadMemAvailableKb();
1778+ auto nodeFreeOpt = GetNodeFreeBytes();
1779+ if (!memAvailableOpt.has_value() || !nodeFreeOpt.has_value()) {
1780+ UBSE_LOG_WARN << "[process_mem] oom detector: read memAvailable failed, skip round";
1781+ return NORMAL_POLL_INTERVAL_MS;
1782+ }
1783+ uint64_t memAvailableBytes = *memAvailableOpt * 1024;
1784+ uint64_t detectThresholdBytes = freeMemoryThresholdBytes_ * 3;
1785+ uint64_t nodeFree = *nodeFreeOpt;
1786+ 
1787+ if (memAvailableBytes < detectThresholdBytes) {
1788+ if (!oomFastPoll_) {
1789+ oomFastWindowCount_ = 0;
1790+ oomFastWindowMin_ = 0;
1791+ }
1792+ oomFastPoll_ = true;
1793+ UBSE_LOG_INFO << "[process_mem] oom detector: fast poll enabled (MemAvailable="
1794+ << BytesToGbDouble(memAvailableBytes) << "GB < detect=" << BytesToGbDouble(detectThresholdBytes)
1795+ << "GB)";
1796+ 
1797+ UpdateOomFastWindow(nodeFree);
1798+ 
1799+ if (memAvailableBytes < emergencyThresholdBytes_) {
1800+ OomEmergencyBorrow(roundNum, nodeFree);
1801+ auto now = std::chrono::steady_clock::now();
1802+ auto sinceLast =
1803+ std::chrono::duration_cast<std::chrono::milliseconds>(now - lastEmergencyBroadcast_).count();
1804+ if (sinceLast >= kEmergencyBroadcastIntervalMs) {
1805+ BroadcastPassiveReturn(roundNum, nodeFree, true);
1806+ lastEmergencyBroadcast_ = now;
1807+ }
1808+ }
1809+ } else {
1810+ oomFastPoll_ = false;
1811+ oomFastWindowCount_ = 0;
1812+ oomFastWindowMin_ = 0;
1813+ UBSE_LOG_INFO << "[process_mem] oom detector: fast poll disabled (MemAvailable="
1814+ << BytesToGbDouble(memAvailableBytes) << "GB >= detect=" << BytesToGbDouble(detectThresholdBytes)
1815+ << "GB)";
1816+ }
1817+ 
1818+ UBSE_LOG_DEBUG << "[process_mem] oom detector heartbeat: MemAvailable=" << BytesToGbDouble(memAvailableBytes)
1819+ << "GB detect=" << BytesToGbDouble(detectThresholdBytes)
1820+ << "GB emergency=" << BytesToGbDouble(emergencyThresholdBytes_)
1821+ << "GB poll=" << (oomFastPoll_ ? "fast" : "normal");
1822+ 
1823+ return oomFastPoll_ ? fastPollIntervalMs_ : NORMAL_POLL_INTERVAL_MS;
1824+}
1825+ 
1826+void ProcessMemPidDecision::UpdateOomFastWindow(uint64_t nodeFree)
1827+{
1828+ if (oomFastWindowCount_ == 0) {
1829+ oomFastWindowMin_ = nodeFree;
1830+ } else {
1831+ oomFastWindowMin_ = std::min(oomFastWindowMin_, nodeFree);
1832+ }
1833+ ++oomFastWindowCount_;
1834+ if (oomFastWindowCount_ >= observeCycles_) {
1835+ UBSE_LOG_DEBUG << "[process_mem] oom active window settle: samples=" << oomFastWindowCount_
1836+ << " min_gb=" << BytesToGbDouble(oomFastWindowMin_)
1837+ << " threshold_gb=" << BytesToGbDouble(freeMemoryThresholdBytes_);
1838+ if (oomFastWindowMin_ > freeMemoryThresholdBytes_) {
1839+ OomActiveReturn(oomFastWindowMin_);
1840+ }
1841+ oomFastWindowCount_ = 0;
1842+ oomFastWindowMin_ = 0;
1843+ }
1844+}
1845+ 
1846+void ProcessMemPidDecision::OomEmergencyBorrow(uint64_t roundNum, uint64_t nodeFree)
1847+{
1848+ if (nodeFree >= freeMemoryThresholdBytes_) {
1849+ return;
1850+ }
1851+ uint64_t shortage = freeMemoryThresholdBytes_ - nodeFree;
1852+ uint64_t pendingBorrow = GetPendingBorrowTotal();
1853+ shortage = (shortage > pendingBorrow) ? (shortage - pendingBorrow) : 0;
1854+ 
1855+ auto candidates = BuildCandidates(roundNum);
1856+ if (candidates.empty()) {
1857+ return;
1858+ }
1859+ ExecuteBorrowRound(candidates, shortage, roundNum, true);
1860+}
1861+ 
1862+void ProcessMemPidDecision::OomActiveReturn(uint64_t windowMinFree)
1863+{
1864+ if (windowMinFree <= freeMemoryThresholdBytes_) {
1865+ return;
1866+ }
1867+ uint64_t budget = windowMinFree - freeMemoryThresholdBytes_;
1868+ 
1869+ auto& infoMgr = ProcessMemPidInfoManager::GetInstance();
1870+ auto snapshot = infoMgr.GetManagedPidCacheSnapshot();
1871+ 
1872+ std::vector<std::pair<pid_t, def::ReturnRequestItem>> debts;
1873+ uint64_t totalRemote = 0;
1874+ CollectActiveReturnDebts(snapshot, debts, totalRemote);
1875+ 
1876+ std::sort(debts.begin(), debts.end(), [](const auto& a, const auto& b) { return a.second.size > b.second.size; });
1877+ 
1878+ UBSE_LOG_INFO << "[process_mem] return active triggered: window_min_gb=" << BytesToGbDouble(windowMinFree)
1879+ << " budget_gb=" << BytesToGbDouble(budget) << " total_remote_gb=" << BytesToGbDouble(totalRemote);
1880+ 
1881+ uint64_t returned = 0;
1882+ for (const auto& [debtPid, item] : debts) {
1883+ if (item.size > budget) {
1884+ UBSE_LOG_DEBUG << "[process_mem] return active debt_id=" << item.name
1885+ << " skip: size_gb=" << BytesToGbDouble(item.size)
1886+ << " > budget_gb=" << BytesToGbDouble(budget);
1887+ continue;
1888+ }
1889+ if (!EnqueueReturnDebt(debtPid, item, ReturnScene::ACTIVE)) {
1890+ UBSE_LOG_WARN << "[process_mem] return active debt_id=" << item.name
1891+ << " enqueue failed (stopping/queue full)";
1892+ continue;
1893+ }
1894+ budget -= item.size;
1895+ returned += item.size;
1896+ }
1897+ 
1898+ UBSE_LOG_INFO << "[process_mem] return active done: returned_gb=" << BytesToGbDouble(returned)
1899+ << " remaining_budget_gb=" << BytesToGbDouble(budget);
1900+}
1901+ 
1902+void ProcessMemPidDecision::CollectActiveReturnDebts(const std::map<pid_t, def::ManagedPidEntry>& snapshot,
1903+ std::vector<std::pair<pid_t, def::ReturnRequestItem>>& debts,
1904+ uint64_t& totalRemote)
1905+{
1906+ for (const auto& [pid, entry] : snapshot) {
1907+ for (const auto& slot : entry.borrow.slots) {
1908+ if (slot.status == def::BorrowSlotStatus::COMPLETED && slot.returnStatus != def::ReturnStatus::RETURNING) {
1909+ debts.emplace_back(pid, def::ReturnRequestItem{slot.debtId, slot.migratedBytes});
1910+ totalRemote += slot.migratedBytes;
1911+ }
1912+ }
1913+ }
1914+}
1915+ 
1916+} // namespace process_mem::decision
@@ -0,0 +1,226 @@
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+#ifndef PROCESS_MEM_PID_DECISION_H
14+#define PROCESS_MEM_PID_DECISION_H
15+#include <array>
16+#include <atomic>
17+#include <chrono>
18+#include <condition_variable>
19+#include <cstdint>
20+#include <functional>
21+#include <map>
22+#include <mutex>
23+#include <optional>
24+#include <set>
25+#include <string>
26+#include <thread>
27+#include <vector>
28+ 
29+#include "ubse_thread_pool.h"
30+#include "process_mem_pid_manager_def.h"
31+ 
32+namespace process_mem::decision {
33+ 
34+enum class ReturnScene : uint8_t
35+{
36+ PASSIVE,
37+ ACTIVE,
38+ TIMEOUT,
39+ EXITED
40+};
41+ 
42+struct CreatedDebtInfo {
43+ int remoteNumaId{-1};
44+ int32_t exportSlotId{-1};
45+ uint64_t capacity{0};
46+};
47+ 
48+struct ReplacementDebtCtx {
49+ int srcNuma{-1};
50+ int oldRemoteNuma{-1};
51+ std::string oldLenderNodeId{};
52+};
53+ 
54+class ProcessMemPidDecision {
55+public:
56+ static ProcessMemPidDecision& GetInstance()
57+ {
58+ static ProcessMemPidDecision instance;
59+ return instance;
60+ }
61+ 
62+ uint32_t Init();
63+ void UnInit();
64+ 
65+ uint32_t HandleReturnRequest(const std::vector<def::ReturnRequestItem>& items);
66+ 
67+ bool EnqueuePidReturn(pid_t pid, ReturnScene scene);
68+ 
69+ uint32_t HandlePidExited(pid_t pid);
70+ 
71+ uint32_t OomPollOnce();
72+ 
73+ inline static std::function<std::optional<uint64_t>()> memAvailableReader;
74+ 
75+private:
76+ uint32_t OnDecisionTimer();
77+ 
78+ bool CheckNodeFreeMemory(uint64_t& outShortage);
79+ 
80+ std::vector<def::BorrowCandidate> BuildCandidates(uint64_t roundNum);
81+ 
82+ int CollectSrcNuma(pid_t pid, uint64_t roundNum);
83+ 
84+ void ExecuteBorrowRound(const std::vector<def::BorrowCandidate>& candidates, uint64_t& shortage, uint64_t roundNum,
85+ bool emergency = false);
86+ 
87+ void CheckTimeouts(uint64_t roundNum);
88+ 
89+ uint64_t GetPendingBorrowTotal() const;
90+ 
91+ std::string RecordPendingBorrow(pid_t pid, uint64_t amount, int srcNumaId, uint64_t roundNum);
92+ 
93+ void AsyncBorrowAndMigrate(const std::string& debtId, pid_t pid, uint64_t amount, int srcNumaId, uint64_t roundNum);
94+ 
95+ void BuildMigrateTargets(const def::BorrowState& borrow, const std::map<int, uint64_t>& increments,
96+ std::vector<std::pair<int, uint64_t>>& numaTargets);
97+ 
98+ def::AtomicMigrateResult CommitBorrowAndMigrate(pid_t pid, const std::string& debtId,
99+ const CreatedDebtInfo& created,
100+ const std::map<int, uint64_t>& increments,
101+ std::vector<std::pair<int, uint64_t>>& numaTargets);
102+ 
103+ void FailBorrowAbort(pid_t pid, const std::string& debtId, uint64_t need, uint64_t roundNum);
104+ 
105+ std::string GetCurrentNodeId();
106+ 
107+ std::optional<uint64_t> GetNodeFreeBytes();
108+ 
109+ std::optional<uint64_t> ReadMemAvailableKb();
110+ 
111+ bool EnqueueReturnDebt(pid_t pid, const def::ReturnRequestItem& item, ReturnScene scene);
112+ 
113+ void RunReturnDebt(pid_t pid, def::ReturnRequestItem item, ReturnScene scene);
114+ 
115+ void RunPidReturn(pid_t pid, ReturnScene scene);
116+ 
117+ uint32_t DoPidReturnOnce(pid_t pid, ReturnScene scene);
118+ 
119+ pid_t ResolveDebtPid(const std::string& name);
120+ 
121+ std::mutex& GetPidMutex(pid_t pid);
122+ 
123+ void BroadcastPassiveReturn(uint64_t roundNum, uint64_t nodeFree, bool emergency);
124+ 
125+ uint32_t ReturnOneDebt(const def::ReturnRequestItem& item, uint64_t& nodeFree);
126+ 
127+ uint32_t ReturnDebtToLocal(pid_t pid, const def::ReturnRequestItem& item, uint64_t& nodeFree, ReturnScene scene);
128+ 
129+ uint32_t ReturnDebtRemoteToRemote(const def::ReturnRequestItem& item, const std::string& oldLenderNodeId,
130+ int oldRemoteNuma, pid_t pid, int srcNuma);
131+ 
132+ void OomEmergencyBorrow(uint64_t roundNum, uint64_t nodeFree);
133+ 
134+ void OomActiveReturn(uint64_t windowMinFree);
135+ 
136+ void OomRearrangeMigrated();
137+ 
138+ void OomLoop();
139+ 
140+ void RecoverBorrowFromObmm();
141+ 
142+ bool EnqueueOrphanReturn(const std::string& debtName);
143+ 
144+ void RunOrphanReturn(const std::string& debtName);
145+ 
146+ void RetryReturnEnqueue(uint32_t& retryCount, const std::function<bool()>& enqueue);
147+ 
148+ uint32_t RecoverSmapProcessConfig();
149+ 
150+ void LoadConfig();
151+ void LoadOomReturnConfig();
152+ bool StartExecutors();
153+ void RunBorrowRound(uint64_t roundNum);
154+ void LogBorrowCandidates(uint64_t roundNum, const std::vector<def::BorrowCandidate>& candidates);
155+ 
156+ void RemoveSlotFinalize(pid_t pid, const std::string& debtId);
157+ uint64_t FlushBorrowIncrements(def::BorrowState& borrow, const std::vector<std::pair<int, uint64_t>>& numaTargets,
158+ const std::map<int, uint64_t>& increments, const std::string& ownDebtId,
159+ const CreatedDebtInfo& created);
160+ bool BuildBorrower(pid_t pid, int srcNumaId, uint64_t need, uint64_t roundNum,
161+ ubse::mem::controller::UbseMemBorrower& borrower);
162+ bool CreateNumaDebt(pid_t pid, uint64_t need, int srcNumaId, const std::string& debtId, uint64_t roundNum,
163+ CreatedDebtInfo& out);
164+ int RmrsMigrateToNumas(pid_t pid, const std::vector<std::pair<int, uint64_t>>& numaTargets);
165+ 
166+ bool CheckPidTimeoutSlots(pid_t pid, def::BorrowState& borrow, uint64_t roundNum);
167+ uint32_t DoReturnDebtOnce(pid_t pid, const def::ReturnRequestItem& item, ReturnScene scene);
168+ bool ReturnOnePidDebt(const ubse::mem::controller::UbseNumaMemoryDebtInfo& debt, bool procExited,
169+ uint32_t& freedCount, uint64_t& freedBytes, uint32_t& failCount);
170+ 
171+ void RecoverBindDebt(pid_t pid, const ubse::mem::controller::UbseNumaMemoryImportDebtInfo& debt);
172+ void RecoverPidMigratedBytes(pid_t pid,
173+ const std::unordered_map<pid_t, std::unordered_map<int, uint64_t>>& smapMemKb,
174+ const std::set<int>& failedNumas, size_t& filled, uint64_t& filledBytes);
175+ void ReportDirtySmapStates(const std::unordered_map<pid_t, std::unordered_map<int, uint64_t>>& smapMemKb,
176+ const std::map<pid_t, def::ManagedPidEntry>& snapshot, size_t& dirty,
177+ size_t& smapEntries);
178+ 
179+ uint32_t CreateReplacementDebt(const def::ReturnRequestItem& item, pid_t pid, const ReplacementDebtCtx& ctx,
180+ std::string& newDebtId, int& newRemoteNuma);
181+ int MigrateDebtRemoteToRemote(pid_t pid, int oldRemoteNuma, int newRemoteNuma, uint64_t sizeBytes);
182+ uint32_t MigrateDebtToReplacement(const def::ReturnRequestItem& item, pid_t pid, const ReplacementDebtCtx& ctx,
183+ std::string& newDebtId, int& newRemoteNuma);
184+ uint32_t DeleteOldReturnDebt(const std::string& debtId);
185+ std::vector<std::string> BuildReplacementCandidates(const std::string& oldLenderNodeId);
186+ 
187+ void UpdateOomFastWindow(uint64_t nodeFree);
188+ void CollectActiveReturnDebts(const std::map<pid_t, def::ManagedPidEntry>& snapshot,
189+ std::vector<std::pair<pid_t, def::ReturnRequestItem>>& debts, uint64_t& totalRemote);
190+ 
191+ uint32_t processIntervalSec_{5};
192+ uint64_t freeMemoryThresholdBytes_{0};
193+ uint32_t timeoutPer128MbMs_{1000};
194+ bool samePlanePrefer_{false};
195+ uint64_t fastPollIntervalMs_{200};
196+ uint64_t emergencyThresholdBytes_{0};
197+ uint32_t observeCycles_{6};
198+ uint64_t returnRetryIntervalMs_{1000};
199+ 
200+ ubse::task_executor::UbseTaskExecutorPtr borrowExecutor_{};
201+ ubse::task_executor::UbseTaskExecutorPtr returnExecutor_{};
202+ 
203+ static constexpr uint32_t kPidMutexNum = 64;
204+ std::array<std::mutex, kPidMutexNum> pidMutexes_{};
205+ 
206+ // remote-to-remote 归还中旧 debt 删除失败的 debt 名;重试时跳过 create+migrate 只重删旧 debt
207+ std::set<std::string> pendingOldDebtDeletes_{};
208+ std::mutex pendingOldDebtDeletesMutex_{};
209+ 
210+ std::atomic<uint64_t> roundNumber_{0};
211+ std::atomic<bool> cycleRunning_{false};
212+ std::atomic<bool> stopping_{false};
213+ 
214+ std::thread oomThread_{};
215+ std::mutex oomMutex_{};
216+ std::condition_variable oomCv_{};
217+ bool oomRunning_{false};
218+ bool oomFastPoll_{false};
219+ uint32_t oomFastWindowCount_{0};
220+ uint64_t oomFastWindowMin_{0};
221+ std::chrono::steady_clock::time_point lastEmergencyBroadcast_{};
222+ std::atomic<uint64_t> oomRoundNumber_{0};
223+};
224+ 
225+} // namespace process_mem::decision
226+#endif
@@ -11,1164 +11,961 @@
11 */11 */
12#include "process_mem_pid_info_manager.h"12#include "process_mem_pid_info_manager.h"
13 13 
14-#include <mutex>14+#include <algorithm>
15+#include <cmath>
16+#include <fstream>
17+#include <set>
18+#include <sstream>
15 19 
16#include "ubse_conf.h"20#include "ubse_conf.h"
17#include "ubse_error.h"21#include "ubse_error.h"
18#include "ubse_logger.h"22#include "ubse_logger.h"
19-#include "ubse_mem_controller.h"
20-#include "ubse_timer.h"
21#include "process_mem_pid_collect.h"23#include "process_mem_pid_collect.h"
22#include "process_mem_pid_config_manager.h"24#include "process_mem_pid_config_manager.h"
25+#include "process_mem_pid_decision.h"
23 26 
24-#include "ubse_node_controller.h"
25#include "process_mem_pid_bridge.h"27#include "process_mem_pid_bridge.h"
26 28 
27namespace process_mem::manager {29namespace process_mem::manager {
28UBSE_DEFINE_THIS_MODULE("process_mem");30UBSE_DEFINE_THIS_MODULE("process_mem");
29-static uint32_t g_borrowTimeOut = 30;
30- 
31-uint32_t ProcessMemPidInfoManager::UpdatePidMemBorrowInfo(pid_t pid, const def::DebtInfo& debtInfo)
32-{
33- std::unique_lock<std::shared_mutex> lock(pidInfoMutex);
34- auto pidIt = pidInfoMap.find(pid);
35- if (pidIt == pidInfoMap.end()) {
36- return UBSE_ERROR;
37- }
38- pidIt->second.memBorrowInfo.debtInfos[debtInfo.numaDesc.name] = debtInfo;
39- return UBSE_OK;
40-}
41- 
42-uint32_t ProcessMemPidInfoManager::UpdatePidMemBorrowInfo(pid_t pid,
43- const ubse::mem::controller::UbseMemBorrower& borrower,
44- const def::DebtInfo& debtInfo)
45-{
46- std::unique_lock<std::shared_mutex> lock(pidInfoMutex);
47- auto pidIt = pidInfoMap.find(pid);
48- if (pidIt == pidInfoMap.end()) {
49- return UBSE_ERROR;
50- }
51- pidIt->second.memBorrowInfo.importSocketId = borrower.affinitySocketId;
52- pidIt->second.memBorrowInfo.remoteNumaId = debtInfo.numaDesc.numaId;
53- pidIt->second.memBorrowInfo.exportSlotId = debtInfo.numaDesc.exportNode.slotId;
54- pidIt->second.memBorrowInfo.debtInfos[debtInfo.numaDesc.name] = debtInfo;
55- return UBSE_OK;
56-}
57- 
58-uint32_t MigrateOut(const def::ProcessMemPidInfo& pidInfo, uint64_t targetRemoteMemory);
59-bool GetMigrateResult(pid_t pid, int64_t remoteNumaId, uint64_t expectSize, bool isBack);
60- 
61-void ProcessMemPidInfoManager::DeletePidMemBorrowInfo(pid_t pid, const std::string& borrowName)
62-{
63- int remoteNumaId = -1;
64- {
65- std::unique_lock<std::shared_mutex> lock(pidInfoMutex);
66- auto pidIt = pidInfoMap.find(pid);
67- if (pidIt == pidInfoMap.end()) {
68- return;
69- }
70- pidIt->second.memBorrowInfo.debtInfos.erase(borrowName);
71- if (!pidIt->second.memBorrowInfo.debtInfos.empty()) {
72- return;
73- }
74- remoteNumaId = pidIt->second.memBorrowInfo.remoteNumaId;
75- pidIt->second.memBorrowInfo = {};
76- }
77- if (remoteNumaId == -1) {
78- return;
79- }
80- def::ProcessMemPidInfo tmpInfo{};
81- tmpInfo.configInfo.pid = pid;
82- tmpInfo.memBorrowInfo.remoteNumaId = remoteNumaId;
83- MigrateOut(tmpInfo, 0);
84- bool res = GetMigrateResult(pid, static_cast<int64_t>(remoteNumaId), 0, true);
85- if (!res) {
86- UBSE_LOG_ERROR << "MigrateOut failed, pid=" << pid;
87- }
88- std::vector<pid_t> pids = {pid};
89- UBSE_LOG_INFO << "rmrsRemove called, pid=" << pid;
90- pid::bridge::ProcessMemPidBridge::rmrsRemove(def::INVALID_REMOTE_NUMA, pids, 0);
91-}
92- 
93-void ProcessMemPidInfoManager::ResetPidMemBorrowInfo(pid_t pid)
94-{
95- std::unique_lock<std::shared_mutex> lock(pidInfoMutex);
96- auto pidIt = pidInfoMap.find(pid);
97- if (pidIt == pidInfoMap.end()) {
98- return;
99- }
100- pidIt->second.memBorrowInfo = {};
101-}
102- 
103-uint32_t ProcessMemPidInfoManager::SetPidInfoMap(const def::ProcessMemPidInfo& pidInfo)
104-{
105- std::unique_lock<std::shared_mutex> lock(pidInfoMutex);
106- if (pidInfo.startTime == 0) {
107- UBSE_LOG_ERROR << "Failed to get exact start time for pid: " << pidInfo.configInfo.pid;
108- return UBSE_ERR_NOT_EXIST;
109- }
110- 
111- auto pidIt = pidInfoMap.find(pidInfo.configInfo.pid);
112- bool existsInPidMap = (pidIt != pidInfoMap.end());
113- auto srcNumaStr = pidInfo.configInfo.srcNumaId.has_value() ? std::to_string(pidInfo.configInfo.srcNumaId.value()) :
114- "N/A";
115- UBSE_LOG_INFO << "srcNuma Id is " << srcNumaStr;
116- if (!existsInPidMap) {
117- ProcessMemPidConfigManager::PersistPidConfigInfo(pidInfo);
118- pidInfoMap.insert_or_assign(pidInfo.configInfo.pid, pidInfo);
119- return UBSE_OK;
120- }
121- 
122- if (pidInfo.startTime != pidIt->second.startTime) {
123- UBSE_LOG_ERROR << "Start time mismatch for pid: " << pidInfo.configInfo.pid
124- << ", provided: " << pidInfo.startTime << ", actual: " << pidIt->second.startTime;
125- return UBSE_ERR_INVALID_ARG;
126- }
127- if (!pidIt->second.memBorrowInfo.debtInfos.empty() &&
128- pidInfo.configInfo.srcNumaId != pidIt->second.configInfo.srcNumaId) {
129- UBSE_LOG_ERROR << "Cannot modify srcNumaId while pid=" << pidInfo.configInfo.pid
130- << " has existing borrow debts";
131- return UBSE_ERR_RESOURCE_BUSY;
132- }
133- pidIt->second.configInfo = pidInfo.configInfo;
134- ProcessMemPidConfigManager::PersistPidConfigInfo(pidIt->second);
135- return UBSE_OK;
136-}
137- 
138-void PrintConfigInfo(const std::vector<def::ProcessMemPidInfo>& pidInfos)
139-{
140- for (const auto& pidInfo : pidInfos) {
141- auto srcNumaStr =
142- pidInfo.configInfo.srcNumaId.has_value() ? std::to_string(pidInfo.configInfo.srcNumaId.value()) : "N/A";
143- UBSE_LOG_INFO << "Pid: " << pidInfo.configInfo.pid << ", NumaId: " << srcNumaStr;
144- }
145-}
146 31 
147void ProcessMemPidInfoManager::Init()32void ProcessMemPidInfoManager::Init()
148{33{
149- std::vector<def::ProcessMemPidInfo> pidInfos{};34+ RefreshProcMemConfigCache();
150- const std::string section = "process_mem.pid";
151- const std::string configKey = "borrow.timeout";
152- ubse::config::UbseGetUInt(section, configKey, g_borrowTimeOut);
153- constexpr uint32_t borrowDefaultTimeOut = 30;
154- if (g_borrowTimeOut == 0) {
155- g_borrowTimeOut = borrowDefaultTimeOut;
156- }
157 35 
158- const int borrowThreadNum = 10;36+ collect::VmRssCollectHandler vmRssHandler = [](const collect::PidCollectInfoMap& collectInfo) {
159- const int returnThreadNum = 10;37+ ProcessMemPidInfoManager::GetInstance().VmRssCheckCallBack(collectInfo);
160- const int exceptionThreadNum = 4;
161- const int queSize = 1024;
162- borrowExecutor = ubse::task_executor::UbseTaskExecutor::Create("PidBorrow", borrowThreadNum, queSize);
163- if (borrowExecutor == nullptr || !borrowExecutor->Start()) {
164- UBSE_LOG_ERROR << "borrowExecutor start failed";
165- }
166- returnExecutor = ubse::task_executor::UbseTaskExecutor::Create("PidReturn", returnThreadNum, queSize);
167- if (returnExecutor == nullptr || !returnExecutor->Start()) {
168- UBSE_LOG_ERROR << "returnExecutor start failed";
169- }
170- exceptionHandleExecutor =
171- ubse::task_executor::UbseTaskExecutor::Create("ExceptionHandle", exceptionThreadNum, queSize);
172- if (exceptionHandleExecutor == nullptr || !exceptionHandleExecutor->Start()) {
173- UBSE_LOG_ERROR << "exceptionHandleExecutor start failed";
174- }
175- 
176- ProcessMemPidConfigManager::GetAllPersistedPidConfigInfo(pidInfos);
177- PrintConfigInfo(pidInfos);
178- for (auto& pidInfo : pidInfos) {
179- if (!ProcessMemPidConfigManager::CheckPidConfigInfo(pidInfo)) {
180- ProcessMemPidConfigManager::DeletePidConfigInfo(pidInfo.configInfo.pid);
181- continue;
182- }
183- SetPidInfoMap(pidInfo);
184- }
185- 
186- collect::NumaMemDistributionCollectHandler handler = [](const collect::CollectInfoMap& collectInfo) {
187- ProcessMemPidInfoManager::GetInstance().TotalMemoryCheckCallBack(collectInfo);
188 };38 };
189- process_mem::collect::ProcessMemPidCollect::GetInstance().RegisterCollectHandler("pidCollectCallback", handler);39+ process_mem::collect::ProcessMemPidCollect::GetInstance().RegisterVmRssCollectHandler("pidVmRssCallback",
40+ vmRssHandler);
41+ 
190 process_mem::collect::ProcessMemPidCollect::GetInstance().Init();42 process_mem::collect::ProcessMemPidCollect::GetInstance().Init();
191 43 
192- UBSE_LOG_INFO << "borrowExecutor and returnExecutor start success";44+ RebuildManagedPidCache();
45+ 
46+ UBSE_LOG_INFO << "ProcessMemPidInfoManager init done";
193}47}
194 48 
195void ProcessMemPidInfoManager::UnInit()49void ProcessMemPidInfoManager::UnInit()
196{50{
197- // 停止采集和定时检查,不再投递新任务51+ process_mem::collect::ProcessMemPidCollect::GetInstance().UnRegisterVmRssCollectHandler("pidVmRssCallback");
198- process_mem::collect::ProcessMemPidCollect::GetInstance().UnRegisterCollectHandler("pidCollectCallback");
199 process_mem::collect::ProcessMemPidCollect::GetInstance().UnInit();52 process_mem::collect::ProcessMemPidCollect::GetInstance().UnInit();
200- ubse::timer::UbseTimerHandlerUnregister("PidMemoryCheck");
201- 
202- // 排空队列后停止,避免积压的借用/归还任务被丢弃
203- if (borrowExecutor != nullptr) {
204- borrowExecutor->Wait();
205- borrowExecutor->Stop();
206- }
207- if (returnExecutor != nullptr) {
208- returnExecutor->Wait();
209- returnExecutor->Stop();
210- }
211- if (exceptionHandleExecutor != nullptr) {
212- exceptionHandleExecutor->Stop();
213- }
214}53}
215 54 
216-uint32_t GetPidNumaSize(pid_t pid, int64_t numaId, uint64_t& numaSize)55+int DoMigrateOut(pid_t pid, const std::map<int, uint64_t>& numaTargets)
217{56{
218- uint64_t tmpNumaSize = 0;
219- std::unordered_map<uint32_t, size_t> numaMemDistribution;
220- auto& collector = process_mem::collect::ProcessMemPidCollect::GetInstance();
221- auto ret = collector.CollectProcessNumaMemDistribution(pid, numaMemDistribution);
222- if (ret != UBSE_OK) {
223- UBSE_LOG_ERROR << "GetPidNumaSize failed, pid is " << pid << ", numaSize is " << numaSize;
224- return UBSE_ERROR;
225- }
226- for (const auto& [key, numaSize] : numaMemDistribution) {
227- if (key == numaId) {
228- tmpNumaSize += numaSize;
229- }
230- }
231- numaSize = tmpNumaSize;
232- return UBSE_OK;
233-}
234- 
235-def::ProcessMemPidInfo ProcessMemPidInfoManager::GetPidInfoMap(pid_t pid)
236-{
237- std::shared_lock<std::shared_mutex> lock(pidInfoMutex);
238- auto pidIt = pidInfoMap.find(pid);
239- if (pidIt == pidInfoMap.end()) {
240- def::ProcessMemPidInfo pidInfo;
241- pidInfo.configInfo.pid = -1;
242- return pidInfo;
243- }
244- return pidIt->second;
245-}
246- 
247-void ProcessMemPidInfoManager::GetAllPidInfo(std::vector<def::ProcessMemPidInfo>& pidInfos)
248-{
249- std::shared_lock<std::shared_mutex> lock(pidInfoMutex);
250- for (auto [key, value] : pidInfoMap) {
251- pidInfos.push_back(value);
252- }
253- return;
254-}
255- 
256-bool GetMigrateResult(pid_t pid, int64_t remoteNumaId, uint64_t expectSize, bool isBack = false)
257-{
258- const int retryTime = 10;
259- const int sleepSec = 2;
260- int nowTime = 0;
261- uint64_t numaSize = 0;
262- uint32_t ret = 0;
263- int def = 10 * (1 << 20);
264- if (isBack) {
265- def = 0;
266- }
267- auto checkFunc = [def](uint64_t numaSize, uint64_t expectSize) {
268- if (numaSize > expectSize) {
269- return (numaSize - expectSize) <= def;
270- }
271- return (expectSize - numaSize) <= def;
272- };
273- do {
274- ret |= GetPidNumaSize(pid, remoteNumaId, numaSize);
275- ++nowTime;
276- if (nowTime < retryTime) {
277- sleep(sleepSec);
278- }
279- } while (nowTime < retryTime && !checkFunc(numaSize, expectSize));
280- 
281- return checkFunc(numaSize, expectSize) && ret == 0;
282-}
283- 
284-std::string GenerateSimpleName(pid_t pid)
285-{
286- auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch())
287- .count();
288- constexpr size_t maxNameLen = 48;
289- constexpr size_t truncateLen = maxNameLen - 1;
290- auto nodeId = ubse::nodeController::UbseNodeController::GetInstance().GetCurrentNodeId();
291- std::string name = nodeId + "-" + std::to_string(pid) + "-" + std::to_string(ms);
292- if (name.size() >= maxNameLen) {
293- name.resize(truncateLen);
294- }
295- return name;
296-}
297- 
298-bool CheckIsNeedBorrow(const def::ProcessMemPidInfo& pidInfo, uint64_t expectRemoteMemory, uint64_t& needBorrowSize)
299-{
300- uint64_t totalSecured = 0;
301- std::vector<std::string> timeoutDebts;
302- for (const auto& [name, debtInfo] : pidInfo.memBorrowInfo.debtInfos) {
303- auto now = std::chrono::steady_clock::now();
304- if (debtInfo.status == def::BorrowStatus::CREATING &&
305- (now - debtInfo.borrowStartTime) > std::chrono::seconds(g_borrowTimeOut)) {
306- timeoutDebts.push_back(name);
307- continue;
308- }
309- totalSecured += debtInfo.numaDesc.size;
310- }
311- for (const auto& name : timeoutDebts) {
312- ProcessMemPidInfoManager::GetInstance().DeletePidMemBorrowInfo(pidInfo.configInfo.pid, name);
313- }
314- UBSE_LOG_INFO << "expectRemoteMemory is " << expectRemoteMemory << ", totalSecured is " << totalSecured;
315- if (expectRemoteMemory <= totalSecured) {
316- return false;
317- }
318- needBorrowSize = expectRemoteMemory - totalSecured;
319- return true;
320-}
321- 
322-uint32_t GetUbseMemBorrower(const def::ProcessMemPidInfo& pidInfo, ubse::mem::controller::UbseMemBorrower& borrower)
323-{
324- borrower.nodeId = ubse::nodeController::UbseNodeController::GetInstance().GetCurrentNodeId();
325- if (borrower.nodeId.empty()) {
326- UBSE_LOG_ERROR << "Get borrowNodeId failed";
327- return UBSE_ERROR;
328- }
329- 
330- borrower.affinitySocketId = pidInfo.memBorrowInfo.importSocketId;
331- if (borrower.affinitySocketId != -1) {
332- return UBSE_OK;
333- }
334- 
335- uint64_t targetNumaId = 0;
336- if (pidInfo.configInfo.srcNumaId.has_value()) {
337- targetNumaId = pidInfo.configInfo.srcNumaId.value();
338- } else {
339- std::unordered_map<uint32_t, size_t> numaDistribution{};
340- auto ret = process_mem::collect::ProcessMemPidCollect::GetInstance().CollectProcessNumaMemDistribution(
341- pidInfo.configInfo.pid, numaDistribution);
342- if (ret != UBSE_OK || numaDistribution.empty()) {
343- return UBSE_OK;
344- }
345- size_t maxMemSize = 0;
346- for (const auto& [numaId, memSize] : numaDistribution) {
347- if (memSize > maxMemSize) {
348- maxMemSize = memSize;
349- targetNumaId = numaId;
350- }
351- }
352- }
353- 
354- auto curNodeInfo = ubse::nodeController::UbseNodeController::GetInstance().GetCurNode();
355- for (const auto& [numaLoc, numaInfo] : curNodeInfo.numaInfos) {
356- if (numaLoc.numaId == targetNumaId) {
357- borrower.affinitySocketId = numaInfo.socketId;
358- }
359- }
360- return UBSE_OK;
361-}
362- 
363-void ExceptionMemoryHandle(const std::string& name)
364-{
365- const int maxRetryTimes = 10;
366- int flag = 0;
367- bool isOk = false;
368- while (flag < maxRetryTimes) {
369- ++flag;
370- auto ret = process_mem::pid::bridge::ProcessMemPidBridge::MemoryReturn(name);
371- if (ret == UBSE_OK || ret == UBSE_ERR_NOT_EXIST) {
372- isOk = true;
373- break;
374- }
375- sleep(1);
376- }
377- 
378- if (!isOk) {
379- ProcessMemPidInfoManager::GetInstance().exceptionHandleExecutor->Execute(
380- [name]() { ExceptionMemoryHandle(name); });
381- }
382-}
383- 
384-int DoMigrateOut(pid_t pid, int destNid, uint64_t memSizeKb)
385-{
386- mempooling::smap::MigrateOutPayload payload{};
387 std::vector<mempooling::smap::MigrateOutPayload> payloads{};57 std::vector<mempooling::smap::MigrateOutPayload> payloads{};
388- mempooling::smap::MigrateOutPayloadInner inner{};58+ for (const auto& [destNid, memSizeKb] : numaTargets) {
389- payload.count = 1;59+ mempooling::smap::MigrateOutPayload payload{};
390- inner.migrateMode = mempooling::smap::MIG_MEMSIZE_MODE;60+ mempooling::smap::MigrateOutPayloadInner inner{};
391- inner.memSize = memSizeKb;61+ payload.count = 1;
392- inner.destNid = destNid;62+ inner.migrateMode = mempooling::smap::MIG_MEMSIZE_MODE;
393- payload.pid = pid;63+ inner.memSize = memSizeKb;
394- payload.inner[0] = inner;64+ inner.destNid = destNid;
395- payloads.push_back(payload);65+ payload.pid = pid;
66+ payload.inner[0] = inner;
67+ payloads.push_back(payload);
68+ }
396 return process_mem::pid::bridge::ProcessMemPidBridge::rmrsMigrateOut(payloads, 0);69 return process_mem::pid::bridge::ProcessMemPidBridge::rmrsMigrateOut(payloads, 0);
397}70}
398 71 
399-uint32_t MigrateOut(const def::ProcessMemPidInfo& pidInfo, uint64_t targetRemoteMemory)72+static std::string MakeProcMemKey(bool isPid, const std::string& identifier)
400{73{
401- if (pidInfo.memBorrowInfo.remoteNumaId == -1) {74+ return (isPid ? "pid_" : "name_") + identifier;
402- return UBSE_OK;
403- }
404- 
405- auto memSizeKb = targetRemoteMemory / 1024;
406- constexpr uint64_t pageSizeKb = 4;
407- memSizeKb = memSizeKb / pageSizeKb * pageSizeKb;
408- 
409- UBSE_LOG_INFO << "MigrateOut begin, targetSize:" << memSizeKb
410- << "KB, remoteNumaId:" << pidInfo.memBorrowInfo.remoteNumaId;
411- int ret = DoMigrateOut(pidInfo.configInfo.pid, pidInfo.memBorrowInfo.remoteNumaId, memSizeKb);
412- if (ret == MEM_POOLING_HANDLING_FAULT) {
413- UBSE_LOG_ERROR << "MigrateOut fault: MEM_POOLING_HANDLING_FAULT, interrupting all business.";
414- return UBSE_ERROR;
415- }
416- return UBSE_OK;
417}75}
418 76 
419-uint64_t RefreshExpectRemoteMemory(pid_t pid, const def::ProcessMemPidInfo& pidInfo, uint64_t defaultExpectRemote)77+static bool IsRootFilterEnabled()
420{78{
421- std::unordered_map<uint32_t, size_t> numaDistribution{};79+ bool filterRoot = true;
422- auto ret = process_mem::collect::ProcessMemPidCollect::GetInstance().CollectProcessNumaMemDistribution(80+ ubse::config::UbseGetBool("process_mem", "filter_root_process", filterRoot);
423- pid, numaDistribution);81+ return filterRoot;
424- if (ret != UBSE_OK) {82+}
425- UBSE_LOG_WARN << "re-collect numa distribution failed, use original: " << defaultExpectRemote;
426- return defaultExpectRemote;
427- }
428 83 
429- const int64_t remoteNuma = pidInfo.memBorrowInfo.remoteNumaId;84+static bool IsRootPid(pid_t pid)
430- uint64_t localMemory = 0;85+{
431- uint64_t remoteMemory = 0;86+ return IsRootFilterEnabled() && collect::GetProcUid(pid) == 0;
432- for (const auto& [numaId, memSize] : numaDistribution) {87+}
433- if (numaId == remoteNuma) {88+ 
434- remoteMemory = memSize;89+void ProcessMemPidInfoManager::RefreshProcMemConfigCache()
90+{
91+ std::unique_lock<std::shared_mutex> lock(procMemConfigMutex_);
92+ procMemConfigCache_.clear();
93+ std::vector<def::ProcessMemNewConfigInfo> allConfigs;
94+ ProcessMemPidConfigManager::GetAllPersistedProcMemConfigs(allConfigs);
95+ 
96+ size_t restoredPidCount = 0;
97+ size_t restoredNameCount = 0;
98+ 
99+ for (auto& cfg : allConfigs) {
100+ if (cfg.isPid) {
101+ auto pidOpt = def::ParsePidFromIdentifier(cfg.identifier);
102+ if (!pidOpt.has_value()) {
103+ UBSE_LOG_WARN << "[process_mem] init restore: invalid PID identifier=" << cfg.identifier
104+ << ", skip recovery, deleting stale config";
105+ (void)ProcessMemPidConfigManager::DeleteProcMemConfig(cfg.isPid, cfg.identifier);
106+ continue;
107+ }
108+ ++restoredPidCount;
435 } else {109 } else {
436- localMemory += memSize;110+ ++restoredNameCount;
437 }111 }
112+ procMemConfigCache_.push_back(cfg);
438 }113 }
439- auto freshExpectRemote = ((localMemory + remoteMemory) * pidInfo.configInfo.targetEvictThreshold) / 100;114+ UBSE_LOG_INFO << "[process_mem] init restore: scanned " << allConfigs.size() << " persisted keys, restored "
440- UBSE_LOG_INFO << "refresh expectRemoteMemory: " << freshExpectRemote << ", localMemory=" << localMemory115+ << procMemConfigCache_.size() << " configs (pid=" << restoredPidCount << " name=" << restoredNameCount
441- << ", remoteMemory=" << remoteMemory;116+ << ")";
442- return freshExpectRemote;
443}117}
444 118 
445-void AsyncBorrowAndMigrateExecute(pid_t pid, const def::DebtInfo& debtInfo, uint64_t expectRemoteMemory)119+uint32_t ProcessMemPidInfoManager::FossilizePidConfig(pid_t pid, const std::string& name, uint64_t maxMemory,
120+ double remoteRatio, bool force)
446{121{
447- auto freshInfo = ProcessMemPidInfoManager::GetInstance().GetPidInfoMap(pid);122+ const std::string idStr = std::to_string(pid);
448- if (freshInfo.configInfo.pid == -1) {123+ if (!force) {
449- return;124+ std::shared_lock<std::shared_mutex> lock(procMemConfigMutex_);
450- }125+ for (const auto& cfg : procMemConfigCache_) {
451- 126+ if (cfg.isPid && cfg.identifier == idStr) {
452- ubse::mem::controller::UbseMemBorrower borrower{};127+ return UBSE_OK;
453- if (GetUbseMemBorrower(freshInfo, borrower) != UBSE_OK) {
454- ProcessMemPidInfoManager::GetInstance().DeletePidMemBorrowInfo(pid, debtInfo.numaDesc.name);
455- return;
456- }
457- 
458- ubse::mem::controller::UbseMemNumaDesc desc{};
459- pid::bridge::MemoryBorrowRequest request{};
460- auto curNodeInfo = ubse::nodeController::UbseNodeController::GetInstance().GetCurNode();
461- uint64_t blockSizeBytes = static_cast<uint64_t>(curNodeInfo.blockSize) * 1024 * 1024;
462- request.name = debtInfo.numaDesc.name;
463- // 根据ubse的对齐规则,size向上取整对齐到blockSize大小,+ blockSize - 1把余数部分"推过"下一个 block 边界
464- request.size = ((debtInfo.numaDesc.size + blockSizeBytes - 1) / blockSizeBytes) * blockSizeBytes;
465- if (memcpy_s(request.usrInfo, ubse::mem::controller::UBSE_MAX_USR_INFO_LEN, debtInfo.numaDesc.usrInfo,
466- ubse::mem::controller::UBSE_MAX_USR_INFO_LEN) != EOK) {
467- ProcessMemPidInfoManager::GetInstance().DeletePidMemBorrowInfo(pid, debtInfo.numaDesc.name);
468- return;
469- }
470- auto ret = process_mem::pid::bridge::ProcessMemPidBridge::MemoryBorrow(freshInfo, borrower, request, desc);
471- auto now = std::chrono::steady_clock::now();
472- if ((now - debtInfo.borrowStartTime > std::chrono::seconds(g_borrowTimeOut)) || ret != UBSE_OK) {
473- UBSE_LOG_ERROR << "MemoryBorrow failed or timeout, pid=" << pid << ", name=" << desc.name;
474- ProcessMemPidInfoManager::GetInstance().DeletePidMemBorrowInfo(pid, desc.name);
475- ProcessMemPidInfoManager::GetInstance().exceptionHandleExecutor->Execute(
476- [name = desc.name]() { ExceptionMemoryHandle(name); });
477- return;
478- }
479- 
480- def::DebtInfo completedDebt = debtInfo;
481- completedDebt.status = def::BorrowStatus::COMPLETED;
482- completedDebt.numaDesc = desc;
483- ret = ProcessMemPidInfoManager::GetInstance().UpdatePidMemBorrowInfo(pid, borrower, completedDebt);
484- if (ret != UBSE_OK) {
485- UBSE_LOG_ERROR << "UpdatePidMemBorrowInfo failed after borrow, pid=" << pid;
486- ProcessMemPidInfoManager::GetInstance().exceptionHandleExecutor->Execute(
487- [name = desc.name]() { ExceptionMemoryHandle(name); });
488- return;
489- }
490- 
491- auto infoAfterBorrow = ProcessMemPidInfoManager::GetInstance().GetPidInfoMap(pid);
492- if (infoAfterBorrow.configInfo.pid == -1) {
493- return;
494- }
495- auto freshExpectRemote = RefreshExpectRemoteMemory(pid, infoAfterBorrow, expectRemoteMemory);
496- if (MigrateOut(infoAfterBorrow, freshExpectRemote) != UBSE_OK) {
497- UBSE_LOG_ERROR << "MigrateOut failed after borrow, interrupting business for pid=" << pid;
498- return;
499- }
500-}
501- 
502-uint32_t BorrowAndMigrate(def::ProcessMemPidInfo& pidInfo, uint64_t expectRemoteMemory, int32_t minNuma)
503-{
504- bool isNeedBorrow = false;
505- uint64_t needBorrowSize = 0;
506- isNeedBorrow = CheckIsNeedBorrow(pidInfo, expectRemoteMemory, needBorrowSize);
507- if (!isNeedBorrow) {
508- if (MigrateOut(pidInfo, expectRemoteMemory) != UBSE_OK) {
509- UBSE_LOG_ERROR << "MigrateOut failed, interrupting business for pid=" << pidInfo.configInfo.pid;
510- return UBSE_ERROR;
511- }
512- return UBSE_OK;
513- }
514- 
515- def::DebtInfo debtInfo{};
516- std::string name = GenerateSimpleName(pidInfo.configInfo.pid);
517- debtInfo.borrowStartTime = std::chrono::steady_clock::now();
518- debtInfo.numaDesc.name = name;
519- debtInfo.numaDesc.size = needBorrowSize;
520- debtInfo.status = def::BorrowStatus::CREATING;
521- const def::ProcessMemUsrInfo usrInfo{
522- .pid = pidInfo.configInfo.pid, .startTime = pidInfo.startTime, .srcNuma = minNuma};
523- auto ret =
524- memcpy_s(debtInfo.numaDesc.usrInfo, ubse::mem::controller::UBSE_MAX_USR_INFO_LEN, &usrInfo, sizeof(usrInfo));
525- if (ret != EOK) {
526- UBSE_LOG_ERROR << "memcpy_s failed";
527- return UBSE_ERROR;
528- }
529- ProcessMemPidInfoManager::GetInstance().UpdatePidMemBorrowInfo(pidInfo.configInfo.pid, debtInfo);
530- 
531- auto pid = pidInfo.configInfo.pid;
532- ProcessMemPidInfoManager::GetInstance().borrowExecutor->Execute(
533- [pid, debtInfo, expectRemoteMemory]() { AsyncBorrowAndMigrateExecute(pid, debtInfo, expectRemoteMemory); });
534- 
535- return UBSE_OK;
536-}
537- 
538-uint32_t BorrowMemoryAndMigrateOut(def::ProcessMemPidInfo& pidInfo, uint64_t localMemorySize, uint64_t remoteMemorySize,
539- int32_t minNuma)
540-{
541- auto expectRemoteMemory = ((localMemorySize + remoteMemorySize) * pidInfo.configInfo.targetEvictThreshold) / 100;
542- UBSE_LOG_INFO << "localMemory is " << localMemorySize << ", remoteMemory is " << remoteMemorySize
543- << ", expect RemoteMemory is " << expectRemoteMemory;
544- if (expectRemoteMemory <= remoteMemorySize) {
545- if (MigrateOut(pidInfo, expectRemoteMemory) != UBSE_OK) {
546- UBSE_LOG_ERROR << "MigrateOut failed, interrupting business for pid=" << pidInfo.configInfo.pid;
547- return UBSE_ERROR;
548- }
549- return UBSE_OK;
550- }
551- BorrowAndMigrate(pidInfo, expectRemoteMemory, minNuma);
552- return UBSE_OK;
553-}
554- 
555-// 归还内存
556-uint32_t MigrateBackAndReturnMemoryAsyncExecute(const def::ProcessMemPidInfo& pidInfo)
557-{
558- if (!ProcessMemPidInfoManager::GetInstance().IsRecoverCompleted()) {
559- UBSE_LOG_INFO << "Recover not completed, retry MigrateBackAndReturnMemory later, pid="
560- << pidInfo.configInfo.pid;
561- ProcessMemPidInfoManager::GetInstance().returnExecutor->Execute(
562- [pidInfo]() { MigrateBackAndReturnMemoryAsyncExecute(pidInfo); });
563- return UBSE_OK;
564- }
565- 
566- int remoteNuma = pidInfo.memBorrowInfo.remoteNumaId;
567- if (!pidInfo.memBorrowInfo.debtInfos.empty()) {
568- if (MigrateOut(pidInfo, 0) != UBSE_OK) {
569- UBSE_LOG_ERROR << "MigrateOut failed in return path, interrupting business for pid="
570- << pidInfo.configInfo.pid;
571- return UBSE_ERROR;
572- }
573- bool res = GetMigrateResult(pidInfo.configInfo.pid, remoteNuma, 0, true);
574- if (!res) {
575- UBSE_LOG_ERROR << "Memory Return rmrsMigrateOut failed";
576- }
577- std::vector<pid_t> pids;
578- pids.push_back(pidInfo.configInfo.pid);
579- UBSE_LOG_INFO << "rmrsRemove called, pid=" << pidInfo.configInfo.pid;
580- res = pid::bridge::ProcessMemPidBridge::rmrsRemove(def::INVALID_REMOTE_NUMA, pids, 0);
581- for (const auto& [name, debtInfo] : pidInfo.memBorrowInfo.debtInfos) {
582- auto borrowName = name;
583- auto ret = process_mem::pid::bridge::ProcessMemPidBridge::rmrsFreeWithMigrate(debtInfo.numaDesc.name);
584- if (ret == MEM_POOLING_HANDLING_FAULT) {
585- UBSE_LOG_ERROR << "rmrsFreeWithMigrate fault: MEM_POOLING_HANDLING_FAULT, "
586- << "interrupting all business for pid=" << pidInfo.configInfo.pid;
587- ProcessMemPidInfoManager::GetInstance().DeletePidMemBorrowInfo(pidInfo.configInfo.pid, name);
588- ProcessMemPidInfoManager::GetInstance().ResetPidMemBorrowInfo(pidInfo.configInfo.pid);
589- return UBSE_ERROR;
590- }
591- if (ret != UBSE_OK && ret != UBSE_ERR_NOT_EXIST) {
592- UBSE_LOG_ERROR << "rmrsFreeWithMigrate failed";
593- ProcessMemPidInfoManager::GetInstance().exceptionHandleExecutor->Execute(
594- [borrowName]() { ExceptionMemoryHandle(borrowName); });
595- }
596- ProcessMemPidInfoManager::GetInstance().DeletePidMemBorrowInfo(pidInfo.configInfo.pid, name);
597- }
598- }
599- // 重置借用信息,下次借用从头开始,exportSlotId 回归 -1
600- ProcessMemPidInfoManager::GetInstance().ResetPidMemBorrowInfo(pidInfo.configInfo.pid);
601- 
602- return UBSE_OK;
603-}
604- 
605-uint32_t MigrateBackAndReturnMemory(def::ProcessMemPidInfo& pidInfo)
606-{
607- auto& mgr = ProcessMemPidInfoManager::GetInstance();
608- if (mgr.returnExecutor.Get() == nullptr) {
609- return UBSE_ERROR;
610- }
611- mgr.returnExecutor->Execute([pidInfo]() { MigrateBackAndReturnMemoryAsyncExecute(pidInfo); });
612- return UBSE_OK;
613-}
614- 
615-uint32_t MemoryCheckHandle(def::ProcessMemPidInfo& info, const std::unordered_map<uint32_t, size_t>& numaMemory)
616-{
617- const int64_t remoteNuma = info.memBorrowInfo.remoteNumaId;
618- uint64_t localMemory = 0;
619- uint64_t remoteMemory = 0;
620- 
621- for (const auto& [numaId, memorySize] : numaMemory) {
622- if (numaId == remoteNuma) {
623- remoteMemory = memorySize;
624- } else {
625- localMemory += memorySize;
626- }
627- }
628- 
629- const auto totalMemory = localMemory + remoteMemory;
630- const auto expectMemory = info.configInfo.expectedMemoryUsage;
631- if (expectMemory == 0) {
632- return UBSE_OK;
633- }
634- const auto needBorrow = totalMemory * 100 > static_cast<uint64_t>(info.configInfo.evictThreshold) * expectMemory;
635- const auto needReturn = totalMemory * 100 < static_cast<uint64_t>(info.configInfo.reclaimThreshold) * expectMemory;
636- 
637- UBSE_LOG_INFO << "needBorrow IS " << needBorrow << ", needReturn is" << needReturn << ", localMemory is "
638- << localMemory << ", remote memory is " << remoteMemory << ",expectMemory is " << expectMemory;
639- if (needBorrow) {
640- int32_t minNuma = -1;
641- for (const auto& [numaId, _] : numaMemory) {
642- if (minNuma == -1 || static_cast<int32_t>(numaId) < minNuma) {
643- minNuma = static_cast<int32_t>(numaId);
644- }
645- }
646- BorrowMemoryAndMigrateOut(info, localMemory, remoteMemory, minNuma);
647- } else if (needReturn) {
648- MigrateBackAndReturnMemory(info);
649- }
650- return UBSE_OK;
651-}
652- 
653-uint32_t ProcessMemPidInfoManager::PerPidMemoryCheckCallBack(pid_t pid,
654- const std::unordered_map<uint32_t, size_t>& numaMemory)
655-{
656- if (!isRecoverCompleted_) {
657- UBSE_LOG_INFO << "isRecoverCompleted_ is false";
658- return UBSE_OK;
659- }
660- def::ProcessMemPidInfo info;
661- {
662- std::unique_lock<std::shared_mutex> lock(pidInfoMutex);
663- auto pidIt = pidInfoMap.find(pid);
664- if (pidIt == pidInfoMap.end()) {
665- return UBSE_OK;
666- }
667- if (pidIt->second.processStatus == def::ProcessStatus::FAULT) {
668- return UBSE_OK;
669- }
670- info = pidIt->second;
671- }
672- for (const auto& [key, value] : numaMemory) {
673- UBSE_LOG_INFO << "pid is " << pid << ", numaId is " << key << ", size is " << value;
674- }
675- 
676- UBSE_LOG_INFO << "evictThreshold is " << info.configInfo.evictThreshold << "target evictThreshold is "
677- << info.configInfo.targetEvictThreshold << "total used is" << info.configInfo.expectedMemoryUsage;
678- MemoryCheckHandle(info, numaMemory);
679- return UBSE_OK;
680-}
681- 
682-template <typename T>
683-bool IsProcessMemDebt(const T& debt)
684-{
685- def::ProcessMemUsrInfo usrInfo{};
686- if (memcpy_s(&usrInfo, sizeof(usrInfo), debt.usrInfo, sizeof(usrInfo)) != EOK) {
687- return false;
688- }
689- return usrInfo.pluginId == def::UsrInfoPluginType::PROCESS_MEM;
690-}
691- 
692-namespace {
693-struct ProcessMemDebtEntry {
694- pid_t pid{};
695- ubse::mem::controller::UbseNumaMemoryImportDebtInfo debtInfo{};
696-};
697- 
698-std::vector<ProcessMemDebtEntry> CollectProcessMemImportDebts()
699-{
700- std::vector<ubse::mem::controller::UbseNumaMemoryImportDebtInfo> debtInfos{};
701- auto ret = ubse::mem::controller::UbseGetNumaMemImportDebtInfoWithLocalNode(debtInfos);
702- if (ret != UBSE_OK) {
703- UBSE_LOG_WARN << "RefreshBorrowInfo: query import debts failed, ret=" << ret;
704- return {};
705- }
706- 
707- std::vector<ProcessMemDebtEntry> result;
708- for (const auto& debtInfo : debtInfos) {
709- if (!IsProcessMemDebt(debtInfo)) {
710- continue;
711- }
712- def::ProcessMemUsrInfo usrInfo{};
713- if (memcpy_s(&usrInfo, sizeof(usrInfo), debtInfo.usrInfo, sizeof(usrInfo)) != EOK || usrInfo.pid <= 0) {
714- continue;
715- }
716- result.push_back({usrInfo.pid, debtInfo});
717- }
718- return result;
719-}
720- 
721-void ApplyImportDebtToBorrowInfo(def::BorrowInfo& borrowInfo,
722- const ubse::mem::controller::UbseNumaMemoryImportDebtInfo& debt)
723-{
724- borrowInfo.remoteNumaId = debt.remoteNumaId;
725- if (!debt.borrowSocketIdList.empty()) {
726- borrowInfo.importSocketId = debt.borrowSocketIdList[0];
727- }
728-}
729- 
730-void MergeOrUpdateDebt(def::BorrowInfo& borrowInfo, const ubse::mem::controller::UbseNumaMemoryImportDebtInfo& debt)
731-{
732- auto debtIt = borrowInfo.debtInfos.find(debt.name);
733- if (debtIt != borrowInfo.debtInfos.end()) {
734- debtIt->second.numaDesc.size = debt.size;
735- debtIt->second.numaDesc.numaId = debt.remoteNumaId;
736- return;
737- }
738- 
739- def::DebtInfo newDebt{};
740- newDebt.status = def::BorrowStatus::COMPLETED;
741- newDebt.numaDesc.name = debt.name;
742- newDebt.numaDesc.numaId = debt.remoteNumaId;
743- newDebt.numaDesc.size = debt.size;
744- memcpy_s(newDebt.numaDesc.usrInfo, ubse::mem::controller::UBSE_MAX_USR_INFO_LEN, debt.usrInfo,
745- ubse::mem::controller::UBSE_MAX_USR_INFO_LEN);
746- borrowInfo.debtInfos[debt.name] = newDebt;
747-}
748- 
749-void RemoveStaleDebts(def::BorrowInfo& borrowInfo, const std::unordered_set<std::string>& activeNames)
750-{
751- auto& debtMap = borrowInfo.debtInfos;
752- for (auto it = debtMap.begin(); it != debtMap.end();) {
753- bool inUbse = activeNames.count(it->first) > 0;
754- if (inUbse) {
755- ++it;
756- continue;
757- }
758- if (it->second.status == def::BorrowStatus::CREATING) {
759- ++it;
760- continue;
761- }
762- UBSE_LOG_INFO << "RefreshBorrowInfo: remove stale debt " << it->first;
763- it = debtMap.erase(it);
764- }
765-}
766- 
767-struct RemovedPidDebt {
768- pid_t pid;
769- std::vector<std::string> debtNames;
770- int remoteNumaId;
771-};
772- 
773-void AsyncMigrateBackAndFreeForRemovedPid(const RemovedPidDebt& entry)
774-{
775- UBSE_LOG_INFO << "RefreshBorrowInfo: async process removed pid=" << entry.pid
776- << ", debtCount=" << entry.debtNames.size() << ", remoteNumaId=" << entry.remoteNumaId;
777- 
778- auto exactStartTime = ProcessMemPidConfigManager::GetExactStartTime(entry.pid);
779- if (exactStartTime == 0) {
780- UBSE_LOG_INFO << "RefreshBorrowInfo: process exited, returning memory directly for pid=" << entry.pid;
781- for (const auto& name : entry.debtNames) {
782- auto ret = process_mem::pid::bridge::ProcessMemPidBridge::MemoryReturn(name);
783- if (ret == UBSE_ERR_NOT_EXIST || ret == UBSE_ERR_UNIMPORT_SUCCESS || ret == UBSE_ERR_DELETING) {
784- continue;
785- }
786- if (ret != UBSE_OK) {
787- UBSE_LOG_ERROR << "RefreshBorrowInfo MemoryReturn failed for exited pid=" << entry.pid
788- << ", debt=" << name << ", ret=" << ret;
789- }
790- }
791- return;
792- }
793- 
794- int ret = DoMigrateOut(entry.pid, entry.remoteNumaId, 0);
795- if (ret == MEM_POOLING_HANDLING_FAULT) {
796- UBSE_LOG_ERROR << "RefreshBorrowInfo MigrateOut fault: MEM_POOLING_HANDLING_FAULT,"
797- << " interrupting for removed pid=" << entry.pid;
798- return;
799- }
800- if (ret != MEM_POOLING_OK) {
801- UBSE_LOG_ERROR << "RefreshBorrowInfo DoMigrateOut failed for removed pid=" << entry.pid << ", ret=" << ret;
802- return;
803- }
804- bool migrateDone = GetMigrateResult(entry.pid, static_cast<int64_t>(entry.remoteNumaId), 0, true);
805- if (!migrateDone) {
806- UBSE_LOG_ERROR << "RefreshBorrowInfo GetMigrateResult failed for removed pid=" << entry.pid;
807- return;
808- }
809- for (const auto& name : entry.debtNames) {
810- auto freeRet = process_mem::pid::bridge::ProcessMemPidBridge::rmrsFreeWithMigrate(name);
811- if (freeRet == MEM_POOLING_HANDLING_FAULT) {
812- UBSE_LOG_ERROR << "RefreshBorrowInfo rmrsFreeWithMigrate fault: MEM_POOLING_HANDLING_FAULT,"
813- << " interrupting for removed pid=" << entry.pid << ", debt=" << name;
814- continue;
815- }
816- if (freeRet != UBSE_OK && freeRet != UBSE_ERR_NOT_EXIST) {
817- UBSE_LOG_ERROR << "RefreshBorrowInfo rmrsFreeWithMigrate failed for removed pid=" << entry.pid
818- << ", debt=" << name << ", ret=" << freeRet;
819- }
820- }
821-}
822- 
823-} // namespace
824- 
825-void ProcessMemPidInfoManager::RefreshBorrowInfo()
826-{
827- auto entries = CollectProcessMemImportDebts();
828- 
829- std::unordered_map<pid_t, std::unordered_set<std::string>> activeDebts;
830- for (const auto& entry : entries) {
831- activeDebts[entry.pid].insert(entry.debtInfo.name);
832- }
833- 
834- // 收集已移除纳管的 pid(UBSE 中有账本但 pidInfoMap 中无配置),按 pid 汇聚
835- std::vector<RemovedPidDebt> removedPidDebts;
836- std::vector<std::pair<pid_t, int>> clearedPids;
837- {
838- std::unique_lock<std::shared_mutex> lock(pidInfoMutex);
839- 
840- for (const auto& entry : entries) {
841- if (pidInfoMap.find(entry.pid) != pidInfoMap.end()) {
842- continue;
843- }
844- auto it = std::find_if(removedPidDebts.begin(), removedPidDebts.end(),
845- [pid = entry.pid](const auto& item) { return item.pid == pid; });
846- if (it != removedPidDebts.end()) {
847- it->debtNames.push_back(entry.debtInfo.name);
848- } else {
849- removedPidDebts.push_back(
850- {entry.pid, {entry.debtInfo.name}, static_cast<int>(entry.debtInfo.remoteNumaId)});
851- }
852- }
853- 
854- // 合并/更新仍在管 pid 的运行态账本
855- for (const auto& entry : entries) {
856- auto pidIt = pidInfoMap.find(entry.pid);
857- if (pidIt == pidInfoMap.end()) {
858- continue;
859- }
860- auto& borrowInfo = pidIt->second.memBorrowInfo;
861- ApplyImportDebtToBorrowInfo(borrowInfo, entry.debtInfo);
862- MergeOrUpdateDebt(borrowInfo, entry.debtInfo);
863- }
864- 
865- // 清理 UBSE 中已不存在的账本(仅清本地缓存,不回迁),保留 CREATING 状态
866- static const std::unordered_set<std::string> emptySet;
867- for (auto& [pid, pidInfo] : pidInfoMap) {
868- auto activeIt = activeDebts.find(pid);
869- const auto& activeNames = (activeIt != activeDebts.end()) ? activeIt->second : emptySet;
870- RemoveStaleDebts(pidInfo.memBorrowInfo, activeNames);
871- if (pidInfo.memBorrowInfo.debtInfos.empty()) {
872- clearedPids.emplace_back(pid, pidInfo.memBorrowInfo.remoteNumaId);
873- pidInfo.memBorrowInfo = {};
874 }128 }
875 }129 }
876 }130 }
877 131 
878- // 异步处理已移除纳管的 pid:一次性回迁所有远端内存,再逐个归还账本132+ auto startTime = ProcessMemPidConfigManager::GetExactStartTime(pid);
879- for (auto& entry : removedPidDebts) {133+ if (startTime == 0) {
880- UBSE_LOG_INFO << "RefreshBorrowInfo: removed pid=" << entry.pid << ", debtCount=" << entry.debtNames.size()134+ UBSE_LOG_WARN << "FossilizePidConfig: pid=" << pid << " does not exist, skip";
881- << ", remoteNumaId=" << entry.remoteNumaId << ", submit to exception executor";
882- exceptionHandleExecutor->Execute([entry = std::move(entry)]() { AsyncMigrateBackAndFreeForRemovedPid(entry); });
883- }
884- 
885- for (const auto& [pid, remoteNumaId] : clearedPids) {
886- std::vector<pid_t> pids = {pid};
887- UBSE_LOG_INFO << "rmrsRemove called, pid=" << pid;
888- pid::bridge::ProcessMemPidBridge::rmrsRemove(def::INVALID_REMOTE_NUMA, pids, 0);
889- }
890-}
891- 
892-void ProcessMemPidInfoManager::TotalMemoryCheckCallBack(const collect::CollectInfoMap& collectInfo)
893-{
894- CheckFaultNodesRecovery();
895- RefreshBorrowInfo();
896- for (const auto& [pid, numaMemoryInfo] : collectInfo) {
897- auto ret = PerPidMemoryCheckCallBack(pid, numaMemoryInfo);
898- if (ret != UBSE_OK) {
899- UBSE_LOG_ERROR << "PerPidMemoryCheckCallBack failed";
900- }
901- }
902-}
903- 
904-uint32_t ProcessMemPidInfoManager::UnsetPidInfo(pid_t pid)
905-{
906- std::unique_lock<std::shared_mutex> lock(pidInfoMutex);
907- auto pidIt = pidInfoMap.find(pid);
908- if (pidIt == pidInfoMap.end()) {
909- UBSE_LOG_INFO << "pid not found in pidInfoMap, pid is " << pid;
910 return UBSE_ERR_NOT_EXIST;135 return UBSE_ERR_NOT_EXIST;
911 }136 }
912- UBSE_LOG_INFO << "pid has borrow info, pid is " << pid;137+ 
913- MigrateBackAndReturnMemory(pidIt->second);138+ def::FossilPidConfigInfo fossil{};
914- ProcessMemPidConfigManager::DeletePidConfigInfo(pid);139+ fossil.name = name;
915- pidInfoMap.erase(pid);140+ fossil.maxMemory = maxMemory;
141+ fossil.remoteRatio = remoteRatio;
142+ fossil.startTime = static_cast<uint64_t>(startTime);
143+ auto persistRet = ProcessMemPidConfigManager::PersistFossilConfig(pid, fossil);
144+ if (persistRet != UBSE_OK) {
145+ UBSE_LOG_ERROR << "FossilizePidConfig: persist failed for pid=" << pid
146+ << ", ret=" << ubse::log::FormatRetCode(persistRet);
147+ return UBSE_ERROR;
148+ }
149+ UBSE_LOG_INFO << "FossilizePidConfig: pid=" << pid << " name=" << name << " maxMemory=" << maxMemory
150+ << " remoteRatio=" << remoteRatio;
916 return UBSE_OK;151 return UBSE_OK;
917}152}
918 153 
919-namespace {154+bool ProcessMemPidInfoManager::HasExplicitPidConfig(pid_t pid) const
920-void RecoverSingleDebtInfo(std::unordered_map<pid_t, def::ProcessMemPidInfo>& pidInfoMap,
921- const ubse::mem::controller::UbseNumaMemoryDebtInfo& debtInfo,
922- ubse::task_executor::UbseTaskExecutorPtr& exceptionHandleExecutor)
923{155{
924- def::ProcessMemUsrInfo usrInfo{};156+ std::shared_lock<std::shared_mutex> lock(procMemConfigMutex_);
925- if (memcpy_s(&usrInfo, sizeof(usrInfo), debtInfo.usrInfo, sizeof(usrInfo)) != EOK) {157+ const std::string idStr = std::to_string(pid);
926- UBSE_LOG_ERROR << "memcpy_s usrInfo failed";158+ for (const auto& cfg : procMemConfigCache_) {
927- return;159+ if (cfg.isPid && cfg.identifier == idStr) {
928- }
929- UBSE_LOG_INFO << "RecoverSingleDebtInfo usrInfo: pluginId=" << static_cast<int>(usrInfo.pluginId)
930- << ", pid=" << usrInfo.pid << ", startTime=" << usrInfo.startTime << ", name=" << debtInfo.name
931- << ", size=" << debtInfo.size << ", remoteNumaId=" << debtInfo.remoteNumaId;
932- if (usrInfo.pluginId != def::UsrInfoPluginType::PROCESS_MEM) {
933- UBSE_LOG_DEBUG << "skip non-process_mem debt, pluginId=" << static_cast<int>(usrInfo.pluginId)
934- << ", name=" << debtInfo.name;
935- return;
936- }
937- 
938- auto pidIt = pidInfoMap.find(usrInfo.pid);
939- if (pidIt == pidInfoMap.end() || pidIt->second.startTime != usrInfo.startTime) {
940- UBSE_LOG_WARN << "pid not found or startTime mismatch, return memory, name=" << debtInfo.name;
941- if (pidIt != pidInfoMap.end()) {
942- pidInfoMap.erase(pidIt);
943- }
944- exceptionHandleExecutor->Execute([name = debtInfo.name]() { ExceptionMemoryHandle(name); });
945- return;
946- }
947- 
948- def::DebtInfo newDebtInfo{};
949- newDebtInfo.status = def::BorrowStatus::COMPLETED;
950- newDebtInfo.numaDesc.name = debtInfo.name;
951- newDebtInfo.numaDesc.numaId = debtInfo.remoteNumaId;
952- newDebtInfo.numaDesc.size = debtInfo.size;
953- if (memcpy_s(newDebtInfo.numaDesc.usrInfo, ubse::mem::controller::UBSE_MAX_USR_INFO_LEN, debtInfo.usrInfo,
954- ubse::mem::controller::UBSE_MAX_USR_INFO_LEN) != EOK) {
955- UBSE_LOG_ERROR << "memcpy_s newDebtInfo usrInfo failed";
956- return;
957- }
958- auto& borrowInfo = pidIt->second.memBorrowInfo;
959- borrowInfo.remoteNumaId = debtInfo.remoteNumaId;
960- if (!debtInfo.borrowSocketIdList.empty()) {
961- borrowInfo.importSocketId = debtInfo.borrowSocketIdList[0];
962- }
963- if (!debtInfo.lentNodeId.empty()) {
964- borrowInfo.exportSlotId = std::stoi(debtInfo.lentNodeId);
965- newDebtInfo.numaDesc.exportNode.slotId = static_cast<uint32_t>(borrowInfo.exportSlotId);
966- }
967- 
968- borrowInfo.debtInfos[debtInfo.name] = newDebtInfo;
969- UBSE_LOG_INFO << "Recover debt success, pid=" << usrInfo.pid << ", name=" << debtInfo.name
970- << ", remoteNumaId=" << debtInfo.remoteNumaId << ", exportSlotId=" << borrowInfo.exportSlotId;
971-}
972-} // namespace
973- 
974-void ProcessMemPidInfoManager::RecoverAllDebtInfoData()
975-{
976- UBSE_LOG_INFO << "RecoverAllDebtInfoData begin";
977- auto curNodeId = ubse::nodeController::UbseNodeController::GetInstance().GetCurrentNodeId();
978- std::vector<ubse::mem::controller::UbseNumaMemoryDebtInfo> debtInfos{};
979- constexpr int retryTime = 10;
980- constexpr int retryIntervalSec = 2;
981- int remainRetry = retryTime;
982- auto ret = ubse::mem::controller::UbseGetNumaMemDebtInfoWithNode(curNodeId, debtInfos);
983- while (ret != UBSE_OK && remainRetry > 0) {
984- UBSE_LOG_WARN << "RecoverAllDebtInfoData retry, ret=" << ret << ", remainRetry=" << remainRetry;
985- sleep(retryIntervalSec);
986- debtInfos.clear();
987- ret = ubse::mem::controller::UbseGetNumaMemDebtInfoWithNode(curNodeId, debtInfos);
988- --remainRetry;
989- }
990- if (ret != UBSE_OK) {
991- UBSE_LOG_ERROR << "RecoverAllDebtInfoData failed after retry, ret=" << ret
992- << ", debtInfos.size=" << debtInfos.size();
993- }
994- UBSE_LOG_INFO << "UbseGetNumaMemDebtInfoWithNode success, total debtInfos=" << debtInfos.size();
995- 
996- uint32_t matchedCount = 0;
997- uint32_t recoveredCount = 0;
998- {
999- std::unique_lock<std::shared_mutex> lock(pidInfoMutex);
1000- UBSE_LOG_INFO << "current pidInfoMap size=" << pidInfoMap.size();
1001- for (const auto& debtInfo : debtInfos) {
1002- if (debtInfo.borrowNodeId != curNodeId) {
1003- continue;
1004- }
1005- if (!IsProcessMemDebt(debtInfo)) {
1006- continue;
1007- }
1008- ++matchedCount;
1009- UBSE_LOG_INFO << "recovering debt: name=" << debtInfo.name << ", size=" << debtInfo.size
1010- << ", remoteNumaId=" << debtInfo.remoteNumaId;
1011- ++recoveredCount;
1012- RecoverSingleDebtInfo(pidInfoMap, debtInfo, exceptionHandleExecutor);
1013- }
1014- }
1015- UBSE_LOG_INFO << "RecoverAllDebtInfoData matched=" << matchedCount << ", recovered=" << recoveredCount;
1016- if (matchedCount == 0 && !debtInfos.empty()) {
1017- UBSE_LOG_WARN << "no process_mem debts found among " << debtInfos.size() << " total debts";
1018- }
1019- isRecoverCompleted_ = true;
1020- UBSE_LOG_INFO << "RecoverAllDebtInfoData completed, isRecoverCompleted_=true";
1021-}
1022- 
1023-namespace {
1024-uint32_t QueryDebtInfoWithRetry(const std::string& nodeId,
1025- std::vector<ubse::mem::controller::UbseNumaMemoryDebtInfo>& debtInfos,
1026- const char* caller, int maxRetries, bool acceptPartial)
1027-{
1028- constexpr int retryIntervalSec = 2;
1029- int remainRetry = maxRetries;
1030- auto ret = ubse::mem::controller::UbseGetNumaMemDebtInfoWithNode(nodeId, debtInfos);
1031- auto shouldRetry = [acceptPartial](uint32_t r) {
1032- if (acceptPartial) {
1033- return r != UBSE_OK && r != UBSE_MEMCONTROLLER_ERROR_PAR_SUCCESS;
1034- }
1035- return r != UBSE_OK;
1036- };
1037- while (shouldRetry(ret) && remainRetry > 0) {
1038- UBSE_LOG_WARN << caller << ": query debts retry, node=" << nodeId << ", ret=" << ret
1039- << ", remainRetry=" << remainRetry;
1040- sleep(retryIntervalSec);
1041- debtInfos.clear();
1042- ret = ubse::mem::controller::UbseGetNumaMemDebtInfoWithNode(nodeId, debtInfos);
1043- --remainRetry;
1044- }
1045- return ret;
1046-}
1047-} // namespace
1048- 
1049-uint32_t ProcessMemPidInfoManager::HandleNodeFaultEvent(const std::string& lentNodeId)
1050-{
1051- UBSE_LOG_INFO << "HandleNodeFaultEvent: lentNodeId=" << lentNodeId;
1052- 
1053- std::vector<ubse::mem::controller::UbseNumaMemoryDebtInfo> debtInfos{};
1054- auto ret = QueryDebtInfoWithRetry(lentNodeId, debtInfos, "HandleNodeFaultEvent", 3, false);
1055- if (ret != UBSE_OK) {
1056- UBSE_LOG_ERROR << "HandleNodeFaultEvent: query debts failed after retry, ret=" << ret;
1057- return ret;
1058- }
1059- UBSE_LOG_INFO << "HandleNodeFaultEvent: query debts done, ret=" << ret << ", total=" << debtInfos.size();
1060- 
1061- // 筛选出本插件在该故障借出节点上的受影响的 PID
1062- std::unordered_set<pid_t> affectedPids;
1063- for (const auto& debtInfo : debtInfos) {
1064- if (debtInfo.lentNodeId != lentNodeId || !IsProcessMemDebt(debtInfo)) {
1065- continue;
1066- }
1067- def::ProcessMemUsrInfo usrInfo{};
1068- if (memcpy_s(&usrInfo, sizeof(usrInfo), debtInfo.usrInfo, sizeof(usrInfo)) != EOK) {
1069- UBSE_LOG_WARN << "HandleNodeFaultEvent: memcpy_s usrInfo failed for debt " << debtInfo.name;
1070- continue;
1071- }
1072- if (usrInfo.pid > 0) {
1073- affectedPids.insert(usrInfo.pid);
1074- }
1075- }
1076- 
1077- if (affectedPids.empty()) {
1078- UBSE_LOG_INFO << "HandleNodeFaultEvent: no affected PIDs for lentNodeId=" << lentNodeId;
1079- return UBSE_OK;
1080- }
1081- 
1082- // 标记受影响的 PID 为 FAULT 状态
1083- {
1084- std::unique_lock<std::shared_mutex> lock(pidInfoMutex);
1085- for (auto pid : affectedPids) {
1086- auto pidIt = pidInfoMap.find(pid);
1087- if (pidIt != pidInfoMap.end()) {
1088- pidIt->second.processStatus = def::ProcessStatus::FAULT;
1089- } else {
1090- UBSE_LOG_WARN << "HandleNodeFaultEvent: pid=" << pid << " not found in pidInfoMap";
1091- }
1092- }
1093- faultedLentNodes_[lentNodeId] = std::move(affectedPids);
1094- }
1095- return UBSE_OK;
1096-}
1097- 
1098-bool IsNodeRecovered(const std::string& lentNodeId)
1099-{
1100- // 条件1: 账本中已无该节点的 ProcessMem 债务
1101- std::vector<ubse::mem::controller::UbseNumaMemoryDebtInfo> debtInfos{};
1102- auto ret = QueryDebtInfoWithRetry(lentNodeId, debtInfos, "IsNodeRecovered", 2, true);
1103- if (ret == UBSE_OK) {
1104- bool hasDebt = false;
1105- for (const auto& debtInfo : debtInfos) {
1106- if (debtInfo.lentNodeId == lentNodeId && IsProcessMemDebt(debtInfo)) {
1107- hasDebt = true;
1108- break;
1109- }
1110- }
1111- if (!hasDebt) {
1112 return true;160 return true;
1113 }161 }
1114 }162 }
1115- 163+ return false;
1116- // 条件2: 节点集群状态已恢复为 WORKING
1117- auto nodeInfo = ubse::nodeController::UbseNodeController::GetInstance().GetNodeById(lentNodeId);
1118- return nodeInfo.clusterState == ubse::nodeController::UbseNodeClusterState::UBSE_NODE_WORKING;
1119}164}
1120 165 
1121-void RestoreFaultPids(const std::vector<std::string>& recoveredNodes,166+void ProcessMemPidInfoManager::CleanupStalePidConfigs()
1122- std::unordered_map<std::string, std::unordered_set<pid_t>>& faultedLentNodes,
1123- std::unordered_map<pid_t, def::ProcessMemPidInfo>& pidInfoMap)
1124{167{
1125- for (const auto& nodeId : recoveredNodes) {168+ std::vector<def::ProcessMemNewConfigInfo> snapshot;
1126- auto nodeIt = faultedLentNodes.find(nodeId);169+ {
1127- if (nodeIt == faultedLentNodes.end()) {170+ std::shared_lock<std::shared_mutex> lock(procMemConfigMutex_);
1128- continue;171+ snapshot = procMemConfigCache_;
172+ }
173+ 
174+ size_t removed = 0;
175+ for (const auto& cfg : snapshot) {
176+ if (CleanupStalePidConfig(cfg)) {
177+ ++removed;
1129 }178 }
1130- for (auto pid : nodeIt->second) {179+ }
1131- auto pidIt = pidInfoMap.find(pid);180+ 
1132- if (pidIt != pidInfoMap.end() && pidIt->second.processStatus == def::ProcessStatus::FAULT) {181+ std::vector<std::pair<pid_t, def::FossilPidConfigInfo>> fossils;
1133- pidIt->second.processStatus = def::ProcessStatus::IDLE;182+ ProcessMemPidConfigManager::GetAllFossilConfigs(fossils);
1134- UBSE_LOG_INFO << "CheckFaultNodesRecovery: pid=" << pid << " restored to IDLE";183+ size_t fossilRemoved = 0;
184+ for (const auto& [pid, fossil] : fossils) {
185+ if (CleanupStaleFossil(pid, fossil)) {
186+ ++fossilRemoved;
187+ }
188+ }
189+ 
190+ if (removed > 0 || fossilRemoved > 0) {
191+ RebuildManagedPidCache();
192+ }
193+}
194+ 
195+bool ProcessMemPidInfoManager::CleanupStalePidConfig(const def::ProcessMemNewConfigInfo& cfg)
196+{
197+ if (!cfg.isPid) {
198+ return false;
199+ }
200+ auto pidOpt = def::ParsePidFromIdentifier(cfg.identifier);
201+ if (!pidOpt.has_value()) {
202+ return false;
203+ }
204+ pid_t pid = pidOpt.value();
205+ if (IsRootPid(pid)) {
206+ (void)ProcessMemPidConfigManager::DeleteProcMemConfig(true, cfg.identifier);
207+ EraseConfigFromCache(true, cfg.identifier);
208+ UBSE_LOG_INFO << "[process_mem] init restore: pid=" << pid << " root process (not managed), config removed";
209+ return true;
210+ }
211+ auto curStartTime = ProcessMemPidConfigManager::GetExactStartTime(pid);
212+ if (curStartTime == 0 || static_cast<uint64_t>(curStartTime) != cfg.startTime) {
213+ (void)ProcessMemPidConfigManager::DeleteProcMemConfig(true, cfg.identifier);
214+ EraseConfigFromCache(true, cfg.identifier);
215+ UBSE_LOG_INFO << "[process_mem] init restore: pid=" << pid
216+ << " abandoned (startTime mismatch: persisted=" << cfg.startTime << " actual=" << curStartTime
217+ << "), config removed";
218+ return true;
219+ }
220+ return false;
221+}
222+ 
223+bool ProcessMemPidInfoManager::CleanupStaleFossil(pid_t pid, const def::FossilPidConfigInfo& fossil)
224+{
225+ if (IsRootPid(pid)) {
226+ (void)ProcessMemPidConfigManager::DeleteFossilConfig(pid);
227+ UBSE_LOG_INFO << "[process_mem] init restore: fossil pid=" << pid << " root process, config removed";
228+ return true;
229+ }
230+ auto curStartTime = ProcessMemPidConfigManager::GetExactStartTime(pid);
231+ if (curStartTime == 0 || static_cast<uint64_t>(curStartTime) != fossil.startTime) {
232+ (void)ProcessMemPidConfigManager::DeleteFossilConfig(pid);
233+ UBSE_LOG_INFO << "[process_mem] init restore: fossil pid=" << pid
234+ << " abandoned (startTime mismatch: persisted=" << fossil.startTime << " actual=" << curStartTime
235+ << "), config removed";
236+ return true;
237+ }
238+ return false;
239+}
240+ 
241+uint32_t ProcessMemPidInfoManager::ValidateProcMemTarget(const def::ProcessMemNewConfigInfo& config,
242+ uint64_t& outStartTime)
243+{
244+ static constexpr uint64_t kProcMemSizeMin = 128ULL * 1024 * 1024;
245+ static constexpr uint64_t kProcMemSizeMax = 32ULL * 1024 * 1024 * 1024;
246+ static constexpr size_t kProcNameMaxLen = 15;
247+ 
248+ if (config.maxMemory < kProcMemSizeMin || config.maxMemory > kProcMemSizeMax) {
249+ UBSE_LOG_ERROR << "SetProcMemConfig: maxMemory out of range [128M, 32G]: " << config.maxMemory;
250+ return UBSE_ERR_INVALID_ARG;
251+ }
252+ if (!std::isfinite(config.remoteRatio) || config.remoteRatio < 0.0 || config.remoteRatio > 1.0) {
253+ UBSE_LOG_ERROR << "SetProcMemConfig: remoteRatio out of range [0.0, 1.0]: " << config.remoteRatio;
254+ return UBSE_ERR_INVALID_ARG;
255+ }
256+ if (config.identifier.size() > kProcNameMaxLen) {
257+ UBSE_LOG_ERROR << "SetProcMemConfig: identifier too long (" << config.identifier.size() << " > "
258+ << kProcNameMaxLen << "): " << config.identifier;
259+ return UBSE_ERR_INVALID_ARG;
260+ }
261+ if (config.identifier.find_first_of("/\\\n\r\t") != std::string::npos) {
262+ UBSE_LOG_ERROR << "SetProcMemConfig: identifier contains illegal chars: " << config.identifier;
263+ return UBSE_ERR_INVALID_ARG;
264+ }
265+ if (config.isPid) {
266+ auto pidOpt = def::ParsePidFromIdentifier(config.identifier);
267+ if (!pidOpt.has_value()) {
268+ UBSE_LOG_ERROR << "SetProcMemConfig: invalid PID format: " << config.identifier;
269+ return UBSE_ERR_INVALID_ARG;
270+ }
271+ pid_t pid = pidOpt.value();
272+ auto startTime = ProcessMemPidConfigManager::GetExactStartTime(pid);
273+ if (startTime == 0) {
274+ UBSE_LOG_ERROR << "SetProcMemConfig: PID " << pid << " does not exist";
275+ return UBSE_ERR_NOT_EXIST;
276+ }
277+ 
278+ if (IsRootPid(pid)) {
279+ UBSE_LOG_ERROR << "SetProcMemConfig: PID " << pid
280+ << " is a root process, not allowed (filter_root_process=true)";
281+ return UBSE_ERR_INVALID_ARG;
282+ }
283+ 
284+ outStartTime = startTime;
285+ }
286+ return UBSE_OK;
287+}
288+ 
289+uint32_t ProcessMemPidInfoManager::SetProcMemConfig(const def::ProcessMemNewConfigInfo& config)
290+{
291+ def::ProcessMemNewConfigInfo mutableConfig = config;
292+ uint64_t startTime = 0;
293+ auto valRet = ValidateProcMemTarget(config, startTime);
294+ if (valRet != UBSE_OK) {
295+ return valRet;
296+ }
297+ if (config.isPid) {
298+ mutableConfig.startTime = startTime;
299+ }
300+ 
301+ std::string key = MakeProcMemKey(mutableConfig.isPid, mutableConfig.identifier);
302+ 
303+ auto persistRet = ProcessMemPidConfigManager::PersistProcMemConfig(mutableConfig);
304+ if (persistRet != UBSE_OK) {
305+ UBSE_LOG_ERROR << "SetProcMemConfig: persist failed for " << key
306+ << ", ret=" << ubse::log::FormatRetCode(persistRet);
307+ return UBSE_ERROR;
308+ }
309+ 
310+ bool updated = false;
311+ {
312+ std::unique_lock<std::shared_mutex> lock(procMemConfigMutex_);
313+ for (auto& existing : procMemConfigCache_) {
314+ if (existing.isPid == mutableConfig.isPid && existing.identifier == mutableConfig.identifier) {
315+ existing = mutableConfig;
316+ updated = true;
317+ UBSE_LOG_INFO << "SetProcMemConfig: updated config for " << key
318+ << ", maxMemory=" << mutableConfig.maxMemory
319+ << ", remoteRatio=" << mutableConfig.remoteRatio;
320+ break;
1135 }321 }
1136 }322 }
1137- UBSE_LOG_INFO << "CheckFaultNodesRecovery: fault node " << nodeId << " removed, " << nodeIt->second.size()323+ if (!updated) {
1138- << " PIDs recovered";324+ procMemConfigCache_.push_back(mutableConfig);
1139- faultedLentNodes.erase(nodeIt);325+ UBSE_LOG_INFO << "SetProcMemConfig: inserted new config for " << key
326+ << ", maxMemory=" << mutableConfig.maxMemory << ", remoteRatio=" << mutableConfig.remoteRatio;
327+ }
328+ }
329+ 
330+ RebuildManagedPidCache();
331+ return UBSE_OK;
332+}
333+ 
334+uint32_t ProcessMemPidInfoManager::RemoveProcMemConfig(bool isPid, const std::string& identifier)
335+{
336+ {
337+ std::shared_lock<std::shared_mutex> lock(procMemConfigMutex_);
338+ bool found = false;
339+ for (const auto& cfg : procMemConfigCache_) {
340+ if (cfg.isPid == isPid && cfg.identifier == identifier) {
341+ found = true;
342+ break;
343+ }
344+ }
345+ if (!found) {
346+ UBSE_LOG_INFO << "RemoveProcMemConfig: config not found for " << (isPid ? "pid=" : "name=") << identifier;
347+ return UBSE_ERR_NOT_EXIST;
348+ }
349+ }
350+ 
351+ // 持久层删除失败时保持缓存与磁盘一致, 不擦除缓存
352+ uint32_t deleteRet = isPid ? RemovePidConfigSideEffects(identifier) : RemoveNameConfigSideEffects(identifier);
353+ if (deleteRet != UBSE_OK) {
354+ UBSE_LOG_ERROR << "RemoveProcMemConfig: persistent delete failed for " << (isPid ? "pid=" : "name=")
355+ << identifier << ", keep cache entry, ret=" << ubse::log::FormatRetCode(deleteRet);
356+ return deleteRet;
357+ }
358+ 
359+ EraseConfigFromCache(isPid, identifier);
360+ 
361+ RebuildManagedPidCache();
362+ return UBSE_OK;
363+}
364+ 
365+uint32_t ProcessMemPidInfoManager::RemovePidConfigSideEffects(const std::string& identifier)
366+{
367+ auto pidOpt = def::ParsePidFromIdentifier(identifier);
368+ uint32_t ret = ProcessMemPidConfigManager::DeleteProcMemConfig(true, identifier);
369+ if (pidOpt.has_value() && ret == UBSE_OK) {
370+ ret = ProcessMemPidConfigManager::DeleteFossilConfig(pidOpt.value());
371+ }
372+ if (pidOpt.has_value() && ret == UBSE_OK) {
373+ (void)decision::ProcessMemPidDecision::GetInstance().EnqueuePidReturn(pidOpt.value(),
374+ decision::ReturnScene::EXITED);
375+ }
376+ return ret;
377+}
378+ 
379+uint32_t ProcessMemPidInfoManager::RemoveNameConfigSideEffects(const std::string& identifier)
380+{
381+ std::vector<std::pair<pid_t, def::FossilPidConfigInfo>> fossils;
382+ ProcessMemPidConfigManager::GetAllFossilConfigs(fossils);
383+ std::set<pid_t> affected;
384+ for (const auto& [pid, fossil] : fossils) {
385+ if (fossil.name == identifier) {
386+ affected.insert(pid);
387+ (void)ProcessMemPidConfigManager::DeleteFossilConfig(pid);
388+ }
389+ }
390+ for (const auto& pe : collect::FindPidsByName(identifier)) {
391+ affected.insert(pe.pid);
392+ }
393+ auto ret = ProcessMemPidConfigManager::DeleteProcMemConfig(false, identifier);
394+ if (ret != UBSE_OK) {
395+ return ret;
396+ }
397+ for (pid_t pid : affected) {
398+ if (HasExplicitPidConfig(pid)) {
399+ continue;
400+ }
401+ (void)decision::ProcessMemPidDecision::GetInstance().EnqueuePidReturn(pid, decision::ReturnScene::EXITED);
402+ }
403+ return UBSE_OK;
404+}
405+ 
406+void ProcessMemPidInfoManager::EraseConfigFromCache(bool isPid, const std::string& identifier)
407+{
408+ std::unique_lock<std::shared_mutex> lock(procMemConfigMutex_);
409+ for (auto it = procMemConfigCache_.begin(); it != procMemConfigCache_.end(); ++it) {
410+ if (it->isPid == isPid && it->identifier == identifier) {
411+ procMemConfigCache_.erase(it);
412+ UBSE_LOG_INFO << "RemoveProcMemConfig: removed config for " << (isPid ? "pid=" : "name=") << identifier;
413+ break;
414+ }
1140 }415 }
1141}416}
1142 417 
1143-void ProcessMemPidInfoManager::CheckFaultNodesRecovery()418+void ProcessMemPidInfoManager::GetAllProcMemConfigs(std::vector<def::ProcessMemNewConfigInfo>& configs) const
1144{419{
1145- // 无锁复制故障节点列表,避免在持有锁时调用 Ubse 接口420+ std::shared_lock<std::shared_mutex> lock(procMemConfigMutex_);
1146- std::unordered_map<std::string, std::unordered_set<pid_t>> faultedCopy;421+ configs = procMemConfigCache_;
422+}
423+ 
424+def::ProcessMemNewConfigInfo ProcessMemPidInfoManager::GetProcMemConfig(bool isPid, const std::string& identifier) const
425+{
426+ std::shared_lock<std::shared_mutex> lock(procMemConfigMutex_);
427+ for (const auto& cfg : procMemConfigCache_) {
428+ if (cfg.isPid == isPid && cfg.identifier == identifier) {
429+ return cfg;
430+ }
431+ }
432+ def::ProcessMemNewConfigInfo emptyCfg{};
433+ emptyCfg.maxMemory = 0;
434+ return emptyCfg;
435+}
436+ 
437+void ProcessMemPidInfoManager::RebuildManagedPidCache()
438+{
439+ std::vector<def::ProcessMemNewConfigInfo> configSnapshot;
1147 {440 {
1148- std::shared_lock<std::shared_mutex> lock(pidInfoMutex);441+ std::shared_lock<std::shared_mutex> configLock(procMemConfigMutex_);
1149- if (faultedLentNodes_.empty()) {442+ configSnapshot = procMemConfigCache_;
1150- return;
1151- }
1152- faultedCopy = faultedLentNodes_;
1153 }443 }
1154 444 
1155- // 逐个检查故障节点是否已恢复445+ std::unique_lock<std::shared_mutex> lock(managedPidCacheMutex_);
1156- std::vector<std::string> recoveredNodes;446+ std::map<pid_t, def::ManagedPidEntry> oldCache = std::move(managedPidCache_);
1157- for (const auto& [lentNodeId, pids] : faultedCopy) {447+ managedPidCache_.clear();
1158- if (pids.empty() || IsNodeRecovered(lentNodeId)) {448+ 
1159- recoveredNodes.push_back(lentNodeId);449+ RebuildMergePidConfigs(configSnapshot);
1160- }450+ std::vector<std::pair<pid_t, const def::ProcessMemNewConfigInfo*>> toFossilize;
451+ RebuildMergeNameConfigs(configSnapshot, toFossilize);
452+ 
453+ lock.unlock();
454+ for (const auto& [pid, cfg] : toFossilize) {
455+ (void)FossilizePidConfig(pid, cfg->identifier, cfg->maxMemory, cfg->remoteRatio, false);
1161 }456 }
1162 457 
1163- if (recoveredNodes.empty()) {458+ RebuildMergeFossils();
459+ std::unique_lock<std::shared_mutex> finalLock(managedPidCacheMutex_);
460+ for (auto& [pid, entry] : managedPidCache_) {
461+ auto it = oldCache.find(pid);
462+ if (it != oldCache.end()) {
463+ entry.borrow = std::move(it->second.borrow);
464+ entry.processStatus = it->second.processStatus;
465+ entry.vmRss = it->second.vmRss;
466+ entry.lastMigrateTime = it->second.lastMigrateTime;
467+ }
468+ }
469+ UBSE_LOG_INFO << "RebuildManagedPidCache: built " << managedPidCache_.size() << " entries";
470+}
471+ 
472+void ProcessMemPidInfoManager::RebuildMergePidConfigs(const std::vector<def::ProcessMemNewConfigInfo>& configSnapshot)
473+{
474+ for (const auto& cfg : configSnapshot) {
475+ if (!cfg.isPid) {
476+ continue;
477+ }
478+ auto pidOpt = def::ParsePidFromIdentifier(cfg.identifier);
479+ if (!pidOpt.has_value()) {
480+ continue;
481+ }
482+ pid_t pid = pidOpt.value();
483+ if (IsRootPid(pid)) {
484+ UBSE_LOG_DEBUG << "RebuildManagedPidCache: pid=" << pid << " skip (root process)";
485+ continue;
486+ }
487+ auto& entry = managedPidCache_[pid];
488+ entry.pid = pid;
489+ entry.sources |= static_cast<uint8_t>(def::ConfigSource::PID_CONFIG);
490+ entry.maxMemory = cfg.maxMemory;
491+ entry.remoteRatio = cfg.remoteRatio;
492+ }
493+}
494+ 
495+void ProcessMemPidInfoManager::RebuildMergeNameConfigs(
496+ const std::vector<def::ProcessMemNewConfigInfo>& configSnapshot,
497+ std::vector<std::pair<pid_t, const def::ProcessMemNewConfigInfo*>>& toFossilize)
498+{
499+ for (const auto& cfg : configSnapshot) {
500+ if (cfg.isPid) {
501+ continue;
502+ }
503+ auto matchedPids = collect::FindPidsByName(cfg.identifier);
504+ for (const auto& pe : matchedPids) {
505+ pid_t pid = pe.pid;
506+ if (IsRootPid(pid)) {
507+ continue;
508+ }
509+ auto& entry = managedPidCache_[pid];
510+ bool isNew = (entry.pid == 0 && entry.sources == 0);
511+ entry.pid = pid;
512+ entry.sources |= static_cast<uint8_t>(def::ConfigSource::NAME_CONFIG);
513+ entry.nameConfigName = cfg.identifier;
514+ if (!(entry.sources & static_cast<uint8_t>(def::ConfigSource::PID_CONFIG))) {
515+ entry.maxMemory = cfg.maxMemory;
516+ entry.remoteRatio = cfg.remoteRatio;
517+ }
518+ if (isNew) {
519+ toFossilize.emplace_back(pid, &cfg);
520+ }
521+ }
522+ }
523+}
524+ 
525+void ProcessMemPidInfoManager::RebuildMergeFossils()
526+{
527+ std::vector<std::pair<pid_t, def::FossilPidConfigInfo>> fossils;
528+ ProcessMemPidConfigManager::GetAllFossilConfigs(fossils);
529+ for (const auto& [pid, fossil] : fossils) {
530+ std::unique_lock<std::shared_mutex> cacheLock(managedPidCacheMutex_);
531+ if (managedPidCache_.find(pid) != managedPidCache_.end()) {
532+ continue;
533+ }
534+ if (IsRootPid(pid)) {
535+ cacheLock.unlock();
536+ (void)ProcessMemPidConfigManager::DeleteFossilConfig(pid);
537+ UBSE_LOG_INFO << "[process_mem] init restore: fossil pid=" << pid << " root process, fossil config removed";
538+ continue;
539+ }
540+ def::ManagedPidEntry entry{};
541+ entry.pid = pid;
542+ if (!fossil.name.empty()) {
543+ entry.sources = static_cast<uint8_t>(def::ConfigSource::NAME_CONFIG);
544+ entry.nameConfigName = fossil.name;
545+ } else {
546+ entry.sources = static_cast<uint8_t>(def::ConfigSource::PID_CONFIG);
547+ }
548+ entry.maxMemory = fossil.maxMemory;
549+ entry.remoteRatio = fossil.remoteRatio;
550+ managedPidCache_[pid] = entry;
551+ }
552+}
553+ 
554+void ProcessMemPidInfoManager::AddNameSourceToManagedPid(pid_t pid, const std::string& name, uint64_t maxMemory,
555+ double remoteRatio)
556+{
557+ if (IsRootPid(pid)) {
1164 return;558 return;
1165 }559 }
1166- 560+ bool isNew = false;
1167- // 恢复受影响的 PID 为 IDLE 状态
1168 {561 {
1169- std::unique_lock<std::shared_mutex> lock(pidInfoMutex);562+ std::shared_lock<std::shared_mutex> lock(managedPidCacheMutex_);
1170- RestoreFaultPids(recoveredNodes, faultedLentNodes_, pidInfoMap);563+ isNew = (managedPidCache_.find(pid) == managedPidCache_.end());
564+ }
565+ if (isNew) {
566+ (void)FossilizePidConfig(pid, name, maxMemory, remoteRatio, false);
567+ }
568+ 
569+ std::unique_lock<std::shared_mutex> lock(managedPidCacheMutex_);
570+ auto it = managedPidCache_.find(pid);
571+ if (it == managedPidCache_.end()) {
572+ def::ManagedPidEntry entry{};
573+ entry.pid = pid;
574+ entry.sources = static_cast<uint8_t>(def::ConfigSource::NAME_CONFIG);
575+ entry.nameConfigName = name;
576+ entry.maxMemory = maxMemory;
577+ entry.remoteRatio = remoteRatio;
578+ managedPidCache_[pid] = entry;
579+ } else {
580+ it->second.sources |= static_cast<uint8_t>(def::ConfigSource::NAME_CONFIG);
581+ it->second.nameConfigName = name;
582+ if (!(it->second.sources & static_cast<uint8_t>(def::ConfigSource::PID_CONFIG))) {
583+ it->second.maxMemory = maxMemory;
584+ it->second.remoteRatio = remoteRatio;
585+ }
1171 }586 }
1172}587}
1173 588 
1174-} // namespace process_mem::manager589+void ProcessMemPidInfoManager::AddChildSourceToManagedPid(pid_t childPid, pid_t parentPid, uint64_t maxMemory,
590+ double remoteRatio)
591+{
592+ if (IsRootPid(childPid)) {
593+ return;
594+ }
595+ std::unique_lock<std::shared_mutex> lock(managedPidCacheMutex_);
596+ auto it = managedPidCache_.find(childPid);
597+ if (it == managedPidCache_.end()) {
598+ lock.unlock();
599+ uint32_t fosRet = FossilizePidConfig(childPid, "", maxMemory, remoteRatio, true);
600+ lock.lock();
601+ if (managedPidCache_.find(childPid) != managedPidCache_.end()) {
602+ return; // 并发期间已被其他路径纳入管理,保留既有状态
603+ }
604+ def::ManagedPidEntry entry{};
605+ entry.pid = childPid;
606+ entry.parentPid = parentPid;
607+ entry.isChild = true;
608+ if (fosRet == UBSE_OK) {
609+ entry.sources = static_cast<uint8_t>(def::ConfigSource::PID_CONFIG);
610+ entry.maxMemory = maxMemory;
611+ entry.remoteRatio = remoteRatio;
612+ }
613+ managedPidCache_[childPid] = entry;
614+ } else {
615+ bool changed = (it->second.maxMemory != maxMemory || it->second.remoteRatio != remoteRatio);
616+ it->second.parentPid = parentPid;
617+ it->second.isChild = true;
618+ it->second.maxMemory = maxMemory;
619+ it->second.remoteRatio = remoteRatio;
620+ it->second.sources |= static_cast<uint8_t>(def::ConfigSource::PID_CONFIG);
621+ if (changed) {
622+ lock.unlock();
623+ FossilizePidConfig(childPid, "", maxMemory, remoteRatio, true);
624+ }
625+ }
626+}
627+ 
628+void ProcessMemPidInfoManager::RemoveManagedPidEntry(pid_t pid)
629+{
630+ std::unique_lock<std::shared_mutex> lock(managedPidCacheMutex_);
631+ managedPidCache_.erase(pid);
632+}
633+ 
634+void ProcessMemPidInfoManager::RemovePidSourceFromManagedPid(pid_t pid)
635+{
636+ std::string fallbackName;
637+ {
638+ std::unique_lock<std::shared_mutex> lock(managedPidCacheMutex_);
639+ auto it = managedPidCache_.find(pid);
640+ if (it == managedPidCache_.end()) {
641+ return;
642+ }
643+ 
644+ it->second.sources &= ~static_cast<uint8_t>(def::ConfigSource::PID_CONFIG);
645+ 
646+ if (!(it->second.sources & static_cast<uint8_t>(def::ConfigSource::NAME_CONFIG))) {
647+ managedPidCache_.erase(it);
648+ return;
649+ }
650+ fallbackName = it->second.nameConfigName;
651+ }
652+ 
653+ std::vector<def::ProcessMemNewConfigInfo> configSnapshot;
654+ {
655+ std::shared_lock<std::shared_mutex> configLock(procMemConfigMutex_);
656+ configSnapshot = procMemConfigCache_;
657+ }
658+ for (const auto& cfg : configSnapshot) {
659+ if (!cfg.isPid && cfg.identifier == fallbackName) {
660+ std::unique_lock<std::shared_mutex> lock(managedPidCacheMutex_);
661+ auto it = managedPidCache_.find(pid);
662+ if (it != managedPidCache_.end()) {
663+ it->second.maxMemory = cfg.maxMemory;
664+ it->second.remoteRatio = cfg.remoteRatio;
665+ }
666+ break;
667+ }
668+ }
669+}
670+ 
671+void ProcessMemPidInfoManager::UpdateManagedPidVmRssBatch(const collect::PidCollectInfoMap& collectInfo)
672+{
673+ std::vector<pid_t> reusedPids;
674+ {
675+ std::unique_lock<std::shared_mutex> lock(managedPidCacheMutex_);
676+ for (const auto& [pid, entry] : collectInfo.entries) {
677+ auto it = managedPidCache_.find(pid);
678+ if (it == managedPidCache_.end()) {
679+ continue;
680+ }
681+ it->second.vmRss = entry.vmRssKb * 1024;
682+ if (it->second.startTime == 0) {
683+ it->second.startTime = ProcessMemPidConfigManager::GetExactStartTime(pid);
684+ continue;
685+ }
686+ const auto& borrow = it->second.borrow;
687+ if (it->second.processStatus == def::ProcessStatus::IDLE && borrow.slots.empty() &&
688+ borrow.currentRemote == 0 && borrow.remoteNumaMigrated.empty()) {
689+ continue;
690+ }
691+ uint64_t curStart = ProcessMemPidConfigManager::GetExactStartTime(pid);
692+ if (curStart == 0 || curStart == it->second.startTime) {
693+ continue;
694+ }
695+ reusedPids.push_back(pid);
696+ UBSE_LOG_WARN << "[process_mem] pid=" << pid << " reused (startTime " << it->second.startTime << " -> "
697+ << curStart << "), reset stale borrow ledger for new process";
698+ it->second.borrow = def::BorrowState{};
699+ it->second.processStatus = def::ProcessStatus::IDLE;
700+ it->second.lastMigrateTime = {};
701+ it->second.startTime = curStart;
702+ }
703+ }
704+ for (pid_t pid : reusedPids) {
705+ (void)decision::ProcessMemPidDecision::GetInstance().EnqueuePidReturn(pid, decision::ReturnScene::EXITED);
706+ }
707+}
708+ 
709+void RebuildRemoteNumaMigrated(def::BorrowState& borrow)
710+{
711+ borrow.remoteNumaMigrated.clear();
712+ for (const auto& s : borrow.slots) {
713+ if (s.capacity == 0 || s.status != def::BorrowSlotStatus::COMPLETED ||
714+ s.returnStatus == def::ReturnStatus::RETURNING || s.remoteNumaId < 0) {
715+ continue;
716+ }
717+ borrow.remoteNumaMigrated[s.remoteNumaId] += s.migratedBytes;
718+ }
719+}
720+ 
721+void ProcessMemPidInfoManager::UpdateManagedPidBorrowState(pid_t pid, const def::BorrowState& borrow,
722+ def::ProcessStatus status)
723+{
724+ std::unique_lock<std::shared_mutex> lock(managedPidCacheMutex_);
725+ auto it = managedPidCache_.find(pid);
726+ if (it == managedPidCache_.end()) {
727+ return;
728+ }
729+ it->second.borrow = borrow;
730+ it->second.processStatus = status;
731+ RebuildRemoteNumaMigrated(it->second.borrow);
732+}
733+ 
734+void ProcessMemPidInfoManager::UpdateManagedPidBorrowStateAtomic(
735+ pid_t pid, const std::function<void(def::BorrowState&, def::ProcessStatus&)>& mutate)
736+{
737+ std::unique_lock<std::shared_mutex> lock(managedPidCacheMutex_);
738+ auto it = managedPidCache_.find(pid);
739+ if (it == managedPidCache_.end()) {
740+ return;
741+ }
742+ def::BorrowState updated = it->second.borrow;
743+ def::ProcessStatus newStatus = it->second.processStatus;
744+ mutate(updated, newStatus);
745+ it->second.borrow = updated;
746+ it->second.processStatus = newStatus;
747+ RebuildRemoteNumaMigrated(it->second.borrow);
748+}
749+ 
750+uint32_t ProcessMemPidInfoManager::UpdateManagedPidSlotReturned(pid_t pid, const std::string& debtId, uint64_t amount)
751+{
752+ std::unique_lock<std::shared_mutex> lock(managedPidCacheMutex_);
753+ auto it = managedPidCache_.find(pid);
754+ if (it == managedPidCache_.end()) {
755+ return UBSE_ERR_NOT_EXIST;
756+ }
757+ 
758+ auto& borrow = it->second.borrow;
759+ auto slotIt = std::find_if(borrow.slots.begin(), borrow.slots.end(),
760+ [&debtId](const def::BorrowSlot& s) { return s.debtId == debtId; });
761+ if (slotIt == borrow.slots.end()) {
762+ return UBSE_ERR_NOT_EXIST;
763+ }
764+ 
765+ borrow.currentRemote = (borrow.currentRemote >= amount) ? (borrow.currentRemote - amount) : 0;
766+ borrow.slots.erase(slotIt);
767+ 
768+ if (borrow.slots.empty()) {
769+ it->second.processStatus = def::ProcessStatus::IDLE;
770+ }
771+ return UBSE_OK;
772+}
773+ 
774+uint32_t ProcessMemPidInfoManager::UpdateManagedPidSlotReturnStatus(pid_t pid, const std::string& debtId,
775+ def::ReturnStatus status)
776+{
777+ std::unique_lock<std::shared_mutex> lock(managedPidCacheMutex_);
778+ auto it = managedPidCache_.find(pid);
779+ if (it == managedPidCache_.end()) {
780+ return UBSE_ERR_NOT_EXIST;
781+ }
782+ 
783+ auto slotIt = std::find_if(it->second.borrow.slots.begin(), it->second.borrow.slots.end(),
784+ [&debtId](const def::BorrowSlot& s) { return s.debtId == debtId; });
785+ if (slotIt == it->second.borrow.slots.end()) {
786+ return UBSE_ERR_NOT_EXIST;
787+ }
788+ 
789+ slotIt->returnStatus = status;
790+ return UBSE_OK;
791+}
792+ 
793+void ProcessMemPidInfoManager::ResetSlotByDebtName(const std::string& debtId)
794+{
795+ std::unique_lock<std::shared_mutex> lock(managedPidCacheMutex_);
796+ for (auto& [pid, entry] : managedPidCache_) {
797+ auto slotIt = std::find_if(entry.borrow.slots.begin(), entry.borrow.slots.end(),
798+ [&debtId](const def::BorrowSlot& s) { return s.debtId == debtId; });
799+ if (slotIt != entry.borrow.slots.end()) {
800+ slotIt->returnStatus = def::ReturnStatus::NONE;
801+ UBSE_LOG_INFO << "[process_mem] reset slot pid=" << pid << " debt_id=" << debtId
802+ << " (debt vanished, clear RETURNING)";
803+ }
804+ }
805+}
806+ 
807+void ProcessMemPidInfoManager::UpdateManagedPidLastMigrateTime(pid_t pid)
808+{
809+ std::unique_lock<std::shared_mutex> lock(managedPidCacheMutex_);
810+ auto it = managedPidCache_.find(pid);
811+ if (it == managedPidCache_.end()) {
812+ return;
813+ }
814+ it->second.lastMigrateTime = std::chrono::steady_clock::now();
815+}
816+ 
817+void ProcessMemPidInfoManager::UpdateManagedPidStatus(pid_t pid, def::ProcessStatus status)
818+{
819+ std::unique_lock<std::shared_mutex> lock(managedPidCacheMutex_);
820+ auto it = managedPidCache_.find(pid);
821+ if (it == managedPidCache_.end()) {
822+ return;
823+ }
824+ it->second.processStatus = status;
825+}
826+ 
827+std::map<pid_t, def::ManagedPidEntry> ProcessMemPidInfoManager::GetManagedPidCacheSnapshot() const
828+{
829+ std::shared_lock<std::shared_mutex> lock(managedPidCacheMutex_);
830+ return managedPidCache_;
831+}
832+ 
833+void ProcessMemPidInfoManager::RebalanceRemoteCheck()
834+{
835+ auto snapshot = GetManagedPidCacheSnapshot();
836+ size_t migratedBack = 0;
837+ 
838+ for (const auto& [pid, entry] : snapshot) {
839+ if (RebalancePidRemote(pid, entry)) {
840+ ++migratedBack;
841+ }
842+ }
843+ 
844+ if (migratedBack > 0) {
845+ UBSE_LOG_INFO << "[process_mem] rebalance done, back=" << migratedBack;
846+ }
847+}
848+ 
849+uint64_t RebalanceSlotTargetKb(const def::BorrowSlot& slot, uint64_t overage)
850+{
851+ if (slot.returnStatus == def::ReturnStatus::RETURNING || slot.remoteNumaId < 0) {
852+ return 0;
853+ }
854+ uint64_t newSize = (slot.migratedBytes > overage) ? (slot.migratedBytes - overage) : 0;
855+ constexpr uint64_t pageSizeKb = 4;
856+ return newSize / 1024 / pageSizeKb * pageSizeKb;
857+}
858+ 
859+uint64_t RebalanceAggregateTargets(const def::BorrowState& borrow, uint64_t overage,
860+ std::map<int, uint64_t>& numaTargets)
861+{
862+ numaTargets.clear();
863+ uint64_t settled = 0;
864+ uint64_t rest = overage;
865+ for (const auto& slot : borrow.slots) {
866+ if (slot.status != def::BorrowSlotStatus::COMPLETED || slot.returnStatus == def::ReturnStatus::RETURNING ||
867+ slot.remoteNumaId < 0) {
868+ continue;
869+ }
870+ uint64_t targetKb = RebalanceSlotTargetKb(slot, rest);
871+ uint64_t reduce = slot.migratedBytes - targetKb * 1024;
872+ if (reduce > rest) {
873+ reduce = rest;
874+ }
875+ settled += reduce;
876+ rest -= reduce;
877+ if (targetKb > 0) {
878+ numaTargets[slot.remoteNumaId] += targetKb;
879+ }
880+ }
881+ for (const auto& slot : borrow.slots) {
882+ if (slot.status == def::BorrowSlotStatus::BORROWING && slot.capacity > 0 &&
883+ slot.returnStatus != def::ReturnStatus::RETURNING && slot.remoteNumaId >= 0) {
884+ numaTargets[slot.remoteNumaId] += slot.capacity / 1024;
885+ }
886+ }
887+ return settled;
888+}
889+ 
890+uint64_t RebalanceApplyTargets(def::BorrowState& borrow, uint64_t overage)
891+{
892+ uint64_t settled = 0;
893+ uint64_t rest = overage;
894+ for (auto& slot : borrow.slots) {
895+ if (slot.status != def::BorrowSlotStatus::COMPLETED || slot.returnStatus == def::ReturnStatus::RETURNING ||
896+ slot.remoteNumaId < 0) {
897+ continue;
898+ }
899+ uint64_t targetKb = RebalanceSlotTargetKb(slot, rest);
900+ uint64_t reduce = slot.migratedBytes - targetKb * 1024;
901+ slot.migratedBytes = targetKb * 1024;
902+ if (reduce > rest) {
903+ reduce = rest;
904+ }
905+ settled += reduce;
906+ rest -= reduce;
907+ }
908+ return settled;
909+}
910+ 
911+bool ProcessMemPidInfoManager::RebalancePidRemote(pid_t pid, const def::ManagedPidEntry& entry)
912+{
913+ if (entry.remoteRatio <= 0.0 || entry.borrow.currentRemote == 0) {
914+ return false;
915+ }
916+ 
917+ uint64_t expectedRemote = static_cast<uint64_t>(entry.vmRss * entry.remoteRatio);
918+ if (entry.borrow.currentRemote <= expectedRemote) {
919+ return false;
920+ }
921+ uint64_t overage = (entry.borrow.currentRemote - expectedRemote) / 4096 * 4096;
922+ if (overage == 0) {
923+ return false;
924+ }
925+ 
926+ std::map<int, uint64_t> numaTargets;
927+ uint64_t settled = RebalanceAggregateTargets(entry.borrow, overage, numaTargets);
928+ if (numaTargets.empty() || settled == 0) {
929+ return false;
930+ }
931+ 
932+ int ret = DoMigrateOut(pid, numaTargets);
933+ if (ret != 0) {
934+ UBSE_LOG_ERROR << "[process_mem] rebalance pid=" << pid << " failed ret=" << ret
935+ << " expected_remote=" << expectedRemote;
936+ return false;
937+ }
938+ 
939+ uint64_t applied = 0;
940+ UpdateManagedPidBorrowStateAtomic(pid, [&](def::BorrowState& newBorrow, def::ProcessStatus& newStatus) {
941+ applied = RebalanceApplyTargets(newBorrow, overage);
942+ newBorrow.currentRemote = (newBorrow.currentRemote >= applied) ? (newBorrow.currentRemote - applied) : 0;
943+ });
944+ if (applied == 0) {
945+ return false;
946+ }
947+ UBSE_LOG_INFO << "[process_mem] rebalance pid=" << pid << " dir=back old_remote=" << entry.borrow.currentRemote
948+ << " applied_bytes=" << applied;
949+ return true;
950+}
951+ 
952+void ProcessMemPidInfoManager::VmRssCheckCallBack(const collect::PidCollectInfoMap& collectInfo)
953+{
954+ size_t totalEntries = collectInfo.entries.size();
955+ size_t rootFilteredCount = 0;
956+ 
957+ for (const auto& [pid, entry] : collectInfo.entries) {
958+ if (entry.rootFiltered) {
959+ ++rootFilteredCount;
960+ }
961+ }
962+ 
963+ UBSE_LOG_DEBUG << "[process_mem] VmRssCheckCallBack: received " << totalEntries
964+ << " entries, root_filtered=" << rootFilteredCount;
965+ 
966+ UpdateManagedPidVmRssBatch(collectInfo);
967+ 
968+ RebalanceRemoteCheck();
969+}
970+ 
971+} // namespace process_mem::manager
@@ -11,13 +11,11 @@
11 */11 */
12#ifndef PROCESS_MEM_PID_INFO_MANAGER_H12#ifndef PROCESS_MEM_PID_INFO_MANAGER_H
13#define PROCESS_MEM_PID_INFO_MANAGER_H13#define PROCESS_MEM_PID_INFO_MANAGER_H
14-#include <atomic>14+#include <functional>
15+#include <map>
15#include <shared_mutex>16#include <shared_mutex>
16-#include <unordered_map>
17-#include <unordered_set>
18#include <vector>17#include <vector>
19 18 
20-#include "ubse_thread_pool.h"
21#include "process_mem_pid_collect.h"19#include "process_mem_pid_collect.h"
22#include "process_mem_pid_manager_def.h"20#include "process_mem_pid_manager_def.h"
23namespace process_mem::manager {21namespace process_mem::manager {
@@ -33,52 +31,63 @@ public:
33 return instance;31 return instance;
34 }32 }
35 33 
36- uint32_t SetPidInfoMap(const def::ProcessMemPidInfo& pidInfo);34+ void VmRssCheckCallBack(const collect::PidCollectInfoMap& collectInfoMap);
37 35 
38- void GetAllPidInfo(std::vector<def::ProcessMemPidInfo>& pidInfos);36+ void RefreshProcMemConfigCache();
37+ uint32_t SetProcMemConfig(const def::ProcessMemNewConfigInfo& config);
38+ uint32_t RemoveProcMemConfig(bool isPid, const std::string& identifier);
39+ void GetAllProcMemConfigs(std::vector<def::ProcessMemNewConfigInfo>& configs) const;
40+ def::ProcessMemNewConfigInfo GetProcMemConfig(bool isPid, const std::string& identifier) const;
39 41 
40- def::ProcessMemPidInfo GetPidInfoMap(pid_t pid);42+ uint32_t FossilizePidConfig(pid_t pid, const std::string& name, uint64_t maxMemory, double remoteRatio, bool force);
41 43 
42- void TotalMemoryCheckCallBack(const collect::CollectInfoMap& collectInfoMap);44+ bool HasExplicitPidConfig(pid_t pid) const;
43 45 
44- uint32_t UnsetPidInfo(pid_t pid);46+ void CleanupStalePidConfigs();
45 47 
46- uint32_t PerPidMemoryCheckCallBack(pid_t pid, const std::unordered_map<uint32_t, size_t>& numaMemory);48+ void RebuildManagedPidCache();
47 49 
48- bool IsRecoverCompleted() const50+ void AddNameSourceToManagedPid(pid_t pid, const std::string& name, uint64_t maxMemory, double remoteRatio);
49- {51+ void AddChildSourceToManagedPid(pid_t childPid, pid_t parentPid, uint64_t maxMemory, double remoteRatio);
50- return isRecoverCompleted_;52+ void RemoveManagedPidEntry(pid_t pid);
51- }53+ void RemovePidSourceFromManagedPid(pid_t pid);
52 54 
53- void RecoverAllDebtInfoData();55+ void UpdateManagedPidVmRssBatch(const collect::PidCollectInfoMap& collectInfo);
54 56 
55- uint32_t HandleNodeFaultEvent(const std::string& lentNodeId);57+ void RebalanceRemoteCheck();
56 58 
57- void CheckFaultNodesRecovery();59+ void UpdateManagedPidBorrowState(pid_t pid, const def::BorrowState& borrow, def::ProcessStatus status);
60+ void UpdateManagedPidStatus(pid_t pid, def::ProcessStatus status);
61+ void UpdateManagedPidLastMigrateTime(pid_t pid);
58 62 
59- void RefreshBorrowInfo();63+ void UpdateManagedPidBorrowStateAtomic(pid_t pid,
64+ const std::function<void(def::BorrowState&, def::ProcessStatus&)>& mutate);
60 65 
61- uint32_t UpdatePidMemBorrowInfo(pid_t pid, const def::DebtInfo& debtInfo);66+ uint32_t UpdateManagedPidSlotReturned(pid_t pid, const std::string& debtId, uint64_t amount);
67+ uint32_t UpdateManagedPidSlotReturnStatus(pid_t pid, const std::string& debtId, def::ReturnStatus status);
68+ void ResetSlotByDebtName(const std::string& debtId);
62 69 
63- uint32_t UpdatePidMemBorrowInfo(pid_t pid, const ubse::mem::controller::UbseMemBorrower& borrower,70+ std::map<pid_t, def::ManagedPidEntry> GetManagedPidCacheSnapshot() const;
64- const def::DebtInfo& debtInfo);
65- 
66- void DeletePidMemBorrowInfo(pid_t pid, const std::string& borrowName);
67- 
68- void ResetPidMemBorrowInfo(pid_t pid);
69- 
70-public:
71- ubse::task_executor::UbseTaskExecutorPtr borrowExecutor{};
72- ubse::task_executor::UbseTaskExecutorPtr returnExecutor{};
73- ubse::task_executor::UbseTaskExecutorPtr exceptionHandleExecutor{};
74 71 
75private:72private:
76- std::atomic<bool> isRecoverCompleted_{false};73+ static uint32_t ValidateProcMemTarget(const def::ProcessMemNewConfigInfo& config, uint64_t& outStartTime);
77- std::shared_mutex pidInfoMutex;74+ 
78- // 异常线程池,用于处理异常任务75+ bool CleanupStalePidConfig(const def::ProcessMemNewConfigInfo& cfg);
79- std::unordered_map<pid_t, def::ProcessMemPidInfo> pidInfoMap;76+ bool CleanupStaleFossil(pid_t pid, const def::FossilPidConfigInfo& fossil);
80- // 故障节点映射: lentNodeId -> 受影响的 PID 集合77+ uint32_t RemovePidConfigSideEffects(const std::string& identifier);
81- std::unordered_map<std::string, std::unordered_set<pid_t>> faultedLentNodes_;78+ uint32_t RemoveNameConfigSideEffects(const std::string& identifier);
79+ void EraseConfigFromCache(bool isPid, const std::string& identifier);
80+ void RebuildMergePidConfigs(const std::vector<def::ProcessMemNewConfigInfo>& configSnapshot);
81+ void RebuildMergeNameConfigs(const std::vector<def::ProcessMemNewConfigInfo>& configSnapshot,
82+ std::vector<std::pair<pid_t, const def::ProcessMemNewConfigInfo*>>& toFossilize);
83+ void RebuildMergeFossils();
84+ bool RebalancePidRemote(pid_t pid, const def::ManagedPidEntry& entry);
85+ 
86+ mutable std::shared_mutex procMemConfigMutex_;
87+ std::vector<def::ProcessMemNewConfigInfo> procMemConfigCache_;
88+ 
89+ mutable std::shared_mutex managedPidCacheMutex_;
90+ std::map<pid_t, def::ManagedPidEntry> managedPidCache_;
82};91};
83} // namespace process_mem::manager92} // namespace process_mem::manager
84#endif93#endif
@@ -23,126 +23,180 @@
23#include "ubse_mem_controller.h"23#include "ubse_mem_controller.h"
24#include "ubse_serial_util.h"24#include "ubse_serial_util.h"
25namespace process_mem::def {25namespace process_mem::def {
26-const uint32_t PROCESS_MEM_MODULE_CODE = 123;26+struct ProcessMemNewConfigInfo {
27-const uint32_t PROCESS_MEM_SERVICE_ID = 11;27+ bool isPid{true};
28+ std::string identifier{};
29+ uint64_t maxMemory{0};
30+ double remoteRatio{0.0};
31+ uint64_t startTime{0};
32+ 
33+ inline ubse::common::def::UbseResult Serialize(ubse::serial::UbseSerialization& serializer) const
34+ {
35+ uint8_t isPidFlag = static_cast<uint8_t>(isPid ? 1 : 0);
36+ serializer << isPidFlag << identifier << maxMemory << remoteRatio << startTime;
37+ return serializer.Check() ? UBSE_OK : UBSE_ERROR;
38+ }
39+ 
40+ inline ubse::common::def::UbseResult Deserialize(ubse::serial::UbseDeSerialization& deserializer)
41+ {
42+ uint8_t isPidFlag = 0;
43+ deserializer >> isPidFlag >> identifier >> maxMemory >> remoteRatio >> startTime;
44+ isPid = (isPidFlag != 0);
45+ return deserializer.Check() ? UBSE_OK : UBSE_ERROR;
46+ }
47+};
48+ 
49+struct ProcessMemDisplayEntry {
50+ int32_t pid{0};
51+ std::string name{};
52+ uint64_t maxMemory{0};
53+ double remoteRatio{0.0};
54+ 
55+ inline ubse::common::def::UbseResult Serialize(ubse::serial::UbseSerialization& serializer) const
56+ {
57+ serializer << pid << name << maxMemory << remoteRatio;
58+ return serializer.Check() ? UBSE_OK : UBSE_ERROR;
59+ }
60+ 
61+ inline ubse::common::def::UbseResult Deserialize(ubse::serial::UbseDeSerialization& deserializer)
62+ {
63+ deserializer >> pid >> name >> maxMemory >> remoteRatio;
64+ return deserializer.Check() ? UBSE_OK : UBSE_ERROR;
65+ }
66+};
67+ 
68+inline const std::string PROC_MEM_PID_KEY_PREFIX = "procMem_pid_";
69+inline const std::string PROC_MEM_NAME_KEY_PREFIX = "procMem_name_";
70+inline const std::string PROC_MEM_FOSSIL_KEY_PREFIX = "procMem_fossil_";
71+ 
72+struct FossilPidConfigInfo {
73+ std::string name{};
74+ uint64_t maxMemory{0};
75+ double remoteRatio{0.0};
76+ uint64_t startTime{0};
77+ 
78+ inline ubse::common::def::UbseResult Serialize(ubse::serial::UbseSerialization& serializer) const
79+ {
80+ serializer << name << maxMemory << remoteRatio << startTime;
81+ return serializer.Check() ? UBSE_OK : UBSE_ERROR;
82+ }
83+ 
84+ inline ubse::common::def::UbseResult Deserialize(ubse::serial::UbseDeSerialization& deserializer)
85+ {
86+ deserializer >> name >> maxMemory >> remoteRatio >> startTime;
87+ return deserializer.Check() ? UBSE_OK : UBSE_ERROR;
88+ }
89+};
90+ 
91+enum class ConfigSource : uint8_t
92+{
93+ PID_CONFIG = 1 << 0,
94+ NAME_CONFIG = 1 << 1,
95+};
96+ 
97+enum class BorrowSlotStatus : uint8_t
98+{
99+ BORROWING,
100+ COMPLETED,
101+ FAILED,
102+};
103+ 
104+enum class AtomicMigrateResult : uint8_t
105+{
106+ kOk,
107+ kFail,
108+ kVanish,
109+};
110+ 
111+enum class ReturnStatus : uint8_t
112+{
113+ NONE,
114+ RETURNING,
115+ RETURNED,
116+};
117+ 
118+struct ReturnRequestItem {
119+ std::string name{};
120+ uint64_t size{0};
121+};
122+ 
123+struct BorrowSlot {
124+ uint64_t capacity{0};
125+ uint64_t migratedBytes{0};
126+ int32_t exportSlotId{-1};
127+ std::string debtId{};
128+ int srcNumaId{-1};
129+ int remoteNumaId{-1};
130+ std::chrono::steady_clock::time_point borrowTime{};
131+ BorrowSlotStatus status{BorrowSlotStatus::BORROWING};
132+ ReturnStatus returnStatus{ReturnStatus::NONE};
133+};
134+ 
135+struct BorrowState {
136+ uint64_t currentRemote{0};
137+ std::vector<BorrowSlot> slots;
138+ std::unordered_map<int, uint64_t> remoteNumaMigrated;
139+};
140+ 
141+struct BorrowCandidate {
142+ pid_t pid{0};
143+ bool isChild{false};
144+ bool hasActiveBorrow{false};
145+ std::chrono::steady_clock::time_point lastMigrateTime{};
146+ uint64_t actual{0};
147+ uint64_t maxMemory{0};
148+ double remoteRatio{0.0};
149+ uint64_t currentRemote{0};
150+ uint64_t canMigrate{0};
151+};
28 152 
29-/**
30- * @brief 进程运行时状态
31- */
32enum class ProcessStatus153enum class ProcessStatus
33{154{
34- IDLE, // 无借用155+ IDLE,
35- WAIT_BORROW, // 等待借用156+ BORROWING,
36- BORROWING, // 借用中157+ BORROWED,
37- REPAYING, // 归还中
38- INACTIVE, // 已停止
39- FAULT, // 故障隔离中(借出节点故障,暂停业务处理)
40};158};
41 159 
42-struct ProcessMemPidConfigInfo {160+struct ManagedPidEntry {
43- pid_t pid{};161+ pid_t pid{0};
44- int evictThreshold{};162+ pid_t parentPid{0};
45- int targetEvictThreshold{};163+ bool isChild{false};
46- int reclaimThreshold{};164+ uint8_t sources{0};
47- uint64_t expectedMemoryUsage{};165+ std::string nameConfigName{};
48- std::optional<uint64_t> srcNumaId; // redis进程存在的numaId,可选166+ uint64_t maxMemory{0};
49- inline ubse::common::def::UbseResult SerializeConfigInfo(ubse::serial::UbseSerialization& serializer) const167+ double remoteRatio{0.0};
50- {
51- serializer << pid << evictThreshold << targetEvictThreshold << reclaimThreshold << expectedMemoryUsage;
52- uint64_t hasSrcNuma = srcNumaId.has_value() ? 1 : 0;
53- serializer << hasSrcNuma;
54- if (srcNumaId.has_value()) {
55- serializer << srcNumaId.value();
56- }
57- return serializer.Check() ? UBSE_OK : UBSE_ERROR;
58- }
59 168 
60- inline ubse::common::def::UbseResult DeserializeConfigInfo(ubse::serial::UbseDeSerialization& deserializer)169+ uint64_t vmRss{0};
61- {170+ 
62- deserializer >> pid >> evictThreshold >> targetEvictThreshold >> reclaimThreshold >> expectedMemoryUsage;171+ // 进程启动时刻(/proc/<pid>/stat 第22字段),用于识别退出后 pid 被复用
63- uint64_t hasSrcNuma = 0;172+ uint64_t startTime{0};
64- deserializer >> hasSrcNuma;173+ 
65- if (hasSrcNuma != 0) {174+ std::chrono::steady_clock::time_point lastMigrateTime{};
66- uint64_t val = 0;175+ 
67- deserializer >> val;176+ ProcessStatus processStatus{ProcessStatus::IDLE};
68- srcNumaId = val;177+ BorrowState borrow{};
69- } else {
70- srcNumaId = std::nullopt;
71- }
72- return deserializer.Check() ? UBSE_OK : UBSE_ERROR;
73- }
74};178};
75 179 
76-enum class BorrowStatus180+inline std::optional<pid_t> ParsePidFromIdentifier(const std::string& identifier)
77{181{
78- COMPLETED,182+ if (identifier.empty() || identifier.find_first_not_of("0123456789") != std::string::npos) {
79- CREATING,183+ return std::nullopt;
80-};
81- 
82-struct DebtInfo {
83- std::chrono::steady_clock::time_point borrowStartTime{}; // 借用开始时间,自定义超时时间,借用中的账本需要关注
84- BorrowStatus status{BorrowStatus::COMPLETED};
85- ubse::mem::controller::UbseMemNumaDesc numaDesc{};
86-};
87- 
88-constexpr uint16_t INVALID_REMOTE_NUMA = 0;
89- 
90-struct BorrowInfo {
91- int32_t remoteNumaId{-1};
92- int32_t exportSlotId{-1}; // 导出的节点Id
93- int32_t importSocketId{-1}; // 导入的socketId
94- std::unordered_map<std::string, DebtInfo> debtInfos; // key-value: name-debtInfo
95-};
96- 
97-struct ProcessMemPidInfo {
98- pid_t ppid{0}; // 父进程 ID(0 表示无父进程)
99- long startTime{0}; // 进程启动时间
100- 
101- // ========== 配置信息(持久化) ==========
102- ProcessMemPidConfigInfo configInfo{}; // 配置信息
103- 
104- // ========== 运行时状态(不持久化) ==========
105- ProcessStatus processStatus{ProcessStatus::IDLE}; // 进程状态
106- 
107- // ========== 内存借用信息(持久化于ubse) ==========
108- BorrowInfo memBorrowInfo; // 借用内存的信息
109- 
110- // ========== 子进程信息 (不持久化)==========
111- // std::unordered_map<pid_t, ProcessMemPidInfo> childrenInfo; // 子进程信息映射
112- 
113- inline ubse::common::def::UbseResult SerializePidInfo(ubse::serial::UbseSerialization& serializer) const
114- {
115- auto ret = configInfo.SerializeConfigInfo(serializer);
116- if (ret != UBSE_OK) {
117- return ret;
118- }
119- serializer << startTime;
120- return serializer.Check() ? UBSE_OK : UBSE_ERROR;
121 }184 }
122- 185+ try {
123- inline ubse::common::def::UbseResult DeserializePidInfo(ubse::serial::UbseDeSerialization& deserializer)186+ pid_t pid = static_cast<pid_t>(std::stoi(identifier));
124- {187+ if (pid > 0) {
125- auto ret = configInfo.DeserializeConfigInfo(deserializer);188+ return pid;
126- if (ret != UBSE_OK) {
127- return ret;
128 }189 }
129- deserializer >> startTime;190+ } catch (...) {
130- return deserializer.Check() ? UBSE_OK : UBSE_ERROR;
131 }191 }
132-};192+ return std::nullopt;
133- 193+}
134-struct PidCollectInfo {
135- std::unordered_map<uint32_t, size_t> numaMemDistribution{}; // 每个numa上内存分布单位为 byte.
136- std::vector<pid_t> childrenInfo{}; // 子进程信息
137-};
138 194 
139enum class UsrInfoPluginType : uint32_t195enum class UsrInfoPluginType : uint32_t
140{196{
141 PROCESS_MEM = 123,197 PROCESS_MEM = 123,
142};198};
143 199 
144-inline const std::string PROCESS_MEM_NAME_PREFIX = "ProcessMem_";
145- 
146struct ProcessMemUsrInfo {200struct ProcessMemUsrInfo {
147 UsrInfoPluginType pluginId{UsrInfoPluginType::PROCESS_MEM};201 UsrInfoPluginType pluginId{UsrInfoPluginType::PROCESS_MEM};
148 int32_t pid{};202 int32_t pid{};
@@ -23,4 +23,4 @@ void UbsePluginDeInit()
23{23{
24 process_mem::pid::bridge::ProcessMemPidBridge::UnInit();24 process_mem::pid::bridge::ProcessMemPidBridge::UnInit();
25 return;25 return;
26-}26+}
@@ -17,17 +17,8 @@
17extern "C" {17extern "C" {
18#endif18#endif
19 19 
20-/**
21- * @brief Plugin initialization function; initializes the Ubse plugin
22- * @param modCode Plugin module code, used to distinguish different plugin instances
23- * @return Initialization result, 0 for success, non-zero for error
24- */
25uint32_t UbsePluginInit(uint16_t modCode);20uint32_t UbsePluginInit(uint16_t modCode);
26 21 
27-/**
28- * @brief Plugin deinitialization function;
29- * @return void
30- */
31void UbsePluginDeInit(void);22void UbsePluginDeInit(void);
32 23 
33#ifdef __cplusplus24#ifdef __cplusplus
@@ -14,9 +14,8 @@
14#define UBSE_CLI_CONSTANT_H14#define UBSE_CLI_CONSTANT_H
15#include <string>15#include <string>
16 16 
17-// Store constants unrelated to the structure.
18namespace constant {17namespace constant {
19-constexpr uint32_t REQUEST_BUFFER_CAPACITY = 8; // Default non-empty request size.18+constexpr uint32_t REQUEST_BUFFER_CAPACITY = 8;
20}19}
21 20 
22namespace systemd::error {21namespace systemd::error {
@@ -45,4 +44,4 @@ namespace node::error {
45const std::string NODE_ATTRIBUTE_EMPTY = "ERROR: Node attribute information is empty.";44const std::string NODE_ATTRIBUTE_EMPTY = "ERROR: Node attribute information is empty.";
46}45}
47 46 
48-#endif // UBSE_CLI_CONSTANT_H47+#endif
@@ -12,149 +12,215 @@
12 12 
13#include "ubse_cli_mem_pid.h"13#include "ubse_cli_mem_pid.h"
14 14 
15+#include <functional>
16+#include <iomanip>
17+#include <sstream>
15#include <string>18#include <string>
16#include <vector>19#include <vector>
17 20 
18#include "ubse_cli_buffer_guard.h"21#include "ubse_cli_buffer_guard.h"
19#include "ubse_cli_constant.h"22#include "ubse_cli_constant.h"
23+#include "ubse_cli_mem_struct.h"
20#include "ubse_cli_reg.h"24#include "ubse_cli_reg.h"
21#include "ubse_error.h"25#include "ubse_error.h"
22#include "ubse_ipc_client.h"26#include "ubse_ipc_client.h"
23#include "ubse_ipc_common.h"27#include "ubse_ipc_common.h"
28+#include "ubse_logger.h"
24#include "ubse_pointer_process.h"29#include "ubse_pointer_process.h"
25#include "ubse_serial_util.h"30#include "ubse_serial_util.h"
26 31 
32+UBSE_DEFINE_THIS_MODULE("ubse_cli");
33+ 
27namespace ubse::cli::reg {34namespace ubse::cli::reg {
28using namespace ubse::cli::framework;35using namespace ubse::cli::framework;
29using namespace ubse::serial;36using namespace ubse::serial;
30 37 
31namespace {38namespace {
32constexpr uint16_t PID_MODULE_CODE = UBSE_MEM;39constexpr uint16_t PID_MODULE_CODE = UBSE_MEM;
33-constexpr uint16_t PID_SET_THRESHOLD_OP = UBSE_MEM_CLI_PID_SET_THRESHOLD;
34-constexpr uint16_t PID_PRINT_OP = UBSE_MEM_CLI_PRINT_PID_INFO;
35-constexpr uint16_t PID_UNSET_OP = UBSE_MEM_CLI_PID_UNSET;
36 40 
37-bool PidInfoDeSerialization(UbseDeSerialization& in, std::vector<process_mem::def::ProcessMemPidInfo>& data)41+std::shared_ptr<UbseCliResultEcho> HandleSimpleIpcResponse(ubse_api_buffer_t& resBuffer, uint32_t invokeRet,
42+ const char* successMsg)
38{43{
39- size_t dataSize{};44+ UbseCliBufferGuard ubseCliBufferGuard(resBuffer);
40- in >> dataSize;45+ if (invokeRet != UBSE_OK) {
41- if (!in.Check()) {46+ UBSE_LOG_ERROR << "IPC invoke failed, ret=" << invokeRet;
42- return false;47+ return UbseCliRegModule::UbseCliStringPromptReply(GetErrorMessage(invokeRet));
43 }48 }
44- for (size_t i = 0; i < dataSize; ++i) {49+ UbseDeSerialization deserial{resBuffer.buffer, resBuffer.length};
45- process_mem::def::ProcessMemPidInfo tmpInfo{};50+ int successCode = 0;
46- auto ret = tmpInfo.configInfo.DeserializeConfigInfo(in);51+ std::string errMsg;
47- if (ret != UBSE_OK) {52+ deserial >> successCode >> errMsg;
48- return false;53+ if (deserial.Check() && successCode == 1) {
49- }54+ return UbseCliRegModule::UbseCliStringPromptReply(successMsg);
50- data.push_back(tmpInfo);
51 }55 }
52- return true;56+ if (!deserial.Check()) {
57+ return UbseCliRegModule::UbseCliStringPromptReply("ERROR: Invalid response from daemon.");
58+ }
59+ UBSE_LOG_ERROR << "Daemon error response: successCode=" << successCode << ", errMsg=" << errMsg;
60+ return UbseCliRegModule::UbseCliStringPromptReply("ERROR: " + errMsg);
53}61}
54 62 
55-std::shared_ptr<UbseCliResultEcho> BuildPidInfoTable(63+std::shared_ptr<UbseCliResultEcho> HandleStructIpcResponse(ubse_api_buffer_t& resBuffer, uint32_t invokeRet,
56- const std::vector<process_mem::def::ProcessMemPidInfo>& pidInfoDetail)64+ std::function<std::string()> formatSuccess)
57{65{
58- UbseCliResBuilder variableCellBuilder(UBSE_CLI_NUM_6, UBSE_CLI_NUM_30);66+ UbseCliBufferGuard ubseCliBufferGuard(resBuffer);
59- size_t row = variableCellBuilder.UbseCliAddRow();67+ if (invokeRet != UBSE_OK) {
60- variableCellBuilder.UbseCliAddlineSeparate(row);68+ UBSE_LOG_ERROR << "IPC invoke failed, ret=" << invokeRet;
61- variableCellBuilder.UbseCliSetCellData(row, UBSE_CLI_NUM_1, "pid");69+ return UbseCliRegModule::UbseCliStringPromptReply(GetErrorMessage(invokeRet));
62- variableCellBuilder.UbseCliSetCellData(row, UBSE_CLI_NUM_2, "evictThreshold");
63- variableCellBuilder.UbseCliSetCellData(row, UBSE_CLI_NUM_3, "targetEvictThreshold");
64- variableCellBuilder.UbseCliSetCellData(row, UBSE_CLI_NUM_4, "reclaimThreshold");
65- variableCellBuilder.UbseCliSetCellData(row, UBSE_CLI_NUM_5, "totalMemoryUsage");
66- variableCellBuilder.UbseCliSetCellData(row, UBSE_CLI_NUM_6, "srcNuma");
67- variableCellBuilder.UbseCliAddBottomlineSeparate();
68- for (const auto& pidInfo : pidInfoDetail) {
69- row = variableCellBuilder.UbseCliAddRow();
70- variableCellBuilder.UbseCliSetCellData(row, UBSE_CLI_NUM_1, std::to_string(pidInfo.configInfo.pid));
71- variableCellBuilder.UbseCliSetCellData(row, UBSE_CLI_NUM_2, std::to_string(pidInfo.configInfo.evictThreshold));
72- variableCellBuilder.UbseCliSetCellData(row, UBSE_CLI_NUM_3,
73- std::to_string(pidInfo.configInfo.targetEvictThreshold));
74- variableCellBuilder.UbseCliSetCellData(row, UBSE_CLI_NUM_4,
75- std::to_string(pidInfo.configInfo.reclaimThreshold));
76- variableCellBuilder.UbseCliSetCellData(row, UBSE_CLI_NUM_5,
77- std::to_string(pidInfo.configInfo.expectedMemoryUsage));
78- variableCellBuilder.UbseCliSetCellData(
79- row, UBSE_CLI_NUM_6,
80- pidInfo.configInfo.srcNumaId.has_value() ? std::to_string(pidInfo.configInfo.srcNumaId.value()) : "N/A");
81 }70 }
82- variableCellBuilder.UbseCliAddBottomlineSeparate();71+ UbseDeSerialization deserial{resBuffer.buffer, resBuffer.length};
83- return UbseCliRegModule::UbseCliVariableCelReply(variableCellBuilder.UbseCliVariableCellBuild());72+ int successCode = 0;
73+ std::string errMsg;
74+ deserial >> successCode >> errMsg;
75+ if (deserial.Check() && successCode == 1) {
76+ return UbseCliRegModule::UbseCliStringPromptReply(formatSuccess());
77+ }
78+ if (!deserial.Check()) {
79+ return UbseCliRegModule::UbseCliStringPromptReply("ERROR: Invalid response from daemon.");
80+ }
81+ UBSE_LOG_ERROR << "Daemon error response: successCode=" << successCode << ", errMsg=" << errMsg;
82+ return UbseCliRegModule::UbseCliStringPromptReply("ERROR: " + errMsg);
84}83}
85-} // anonymous namespace84+ 
85+static constexpr uint64_t kBytesPerKiB = 1024ULL;
86+static constexpr uint64_t kBytesPerMiB = kBytesPerKiB * kBytesPerKiB;
87+static constexpr uint64_t kBytesPerGiB = kBytesPerKiB * kBytesPerKiB * kBytesPerKiB;
88+ 
89+static std::string FormatMaxMemoryHuman(uint64_t bytes)
90+{
91+ if (bytes >= kBytesPerGiB && bytes % kBytesPerGiB == 0) {
92+ return std::to_string(bytes / kBytesPerGiB) + "G";
93+ }
94+ if (bytes >= kBytesPerMiB && bytes % kBytesPerMiB == 0) {
95+ return std::to_string(bytes / kBytesPerMiB) + "M";
96+ }
97+ if (bytes >= kBytesPerKiB && bytes % kBytesPerKiB == 0) {
98+ return std::to_string(bytes / kBytesPerKiB) + "K";
99+ }
100+ return std::to_string(bytes);
101+}
102+ 
103+static std::string FormatRatioHuman(double ratio)
104+{
105+ std::ostringstream oss;
106+ oss << std::fixed << std::setprecision(2) << ratio;
107+ return oss.str();
108+}
109+ 
110+std::shared_ptr<UbseCliResultEcho> BuildProcessMemConfigTable(
111+ const std::vector<process_mem::def::ProcessMemDisplayEntry>& entries)
112+{
113+ if (entries.empty()) {
114+ return UbseCliRegModule::UbseCliStringPromptReply("No process is currently managed by process_mem");
115+ }
116+ constexpr size_t COL_COUNT = 4;
117+ constexpr size_t COL_WIDTH = 30;
118+ UbseCliResBuilder builder(COL_COUNT, COL_WIDTH);
119+ size_t row = builder.UbseCliAddRow();
120+ builder.UbseCliAddlineSeparate(row);
121+ builder.UbseCliSetCellData(row, 1, "PID");
122+ builder.UbseCliSetCellData(row, 2, "Name");
123+ builder.UbseCliSetCellData(row, 3, "Size");
124+ builder.UbseCliSetCellData(row, 4, "RemoteRatio");
125+ builder.UbseCliAddBottomlineSeparate();
126+ 
127+ for (const auto& entry : entries) {
128+ row = builder.UbseCliAddRow();
129+ builder.UbseCliSetCellData(row, 1, entry.pid > 0 ? std::to_string(entry.pid) : "N/A");
130+ builder.UbseCliSetCellData(row, 2, entry.name);
131+ builder.UbseCliSetCellData(row, 3, FormatMaxMemoryHuman(entry.maxMemory));
132+ builder.UbseCliSetCellData(row, 4, FormatRatioHuman(entry.remoteRatio));
133+ }
134+ builder.UbseCliAddBottomlineSeparate();
135+ return UbseCliRegModule::UbseCliVariableCelReply(builder.UbseCliVariableCellBuild());
136+}
137+ 
138+} // namespace
86 139 
87class UbseCliMemPid::UbseCliMemPidImpl {140class UbseCliMemPid::UbseCliMemPidImpl {
88public:141public:
89- std::shared_ptr<framework::UbseCliResultEcho> UbseCliSetPidThresholdImpl(142+ std::shared_ptr<framework::UbseCliResultEcho> UbseCliSetProcessMemImpl(
90- const process_mem::def::ProcessMemPidInfo& pidInfo)143+ const process_mem::def::ProcessMemNewConfigInfo& newConfig)
91 {144 {
92 UbseSerialization serializer;145 UbseSerialization serializer;
93- auto ret = pidInfo.configInfo.SerializeConfigInfo(serializer);146+ auto ret = newConfig.Serialize(serializer);
94 if (ret != UBSE_OK) {147 if (ret != UBSE_OK) {
95 return UbseCliRegModule::UbseCliStringPromptReply(data::error::REQUEST_INFO_SER_FAILED);148 return UbseCliRegModule::UbseCliStringPromptReply(data::error::REQUEST_INFO_SER_FAILED);
96 }149 }
97 ubse_api_buffer_t reqBuffer{serializer.GetBuffer(), static_cast<uint32_t>(serializer.GetLength())};150 ubse_api_buffer_t reqBuffer{serializer.GetBuffer(), static_cast<uint32_t>(serializer.GetLength())};
98 ubse_api_buffer_t resBuffer{};151 ubse_api_buffer_t resBuffer{};
99- UbseCliWaitIndicator waitIndicator("Setting pidInfo");152+ UbseCliWaitIndicator waitIndicator("Setting process-mem config");
100- ret = ubse_invoke_call(PID_MODULE_CODE, PID_SET_THRESHOLD_OP, &reqBuffer, &resBuffer);153+ ret = ubse_invoke_call(PID_MODULE_CODE, UBSE_MEM_CLI_PROC_MEM_SET, &reqBuffer, &resBuffer);
101- UbseCliBufferGuard ubseCliBufferGuard(resBuffer);154+ return HandleStructIpcResponse(resBuffer, ret, [&newConfig]() {
102- if (ret != UBSE_OK) {155+ std::ostringstream echo;
103- return UbseCliRegModule::UbseCliStringPromptReply(156+ if (newConfig.isPid) {
104- std::string("ERROR: Internal error with error code " + std::to_string(ret)));157+ echo << "PID: " << newConfig.identifier << "\n";
105- }158+ } else {
106- UbseDeSerialization deserial{resBuffer.buffer, resBuffer.length};159+ echo << "Name: " << newConfig.identifier << "\n";
107- int successCode = 0;160+ }
108- std::string errMsg;161+ echo << "Size: " << FormatMaxMemoryHuman(newConfig.maxMemory) << "\n";
109- deserial >> successCode >> errMsg;162+ echo << "RemoteRatio: " << std::fixed << std::setprecision(2) << newConfig.remoteRatio << "\n";
110- if (deserial.Check() && successCode == 1) {163+ echo << "Set process-mem config successfully";
111- return UbseCliRegModule::UbseCliStringPromptReply("Set successfully");164+ return echo.str();
112- }165+ });
113- return UbseCliRegModule::UbseCliStringPromptReply("ERROR: " + errMsg);
114 }166 }
115 167 
116- std::shared_ptr<framework::UbseCliResultEcho> UbseCliPrintPidInfoImpl()168+ std::shared_ptr<framework::UbseCliResultEcho> UbseCliRemoveProcessMemImpl(bool isPid, const std::string& identifier)
169+ {
170+ process_mem::def::ProcessMemNewConfigInfo newConfig{};
171+ newConfig.isPid = isPid;
172+ newConfig.identifier = identifier;
173+ UbseSerialization serializer;
174+ auto serRet = newConfig.Serialize(serializer);
175+ if (serRet != UBSE_OK || !serializer.Check()) {
176+ return UbseCliRegModule::UbseCliStringPromptReply(data::error::REQUEST_INFO_SER_FAILED);
177+ }
178+ ubse_api_buffer_t reqBuffer{serializer.GetBuffer(), static_cast<uint32_t>(serializer.GetLength())};
179+ ubse_api_buffer_t resBuffer{};
180+ uint32_t ret = ubse_invoke_call(PID_MODULE_CODE, UBSE_MEM_CLI_PROC_MEM_REMOVE, &reqBuffer, &resBuffer);
181+ std::string successMsg = isPid ? ("pid " + identifier + " removed") : ("name '" + identifier + "' removed");
182+ return HandleSimpleIpcResponse(resBuffer, ret, successMsg.c_str());
183+ }
184+ 
185+ std::shared_ptr<framework::UbseCliResultEcho> UbseCliDisplayProcessMemImpl(uint32_t opcode)
117 {186 {
118 UbseSerialization serial;187 UbseSerialization serial;
119 ubse_api_buffer_t reqBuffer{serial.GetBuffer(), static_cast<uint32_t>(serial.GetLength())};188 ubse_api_buffer_t reqBuffer{serial.GetBuffer(), static_cast<uint32_t>(serial.GetLength())};
120 ubse_api_buffer_t resBuffer{};189 ubse_api_buffer_t resBuffer{};
121- uint32_t ret = ubse_invoke_call(PID_MODULE_CODE, PID_PRINT_OP, &reqBuffer, &resBuffer);190+ uint32_t ret = ubse_invoke_call(PID_MODULE_CODE, opcode, &reqBuffer, &resBuffer);
122 UbseCliBufferGuard ubseCliBufferGuard(resBuffer);191 UbseCliBufferGuard ubseCliBufferGuard(resBuffer);
123 if (ret != UBSE_OK) {192 if (ret != UBSE_OK) {
124- return UbseCliRegModule::UbseCliStringPromptReply(193+ UBSE_LOG_ERROR << "DisplayProcessMem IPC invoke failed, ret=" << ret;
125- std::string("ERROR: Internal error with error code " + std::to_string(ret)));194+ return UbseCliRegModule::UbseCliStringPromptReply(GetErrorMessage(ret));
126 }195 }
127- UbseDeSerialization derial{resBuffer.buffer, resBuffer.length};196+ 
128- std::vector<process_mem::def::ProcessMemPidInfo> pidInfo{};197+ UbseDeSerialization deserial{resBuffer.buffer, resBuffer.length};
129- if (!PidInfoDeSerialization(derial, pidInfo)) {198+ size_t count = 0;
130- return UbseCliRegModule::UbseCliStringPromptReply("ERROR");199+ deserial >> count;
200+ if (!deserial.Check()) {
201+ return UbseCliRegModule::UbseCliStringPromptReply("ERROR: Deserialization failed.");
131 }202 }
132- if (pidInfo.empty()) {203+ 
133- return UbseCliRegModule::UbseCliStringPromptReply("No process is currently managed by process_mem");204+ std::vector<process_mem::def::ProcessMemDisplayEntry> entries;
205+ for (size_t i = 0; i < count; ++i) {
206+ process_mem::def::ProcessMemDisplayEntry entry;
207+ if (entry.Deserialize(deserial) != UBSE_OK) {
208+ return UbseCliRegModule::UbseCliStringPromptReply("ERROR: Deserialization failed.");
209+ }
210+ entries.push_back(std::move(entry));
134 }211 }
135- return BuildPidInfoTable(pidInfo);212+ 
213+ return BuildProcessMemConfigTable(entries);
136 }214 }
137 215 
138- std::shared_ptr<framework::UbseCliResultEcho> UbseCliUnsetPidImpl(pid_t pid)216+ std::shared_ptr<framework::UbseCliResultEcho> UbseCliDisplayProcessMemConfigImpl()
139 {217 {
140- UbseSerialization serial;218+ return UbseCliDisplayProcessMemImpl(UBSE_MEM_CLI_PROC_MEM_DISPLAY_CONFIG);
141- serial << pid;219+ }
142- ubse_api_buffer_t reqBuffer{serial.GetBuffer(), static_cast<uint32_t>(serial.GetLength())};220+ 
143- ubse_api_buffer_t resBuffer{};221+ std::shared_ptr<framework::UbseCliResultEcho> UbseCliDisplayProcessMemDetailImpl()
144- uint32_t ret = ubse_invoke_call(PID_MODULE_CODE, PID_UNSET_OP, &reqBuffer, &resBuffer);222+ {
145- UbseCliBufferGuard ubseCliBufferGuard(resBuffer);223+ return UbseCliDisplayProcessMemImpl(UBSE_MEM_CLI_PROC_MEM_DISPLAY_DETAIL);
146- if (ret != UBSE_OK) {
147- return UbseCliRegModule::UbseCliStringPromptReply(
148- std::string("ERROR: Internal error with error code " + std::to_string(ret)));
149- }
150- UbseDeSerialization deserial{resBuffer.buffer, resBuffer.length};
151- int successCode = 0;
152- std::string errMsg;
153- deserial >> successCode >> errMsg;
154- if (deserial.Check() && successCode == 1) {
155- return UbseCliRegModule::UbseCliStringPromptReply("Unset successfully");
156- }
157- return UbseCliRegModule::UbseCliStringPromptReply("ERROR: " + errMsg);
158 }224 }
159};225};
160 226 
@@ -165,28 +231,37 @@ UbseCliMemPid::UbseCliMemPid()
165 231 
166UbseCliMemPid::~UbseCliMemPid() noexcept = default;232UbseCliMemPid::~UbseCliMemPid() noexcept = default;
167 233 
168-std::shared_ptr<framework::UbseCliResultEcho> UbseCliMemPid::UbseCliSetPidThreshold(234+std::shared_ptr<framework::UbseCliResultEcho> UbseCliMemPid::UbseCliSetProcessMem(
169- const process_mem::def::ProcessMemPidInfo& pidInfo)235+ const process_mem::def::ProcessMemNewConfigInfo& newConfig)
170{236{
171 if (pImpl_ == nullptr) {237 if (pImpl_ == nullptr) {
172 return UbseCliRegModule::UbseCliStringPromptReply(systemd::error::ALLOCATION_ERROR);238 return UbseCliRegModule::UbseCliStringPromptReply(systemd::error::ALLOCATION_ERROR);
173 }239 }
174- return pImpl_->UbseCliSetPidThresholdImpl(pidInfo);240+ return pImpl_->UbseCliSetProcessMemImpl(newConfig);
175}241}
176 242 
177-std::shared_ptr<framework::UbseCliResultEcho> UbseCliMemPid::UbseCliPrintPidInfo()243+std::shared_ptr<framework::UbseCliResultEcho> UbseCliMemPid::UbseCliRemoveProcessMem(bool isPid,
244+ const std::string& identifier)
178{245{
179 if (pImpl_ == nullptr) {246 if (pImpl_ == nullptr) {
180 return UbseCliRegModule::UbseCliStringPromptReply(systemd::error::ALLOCATION_ERROR);247 return UbseCliRegModule::UbseCliStringPromptReply(systemd::error::ALLOCATION_ERROR);
181 }248 }
182- return pImpl_->UbseCliPrintPidInfoImpl();249+ return pImpl_->UbseCliRemoveProcessMemImpl(isPid, identifier);
183}250}
184 251 
185-std::shared_ptr<framework::UbseCliResultEcho> UbseCliMemPid::UbseCliUnsetPid(pid_t pid)252+std::shared_ptr<framework::UbseCliResultEcho> UbseCliMemPid::UbseCliDisplayProcessMemConfig()
186{253{
187 if (pImpl_ == nullptr) {254 if (pImpl_ == nullptr) {
188 return UbseCliRegModule::UbseCliStringPromptReply(systemd::error::ALLOCATION_ERROR);255 return UbseCliRegModule::UbseCliStringPromptReply(systemd::error::ALLOCATION_ERROR);
189 }256 }
190- return pImpl_->UbseCliUnsetPidImpl(pid);257+ return pImpl_->UbseCliDisplayProcessMemConfigImpl();
258+}
259+ 
260+std::shared_ptr<framework::UbseCliResultEcho> UbseCliMemPid::UbseCliDisplayProcessMemDetail()
261+{
262+ if (pImpl_ == nullptr) {
263+ return UbseCliRegModule::UbseCliStringPromptReply(systemd::error::ALLOCATION_ERROR);
264+ }
265+ return pImpl_->UbseCliDisplayProcessMemDetailImpl();
191}266}
192} // namespace ubse::cli::reg267} // namespace ubse::cli::reg
@@ -22,10 +22,11 @@ public:
22 UbseCliMemPid();22 UbseCliMemPid();
23 ~UbseCliMemPid() noexcept;23 ~UbseCliMemPid() noexcept;
24 24 
25- std::shared_ptr<framework::UbseCliResultEcho> UbseCliSetPidThreshold(25+ std::shared_ptr<framework::UbseCliResultEcho> UbseCliSetProcessMem(
26- const process_mem::def::ProcessMemPidInfo& pidInfo);26+ const process_mem::def::ProcessMemNewConfigInfo& newConfig);
27- std::shared_ptr<framework::UbseCliResultEcho> UbseCliPrintPidInfo();27+ std::shared_ptr<framework::UbseCliResultEcho> UbseCliRemoveProcessMem(bool isPid, const std::string& identifier);
28- std::shared_ptr<framework::UbseCliResultEcho> UbseCliUnsetPid(pid_t pid);28+ std::shared_ptr<framework::UbseCliResultEcho> UbseCliDisplayProcessMemConfig();
29+ std::shared_ptr<framework::UbseCliResultEcho> UbseCliDisplayProcessMemDetail();
29 30 
30private:31private:
31 class UbseCliMemPidImpl;32 class UbseCliMemPidImpl;
@@ -21,23 +21,23 @@
21#include "ubse_cli_mem_query.h"21#include "ubse_cli_mem_query.h"
22#include "ubse_error.h"22#include "ubse_error.h"
23#include "ubse_ipc_common.h"23#include "ubse_ipc_common.h"
24+#include "ubse_logger.h"
24#include "ubse_mem_controller.h"25#include "ubse_mem_controller.h"
25#include "ubse_serial_util.h"26#include "ubse_serial_util.h"
26#include "ubse_str_util.h"27#include "ubse_str_util.h"
27 28 
29+UBSE_DEFINE_THIS_MODULE("ubse_cli");
30+ 
28namespace ubse::cli::reg {31namespace ubse::cli::reg {
29UBSE_CLI_REGISTER_MODULE("CLI_MEM_MODULE", UbseCliRegMemModule);32UBSE_CLI_REGISTER_MODULE("CLI_MEM_MODULE", UbseCliRegMemModule);
30using namespace ubse::cli::framework;33using namespace ubse::cli::framework;
31using namespace ubse::serial;34using namespace ubse::serial;
32using namespace ubse::mem::controller;35using namespace ubse::mem::controller;
33 36 
34-// public option reg
35constexpr const char* PUBLIC_NAME_OPTION = "name";37constexpr const char* PUBLIC_NAME_OPTION = "name";
36-// public option desc
37constexpr const char* PUBLIC_NAME_OPTION_TIP =38constexpr const char* PUBLIC_NAME_OPTION_TIP =
38 "Input a unique name. The name must not exceed 47 characters "39 "Input a unique name. The name must not exceed 47 characters "
39 "and can only include English letters, numbers, dots, colons, underscores, and hyphens.";40 "and can only include English letters, numbers, dots, colons, underscores, and hyphens.";
40-// public option input error
41constexpr const char* PUBLIC_NAME_OPTION_REQUIRED =41constexpr const char* PUBLIC_NAME_OPTION_REQUIRED =
42 "ERROR: The request option -n or --name is required, and the supported name must not exceed 47 characters and can "42 "ERROR: The request option -n or --name is required, and the supported name must not exceed 47 characters and can "
43 "only include English letters, numbers, dots, colons, underscores, and hyphens.";43 "only include English letters, numbers, dots, colons, underscores, and hyphens.";
@@ -45,11 +45,9 @@ constexpr const char* PUBLIC_NAME_PARAM_INVALID =
45 "ERROR: Invalid name. The name must not exceed 47 characters and can only include English letters, numbers, dots, "45 "ERROR: Invalid name. The name must not exceed 47 characters and can only include English letters, numbers, dots, "
46 "colons, underscores, and hyphens.";46 "colons, underscores, and hyphens.";
47 47 
48-// display memory option reg
49constexpr const char* DISPLAY_MEM_T_OPTION = "type";48constexpr const char* DISPLAY_MEM_T_OPTION = "type";
50constexpr const char* DISPLAY_MEM_BT_OPTION = "borrow-type";49constexpr const char* DISPLAY_MEM_BT_OPTION = "borrow-type";
51constexpr const char* DISPLAY_MEM_N_OPTION = "name";50constexpr const char* DISPLAY_MEM_N_OPTION = "name";
52-// display memory option desc
53constexpr const char* DISPLAY_MEM_TYPE_OPTION_TIP =51constexpr const char* DISPLAY_MEM_TYPE_OPTION_TIP =
54 "Query the memory information of a specified option. The option is as follows: node_borrow, borrow_detail, "52 "Query the memory information of a specified option. The option is as follows: node_borrow, borrow_detail, "
55 "node_lend, numa_status, config.";53 "node_lend, numa_status, config.";
@@ -60,7 +58,6 @@ constexpr const char* DISPLAY_MEM_NAME_OPTION_TIP =
60 "Input a unique name to filter memory account. The name must not exceed 47 characters and can only include English "58 "Input a unique name to filter memory account. The name must not exceed 47 characters and can only include English "
61 "letters, numbers, dots, colons, underscores, and hyphens. Supported only when the type parameter is "59 "letters, numbers, dots, colons, underscores, and hyphens. Supported only when the type parameter is "
62 "borrow_detail.";60 "borrow_detail.";
63-// display memory option input error
64constexpr const char* DISPLAY_MEM_TYPE_OPTION_REQUIRED =61constexpr const char* DISPLAY_MEM_TYPE_OPTION_REQUIRED =
65 "ERROR: The request option -t or --type is required, and the supported param is as follows: node_borrow, "62 "ERROR: The request option -t or --type is required, and the supported param is as follows: node_borrow, "
66 "borrow_detail, node_lend, numa_status, config.";63 "borrow_detail, node_lend, numa_status, config.";
@@ -73,24 +70,19 @@ constexpr const char* DISPLAY_MEM_NAME_PARAM_INVALID = PUBLIC_NAME_PARAM_INVALID
73 70 
74constexpr const char* PID_OPTION = "pid";71constexpr const char* PID_OPTION = "pid";
75constexpr const char* PID_OPTION_TIP = "PID of the target process. Range: 1-4194304";72constexpr const char* PID_OPTION_TIP = "PID of the target process. Range: 1-4194304";
76-constexpr const char* EVICT_THRESHOLD_OPTION = "evict-thresh";73+constexpr const char* NAME_OPTION = "name";
77-constexpr const char* TARGET_EVICT_THRESHOLD_OPTION = "target-evict-thresh";74+constexpr const char* NAME_OPTION_TIP = "Process name (comm). Mutually exclusive with --pid. Max 15 chars, "
78-constexpr const char* RECLAIM_THRESHOLD_OPTION = "reclaim-thresh";75+ "letters/numbers/dots/colons/underscores/hyphens.";
79-constexpr const char* EVICT_THRESHOLD_OPTION_TIP =76+ 
80- "Eviction threshold (%). Eviction is triggered when total memory usage exceeds this ratio. "
81- "Must be at least 5 higher than reclaim-thresh to avoid oscillation. Range: 1-100";
82-constexpr const char* TARGET_EVICT_THRESHOLD_OPTION_TIP =
83- "Target eviction ratio (%). Target proportion of remote memory to total memory after eviction. Range: 1-100";
84-constexpr const char* RECLAIM_THRESHOLD_OPTION_TIP =
85- "Reclaim threshold (%). All remote memory is migrated back and released when total memory usage "
86- "drops below this ratio. Must be at least 5 lower than evict-thresh to avoid oscillation. Range: 1-100";
87-constexpr const char* SRC_NUMAID_OPTION = "src-numa";
88-constexpr const char* SRC_NUMAID_OPTION_TIP =
89- "Local NUMA node ID (optional). The lending socket is selected on the same plane as this NUMA node";
90constexpr const char* SIZE_OPTION = "size";77constexpr const char* SIZE_OPTION = "size";
91-constexpr const char* SIZE_OPTION_TIP = "Specify the size. The range is from 128M to 32G. "78+constexpr const char* REMOTE_RATIO_OPTION = "remote-ratio";
92- "Support up to 2 decimal places. Example: 1G, 512M, 1.5G";79+constexpr const char* SIZE_OPTION_TIP =
93-constexpr const char* INVALID_SIZE_OPTION_TIP = "ERROR: Invalid size param. Please check the form.";80+ "Size of the process memory. The range is from 128M to 32G, support up to 2 "
81+ "decimal places (e.g., 8G, 512M, 1.5G). Units: B/K/M/G (binary); default unit is "
82+ "MiB when omitted.";
83+constexpr const char* REMOTE_RATIO_OPTION_TIP = "Max remote memory ratio. Range: 0.0-1.0";
84+constexpr const char* SIZE_INVALID_FORMAT = "ERROR: Invalid size format.";
85+constexpr const char* REMOTE_RATIO_INVALID_FORMAT = "ERROR: Invalid remote-ratio, range 0.0-1.0";
94 86 
95constexpr const char* DISPLAY_MEM_NAME_OPTION_UNSUPPORT =87constexpr const char* DISPLAY_MEM_NAME_OPTION_UNSUPPORT =
96 "ERROR: The -n or --name option only supports when the -t or --type parameter is borrow_detail.";88 "ERROR: The -n or --name option only supports when the -t or --type parameter is borrow_detail.";
@@ -103,13 +95,11 @@ constexpr const char* DISPLAY_MEM_ALL_OPTION_TIP =
103constexpr const char* DISPLAY_MEM_ALL_OPTION_UNSUPPORT =95constexpr const char* DISPLAY_MEM_ALL_OPTION_UNSUPPORT =
104 "ERROR: The -a or --all option only supports when the -t or --type parameter is numa_status.";96 "ERROR: The -a or --all option only supports when the -t or --type parameter is numa_status.";
105 97 
106-// create memory option reg
107constexpr const char* CREATE_MEM_T_OPTION = "type";98constexpr const char* CREATE_MEM_T_OPTION = "type";
108constexpr const char* CREATE_MEM_L_OPTION = "link-id";99constexpr const char* CREATE_MEM_L_OPTION = "link-id";
109constexpr const char* CREATE_MEM_S_OPTION = "size";100constexpr const char* CREATE_MEM_S_OPTION = "size";
110constexpr const char* CREATE_MEM_N_OPTION = PUBLIC_NAME_OPTION;101constexpr const char* CREATE_MEM_N_OPTION = PUBLIC_NAME_OPTION;
111constexpr const char* CREATE_MEM_R_OPTION = "region";102constexpr const char* CREATE_MEM_R_OPTION = "region";
112-// create memory option desc
113constexpr const char* CREATE_MEM_TYPE_OPTION_TIP = "Specify the type. The option is as follows: numa, fd, share.";103constexpr const char* CREATE_MEM_TYPE_OPTION_TIP = "Specify the type. The option is as follows: numa, fd, share.";
114constexpr const char* CREATE_MEM_LINK_OPTION_TIP =104constexpr const char* CREATE_MEM_LINK_OPTION_TIP =
115 "Specify the link-id. The format is: nodeID/socketID/portID-nodeID/socketID/portID (e.g., 1/36/0-2/36/0). "105 "Specify the link-id. The format is: nodeID/socketID/portID-nodeID/socketID/portID (e.g., 1/36/0-2/36/0). "
@@ -119,7 +109,6 @@ constexpr const char* CREATE_MEM_SIZE_OPTION_TIP = "Specify the size. The minimu
119constexpr const char* CREATE_MEM_NAME_OPTION_TIP = PUBLIC_NAME_OPTION_TIP;109constexpr const char* CREATE_MEM_NAME_OPTION_TIP = PUBLIC_NAME_OPTION_TIP;
120constexpr const char* CREATE_MEM_REGION_OPTION_TIP = "Specify the shared region node IDs. The format is: node1,node2 "110constexpr const char* CREATE_MEM_REGION_OPTION_TIP = "Specify the shared region node IDs. The format is: node1,node2 "
121 "(e.g., 1,2). Supported only when the type parameter is share.";111 "(e.g., 1,2). Supported only when the type parameter is share.";
122-// create memory option input error
123constexpr const char* CREATE_MEM_NAME_OPTION_REQUIRED = PUBLIC_NAME_OPTION_REQUIRED;112constexpr const char* CREATE_MEM_NAME_OPTION_REQUIRED = PUBLIC_NAME_OPTION_REQUIRED;
124constexpr const char* CREATE_MEM_NAME_PARAM_INVALID = PUBLIC_NAME_PARAM_INVALID;113constexpr const char* CREATE_MEM_NAME_PARAM_INVALID = PUBLIC_NAME_PARAM_INVALID;
125constexpr const char* CREATE_MEM_TYPE_OPTION_REQUIRED =114constexpr const char* CREATE_MEM_TYPE_OPTION_REQUIRED =
@@ -139,32 +128,23 @@ constexpr const char* CREATE_MEM_LINK_OPTION_UNSUPPORT =
139constexpr const char* CREATE_MEM_REGION_OPTION_UNSUPPORT =128constexpr const char* CREATE_MEM_REGION_OPTION_UNSUPPORT =
140 "ERROR: The -r or --region option only supports when the -t or --type parameter is share.";129 "ERROR: The -r or --region option only supports when the -t or --type parameter is share.";
141 130 
142-// delete memory option reg
143constexpr const char* DELETE_MEM_N_OPTION = PUBLIC_NAME_OPTION;131constexpr const char* DELETE_MEM_N_OPTION = PUBLIC_NAME_OPTION;
144constexpr const char* DELETE_MEM_T_OPTION = "type";132constexpr const char* DELETE_MEM_T_OPTION = "type";
145-// delete memory option desc
146constexpr const char* DELETE_MEM_NAME_OPTION_TIP = PUBLIC_NAME_OPTION_TIP;133constexpr const char* DELETE_MEM_NAME_OPTION_TIP = PUBLIC_NAME_OPTION_TIP;
147constexpr const char* DELETE_MEM_TYPE_OPTION_TIP =134constexpr const char* DELETE_MEM_TYPE_OPTION_TIP =
148 "Input the type to delete memory. The default value is numa. The option is as follows: fd, numa, share, addr.";135 "Input the type to delete memory. The default value is numa. The option is as follows: fd, numa, share, addr.";
149-// delete memory option input error
150constexpr const char* DELETE_MEM_NAME_OPTION_REQUIRED = PUBLIC_NAME_OPTION_REQUIRED;136constexpr const char* DELETE_MEM_NAME_OPTION_REQUIRED = PUBLIC_NAME_OPTION_REQUIRED;
151constexpr const char* DELETE_MEM_NAME_PARAM_INVALID = PUBLIC_NAME_PARAM_INVALID;137constexpr const char* DELETE_MEM_NAME_PARAM_INVALID = PUBLIC_NAME_PARAM_INVALID;
152constexpr const char* DELETE_MEM_TYPE_PARAM_INVALID =138constexpr const char* DELETE_MEM_TYPE_PARAM_INVALID =
153 "ERROR: Invalid type. The supported param is as follows: numa, fd, share, addr.";139 "ERROR: Invalid type. The supported param is as follows: numa, fd, share, addr.";
154 140 
155-// attach memory option reg
156constexpr const char* ATTACH_MEM_N_OPTION = PUBLIC_NAME_OPTION;141constexpr const char* ATTACH_MEM_N_OPTION = PUBLIC_NAME_OPTION;
157-// attach memory option desc
158constexpr const char* ATTACH_MEM_NAME_OPTION_TIP = PUBLIC_NAME_OPTION_TIP;142constexpr const char* ATTACH_MEM_NAME_OPTION_TIP = PUBLIC_NAME_OPTION_TIP;
159-// attach memory option input error
160constexpr const char* ATTACH_MEM_NAME_OPTION_REQUIRED = PUBLIC_NAME_OPTION_REQUIRED;143constexpr const char* ATTACH_MEM_NAME_OPTION_REQUIRED = PUBLIC_NAME_OPTION_REQUIRED;
161constexpr const char* ATTACH_MEM_NAME_PARAM_INVALID = PUBLIC_NAME_PARAM_INVALID;144constexpr const char* ATTACH_MEM_NAME_PARAM_INVALID = PUBLIC_NAME_PARAM_INVALID;
162 145 
163-// detach memory option reg
164constexpr const char* DETACH_MEM_N_OPTION = PUBLIC_NAME_OPTION;146constexpr const char* DETACH_MEM_N_OPTION = PUBLIC_NAME_OPTION;
165-// detach memory option desc
166constexpr const char* DETACH_MEM_NAME_OPTION_TIP = PUBLIC_NAME_OPTION_TIP;147constexpr const char* DETACH_MEM_NAME_OPTION_TIP = PUBLIC_NAME_OPTION_TIP;
167-// detach memory option input error
168constexpr const char* DETACH_MEM_NAME_OPTION_REQUIRED = PUBLIC_NAME_OPTION_REQUIRED;148constexpr const char* DETACH_MEM_NAME_OPTION_REQUIRED = PUBLIC_NAME_OPTION_REQUIRED;
169constexpr const char* DETACH_MEM_NAME_PARAM_INVALID = PUBLIC_NAME_PARAM_INVALID;149constexpr const char* DETACH_MEM_NAME_PARAM_INVALID = PUBLIC_NAME_PARAM_INVALID;
170 150 
@@ -173,7 +153,6 @@ constexpr const char* BORROW_DETAIL_EMPTY = "INFO: The borrow detail information
173 153 
174constexpr const char* SERIALIZATION_ERROR = "ERROR: Serialization failed.";154constexpr const char* SERIALIZATION_ERROR = "ERROR: Serialization failed.";
175constexpr const char* DE_SERIALIZATION_ERROR = "ERROR: Deserialization failed.";155constexpr const char* DE_SERIALIZATION_ERROR = "ERROR: Deserialization failed.";
176-constexpr const char* MEMORY_INTERNAL_ERROR = "ERROR: Internal error with error code ";
177constexpr const char* MEMORY_EMPTY_ERROR = "ERROR: Failed to obtain memory information";156constexpr const char* MEMORY_EMPTY_ERROR = "ERROR: Failed to obtain memory information";
178constexpr const char* SET_TIMER_ERROR = "ERROR: Set timer failed. ";157constexpr const char* SET_TIMER_ERROR = "ERROR: Set timer failed. ";
179 158 
@@ -185,16 +164,22 @@ const int8_t MEM_SUCCESS_CODE = UBSE_OK;
185const int MAX_NAME_LENGTH = 47;164const int MAX_NAME_LENGTH = 47;
186const uint32_t REQUEST_BUFFER_CAPACITY = 8;165const uint32_t REQUEST_BUFFER_CAPACITY = 8;
187const int8_t RETRY_WAIT_TIME = 10;166const int8_t RETRY_WAIT_TIME = 10;
188-constexpr size_t NODE_LENGTH = 80; // hostname(slot_id), hostname最长为64167+constexpr size_t NODE_LENGTH = 80;
189 168 
190std::string FormatHostnameSlot(const std::string& hostname, uint32_t slotId)169std::string FormatHostnameSlot(const std::string& hostname, uint32_t slotId)
191{170{
192 return (hostname.empty() ? "-" : hostname) + "(" + std::to_string(slotId) + ")";171 return (hostname.empty() ? "-" : hostname) + "(" + std::to_string(slotId) + ")";
193}172}
194 173 
174+std::string FormatNameParamError(int maxLength)
175+{
176+ return "ERROR: Invalid name. The name must not exceed " + std::to_string(maxLength) +
177+ " characters and can only include English letters, numbers, dots, colons, underscores, and hyphens.";
178+}
179+ 
195std::shared_ptr<UbseCliResultEcho> UbseCliRegMemModule::UbseCliQueryNodeBorrowInfo()180std::shared_ptr<UbseCliResultEcho> UbseCliRegMemModule::UbseCliQueryNodeBorrowInfo()
196{181{
197- UbseSerialization ubse_req_serial(REQUEST_BUFFER_CAPACITY); // Create a blank request.182+ UbseSerialization ubse_req_serial(REQUEST_BUFFER_CAPACITY);
198 ubse_api_buffer_t ubse_req_buffer{ubse_req_serial.GetBuffer(), static_cast<uint32_t>(ubse_req_serial.GetLength())};183 ubse_api_buffer_t ubse_req_buffer{ubse_req_serial.GetBuffer(), static_cast<uint32_t>(ubse_req_serial.GetLength())};
199 ubse_api_buffer_t ubse_res_buffer{};184 ubse_api_buffer_t ubse_res_buffer{};
200 uint32_t ret = ubse_invoke_call(MEM_MODULE_CODE, MEM_NODE_BORROW_OP_CODE, &ubse_req_buffer, &ubse_res_buffer);185 uint32_t ret = ubse_invoke_call(MEM_MODULE_CODE, MEM_NODE_BORROW_OP_CODE, &ubse_req_buffer, &ubse_res_buffer);
@@ -327,6 +312,57 @@ std::shared_ptr<UbseCliResultEcho> UbseCliRegMemModule::UbseCliProcessNumaStatus
327 return UbseCliProcessNumaStatusDataWithHugepages(deSerialization, numaInfoSize);312 return UbseCliProcessNumaStatusDataWithHugepages(deSerialization, numaInfoSize);
328}313}
329 314 
315+static void FillNumaStatusHeader(UbseCliResBuilder& builder, size_t row, bool show2MColumns,
316+ const std::string& largeTotalCol, const std::string& largeFreeCol)
317+{
318+ builder.UbseCliSetCellData(row, UBSE_CLI_NUM_1, "node");
319+ builder.UbseCliSetCellData(row, UBSE_CLI_NUM_2, "numa");
320+ builder.UbseCliSetCellData(row, UBSE_CLI_NUM_3, "total");
321+ builder.UbseCliSetCellData(row, UBSE_CLI_NUM_4, "used");
322+ builder.UbseCliSetCellData(row, UBSE_CLI_NUM_5, "free");
323+ builder.UbseCliSetCellData(row, UBSE_CLI_NUM_6, "used_percent");
324+ if (show2MColumns) {
325+ builder.UbseCliSetCellData(row, UBSE_CLI_NUM_7, "2M_total");
326+ builder.UbseCliSetCellData(row, UBSE_CLI_NUM_8, "2M_free");
327+ builder.UbseCliSetCellData(row, UBSE_CLI_NUM_9, largeTotalCol);
328+ builder.UbseCliSetCellData(row, UBSE_CLI_NUM_10, largeFreeCol);
329+ } else {
330+ builder.UbseCliSetCellData(row, UBSE_CLI_NUM_7, largeTotalCol);
331+ builder.UbseCliSetCellData(row, UBSE_CLI_NUM_8, largeFreeCol);
332+ }
333+}
334+ 
335+static uint32_t FillNumaStatusRow(UbseCliResBuilder& builder, size_t row, bool show2MColumns,
336+ UbseDeSerialization& deSerialization)
337+{
338+ UbseNumaStatusInfo numaInfo{};
339+ std::string nr2M;
340+ std::string free2M;
341+ std::string nrLarge;
342+ std::string freeLarge;
343+ deSerialization >> numaInfo.node >> numaInfo.numa >> numaInfo.total >> numaInfo.used >> numaInfo.freeSize >>
344+ numaInfo.used_percent >> nr2M >> free2M >> nrLarge >> freeLarge;
345+ if (!deSerialization.Check()) {
346+ return UBSE_ERROR;
347+ }
348+ builder.UbseCliSetCellData(row, UBSE_CLI_NUM_1, numaInfo.node);
349+ builder.UbseCliSetCellData(row, UBSE_CLI_NUM_2, numaInfo.numa);
350+ builder.UbseCliSetCellData(row, UBSE_CLI_NUM_3, numaInfo.total);
351+ builder.UbseCliSetCellData(row, UBSE_CLI_NUM_4, numaInfo.used);
352+ builder.UbseCliSetCellData(row, UBSE_CLI_NUM_5, numaInfo.freeSize);
353+ builder.UbseCliSetCellData(row, UBSE_CLI_NUM_6, numaInfo.used_percent);
354+ if (show2MColumns) {
355+ builder.UbseCliSetCellData(row, UBSE_CLI_NUM_7, nr2M);
356+ builder.UbseCliSetCellData(row, UBSE_CLI_NUM_8, free2M);
357+ builder.UbseCliSetCellData(row, UBSE_CLI_NUM_9, nrLarge);
358+ builder.UbseCliSetCellData(row, UBSE_CLI_NUM_10, freeLarge);
359+ } else {
360+ builder.UbseCliSetCellData(row, UBSE_CLI_NUM_7, nrLarge);
361+ builder.UbseCliSetCellData(row, UBSE_CLI_NUM_8, freeLarge);
362+ }
363+ return UBSE_OK;
364+}
365+ 
330std::shared_ptr<UbseCliResultEcho> UbseCliRegMemModule::UbseCliProcessNumaStatusDataWithHugepages(366std::shared_ptr<UbseCliResultEcho> UbseCliRegMemModule::UbseCliProcessNumaStatusDataWithHugepages(
331 UbseDeSerialization& deSerialization, size_t numaInfoSize)367 UbseDeSerialization& deSerialization, size_t numaInfoSize)
332{368{
@@ -337,55 +373,18 @@ std::shared_ptr<UbseCliResultEcho> UbseCliRegMemModule::UbseCliProcessNumaStatus
337 }373 }
338 std::string largeTotalCol = pageSizeType + "_total";374 std::string largeTotalCol = pageSizeType + "_total";
339 std::string largeFreeCol = pageSizeType + "_free";375 std::string largeFreeCol = pageSizeType + "_free";
340- // 64k环境(pageSizeType=="512M")下不展示2M大页,仅展示512M大页;其他环境(如4k, 1G)展示全部4列大页
341 bool show2MColumns = (pageSizeType != "512M");376 bool show2MColumns = (pageSizeType != "512M");
342 size_t colCount = show2MColumns ? UBSE_CLI_NUM_10 : UBSE_CLI_NUM_8;377 size_t colCount = show2MColumns ? UBSE_CLI_NUM_10 : UBSE_CLI_NUM_8;
343 UbseCliResBuilder variable_cell_builder(colCount, NODE_LENGTH);378 UbseCliResBuilder variable_cell_builder(colCount, NODE_LENGTH);
344 size_t row = variable_cell_builder.UbseCliAddRow();379 size_t row = variable_cell_builder.UbseCliAddRow();
345 variable_cell_builder.UbseCliAddlineSeparate(row);380 variable_cell_builder.UbseCliAddlineSeparate(row);
346- variable_cell_builder.UbseCliSetCellData(row, UBSE_CLI_NUM_1, "node");381+ FillNumaStatusHeader(variable_cell_builder, row, show2MColumns, largeTotalCol, largeFreeCol);
347- variable_cell_builder.UbseCliSetCellData(row, UBSE_CLI_NUM_2, "numa");
348- variable_cell_builder.UbseCliSetCellData(row, UBSE_CLI_NUM_3, "total");
349- variable_cell_builder.UbseCliSetCellData(row, UBSE_CLI_NUM_4, "used");
350- variable_cell_builder.UbseCliSetCellData(row, UBSE_CLI_NUM_5, "free");
351- variable_cell_builder.UbseCliSetCellData(row, UBSE_CLI_NUM_6, "used_percent");
352- if (show2MColumns) {
353- variable_cell_builder.UbseCliSetCellData(row, UBSE_CLI_NUM_7, "2M_total");
354- variable_cell_builder.UbseCliSetCellData(row, UBSE_CLI_NUM_8, "2M_free");
355- variable_cell_builder.UbseCliSetCellData(row, UBSE_CLI_NUM_9, largeTotalCol);
356- variable_cell_builder.UbseCliSetCellData(row, UBSE_CLI_NUM_10, largeFreeCol);
357- } else {
358- variable_cell_builder.UbseCliSetCellData(row, UBSE_CLI_NUM_7, largeTotalCol);
359- variable_cell_builder.UbseCliSetCellData(row, UBSE_CLI_NUM_8, largeFreeCol);
360- }
361 variable_cell_builder.UbseCliAddBottomlineSeparate();382 variable_cell_builder.UbseCliAddBottomlineSeparate();
362 for (size_t i = 0; i < numaInfoSize; i++) {383 for (size_t i = 0; i < numaInfoSize; i++) {
363 row = variable_cell_builder.UbseCliAddRow();384 row = variable_cell_builder.UbseCliAddRow();
364- UbseNumaStatusInfo numaInfo{};385+ if (FillNumaStatusRow(variable_cell_builder, row, show2MColumns, deSerialization) != UBSE_OK) {
365- std::string nr2M;
366- std::string free2M;
367- std::string nrLarge;
368- std::string freeLarge;
369- deSerialization >> numaInfo.node >> numaInfo.numa >> numaInfo.total >> numaInfo.used >> numaInfo.freeSize >>
370- numaInfo.used_percent >> nr2M >> free2M >> nrLarge >> freeLarge;
371- if (!deSerialization.Check()) {
372 return UbseCliStringPromptReply(DE_SERIALIZATION_ERROR);386 return UbseCliStringPromptReply(DE_SERIALIZATION_ERROR);
373 }387 }
374- variable_cell_builder.UbseCliSetCellData(row, UBSE_CLI_NUM_1, numaInfo.node);
375- variable_cell_builder.UbseCliSetCellData(row, UBSE_CLI_NUM_2, numaInfo.numa);
376- variable_cell_builder.UbseCliSetCellData(row, UBSE_CLI_NUM_3, numaInfo.total);
377- variable_cell_builder.UbseCliSetCellData(row, UBSE_CLI_NUM_4, numaInfo.used);
378- variable_cell_builder.UbseCliSetCellData(row, UBSE_CLI_NUM_5, numaInfo.freeSize);
379- variable_cell_builder.UbseCliSetCellData(row, UBSE_CLI_NUM_6, numaInfo.used_percent);
380- if (show2MColumns) {
381- variable_cell_builder.UbseCliSetCellData(row, UBSE_CLI_NUM_7, nr2M);
382- variable_cell_builder.UbseCliSetCellData(row, UBSE_CLI_NUM_8, free2M);
383- variable_cell_builder.UbseCliSetCellData(row, UBSE_CLI_NUM_9, nrLarge);
384- variable_cell_builder.UbseCliSetCellData(row, UBSE_CLI_NUM_10, freeLarge);
385- } else {
386- variable_cell_builder.UbseCliSetCellData(row, UBSE_CLI_NUM_7, nrLarge);
387- variable_cell_builder.UbseCliSetCellData(row, UBSE_CLI_NUM_8, freeLarge);
388- }
389 }388 }
390 variable_cell_builder.UbseCliAddBottomlineSeparate();389 variable_cell_builder.UbseCliAddBottomlineSeparate();
391 return UbseCliVariableCelReply(variable_cell_builder.UbseCliVariableCellBuild());390 return UbseCliVariableCelReply(variable_cell_builder.UbseCliVariableCellBuild());
@@ -527,14 +526,18 @@ std::shared_ptr<UbseCliResultEcho> UbseCliRegMemModule::DisplayProcessMemFunc(
527{526{
528 auto it = params.find("type");527 auto it = params.find("type");
529 if (it == params.end()) {528 if (it == params.end()) {
530- return UbseCliStringPromptReply(529+ return UbseCliStringPromptReply("ERROR: The request option -t or --type is required, and the supported params "
531- "ERROR: The request option -t or --type is required, and the supported param is as follows: config.");530+ "are as follows: config, proc_detail.");
532 }531 }
533- if (it->second != "config") {532+ if (it->second != "config" && it->second != "proc_detail") {
534- return UbseCliStringPromptReply("ERROR: Invalid type. The supported param is as follows: config.");533+ return UbseCliStringPromptReply(
534+ "ERROR: Invalid type. The supported params are as follows: config, proc_detail.");
535 }535 }
536 UbseCliMemPid memPid{};536 UbseCliMemPid memPid{};
537- return memPid.UbseCliPrintPidInfo();537+ if (it->second == "proc_detail") {
538+ return memPid.UbseCliDisplayProcessMemDetail();
539+ }
540+ return memPid.UbseCliDisplayProcessMemConfig();
538}541}
539 542 
540std::shared_ptr<UbseCliResultEcho> UbseCliRegMemModule::UbseCliCheckMemoryStatusFunc(543std::shared_ptr<UbseCliResultEcho> UbseCliRegMemModule::UbseCliCheckMemoryStatusFunc(
@@ -634,7 +637,7 @@ bool SizeIsMatch(const std::string& str, size_t& size)
634 multiplier = 1024ULL * 1024ULL * 1024ULL;637 multiplier = 1024ULL * 1024ULL * 1024ULL;
635 }638 }
636 if (tmpSize > std::numeric_limits<size_t>::max() / multiplier) {639 if (tmpSize > std::numeric_limits<size_t>::max() / multiplier) {
637- return false; // 溢出640+ return false;
638 }641 }
639 642 
640 size = tmpSize * multiplier;643 size = tmpSize * multiplier;
@@ -643,17 +646,30 @@ bool SizeIsMatch(const std::string& str, size_t& size)
643 646 
644bool CheckName(const std::string& name)647bool CheckName(const std::string& name)
645{648{
646- if (name.length() > MAX_NAME_LENGTH || name.empty()) {649+ return CheckName(name, MAX_NAME_LENGTH);
650+}
651+ 
652+bool CheckName(const std::string& name, int maxLength)
653+{
654+ if (name.length() > static_cast<size_t>(maxLength) || name.empty()) {
647 return false;655 return false;
648 }656 }
649 for (char c : name) {657 for (char c : name) {
650- if (!isdigit(c) && !isalpha(c) && c != '_' && c != '-' && c != '.' && c != ':') {658+ const unsigned char uc = static_cast<unsigned char>(c);
659+ if (!isdigit(uc) && !isalpha(uc) && c != '_' && c != '-' && c != '.' && c != ':') {
651 return false;660 return false;
652 }661 }
653 }662 }
654 return true;663 return true;
655}664}
656 665 
666+constexpr int MAX_PROC_NAME_LENGTH = 15;
667+ 
668+bool CheckProcName(const std::string& name)
669+{
670+ return CheckName(name, MAX_PROC_NAME_LENGTH);
671+}
672+ 
657static std::string FormatMemoryInfoReply(const std::string& name, int64_t numaId, const std::string& importNode,673static std::string FormatMemoryInfoReply(const std::string& name, int64_t numaId, const std::string& importNode,
658 const std::string& exportNode)674 const std::string& exportNode)
659{675{
@@ -723,7 +739,6 @@ ParsedResponse ParseResponseBuffer(const ubse_api_buffer_t& responseBuffer)
723 return result;739 return result;
724}740}
725 741 
726-// 处理 IPC 超时后的轮询查询逻辑
727std::shared_ptr<UbseCliResultEcho> HandleTimeoutRetry(const std::string& name)742std::shared_ptr<UbseCliResultEcho> HandleTimeoutRetry(const std::string& name)
728{743{
729 while (true) {744 while (true) {
@@ -786,7 +801,6 @@ bool ParseRegionString(const std::string& regionStr, std::vector<uint32_t>& regi
786 try {801 try {
787 size_t pos = 0;802 size_t pos = 0;
788 unsigned long value = std::stoul(item, &pos);803 unsigned long value = std::stoul(item, &pos);
789- // 检查是否完整解析
790 if (pos != item.length()) {804 if (pos != item.length()) {
791 return false;805 return false;
792 }806 }
@@ -795,7 +809,7 @@ bool ParseRegionString(const std::string& regionStr, std::vector<uint32_t>& regi
795 }809 }
796 regions.push_back(static_cast<uint32_t>(value));810 regions.push_back(static_cast<uint32_t>(value));
797 } catch (...) {811 } catch (...) {
798- return false; // 不是有效数字812+ return false;
799 }813 }
800 }814 }
801 815 
@@ -874,22 +888,18 @@ std::shared_ptr<UbseCliResultEcho> UbseCliRegMemModule::CreateMemoryFunc(
874 if (type != "numa" && type != "share" && type != "fd") {888 if (type != "numa" && type != "share" && type != "fd") {
875 return UbseCliStringPromptReply(CREATE_MEM_TYPE_PARAM_INVALID);889 return UbseCliStringPromptReply(CREATE_MEM_TYPE_PARAM_INVALID);
876 }890 }
877- // 验证参数白名单
878 auto allowedParams = GetAllowedParams(type);891 auto allowedParams = GetAllowedParams(type);
879 std::string errorMsg;892 std::string errorMsg;
880 if (!ValidateParamsWhitelist(params, allowedParams, errorMsg)) {893 if (!ValidateParamsWhitelist(params, allowedParams, errorMsg)) {
881 return UbseCliStringPromptReply(errorMsg);894 return UbseCliStringPromptReply(errorMsg);
882 }895 }
883- // 验证公共参数
884 std::string name;896 std::string name;
885 size_t size{};897 size_t size{};
886 if (!ValidateCommonParams(params, name, size, errorMsg)) {898 if (!ValidateCommonParams(params, name, size, errorMsg)) {
887 return UbseCliStringPromptReply(errorMsg);899 return UbseCliStringPromptReply(errorMsg);
888 }900 }
889- // 根据类型创建
890 UbseCliMemCreate memCreate{};901 UbseCliMemCreate memCreate{};
891 if (type == "numa") {902 if (type == "numa") {
892- // NUMA 类型:可以有 link 参数
893 auto itLink = params.find(CREATE_MEM_L_OPTION);903 auto itLink = params.find(CREATE_MEM_L_OPTION);
894 if (itLink != params.end() && !LinkIsMatch(itLink->second)) {904 if (itLink != params.end() && !LinkIsMatch(itLink->second)) {
895 return UbseCliStringPromptReply(CREATE_MEM_LINK_PARAM_INVALID);905 return UbseCliStringPromptReply(CREATE_MEM_LINK_PARAM_INVALID);
@@ -897,7 +907,6 @@ std::shared_ptr<UbseCliResultEcho> UbseCliRegMemModule::CreateMemoryFunc(
897 auto linkValue = (itLink != params.end()) ? itLink->second : "";907 auto linkValue = (itLink != params.end()) ? itLink->second : "";
898 return memCreate.UbseCliCreateNumaMem(name, size, linkValue);908 return memCreate.UbseCliCreateNumaMem(name, size, linkValue);
899 } else if (type == "share") {909 } else if (type == "share") {
900- // SHARE 类型:可以有 region 参数
901 auto itRegion = params.find(CREATE_MEM_R_OPTION);910 auto itRegion = params.find(CREATE_MEM_R_OPTION);
902 std::vector<uint32_t> region{};911 std::vector<uint32_t> region{};
903 if (itRegion != params.end() && !ParseRegionString(itRegion->second, region)) {912 if (itRegion != params.end() && !ParseRegionString(itRegion->second, region)) {
@@ -905,7 +914,6 @@ std::shared_ptr<UbseCliResultEcho> UbseCliRegMemModule::CreateMemoryFunc(
905 }914 }
906 return memCreate.UbseCliCreateShareMem(name, size, region);915 return memCreate.UbseCliCreateShareMem(name, size, region);
907 } else {916 } else {
908- // type == "fd"
909 return memCreate.UbseCliCreateFdMem(name, size);917 return memCreate.UbseCliCreateFdMem(name, size);
910 }918 }
911}919}
@@ -915,7 +923,9 @@ UbseCliCommandInfo UbseCliRegMemModule::DisplayProcessMem()
915 UbseCliRegBuilder builder;923 UbseCliRegBuilder builder;
916 builder.UbseCliSetCommand("display")924 builder.UbseCliSetCommand("display")
917 .UbseCliSetType("process-mem")925 .UbseCliSetType("process-mem")
918- .UbseCliAddOption("t", "type", "Query the process memory configuration. The option is as follows: config.")926+ .UbseCliAddOption("t", "type",
927+ "Query the process memory configuration. The option is as follows: config, "
928+ "proc_detail.")
919 .UbseCliSetFunc(DisplayProcessMemFunc);929 .UbseCliSetFunc(DisplayProcessMemFunc);
920 return builder.UbseCliBuild();930 return builder.UbseCliBuild();
921}931}
@@ -926,12 +936,10 @@ UbseCliCommandInfo UbseCliRegMemModule::ChangeMemory()
926 builder.UbseCliSetCommand("change")936 builder.UbseCliSetCommand("change")
927 .UbseCliSetType("process-mem")937 .UbseCliSetType("process-mem")
928 .UbseCliAddOption("p", PID_OPTION, PID_OPTION_TIP)938 .UbseCliAddOption("p", PID_OPTION, PID_OPTION_TIP)
929- .UbseCliAddOption("e", EVICT_THRESHOLD_OPTION, EVICT_THRESHOLD_OPTION_TIP)939+ .UbseCliAddOption("n", NAME_OPTION, NAME_OPTION_TIP)
930- .UbseCliAddOption("t", TARGET_EVICT_THRESHOLD_OPTION, TARGET_EVICT_THRESHOLD_OPTION_TIP)
931- .UbseCliAddOption("r", RECLAIM_THRESHOLD_OPTION, RECLAIM_THRESHOLD_OPTION_TIP)
932 .UbseCliAddOption("s", SIZE_OPTION, SIZE_OPTION_TIP)940 .UbseCliAddOption("s", SIZE_OPTION, SIZE_OPTION_TIP)
933- .UbseCliAddOption("sn", SRC_NUMAID_OPTION, SRC_NUMAID_OPTION_TIP)941+ .UbseCliAddOption("r", REMOTE_RATIO_OPTION, REMOTE_RATIO_OPTION_TIP)
934- .UbseCliSetFunc(PidSetThresholdFunc);942+ .UbseCliSetFunc(ChangeProcessMemFunc);
935 return builder.UbseCliBuild();943 return builder.UbseCliBuild();
936}944}
937 945 
@@ -965,23 +973,45 @@ static std::string ValidatePid(const std::string& pidStr)
965 return "";973 return "";
966}974}
967 975 
968-std::shared_ptr<UbseCliResultEcho> UbseCliRegMemModule::PidUnSetFunc(const std::map<std::string, std::string>& params)976+static std::shared_ptr<UbseCliResultEcho> DoRemoveByPid(const std::string& pidStr)
977+{
978+ UbseCliMemPid memPid{};
979+ auto removeResult = memPid.UbseCliRemoveProcessMem(true, pidStr);
980+ return removeResult;
981+}
982+ 
983+static std::shared_ptr<UbseCliResultEcho> DoRemoveByName(const std::string& name)
984+{
985+ UbseCliMemPid memPid{};
986+ auto removeResult = memPid.UbseCliRemoveProcessMem(false, name);
987+ return removeResult;
988+}
989+ 
990+std::shared_ptr<UbseCliResultEcho> UbseCliRegMemModule::RemoveProcessMemFunc(
991+ const std::map<std::string, std::string>& params)
969{992{
970 if (!UbseCliRegModule::DisableTimeoutTimer()) {993 if (!UbseCliRegModule::DisableTimeoutTimer()) {
971 return UbseCliStringPromptReply(SET_TIMER_ERROR);994 return UbseCliStringPromptReply(SET_TIMER_ERROR);
972 }995 }
973- auto pid = params.find(PID_OPTION);996+ auto pidIt = params.find(PID_OPTION);
974- if (pid == params.end()) {997+ auto nameIt = params.find(NAME_OPTION);
975- return UbseCliStringPromptReply("ERROR: The request option -p or --pid is required.");998+ if (pidIt == params.end() && nameIt == params.end()) {
999+ return UbseCliStringPromptReply("ERROR: The request option -p/--pid or -n/--name is required.");
976 }1000 }
977- auto errMsg = ValidatePid(pid->second);1001+ if (pidIt != params.end() && nameIt != params.end()) {
1002+ return UbseCliStringPromptReply("ERROR: --pid and --name are mutually exclusive.");
1003+ }
1004+ if (nameIt != params.end()) {
1005+ if (!CheckProcName(nameIt->second)) {
1006+ return UbseCliStringPromptReply(FormatNameParamError(MAX_PROC_NAME_LENGTH));
1007+ }
1008+ return DoRemoveByName(nameIt->second);
1009+ }
1010+ auto errMsg = ValidatePid(pidIt->second);
978 if (!errMsg.empty()) {1011 if (!errMsg.empty()) {
979- return UbseCliStringPromptReply(errMsg);1012+ return UbseCliStringPromptReply("ERROR: " + errMsg);
980 }1013 }
981- pid_t tmpPid = 0;1014+ return DoRemoveByPid(pidIt->second);
982- utils::ConvertStrToInt(pid->second, tmpPid);
983- UbseCliMemPid memPid{};
984- return memPid.UbseCliUnsetPid(tmpPid);
985}1015}
986 1016 
987UbseCliCommandInfo UbseCliRegMemModule::RemoveMemory()1017UbseCliCommandInfo UbseCliRegMemModule::RemoveMemory()
@@ -990,7 +1020,8 @@ UbseCliCommandInfo UbseCliRegMemModule::RemoveMemory()
990 builder.UbseCliSetCommand("remove")1020 builder.UbseCliSetCommand("remove")
991 .UbseCliSetType("process-mem")1021 .UbseCliSetType("process-mem")
992 .UbseCliAddOption("p", PID_OPTION, PID_OPTION_TIP)1022 .UbseCliAddOption("p", PID_OPTION, PID_OPTION_TIP)
993- .UbseCliSetFunc(PidUnSetFunc);1023+ .UbseCliAddOption("n", NAME_OPTION, NAME_OPTION_TIP)
1024+ .UbseCliSetFunc(RemoveProcessMemFunc);
994 return builder.UbseCliBuild();1025 return builder.UbseCliBuild();
995}1026}
996 1027 
@@ -1019,7 +1050,7 @@ std::shared_ptr<UbseCliResultEcho> UbseCliRegMemModule::DeleteMemoryFunc(
1019 if (!CheckName(name->second)) {1050 if (!CheckName(name->second)) {
1020 return UbseCliStringPromptReply(DELETE_MEM_NAME_PARAM_INVALID);1051 return UbseCliStringPromptReply(DELETE_MEM_NAME_PARAM_INVALID);
1021 }1052 }
1022- std::string deleteType = "numa"; // 默认使用numa1053+ std::string deleteType = "numa";
1023 auto type = params.find(DELETE_MEM_T_OPTION);1054 auto type = params.find(DELETE_MEM_T_OPTION);
1024 if (type != params.end()) {1055 if (type != params.end()) {
1025 if (!CheckDeleteType(type->second)) {1056 if (!CheckDeleteType(type->second)) {
@@ -1037,7 +1068,6 @@ std::shared_ptr<UbseCliResultEcho> UbseCliRegMemModule::DeleteMemoryFunc(
1037 UbseCliWaitIndicator waitIndicator("Deleting memory");1068 UbseCliWaitIndicator waitIndicator("Deleting memory");
1038 uint32_t ret = ubse_invoke_call(MEM_MODULE_CODE, MEMORY_DELETE_OP_CODE, &reqBuffer, &resBuffer);1069 uint32_t ret = ubse_invoke_call(MEM_MODULE_CODE, MEMORY_DELETE_OP_CODE, &reqBuffer, &resBuffer);
1039 UbseCliBufferGuard ubseCliBufferGuard(resBuffer);1070 UbseCliBufferGuard ubseCliBufferGuard(resBuffer);
1040- // 处理超时错误
1041 if (ret == UBSE_ERR_TIMED_OUT) {1071 if (ret == UBSE_ERR_TIMED_OUT) {
1042 return HandleTimeoutRetry(name->second);1072 return HandleTimeoutRetry(name->second);
1043 }1073 }
@@ -1045,14 +1075,12 @@ std::shared_ptr<UbseCliResultEcho> UbseCliRegMemModule::DeleteMemoryFunc(
1045 return UbseCliStringPromptReply(GetErrorMessage(ret));1075 return UbseCliStringPromptReply(GetErrorMessage(ret));
1046 }1076 }
1047 UbseDeSerialization deserial{resBuffer.buffer, resBuffer.length};1077 UbseDeSerialization deserial{resBuffer.buffer, resBuffer.length};
1048- // 成功回显
1049 uint32_t errorCode{0};1078 uint32_t errorCode{0};
1050 std::string errMsg;1079 std::string errMsg;
1051 deserial >> errorCode >> errMsg;1080 deserial >> errorCode >> errMsg;
1052 if (deserial.Check() && errorCode == MEM_SUCCESS_CODE) {1081 if (deserial.Check() && errorCode == MEM_SUCCESS_CODE) {
1053 return UbseCliStringPromptReply("Delete successfully");1082 return UbseCliStringPromptReply("Delete successfully");
1054 }1083 }
1055- // 失败回显
1056 if (deserial.Check() && errorCode == UBSE_ERR_NOT_SUPPORTED) {1084 if (deserial.Check() && errorCode == UBSE_ERR_NOT_SUPPORTED) {
1057 return UbseCliStringPromptReply(GetErrorMessage(errorCode));1085 return UbseCliStringPromptReply(GetErrorMessage(errorCode));
1058 }1086 }
@@ -1152,57 +1180,15 @@ UbseCliCommandInfo UbseCliRegMemModule::ShmMemoryDetach()
1152 return builder.UbseCliBuild();1180 return builder.UbseCliBuild();
1153}1181}
1154 1182 
1155-// Threshold limits1183+static constexpr uint64_t PROCESS_MEM_SIZE_MIN = 128ULL * 1024 * 1024;
1156-static constexpr int THRESHOLD_PERCENT_MAX = 100;1184+static constexpr uint64_t PROCESS_MEM_SIZE_MAX = 32ULL * 1024 * 1024 * 1024;
1157-static constexpr int MIN_EVICT_RECLAIM_DELTA = 5;
1158 1185 
1159-// process_mem size range: 128M ~ 32G
1160-static constexpr uint64_t PROCESS_MEM_SIZE_MIN = 128ULL * 1024 * 1024; // 128M
1161-static constexpr uint64_t PROCESS_MEM_SIZE_MAX = 32ULL * 1024 * 1024 * 1024; // 32G
1162- 
1163-// Size conversion constants
1164static constexpr uint64_t BYTES_PER_KIB = 1024ULL;1186static constexpr uint64_t BYTES_PER_KIB = 1024ULL;
1165static constexpr uint64_t BYTES_PER_MIB = BYTES_PER_KIB * BYTES_PER_KIB;1187static constexpr uint64_t BYTES_PER_MIB = BYTES_PER_KIB * BYTES_PER_KIB;
1166static constexpr uint64_t BYTES_PER_GIB = BYTES_PER_KIB * BYTES_PER_KIB * BYTES_PER_KIB;1188static constexpr uint64_t BYTES_PER_GIB = BYTES_PER_KIB * BYTES_PER_KIB * BYTES_PER_KIB;
1167static constexpr int DECIMAL_BASE = 10;1189static constexpr int DECIMAL_BASE = 10;
1168static constexpr int DECIMAL_DIGITS_GROUP = 3;1190static constexpr int DECIMAL_DIGITS_GROUP = 3;
1169 1191 
1170-static std::string JoinStrings(const std::vector<std::string>& parts, const std::string& delim)
1171-{
1172- std::string result;
1173- for (size_t i = 0; i < parts.size(); ++i) {
1174- if (i > 0) {
1175- result += delim;
1176- }
1177- result += parts[i];
1178- }
1179- return result;
1180-}
1181- 
1182-static std::string ValidateThreshold(const std::string& valStr, const std::string& paramName)
1183-{
1184- if (!IsValidIntegerString(valStr)) {
1185- return paramName + " must be an integer, range 1~" + std::to_string(THRESHOLD_PERCENT_MAX);
1186- }
1187- int val = 0;
1188- if (utils::ConvertStrToInt(valStr, val) != UBSE_OK || val <= 0 || val > THRESHOLD_PERCENT_MAX) {
1189- return paramName + " must be an integer, range 1~" + std::to_string(THRESHOLD_PERCENT_MAX);
1190- }
1191- return "";
1192-}
1193- 
1194-static std::string ValidateEvictReclaimDelta(const std::string& evictThreshold, const std::string& reclaimThreshold)
1195-{
1196- int evict = 0;
1197- int reclaim = 0;
1198- if (utils::ConvertStrToInt(evictThreshold, evict) != UBSE_OK ||
1199- utils::ConvertStrToInt(reclaimThreshold, reclaim) != UBSE_OK || (evict - reclaim) < MIN_EVICT_RECLAIM_DELTA) {
1200- return "--evict-thresh must be at least " + std::to_string(MIN_EVICT_RECLAIM_DELTA) +
1201- " higher than --reclaim-thresh to avoid oscillation";
1202- }
1203- return "";
1204-}
1205- 
1206static bool ConvertIntegerPart(uint64_t intPart, uint64_t unitMultiplier, uint64_t& size)1192static bool ConvertIntegerPart(uint64_t intPart, uint64_t unitMultiplier, uint64_t& size)
1207{1193{
1208 if (unitMultiplier == 0) {1194 if (unitMultiplier == 0) {
@@ -1251,7 +1237,6 @@ static bool ConvertDecimalPart(uint64_t intPart, const std::sub_match<std::strin
1251 1237 
1252bool SizeConversion(const std::string& str, uint64_t& size)1238bool SizeConversion(const std::string& str, uint64_t& size)
1253{1239{
1254- // 支持整数 (128M, 1G) 和小数 (1.5G, 0.25M),小数最多2位
1255 std::regex pattern(R"(^(\d+)(\.(\d{1,2}))?([BKMG])$)");1240 std::regex pattern(R"(^(\d+)(\.(\d{1,2}))?([BKMG])$)");
1256 std::smatch match;1241 std::smatch match;
1257 if (!std::regex_match(str, match, pattern)) {1242 if (!std::regex_match(str, match, pattern)) {
@@ -1283,125 +1268,139 @@ bool SizeConversion(const std::string& str, uint64_t& size)
1283 return ConvertDecimalPart(intPart, match[DECIMAL_DIGITS_GROUP], unitMultiplier, size);1268 return ConvertDecimalPart(intPart, match[DECIMAL_DIGITS_GROUP], unitMultiplier, size);
1284}1269}
1285 1270 
1286-static std::string ValidateSize(const std::string& sizeStr, uint64_t& size)1271+static std::string ConvertSizeToBytes(const std::string& valStr, uint64_t& outBytes)
1287{1272{
1288- if (!SizeConversion(sizeStr, size)) {1273+ if (SizeConversion(valStr, outBytes)) {
1289- return "--size: invalid format, supported range is 128M~32G (e.g., 128M, 1G, 1.5G)";1274+ return "";
1290 }1275 }
1291- if (size < PROCESS_MEM_SIZE_MIN || size > PROCESS_MEM_SIZE_MAX) {1276+ std::regex pattern(R"(^(\d+)(\.(\d{1,2}))?$)");
1292- return "--size: out of range, supported range is 128M~32G";1277+ std::smatch match;
1278+ if (!std::regex_match(valStr, match, pattern)) {
1279+ return SIZE_INVALID_FORMAT;
1280+ }
1281+ uint64_t intPart{0};
1282+ if (ubse::utils::ConvertStrToUint64(match[1], intPart) != UBSE_OK) {
1283+ return SIZE_INVALID_FORMAT;
1284+ }
1285+ if (match[DECIMAL_DIGITS_GROUP].matched) {
1286+ if (!ConvertDecimalPart(intPart, match[DECIMAL_DIGITS_GROUP], BYTES_PER_MIB, outBytes)) {
1287+ return SIZE_INVALID_FORMAT;
1288+ }
1289+ } else {
1290+ if (!ConvertIntegerPart(intPart, BYTES_PER_MIB, outBytes)) {
1291+ return SIZE_INVALID_FORMAT;
1292+ }
1293 }1293 }
1294 return "";1294 return "";
1295}1295}
1296 1296 
1297-static auto FindRequiredParam(const std::map<std::string, std::string>& params, const std::string& key,1297+static std::string ValidateRemoteRatio(const std::string& valStr)
1298- const std::string& displayName, std::vector<std::string>& missingParams)
1299- -> std::map<std::string, std::string>::const_iterator
1300{1298{
1301- auto it = params.find(key);1299+ if (valStr.empty()) {
1302- if (it == params.end()) {1300+ return REMOTE_RATIO_INVALID_FORMAT;
1303- missingParams.push_back(displayName);
1304 }1301 }
1305- return it;1302+ std::regex pattern(R"(^\d+(\.\d+)?$)");
1306-}1303+ if (!std::regex_match(valStr, pattern)) {
1307- 1304+ return REMOTE_RATIO_INVALID_FORMAT;
1308-static void CheckAndPushError(const std::string& err, std::vector<std::string>& errors)
1309-{
1310- if (!err.empty()) {
1311- errors.push_back(err);
1312 }1305 }
1313-}1306+ try {
1314- 1307+ double ratio = std::stod(valStr);
1315-struct ValidatedPidThresholdResult {1308+ if (ratio < 0.0 || ratio > 1.0) {
1316- std::string pidVal;1309+ return REMOTE_RATIO_INVALID_FORMAT;
1317- std::string evictThreshold;1310+ }
1318- std::string targetEvictThreshold;1311+ } catch (...) {
1319- std::string reclaimThreshold;1312+ return REMOTE_RATIO_INVALID_FORMAT;
1320- uint64_t size{};
1321-};
1322- 
1323-static std::string ValidatePidSetThresholdParams(const std::map<std::string, std::string>& params,
1324- ValidatedPidThresholdResult& out)
1325-{
1326- std::vector<std::string> missingParams;
1327- auto pid = FindRequiredParam(params, PID_OPTION, "--pid", missingParams);
1328- auto evictIt =
1329- FindRequiredParam(params, EVICT_THRESHOLD_OPTION, "--" + std::string(EVICT_THRESHOLD_OPTION), missingParams);
1330- auto targetEvictIt = FindRequiredParam(params, TARGET_EVICT_THRESHOLD_OPTION,
1331- "--" + std::string(TARGET_EVICT_THRESHOLD_OPTION), missingParams);
1332- auto reclaimIt = FindRequiredParam(params, RECLAIM_THRESHOLD_OPTION, "--" + std::string(RECLAIM_THRESHOLD_OPTION),
1333- missingParams);
1334- auto sizeIt = FindRequiredParam(params, SIZE_OPTION, "--" + std::string(SIZE_OPTION), missingParams);
1335- if (!missingParams.empty()) {
1336- return "ERROR: missing required parameter(s): " + JoinStrings(missingParams, ", ");
1337- }
1338- 
1339- out.pidVal = pid->second;
1340- out.evictThreshold = evictIt->second;
1341- out.targetEvictThreshold = targetEvictIt->second;
1342- out.reclaimThreshold = reclaimIt->second;
1343- 
1344- std::vector<std::string> errors;
1345- CheckAndPushError(ValidatePid(out.pidVal), errors);
1346- auto evictErr = ValidateThreshold(out.evictThreshold, "--evict-thresh");
1347- CheckAndPushError(evictErr, errors);
1348- CheckAndPushError(ValidateThreshold(out.targetEvictThreshold, "--target-evict-thresh"), errors);
1349- auto reclaimErr = ValidateThreshold(out.reclaimThreshold, "--reclaim-thresh");
1350- CheckAndPushError(reclaimErr, errors);
1351- if (evictErr.empty() && reclaimErr.empty()) {
1352- CheckAndPushError(ValidateEvictReclaimDelta(out.evictThreshold, out.reclaimThreshold), errors);
1353- }
1354- CheckAndPushError(ValidateSize(sizeIt->second, out.size), errors);
1355- if (!errors.empty()) {
1356- return "ERROR: " + JoinStrings(errors, "; ");
1357 }1313 }
1358 return "";1314 return "";
1359}1315}
1360 1316 
1361-process_mem::def::ProcessMemPidInfo SetPidManagerInfo(const std::string& pidStr, const std::string& evictThresholdStr,1317+static std::string ValidateChangePidName(const std::map<std::string, std::string>& params,
1362- const std::string& targetEvictThresholdStr,1318+ process_mem::def::ProcessMemNewConfigInfo& newConfig)
1363- const std::string& reclaimThresholdStr, uint64_t size)
1364{1319{
1365- process_mem::def::ProcessMemPidInfo tmpInfo{};1320+ auto pidIt = params.find(PID_OPTION);
1366- tmpInfo.configInfo.expectedMemoryUsage = size;1321+ auto nameIt = params.find(NAME_OPTION);
1367- if (utils::ConvertStrToInt(pidStr, tmpInfo.configInfo.pid) != UBSE_OK ||1322+ if (pidIt == params.end() && nameIt == params.end()) {
1368- utils::ConvertStrToInt(evictThresholdStr, tmpInfo.configInfo.evictThreshold) != UBSE_OK ||1323+ return "ERROR: -p/--pid or -n/--name is required.";
1369- utils::ConvertStrToInt(targetEvictThresholdStr, tmpInfo.configInfo.targetEvictThreshold) != UBSE_OK ||
1370- utils::ConvertStrToInt(reclaimThresholdStr, tmpInfo.configInfo.reclaimThreshold) != UBSE_OK) {
1371- tmpInfo.configInfo.pid = -1;
1372 }1324 }
1373- return tmpInfo;1325+ if (pidIt != params.end() && nameIt != params.end()) {
1326+ return "ERROR: --pid and --name are mutually exclusive.";
1327+ }
1328+ if (pidIt != params.end()) {
1329+ auto errMsg = ValidatePid(pidIt->second);
1330+ if (!errMsg.empty()) {
1331+ return "ERROR: " + errMsg;
1332+ }
1333+ newConfig.isPid = true;
1334+ newConfig.identifier = pidIt->second;
1335+ } else {
1336+ if (!CheckProcName(nameIt->second)) {
1337+ return FormatNameParamError(MAX_PROC_NAME_LENGTH);
1338+ }
1339+ newConfig.isPid = false;
1340+ newConfig.identifier = nameIt->second;
1341+ }
1342+ return "";
1374}1343}
1375 1344 
1376-std::shared_ptr<UbseCliResultEcho> UbseCliRegMemModule::PidSetThresholdFunc(1345+static std::string ValidateChangeSize(const std::map<std::string, std::string>& params,
1346+ process_mem::def::ProcessMemNewConfigInfo& newConfig)
1347+{
1348+ auto sizeIt = params.find(SIZE_OPTION);
1349+ if (sizeIt == params.end()) {
1350+ return "ERROR: -s/--size is required.";
1351+ }
1352+ uint64_t sizeBytes = 0;
1353+ auto sizeErr = ConvertSizeToBytes(sizeIt->second, sizeBytes);
1354+ if (!sizeErr.empty()) {
1355+ return sizeErr;
1356+ }
1357+ if (sizeBytes < PROCESS_MEM_SIZE_MIN || sizeBytes > PROCESS_MEM_SIZE_MAX) {
1358+ UBSE_LOG_ERROR << "size out of range: value=" << sizeBytes << " (valid: " << PROCESS_MEM_SIZE_MIN << "-"
1359+ << PROCESS_MEM_SIZE_MAX << ")";
1360+ return "ERROR: -s/--size out of range, supported range is 128M~32G";
1361+ }
1362+ newConfig.maxMemory = sizeBytes;
1363+ return "";
1364+}
1365+ 
1366+static std::string ValidateChangeRemoteRatio(const std::map<std::string, std::string>& params,
1367+ process_mem::def::ProcessMemNewConfigInfo& newConfig)
1368+{
1369+ auto remoteRatioIt = params.find(REMOTE_RATIO_OPTION);
1370+ if (remoteRatioIt == params.end()) {
1371+ return "ERROR: -r/--remote-ratio is required.";
1372+ }
1373+ auto ratioErr = ValidateRemoteRatio(remoteRatioIt->second);
1374+ if (!ratioErr.empty()) {
1375+ return ratioErr;
1376+ }
1377+ newConfig.remoteRatio = std::stod(remoteRatioIt->second);
1378+ return "";
1379+}
1380+ 
1381+std::shared_ptr<UbseCliResultEcho> UbseCliRegMemModule::ChangeProcessMemFunc(
1377 const std::map<std::string, std::string>& params)1382 const std::map<std::string, std::string>& params)
1378{1383{
1379 if (!UbseCliRegModule::DisableTimeoutTimer()) {1384 if (!UbseCliRegModule::DisableTimeoutTimer()) {
1380 return UbseCliStringPromptReply(SET_TIMER_ERROR);1385 return UbseCliStringPromptReply(SET_TIMER_ERROR);
1381 }1386 }
1382 1387 
1383- ValidatedPidThresholdResult validated;1388+ process_mem::def::ProcessMemNewConfigInfo newConfig{};
1384- auto errMsg = ValidatePidSetThresholdParams(params, validated);1389+ auto errMsg = ValidateChangePidName(params, newConfig);
1390+ if (!errMsg.empty()) {
1391+ return UbseCliStringPromptReply(errMsg);
1392+ }
1393+ errMsg = ValidateChangeSize(params, newConfig);
1394+ if (!errMsg.empty()) {
1395+ return UbseCliStringPromptReply(errMsg);
1396+ }
1397+ errMsg = ValidateChangeRemoteRatio(params, newConfig);
1385 if (!errMsg.empty()) {1398 if (!errMsg.empty()) {
1386 return UbseCliStringPromptReply(errMsg);1399 return UbseCliStringPromptReply(errMsg);
1387 }1400 }
1388 1401 
1389- auto pidInfo = SetPidManagerInfo(validated.pidVal, validated.evictThreshold, validated.targetEvictThreshold,
1390- validated.reclaimThreshold, validated.size);
1391- if (pidInfo.configInfo.pid == -1) {
1392- return UbseCliStringPromptReply("Internal error");
1393- }
1394- 
1395- auto srcNumaIdParam = params.find(SRC_NUMAID_OPTION);
1396- if (srcNumaIdParam != params.end()) {
1397- uint64_t srcNumaId;
1398- if (utils::ConvertStrToUint64(srcNumaIdParam->second, srcNumaId) != UBSE_OK) {
1399- return UbseCliStringPromptReply("Invalid --src-numa value");
1400- }
1401- pidInfo.configInfo.srcNumaId = srcNumaId;
1402- }
1403 UbseCliMemPid memPid{};1402 UbseCliMemPid memPid{};
1404- return memPid.UbseCliSetPidThreshold(pidInfo);1403+ return memPid.UbseCliSetProcessMem(newConfig);
1405}1404}
1406 1405 
1407} // namespace ubse::cli::reg1406} // namespace ubse::cli::reg
@@ -67,10 +67,12 @@ private:
67 67 
68 static std::shared_ptr<UbseCliResultEcho> CreateMemoryFunc(const std::map<std::string, std::string>& params);68 static std::shared_ptr<UbseCliResultEcho> CreateMemoryFunc(const std::map<std::string, std::string>& params);
69 69 
70- static std::shared_ptr<UbseCliResultEcho> PidSetThresholdFunc(const std::map<std::string, std::string>& params);70+ static std::shared_ptr<UbseCliResultEcho> RemoveProcessMemFunc(const std::map<std::string, std::string>& params);
71 71 
72- static std::shared_ptr<UbseCliResultEcho> PidUnSetFunc(const std::map<std::string, std::string>& params);72+ static std::shared_ptr<UbseCliResultEcho> ChangeProcessMemFunc(const std::map<std::string, std::string>& params);
73};73};
74bool CheckName(const std::string& name);74bool CheckName(const std::string& name);
75+bool CheckName(const std::string& name, int maxLength);
76+bool CheckProcName(const std::string& name);
75} // namespace ubse::cli::reg77} // namespace ubse::cli::reg
76-#endif78+#endif
@@ -86,9 +86,6 @@ typedef enum
86 UBSE_MEM_CLI_SHM_ATTACH = 0x0045, // SHM链接(CLI)86 UBSE_MEM_CLI_SHM_ATTACH = 0x0045, // SHM链接(CLI)
87 UBSE_MEM_CLI_SHM_DETACH = 0x0046, // SHM释放(CLI)87 UBSE_MEM_CLI_SHM_DETACH = 0x0046, // SHM释放(CLI)
88 UBSE_MEM_CLI_MEMORY_DELETE_BY_NAME_TYPE = 0x0047, // 内存删除(聚合接口)(CLI)88 UBSE_MEM_CLI_MEMORY_DELETE_BY_NAME_TYPE = 0x0047, // 内存删除(聚合接口)(CLI)
89- UBSE_MEM_CLI_PID_SET_THRESHOLD = 0x0048, // PID阈值设置(CLI) [DEPRECATED:旧模型,待CLI切换后移除]
90- UBSE_MEM_CLI_PRINT_PID_INFO = 0x0049, // PID信息查询(CLI) [DEPRECATED:旧模型,待CLI切换后移除]
91- UBSE_MEM_CLI_PID_UNSET = 0x0050, // PID配置删除(CLI) [DEPRECATED:旧模型,待CLI切换后移除]
92 // 0x0048~0x0050 已废弃(旧PID阈值模型), 0x0051~0x0052 为其他特性预留, 禁止复用89 // 0x0048~0x0050 已废弃(旧PID阈值模型), 0x0051~0x0052 为其他特性预留, 禁止复用
93 UBSE_MEM_CLI_PROC_MEM_SET = 0x0053, // process-mem新模型设置(CLI)90 UBSE_MEM_CLI_PROC_MEM_SET = 0x0053, // process-mem新模型设置(CLI)
94 UBSE_MEM_CLI_PROC_MEM_REMOVE = 0x0054, // process-mem新模型删除(CLI)91 UBSE_MEM_CLI_PROC_MEM_REMOVE = 0x0054, // process-mem新模型删除(CLI)
@@ -210,7 +210,6 @@ enum class UbseMemFaultOpCode
210 UBSE_SHARE_MEM_FAULT_NOTIFY_REPLY = 0x0004, // 共享内存故障通知回复210 UBSE_SHARE_MEM_FAULT_NOTIFY_REPLY = 0x0004, // 共享内存故障通知回复
211 UBSE_MEM_FAULT_BMC_AGENTS = 0x0005, // MemFault在master收到BMC故障通知,传递给agents.211 UBSE_MEM_FAULT_BMC_AGENTS = 0x0005, // MemFault在master收到BMC故障通知,传递给agents.
212 UBSE_SINGLE_IMPORT_DEBT_NOTIFY = 0x0006, // Master通知Agent单导入债务信息212 UBSE_SINGLE_IMPORT_DEBT_NOTIFY = 0x0006, // Master通知Agent单导入债务信息
213- UBSE_PROCESS_MEM_NODE_FAULT_NOTIFY = 0x0007, // Master通知Agent处理process_mem节点故障 [DEPRECATED:旧模型,待bridge切换后移除]
214 UBSE_PROCESS_MEM_RETURN_REQUEST = 0x0008, // 借出方通知借入方归还 process_mem 远端内存213 UBSE_PROCESS_MEM_RETURN_REQUEST = 0x0008, // 借出方通知借入方归还 process_mem 远端内存
215};214};
216 215 
@@ -694,117 +694,3 @@ uint32_t mock_urma_qos_invoke_call_not_applied(uint16_t module_code, uint16_t op
694 }694 }
695 return UBSE_URMACONTRL_ERROR_ETS_TEMPLATE_NOT_APPLIED;695 return UBSE_URMACONTRL_ERROR_ETS_TEMPLATE_NOT_APPLIED;
696}696}
697- 
698-uint32_t mock_pid_op_success(uint16_t module_code, uint16_t op_code, const ubse_api_buffer_t* request_data,
699- ubse_api_buffer_t* response_data)
700-{
701- UbseSerialization ser;
702- int successCode = 1;
703- std::string errMsg;
704- ser << successCode << errMsg;
705- response_data->buffer = static_cast<uint8_t*>(malloc(ser.GetLength()));
706- response_data->length = 0;
707- if (!response_data->buffer) {
708- return UBSE_ERROR;
709- }
710- response_data->length = (uint32_t)ser.GetLength();
711- if (memcpy_s(response_data->buffer, ser.GetLength(), ser.GetBuffer(), ser.GetLength()) != EOK) {
712- free(response_data->buffer);
713- response_data->buffer = nullptr;
714- response_data->length = 0;
715- return UBSE_ERROR;
716- }
717- return UBSE_OK;
718-}
719- 
720-uint32_t mock_pid_op_failed(uint16_t module_code, uint16_t op_code, const ubse_api_buffer_t* request_data,
721- ubse_api_buffer_t* response_data)
722-{
723- UbseSerialization ser;
724- int successCode = 0;
725- std::string errMsg = "test error";
726- ser << successCode << errMsg;
727- response_data->buffer = static_cast<uint8_t*>(malloc(ser.GetLength()));
728- response_data->length = 0;
729- if (!response_data->buffer) {
730- return UBSE_ERROR;
731- }
732- response_data->length = (uint32_t)ser.GetLength();
733- if (memcpy_s(response_data->buffer, ser.GetLength(), ser.GetBuffer(), ser.GetLength()) != EOK) {
734- free(response_data->buffer);
735- response_data->buffer = nullptr;
736- response_data->length = 0;
737- return UBSE_ERROR;
738- }
739- return UBSE_OK;
740-}
741- 
742-uint32_t mock_pid_print_empty(uint16_t module_code, uint16_t op_code, const ubse_api_buffer_t* request_data,
743- ubse_api_buffer_t* response_data)
744-{
745- UbseSerialization ser;
746- size_t count = 0;
747- ser << count;
748- response_data->buffer = static_cast<uint8_t*>(malloc(ser.GetLength()));
749- response_data->length = 0;
750- if (!response_data->buffer) {
751- return UBSE_ERROR;
752- }
753- response_data->length = (uint32_t)ser.GetLength();
754- if (memcpy_s(response_data->buffer, ser.GetLength(), ser.GetBuffer(), ser.GetLength()) != EOK) {
755- free(response_data->buffer);
756- response_data->buffer = nullptr;
757- response_data->length = 0;
758- return UBSE_ERROR;
759- }
760- return UBSE_OK;
761-}
762- 
763-uint32_t mock_pid_print_success(uint16_t module_code, uint16_t op_code, const ubse_api_buffer_t* request_data,
764- ubse_api_buffer_t* response_data)
765-{
766- UbseSerialization ser;
767- size_t count = 1;
768- ser << count;
769- int pidVal = 42;
770- int evictThreshold = 80;
771- int targetEvictThreshold = 70;
772- int reclaimThreshold = 60;
773- uint64_t expectedMemoryUsage = 1024;
774- uint64_t hasSrcNuma = 0;
775- ser << pidVal << evictThreshold << targetEvictThreshold << reclaimThreshold << expectedMemoryUsage << hasSrcNuma;
776- response_data->buffer = static_cast<uint8_t*>(malloc(ser.GetLength()));
777- response_data->length = 0;
778- if (!response_data->buffer) {
779- return UBSE_ERROR;
780- }
781- response_data->length = (uint32_t)ser.GetLength();
782- if (memcpy_s(response_data->buffer, ser.GetLength(), ser.GetBuffer(), ser.GetLength()) != EOK) {
783- free(response_data->buffer);
784- response_data->buffer = nullptr;
785- response_data->length = 0;
786- return UBSE_ERROR;
787- }
788- return UBSE_OK;
789-}
790- 
791-uint32_t mock_pid_print_deserialize_failed(uint16_t module_code, uint16_t op_code,
792- const ubse_api_buffer_t* request_data, ubse_api_buffer_t* response_data)
793-{
794- UbseSerialization ser;
795- std::string garbage = "not a size_t value";
796- ser << garbage;
797- response_data->buffer = static_cast<uint8_t*>(malloc(ser.GetLength()));
798- response_data->length = 0;
799- if (!response_data->buffer) {
800- return UBSE_ERROR;
801- }
802- response_data->length = (uint32_t)ser.GetLength();
803- if (memcpy_s(response_data->buffer, ser.GetLength(), ser.GetBuffer(), ser.GetLength()) != EOK) {
804- free(response_data->buffer);
805- response_data->buffer = nullptr;
806- response_data->length = 0;
807- return UBSE_ERROR;
808- }
809- return UBSE_OK;
810-}
@@ -127,16 +127,4 @@ uint32_t mock_node_info_ubse_invoke_call_success(uint16_t module_code, uint16_t
127uint32_t mock_urma_qos_invoke_call_not_applied(uint16_t module_code, uint16_t op_code,127uint32_t mock_urma_qos_invoke_call_not_applied(uint16_t module_code, uint16_t op_code,
128 const ubse_api_buffer_t* request_data, ubse_api_buffer_t* response_data);128 const ubse_api_buffer_t* request_data, ubse_api_buffer_t* response_data);
129 129 
130-// Process Mem PID mocks
131-uint32_t mock_pid_op_success(uint16_t module_code, uint16_t op_code, const ubse_api_buffer_t* request_data,
132- ubse_api_buffer_t* response_data);
133-uint32_t mock_pid_op_failed(uint16_t module_code, uint16_t op_code, const ubse_api_buffer_t* request_data,
134- ubse_api_buffer_t* response_data);
135-uint32_t mock_pid_print_empty(uint16_t module_code, uint16_t op_code, const ubse_api_buffer_t* request_data,
136- ubse_api_buffer_t* response_data);
137-uint32_t mock_pid_print_success(uint16_t module_code, uint16_t op_code, const ubse_api_buffer_t* request_data,
138- ubse_api_buffer_t* response_data);
139-uint32_t mock_pid_print_deserialize_failed(uint16_t module_code, uint16_t op_code,
140- const ubse_api_buffer_t* request_data, ubse_api_buffer_t* response_data);
141- 
142#endif130#endif
@@ -16,106 +16,4 @@ void TestUbseCliMemPid::SetUp() {}
16 16 
17void TestUbseCliMemPid::TearDown() {}17void TestUbseCliMemPid::TearDown() {}
18 18 
19-TEST_F(TestUbseCliMemPid, SetPidThresholdInvokeFailed)
20-{
21- UbseCliMemPid pidModule;
22- MOCKER(&ubse_invoke_call).stubs().will(invoke(mock_ubse_invoke_call_failed));
23- ProcessMemPidInfo pidInfo;
24- auto result = pidModule.UbseCliSetPidThreshold(pidInfo);
25- ASSERT_NE(result, nullptr);
26- EXPECT_NO_THROW(result->UbseCliDisplayResult());
27- MOCKER(&ubse_invoke_call).reset();
28-}
29- 
30-TEST_F(TestUbseCliMemPid, SetPidThresholdSuccess)
31-{
32- UbseCliMemPid pidModule;
33- MOCKER(&ubse_invoke_call).stubs().will(invoke(mock_pid_op_success));
34- ProcessMemPidInfo pidInfo;
35- auto result = pidModule.UbseCliSetPidThreshold(pidInfo);
36- ASSERT_NE(result, nullptr);
37- EXPECT_NO_THROW(result->UbseCliDisplayResult());
38- MOCKER(&ubse_invoke_call).reset();
39-}
40- 
41-TEST_F(TestUbseCliMemPid, SetPidThresholdFailed)
42-{
43- UbseCliMemPid pidModule;
44- MOCKER(&ubse_invoke_call).stubs().will(invoke(mock_pid_op_failed));
45- ProcessMemPidInfo pidInfo;
46- auto result = pidModule.UbseCliSetPidThreshold(pidInfo);
47- ASSERT_NE(result, nullptr);
48- EXPECT_NO_THROW(result->UbseCliDisplayResult());
49- MOCKER(&ubse_invoke_call).reset();
50-}
51- 
52-TEST_F(TestUbseCliMemPid, PrintPidInfoInvokeFailed)
53-{
54- UbseCliMemPid pidModule;
55- MOCKER(&ubse_invoke_call).stubs().will(invoke(mock_ubse_invoke_call_failed));
56- auto result = pidModule.UbseCliPrintPidInfo();
57- ASSERT_NE(result, nullptr);
58- EXPECT_NO_THROW(result->UbseCliDisplayResult());
59- MOCKER(&ubse_invoke_call).reset();
60-}
61- 
62-TEST_F(TestUbseCliMemPid, PrintPidInfoEmpty)
63-{
64- UbseCliMemPid pidModule;
65- MOCKER(&ubse_invoke_call).stubs().will(invoke(mock_pid_print_empty));
66- auto result = pidModule.UbseCliPrintPidInfo();
67- ASSERT_NE(result, nullptr);
68- EXPECT_NO_THROW(result->UbseCliDisplayResult());
69- MOCKER(&ubse_invoke_call).reset();
70-}
71- 
72-TEST_F(TestUbseCliMemPid, PrintPidInfoDeserializeFailed)
73-{
74- UbseCliMemPid pidModule;
75- MOCKER(&ubse_invoke_call).stubs().will(invoke(mock_pid_print_deserialize_failed));
76- auto result = pidModule.UbseCliPrintPidInfo();
77- ASSERT_NE(result, nullptr);
78- EXPECT_NO_THROW(result->UbseCliDisplayResult());
79- MOCKER(&ubse_invoke_call).reset();
80-}
81- 
82-TEST_F(TestUbseCliMemPid, PrintPidInfoSuccess)
83-{
84- UbseCliMemPid pidModule;
85- MOCKER(&ubse_invoke_call).stubs().will(invoke(mock_pid_print_success));
86- auto result = pidModule.UbseCliPrintPidInfo();
87- ASSERT_NE(result, nullptr);
88- EXPECT_NO_THROW(result->UbseCliDisplayResult());
89- MOCKER(&ubse_invoke_call).reset();
90-}
91- 
92-TEST_F(TestUbseCliMemPid, UnsetPidInvokeFailed)
93-{
94- UbseCliMemPid pidModule;
95- MOCKER(&ubse_invoke_call).stubs().will(invoke(mock_ubse_invoke_call_failed));
96- auto result = pidModule.UbseCliUnsetPid(123);
97- ASSERT_NE(result, nullptr);
98- EXPECT_NO_THROW(result->UbseCliDisplayResult());
99- MOCKER(&ubse_invoke_call).reset();
100-}
101- 
102-TEST_F(TestUbseCliMemPid, UnsetPidSuccess)
103-{
104- UbseCliMemPid pidModule;
105- MOCKER(&ubse_invoke_call).stubs().will(invoke(mock_pid_op_success));
106- auto result = pidModule.UbseCliUnsetPid(123);
107- ASSERT_NE(result, nullptr);
108- EXPECT_NO_THROW(result->UbseCliDisplayResult());
109- MOCKER(&ubse_invoke_call).reset();
110-}
111- 
112-TEST_F(TestUbseCliMemPid, UnsetPidFailed)
113-{
114- UbseCliMemPid pidModule;
115- MOCKER(&ubse_invoke_call).stubs().will(invoke(mock_pid_op_failed));
116- auto result = pidModule.UbseCliUnsetPid(123);
117- ASSERT_NE(result, nullptr);
118- EXPECT_NO_THROW(result->UbseCliDisplayResult());
119- MOCKER(&ubse_invoke_call).reset();
120-}
121} // namespace ubse::ut::cli19} // namespace ubse::ut::cli
@@ -6,7 +6,8 @@ add_executable(${CMAKE_PROJECT_NAME}_process_mem_ut EXCLUDE_FROM_ALL
6 ${PROCESS_MEM_TEST_SOURCES} ${MOCK_SOURCES}6 ${PROCESS_MEM_TEST_SOURCES} ${MOCK_SOURCES}
7 ${SRC_DIR}/process_mem_pid_bridge.cpp ${SRC_DIR}/process_mem_pid_collect.cpp7 ${SRC_DIR}/process_mem_pid_bridge.cpp ${SRC_DIR}/process_mem_pid_collect.cpp
8 ${SRC_DIR}/process_mem_pid_config_manager.cpp ${SRC_DIR}/process_mem_pid_info_manager.cpp8 ${SRC_DIR}/process_mem_pid_config_manager.cpp ${SRC_DIR}/process_mem_pid_info_manager.cpp
9- ${SRC_DIR}/process_mem_plugin.cpp ${PROJECT_SOURCE_DIR}/test/UT/main.cpp)9+ ${SRC_DIR}/process_mem_pid_decision.cpp ${SRC_DIR}/process_mem_plugin.cpp
10+ ${PROJECT_SOURCE_DIR}/test/UT/main.cpp)
10set_target_properties(${CMAKE_PROJECT_NAME}_process_mem_ut PROPERTIES LINK_FLAGS "-Wl,--as-needed")11set_target_properties(${CMAKE_PROJECT_NAME}_process_mem_ut PROPERTIES LINK_FLAGS "-Wl,--as-needed")
11target_compile_options(${CMAKE_PROJECT_NAME}_process_mem_ut PRIVATE12target_compile_options(${CMAKE_PROJECT_NAME}_process_mem_ut PRIVATE
12 -fPIC -w -g -Wno-all -Wno-error -Wno-non-template-friend13 -fPIC -w -g -Wno-all -Wno-error -Wno-non-template-friend
@@ -27,5 +28,5 @@ target_include_directories(${CMAKE_PROJECT_NAME}_process_mem_ut BEFORE PUBLIC
27target_link_options(${CMAKE_PROJECT_NAME}_process_mem_ut PRIVATE -Wl,--relax --coverage -rdynamic -Wl,--as-needed)28target_link_options(${CMAKE_PROJECT_NAME}_process_mem_ut PRIVATE -Wl,--relax --coverage -rdynamic -Wl,--as-needed)
28target_compile_definitions(${CMAKE_PROJECT_NAME}_process_mem_ut PRIVATE UB_ENVIRONMENT ENABLE_COV=OFF)29target_compile_definitions(${CMAKE_PROJECT_NAME}_process_mem_ut PRIVATE UB_ENVIRONMENT ENABLE_COV=OFF)
29target_link_libraries(${CMAKE_PROJECT_NAME}_process_mem_ut PUBLIC30target_link_libraries(${CMAKE_PROJECT_NAME}_process_mem_ut PUBLIC
30- test_exclusive_main ubse_serial mempooling_static rapidjson GTest::gmock_main mockcpp boundscheck)31+ test_exclusive_main ubse_serial rapidjson GTest::gmock_main mockcpp boundscheck trace_context)
31add_independent_exec_ut(${CMAKE_PROJECT_NAME}_process_mem_ut)32add_independent_exec_ut(${CMAKE_PROJECT_NAME}_process_mem_ut)
@@ -106,4 +106,4 @@ struct UbseLog {
106 bool operator==(UbseLoggerEntry& loggerEntry);106 bool operator==(UbseLoggerEntry& loggerEntry);
107};107};
108} // namespace ubse::log108} // namespace ubse::log
109-#endif // UBSE_LOGGER_H109+#endif
@@ -1,25 +1,63 @@
1#pragma once1#pragma once
2 2 
3+#include <string>
4+#include <vector>
5+ 
3#include "ubse_mem_controller.h"6#include "ubse_mem_controller.h"
7+#include "process_mem_pid_bridge.h"
4 8 
5namespace ubse::mem::controller {9namespace ubse::mem::controller {
6void MockSetDebtInfos(const std::vector<UbseNumaMemoryDebtInfo>& infos);10void MockSetDebtInfos(const std::vector<UbseNumaMemoryDebtInfo>& infos);
7void MockSetImportDebtInfos(const std::vector<UbseNumaMemoryImportDebtInfo>& infos);11void MockSetImportDebtInfos(const std::vector<UbseNumaMemoryImportDebtInfo>& infos);
8void MockClearDebtInfos();12void MockClearDebtInfos();
9void MockSetNumaCreateError(uint32_t err);13void MockSetNumaCreateError(uint32_t err);
14+void MockSetNumaCreateErrorOnce(uint32_t err);
10void MockSetNumaDeleteError(uint32_t err);15void MockSetNumaDeleteError(uint32_t err);
16+void MockSetNumaDeleteErrorOnce(uint32_t err);
17+uint32_t MockGetNumaDeleteCallCount();
11void MockSetDebtInfoWithNodeError(uint32_t err);18void MockSetDebtInfoWithNodeError(uint32_t err);
19+void MockSetNumaCreateDesc(int64_t numaId, uint32_t exportSlotId, uint64_t size);
20+std::string MockGetLastNumaCreateName();
21+uint64_t MockGetLastNumaCreateSize();
22+void MockBlockNumaCreate();
23+void MockReleaseNumaCreate();
24+bool MockWaitNumaCreateEntered(int timeoutMs);
25+void MockBlockNumaDelete();
26+void MockReleaseNumaDelete();
27+bool MockWaitNumaDeleteEntered(int timeoutMs);
12void MockResetAllErrors();28void MockResetAllErrors();
29+ 
30+void MockSetNodeNumaInfos(const std::vector<UbseNodeNumaInfo>& infos);
31+std::string MockGetLastNumaDeleteName();
32+void MockAdjustNodeFree(uint64_t deltaBytes);
13} // namespace ubse::mem::controller33} // namespace ubse::mem::controller
14 34 
35+namespace ubse::com {
36+struct MockRpcSendRecord {
37+ std::string address;
38+ std::string payload;
39+};
40+void MockResetRpcState();
41+const std::vector<MockRpcSendRecord>& MockGetRpcSendRecords();
42+} // namespace ubse::com
43+ 
15namespace ubse::nodeController {44namespace ubse::nodeController {
16void MockSetCurrentNodeId(const std::string& nodeId);45void MockSetCurrentNodeId(const std::string& nodeId);
17void MockResetCurrentNodeId();46void MockResetCurrentNodeId();
18} // namespace ubse::nodeController47} // namespace ubse::nodeController
19 48 
49+namespace ubse::task_executor {
50+void MockSetExecutorAsync(bool async);
51+void MockWaitExecutorIdle();
52+} // namespace ubse::task_executor
53+ 
20namespace ubse::storage {54namespace ubse::storage {
21void MockSetStoragePutError(uint32_t err);55void MockSetStoragePutError(uint32_t err);
22void MockSetStorageQueryError(uint32_t err);56void MockSetStorageQueryError(uint32_t err);
23void MockSetStorageDeleteError(uint32_t err);57void MockSetStorageDeleteError(uint32_t err);
58+void MockSetStorageListError(uint32_t err);
59+void MockSetStorageKeys(const std::vector<std::string>& keys);
60+void MockSetStorageQueryPayload(const std::string& keyPrefix, const std::string& key,
61+ const std::vector<uint8_t>& payload);
24void MockResetStorageErrors();62void MockResetStorageErrors();
25} // namespace ubse::storage63} // namespace ubse::storage
@@ -11,9 +11,22 @@
11 */11 */
12 12 
13#include "ubse_com.h"13#include "ubse_com.h"
14+#include "mock_control.h"
14 15 
15namespace ubse::com {16namespace ubse::com {
16 17 
18+static std::vector<MockRpcSendRecord> g_mockRpcSendRecords;
19+ 
20+void MockResetRpcState()
21+{
22+ g_mockRpcSendRecords.clear();
23+}
24+ 
25+const std::vector<MockRpcSendRecord>& MockGetRpcSendRecords()
26+{
27+ return g_mockRpcSendRecords;
28+}
29+ 
17uint32_t UbseRegRpcService(const UbseComEndpoint& endpoint, const UbseComServiceHandler& handler)30uint32_t UbseRegRpcService(const UbseComEndpoint& endpoint, const UbseComServiceHandler& handler)
18{31{
19 return 0;32 return 0;
@@ -22,6 +35,12 @@ uint32_t UbseRegRpcService(const UbseComEndpoint& endpoint, const UbseComService
22uint32_t UbseRpcSend(const UbseComEndpoint& endpoint, const UbseByteBuffer& reqData, void* ctx,35uint32_t UbseRpcSend(const UbseComEndpoint& endpoint, const UbseByteBuffer& reqData, void* ctx,
23 const UbseComRespHandler& handler)36 const UbseComRespHandler& handler)
24{37{
38+ MockRpcSendRecord record;
39+ record.address = endpoint.address;
40+ if (reqData.data != nullptr && reqData.len > 0) {
41+ record.payload.assign(reinterpret_cast<const char*>(reqData.data), reqData.len);
42+ }
43+ g_mockRpcSendRecords.push_back(std::move(record));
25 return 0;44 return 0;
26}45}
27 46 
@@ -10,17 +10,101 @@
10 * See the Mulan PSL v2 for more details.10 * See the Mulan PSL v2 for more details.
11 */11 */
12 12 
13-#include "ubse_mem_controller.h"13+#include <chrono>
14+#include <condition_variable>
15+#include <cstring>
16+#include <mutex>
17+ 
14#include "ubse_error.h"18#include "ubse_error.h"
19+#include "ubse_mem_controller.h"
20+#include "mock_control.h"
15 21 
16namespace ubse::mem::controller {22namespace ubse::mem::controller {
17 23 
18-// Configurable mock data for tests
19static std::vector<UbseNumaMemoryDebtInfo> g_mockDebtInfos;24static std::vector<UbseNumaMemoryDebtInfo> g_mockDebtInfos;
20static std::vector<UbseNumaMemoryImportDebtInfo> g_mockImportDebtInfos;25static std::vector<UbseNumaMemoryImportDebtInfo> g_mockImportDebtInfos;
21static uint32_t g_mockNumaCreateError = UBSE_OK;26static uint32_t g_mockNumaCreateError = UBSE_OK;
27+static uint32_t g_mockNumaCreateErrorOnce = UBSE_OK;
22static uint32_t g_mockNumaDeleteError = UBSE_OK;28static uint32_t g_mockNumaDeleteError = UBSE_OK;
29+static uint32_t g_mockNumaDeleteErrorOnce = UBSE_OK;
30+static uint32_t g_mockNumaDeleteCallCount = 0;
23static uint32_t g_mockDebtInfoWithNodeError = UBSE_OK;31static uint32_t g_mockDebtInfoWithNodeError = UBSE_OK;
32+static std::vector<UbseNodeNumaInfo> g_mockNodeNumaInfos;
33+static int64_t g_mockNumaCreateNumaId = 3;
34+static uint32_t g_mockNumaCreateExportSlot = 7;
35+static uint64_t g_mockNumaCreateSize = 0;
36+static uint64_t g_mockLastNumaCreateOptSize = 0;
37+static std::string g_mockLastNumaCreateName;
38+static std::string g_mockLastNumaDeleteName;
39+ 
40+static std::mutex g_createMutex;
41+static std::condition_variable g_createCv;
42+static bool g_createBlocked = false;
43+static bool g_createEntered = false;
44+static std::condition_variable g_createEnteredCv;
45+ 
46+static std::mutex g_deleteMutex;
47+static std::condition_variable g_deleteCv;
48+static bool g_deleteBlocked = false;
49+static bool g_deleteEntered = false;
50+static std::condition_variable g_deleteEnteredCv;
51+ 
52+void MockBlockNumaCreate()
53+{
54+ std::lock_guard<std::mutex> lock(g_createMutex);
55+ g_createBlocked = true;
56+ g_createEntered = false;
57+}
58+ 
59+void MockReleaseNumaCreate()
60+{
61+ std::lock_guard<std::mutex> lock(g_createMutex);
62+ g_createBlocked = false;
63+ g_createCv.notify_all();
64+}
65+ 
66+bool MockWaitNumaCreateEntered(int timeoutMs)
67+{
68+ std::unique_lock<std::mutex> lock(g_createMutex);
69+ g_createEnteredCv.wait_for(lock, std::chrono::milliseconds(timeoutMs), [] { return g_createEntered; });
70+ return g_createEntered;
71+}
72+ 
73+void MockBlockNumaDelete()
74+{
75+ std::lock_guard<std::mutex> lock(g_deleteMutex);
76+ g_deleteBlocked = true;
77+ g_deleteEntered = false;
78+}
79+ 
80+void MockReleaseNumaDelete()
81+{
82+ std::lock_guard<std::mutex> lock(g_deleteMutex);
83+ g_deleteBlocked = false;
84+ g_deleteCv.notify_all();
85+}
86+ 
87+bool MockWaitNumaDeleteEntered(int timeoutMs)
88+{
89+ std::unique_lock<std::mutex> lock(g_deleteMutex);
90+ g_deleteEnteredCv.wait_for(lock, std::chrono::milliseconds(timeoutMs), [] { return g_deleteEntered; });
91+ return g_deleteEntered;
92+}
93+ 
94+void MockSetNumaCreateDesc(int64_t numaId, uint32_t exportSlotId, uint64_t size)
95+{
96+ g_mockNumaCreateNumaId = numaId;
97+ g_mockNumaCreateExportSlot = exportSlotId;
98+ g_mockNumaCreateSize = size;
99+}
100+std::string MockGetLastNumaCreateName()
101+{
102+ return g_mockLastNumaCreateName;
103+}
104+uint64_t MockGetLastNumaCreateSize()
105+{
106+ return g_mockLastNumaCreateOptSize;
107+}
24 108 
25void MockSetDebtInfos(const std::vector<UbseNumaMemoryDebtInfo>& infos)109void MockSetDebtInfos(const std::vector<UbseNumaMemoryDebtInfo>& infos)
26{110{
@@ -39,21 +123,63 @@ void MockSetNumaCreateError(uint32_t err)
39{123{
40 g_mockNumaCreateError = err;124 g_mockNumaCreateError = err;
41}125}
126+void MockSetNumaCreateErrorOnce(uint32_t err)
127+{
128+ g_mockNumaCreateErrorOnce = err;
129+}
42void MockSetNumaDeleteError(uint32_t err)130void MockSetNumaDeleteError(uint32_t err)
43{131{
44 g_mockNumaDeleteError = err;132 g_mockNumaDeleteError = err;
45}133}
134+void MockSetNumaDeleteErrorOnce(uint32_t err)
135+{
136+ g_mockNumaDeleteErrorOnce = err;
137+}
138+uint32_t MockGetNumaDeleteCallCount()
139+{
140+ return g_mockNumaDeleteCallCount;
141+}
46void MockSetDebtInfoWithNodeError(uint32_t err)142void MockSetDebtInfoWithNodeError(uint32_t err)
47{143{
48 g_mockDebtInfoWithNodeError = err;144 g_mockDebtInfoWithNodeError = err;
49}145}
146+void MockSetNodeNumaInfos(const std::vector<UbseNodeNumaInfo>& infos)
147+{
148+ g_mockNodeNumaInfos = infos;
149+}
150+std::string MockGetLastNumaDeleteName()
151+{
152+ return g_mockLastNumaDeleteName;
153+}
154+ 
155+void MockAdjustNodeFree(uint64_t deltaBytes)
156+{
157+ if (g_mockNodeNumaInfos.empty()) {
158+ return;
159+ }
160+ for (auto& info : g_mockNodeNumaInfos) {
161+ info.memFree += deltaBytes;
162+ }
163+}
50void MockResetAllErrors()164void MockResetAllErrors()
51{165{
52 g_mockDebtInfos.clear();166 g_mockDebtInfos.clear();
53 g_mockImportDebtInfos.clear();167 g_mockImportDebtInfos.clear();
168+ g_mockNodeNumaInfos.clear();
169+ g_mockLastNumaCreateName.clear();
170+ g_mockLastNumaDeleteName.clear();
54 g_mockNumaCreateError = UBSE_OK;171 g_mockNumaCreateError = UBSE_OK;
172+ g_mockNumaCreateErrorOnce = UBSE_OK;
55 g_mockNumaDeleteError = UBSE_OK;173 g_mockNumaDeleteError = UBSE_OK;
174+ g_mockNumaDeleteErrorOnce = UBSE_OK;
175+ g_mockNumaDeleteCallCount = 0;
56 g_mockDebtInfoWithNodeError = UBSE_OK;176 g_mockDebtInfoWithNodeError = UBSE_OK;
177+ g_mockNumaCreateNumaId = 3;
178+ g_mockNumaCreateExportSlot = 7;
179+ g_mockNumaCreateSize = 0;
180+ g_mockLastNumaCreateOptSize = 0;
181+ MockReleaseNumaCreate();
182+ MockReleaseNumaDelete();
57}183}
58 184 
59UbseResult UbseGetNumaMemDebtInfoWithNode(const std::string& nodeId, std::vector<UbseNumaMemoryDebtInfo>& debtInfos)185UbseResult UbseGetNumaMemDebtInfoWithNode(const std::string& nodeId, std::vector<UbseNumaMemoryDebtInfo>& debtInfos)
@@ -69,13 +195,48 @@ UbseResult UbseGetNumaMemDebtInfoWithNode(const std::string& nodeId, std::vector
69 195 
70UbseResult UbseMemNumaDelete(const std::string& name, const UbseMemBorrower& borrower)196UbseResult UbseMemNumaDelete(const std::string& name, const UbseMemBorrower& borrower)
71{197{
198+ ++g_mockNumaDeleteCallCount;
199+ g_mockLastNumaDeleteName = name;
200+ if (g_mockNumaDeleteErrorOnce != UBSE_OK) {
201+ UbseResult onceErr = g_mockNumaDeleteErrorOnce;
202+ g_mockNumaDeleteErrorOnce = UBSE_OK;
203+ return onceErr;
204+ }
205+ {
206+ std::unique_lock<std::mutex> lock(g_deleteMutex);
207+ g_deleteEntered = true;
208+ g_deleteEnteredCv.notify_all();
209+ g_deleteCv.wait(lock, [] { return !g_deleteBlocked; });
210+ }
72 return g_mockNumaDeleteError;211 return g_mockNumaDeleteError;
73}212}
74 213 
75UbseResult UbseMemNumaCreate(const std::string& name, const UbseMemBorrower& borrower, const UbseMemNumaCreateOpt& opt,214UbseResult UbseMemNumaCreate(const std::string& name, const UbseMemBorrower& borrower, const UbseMemNumaCreateOpt& opt,
76 UbseMemNumaDesc& desc)215 UbseMemNumaDesc& desc)
77{216{
78- return g_mockNumaCreateError;217+ if (g_mockNumaCreateErrorOnce != UBSE_OK) {
218+ UbseResult onceErr = g_mockNumaCreateErrorOnce;
219+ g_mockNumaCreateErrorOnce = UBSE_OK;
220+ return onceErr;
221+ }
222+ if (g_mockNumaCreateError != UBSE_OK) {
223+ return g_mockNumaCreateError;
224+ }
225+ g_mockLastNumaCreateName = name;
226+ g_mockLastNumaCreateOptSize = opt.size;
227+ {
228+ std::unique_lock<std::mutex> lock(g_createMutex);
229+ g_createEntered = true;
230+ g_createEnteredCv.notify_all();
231+ g_createCv.wait(lock, [] { return !g_createBlocked; });
232+ }
233+ desc.name = name;
234+ desc.numaId = g_mockNumaCreateNumaId;
235+ desc.exportNode.slotId = g_mockNumaCreateExportSlot;
236+ desc.size = (g_mockNumaCreateSize != 0) ? g_mockNumaCreateSize : opt.size;
237+ memcpy(desc.usrInfo, opt.usrInfo, UBSE_MAX_USR_INFO_LEN);
238+ MockAdjustNodeFree(opt.size);
239+ return UBSE_OK;
79}240}
80 241 
81UbseResult UbseMemNumaCreateWithCandidate(const std::string& name, const UbseMemBorrower& borrower,242UbseResult UbseMemNumaCreateWithCandidate(const std::string& name, const UbseMemBorrower& borrower,
@@ -134,6 +295,9 @@ UbseResult UbseMemDebtCircleCheck(const std::string& srcNodeId, const std::strin
134 295 
135UbseResult UbseGetNodeNumaInfoByNodeId(const std::string& nodeId, std::vector<UbseNodeNumaInfo>& numaNodeInfoList)296UbseResult UbseGetNodeNumaInfoByNodeId(const std::string& nodeId, std::vector<UbseNodeNumaInfo>& numaNodeInfoList)
136{297{
298+ if (!g_mockNodeNumaInfos.empty()) {
299+ numaNodeInfoList = g_mockNodeNumaInfos;
300+ }
137 return UBSE_OK;301 return UBSE_OK;
138}302}
139 303 
@@ -0,0 +1,110 @@
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 <mutex>
14+ 
15+#include "ubse_smap_mock.h"
16+ 
17+namespace ubse::smap {
18+ 
19+static std::mutex g_mtx;
20+static int g_successCount = -1;
21+static int g_failRet = 0;
22+static uint32_t g_callCount = 0;
23+static uint64_t g_callSeq = 0;
24+static std::map<pid_t, std::map<int, uint64_t>> g_targets;
25+static std::map<pid_t, std::map<int, uint64_t>> g_lastCallSeq;
26+static std::vector<std::pair<int, uint64_t>> g_calls;
27+ 
28+void MockResetMigrateState()
29+{
30+ std::lock_guard<std::mutex> lock(g_mtx);
31+ g_successCount = -1;
32+ g_failRet = 0;
33+ g_callCount = 0;
34+ g_callSeq = 0;
35+ g_targets.clear();
36+ g_lastCallSeq.clear();
37+ g_calls.clear();
38+}
39+ 
40+void MockSetMigrateFail(int successCount, int failRet)
41+{
42+ std::lock_guard<std::mutex> lock(g_mtx);
43+ g_successCount = successCount;
44+ g_failRet = failRet;
45+}
46+ 
47+int MockRmrsMigrateOut(const std::vector<mempooling::smap::MigrateOutPayload>& payloads, int)
48+{
49+ std::unique_lock<std::mutex> lock(g_mtx);
50+ const uint64_t callSeq = ++g_callSeq;
51+ ++g_callCount;
52+ for (const auto& p : payloads) {
53+ for (int i = 0; i < p.count; ++i) {
54+ g_calls.emplace_back(p.inner[i].destNid, p.inner[i].memSize);
55+ g_lastCallSeq[p.pid][p.inner[i].destNid] = callSeq;
56+ }
57+ }
58+ if (g_successCount >= 0 && g_callCount > static_cast<uint32_t>(g_successCount)) {
59+ return g_failRet;
60+ }
61+ for (const auto& p : payloads) {
62+ for (int i = 0; i < p.count; ++i) {
63+ if (g_lastCallSeq[p.pid][p.inner[i].destNid] == callSeq) {
64+ g_targets[p.pid][p.inner[i].destNid] = p.inner[i].memSize;
65+ }
66+ }
67+ }
68+ for (auto& [pid, targets] : g_targets) {
69+ for (auto it = targets.begin(); it != targets.end();) {
70+ if (g_lastCallSeq[pid][it->first] < callSeq) {
71+ it = targets.erase(it);
72+ } else {
73+ ++it;
74+ }
75+ }
76+ }
77+ return 0;
78+}
79+ 
80+uint64_t MockGetMigrateTargetKb(pid_t pid, int numaId)
81+{
82+ std::lock_guard<std::mutex> lock(g_mtx);
83+ auto it = g_targets.find(pid);
84+ if (it == g_targets.end()) {
85+ return 0;
86+ }
87+ auto itNuma = it->second.find(numaId);
88+ return (itNuma == it->second.end()) ? 0 : itNuma->second;
89+}
90+ 
91+std::map<int, uint64_t> MockGetMigrateTargets(pid_t pid)
92+{
93+ std::lock_guard<std::mutex> lock(g_mtx);
94+ auto it = g_targets.find(pid);
95+ return (it == g_targets.end()) ? std::map<int, uint64_t>{} : it->second;
96+}
97+ 
98+uint32_t MockGetMigrateCallCount()
99+{
100+ std::lock_guard<std::mutex> lock(g_mtx);
101+ return g_callCount;
102+}
103+ 
104+std::vector<std::pair<int, uint64_t>> MockGetMigrateCalls()
105+{
106+ std::lock_guard<std::mutex> lock(g_mtx);
107+ return g_calls;
108+}
109+ 
110+} // namespace ubse::smap
@@ -0,0 +1,18 @@
1+#pragma once
2+ 
3+#include <cstdint>
4+#include <map>
5+#include <utility>
6+#include <vector>
7+ 
8+#include "process_mem_pid_bridge.h"
9+ 
10+namespace ubse::smap {
11+void MockResetMigrateState();
12+void MockSetMigrateFail(int successCount, int failRet);
13+int MockRmrsMigrateOut(const std::vector<mempooling::smap::MigrateOutPayload>& payloads, int);
14+uint64_t MockGetMigrateTargetKb(pid_t pid, int numaId);
15+std::map<int, uint64_t> MockGetMigrateTargets(pid_t pid);
16+uint32_t MockGetMigrateCallCount();
17+std::vector<std::pair<int, uint64_t>> MockGetMigrateCalls();
18+} // namespace ubse::smap
@@ -10,6 +10,9 @@
10 * See the Mulan PSL v2 for more details.10 * See the Mulan PSL v2 for more details.
11 */11 */
12 12 
13+#include <map>
14+#include <vector>
15+ 
13#include "ubse_storage.h"16#include "ubse_storage.h"
14 17 
15namespace ubse::storage {18namespace ubse::storage {
@@ -17,6 +20,15 @@ namespace ubse::storage {
17static uint32_t g_mockStoragePutError = 0;20static uint32_t g_mockStoragePutError = 0;
18static uint32_t g_mockStorageQueryError = 0;21static uint32_t g_mockStorageQueryError = 0;
19static uint32_t g_mockStorageDeleteError = 0;22static uint32_t g_mockStorageDeleteError = 0;
23+static uint32_t g_mockStorageListError = 0;
24+static std::vector<std::string> g_mockStorageKeys;
25+static std::map<std::string, std::vector<uint8_t>> g_mockStorageQueryPayloads;
26+ 
27+void MockSetStorageQueryPayload(const std::string& keyPrefix, const std::string& key,
28+ const std::vector<uint8_t>& payload)
29+{
30+ g_mockStorageQueryPayloads[keyPrefix + ":" + key] = payload;
31+}
20 32 
21void MockSetStoragePutError(uint32_t err)33void MockSetStoragePutError(uint32_t err)
22{34{
@@ -30,11 +42,22 @@ void MockSetStorageDeleteError(uint32_t err)
30{42{
31 g_mockStorageDeleteError = err;43 g_mockStorageDeleteError = err;
32}44}
45+void MockSetStorageListError(uint32_t err)
46+{
47+ g_mockStorageListError = err;
48+}
49+void MockSetStorageKeys(const std::vector<std::string>& keys)
50+{
51+ g_mockStorageKeys = keys;
52+}
33void MockResetStorageErrors()53void MockResetStorageErrors()
34{54{
35 g_mockStoragePutError = 0;55 g_mockStoragePutError = 0;
36 g_mockStorageQueryError = 0;56 g_mockStorageQueryError = 0;
37 g_mockStorageDeleteError = 0;57 g_mockStorageDeleteError = 0;
58+ g_mockStorageListError = 0;
59+ g_mockStorageKeys.clear();
60+ g_mockStorageQueryPayloads.clear();
38}61}
39 62 
40uint32_t UbseStoragePutData(const std::string& keyPrefix, const std::string& key, UbseByteBuffer* data)63uint32_t UbseStoragePutData(const std::string& keyPrefix, const std::string& key, UbseByteBuffer* data)
@@ -45,6 +68,13 @@ uint32_t UbseStoragePutData(const std::string& keyPrefix, const std::string& key
45uint32_t UbseStorageQueryData(const std::string& keyPrefix, const std::string& key, void* ctx,68uint32_t UbseStorageQueryData(const std::string& keyPrefix, const std::string& key, void* ctx,
46 UbseStorageDealDataFunc func)69 UbseStorageDealDataFunc func)
47{70{
71+ auto it = g_mockStorageQueryPayloads.find(keyPrefix + ":" + key);
72+ if (it != g_mockStorageQueryPayloads.end()) {
73+ UbseByteBuffer buff{};
74+ buff.data = const_cast<uint8_t*>(it->second.data());
75+ buff.len = it->second.size();
76+ func(keyPrefix, key, buff, ctx);
77+ }
48 return g_mockStorageQueryError;78 return g_mockStorageQueryError;
49}79}
50 80 
@@ -52,4 +82,13 @@ uint32_t UbseStorageDeleteData(const std::string& keyPrefix, const std::string&
52{82{
53 return g_mockStorageDeleteError;83 return g_mockStorageDeleteError;
54}84}
85+ 
86+uint32_t UbseStorageListKeys(const std::string& keyPrefix, std::vector<std::string>& keys)
87+{
88+ if (g_mockStorageListError != 0) {
89+ return g_mockStorageListError;
90+ }
91+ keys = g_mockStorageKeys;
92+ return 0;
93+}
55} // namespace ubse::storage94} // namespace ubse::storage
@@ -12,8 +12,93 @@
12 12 
13#include "ubse_thread_pool.h"13#include "ubse_thread_pool.h"
14 14 
15+#include <atomic>
16+#include <condition_variable>
17+#include <functional>
18+#include <mutex>
19+#include <queue>
20+#include <thread>
21+ 
22+#include "mock_control.h"
23+ 
15namespace ubse::task_executor {24namespace ubse::task_executor {
16 25 
26+namespace {
27+std::atomic<bool> g_asyncMode{false};
28+std::atomic<bool> g_workerStop{false};
29+std::atomic<uint64_t> g_inflight{0};
30+std::mutex g_qMutex;
31+std::condition_variable g_qCv;
32+std::condition_variable g_idleCv;
33+std::queue<std::function<void()>> g_tasks;
34+std::vector<std::thread> g_workers;
35+ 
36+void MockWorkerLoop()
37+{
38+ for (;;) {
39+ std::function<void()> task;
40+ {
41+ std::unique_lock<std::mutex> lock(g_qMutex);
42+ g_qCv.wait(lock, [] { return g_workerStop.load() || !g_tasks.empty(); });
43+ if (g_workerStop.load()) {
44+ return;
45+ }
46+ task = std::move(g_tasks.front());
47+ g_tasks.pop();
48+ }
49+ task();
50+ if (g_inflight.fetch_sub(1) == 1) {
51+ g_idleCv.notify_all();
52+ }
53+ }
54+}
55+ 
56+bool TryEnqueue(const std::function<void()>& task)
57+{
58+ if (!g_asyncMode.load()) {
59+ return false;
60+ }
61+ g_inflight.fetch_add(1);
62+ {
63+ std::lock_guard<std::mutex> lock(g_qMutex);
64+ g_tasks.push(task);
65+ }
66+ g_qCv.notify_one();
67+ return true;
68+}
69+} // namespace
70+ 
71+void MockSetExecutorAsync(bool async)
72+{
73+ if (async == g_asyncMode.load()) {
74+ return;
75+ }
76+ if (async) {
77+ g_workerStop.store(false);
78+ for (int i = 0; i < 2; ++i) {
79+ g_workers.emplace_back(MockWorkerLoop);
80+ }
81+ g_asyncMode.store(true);
82+ } else {
83+ MockWaitExecutorIdle();
84+ g_workerStop.store(true);
85+ g_qCv.notify_all();
86+ for (auto& worker : g_workers) {
87+ if (worker.joinable()) {
88+ worker.join();
89+ }
90+ }
91+ g_workers.clear();
92+ g_asyncMode.store(false);
93+ }
94+}
95+ 
96+void MockWaitExecutorIdle()
97+{
98+ std::unique_lock<std::mutex> lock(g_qMutex);
99+ g_idleCv.wait(lock, [] { return g_inflight.load() == 0 && g_tasks.empty(); });
100+}
101+ 
17void UbseRunnable::Run()102void UbseRunnable::Run()
18{103{
19 if (mTask) {104 if (mTask) {
@@ -69,7 +154,9 @@ bool UbseTaskExecutor::Execute(const UbseRunnablePtr& runnable)
69 return false;154 return false;
70 }155 }
71 if (runnable.Get() != nullptr) {156 if (runnable.Get() != nullptr) {
72- runnable->Run();157+ if (!TryEnqueue([runnable]() { runnable->Run(); })) {
158+ runnable->Run();
159+ }
73 }160 }
74 return true;161 return true;
75}162}
@@ -80,7 +167,9 @@ bool UbseTaskExecutor::Execute(const std::function<void()>& task)
80 return false;167 return false;
81 }168 }
82 if (task) {169 if (task) {
83- task();170+ if (!TryEnqueue(task)) {
171+ task();
172+ }
84 }173 }
85 return true;174 return true;
86}175}
@@ -0,0 +1,135 @@
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 "test_process_mem_pid_borrow_reuse.h"
14+ 
15+namespace ubse::ut::process_mem {
16+using namespace ::process_mem::decision;
17+using namespace ::process_mem::manager;
18+using namespace ::process_mem::def;
19+using namespace ::process_mem::pid::bridge;
20+ 
21+namespace {
22+constexpr uint64_t BYTES_PER_GB = 1073741824;
23+constexpr uint64_t GB3_5 = 3758096384;
24+} // namespace
25+ 
26+void TestProcessMemPidBorrowReuse::SetUp()
27+{
28+ ubse::mem::controller::MockResetAllErrors();
29+ ubse::nodeController::MockSetCurrentNodeId("NODE0");
30+ 
31+ ubse::smap::MockResetMigrateState();
32+ ProcessMemPidBridge::rmrsMigrateOut = ubse::smap::MockRmrsMigrateOut;
33+ 
34+ auto& decision = ProcessMemPidDecision::GetInstance();
35+ if (decision.returnExecutor_ == nullptr || !decision.returnExecutor_->IsStart()) {
36+ decision.returnExecutor_ = ubse::task_executor::UbseTaskExecutor::Create("ProcMemReuseReturn", 2, 64);
37+ decision.returnExecutor_->Start();
38+ }
39+}
40+ 
41+void TestProcessMemPidBorrowReuse::TearDown()
42+{
43+ ProcessMemPidBridge::rmrsMigrateOut = {};
44+ ubse::smap::MockResetMigrateState();
45+ 
46+ auto& infoMgr = ProcessMemPidInfoManager::GetInstance();
47+ auto snapshot = infoMgr.GetManagedPidCacheSnapshot();
48+ for (const auto& [pid, entry] : snapshot) {
49+ infoMgr.RemoveManagedPidEntry(pid);
50+ }
51+ ubse::nodeController::MockResetCurrentNodeId();
52+}
53+ 
54+void TestProcessMemPidBorrowReuse::AddPidWithBorrow(pid_t pid, const def::BorrowState& borrow,
55+ def::ProcessStatus status)
56+{
57+ auto& infoMgr = ProcessMemPidInfoManager::GetInstance();
58+ infoMgr.AddNameSourceToManagedPid(pid, "reuse_proc", 32 * BYTES_PER_GB, 0.5);
59+ infoMgr.UpdateManagedPidBorrowState(pid, borrow, status);
60+}
61+ 
62+TEST_F(TestProcessMemPidBorrowReuse, NewBorrowAllTargetsOneCallMigrateFailWholeAbort)
63+{
64+ const pid_t pid = 15964;
65+ constexpr uint64_t need = 2 * BYTES_PER_GB;
66+ 
67+ def::BorrowSlot slot;
68+ slot.capacity = 4 * BYTES_PER_GB;
69+ slot.migratedBytes = GB3_5;
70+ slot.debtId = "d1";
71+ slot.remoteNumaId = 3;
72+ slot.status = def::BorrowSlotStatus::COMPLETED;
73+ def::BorrowState borrow;
74+ borrow.currentRemote = GB3_5;
75+ borrow.slots.push_back(slot);
76+ AddPidWithBorrow(pid, borrow, def::ProcessStatus::BORROWED);
77+ 
78+ ubse::mem::controller::MockSetNumaCreateDesc(5, 9, 0);
79+ ubse::smap::MockSetMigrateFail(0, 3);
80+ 
81+ auto debtId = ProcessMemPidDecision::GetInstance().RecordPendingBorrow(pid, need, 0, 1);
82+ ASSERT_FALSE(debtId.empty());
83+ ProcessMemPidDecision::GetInstance().AsyncBorrowAndMigrate(debtId, pid, need, 0, 1);
84+ 
85+ ASSERT_EQ(ubse::smap::MockGetMigrateCallCount(), 1u);
86+ auto calls = ubse::smap::MockGetMigrateCalls();
87+ ASSERT_EQ(calls.size(), 2u);
88+ EXPECT_EQ(calls[0].first, 3);
89+ EXPECT_EQ(calls[0].second, GB3_5 / 1024);
90+ EXPECT_EQ(calls[1].first, 5);
91+ EXPECT_EQ(calls[1].second, need / 1024);
92+ EXPECT_EQ(ubse::smap::MockGetMigrateTargetKb(pid, 3), 0u);
93+ EXPECT_EQ(ubse::smap::MockGetMigrateTargetKb(pid, 5), 0u);
94+ 
95+ EXPECT_EQ(ubse::mem::controller::MockGetNumaDeleteCallCount(), 1u);
96+ EXPECT_EQ(ubse::mem::controller::MockGetLastNumaDeleteName(), debtId);
97+ auto snapshot = ProcessMemPidInfoManager::GetInstance().GetManagedPidCacheSnapshot();
98+ auto it = snapshot.find(pid);
99+ ASSERT_NE(it, snapshot.end());
100+ ASSERT_EQ(it->second.borrow.slots.size(), 1u);
101+ EXPECT_EQ(it->second.borrow.slots[0].migratedBytes, GB3_5);
102+ EXPECT_EQ(it->second.borrow.currentRemote, GB3_5);
103+ EXPECT_EQ(it->second.processStatus, def::ProcessStatus::BORROWED);
104+}
105+ 
106+TEST_F(TestProcessMemPidBorrowReuse, VanishedSlotAfterCreateReturnsUbseDebt)
107+{
108+ const pid_t pid = 15964;
109+ constexpr uint64_t need = 1 * BYTES_PER_GB;
110+ auto& decision = ProcessMemPidDecision::GetInstance();
111+ auto& infoMgr = ProcessMemPidInfoManager::GetInstance();
112+ decision.timeoutPer128MbMs_ = 1000;
113+ 
114+ infoMgr.AddNameSourceToManagedPid(pid, "reuse_proc", 32 * BYTES_PER_GB, 0.5);
115+ 
116+ auto debtId = decision.RecordPendingBorrow(pid, need, 0, 1);
117+ ASSERT_FALSE(debtId.empty());
118+ 
119+ auto snapshot = infoMgr.GetManagedPidCacheSnapshot();
120+ auto borrow = snapshot.at(pid).borrow;
121+ ASSERT_EQ(borrow.slots.size(), 1u);
122+ borrow.slots[0].borrowTime = std::chrono::steady_clock::now() - std::chrono::seconds(60);
123+ infoMgr.UpdateManagedPidBorrowState(pid, borrow, def::ProcessStatus::BORROWING);
124+ decision.CheckTimeouts(2);
125+ ASSERT_EQ(infoMgr.GetManagedPidCacheSnapshot().at(pid).borrow.slots.size(), 0u);
126+ EXPECT_EQ(ubse::mem::controller::MockGetNumaDeleteCallCount(), 1u);
127+ 
128+ ubse::mem::controller::MockSetNumaCreateDesc(5, 9, need);
129+ decision.AsyncBorrowAndMigrate(debtId, pid, need, 0, 1);
130+ EXPECT_EQ(ubse::mem::controller::MockGetNumaDeleteCallCount(), 2u);
131+ EXPECT_EQ(ubse::mem::controller::MockGetLastNumaDeleteName(), debtId);
132+ snapshot = infoMgr.GetManagedPidCacheSnapshot();
133+ EXPECT_EQ(snapshot.at(pid).borrow.slots.size(), 0u);
134+}
135+} // namespace ubse::ut::process_mem
@@ -0,0 +1,48 @@
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+#ifndef TEST_PROCESS_MEM_PID_BORROW_REUSE_H
14+#define TEST_PROCESS_MEM_PID_BORROW_REUSE_H
15+ 
16+#include <cstdint>
17+#include <functional>
18+#include <map>
19+#include <string>
20+#include <utility>
21+#include <vector>
22+ 
23+#include "gtest/gtest.h"
24+ 
25+#include "mock/ubse/mock_control.h"
26+#include "mock/ubse/ubse_smap_mock.h"
27+#include "process_mem_pid_bridge.h"
28+#include "process_mem_pid_info_manager.h"
29+ 
30+#define private public
31+#include "process_mem_pid_decision.h"
32+#undef private
33+ 
34+namespace ubse::ut::process_mem {
35+namespace def = ::process_mem::def;
36+ 
37+class TestProcessMemPidBorrowReuse : public testing::Test {
38+public:
39+ TestProcessMemPidBorrowReuse() = default;
40+ 
41+ void SetUp() override;
42+ void TearDown() override;
43+ 
44+ void AddPidWithBorrow(pid_t pid, const def::BorrowState& borrow, def::ProcessStatus status);
45+};
46+ 
47+} // namespace ubse::ut::process_mem
48+#endif
@@ -12,159 +12,31 @@
12 12 
13#include "test_process_mem_pid_bridge.h"13#include "test_process_mem_pid_bridge.h"
14 14 
15+#include <map>
16+ 
15#include "ubse_api_server.h"17#include "ubse_api_server.h"
16#include "ubse_node_controller.h"18#include "ubse_node_controller.h"
17#include "ubse_serial_util.h"19#include "ubse_serial_util.h"
18#include "mock/ubse/mock_control.h"20#include "mock/ubse/mock_control.h"
19#include "process_mem_pid_info_manager.h"21#include "process_mem_pid_info_manager.h"
20 22 
21-// Forward declare functions from process_mem_pid_bridge.cpp for testing
22namespace process_mem::pid::bridge {23namespace process_mem::pid::bridge {
23-void GetLentInfo(const ubse::mem::controller::UbseNumaMemoryDebtInfo& info,
24- const ubse::mem::controller::UbseMemNumaDesc& desc, uint32_t& socketId, uint64_t& numaId, bool& flag);
25-uint32_t ValidateSrcNumaIdOnCurrentNode(const process_mem::def::ProcessMemPidConfigInfo& configInfo);
26-uint32_t SendSetPidMapErrorResponse(uint32_t ret, uint64_t requestId);
27uint32_t SendPidSetResponse(int successCode, const std::string& errorMsg, uint64_t requestId);24uint32_t SendPidSetResponse(int successCode, const std::string& errorMsg, uint64_t requestId);
28-uint32_t SetPidInfo(const api::server::UbseIpcMessage& request, const api::server::UbseRequestContext& context);25+void BuildConfigEntries(std::vector<::process_mem::def::ProcessMemDisplayEntry>& entries,
29-uint32_t PidInfoPrint(const api::server::UbseIpcMessage& request, const api::server::UbseRequestContext& context);26+ const std::vector<::process_mem::def::ProcessMemNewConfigInfo>& newConfigs);
30-uint32_t UnSetPidInfoPrint(const api::server::UbseIpcMessage& request, const api::server::UbseRequestContext& context);27+void BuildProcDetailEntries(std::vector<::process_mem::def::ProcessMemDisplayEntry>& entries,
28+ const std::map<pid_t, ::process_mem::def::ManagedPidEntry>& managedSnapshot);
31} // namespace process_mem::pid::bridge29} // namespace process_mem::pid::bridge
32 30 
33namespace ubse::ut::process_mem {31namespace ubse::ut::process_mem {
34using namespace ::process_mem::pid::bridge;32using namespace ::process_mem::pid::bridge;
35-using namespace ::process_mem::def;
36using namespace ::process_mem::manager;33using namespace ::process_mem::manager;
34+namespace def = ::process_mem::def;
37 35 
38void TestProcessMemPidBridge::SetUp() {}36void TestProcessMemPidBridge::SetUp() {}
39 37 
40void TestProcessMemPidBridge::TearDown() {}38void TestProcessMemPidBridge::TearDown() {}
41 39 
42-// ==================== GetLentInfo tests ====================
43- 
44-TEST_F(TestProcessMemPidBridge, GetLentInfoMatchingNameAndBorrowNode)
45-{
46- ubse::mem::controller::UbseNumaMemoryDebtInfo info{};
47- info.name = "test_debt";
48- info.borrowNodeId = "1";
49- info.lentSocketIdList = {0};
50- info.lentNumaIdList = {2};
51- 
52- ubse::mem::controller::UbseMemNumaDesc desc{};
53- desc.name = "test_debt";
54- desc.importNode.slotId = 1;
55- 
56- uint32_t socketId = 99;
57- uint64_t numaId = 99;
58- bool flag = false;
59- GetLentInfo(info, desc, socketId, numaId, flag);
60- EXPECT_TRUE(flag);
61- EXPECT_EQ(socketId, 0u);
62- EXPECT_EQ(numaId, 2u);
63-}
64- 
65-TEST_F(TestProcessMemPidBridge, GetLentInfoMismatchingName)
66-{
67- ubse::mem::controller::UbseNumaMemoryDebtInfo info{};
68- info.name = "other_debt";
69- info.borrowNodeId = "1";
70- info.lentSocketIdList = {0};
71- info.lentNumaIdList = {2};
72- 
73- ubse::mem::controller::UbseMemNumaDesc desc{};
74- desc.name = "test_debt";
75- desc.importNode.slotId = 1;
76- 
77- uint32_t socketId = 99;
78- uint64_t numaId = 99;
79- bool flag = false;
80- GetLentInfo(info, desc, socketId, numaId, flag);
81- EXPECT_FALSE(flag);
82-}
83- 
84-TEST_F(TestProcessMemPidBridge, GetLentInfoMismatchingBorrowNode)
85-{
86- ubse::mem::controller::UbseNumaMemoryDebtInfo info{};
87- info.name = "test_debt";
88- info.borrowNodeId = "2";
89- info.lentSocketIdList = {0};
90- info.lentNumaIdList = {2};
91- 
92- ubse::mem::controller::UbseMemNumaDesc desc{};
93- desc.name = "test_debt";
94- desc.importNode.slotId = 1;
95- 
96- uint32_t socketId = 99;
97- uint64_t numaId = 99;
98- bool flag = false;
99- GetLentInfo(info, desc, socketId, numaId, flag);
100- EXPECT_FALSE(flag);
101-}
102- 
103-TEST_F(TestProcessMemPidBridge, GetLentInfoEmptySocketAndNumaList)
104-{
105- ubse::mem::controller::UbseNumaMemoryDebtInfo info{};
106- info.name = "test_debt";
107- info.borrowNodeId = "1";
108- // Both empty: flag stays false, no crash
109- 
110- ubse::mem::controller::UbseMemNumaDesc desc{};
111- desc.name = "test_debt";
112- desc.importNode.slotId = 1;
113- 
114- uint32_t socketId = 99;
115- uint64_t numaId = 99;
116- bool flag = false;
117- GetLentInfo(info, desc, socketId, numaId, flag);
118- EXPECT_FALSE(flag);
119-}
120- 
121-// ==================== ValidateSrcNumaIdOnCurrentNode tests ====================
122- 
123-TEST_F(TestProcessMemPidBridge, ValidateSrcNumaIdOnCurrentNodeNoSrcNuma)
124-{
125- ProcessMemPidConfigInfo configInfo{};
126- configInfo.srcNumaId = std::nullopt;
127- auto ret = ValidateSrcNumaIdOnCurrentNode(configInfo);
128- EXPECT_EQ(ret, UBSE_OK);
129-}
130- 
131-TEST_F(TestProcessMemPidBridge, ValidateSrcNumaIdOnCurrentNodeWithSrcNuma)
132-{
133- ProcessMemPidConfigInfo configInfo{};
134- configInfo.srcNumaId = 2;
135- auto ret = ValidateSrcNumaIdOnCurrentNode(configInfo);
136- // mock GetCurNode returns empty numaInfos, so srcNumaId won't match
137- EXPECT_NE(ret, UBSE_OK);
138-}
139- 
140-// ==================== SendSetPidMapErrorResponse tests ====================
141- 
142-TEST_F(TestProcessMemPidBridge, SendSetPidMapErrorResponseNotExist)
143-{
144- auto ret = SendSetPidMapErrorResponse(UBSE_ERR_NOT_EXIST, 12345);
145- EXPECT_EQ(ret, UBSE_OK);
146-}
147- 
148-TEST_F(TestProcessMemPidBridge, SendSetPidMapErrorResponseInvalidArg)
149-{
150- auto ret = SendSetPidMapErrorResponse(UBSE_ERR_INVALID_ARG, 12345);
151- EXPECT_EQ(ret, UBSE_OK);
152-}
153- 
154-TEST_F(TestProcessMemPidBridge, SendSetPidMapErrorResponseResourceBusy)
155-{
156- auto ret = SendSetPidMapErrorResponse(UBSE_ERR_RESOURCE_BUSY, 12345);
157- EXPECT_EQ(ret, UBSE_OK);
158-}
159- 
160-TEST_F(TestProcessMemPidBridge, SendSetPidMapErrorResponseGeneric)
161-{
162- auto ret = SendSetPidMapErrorResponse(UBSE_ERROR, 12345);
163- EXPECT_EQ(ret, UBSE_OK);
164-}
165- 
166-// ==================== SendPidSetResponse tests ====================
167- 
168TEST_F(TestProcessMemPidBridge, SendPidSetResponseSuccess)40TEST_F(TestProcessMemPidBridge, SendPidSetResponseSuccess)
169{41{
170 auto ret = SendPidSetResponse(1, "", 12345);42 auto ret = SendPidSetResponse(1, "", 12345);
@@ -177,180 +49,18 @@ TEST_F(TestProcessMemPidBridge, SendPidSetResponseFailure)
177 EXPECT_EQ(ret, UBSE_OK);49 EXPECT_EQ(ret, UBSE_OK);
178}50}
179 51 
180-// ==================== SetPidInfo tests ====================52+TEST_F(TestProcessMemPidBridge, SendPidSetResponseWithLongMessage)
181- 
182-TEST_F(TestProcessMemPidBridge, SetPidInfoWithInvalidDeserialize)
183{53{
184- api::server::UbseIpcMessage request{};54+ auto ret = SendPidSetResponse(1, "A very long error message that exceeds normal length", 99999);
185- request.buffer = nullptr;
186- request.length = 0;
187- api::server::UbseRequestContext context{};
188- context.requestId = 1;
189- auto ret = SetPidInfo(request, context);
190- EXPECT_NE(ret, UBSE_OK);
191-}
192- 
193-TEST_F(TestProcessMemPidBridge, SetPidInfoWithValidData)
194-{
195- ProcessMemPidInfo pidInfo{};
196- pidInfo.configInfo.pid = 77777;
197- pidInfo.configInfo.evictThreshold = 80;
198- pidInfo.startTime = 1000;
199- 
200- ubse::serial::UbseSerialization serializer;
201- pidInfo.configInfo.SerializeConfigInfo(serializer);
202- 
203- api::server::UbseIpcMessage request{};
204- request.buffer = serializer.GetBuffer();
205- request.length = static_cast<uint32_t>(serializer.GetLength());
206- api::server::UbseRequestContext context{};
207- context.requestId = 1;
208- 
209- auto ret = SetPidInfo(request, context);
210- // mock curNode has no numa matching srcNumaId -> will reject with message about srcNumaId
211 EXPECT_EQ(ret, UBSE_OK);55 EXPECT_EQ(ret, UBSE_OK);
212}56}
213 57 
214-// ==================== PidInfoPrint tests ====================58+TEST_F(TestProcessMemPidBridge, InitToleratesMissingLibrary)
215- 
216-TEST_F(TestProcessMemPidBridge, PidInfoPrintSuccess)
217-{
218- api::server::UbseIpcMessage request{};
219- request.buffer = nullptr;
220- request.length = 0;
221- api::server::UbseRequestContext context{};
222- context.requestId = 1;
223- auto ret = PidInfoPrint(request, context);
224- EXPECT_EQ(ret, UBSE_OK);
225-}
226- 
227-// ==================== UnSetPidInfoPrint tests ====================
228- 
229-TEST_F(TestProcessMemPidBridge, UnSetPidInfoPrintNotConfigured)
230-{
231- ubse::serial::UbseSerialization serializer;
232- pid_t pid = 99999999;
233- serializer << pid;
234- 
235- api::server::UbseIpcMessage request{};
236- request.buffer = serializer.GetBuffer();
237- request.length = static_cast<uint32_t>(serializer.GetLength());
238- api::server::UbseRequestContext context{};
239- context.requestId = 1;
240- 
241- auto ret = UnSetPidInfoPrint(request, context);
242- EXPECT_EQ(ret, UBSE_OK);
243-}
244- 
245-TEST_F(TestProcessMemPidBridge, UnSetPidInfoPrintSuccess)
246-{
247- auto& mgr = ProcessMemPidInfoManager::GetInstance();
248- ProcessMemPidInfo pidInfo{};
249- pidInfo.configInfo.pid = 50001;
250- pidInfo.startTime = 1000;
251- mgr.SetPidInfoMap(pidInfo);
252- 
253- ubse::serial::UbseSerialization serializer;
254- pid_t unsetPid = 50001;
255- serializer << unsetPid;
256- 
257- api::server::UbseIpcMessage request{};
258- request.buffer = serializer.GetBuffer();
259- request.length = static_cast<uint32_t>(serializer.GetLength());
260- api::server::UbseRequestContext context{};
261- context.requestId = 1;
262- 
263- auto ret = UnSetPidInfoPrint(request, context);
264- EXPECT_EQ(ret, UBSE_OK);
265-}
266- 
267-TEST_F(TestProcessMemPidBridge, MemoryBorrowWithExportSlotIdNegative)
268-{
269- ProcessMemPidInfo pidInfo{};
270- pidInfo.configInfo.pid = 1234;
271- pidInfo.memBorrowInfo.exportSlotId = -1;
272- 
273- ubse::mem::controller::UbseMemBorrower borrower{};
274- borrower.nodeId = "NODE0";
275- 
276- MemoryBorrowRequest request{};
277- request.name = "test_borrow";
278- request.size = 1024 * 1024;
279- 
280- ubse::mem::controller::UbseMemNumaDesc borrowInfo{};
281- 
282- auto ret = ProcessMemPidBridge::MemoryBorrow(pidInfo, borrower, request, borrowInfo);
283- EXPECT_EQ(ret, UBSE_OK);
284-}
285- 
286-TEST_F(TestProcessMemPidBridge, MemoryBorrowWithExportSlotIdSet)
287-{
288- ProcessMemPidInfo pidInfo{};
289- pidInfo.configInfo.pid = 2345;
290- pidInfo.memBorrowInfo.exportSlotId = 1;
291- 
292- ubse::mem::controller::UbseMemBorrower borrower{};
293- borrower.nodeId = "NODE0";
294- 
295- MemoryBorrowRequest request{};
296- request.name = "test_borrow_candidate";
297- request.size = 2048 * 1024;
298- 
299- ubse::mem::controller::UbseMemNumaDesc borrowInfo{};
300- 
301- auto ret = ProcessMemPidBridge::MemoryBorrow(pidInfo, borrower, request, borrowInfo);
302- EXPECT_EQ(ret, UBSE_OK);
303-}
304- 
305-TEST_F(TestProcessMemPidBridge, MemoryReturnSuccess)
306-{
307- auto ret = ProcessMemPidBridge::MemoryReturn("test_return_name");
308- EXPECT_EQ(ret, UBSE_OK);
309-}
310- 
311-TEST_F(TestProcessMemPidBridge, GetRemoteNumaSocketInfo)
312-{
313- ubse::mem::controller::UbseMemNumaDesc desc{};
314- desc.name = "test_numa";
315- desc.exportNode.slotId = 1;
316- desc.importNode.slotId = 2;
317- 
318- uint32_t socketId = 0;
319- uint64_t numaId = 0;
320- 
321- auto ret = ProcessMemPidBridge::GetRemoteNumaSocketInfo(desc, socketId, numaId);
322- EXPECT_NE(ret, UBSE_OK);
323-}
324- 
325-TEST_F(TestProcessMemPidBridge, FaultHandlerSuccess)
326-{
327- auto ret = ProcessMemPidBridge::FaultHandler(ubse::ras::ALARM_PANIC_EVENT, "NODE_FAULT");
328- EXPECT_EQ(ret, UBSE_OK);
329-}
330- 
331-TEST_F(TestProcessMemPidBridge, ProcessMemNodeFaultNotifyHandler)
332-{
333- ubse::serial::UbseSerialization serializer;
334- std::string faultNodeId = "NODE_FAULT_1";
335- serializer << faultNodeId;
336- 
337- UbseByteBuffer req{};
338- req.data = serializer.GetBuffer();
339- req.len = serializer.GetLength();
340- 
341- UbseByteBuffer resp{};
342- EXPECT_NO_THROW(ProcessMemPidBridge::ProcessMemNodeFaultNotifyHandler(req, resp));
343-}
344- 
345-TEST_F(TestProcessMemPidBridge, NotifyBorrowNodesOnFault)
346-{
347- EXPECT_NO_THROW(ProcessMemPidBridge::NotifyBorrowNodesOnFault("NODE_FAULT_2"));
348-}
349- 
350-TEST_F(TestProcessMemPidBridge, InitFailNoLibrary)
351{59{
60+ ProcessMemPidBridge::memPoolingHandle = nullptr;
352 auto ret = ProcessMemPidBridge::Init();61 auto ret = ProcessMemPidBridge::Init();
353- EXPECT_NE(ret, UBSE_OK);62+ EXPECT_EQ(ret, UBSE_OK);
63+ EXPECT_EQ(ProcessMemPidBridge::memPoolingHandle, nullptr);
354}64}
355 65 
356TEST_F(TestProcessMemPidBridge, UnInitWithNullHandle)66TEST_F(TestProcessMemPidBridge, UnInitWithNullHandle)
@@ -360,110 +70,9 @@ TEST_F(TestProcessMemPidBridge, UnInitWithNullHandle)
360 EXPECT_EQ(ret, UBSE_OK);70 EXPECT_EQ(ret, UBSE_OK);
361}71}
362 72 
363-TEST_F(TestProcessMemPidBridge, MemoryBorrowWithZeroSize)73+TEST_F(TestProcessMemPidBridge, MemoryReturnSuccess)
364{74{
365- ProcessMemPidInfo pidInfo{};75+ auto ret = ProcessMemPidBridge::MemoryReturn("test_return_name");
366- pidInfo.configInfo.pid = 3001;
367- pidInfo.memBorrowInfo.exportSlotId = -1;
368- 
369- ubse::mem::controller::UbseMemBorrower borrower{};
370- borrower.nodeId = "NODE0";
371- 
372- MemoryBorrowRequest request{};
373- request.name = "test_borrow_zero_size";
374- request.size = 0;
375- 
376- ubse::mem::controller::UbseMemNumaDesc borrowInfo{};
377- 
378- auto ret = ProcessMemPidBridge::MemoryBorrow(pidInfo, borrower, request, borrowInfo);
379- EXPECT_EQ(ret, UBSE_OK);
380-}
381- 
382-TEST_F(TestProcessMemPidBridge, MemoryBorrowWithEmptyName)
383-{
384- ProcessMemPidInfo pidInfo{};
385- pidInfo.configInfo.pid = 3002;
386- pidInfo.memBorrowInfo.exportSlotId = -1;
387- 
388- ubse::mem::controller::UbseMemBorrower borrower{};
389- borrower.nodeId = "NODE0";
390- 
391- MemoryBorrowRequest request{};
392- request.name = "";
393- request.size = 1024 * 1024;
394- 
395- ubse::mem::controller::UbseMemNumaDesc borrowInfo{};
396- 
397- auto ret = ProcessMemPidBridge::MemoryBorrow(pidInfo, borrower, request, borrowInfo);
398- EXPECT_EQ(ret, UBSE_OK);
399-}
400- 
401-TEST_F(TestProcessMemPidBridge, MemoryBorrowWithUsrInfo)
402-{
403- ProcessMemPidInfo pidInfo{};
404- pidInfo.configInfo.pid = 3003;
405- pidInfo.memBorrowInfo.exportSlotId = -1;
406- 
407- ubse::mem::controller::UbseMemBorrower borrower{};
408- borrower.nodeId = "NODE0";
409- 
410- MemoryBorrowRequest request{};
411- request.name = "test_borrow_usr_info";
412- request.size = 4096 * 1024;
413- ProcessMemUsrInfo usrInfo{};
414- usrInfo.pluginId = UsrInfoPluginType::PROCESS_MEM;
415- usrInfo.pid = 3003;
416- usrInfo.startTime = 12345;
417- usrInfo.srcNuma = 0;
418- memcpy(request.usrInfo, &usrInfo, sizeof(usrInfo));
419- 
420- ubse::mem::controller::UbseMemNumaDesc borrowInfo{};
421- 
422- auto ret = ProcessMemPidBridge::MemoryBorrow(pidInfo, borrower, request, borrowInfo);
423- EXPECT_EQ(ret, UBSE_OK);
424-}
425- 
426-TEST_F(TestProcessMemPidBridge, MemoryBorrowWithCandidateAndUsrInfo)
427-{
428- ProcessMemPidInfo pidInfo{};
429- pidInfo.configInfo.pid = 3004;
430- pidInfo.memBorrowInfo.exportSlotId = 5;
431- 
432- ubse::mem::controller::UbseMemBorrower borrower{};
433- borrower.nodeId = "NODE0";
434- 
435- MemoryBorrowRequest request{};
436- request.name = "test_borrow_candidate_usr";
437- request.size = 8192 * 1024;
438- ProcessMemUsrInfo usrInfo{};
439- usrInfo.pluginId = UsrInfoPluginType::PROCESS_MEM;
440- usrInfo.pid = 3004;
441- usrInfo.startTime = 54321;
442- usrInfo.srcNuma = 1;
443- memcpy(request.usrInfo, &usrInfo, sizeof(usrInfo));
444- 
445- ubse::mem::controller::UbseMemNumaDesc borrowInfo{};
446- 
447- auto ret = ProcessMemPidBridge::MemoryBorrow(pidInfo, borrower, request, borrowInfo);
448- EXPECT_EQ(ret, UBSE_OK);
449-}
450- 
451-TEST_F(TestProcessMemPidBridge, MemoryBorrowWithLargeExportSlotId)
452-{
453- ProcessMemPidInfo pidInfo{};
454- pidInfo.configInfo.pid = 3005;
455- pidInfo.memBorrowInfo.exportSlotId = 255;
456- 
457- ubse::mem::controller::UbseMemBorrower borrower{};
458- borrower.nodeId = "NODE1";
459- 
460- MemoryBorrowRequest request{};
461- request.name = "test_borrow_large_slot";
462- request.size = 16 * 1024 * 1024;
463- 
464- ubse::mem::controller::UbseMemNumaDesc borrowInfo{};
465- 
466- auto ret = ProcessMemPidBridge::MemoryBorrow(pidInfo, borrower, request, borrowInfo);
467 EXPECT_EQ(ret, UBSE_OK);76 EXPECT_EQ(ret, UBSE_OK);
468}77}
469 78 
@@ -473,327 +82,6 @@ TEST_F(TestProcessMemPidBridge, MemoryReturnWithEmptyName)
473 EXPECT_EQ(ret, UBSE_OK);82 EXPECT_EQ(ret, UBSE_OK);
474}83}
475 84 
476-TEST_F(TestProcessMemPidBridge, GetRemoteNumaSocketInfoWithDefaultDesc)
477-{
478- ubse::mem::controller::UbseMemNumaDesc desc{};
479- 
480- uint32_t socketId = 0;
481- uint64_t numaId = 0;
482- 
483- auto ret = ProcessMemPidBridge::GetRemoteNumaSocketInfo(desc, socketId, numaId);
484- EXPECT_NE(ret, UBSE_OK);
485-}
486- 
487-TEST_F(TestProcessMemPidBridge, GetRemoteNumaSocketInfoWithSameSlotIds)
488-{
489- ubse::mem::controller::UbseMemNumaDesc desc{};
490- desc.name = "test_same_slots";
491- desc.exportNode.slotId = 1;
492- desc.importNode.slotId = 1;
493- 
494- uint32_t socketId = 0;
495- uint64_t numaId = 0;
496- 
497- auto ret = ProcessMemPidBridge::GetRemoteNumaSocketInfo(desc, socketId, numaId);
498- EXPECT_NE(ret, UBSE_OK);
499-}
500- 
501-TEST_F(TestProcessMemPidBridge, GetRemoteNumaSocketInfoWithLargeSlotIds)
502-{
503- ubse::mem::controller::UbseMemNumaDesc desc{};
504- desc.name = "test_large_slots";
505- desc.exportNode.slotId = 100;
506- desc.importNode.slotId = 200;
507- 
508- uint32_t socketId = 0;
509- uint64_t numaId = 0;
510- 
511- auto ret = ProcessMemPidBridge::GetRemoteNumaSocketInfo(desc, socketId, numaId);
512- EXPECT_NE(ret, UBSE_OK);
513-}
514- 
515-TEST_F(TestProcessMemPidBridge, FaultHandlerWithKernelRebootEvent)
516-{
517- auto ret = ProcessMemPidBridge::FaultHandler(ubse::ras::ALARM_KERNEL_REBOOT_EVENT, "NODE_REBOOT");
518- EXPECT_EQ(ret, UBSE_OK);
519-}
520- 
521-TEST_F(TestProcessMemPidBridge, FaultHandlerWithEmptyFaultInfo)
522-{
523- auto ret = ProcessMemPidBridge::FaultHandler(ubse::ras::ALARM_PANIC_EVENT, "");
524- EXPECT_EQ(ret, UBSE_OK);
525-}
526- 
527-TEST_F(TestProcessMemPidBridge, ProcessMemNodeFaultNotifyHandlerWithNullData)
528-{
529- UbseByteBuffer req{};
530- req.data = nullptr;
531- req.len = 0;
532- 
533- UbseByteBuffer resp{};
534- EXPECT_NO_THROW(ProcessMemPidBridge::ProcessMemNodeFaultNotifyHandler(req, resp));
535-}
536- 
537-TEST_F(TestProcessMemPidBridge, ProcessMemNodeFaultNotifyHandlerWithEmptyData)
538-{
539- uint8_t dummy = 0;
540- UbseByteBuffer req{};
541- req.data = &dummy;
542- req.len = 0;
543- 
544- UbseByteBuffer resp{};
545- EXPECT_NO_THROW(ProcessMemPidBridge::ProcessMemNodeFaultNotifyHandler(req, resp));
546-}
547- 
548-TEST_F(TestProcessMemPidBridge, ProcessMemNodeFaultNotifyHandlerWithValidData)
549-{
550- ubse::serial::UbseSerialization serializer;
551- std::string faultNodeId = "NODE_FAULT_VALID";
552- serializer << faultNodeId;
553- 
554- UbseByteBuffer req{};
555- req.data = serializer.GetBuffer();
556- req.len = serializer.GetLength();
557- 
558- UbseByteBuffer resp{};
559- EXPECT_NO_THROW(ProcessMemPidBridge::ProcessMemNodeFaultNotifyHandler(req, resp));
560-}
561- 
562-TEST_F(TestProcessMemPidBridge, NotifyBorrowNodesOnFaultWithEmptyNodeId)
563-{
564- EXPECT_NO_THROW(ProcessMemPidBridge::NotifyBorrowNodesOnFault(""));
565-}
566- 
567-TEST_F(TestProcessMemPidBridge, NotifyBorrowNodesOnFaultWithNonExistentNode)
568-{
569- EXPECT_NO_THROW(ProcessMemPidBridge::NotifyBorrowNodesOnFault("NON_EXISTENT_NODE"));
570-}
571- 
572-TEST_F(TestProcessMemPidBridge, InitFailNoLibraryResetsHandle)
573-{
574- ProcessMemPidBridge::memPoolingHandle = nullptr;
575- auto ret = ProcessMemPidBridge::Init();
576- EXPECT_NE(ret, UBSE_OK);
577- EXPECT_EQ(ProcessMemPidBridge::memPoolingHandle, nullptr);
578-}
579- 
580-// ==================== Extended tests ====================
581- 
582-TEST_F(TestProcessMemPidBridge, FaultHandlerWithUnknownFaultEvent)
583-{
584- auto ret = ProcessMemPidBridge::FaultHandler(static_cast<ubse::ras::ALARM_FAULT_TYPE>(999), "NODE_UNKNOWN");
585- EXPECT_EQ(ret, UBSE_OK);
586-}
587- 
588-// ==================== Additional edge case tests ====================
589- 
590-TEST_F(TestProcessMemPidBridge, MemoryBorrowWithDifferentUsrInfoSizes)
591-{
592- ProcessMemPidInfo pidInfo{};
593- pidInfo.configInfo.pid = 4001;
594- pidInfo.memBorrowInfo.exportSlotId = -1;
595- 
596- ubse::mem::controller::UbseMemBorrower borrower{};
597- borrower.nodeId = "NODE2";
598- 
599- MemoryBorrowRequest request{};
600- request.name = "test_diff_usr_sizes";
601- request.size = 1024;
602- ProcessMemUsrInfo usrInfo{};
603- usrInfo.pluginId = UsrInfoPluginType::PROCESS_MEM;
604- usrInfo.pid = 4001;
605- memcpy(request.usrInfo, &usrInfo, sizeof(usrInfo));
606- 
607- ubse::mem::controller::UbseMemNumaDesc borrowInfo{};
608- auto ret = ProcessMemPidBridge::MemoryBorrow(pidInfo, borrower, request, borrowInfo);
609- EXPECT_EQ(ret, UBSE_OK);
610-}
611- 
612-TEST_F(TestProcessMemPidBridge, MemoryBorrowWithNodeIdVariations)
613-{
614- ProcessMemPidInfo pidInfo{};
615- pidInfo.configInfo.pid = 4002;
616- pidInfo.memBorrowInfo.exportSlotId = -1;
617- 
618- ubse::mem::controller::UbseMemBorrower borrower{};
619- borrower.nodeId = "NODE_ZERO";
620- 
621- MemoryBorrowRequest request{};
622- request.name = "test_node_variation";
623- request.size = 4096;
624- 
625- ubse::mem::controller::UbseMemNumaDesc borrowInfo{};
626- auto ret = ProcessMemPidBridge::MemoryBorrow(pidInfo, borrower, request, borrowInfo);
627- EXPECT_EQ(ret, UBSE_OK);
628-}
629- 
630-TEST_F(TestProcessMemPidBridge, MemoryBorrowWithLargeSize)
631-{
632- ProcessMemPidInfo pidInfo{};
633- pidInfo.configInfo.pid = 4003;
634- pidInfo.memBorrowInfo.exportSlotId = -1;
635- 
636- ubse::mem::controller::UbseMemBorrower borrower{};
637- borrower.nodeId = "NODE0";
638- 
639- MemoryBorrowRequest request{};
640- request.name = "test_large_size";
641- request.size = UINT64_MAX;
642- 
643- ubse::mem::controller::UbseMemNumaDesc borrowInfo{};
644- auto ret = ProcessMemPidBridge::MemoryBorrow(pidInfo, borrower, request, borrowInfo);
645- EXPECT_EQ(ret, UBSE_OK);
646-}
647- 
648-TEST_F(TestProcessMemPidBridge, MemoryReturnWithTrailingSpaces)
649-{
650- auto ret = ProcessMemPidBridge::MemoryReturn("name_with_trailing ");
651- EXPECT_EQ(ret, UBSE_OK);
652-}
653- 
654-TEST_F(TestProcessMemPidBridge, SetPidInfoWithZeroThresholds)
655-{
656- ProcessMemPidInfo pidInfo{};
657- pidInfo.configInfo.pid = 5001;
658- pidInfo.configInfo.evictThreshold = 0;
659- pidInfo.configInfo.targetEvictThreshold = 0;
660- pidInfo.configInfo.reclaimThreshold = 0;
661- pidInfo.configInfo.expectedMemoryUsage = 0;
662- pidInfo.startTime = 1000;
663- 
664- ubse::serial::UbseSerialization serializer;
665- pidInfo.configInfo.SerializeConfigInfo(serializer);
666- 
667- api::server::UbseIpcMessage request{};
668- request.buffer = serializer.GetBuffer();
669- request.length = static_cast<uint32_t>(serializer.GetLength());
670- api::server::UbseRequestContext context{};
671- context.requestId = 2;
672- 
673- auto ret = SetPidInfo(request, context);
674- EXPECT_EQ(ret, UBSE_OK);
675-}
676- 
677-TEST_F(TestProcessMemPidBridge, PidInfoPrintWithConfiguredPids)
678-{
679- ProcessMemPidInfo pidInfo{};
680- pidInfo.configInfo.pid = 5002;
681- pidInfo.startTime = 1000;
682- auto& mgr = ProcessMemPidInfoManager::GetInstance();
683- mgr.SetPidInfoMap(pidInfo);
684- 
685- api::server::UbseIpcMessage request{};
686- request.buffer = nullptr;
687- request.length = 0;
688- api::server::UbseRequestContext context{};
689- context.requestId = 2;
690- auto ret = PidInfoPrint(request, context);
691- EXPECT_EQ(ret, UBSE_OK);
692- 
693- mgr.UnsetPidInfo(5002);
694-}
695- 
696-TEST_F(TestProcessMemPidBridge, GetRemoteNumaSocketInfoWithValidDesc)
697-{
698- ubse::mem::controller::UbseMemNumaDesc desc{};
699- desc.name = "remote_numa";
700- desc.exportNode.slotId = 5;
701- desc.importNode.slotId = 3;
702- 
703- uint32_t socketId = 99;
704- uint64_t numaId = 99;
705- auto ret = ProcessMemPidBridge::GetRemoteNumaSocketInfo(desc, socketId, numaId);
706- EXPECT_NE(ret, UBSE_OK);
707-}
708- 
709-TEST_F(TestProcessMemPidBridge, NotifyBorrowNodesOnFaultWithValidNodeId)
710-{
711- auto ret = ProcessMemPidBridge::NotifyBorrowNodesOnFault("VALID_NODE_1");
712- EXPECT_EQ(ret, UBSE_OK);
713-}
714- 
715-// ==================== PidInfoPrint with null buffer (empty request) ====================
716- 
717-TEST_F(TestProcessMemPidBridge, PidInfoPrintWithNonnullEmptyBuffer)
718-{
719- api::server::UbseIpcMessage request{};
720- uint8_t dummy = 0;
721- request.buffer = &dummy;
722- request.length = 0;
723- api::server::UbseRequestContext context{};
724- context.requestId = 3;
725- auto ret = PidInfoPrint(request, context);
726- EXPECT_EQ(ret, UBSE_OK);
727-}
728- 
729-// ==================== UnSetPidInfoPrint edge cases ====================
730- 
731-TEST_F(TestProcessMemPidBridge, UnSetPidInfoPrintWithNullBuffer)
732-{
733- api::server::UbseIpcMessage request{};
734- request.buffer = nullptr;
735- request.length = 0;
736- api::server::UbseRequestContext context{};
737- context.requestId = 4;
738- auto ret = UnSetPidInfoPrint(request, context);
739- EXPECT_EQ(ret, UBSE_OK);
740-}
741- 
742-// ==================== ValidateSrcNumaIdOnCurrentNode with nullopt ====================
743- 
744-TEST_F(TestProcessMemPidBridge, ValidateSrcNumaIdOnCurrentNodeNullOpt)
745-{
746- ProcessMemPidConfigInfo configInfo{};
747- configInfo.srcNumaId = std::nullopt;
748- auto ret = ValidateSrcNumaIdOnCurrentNode(configInfo);
749- EXPECT_EQ(ret, UBSE_OK);
750-}
751- 
752-// ==================== FaultHandler with all known event types ====================
753- 
754-TEST_F(TestProcessMemPidBridge, FaultHandlerPanicEvent)
755-{
756- auto ret = ProcessMemPidBridge::FaultHandler(ubse::ras::ALARM_PANIC_EVENT, "NODE_PANIC");
757- EXPECT_EQ(ret, UBSE_OK);
758-}
759- 
760-TEST_F(TestProcessMemPidBridge, FaultHandlerKernelRebootEvent)
761-{
762- auto ret = ProcessMemPidBridge::FaultHandler(ubse::ras::ALARM_KERNEL_REBOOT_EVENT, "NODE_REBOOT_1");
763- EXPECT_EQ(ret, UBSE_OK);
764-}
765- 
766-// ==================== SendSetPidMapErrorResponse all branches ====================
767- 
768-TEST_F(TestProcessMemPidBridge, SendSetPidMapErrorResponseAllBranches)
769-{
770- // UBSE_ERR_NOT_EXIST -> "PID does not exist on current node"
771- auto ret1 = SendSetPidMapErrorResponse(UBSE_ERR_NOT_EXIST, 99999);
772- EXPECT_EQ(ret1, UBSE_OK);
773- 
774- // UBSE_ERR_INVALID_ARG -> "PID start time mismatch..."
775- auto ret2 = SendSetPidMapErrorResponse(UBSE_ERR_INVALID_ARG, 99999);
776- EXPECT_EQ(ret2, UBSE_OK);
777- 
778- // UBSE_ERR_RESOURCE_BUSY -> "Cannot modify srcNumaId..."
779- auto ret3 = SendSetPidMapErrorResponse(UBSE_ERR_RESOURCE_BUSY, 99999);
780- EXPECT_EQ(ret3, UBSE_OK);
781- 
782- // Default -> "Set PID info failed"
783- auto ret4 = SendSetPidMapErrorResponse(UBSE_ERROR, 99999);
784- EXPECT_EQ(ret4, UBSE_OK);
785-}
786- 
787-// ==================== SendPidSetResponse edge cases ====================
788- 
789-TEST_F(TestProcessMemPidBridge, SendPidSetResponseWithLongMessage)
790-{
791- auto ret = SendPidSetResponse(1, "A very long error message that exceeds normal length", 99999);
792- EXPECT_EQ(ret, UBSE_OK);
793-}
794- 
795-// ==================== MemoryReturn with empty nodeId ====================
796- 
797TEST_F(TestProcessMemPidBridge, MemoryReturnEmptyNodeId)85TEST_F(TestProcessMemPidBridge, MemoryReturnEmptyNodeId)
798{86{
799 ubse::nodeController::MockSetCurrentNodeId("");87 ubse::nodeController::MockSetCurrentNodeId("");
@@ -802,62 +90,6 @@ TEST_F(TestProcessMemPidBridge, MemoryReturnEmptyNodeId)
802 ubse::nodeController::MockResetCurrentNodeId();90 ubse::nodeController::MockResetCurrentNodeId();
803}91}
804 92 
805-// ==================== GetRemoteNumaSocketInfo retry tests ====================
806- 
807-TEST_F(TestProcessMemPidBridge, GetRemoteNumaSocketInfoRetryAndError)
808-{
809- ubse::mem::controller::MockSetDebtInfoWithNodeError(UBSE_ERROR);
810- 
811- ubse::mem::controller::UbseMemNumaDesc desc{};
812- desc.name = "retry_test";
813- desc.exportNode.slotId = 5;
814- 
815- uint32_t socketId = 0;
816- uint64_t numaId = 0;
817- auto ret = ProcessMemPidBridge::GetRemoteNumaSocketInfo(desc, socketId, numaId);
818- EXPECT_NE(ret, UBSE_OK);
819- 
820- ubse::mem::controller::MockSetDebtInfoWithNodeError(UBSE_OK);
821-}
822- 
823-// ==================== NotifyBorrowNodesOnFault retry+error tests ====================
824- 
825-TEST_F(TestProcessMemPidBridge, NotifyBorrowNodesOnFaultRetryExhausted)
826-{
827- ubse::mem::controller::MockSetDebtInfoWithNodeError(UBSE_ERROR);
828- 
829- auto ret = ProcessMemPidBridge::NotifyBorrowNodesOnFault("FAULT_RETRY_NODE");
830- EXPECT_NE(ret, UBSE_OK);
831- 
832- ubse::mem::controller::MockSetDebtInfoWithNodeError(UBSE_OK);
833-}
834- 
835-// ==================== SetPidInfo success path test ====================
836- 
837-TEST_F(TestProcessMemPidBridge, SetPidInfoSuccessPath)
838-{
839- ProcessMemPidInfo pidInfo{};
840- pidInfo.configInfo.pid = getpid();
841- pidInfo.configInfo.evictThreshold = 80;
842- 
843- ubse::serial::UbseSerialization serializer;
844- pidInfo.configInfo.SerializeConfigInfo(serializer);
845- 
846- api::server::UbseIpcMessage request{};
847- request.buffer = serializer.GetBuffer();
848- request.length = static_cast<uint32_t>(serializer.GetLength());
849- api::server::UbseRequestContext context{};
850- context.requestId = 100;
851- 
852- auto ret = SetPidInfo(request, context);
853- EXPECT_EQ(ret, UBSE_OK);
854- 
855- // Cleanup
856- ProcessMemPidInfoManager::GetInstance().UnsetPidInfo(getpid());
857-}
858- 
859-// ==================== MemoryReturn with delete error ====================
860- 
861TEST_F(TestProcessMemPidBridge, MemoryReturnDeleteError)93TEST_F(TestProcessMemPidBridge, MemoryReturnDeleteError)
862{94{
863 ubse::mem::controller::MockSetNumaDeleteError(UBSE_ERROR);95 ubse::mem::controller::MockSetNumaDeleteError(UBSE_ERROR);
@@ -868,110 +100,165 @@ TEST_F(TestProcessMemPidBridge, MemoryReturnDeleteError)
868 ubse::mem::controller::MockSetNumaDeleteError(UBSE_OK);100 ubse::mem::controller::MockSetNumaDeleteError(UBSE_OK);
869}101}
870 102 
871-// ==================== MemoryBorrow create error tests ====================103+namespace {
872 104 
873-TEST_F(TestProcessMemPidBridge, MemoryBorrowCreateError)105+def::ProcessMemNewConfigInfo MakePidCfg(const std::string& pid, uint64_t maxMemory, double ratio)
874{106{
875- ProcessMemPidInfo pidInfo{};107+ def::ProcessMemNewConfigInfo cfg{};
876- pidInfo.configInfo.pid = 3001;108+ cfg.isPid = true;
877- pidInfo.memBorrowInfo.exportSlotId = -1;109+ cfg.identifier = pid;
878- 110+ cfg.maxMemory = maxMemory;
879- ubse::mem::controller::UbseMemBorrower borrower{};111+ cfg.remoteRatio = ratio;
880- borrower.nodeId = "NODE0";112+ return cfg;
881- 
882- MemoryBorrowRequest request{};
883- request.name = "borrow_create_err";
884- request.size = 1024 * 1024;
885- 
886- ubse::mem::controller::MockSetNumaCreateError(UBSE_ERROR);
887- 
888- ubse::mem::controller::UbseMemNumaDesc borrowInfo{};
889- auto ret = ProcessMemPidBridge::MemoryBorrow(pidInfo, borrower, request, borrowInfo);
890- EXPECT_NE(ret, UBSE_OK);
891- 
892- ubse::mem::controller::MockSetNumaCreateError(UBSE_OK);
893}113}
894 114 
895-TEST_F(TestProcessMemPidBridge, MemoryBorrowWithCandidateCreateError)115+def::ProcessMemNewConfigInfo MakeNameCfg(const std::string& name, uint64_t maxMemory, double ratio)
896{116{
897- ProcessMemPidInfo pidInfo{};117+ def::ProcessMemNewConfigInfo cfg{};
898- pidInfo.configInfo.pid = 3002;118+ cfg.isPid = false;
899- pidInfo.memBorrowInfo.exportSlotId = 5;119+ cfg.identifier = name;
900- 120+ cfg.maxMemory = maxMemory;
901- ubse::mem::controller::UbseMemBorrower borrower{};121+ cfg.remoteRatio = ratio;
902- borrower.nodeId = "NODE0";122+ return cfg;
903- 
904- MemoryBorrowRequest request{};
905- request.name = "borrow_candidate_err";
906- request.size = 2048 * 1024;
907- 
908- ubse::mem::controller::MockSetNumaCreateError(UBSE_ERROR);
909- 
910- ubse::mem::controller::UbseMemNumaDesc borrowInfo{};
911- auto ret = ProcessMemPidBridge::MemoryBorrow(pidInfo, borrower, request, borrowInfo);
912- EXPECT_NE(ret, UBSE_OK);
913- 
914- ubse::mem::controller::MockSetNumaCreateError(UBSE_OK);
915}123}
916 124 
917-// ==================== FaultHandler error paths ====================125+} // namespace
918 126 
919-TEST_F(TestProcessMemPidBridge, FaultHandlerWithHandleNodeFaultError)127+TEST_F(TestProcessMemPidBridge, DisplayConfigPidAlone)
920{128{
921- // HandleNodeFaultEvent may return an error for unexpected input129+ std::vector<def::ProcessMemNewConfigInfo> configs = {MakePidCfg("1001", 10ull * 1024 * 1024 * 1024, 0.5)};
922- auto ret = ProcessMemPidBridge::FaultHandler(ubse::ras::ALARM_PANIC_EVENT, "");130+ 
923- // The fault handler should still work, HandleNodeFaultEvent may succeed131+ std::vector<def::ProcessMemDisplayEntry> entries;
924- (void)ret;132+ BuildConfigEntries(entries, configs);
133+ 
134+ ASSERT_EQ(entries.size(), 1u);
135+ EXPECT_EQ(entries[0].pid, 1001);
136+ EXPECT_EQ(entries[0].name, "N/A");
137+ EXPECT_EQ(entries[0].maxMemory, 10ull * 1024 * 1024 * 1024);
138+ EXPECT_DOUBLE_EQ(entries[0].remoteRatio, 0.5);
925}139}
926 140 
927-// ==================== NotifyBorrowNodesOnFault with remote targets (coverage for SendProcessMemNodeFaultToNode) ====================141+TEST_F(TestProcessMemPidBridge, DisplayConfigNameAlone)
928- 
929-TEST_F(TestProcessMemPidBridge, NotifyBorrowNodesOnFaultSendsToRemote)
930{142{
931- ubse::mem::controller::UbseNumaMemoryDebtInfo debt{};143+ std::vector<def::ProcessMemNewConfigInfo> configs = {MakeNameCfg("testproc", 5ull * 1024 * 1024 * 1024, 0.3)};
932- debt.name = "send_to_remote";
933- debt.lentNodeId = "FAULT_SEND_NODE";
934- debt.borrowNodeId = "REMOTE_TARGET"; // != "NODE0"
935 144 
936- ubse::mem::controller::MockSetDebtInfos({debt});145+ std::vector<def::ProcessMemDisplayEntry> entries;
146+ BuildConfigEntries(entries, configs);
937 147 
938- auto ret = ProcessMemPidBridge::NotifyBorrowNodesOnFault("FAULT_SEND_NODE");148+ ASSERT_EQ(entries.size(), 1u);
939- EXPECT_EQ(ret, UBSE_OK);149+ EXPECT_EQ(entries[0].pid, 0);
940- 150+ EXPECT_EQ(entries[0].name, "testproc");
941- ubse::mem::controller::MockClearDebtInfos();151+ EXPECT_EQ(entries[0].maxMemory, 5ull * 1024 * 1024 * 1024);
152+ EXPECT_DOUBLE_EQ(entries[0].remoteRatio, 0.3);
942}153}
943 154 
944-// ==================== FaultHandler with NotifyBorrowNodesOnFault error ====================155+TEST_F(TestProcessMemPidBridge, DisplayConfigPidAndNameSorted)
945- 
946-TEST_F(TestProcessMemPidBridge, FaultHandlerNotifyBorrowNodesFailed)
947{156{
948- ubse::mem::controller::MockSetDebtInfoWithNodeError(UBSE_ERROR);157+ std::vector<def::ProcessMemNewConfigInfo> configs = {MakePidCfg("2002", 8ull * 1024 * 1024 * 1024, 0.4),
158+ MakeNameCfg("testproc", 5ull * 1024 * 1024 * 1024, 0.3),
159+ MakePidCfg("1001", 10ull * 1024 * 1024 * 1024, 0.5)};
949 160 
950- auto ret = ProcessMemPidBridge::FaultHandler(ubse::ras::ALARM_PANIC_EVENT, "FAULT_NOTIFY_FAIL");161+ std::vector<def::ProcessMemDisplayEntry> entries;
951- EXPECT_NE(ret, UBSE_OK);162+ BuildConfigEntries(entries, configs);
952 163 
953- ubse::mem::controller::MockSetDebtInfoWithNodeError(UBSE_OK);164+ ASSERT_EQ(entries.size(), 3u);
165+ EXPECT_EQ(entries[0].pid, 1001);
166+ EXPECT_EQ(entries[0].name, "N/A");
167+ EXPECT_EQ(entries[1].pid, 2002);
168+ EXPECT_EQ(entries[1].name, "N/A");
169+ EXPECT_EQ(entries[2].pid, 0);
170+ EXPECT_EQ(entries[2].name, "testproc");
954}171}
955 172 
956-// ==================== SetPidInfo with srcNumaId not found ====================173+TEST_F(TestProcessMemPidBridge, DisplayDetailNameManagedEntries)
957- 
958-TEST_F(TestProcessMemPidBridge, SetPidInfoSrcNumaIdNotFound)
959{174{
960- ProcessMemPidConfigInfo configInfo{};175+ std::map<pid_t, def::ManagedPidEntry> snapshot;
961- configInfo.pid = getpid();176+ def::ManagedPidEntry e1{};
962- configInfo.evictThreshold = 80;177+ e1.pid = 1001;
963- configInfo.srcNumaId = 42; // Non-matching numaId178+ e1.nameConfigName = "testproc";
179+ e1.maxMemory = 5ull * 1024 * 1024 * 1024;
180+ e1.remoteRatio = 0.3;
181+ def::ManagedPidEntry e2{};
182+ e2.pid = 2002;
183+ e2.nameConfigName = "testproc";
184+ e2.maxMemory = 5ull * 1024 * 1024 * 1024;
185+ e2.remoteRatio = 0.3;
186+ snapshot[1001] = e1;
187+ snapshot[2002] = e2;
964 188 
965- ubse::serial::UbseSerialization serializer;189+ std::vector<def::ProcessMemDisplayEntry> entries;
966- configInfo.SerializeConfigInfo(serializer);190+ BuildProcDetailEntries(entries, snapshot);
967 191 
968- api::server::UbseIpcMessage request{};192+ ASSERT_EQ(entries.size(), 2u);
969- request.buffer = serializer.GetBuffer();193+ EXPECT_EQ(entries[0].pid, 1001);
970- request.length = static_cast<uint32_t>(serializer.GetLength());194+ EXPECT_EQ(entries[0].name, "testproc");
971- api::server::UbseRequestContext context{};195+ EXPECT_EQ(entries[0].maxMemory, 5ull * 1024 * 1024 * 1024);
972- context.requestId = 101;196+ EXPECT_DOUBLE_EQ(entries[0].remoteRatio, 0.3);
197+ EXPECT_EQ(entries[1].pid, 2002);
198+ EXPECT_EQ(entries[1].name, "testproc");
199+ EXPECT_EQ(entries[1].maxMemory, 5ull * 1024 * 1024 * 1024);
200+}
973 201 
974- auto ret = SetPidInfo(request, context);202+TEST_F(TestProcessMemPidBridge, DisplayDetailNoManagedEntries)
975- EXPECT_EQ(ret, UBSE_OK); // SendPidSetResponse returns UBSE_OK even for error responses203+{
204+ std::map<pid_t, def::ManagedPidEntry> snapshot;
205+ 
206+ std::vector<def::ProcessMemDisplayEntry> entries;
207+ BuildProcDetailEntries(entries, snapshot);
208+ 
209+ EXPECT_TRUE(entries.empty());
210+}
211+ 
212+TEST_F(TestProcessMemPidBridge, DisplayDetailPidEntryShowsNA)
213+{
214+ std::map<pid_t, def::ManagedPidEntry> snapshot;
215+ def::ManagedPidEntry e{};
216+ e.pid = 2002;
217+ e.maxMemory = 10ull * 1024 * 1024 * 1024;
218+ e.remoteRatio = 0.5;
219+ snapshot[2002] = e;
220+ 
221+ std::vector<def::ProcessMemDisplayEntry> entries;
222+ BuildProcDetailEntries(entries, snapshot);
223+ 
224+ ASSERT_EQ(entries.size(), 1u);
225+ EXPECT_EQ(entries[0].pid, 2002);
226+ EXPECT_EQ(entries[0].name, "N/A");
227+ EXPECT_EQ(entries[0].maxMemory, 10ull * 1024 * 1024 * 1024);
228+ EXPECT_DOUBLE_EQ(entries[0].remoteRatio, 0.5);
229+}
230+ 
231+TEST_F(TestProcessMemPidBridge, DisplayDetailMixedNameAndPidEntries)
232+{
233+ std::map<pid_t, def::ManagedPidEntry> snapshot;
234+ def::ManagedPidEntry e1{};
235+ e1.pid = 1001;
236+ e1.sources = static_cast<uint8_t>(def::ConfigSource::NAME_CONFIG);
237+ e1.nameConfigName = "testproc";
238+ e1.maxMemory = 5ull * 1024 * 1024 * 1024;
239+ e1.remoteRatio = 0.3;
240+ def::ManagedPidEntry e2{};
241+ e2.pid = 2002;
242+ // name+pid 同时命中: pid 配置优先, name 强制 N/A, 值取 pid 配置
243+ e2.sources = static_cast<uint8_t>(def::ConfigSource::PID_CONFIG) |
244+ static_cast<uint8_t>(def::ConfigSource::NAME_CONFIG);
245+ e2.nameConfigName = "testproc";
246+ e2.maxMemory = 10ull * 1024 * 1024 * 1024;
247+ e2.remoteRatio = 0.5;
248+ snapshot[1001] = e1;
249+ snapshot[2002] = e2;
250+ 
251+ std::vector<def::ProcessMemDisplayEntry> entries;
252+ BuildProcDetailEntries(entries, snapshot);
253+ 
254+ ASSERT_EQ(entries.size(), 2u);
255+ EXPECT_EQ(entries[0].pid, 1001);
256+ EXPECT_EQ(entries[0].name, "testproc");
257+ EXPECT_EQ(entries[0].maxMemory, 5ull * 1024 * 1024 * 1024);
258+ EXPECT_DOUBLE_EQ(entries[0].remoteRatio, 0.3);
259+ EXPECT_EQ(entries[1].pid, 2002);
260+ EXPECT_EQ(entries[1].name, "N/A");
261+ EXPECT_EQ(entries[1].maxMemory, 10ull * 1024 * 1024 * 1024);
262+ EXPECT_DOUBLE_EQ(entries[1].remoteRatio, 0.5);
976}263}
977} // namespace ubse::ut::process_mem264} // namespace ubse::ut::process_mem
@@ -12,9 +12,11 @@
12 12 
13#include "test_process_mem_pid_collect.h"13#include "test_process_mem_pid_collect.h"
14 14 
15+#include <fstream>
16+#include <sstream>
17+ 
15#include "process_mem_pid_info_manager.h"18#include "process_mem_pid_info_manager.h"
16 19 
17-// Forward declare functions from process_mem_pid_collect.cpp for testing
18namespace process_mem::collect {20namespace process_mem::collect {
19size_t GetPageSize();21size_t GetPageSize();
20bool GetNumaInfoFromToken(const std::string& token, const size_t pageSize,22bool GetNumaInfoFromToken(const std::string& token, const size_t pageSize,
@@ -22,138 +24,94 @@ bool GetNumaInfoFromToken(const std::string& token, const size_t pageSize,
22void ParseLine(const std::string& line, std::unordered_map<uint32_t, size_t>& numaMemDistribution);24void ParseLine(const std::string& line, std::unordered_map<uint32_t, size_t>& numaMemDistribution);
23std::vector<pid_t> GetChildrenPidsFallback(pid_t parentPid);25std::vector<pid_t> GetChildrenPidsFallback(pid_t parentPid);
24std::vector<pid_t> GetChildrenPids(pid_t parentPid);26std::vector<pid_t> GetChildrenPids(pid_t parentPid);
25-uint32_t GetPidInfoByCollect(process_mem::def::ProcessMemPidInfo& pidInfo, CollectInfoMap& pidCollectInfo);
26} // namespace process_mem::collect27} // namespace process_mem::collect
27 28 
28namespace ubse::ut::process_mem {29namespace ubse::ut::process_mem {
29using namespace ::process_mem::collect;30using namespace ::process_mem::collect;
30-using namespace ::process_mem::def;
31using namespace ::process_mem::manager;31using namespace ::process_mem::manager;
32+namespace def = ::process_mem::def;
32 33 
33void TestProcessMemPidCollect::SetUp() {}34void TestProcessMemPidCollect::SetUp() {}
34 35 
35void TestProcessMemPidCollect::TearDown() {}36void TestProcessMemPidCollect::TearDown() {}
36 37 
37-// ==================== GetPageSize tests ====================
38- 
39TEST_F(TestProcessMemPidCollect, GetPageSizeReturnsPositive)38TEST_F(TestProcessMemPidCollect, GetPageSizeReturnsPositive)
40{39{
41 size_t pageSize = GetPageSize();40 size_t pageSize = GetPageSize();
42 EXPECT_GT(pageSize, 0u);41 EXPECT_GT(pageSize, 0u);
43}42}
44 43 
45-// ==================== GetNumaInfoFromToken tests ====================44+struct NumaTokenCase {
45+ std::string token;
46+ size_t pageSize;
47+ bool expectEmpty;
48+ uint32_t expectNuma{0};
49+ uint64_t expectBytes{0};
50+ std::string secondToken;
51+};
46 52 
47-TEST_F(TestProcessMemPidCollect, GetNumaInfoFromTokenValid)53+struct NumaLineCase {
54+ std::string line;
55+ std::vector<std::pair<uint32_t, uint64_t>> expect;
56+};
57+ 
58+class TestProcessMemPidCollectNumaToken
59+ : public TestProcessMemPidCollect
60+ , public ::testing::WithParamInterface<NumaTokenCase> {
61+};
62+class TestProcessMemPidCollectParseLine
63+ : public TestProcessMemPidCollect
64+ , public ::testing::WithParamInterface<NumaLineCase> {
65+};
66+ 
67+TEST_P(TestProcessMemPidCollectNumaToken, GetNumaInfoFromToken)
48{68{
69+ const auto& tc = GetParam();
49 std::unordered_map<uint32_t, size_t> numaMemDistribution;70 std::unordered_map<uint32_t, size_t> numaMemDistribution;
50- bool result = GetNumaInfoFromToken("N0=100", 4096, numaMemDistribution);71+ bool result = GetNumaInfoFromToken(tc.token, tc.pageSize, numaMemDistribution);
51 EXPECT_TRUE(result);72 EXPECT_TRUE(result);
52- EXPECT_EQ(numaMemDistribution[0], 100u * 4096);73+ if (!tc.secondToken.empty()) {
74+ EXPECT_TRUE(GetNumaInfoFromToken(tc.secondToken, tc.pageSize, numaMemDistribution));
75+ }
76+ if (tc.expectEmpty) {
77+ EXPECT_TRUE(numaMemDistribution.empty());
78+ } else {
79+ EXPECT_EQ(numaMemDistribution[tc.expectNuma], tc.expectBytes);
80+ }
53}81}
54 82 
55-TEST_F(TestProcessMemPidCollect, GetNumaInfoFromTokenValidMultiplePages)83+INSTANTIATE_TEST_SUITE_P(
56-{84+ NumaTokenCases, TestProcessMemPidCollectNumaToken,
57- std::unordered_map<uint32_t, size_t> numaMemDistribution;85+ testing::Values(NumaTokenCase{"N0=100", 4096, false, 0, 100u * 4096, ""},
58- bool result = GetNumaInfoFromToken("N3=500", 1024, numaMemDistribution);86+ NumaTokenCase{"N3=500", 1024, false, 3, 500u * 1024, ""},
59- EXPECT_TRUE(result);87+ NumaTokenCase{"N12=256", 4096, false, 12, 256u * 4096, ""},
60- EXPECT_EQ(numaMemDistribution[3], 500u * 1024);88+ NumaTokenCase{"kernel=100", 4096, true, 0, 0, ""}, NumaTokenCase{"N", 4096, true, 0, 0, ""},
61-}89+ NumaTokenCase{"NX=100", 4096, true, 0, 0, ""}, NumaTokenCase{"", 4096, true, 0, 0, ""},
90+ NumaTokenCase{"N0=100", 4096, false, 0, 150u * 4096, "N0=50"},
91+ NumaTokenCase{"N0x100", 4096, true, 0, 0, ""}, NumaTokenCase{"N0=0", 4096, false, 0, 0u, ""},
92+ NumaTokenCase{"N-1=100", 4096, true, 0, 0, ""}, NumaTokenCase{" N0=100", 4096, true, 0, 0, ""},
93+ NumaTokenCase{"N999=123", 1024, false, 999, 123u * 1024, ""},
94+ NumaTokenCase{"N0=100=200", 4096, false, 0, 100u * 4096, ""}));
62 95 
63-TEST_F(TestProcessMemPidCollect, GetNumaInfoFromTokenMultiDigitNuma)96+TEST_P(TestProcessMemPidCollectParseLine, ParseLine)
64{97{
98+ const auto& tc = GetParam();
65 std::unordered_map<uint32_t, size_t> numaMemDistribution;99 std::unordered_map<uint32_t, size_t> numaMemDistribution;
66- bool result = GetNumaInfoFromToken("N12=256", 4096, numaMemDistribution);100+ EXPECT_NO_THROW(ParseLine(tc.line, numaMemDistribution));
67- EXPECT_TRUE(result);
68- EXPECT_EQ(numaMemDistribution[12], 256u * 4096);
69-}
70- 
71-TEST_F(TestProcessMemPidCollect, GetNumaInfoFromTokenNotNumaToken)
72-{
73- std::unordered_map<uint32_t, size_t> numaMemDistribution;
74- bool result = GetNumaInfoFromToken("kernel=100", 4096, numaMemDistribution);
75- EXPECT_TRUE(result);
76- EXPECT_TRUE(numaMemDistribution.empty());
77-}
78- 
79-TEST_F(TestProcessMemPidCollect, GetNumaInfoFromTokenTooShort)
80-{
81- std::unordered_map<uint32_t, size_t> numaMemDistribution;
82- bool result = GetNumaInfoFromToken("N", 4096, numaMemDistribution);
83- EXPECT_TRUE(result);
84- EXPECT_TRUE(numaMemDistribution.empty());
85-}
86- 
87-TEST_F(TestProcessMemPidCollect, GetNumaInfoFromTokenNoNumaAfterPrefix)
88-{
89- std::unordered_map<uint32_t, size_t> numaMemDistribution;
90- bool result = GetNumaInfoFromToken("NX=100", 4096, numaMemDistribution);
91- EXPECT_TRUE(result);
92- EXPECT_TRUE(numaMemDistribution.empty());
93-}
94- 
95-TEST_F(TestProcessMemPidCollect, GetNumaInfoFromTokenEmpty)
96-{
97- std::unordered_map<uint32_t, size_t> numaMemDistribution;
98- bool result = GetNumaInfoFromToken("", 4096, numaMemDistribution);
99- EXPECT_TRUE(result);
100- EXPECT_TRUE(numaMemDistribution.empty());
101-}
102- 
103-TEST_F(TestProcessMemPidCollect, GetNumaInfoFromTokenAccumulates)
104-{
105- std::unordered_map<uint32_t, size_t> numaMemDistribution;
106- // First token
107- GetNumaInfoFromToken("N0=100", 4096, numaMemDistribution);
108- // Second token with same numa adds to existing
109- GetNumaInfoFromToken("N0=50", 4096, numaMemDistribution);
110- EXPECT_EQ(numaMemDistribution[0], 150u * 4096);
111-}
112- 
113-TEST_F(TestProcessMemPidCollect, GetNumaInfoFromTokenNoEquals)
114-{
115- std::unordered_map<uint32_t, size_t> numaMemDistribution;
116- bool result = GetNumaInfoFromToken("N0x100", 4096, numaMemDistribution);
117- EXPECT_TRUE(result);
118- EXPECT_TRUE(numaMemDistribution.empty());
119-}
120- 
121-// ==================== ParseLine tests ====================
122- 
123-TEST_F(TestProcessMemPidCollect, ParseLineSingleNumaToken)
124-{
125- std::unordered_map<uint32_t, size_t> numaMemDistribution;
126- ParseLine("N0=100", numaMemDistribution);
127 size_t pageSize = GetPageSize();101 size_t pageSize = GetPageSize();
128- EXPECT_EQ(numaMemDistribution[0], 100u * pageSize);102+ ASSERT_EQ(numaMemDistribution.size(), tc.expect.size());
103+ for (const auto& [numa, pages] : tc.expect) {
104+ EXPECT_EQ(numaMemDistribution[numa], pages * pageSize);
105+ }
129}106}
130 107 
131-TEST_F(TestProcessMemPidCollect, ParseLineMultipleNumaTokens)108+INSTANTIATE_TEST_SUITE_P(ParseLineCases, TestProcessMemPidCollectParseLine,
132-{109+ testing::Values(NumaLineCase{"N0=100", {{0, 100}}},
133- std::unordered_map<uint32_t, size_t> numaMemDistribution;110+ NumaLineCase{"N0=100 N1=200 N2=50", {{0, 100}, {1, 200}, {2, 50}}},
134- ParseLine("N0=100 N1=200 N2=50", numaMemDistribution);111+ NumaLineCase{"N0=100 kernel=50 N1=200", {{0, 100}, {1, 200}}},
135- size_t pageSize = GetPageSize();112+ NumaLineCase{"", {}},
136- EXPECT_EQ(numaMemDistribution[0], 100u * pageSize);113+ NumaLineCase{" N0=100 N1=200 ", {{0, 100}, {1, 200}}},
137- EXPECT_EQ(numaMemDistribution[1], 200u * pageSize);114+ NumaLineCase{"N0=100\nN1=200", {{0, 100}, {1, 200}}}));
138- EXPECT_EQ(numaMemDistribution[2], 50u * pageSize);
139-}
140- 
141-TEST_F(TestProcessMemPidCollect, ParseLineMixedTokens)
142-{
143- std::unordered_map<uint32_t, size_t> numaMemDistribution;
144- ParseLine("N0=100 kernel=50 N1=200", numaMemDistribution);
145- EXPECT_GT(numaMemDistribution[0], 0u);
146- EXPECT_GT(numaMemDistribution[1], 0u);
147-}
148- 
149-TEST_F(TestProcessMemPidCollect, ParseLineEmpty)
150-{
151- std::unordered_map<uint32_t, size_t> numaMemDistribution;
152- EXPECT_NO_THROW(ParseLine("", numaMemDistribution));
153- EXPECT_TRUE(numaMemDistribution.empty());
154-}
155- 
156-// ==================== GetChildrenPids tests ====================
157 115 
158TEST_F(TestProcessMemPidCollect, GetChildrenPidsFallbackReturnsVector)116TEST_F(TestProcessMemPidCollect, GetChildrenPidsFallbackReturnsVector)
159{117{
@@ -167,59 +125,6 @@ TEST_F(TestProcessMemPidCollect, GetChildrenPidsReturnsVector)
167 EXPECT_TRUE(children.empty() || !children.empty());125 EXPECT_TRUE(children.empty() || !children.empty());
168}126}
169 127 
170-// ==================== GetPidInfoByCollect tests ====================
171- 
172-TEST_F(TestProcessMemPidCollect, GetPidInfoByCollectNonExistentPid)
173-{
174- ProcessMemPidInfo pidInfo{};
175- pidInfo.configInfo.pid = 99999999;
176- CollectInfoMap pidCollectInfo;
177- auto ret = GetPidInfoByCollect(pidInfo, pidCollectInfo);
178- EXPECT_NE(ret, UBSE_OK);
179- EXPECT_EQ(pidInfo.processStatus, ProcessStatus::INACTIVE);
180-}
181- 
182-TEST_F(TestProcessMemPidCollect, RegisterCollectHandlerSuccess)
183-{
184- auto& collector = ProcessMemPidCollect::GetInstance();
185- NumaMemDistributionCollectHandler handler = [](CollectInfoMap) {
186- };
187- auto ret = collector.RegisterCollectHandler("test_handler", handler);
188- EXPECT_EQ(ret, UBSE_OK);
189- collector.UnRegisterCollectHandler("test_handler");
190-}
191- 
192-TEST_F(TestProcessMemPidCollect, RegisterCollectHandlerNullHandler)
193-{
194- auto& collector = ProcessMemPidCollect::GetInstance();
195- NumaMemDistributionCollectHandler nullHandler;
196- auto ret = collector.RegisterCollectHandler("null_handler", nullHandler);
197- EXPECT_EQ(ret, UBSE_ERROR);
198-}
199- 
200-TEST_F(TestProcessMemPidCollect, RegisterCollectHandlerOverwrite)
201-{
202- auto& collector = ProcessMemPidCollect::GetInstance();
203- NumaMemDistributionCollectHandler handler1 = [](CollectInfoMap) {
204- };
205- NumaMemDistributionCollectHandler handler2 = [](CollectInfoMap) {
206- };
207- 
208- auto ret = collector.RegisterCollectHandler("overwrite_handler", handler1);
209- EXPECT_EQ(ret, UBSE_OK);
210- 
211- ret = collector.RegisterCollectHandler("overwrite_handler", handler2);
212- EXPECT_EQ(ret, UBSE_OK);
213- 
214- collector.UnRegisterCollectHandler("overwrite_handler");
215-}
216- 
217-TEST_F(TestProcessMemPidCollect, UnRegisterCollectHandlerNotExist)
218-{
219- auto& collector = ProcessMemPidCollect::GetInstance();
220- EXPECT_NO_THROW(collector.UnRegisterCollectHandler("non_existent_handler"));
221-}
222- 
223TEST_F(TestProcessMemPidCollect, CollectProcessNumaMemDistributionInvalidPid)128TEST_F(TestProcessMemPidCollect, CollectProcessNumaMemDistributionInvalidPid)
224{129{
225 auto& collector = ProcessMemPidCollect::GetInstance();130 auto& collector = ProcessMemPidCollect::GetInstance();
@@ -236,114 +141,11 @@ TEST_F(TestProcessMemPidCollect, CollectProcessNumaMemDistributionNonExistentPid
236 EXPECT_NE(ret, UBSE_OK);141 EXPECT_NE(ret, UBSE_OK);
237}142}
238 143 
239-TEST_F(TestProcessMemPidCollect, RegisterMultipleHandlers)
240-{
241- auto& collector = ProcessMemPidCollect::GetInstance();
242- NumaMemDistributionCollectHandler handler1 = [](CollectInfoMap) {
243- };
244- NumaMemDistributionCollectHandler handler2 = [](CollectInfoMap) {
245- };
246- 
247- auto ret1 = collector.RegisterCollectHandler("multi_handler_1", handler1);
248- auto ret2 = collector.RegisterCollectHandler("multi_handler_2", handler2);
249- EXPECT_EQ(ret1, UBSE_OK);
250- EXPECT_EQ(ret2, UBSE_OK);
251- 
252- collector.UnRegisterCollectHandler("multi_handler_1");
253- collector.UnRegisterCollectHandler("multi_handler_2");
254-}
255- 
256-TEST_F(TestProcessMemPidCollect, UnRegisterAndReRegister)
257-{
258- auto& collector = ProcessMemPidCollect::GetInstance();
259- NumaMemDistributionCollectHandler handler = [](CollectInfoMap) {
260- };
261- 
262- auto ret = collector.RegisterCollectHandler("reregister_handler", handler);
263- EXPECT_EQ(ret, UBSE_OK);
264- 
265- collector.UnRegisterCollectHandler("reregister_handler");
266- 
267- ret = collector.RegisterCollectHandler("reregister_handler", handler);
268- EXPECT_EQ(ret, UBSE_OK);
269- 
270- collector.UnRegisterCollectHandler("reregister_handler");
271-}
272- 
273-// ==================== Additional edge case tests ====================
274- 
275-TEST_F(TestProcessMemPidCollect, GetNumaInfoFromTokenZeroPages)
276-{
277- std::unordered_map<uint32_t, size_t> numaMemDistribution;
278- bool result = GetNumaInfoFromToken("N0=0", 4096, numaMemDistribution);
279- EXPECT_TRUE(result);
280- EXPECT_EQ(numaMemDistribution[0], 0u);
281-}
282- 
283-TEST_F(TestProcessMemPidCollect, GetNumaInfoFromTokenNegativeNuma)
284-{
285- std::unordered_map<uint32_t, size_t> numaMemDistribution;
286- bool result = GetNumaInfoFromToken("N-1=100", 4096, numaMemDistribution);
287- EXPECT_TRUE(result);
288- EXPECT_TRUE(numaMemDistribution.empty());
289-}
290- 
291-TEST_F(TestProcessMemPidCollect, GetNumaInfoFromTokenLeadingSpaces)
292-{
293- std::unordered_map<uint32_t, size_t> numaMemDistribution;
294- bool result = GetNumaInfoFromToken(" N0=100", 4096, numaMemDistribution);
295- EXPECT_TRUE(result);
296- EXPECT_TRUE(numaMemDistribution.empty());
297-}
298- 
299-TEST_F(TestProcessMemPidCollect, GetNumaInfoFromTokenWithMaxNuma)
300-{
301- std::unordered_map<uint32_t, size_t> numaMemDistribution;
302- bool result = GetNumaInfoFromToken("N999=123", 1024, numaMemDistribution);
303- EXPECT_TRUE(result);
304- EXPECT_EQ(numaMemDistribution[999], 123u * 1024);
305-}
306- 
307-TEST_F(TestProcessMemPidCollect, GetNumaInfoFromTokenMultipleEquals)
308-{
309- std::unordered_map<uint32_t, size_t> numaMemDistribution;
310- bool result = GetNumaInfoFromToken("N0=100=200", 4096, numaMemDistribution);
311- EXPECT_TRUE(result);
312- // strtoul stops at non-digit, so after = we get "100=200" -> strtoul gets 100
313-}
314- 
315-TEST_F(TestProcessMemPidCollect, ParseLineWithEmptyTokens)
316-{
317- std::unordered_map<uint32_t, size_t> numaMemDistribution;
318- ParseLine(" N0=100 N1=200 ", numaMemDistribution);
319- size_t pageSize = GetPageSize();
320- EXPECT_EQ(numaMemDistribution[0], 100u * pageSize);
321- EXPECT_EQ(numaMemDistribution[1], 200u * pageSize);
322-}
323- 
324-TEST_F(TestProcessMemPidCollect, ParseLineNewlineHandling)
325-{
326- std::unordered_map<uint32_t, size_t> numaMemDistribution;
327- ParseLine("N0=100\nN1=200", numaMemDistribution);
328- size_t pageSize = GetPageSize();
329- EXPECT_EQ(numaMemDistribution[0], 100u * pageSize);
330-}
331- 
332TEST_F(TestProcessMemPidCollect, CollectProcessNumaMemDistributionInitPid)144TEST_F(TestProcessMemPidCollect, CollectProcessNumaMemDistributionInitPid)
333{145{
334 auto& collector = ProcessMemPidCollect::GetInstance();146 auto& collector = ProcessMemPidCollect::GetInstance();
335 std::unordered_map<uint32_t, size_t> numaMemDistribution;147 std::unordered_map<uint32_t, size_t> numaMemDistribution;
336 auto ret = collector.CollectProcessNumaMemDistribution(1, numaMemDistribution);148 auto ret = collector.CollectProcessNumaMemDistribution(1, numaMemDistribution);
337- // init process (pid 1) should exist
338- (void)ret;
339-}
340- 
341-TEST_F(TestProcessMemPidCollect, GetPidInfoByCollectInitPid)
342-{
343- ProcessMemPidInfo pidInfo{};
344- pidInfo.configInfo.pid = 1;
345- CollectInfoMap pidCollectInfo;
346- auto ret = GetPidInfoByCollect(pidInfo, pidCollectInfo);
347 (void)ret;149 (void)ret;
348}150}
349 151 
@@ -359,24 +161,63 @@ TEST_F(TestProcessMemPidCollect, GetChildrenPidsFallbackForKnownPid)
359 EXPECT_TRUE(children.empty() || !children.empty());161 EXPECT_TRUE(children.empty() || !children.empty());
360}162}
361 163 
362-TEST_F(TestProcessMemPidCollect, RegisterNullHandlerByName)164+std::string ReadComm(pid_t pid)
363{165{
364- auto& collector = ProcessMemPidCollect::GetInstance();166+ std::ifstream commFile("/proc/" + std::to_string(pid) + "/comm");
365- NumaMemDistributionCollectHandler nullHandler;167+ std::string comm;
366- auto ret = collector.RegisterCollectHandler("test_null_handler", nullHandler);168+ std::getline(commFile, comm);
367- EXPECT_EQ(ret, UBSE_ERROR);169+ return comm;
368}170}
369 171 
370-TEST_F(TestProcessMemPidCollect, UnRegisterThenCallCollect)172+TEST_F(TestProcessMemPidCollect, HandleNewPidsByNameSkipsRootProcess)
371{173{
372- auto& collector = ProcessMemPidCollect::GetInstance();174+ ASSERT_EQ(GetProcUid(1), 0);
373- NumaMemDistributionCollectHandler handler = [](CollectInfoMap) {
374- };
375- collector.RegisterCollectHandler("tmp_handler", handler);
376- collector.UnRegisterCollectHandler("tmp_handler");
377 175 
378- std::unordered_map<uint32_t, size_t> numaMemDistribution;176+ auto& mgr = ProcessMemPidInfoManager::GetInstance();
379- auto ret = collector.CollectProcessNumaMemDistribution(1, numaMemDistribution);177+ std::string comm = ReadComm(1);
380- (void)ret;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);
381}222}
382} // namespace ubse::ut::process_mem223} // namespace ubse::ut::process_mem
@@ -14,7 +14,10 @@
14#define TEST_PROCESS_MEM_PID_COLLECT_H14#define TEST_PROCESS_MEM_PID_COLLECT_H
15 15 
16#include "gtest/gtest.h"16#include "gtest/gtest.h"
17+ 
18+#define private public
17#include "process_mem_pid_collect.h"19#include "process_mem_pid_collect.h"
20+#undef private
18 21 
19namespace ubse::ut::process_mem {22namespace ubse::ut::process_mem {
20 23 
@@ -18,9 +18,15 @@ namespace ubse::ut::process_mem {
18using namespace ::process_mem::manager;18using namespace ::process_mem::manager;
19using namespace ::process_mem::def;19using namespace ::process_mem::def;
20 20 
21-void TestProcessMemPidConfigManager::SetUp() {}21+void TestProcessMemPidConfigManager::SetUp()
22+{
23+ ubse::storage::MockResetStorageErrors();
24+}
22 25 
23-void TestProcessMemPidConfigManager::TearDown() {}26+void TestProcessMemPidConfigManager::TearDown()
27+{
28+ ubse::storage::MockResetStorageErrors();
29+}
24 30 
25TEST_F(TestProcessMemPidConfigManager, GetExactStartTimeInvalidPid)31TEST_F(TestProcessMemPidConfigManager, GetExactStartTimeInvalidPid)
26{32{
@@ -34,198 +40,121 @@ TEST_F(TestProcessMemPidConfigManager, GetExactStartTimeNonExistentPid)
34 EXPECT_EQ(startTime, 0u);40 EXPECT_EQ(startTime, 0u);
35}41}
36 42 
37-TEST_F(TestProcessMemPidConfigManager, IsPidInfoExistInvalidPid)43+TEST_F(TestProcessMemPidConfigManager, PersistProcMemConfigPidMode)
38{44{
39- bool result = ProcessMemPidConfigManager::IsPidInfoExist(-1, 12345);45+ ProcessMemNewConfigInfo config{};
40- EXPECT_FALSE(result);46+ config.isPid = true;
47+ config.identifier = "1234";
48+ config.maxMemory = 1024000;
49+ config.remoteRatio = 0.5;
50+ config.startTime = 9999;
51+ 
52+ auto ret = ProcessMemPidConfigManager::PersistProcMemConfig(config);
53+ EXPECT_EQ(ret, UBSE_OK);
54+ 
55+ ProcessMemPidConfigManager::DeleteProcMemConfig(true, "1234");
41}56}
42 57 
43-TEST_F(TestProcessMemPidConfigManager, IsPidInfoExistNonExistentPid)58+TEST_F(TestProcessMemPidConfigManager, PersistProcMemConfigNameMode)
44{59{
45- bool result = ProcessMemPidConfigManager::IsPidInfoExist(99999999, 12345);60+ ProcessMemNewConfigInfo config{};
46- EXPECT_FALSE(result);61+ config.isPid = false;
62+ config.identifier = "myproc";
63+ config.maxMemory = 2048000;
64+ config.remoteRatio = 0.3;
65+ 
66+ auto ret = ProcessMemPidConfigManager::PersistProcMemConfig(config);
67+ EXPECT_EQ(ret, UBSE_OK);
68+ 
69+ ProcessMemPidConfigManager::DeleteProcMemConfig(false, "myproc");
47}70}
48 71 
49-TEST_F(TestProcessMemPidConfigManager, IsPidInfoExistValidPidWrongStartTime)72+TEST_F(TestProcessMemPidConfigManager, PersistProcMemConfigStoragePutError)
50{73{
51- bool result = ProcessMemPidConfigManager::IsPidInfoExist(1, 0);74+ ubse::storage::MockSetStoragePutError(UBSE_ERROR);
52- EXPECT_FALSE(result);75+ 
76+ ProcessMemNewConfigInfo config{};
77+ config.isPid = true;
78+ config.identifier = "4001";
79+ config.maxMemory = 1024000;
80+ 
81+ auto ret = ProcessMemPidConfigManager::PersistProcMemConfig(config);
82+ EXPECT_EQ(ret, UBSE_ERROR);
53}83}
54 84 
55-TEST_F(TestProcessMemPidConfigManager, IsPidInfoExistValidPid)85+TEST_F(TestProcessMemPidConfigManager, DeleteProcMemConfigNonExistent)
56{86{
57- auto exactStartTime = ProcessMemPidConfigManager::GetExactStartTime(1);87+ EXPECT_NO_THROW(ProcessMemPidConfigManager::DeleteProcMemConfig(true, "99999999"));
58- bool result = ProcessMemPidConfigManager::IsPidInfoExist(1, exactStartTime);
59- EXPECT_TRUE(result);
60}88}
61 89 
62-TEST_F(TestProcessMemPidConfigManager, CheckPidConfigInfoInvalidPid)90+TEST_F(TestProcessMemPidConfigManager, DeleteProcMemConfigStorageDeleteError)
63{91{
64- ProcessMemPidInfo pidInfo{};92+ ubse::storage::MockSetStorageDeleteError(UBSE_ERROR);
65- pidInfo.configInfo.pid = -1;93+ EXPECT_NO_THROW(ProcessMemPidConfigManager::DeleteProcMemConfig(true, "4002"));
66- pidInfo.startTime = 12345;
67- bool result = ProcessMemPidConfigManager::CheckPidConfigInfo(pidInfo);
68- EXPECT_FALSE(result);
69}94}
70 95 
71-TEST_F(TestProcessMemPidConfigManager, CheckPidConfigInfoNonExistentPid)96+TEST_F(TestProcessMemPidConfigManager, GetAllPersistedProcMemConfigsEmpty)
72{97{
73- ProcessMemPidInfo pidInfo{};98+ std::vector<ProcessMemNewConfigInfo> configs;
74- pidInfo.configInfo.pid = 99999999;99+ EXPECT_NO_THROW(ProcessMemPidConfigManager::GetAllPersistedProcMemConfigs(configs));
75- pidInfo.startTime = 12345;100+ EXPECT_TRUE(configs.empty());
76- bool result = ProcessMemPidConfigManager::CheckPidConfigInfo(pidInfo);
77- EXPECT_FALSE(result);
78}101}
79 102 
80-TEST_F(TestProcessMemPidConfigManager, CheckPidConfigInfoValidPidWrongStartTime)103+TEST_F(TestProcessMemPidConfigManager, GetAllPersistedProcMemConfigsStorageListError)
81{104{
82- ProcessMemPidInfo pidInfo{};105+ ubse::storage::MockSetStorageListError(UBSE_ERROR);
83- pidInfo.configInfo.pid = 1;106+ 
84- pidInfo.startTime = 0;107+ std::vector<ProcessMemNewConfigInfo> configs;
85- bool result = ProcessMemPidConfigManager::CheckPidConfigInfo(pidInfo);108+ EXPECT_NO_THROW(ProcessMemPidConfigManager::GetAllPersistedProcMemConfigs(configs));
86- EXPECT_FALSE(result);109+ EXPECT_TRUE(configs.empty());
87}110}
88 111 
89-TEST_F(TestProcessMemPidConfigManager, PersistPidConfigInfo)112+TEST_F(TestProcessMemPidConfigManager, QueryProcMemConfigCallbackNullBuffer)
90-{
91- ProcessMemPidInfo pidInfo{};
92- pidInfo.configInfo.pid = 1234;
93- pidInfo.configInfo.evictThreshold = 80;
94- pidInfo.configInfo.targetEvictThreshold = 70;
95- pidInfo.configInfo.reclaimThreshold = 50;
96- pidInfo.configInfo.expectedMemoryUsage = 1024000;
97- pidInfo.startTime = 9999;
98- 
99- EXPECT_NO_THROW(ProcessMemPidConfigManager::PersistPidConfigInfo(pidInfo));
100- 
101- ProcessMemPidConfigManager::DeletePidConfigInfo(1234);
102-}
103- 
104-TEST_F(TestProcessMemPidConfigManager, DeletePidConfigInfo)
105-{
106- ProcessMemPidInfo pidInfo{};
107- pidInfo.configInfo.pid = 5678;
108- pidInfo.configInfo.evictThreshold = 85;
109- pidInfo.startTime = 8888;
110- 
111- ProcessMemPidConfigManager::PersistPidConfigInfo(pidInfo);
112- EXPECT_NO_THROW(ProcessMemPidConfigManager::DeletePidConfigInfo(5678));
113-}
114- 
115-TEST_F(TestProcessMemPidConfigManager, DeletePidConfigInfoNonExistent)
116-{
117- EXPECT_NO_THROW(ProcessMemPidConfigManager::DeletePidConfigInfo(99999999));
118-}
119- 
120-TEST_F(TestProcessMemPidConfigManager, GetAllPersistedPidConfigInfo)
121-{
122- ProcessMemPidInfo pidInfo1{};
123- pidInfo1.configInfo.pid = 1111;
124- pidInfo1.startTime = 1000;
125- pidInfo1.configInfo.evictThreshold = 80;
126- 
127- ProcessMemPidInfo pidInfo2{};
128- pidInfo2.configInfo.pid = 2222;
129- pidInfo2.startTime = 2000;
130- pidInfo2.configInfo.evictThreshold = 90;
131- 
132- ProcessMemPidConfigManager::PersistPidConfigInfo(pidInfo1);
133- ProcessMemPidConfigManager::PersistPidConfigInfo(pidInfo2);
134- 
135- std::vector<ProcessMemPidInfo> pidInfos;
136- EXPECT_NO_THROW(ProcessMemPidConfigManager::GetAllPersistedPidConfigInfo(pidInfos));
137- 
138- ProcessMemPidConfigManager::DeletePidConfigInfo(1111);
139- ProcessMemPidConfigManager::DeletePidConfigInfo(2222);
140-}
141- 
142-TEST_F(TestProcessMemPidConfigManager, QueryPidConfigCallbackNullBuffer)
143{113{
144 UbseByteBuffer buff{};114 UbseByteBuffer buff{};
145 buff.data = nullptr;115 buff.data = nullptr;
146 buff.len = 0;116 buff.len = 0;
147- std::vector<ProcessMemPidInfo> pidInfos;117+ std::vector<ProcessMemNewConfigInfo> configs;
148- EXPECT_NO_THROW(ProcessMemPidConfigManager::QueryPidConfigCallback("prefix", "key", buff, &pidInfos));118+ EXPECT_NO_THROW(ProcessMemPidConfigManager::QueryProcMemConfigCallback("prefix", "key", buff, &configs));
149- EXPECT_TRUE(pidInfos.empty());119+ EXPECT_TRUE(configs.empty());
150}120}
151 121 
152-TEST_F(TestProcessMemPidConfigManager, QueryPidConfigCallbackValidBuffer)122+TEST_F(TestProcessMemPidConfigManager, QueryProcMemConfigCallbackValidBuffer)
153{123{
154- ProcessMemPidInfo pidInfo{};124+ ProcessMemNewConfigInfo config{};
155- pidInfo.configInfo.pid = 3333;125+ config.isPid = true;
156- pidInfo.startTime = 3000;126+ config.identifier = "3333";
157- pidInfo.configInfo.evictThreshold = 75;127+ config.maxMemory = 3072000;
128+ config.remoteRatio = 0.6;
129+ config.startTime = 3000;
158 130 
159 ubse::serial::UbseSerialization serializer;131 ubse::serial::UbseSerialization serializer;
160- auto ret = pidInfo.SerializePidInfo(serializer);132+ auto ret = config.Serialize(serializer);
161 EXPECT_EQ(ret, UBSE_OK);133 EXPECT_EQ(ret, UBSE_OK);
162 134 
163 UbseByteBuffer buff{};135 UbseByteBuffer buff{};
164 buff.data = serializer.GetBuffer();136 buff.data = serializer.GetBuffer();
165 buff.len = serializer.GetLength();137 buff.len = serializer.GetLength();
166 138 
167- std::vector<ProcessMemPidInfo> pidInfos;139+ std::vector<ProcessMemNewConfigInfo> configs;
168- EXPECT_NO_THROW(ProcessMemPidConfigManager::QueryPidConfigCallback("prefix", "3333", buff, &pidInfos));140+ EXPECT_NO_THROW(ProcessMemPidConfigManager::QueryProcMemConfigCallback("prefix", "3333", buff, &configs));
141+ ASSERT_EQ(configs.size(), 1u);
142+ EXPECT_TRUE(configs[0].isPid);
143+ EXPECT_EQ(configs[0].identifier, "3333");
144+ EXPECT_EQ(configs[0].maxMemory, 3072000u);
145+ EXPECT_DOUBLE_EQ(configs[0].remoteRatio, 0.6);
146+ EXPECT_EQ(configs[0].startTime, 3000u);
169}147}
170 148 
171-// ==================== Storage error path tests ====================149+TEST_F(TestProcessMemPidConfigManager, QueryProcMemConfigCallbackInvalidData)
172- 
173-TEST_F(TestProcessMemPidConfigManager, PersistPidConfigInfoStoragePutError)
174-{
175- ubse::storage::MockSetStoragePutError(UBSE_ERROR);
176- 
177- ProcessMemPidInfo pidInfo{};
178- pidInfo.configInfo.pid = 4001;
179- pidInfo.configInfo.evictThreshold = 80;
180- pidInfo.startTime = 1000;
181- 
182- EXPECT_NO_THROW(ProcessMemPidConfigManager::PersistPidConfigInfo(pidInfo));
183- 
184- ubse::storage::MockSetStoragePutError(UBSE_OK);
185-}
186- 
187-TEST_F(TestProcessMemPidConfigManager, DeletePidConfigInfoStorageDeleteError)
188-{
189- ubse::storage::MockSetStorageDeleteError(UBSE_ERROR);
190- 
191- EXPECT_NO_THROW(ProcessMemPidConfigManager::DeletePidConfigInfo(4002));
192- 
193- ubse::storage::MockSetStorageDeleteError(UBSE_OK);
194-}
195- 
196-TEST_F(TestProcessMemPidConfigManager, GetAllPersistedPidConfigInfoStorageQueryError)
197-{
198- ubse::storage::MockSetStorageQueryError(UBSE_ERROR);
199- 
200- std::vector<ProcessMemPidInfo> pidInfos;
201- EXPECT_NO_THROW(ProcessMemPidConfigManager::GetAllPersistedPidConfigInfo(pidInfos));
202- 
203- ubse::storage::MockSetStorageQueryError(UBSE_OK);
204-}
205- 
206-// ==================== CheckPidConfigInfo with valid pid ====================
207- 
208-TEST_F(TestProcessMemPidConfigManager, CheckPidConfigInfoValidPid)
209-{
210- ProcessMemPidInfo pidInfo{};
211- pidInfo.configInfo.pid = 1;
212- auto exactStartTime = ProcessMemPidConfigManager::GetExactStartTime(1);
213- pidInfo.startTime = exactStartTime;
214- bool result = ProcessMemPidConfigManager::CheckPidConfigInfo(pidInfo);
215- EXPECT_TRUE(result);
216-}
217- 
218-// ==================== QueryPidConfigCallback with deserialization error path ====================
219- 
220-TEST_F(TestProcessMemPidConfigManager, QueryPidConfigCallbackInvalidData)
221{150{
222 UbseByteBuffer buff{};151 UbseByteBuffer buff{};
223 uint8_t badData = 0xFF;152 uint8_t badData = 0xFF;
224 buff.data = &badData;153 buff.data = &badData;
225 buff.len = 1;154 buff.len = 1;
226 155 
227- std::vector<ProcessMemPidInfo> pidInfos;156+ std::vector<ProcessMemNewConfigInfo> configs;
228- EXPECT_NO_THROW(ProcessMemPidConfigManager::QueryPidConfigCallback("prefix", "key", buff, &pidInfos));157+ EXPECT_NO_THROW(ProcessMemPidConfigManager::QueryProcMemConfigCallback("prefix", "key", buff, &configs));
229- EXPECT_TRUE(pidInfos.empty());158+ EXPECT_TRUE(configs.empty());
230}159}
231} // namespace ubse::ut::process_mem160} // namespace ubse::ut::process_mem
@@ -0,0 +1,1797 @@
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+#include "test_process_mem_pid_decision.h"
13+ 
14+#include <unistd.h>
15+#include <cstring>
16+#include <set>
17+ 
18+#include "ubse_error.h"
19+#include "ubse_serial_util.h"
20+#include "process_mem_pid_collect.h"
21+#include "process_mem_pid_config_manager.h"
22+ 
23+namespace ubse::ut::process_mem {
24+using namespace ::process_mem::decision;
25+using namespace ::process_mem::manager;
26+using namespace ::process_mem::def;
27+using namespace ::process_mem::pid::bridge;
28+using namespace ::process_mem::collect;
29+ 
30+namespace {
31+constexpr uint64_t GB = 1073741824ULL;
32+constexpr uint64_t THRESHOLD_BYTES = 50 * GB;
33+constexpr uint64_t EMERGENCY_BYTES = 5 * GB;
34+ 
35+constexpr uint32_t KB_PER_GB = 1024 * 1024;
36+ 
37+std::vector<ubse::mem::controller::UbseNumaMemoryDebtInfo> MakeLentDebts(const std::vector<std::string>& borrowers)
38+{
39+ std::vector<ubse::mem::controller::UbseNumaMemoryDebtInfo> debts;
40+ for (const auto& borrower : borrowers) {
41+ ubse::mem::controller::UbseNumaMemoryDebtInfo debt;
42+ debt.name = "debt-" + borrower;
43+ debt.borrowNodeId = borrower;
44+ debt.lentNodeId = "NODE0";
45+ debt.size = 1 * GB;
46+ debt.remoteNumaId = 5;
47+ debts.push_back(debt);
48+ }
49+ return debts;
50+}
51+ 
52+ubse::mem::controller::UbseNumaMemoryDebtInfo MakeDebt(const std::string& name, const std::string& lender,
53+ int64_t remoteNuma, int32_t pid)
54+{
55+ ubse::mem::controller::UbseNumaMemoryDebtInfo debt;
56+ debt.name = name;
57+ debt.borrowNodeId = "NODE0";
58+ debt.lentNodeId = lender;
59+ debt.size = 1 * GB;
60+ debt.remoteNumaId = remoteNuma;
61+ ProcessMemUsrInfo usr{};
62+ usr.pid = pid;
63+ usr.srcNuma = 1;
64+ memset(debt.usrInfo, 0, sizeof(debt.usrInfo));
65+ memcpy(debt.usrInfo, &usr, sizeof(usr));
66+ return debt;
67+}
68+ 
69+BorrowState MakeBorrow(uint64_t amountGb, const std::string& debtId)
70+{
71+ BorrowState borrow;
72+ borrow.currentRemote = amountGb * GB;
73+ BorrowSlot slot;
74+ slot.debtId = debtId;
75+ slot.migratedBytes = amountGb * GB;
76+ slot.capacity = amountGb * GB;
77+ slot.status = BorrowSlotStatus::COMPLETED;
78+ slot.remoteNumaId = 5;
79+ borrow.slots.push_back(slot);
80+ return borrow;
81+}
82+ 
83+void AddManagedPid(pid_t pid, uint64_t maxGb, double ratio, uint64_t vmRssGb, const BorrowState& borrow = {})
84+{
85+ auto& mgr = ProcessMemPidInfoManager::GetInstance();
86+ mgr.AddNameSourceToManagedPid(pid, "proc" + std::to_string(pid), maxGb * GB, ratio);
87+ PidCollectInfoMap collectMap;
88+ collectMap.entries[pid].vmRssKb = vmRssGb * 1024 * 1024;
89+ mgr.UpdateManagedPidVmRssBatch(collectMap);
90+ if (!borrow.slots.empty()) {
91+ mgr.UpdateManagedPidBorrowState(pid, borrow, ProcessStatus::BORROWED);
92+ }
93+}
94+ 
95+// nodeFree 口径统一为 MemAvailable 后,mock 直接驱动 memAvailableReader
96+void MockNodeFreeBytes(uint64_t bytes)
97+{
98+ ProcessMemPidDecision::memAvailableReader = [bytes]() {
99+ return bytes / 1024;
100+ };
101+}
102+ 
103+void MockNodeFree(uint64_t freeGb)
104+{
105+ MockNodeFreeBytes(freeGb * GB);
106+}
107+ 
108+std::vector<ReturnRequestItem> DecodeItems(const std::string& payload)
109+{
110+ ubse::serial::UbseDeSerialization deserializer{reinterpret_cast<const ubse::serial::base_ptr_type*>(payload.data()),
111+ payload.size()};
112+ ubse::serial::common_len count = 0;
113+ deserializer >> ubse::serial::array_len_capture(count);
114+ std::vector<ReturnRequestItem> items;
115+ for (ubse::serial::common_len i = 0; i < count; ++i) {
116+ ReturnRequestItem item;
117+ deserializer >> item.name >> item.size;
118+ items.push_back(item);
119+ }
120+ return items;
121+}
122+} // namespace
123+ 
124+void TestProcessMemPidDecision::SetUp()
125+{
126+ ubse::mem::controller::MockResetAllErrors();
127+ ubse::com::MockResetRpcState();
128+ ubse::nodeController::MockSetCurrentNodeId("NODE0");
129+ 
130+ ubse::smap::MockResetMigrateState();
131+ ProcessMemPidBridge::rmrsMigrateOut = ubse::smap::MockRmrsMigrateOut;
132+ ProcessMemPidBridge::rmrsFreeWithMigrate = [](const std::string&) {
133+ return 0;
134+ };
135+ 
136+ auto& decision = ProcessMemPidDecision::GetInstance();
137+ decision.stopping_ = false;
138+ decision.freeMemoryThresholdBytes_ = THRESHOLD_BYTES;
139+ decision.emergencyThresholdBytes_ = EMERGENCY_BYTES;
140+ decision.fastPollIntervalMs_ = 200;
141+ decision.observeCycles_ = 6;
142+ decision.returnRetryIntervalMs_ = 1;
143+ decision.oomFastPoll_ = false;
144+ decision.oomFastWindowCount_ = 0;
145+ decision.oomFastWindowMin_ = 0;
146+ if (decision.borrowExecutor_ == nullptr || !decision.borrowExecutor_->IsStart()) {
147+ decision.borrowExecutor_ = ubse::task_executor::UbseTaskExecutor::Create("ProcMemDecisionTest", 2, 64);
148+ decision.borrowExecutor_->Start();
149+ }
150+ if (decision.returnExecutor_ == nullptr || !decision.returnExecutor_->IsStart()) {
151+ decision.returnExecutor_ = ubse::task_executor::UbseTaskExecutor::Create("ProcMemReturnTest", 2, 64);
152+ decision.returnExecutor_->Start();
153+ }
154+}
155+ 
156+void TestProcessMemPidDecision::TearDown()
157+{
158+ ProcessMemPidBridge::rmrsMigrateOut = {};
159+ ubse::smap::MockResetMigrateState();
160+ ProcessMemPidBridge::rmrsRemove = {};
161+ ProcessMemPidBridge::rmrsFreeWithMigrate = {};
162+ ProcessMemPidBridge::rmrsRemoteToRemote = {};
163+ ProcessMemPidBridge::rmrsProcessConfigQuery = {};
164+ ProcessMemPidDecision::memAvailableReader = nullptr;
165+ ProcessMemPidCollect::GetInstance().SetCollectVmRssOverride(nullptr);
166+ 
167+ auto& mgr = ProcessMemPidInfoManager::GetInstance();
168+ auto snapshot = mgr.GetManagedPidCacheSnapshot();
169+ for (const auto& [pid, entry] : snapshot) {
170+ mgr.RemoveManagedPidEntry(pid);
171+ }
172+ ubse::mem::controller::MockResetAllErrors();
173+ ubse::task_executor::MockSetExecutorAsync(false);
174+ ubse::com::MockResetRpcState();
175+}
176+ 
177+TEST_F(TestProcessMemPidDecision, PassiveReturnNoBroadcastWhenFreeAboveThreshold)
178+{
179+ MockNodeFree(60);
180+ ubse::mem::controller::MockSetDebtInfos(MakeLentDebts({"NODE1", "NODE2"}));
181+ 
182+ ProcessMemPidDecision::GetInstance().OnDecisionTimer();
183+ 
184+ EXPECT_TRUE(ubse::com::MockGetRpcSendRecords().empty());
185+}
186+ 
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+ 
220+TEST_F(TestProcessMemPidDecision, PassiveReturnStopsWhenFreeRecovered)
221+{
222+ MockNodeFree(10);
223+ ubse::mem::controller::MockSetDebtInfos(MakeLentDebts({"NODE1", "NODE2"}));
224+ 
225+ auto& decision = ProcessMemPidDecision::GetInstance();
226+ decision.OnDecisionTimer();
227+ EXPECT_EQ(ubse::com::MockGetRpcSendRecords().size(), 2u);
228+ 
229+ MockNodeFree(60);
230+ decision.OnDecisionTimer();
231+ EXPECT_EQ(ubse::com::MockGetRpcSendRecords().size(), 2u);
232+}
233+ 
234+TEST_F(TestProcessMemPidDecision, BorrowerDirectReturn)
235+{
236+ MockNodeFree(60);
237+ ubse::mem::controller::MockSetDebtInfos({MakeDebt("debt-x", "NODE0", 5, 1001)});
238+ AddManagedPid(1001, 10, 0.5, 4, MakeBorrow(1, "debt-x"));
239+ 
240+ std::vector<std::string> freedDebts;
241+ ProcessMemPidBridge::rmrsFreeWithMigrate = [&freedDebts](const std::string& name) {
242+ freedDebts.push_back(name);
243+ return 0;
244+ };
245+ 
246+ ReturnRequestItem item{"debt-x", 1 * GB};
247+ EXPECT_EQ(ProcessMemPidDecision::GetInstance().HandleReturnRequest({item}), UBSE_OK);
248+ 
249+ ASSERT_EQ(freedDebts.size(), 1u);
250+ EXPECT_EQ(freedDebts[0], "debt-x");
251+ 
252+ auto snapshot = ProcessMemPidInfoManager::GetInstance().GetManagedPidCacheSnapshot();
253+ auto it = snapshot.find(1001);
254+ ASSERT_NE(it, snapshot.end());
255+ EXPECT_EQ(it->second.borrow.currentRemote, 0u);
256+ EXPECT_TRUE(it->second.borrow.slots.empty());
257+ EXPECT_EQ(it->second.processStatus, ProcessStatus::IDLE);
258+}
259+ 
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+ 
421+TEST_F(TestProcessMemPidDecision, TimeoutReturnEnqueuedAndSlotRemoved)
422+{
423+ const pid_t pid = 1001;
424+ const std::string debtId = "debt-timeout";
425+ auto& decision = ProcessMemPidDecision::GetInstance();
426+ auto& infoMgr = ProcessMemPidInfoManager::GetInstance();
427+ decision.timeoutPer128MbMs_ = 1000;
428+ 
429+ BorrowState borrow;
430+ BorrowSlot slot;
431+ slot.debtId = debtId;
432+ slot.capacity = 1 * GB;
433+ slot.migratedBytes = 1 * GB;
434+ slot.remoteNumaId = 3;
435+ slot.status = BorrowSlotStatus::BORROWING;
436+ borrow.slots.push_back(slot);
437+ AddManagedPid(pid, 10, 0.5, 4, borrow);
438+ 
439+ auto snapshot = infoMgr.GetManagedPidCacheSnapshot();
440+ auto stale = snapshot.at(pid).borrow;
441+ stale.slots[0].borrowTime = std::chrono::steady_clock::now() - std::chrono::seconds(60);
442+ infoMgr.UpdateManagedPidBorrowState(pid, stale, ProcessStatus::BORROWING);
443+ 
444+ ubse::task_executor::MockSetExecutorAsync(true);
445+ ubse::mem::controller::MockBlockNumaDelete();
446+ decision.CheckTimeouts(2);
447+ 
448+ ASSERT_TRUE(ubse::mem::controller::MockWaitNumaDeleteEntered(5000));
449+ snapshot = infoMgr.GetManagedPidCacheSnapshot();
450+ EXPECT_TRUE(snapshot.at(pid).borrow.slots.empty());
451+ EXPECT_EQ(snapshot.at(pid).borrow.currentRemote, 0u);
452+ EXPECT_EQ(ubse::mem::controller::MockGetNumaDeleteCallCount(), 1u);
453+ EXPECT_EQ(ubse::mem::controller::MockGetLastNumaDeleteName(), debtId);
454+ 
455+ ubse::mem::controller::MockReleaseNumaDelete();
456+ ubse::task_executor::MockWaitExecutorIdle();
457+ EXPECT_EQ(ubse::mem::controller::MockGetNumaDeleteCallCount(), 1u);
458+ 
459+ ubse::task_executor::MockSetExecutorAsync(false);
460+}
461+ 
462+TEST_F(TestProcessMemPidDecision, PidExitedFreesAllMatchedDebts)
463+{
464+ ubse::mem::controller::MockSetDebtInfos({MakeDebt("debt-a", "NODE1", 5, 1001), MakeDebt("debt-b", "NODE2", 6, 1001),
465+ MakeDebt("debt-c", "NODE1", 5, 1002)});
466+ 
467+ std::vector<std::string> freedDebts;
468+ ProcessMemPidBridge::rmrsFreeWithMigrate = [&freedDebts](const std::string& name) {
469+ freedDebts.push_back(name);
470+ return 0;
471+ };
472+ 
473+ EXPECT_EQ(ProcessMemPidDecision::GetInstance().HandlePidExited(1001), UBSE_OK);
474+ 
475+ std::set<std::string> freedSet(freedDebts.begin(), freedDebts.end());
476+ EXPECT_EQ(freedSet, std::set<std::string>({"debt-a", "debt-b"}));
477+}
478+ 
479+TEST_F(TestProcessMemPidDecision, PidExitedNoDebtNoFree)
480+{
481+ ubse::mem::controller::MockSetDebtInfos({MakeDebt("debt-c", "NODE1", 5, 1002)});
482+ 
483+ std::vector<std::string> freedDebts;
484+ ProcessMemPidBridge::rmrsFreeWithMigrate = [&freedDebts](const std::string& name) {
485+ freedDebts.push_back(name);
486+ return 0;
487+ };
488+ 
489+ EXPECT_EQ(ProcessMemPidDecision::GetInstance().HandlePidExited(9999), UBSE_OK);
490+ EXPECT_TRUE(freedDebts.empty());
491+}
492+ 
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+ 
513+TEST_F(TestProcessMemPidDecision, PidExitedUbsedDeleteThenRmrsReturn)
514+{
515+ ubse::mem::controller::MockSetDebtInfos({MakeDebt("debt-a", "NODE1", 5, 1001)});
516+ 
517+ std::vector<std::string> freedDebts;
518+ ProcessMemPidBridge::rmrsFreeWithMigrate = [&freedDebts](const std::string& name) {
519+ freedDebts.push_back(name);
520+ return 0;
521+ };
522+ 
523+ EXPECT_EQ(ProcessMemPidDecision::GetInstance().HandlePidExited(1001), UBSE_OK);
524+ 
525+ EXPECT_GE(ubse::mem::controller::MockGetNumaDeleteCallCount(), 1u);
526+ ASSERT_EQ(freedDebts.size(), 1u);
527+ EXPECT_EQ(freedDebts[0], "debt-a");
528+}
529+ 
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+ 
566+TEST_F(TestProcessMemPidDecision, PidReturnEnqueuedAsync)
567+{
568+ ubse::mem::controller::MockSetDebtInfos(
569+ {MakeDebt("debt-a", "NODE1", 5, 1001), MakeDebt("debt-b", "NODE2", 6, 1001)});
570+ BorrowState borrow = MakeBorrow(1, "debt-a");
571+ borrow.slots.push_back(MakeBorrow(2, "debt-b").slots[0]);
572+ borrow.currentRemote = 3 * GB;
573+ AddManagedPid(1001, 10, 0.5, 4, borrow);
574+ 
575+ std::mutex gateMutex;
576+ std::condition_variable gateCv;
577+ bool gateOpen = false;
578+ std::atomic<bool> freeStarted{false};
579+ std::vector<std::string> freedDebts;
580+ ProcessMemPidBridge::rmrsFreeWithMigrate = [&](const std::string& name) {
581+ freedDebts.push_back(name);
582+ freeStarted = true;
583+ std::unique_lock<std::mutex> lock(gateMutex);
584+ gateCv.wait(lock, [&] { return gateOpen; });
585+ return 0;
586+ };
587+ 
588+ ubse::task_executor::MockSetExecutorAsync(true);
589+ EXPECT_EQ(ProcessMemPidDecision::GetInstance().HandlePidExited(1001), UBSE_OK);
590+ 
591+ auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(5);
592+ while (!freeStarted && std::chrono::steady_clock::now() < deadline) {
593+ std::this_thread::sleep_for(std::chrono::milliseconds(1));
594+ }
595+ ASSERT_TRUE(freeStarted);
596+ EXPECT_EQ(freedDebts.size(), 1u);
597+ 
598+ {
599+ std::lock_guard<std::mutex> lock(gateMutex);
600+ gateOpen = true;
601+ }
602+ gateCv.notify_all();
603+ ubse::task_executor::MockWaitExecutorIdle();
604+ 
605+ std::set<std::string> freedSet(freedDebts.begin(), freedDebts.end());
606+ EXPECT_EQ(freedSet, std::set<std::string>({"debt-a", "debt-b"}));
607+ EXPECT_EQ(ubse::mem::controller::MockGetNumaDeleteCallCount(), 2u);
608+ 
609+ ubse::task_executor::MockSetExecutorAsync(false);
610+}
611+ 
612+TEST_F(TestProcessMemPidDecision, OomDetectorNormalPoll)
613+{
614+ ProcessMemPidDecision::memAvailableReader = []() {
615+ return 200 * KB_PER_GB;
616+ };
617+ 
618+ auto& decision = ProcessMemPidDecision::GetInstance();
619+ EXPECT_EQ(decision.OomPollOnce(), 1000u);
620+ EXPECT_FALSE(decision.oomFastPoll_);
621+ EXPECT_EQ(decision.oomFastWindowCount_, 0u);
622+}
623+ 
624+TEST_F(TestProcessMemPidDecision, OomDetectorFastPoll)
625+{
626+ ProcessMemPidDecision::memAvailableReader = []() {
627+ return 100 * KB_PER_GB;
628+ };
629+ MockNodeFree(10);
630+ 
631+ auto& decision = ProcessMemPidDecision::GetInstance();
632+ EXPECT_EQ(decision.OomPollOnce(), 200u);
633+ EXPECT_TRUE(decision.oomFastPoll_);
634+ EXPECT_EQ(decision.oomFastWindowCount_, 1u);
635+ EXPECT_EQ(decision.oomFastWindowMin_, 10 * GB);
636+}
637+ 
638+TEST_F(TestProcessMemPidDecision, OomEmergencyBorrow)
639+{
640+ ProcessMemPidDecision::memAvailableReader = []() {
641+ return 3 * KB_PER_GB;
642+ };
643+ AddManagedPid(1001, 10, 0.5, 4);
644+ 
645+ int migrateCalls = 0;
646+ ProcessMemPidBridge::rmrsMigrateOut = [&migrateCalls](const std::vector<mempooling::smap::MigrateOutPayload>&,
647+ int) {
648+ ++migrateCalls;
649+ return 0;
650+ };
651+ 
652+ ProcessMemPidDecision::GetInstance().OomPollOnce();
653+ 
654+ EXPECT_EQ(migrateCalls, 1);
655+ auto snapshot = ProcessMemPidInfoManager::GetInstance().GetManagedPidCacheSnapshot();
656+ auto it = snapshot.find(1001);
657+ ASSERT_NE(it, snapshot.end());
658+ EXPECT_EQ(it->second.borrow.currentRemote, 2 * GB);
659+ EXPECT_EQ(it->second.borrow.slots.size(), 1u);
660+}
661+ 
662+TEST_F(TestProcessMemPidDecision, OomEmergencyAllowsExistingDebt)
663+{
664+ ProcessMemPidDecision::memAvailableReader = []() {
665+ return 3 * KB_PER_GB;
666+ };
667+ AddManagedPid(1001, 10, 0.5, 4, MakeBorrow(1, "debt-x"));
668+ 
669+ int migrateCalls = 0;
670+ ProcessMemPidBridge::rmrsMigrateOut = [&migrateCalls](const std::vector<mempooling::smap::MigrateOutPayload>&,
671+ int) {
672+ ++migrateCalls;
673+ return 0;
674+ };
675+ 
676+ ProcessMemPidDecision::GetInstance().OomPollOnce();
677+ 
678+ EXPECT_EQ(migrateCalls, 1);
679+ auto snapshot = ProcessMemPidInfoManager::GetInstance().GetManagedPidCacheSnapshot();
680+ auto it = snapshot.find(1001);
681+ ASSERT_NE(it, snapshot.end());
682+ EXPECT_EQ(it->second.borrow.currentRemote, 2 * GB);
683+ EXPECT_EQ(it->second.borrow.slots.size(), 2u);
684+}
685+ 
686+TEST_F(TestProcessMemPidDecision, OomLenderEmergencyNotify)
687+{
688+ ProcessMemPidDecision::memAvailableReader = []() {
689+ return 3 * KB_PER_GB;
690+ };
691+ ubse::mem::controller::MockSetDebtInfos(MakeLentDebts({"NODE1", "NODE2"}));
692+ 
693+ ProcessMemPidDecision::GetInstance().OomPollOnce();
694+ 
695+ EXPECT_EQ(ubse::com::MockGetRpcSendRecords().size(), 2u);
696+}
697+ 
698+TEST_F(TestProcessMemPidDecision, OomActiveReturnTrigger)
699+{
700+ ProcessMemPidDecision::memAvailableReader = []() {
701+ return 100 * KB_PER_GB;
702+ };
703+ MockNodeFree(60);
704+ AddManagedPid(1001, 10, 0.5, 4, MakeBorrow(2, "debt-a"));
705+ AddManagedPid(1002, 10, 0.5, 4, MakeBorrow(1, "debt-b"));
706+ 
707+ auto& decision = ProcessMemPidDecision::GetInstance();
708+ for (int i = 0; i < 5; ++i) {
709+ EXPECT_EQ(decision.OomPollOnce(), 200u);
710+ }
711+ EXPECT_EQ(decision.oomFastWindowCount_, 5u);
712+ 
713+ decision.OomPollOnce();
714+ 
715+ EXPECT_EQ(ubse::mem::controller::MockGetNumaDeleteCallCount(), 2);
716+ EXPECT_EQ(ubse::mem::controller::MockGetLastNumaDeleteName(), "debt-b");
717+ EXPECT_EQ(decision.oomFastWindowCount_, 0u);
718+ 
719+ auto snapshot = ProcessMemPidInfoManager::GetInstance().GetManagedPidCacheSnapshot();
720+ for (pid_t pid : {1001, 1002}) {
721+ auto it = snapshot.find(pid);
722+ ASSERT_NE(it, snapshot.end());
723+ EXPECT_EQ(it->second.borrow.currentRemote, 0u);
724+ EXPECT_TRUE(it->second.borrow.slots.empty());
725+ EXPECT_EQ(it->second.processStatus, ProcessStatus::IDLE);
726+ }
727+}
728+ 
729+TEST_F(TestProcessMemPidDecision, OomActiveCounterReset)
730+{
731+ ProcessMemPidDecision::memAvailableReader = []() {
732+ return 100 * KB_PER_GB;
733+ };
734+ MockNodeFree(60);
735+ AddManagedPid(1001, 10, 0.5, 4, MakeBorrow(1, "debt-x"));
736+ 
737+ auto& decision = ProcessMemPidDecision::GetInstance();
738+ for (int i = 0; i < 3; ++i) {
739+ decision.OomPollOnce();
740+ }
741+ MockNodeFree(30);
742+ decision.OomPollOnce();
743+ EXPECT_EQ(decision.oomFastWindowMin_, 30 * GB);
744+ 
745+ for (int i = 0; i < 2; ++i) {
746+ decision.OomPollOnce();
747+ }
748+ EXPECT_EQ(decision.oomFastWindowCount_, 0u);
749+ EXPECT_EQ(ubse::mem::controller::MockGetNumaDeleteCallCount(), 0);
750+}
751+ 
752+TEST_F(TestProcessMemPidDecision, OomActiveReturnSkipOversizeDebt)
753+{
754+ ProcessMemPidDecision::memAvailableReader = []() {
755+ return 100 * KB_PER_GB;
756+ };
757+ MockNodeFree(55);
758+ AddManagedPid(1001, 10, 0.5, 4, MakeBorrow(8, "debt-big"));
759+ AddManagedPid(1002, 10, 0.5, 4, MakeBorrow(3, "debt-ok"));
760+ 
761+ auto& decision = ProcessMemPidDecision::GetInstance();
762+ for (int i = 0; i < 6; ++i) {
763+ decision.OomPollOnce();
764+ }
765+ 
766+ EXPECT_EQ(ubse::mem::controller::MockGetNumaDeleteCallCount(), 1);
767+ EXPECT_EQ(ubse::mem::controller::MockGetLastNumaDeleteName(), "debt-ok");
768+ 
769+ auto snapshot = ProcessMemPidInfoManager::GetInstance().GetManagedPidCacheSnapshot();
770+ auto big = snapshot.find(1001);
771+ ASSERT_NE(big, snapshot.end());
772+ EXPECT_EQ(big->second.borrow.currentRemote, 8 * GB);
773+ auto ok = snapshot.find(1002);
774+ ASSERT_NE(ok, snapshot.end());
775+ EXPECT_EQ(ok->second.borrow.currentRemote, 0u);
776+}
777+ 
778+TEST_F(TestProcessMemPidDecision, OomActiveReturnDeleteFailRetriesUntilSuccess)
779+{
780+ ProcessMemPidDecision::memAvailableReader = []() {
781+ return 100 * KB_PER_GB;
782+ };
783+ MockNodeFree(60);
784+ AddManagedPid(1001, 10, 0.5, 4, MakeBorrow(2, "debt-a"));
785+ ubse::mem::controller::MockSetNumaDeleteErrorOnce(UBSE_ERROR);
786+ 
787+ auto& decision = ProcessMemPidDecision::GetInstance();
788+ for (int i = 0; i < 6; ++i) {
789+ decision.OomPollOnce();
790+ }
791+ 
792+ EXPECT_EQ(ubse::mem::controller::MockGetNumaDeleteCallCount(), 2);
793+ auto snapshot = ProcessMemPidInfoManager::GetInstance().GetManagedPidCacheSnapshot();
794+ auto it = snapshot.find(1001);
795+ ASSERT_NE(it, snapshot.end());
796+ EXPECT_EQ(it->second.borrow.currentRemote, 0u);
797+ EXPECT_TRUE(it->second.borrow.slots.empty());
798+ EXPECT_EQ(it->second.processStatus, ProcessStatus::IDLE);
799+ 
800+ for (int i = 0; i < 6; ++i) {
801+ decision.OomPollOnce();
802+ }
803+ EXPECT_EQ(ubse::mem::controller::MockGetNumaDeleteCallCount(), 2);
804+}
805+ 
806+TEST_F(TestProcessMemPidDecision, OomActiveReturnNotExistCleansLedger)
807+{
808+ ProcessMemPidDecision::memAvailableReader = []() {
809+ return 100 * KB_PER_GB;
810+ };
811+ MockNodeFree(60);
812+ AddManagedPid(1001, 10, 0.5, 4, MakeBorrow(2, "debt-a"));
813+ ubse::mem::controller::MockSetNumaDeleteError(UBSE_ERR_NOT_EXIST);
814+ 
815+ auto& decision = ProcessMemPidDecision::GetInstance();
816+ for (int i = 0; i < 6; ++i) {
817+ decision.OomPollOnce();
818+ }
819+ EXPECT_EQ(ubse::mem::controller::MockGetNumaDeleteCallCount(), 1);
820+ 
821+ auto snapshot = ProcessMemPidInfoManager::GetInstance().GetManagedPidCacheSnapshot();
822+ auto it = snapshot.find(1001);
823+ ASSERT_NE(it, snapshot.end());
824+ EXPECT_EQ(it->second.borrow.currentRemote, 0u);
825+ EXPECT_TRUE(it->second.borrow.slots.empty());
826+ EXPECT_EQ(it->second.processStatus, ProcessStatus::IDLE);
827+}
828+ 
829+TEST_F(TestProcessMemPidDecision, OomRearrangeMigratedBigFirst)
830+{
831+ const pid_t pid = 15980;
832+ BorrowState borrow;
833+ BorrowSlot big;
834+ big.debtId = "debt-big";
835+ big.capacity = 8 * GB;
836+ big.migratedBytes = 1 * GB;
837+ big.remoteNumaId = 5;
838+ big.status = BorrowSlotStatus::COMPLETED;
839+ BorrowSlot mid;
840+ mid.debtId = "debt-mid";
841+ mid.capacity = 4 * GB;
842+ mid.migratedBytes = 4 * GB;
843+ mid.remoteNumaId = 5;
844+ mid.status = BorrowSlotStatus::COMPLETED;
845+ BorrowSlot sml;
846+ sml.debtId = "debt-sml";
847+ sml.capacity = 2 * GB;
848+ sml.migratedBytes = 2 * GB;
849+ sml.remoteNumaId = 5;
850+ sml.status = BorrowSlotStatus::COMPLETED;
851+ BorrowSlot numa9;
852+ numa9.debtId = "debt-n9";
853+ numa9.capacity = 5 * GB;
854+ numa9.migratedBytes = 3 * GB;
855+ numa9.remoteNumaId = 9;
856+ numa9.status = BorrowSlotStatus::COMPLETED;
857+ BorrowSlot inFlight;
858+ inFlight.debtId = "debt-inflight";
859+ inFlight.capacity = 3 * GB;
860+ inFlight.migratedBytes = 2 * GB;
861+ inFlight.remoteNumaId = 5;
862+ inFlight.status = BorrowSlotStatus::BORROWING;
863+ BorrowSlot returning;
864+ returning.debtId = "debt-ret";
865+ returning.capacity = 6 * GB;
866+ returning.migratedBytes = 6 * GB;
867+ returning.remoteNumaId = 5;
868+ returning.status = BorrowSlotStatus::COMPLETED;
869+ returning.returnStatus = ReturnStatus::RETURNING;
870+ borrow.slots = {big, mid, sml, numa9, inFlight, returning};
871+ borrow.currentRemote = 16 * GB;
872+ AddManagedPid(pid, 32, 0.5, 16, borrow);
873+ 
874+ ProcessMemPidDecision::GetInstance().OomRearrangeMigrated();
875+ 
876+ auto snapshot = ProcessMemPidInfoManager::GetInstance().GetManagedPidCacheSnapshot();
877+ auto it = snapshot.find(pid);
878+ ASSERT_NE(it, snapshot.end());
879+ const auto& slots = it->second.borrow.slots;
880+ ASSERT_EQ(slots.size(), 6u);
881+ auto mig = [&slots](const std::string& debtId) -> uint64_t {
882+ for (const auto& s : slots) {
883+ if (s.debtId == debtId) {
884+ return s.migratedBytes;
885+ }
886+ }
887+ return ~0ULL;
888+ };
889+ EXPECT_EQ(mig("debt-big"), 7 * GB);
890+ EXPECT_EQ(mig("debt-mid"), 0u);
891+ EXPECT_EQ(mig("debt-sml"), 0u);
892+ EXPECT_EQ(mig("debt-n9"), 3 * GB);
893+ EXPECT_EQ(mig("debt-inflight"), 2 * GB);
894+ EXPECT_EQ(mig("debt-ret"), 6 * GB);
895+ EXPECT_EQ(it->second.borrow.currentRemote, 16 * GB);
896+ EXPECT_EQ(it->second.processStatus, ProcessStatus::BORROWED);
897+}
898+ 
899+TEST_F(TestProcessMemPidDecision, OomActiveReturnUsesMigratedBytes)
900+{
901+ ProcessMemPidDecision::memAvailableReader = []() {
902+ return 100 * KB_PER_GB;
903+ };
904+ MockNodeFree(55);
905+ const pid_t pid = 1003;
906+ BorrowState borrow;
907+ BorrowSlot big;
908+ big.debtId = "debt-big";
909+ big.capacity = 8 * GB;
910+ big.migratedBytes = 8 * GB;
911+ big.remoteNumaId = 5;
912+ big.status = BorrowSlotStatus::COMPLETED;
913+ BorrowSlot mid;
914+ mid.debtId = "debt-mid";
915+ mid.capacity = 4 * GB;
916+ mid.migratedBytes = 2 * GB;
917+ mid.remoteNumaId = 5;
918+ mid.status = BorrowSlotStatus::COMPLETED;
919+ BorrowSlot empty;
920+ empty.debtId = "debt-empty";
921+ empty.capacity = 2 * GB;
922+ empty.migratedBytes = 0;
923+ empty.remoteNumaId = 5;
924+ empty.status = BorrowSlotStatus::COMPLETED;
925+ borrow.slots = {big, mid, empty};
926+ borrow.currentRemote = 10 * GB;
927+ AddManagedPid(pid, 32, 0.5, 16, borrow);
928+ 
929+ auto& decision = ProcessMemPidDecision::GetInstance();
930+ for (int i = 0; i < 6; ++i) {
931+ decision.OomPollOnce();
932+ }
933+ 
934+ EXPECT_EQ(ubse::mem::controller::MockGetNumaDeleteCallCount(), 2);
935+ EXPECT_EQ(ubse::mem::controller::MockGetLastNumaDeleteName(), "debt-empty");
936+ auto snapshot = ProcessMemPidInfoManager::GetInstance().GetManagedPidCacheSnapshot();
937+ auto it = snapshot.find(pid);
938+ ASSERT_NE(it, snapshot.end());
939+ EXPECT_EQ(it->second.borrow.currentRemote, 8 * GB);
940+ ASSERT_EQ(it->second.borrow.slots.size(), 1u);
941+ EXPECT_EQ(it->second.borrow.slots[0].debtId, "debt-big");
942+ EXPECT_EQ(it->second.borrow.slots[0].migratedBytes, 8 * GB);
943+}
944+ 
945+TEST_F(TestProcessMemPidDecision, UpdateManagedPidSlotReturned)
946+{
947+ AddManagedPid(1001, 10, 0.5, 4, MakeBorrow(1, "debt-x"));
948+ auto& mgr = ProcessMemPidInfoManager::GetInstance();
949+ 
950+ EXPECT_EQ(mgr.UpdateManagedPidSlotReturned(1001, "debt-x", 1 * GB), UBSE_OK);
951+ auto snapshot = mgr.GetManagedPidCacheSnapshot();
952+ auto it = snapshot.find(1001);
953+ ASSERT_NE(it, snapshot.end());
954+ EXPECT_EQ(it->second.borrow.currentRemote, 0u);
955+ EXPECT_TRUE(it->second.borrow.slots.empty());
956+ EXPECT_EQ(it->second.processStatus, ProcessStatus::IDLE);
957+ 
958+ EXPECT_EQ(mgr.UpdateManagedPidSlotReturned(9999, "x", 1), UBSE_ERR_NOT_EXIST);
959+ EXPECT_EQ(mgr.UpdateManagedPidSlotReturned(1001, "unknown-debt", 1), UBSE_ERR_NOT_EXIST);
960+}
961+ 
962+ubse::mem::controller::UbseNumaMemoryImportDebtInfo MakeImportDebt(const std::string& name, uint64_t size,
963+ int64_t remoteNuma, int32_t pid, int64_t startTime)
964+{
965+ ubse::mem::controller::UbseNumaMemoryImportDebtInfo debt;
966+ debt.name = name;
967+ debt.size = size;
968+ debt.remoteNumaId = remoteNuma;
969+ memset(debt.usrInfo, 0, sizeof(debt.usrInfo));
970+ ProcessMemUsrInfo usr{};
971+ usr.pluginId = UsrInfoPluginType::PROCESS_MEM;
972+ usr.pid = pid;
973+ usr.startTime = startTime;
974+ usr.srcNuma = 1;
975+ memcpy(debt.usrInfo, &usr, sizeof(usr));
976+ return debt;
977+}
978+ 
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+ 
1013+TEST_F(TestProcessMemPidDecision, RecoverBorrowOrphanReleasedWhenPidMissing)
1014+{
1015+ ubse::mem::controller::MockSetImportDebtInfos({MakeImportDebt("debt-orphan", 1 * GB, 5, 1001, 123456)});
1016+ 
1017+ ProcessMemPidDecision::GetInstance().RecoverBorrowFromObmm();
1018+ 
1019+ EXPECT_EQ(ubse::mem::controller::MockGetLastNumaDeleteName(), "debt-orphan");
1020+}
1021+ 
1022+TEST_F(TestProcessMemPidDecision, RecoverBorrowOrphanRetriedUntilSuccess)
1023+{
1024+ ProcessMemPidDecision::GetInstance().returnRetryIntervalMs_ = 1;
1025+ ubse::mem::controller::MockSetNumaDeleteErrorOnce(UBSE_ERR_TIMEOUT);
1026+ ubse::mem::controller::MockSetImportDebtInfos({MakeImportDebt("debt-retry", 1 * GB, 5, 1003, 123456)});
1027+ 
1028+ ProcessMemPidDecision::GetInstance().RecoverBorrowFromObmm();
1029+ 
1030+ EXPECT_GE(ubse::mem::controller::MockGetNumaDeleteCallCount(), 2u);
1031+ EXPECT_EQ(ubse::mem::controller::MockGetLastNumaDeleteName(), "debt-retry");
1032+}
1033+ 
1034+TEST_F(TestProcessMemPidDecision, RecoverBorrowOrphanNotExistStopsRetry)
1035+{
1036+ ProcessMemPidDecision::GetInstance().returnRetryIntervalMs_ = 1;
1037+ ubse::mem::controller::MockSetNumaDeleteErrorOnce(UBSE_ERR_NOT_EXIST);
1038+ ubse::mem::controller::MockSetImportDebtInfos({MakeImportDebt("debt-gone", 1 * GB, 5, 1004, 123456)});
1039+ 
1040+ ProcessMemPidDecision::GetInstance().RecoverBorrowFromObmm();
1041+ 
1042+ EXPECT_EQ(ubse::mem::controller::MockGetNumaDeleteCallCount(), 1u);
1043+ EXPECT_EQ(ubse::mem::controller::MockGetLastNumaDeleteName(), "debt-gone");
1044+}
1045+ 
1046+TEST_F(TestProcessMemPidDecision, RecoverBorrowOrphanReleasedOnStartTimeMismatch)
1047+{
1048+ pid_t pid = getpid();
1049+ AddManagedPid(pid, 10, 0.5, 2);
1050+ auto startTime = ProcessMemPidConfigManager::GetExactStartTime(pid);
1051+ ASSERT_NE(startTime, 0u);
1052+ 
1053+ ubse::mem::controller::MockSetImportDebtInfos(
1054+ {MakeImportDebt("debt-stale", 1 * GB, 5, pid, static_cast<int64_t>(startTime) + 1)});
1055+ 
1056+ ProcessMemPidDecision::GetInstance().RecoverBorrowFromObmm();
1057+ 
1058+ EXPECT_EQ(ubse::mem::controller::MockGetLastNumaDeleteName(), "debt-stale");
1059+ auto snapshot = ProcessMemPidInfoManager::GetInstance().GetManagedPidCacheSnapshot();
1060+ EXPECT_TRUE(snapshot.at(pid).borrow.slots.empty());
1061+}
1062+ 
1063+TEST_F(TestProcessMemPidDecision, RecoverBorrowSkipsNonProcessMemUsrInfo)
1064+{
1065+ ubse::mem::controller::UbseNumaMemoryImportDebtInfo other;
1066+ other.name = "debt-other";
1067+ other.size = 1 * GB;
1068+ other.remoteNumaId = 5;
1069+ memset(other.usrInfo, 0, sizeof(other.usrInfo));
1070+ 
1071+ ubse::mem::controller::UbseNumaMemoryImportDebtInfo badPid;
1072+ badPid.name = "debt-badpid";
1073+ badPid.size = 1 * GB;
1074+ badPid.remoteNumaId = 5;
1075+ memset(badPid.usrInfo, 0, sizeof(badPid.usrInfo));
1076+ ProcessMemUsrInfo usr{};
1077+ usr.pluginId = UsrInfoPluginType::PROCESS_MEM;
1078+ usr.pid = -1;
1079+ memcpy(badPid.usrInfo, &usr, sizeof(usr));
1080+ 
1081+ ubse::mem::controller::MockSetImportDebtInfos({other, badPid});
1082+ 
1083+ ProcessMemPidDecision::GetInstance().RecoverBorrowFromObmm();
1084+ 
1085+ EXPECT_TRUE(ubse::mem::controller::MockGetLastNumaDeleteName().empty());
1086+}
1087+ 
1088+TEST_F(TestProcessMemPidDecision, RecoverSmapConfigUnavailable)
1089+{
1090+ ProcessMemPidBridge::rmrsProcessConfigQuery = {};
1091+ EXPECT_EQ(ProcessMemPidDecision::GetInstance().RecoverSmapProcessConfig(), 0u);
1092+}
1093+ 
1094+TEST_F(TestProcessMemPidDecision, RecoverSmapConfigReportsDirtyStates)
1095+{
1096+ pid_t pid = getpid();
1097+ AddManagedPid(pid, 10, 0.5, 2, MakeBorrow(1, "debt-x"));
1098+ AddManagedPid(2001, 10, 0.5, 2);
1099+ ubse::mem::controller::MockSetImportDebtInfos({MakeImportDebt("debt-numa5", 1 * GB, 5, 1000, 0)});
1100+ 
1101+ std::set<int> queriedNumas;
1102+ ProcessMemPidBridge::rmrsProcessConfigQuery =
1103+ [&queriedNumas, pid](int nid, mempooling::smap::ProcessPayload* payloads, int capacity, int* realLen) {
1104+ queriedNumas.insert(nid);
1105+ if (nid != 5) {
1106+ *realLen = 0;
1107+ return 0;
1108+ }
1109+ mempooling::smap::ProcessPayload p1{};
1110+ p1.pid = pid;
1111+ p1.scanType = 1;
1112+ p1.memSize = 1 * 1024 * 1024;
1113+ mempooling::smap::ProcessPayload p2{};
1114+ p2.pid = 2002;
1115+ p2.scanType = 1;
1116+ p2.memSize = 2 * 1024 * 1024;
1117+ mempooling::smap::ProcessPayload p3{};
1118+ p3.pid = 2001;
1119+ p3.scanType = 1;
1120+ p3.memSize = 1 * 1024 * 1024;
1121+ *realLen = 3;
1122+ payloads[0] = p1;
1123+ payloads[1] = p2;
1124+ payloads[2] = p3;
1125+ return 0;
1126+ };
1127+ 
1128+ EXPECT_EQ(ProcessMemPidDecision::GetInstance().RecoverSmapProcessConfig(), 2u);
1129+ EXPECT_EQ(queriedNumas, std::set<int>({5}));
1130+}
1131+ 
1132+TEST_F(TestProcessMemPidDecision, RecoverSmapConfigQueryAllFail)
1133+{
1134+ ubse::mem::controller::MockSetImportDebtInfos({MakeImportDebt("debt-numa5", 1 * GB, 5, 1000, 0)});
1135+ ProcessMemPidBridge::rmrsProcessConfigQuery = [](int, mempooling::smap::ProcessPayload*, int, int*) {
1136+ return -1;
1137+ };
1138+ EXPECT_EQ(ProcessMemPidDecision::GetInstance().RecoverSmapProcessConfig(), 0u);
1139+}
1140+ 
1141+TEST_F(TestProcessMemPidDecision, RecoverSmapConfigMultiRemoteNumaAccumulates)
1142+{
1143+ pid_t pid = getpid();
1144+ AddManagedPid(pid, 10, 0.5, 2, MakeBorrow(1, "debt-x"));
1145+ ubse::mem::controller::MockSetImportDebtInfos(
1146+ {MakeImportDebt("debt-numa5", 1 * GB, 5, 1000, 0), MakeImportDebt("debt-numa7", 1 * GB, 7, 1000, 0)});
1147+ ProcessMemPidBridge::rmrsProcessConfigQuery = [pid](int nid, mempooling::smap::ProcessPayload* payloads, int,
1148+ int* realLen) {
1149+ if (nid != 5 && nid != 7) {
1150+ *realLen = 0;
1151+ return 0;
1152+ }
1153+ mempooling::smap::ProcessPayload p{};
1154+ p.pid = pid;
1155+ p.scanType = 1;
1156+ p.memSize = 1 * 1024 * 1024;
1157+ *realLen = 1;
1158+ payloads[0] = p;
1159+ return 0;
1160+ };
1161+ 
1162+ EXPECT_EQ(ProcessMemPidDecision::GetInstance().RecoverSmapProcessConfig(), 1u);
1163+ auto snapshot = ProcessMemPidInfoManager::GetInstance().GetManagedPidCacheSnapshot();
1164+ const auto& borrow = snapshot.at(pid).borrow;
1165+ ASSERT_EQ(borrow.slots.size(), 1u);
1166+ EXPECT_EQ(borrow.slots[0].migratedBytes, 1 * GB);
1167+ EXPECT_EQ(borrow.currentRemote, 1 * GB);
1168+ EXPECT_EQ(borrow.remoteNumaMigrated.size(), 1u);
1169+ EXPECT_EQ(borrow.remoteNumaMigrated.at(5), 1 * GB);
1170+}
1171+ 
1172+TEST_F(TestProcessMemPidDecision, RecoverSmapConfigFillsAcrossMultipleSlots)
1173+{
1174+ pid_t pid = getpid();
1175+ BorrowState borrow;
1176+ BorrowSlot s5;
1177+ s5.debtId = "debt-a";
1178+ s5.migratedBytes = 1 * GB;
1179+ s5.capacity = 1 * GB;
1180+ s5.status = BorrowSlotStatus::COMPLETED;
1181+ s5.remoteNumaId = 5;
1182+ BorrowSlot s7 = s5;
1183+ s7.debtId = "debt-b";
1184+ s7.remoteNumaId = 7;
1185+ borrow.slots = {s5, s7};
1186+ borrow.currentRemote = 2 * GB;
1187+ AddManagedPid(pid, 10, 0.5, 2, borrow);
1188+ ubse::mem::controller::MockSetImportDebtInfos(
1189+ {MakeImportDebt("debt-numa5", 1 * GB, 5, 1000, 0), MakeImportDebt("debt-numa7", 1 * GB, 7, 1000, 0)});
1190+ 
1191+ ProcessMemPidBridge::rmrsProcessConfigQuery = [pid](int nid, mempooling::smap::ProcessPayload* payloads, int,
1192+ int* realLen) {
1193+ if (nid != 5 && nid != 7) {
1194+ *realLen = 0;
1195+ return 0;
1196+ }
1197+ mempooling::smap::ProcessPayload p{};
1198+ p.pid = pid;
1199+ p.scanType = 1;
1200+ p.memSize = 1 * 1024 * 1024;
1201+ *realLen = 1;
1202+ payloads[0] = p;
1203+ return 0;
1204+ };
1205+ 
1206+ EXPECT_EQ(ProcessMemPidDecision::GetInstance().RecoverSmapProcessConfig(), 0u);
1207+ auto snapshot = ProcessMemPidInfoManager::GetInstance().GetManagedPidCacheSnapshot();
1208+ const auto& borrowAfter = snapshot.at(pid).borrow;
1209+ ASSERT_EQ(borrowAfter.slots.size(), 2u);
1210+ EXPECT_EQ(borrowAfter.slots[0].migratedBytes, 1 * GB);
1211+ EXPECT_EQ(borrowAfter.slots[1].migratedBytes, 1 * GB);
1212+ EXPECT_EQ(borrowAfter.currentRemote, 2 * GB);
1213+ EXPECT_EQ(borrowAfter.remoteNumaMigrated.at(5), 1 * GB);
1214+ EXPECT_EQ(borrowAfter.remoteNumaMigrated.at(7), 1 * GB);
1215+}
1216+ 
1217+TEST_F(TestProcessMemPidDecision, RecoverSmapConfigFillsSlotsByCapacityInOrder)
1218+{
1219+ pid_t pid = getpid();
1220+ BorrowState borrow;
1221+ BorrowSlot s1;
1222+ s1.debtId = "debt-big";
1223+ s1.migratedBytes = 2 * GB;
1224+ s1.capacity = 2 * GB;
1225+ s1.status = BorrowSlotStatus::COMPLETED;
1226+ s1.remoteNumaId = 5;
1227+ BorrowSlot s2 = s1;
1228+ s2.debtId = "debt-small";
1229+ s2.migratedBytes = 1 * GB;
1230+ s2.capacity = 1 * GB;
1231+ borrow.slots = {s1, s2};
1232+ borrow.currentRemote = 3 * GB;
1233+ AddManagedPid(pid, 10, 0.5, 2, borrow);
1234+ ubse::mem::controller::MockSetImportDebtInfos({MakeImportDebt("debt-numa5", 1 * GB, 5, 1000, 0)});
1235+ 
1236+ ProcessMemPidBridge::rmrsProcessConfigQuery = [pid](int nid, mempooling::smap::ProcessPayload* payloads, int,
1237+ int* realLen) {
1238+ if (nid != 5) {
1239+ *realLen = 0;
1240+ return 0;
1241+ }
1242+ mempooling::smap::ProcessPayload p{};
1243+ p.pid = pid;
1244+ p.scanType = 1;
1245+ p.memSize = 1536 * 1024;
1246+ *realLen = 1;
1247+ payloads[0] = p;
1248+ return 0;
1249+ };
1250+ 
1251+ EXPECT_EQ(ProcessMemPidDecision::GetInstance().RecoverSmapProcessConfig(), 0u);
1252+ auto snapshot = ProcessMemPidInfoManager::GetInstance().GetManagedPidCacheSnapshot();
1253+ const auto& borrowAfter = snapshot.at(pid).borrow;
1254+ ASSERT_EQ(borrowAfter.slots.size(), 2u);
1255+ EXPECT_EQ(borrowAfter.slots[0].migratedBytes, 1536 * 1024 * 1024u);
1256+ EXPECT_EQ(borrowAfter.slots[1].migratedBytes, 0u);
1257+ EXPECT_EQ(borrowAfter.currentRemote, 1536 * 1024 * 1024u);
1258+}
1259+ 
1260+TEST_F(TestProcessMemPidDecision, RecoverSmapConfigFillsRemainderWhenCapacityExceeded)
1261+{
1262+ pid_t pid = getpid();
1263+ BorrowState borrow;
1264+ BorrowSlot slot;
1265+ slot.debtId = "debt-2g";
1266+ slot.capacity = 2 * GB;
1267+ slot.migratedBytes = slot.capacity;
1268+ slot.status = BorrowSlotStatus::COMPLETED;
1269+ slot.remoteNumaId = 5;
1270+ borrow.slots = {slot};
1271+ borrow.currentRemote = slot.capacity;
1272+ AddManagedPid(pid, 10, 0.5, 2, borrow);
1273+ ubse::mem::controller::MockSetImportDebtInfos({MakeImportDebt("debt-numa5", 2 * GB, 5, 1000, 0)});
1274+ 
1275+ ProcessMemPidBridge::rmrsProcessConfigQuery = [pid](int nid, mempooling::smap::ProcessPayload* payloads, int,
1276+ int* realLen) {
1277+ if (nid != 5) {
1278+ *realLen = 0;
1279+ return 0;
1280+ }
1281+ mempooling::smap::ProcessPayload p{};
1282+ p.pid = pid;
1283+ p.scanType = 1;
1284+ p.memSize = 1536 * 1024;
1285+ *realLen = 1;
1286+ payloads[0] = p;
1287+ return 0;
1288+ };
1289+ 
1290+ EXPECT_EQ(ProcessMemPidDecision::GetInstance().RecoverSmapProcessConfig(), 0u);
1291+ auto snapshot = ProcessMemPidInfoManager::GetInstance().GetManagedPidCacheSnapshot();
1292+ const auto& borrowAfter = snapshot.at(pid).borrow;
1293+ ASSERT_EQ(borrowAfter.slots.size(), 1u);
1294+ EXPECT_EQ(borrowAfter.slots[0].migratedBytes, 1536 * 1024 * 1024u);
1295+ EXPECT_EQ(borrowAfter.currentRemote, 1536 * 1024 * 1024u);
1296+ EXPECT_EQ(borrowAfter.remoteNumaMigrated.at(5), 1536 * 1024 * 1024u);
1297+}
1298+ 
1299+TEST_F(TestProcessMemPidDecision, RecoverSmapConfigIgnoresHamProcesses)
1300+{
1301+ ubse::mem::controller::MockSetImportDebtInfos({MakeImportDebt("debt-numa5", 1 * GB, 5, 1000, 0)});
1302+ ProcessMemPidBridge::rmrsProcessConfigQuery = [](int nid, mempooling::smap::ProcessPayload* payloads, int,
1303+ int* realLen) {
1304+ if (nid != 5) {
1305+ *realLen = 0;
1306+ return 0;
1307+ }
1308+ mempooling::smap::ProcessPayload p{};
1309+ p.pid = 3001;
1310+ p.scanType = 0;
1311+ p.memSize = 4 * 1024 * 1024;
1312+ *realLen = 1;
1313+ payloads[0] = p;
1314+ return 0;
1315+ };
1316+ 
1317+ EXPECT_EQ(ProcessMemPidDecision::GetInstance().RecoverSmapProcessConfig(), 0u);
1318+}
1319+ 
1320+TEST_F(TestProcessMemPidDecision, MultiCycleBorrowingLedgerVisibleAndNoDuplicateBorrow)
1321+{
1322+ const pid_t pid = 15970;
1323+ constexpr uint64_t maxGb = 32;
1324+ constexpr double ratio = 0.5;
1325+ constexpr uint64_t vmRssGb = 20;
1326+ AddManagedPid(pid, maxGb, ratio, vmRssGb);
1327+ 
1328+ ProcessMemPidBridge::rmrsMigrateOut = [](const std::vector<mempooling::smap::MigrateOutPayload>&, int) {
1329+ return 0;
1330+ };
1331+ 
1332+ auto& decision = ProcessMemPidDecision::GetInstance();
1333+ auto& infoMgr = ProcessMemPidInfoManager::GetInstance();
1334+ 
1335+ ubse::task_executor::MockSetExecutorAsync(true);
1336+ ubse::mem::controller::MockBlockNumaCreate();
1337+ 
1338+ MockNodeFree(10);
1339+ std::thread round1Thread([&decision]() { decision.OnDecisionTimer(); });
1340+ round1Thread.join();
1341+ ASSERT_TRUE(ubse::mem::controller::MockWaitNumaCreateEntered(5000));
1342+ 
1343+ auto snapshot = infoMgr.GetManagedPidCacheSnapshot();
1344+ auto it = snapshot.find(pid);
1345+ ASSERT_NE(it, snapshot.end());
1346+ ASSERT_EQ(it->second.borrow.slots.size(), 1u);
1347+ EXPECT_EQ(it->second.borrow.slots[0].migratedBytes, 10 * GB);
1348+ EXPECT_FALSE(it->second.borrow.slots[0].debtId.empty());
1349+ EXPECT_EQ(it->second.borrow.slots[0].status, BorrowSlotStatus::BORROWING);
1350+ EXPECT_EQ(it->second.processStatus, ProcessStatus::BORROWING);
1351+ std::string firstDebtId = it->second.borrow.slots[0].debtId;
1352+ EXPECT_EQ(decision.GetPendingBorrowTotal(), 10 * GB);
1353+ 
1354+ std::thread round2Thread([&decision]() { decision.OnDecisionTimer(); });
1355+ round2Thread.join();
1356+ snapshot = infoMgr.GetManagedPidCacheSnapshot();
1357+ it = snapshot.find(pid);
1358+ ASSERT_NE(it, snapshot.end());
1359+ ASSERT_EQ(it->second.borrow.slots.size(), 1u);
1360+ EXPECT_EQ(it->second.borrow.slots[0].debtId, firstDebtId);
1361+ EXPECT_EQ(it->second.borrow.slots[0].status, BorrowSlotStatus::BORROWING);
1362+ EXPECT_EQ(ubse::mem::controller::MockGetLastNumaCreateName(), firstDebtId);
1363+ EXPECT_EQ(decision.GetPendingBorrowTotal(), 10 * GB);
1364+ 
1365+ ubse::mem::controller::MockReleaseNumaCreate();
1366+ ubse::task_executor::MockWaitExecutorIdle();
1367+ snapshot = infoMgr.GetManagedPidCacheSnapshot();
1368+ it = snapshot.find(pid);
1369+ ASSERT_NE(it, snapshot.end());
1370+ ASSERT_EQ(it->second.borrow.slots.size(), 1u);
1371+ EXPECT_EQ(it->second.borrow.slots[0].debtId, firstDebtId);
1372+ EXPECT_EQ(it->second.borrow.slots[0].migratedBytes, 10 * GB);
1373+ EXPECT_EQ(it->second.borrow.slots[0].capacity, 10 * GB);
1374+ EXPECT_EQ(it->second.borrow.slots[0].remoteNumaId, 3);
1375+ EXPECT_EQ(it->second.borrow.slots[0].status, BorrowSlotStatus::COMPLETED);
1376+ EXPECT_EQ(it->second.borrow.currentRemote, 10 * GB);
1377+ EXPECT_EQ(it->second.processStatus, ProcessStatus::BORROWED);
1378+ EXPECT_EQ(decision.GetPendingBorrowTotal(), 0u);
1379+ 
1380+ std::thread round3Thread([&decision]() { decision.OnDecisionTimer(); });
1381+ round3Thread.join();
1382+ snapshot = infoMgr.GetManagedPidCacheSnapshot();
1383+ it = snapshot.find(pid);
1384+ ASSERT_NE(it, snapshot.end());
1385+ ASSERT_EQ(it->second.borrow.slots.size(), 1u);
1386+ EXPECT_EQ(it->second.borrow.slots[0].debtId, firstDebtId);
1387+ EXPECT_EQ(it->second.borrow.slots[0].status, BorrowSlotStatus::COMPLETED);
1388+ EXPECT_EQ(ubse::mem::controller::MockGetLastNumaCreateName(), firstDebtId);
1389+ EXPECT_EQ(it->second.borrow.currentRemote, 10 * GB);
1390+ 
1391+ ubse::task_executor::MockSetExecutorAsync(false);
1392+}
1393+ 
1394+TEST_F(TestProcessMemPidDecision, NodeFreeChangeConsidersInFlightBorrow)
1395+{
1396+ const pid_t pid = 15970;
1397+ constexpr uint64_t maxGb = 32;
1398+ constexpr double ratio = 0.5;
1399+ AddManagedPid(pid, maxGb, ratio, 40);
1400+ 
1401+ auto& decision = ProcessMemPidDecision::GetInstance();
1402+ auto& infoMgr = ProcessMemPidInfoManager::GetInstance();
1403+ 
1404+ ubse::task_executor::MockSetExecutorAsync(true);
1405+ ubse::mem::controller::MockBlockNumaCreate();
1406+ 
1407+ MockNodeFree(40);
1408+ std::thread round1Thread([&decision]() { decision.OnDecisionTimer(); });
1409+ round1Thread.join();
1410+ ASSERT_TRUE(ubse::mem::controller::MockWaitNumaCreateEntered(5000));
1411+ auto snapshot = infoMgr.GetManagedPidCacheSnapshot();
1412+ ASSERT_EQ(snapshot.at(pid).borrow.slots.size(), 1u);
1413+ EXPECT_EQ(snapshot.at(pid).borrow.slots[0].migratedBytes, 10 * GB);
1414+ EXPECT_EQ(snapshot.at(pid).borrow.slots[0].status, BorrowSlotStatus::BORROWING);
1415+ std::string firstDebtId = snapshot.at(pid).borrow.slots[0].debtId;
1416+ EXPECT_EQ(decision.GetPendingBorrowTotal(), 10 * GB);
1417+ 
1418+ MockNodeFree(35);
1419+ std::thread round2Thread([&decision]() { decision.OnDecisionTimer(); });
1420+ round2Thread.join();
1421+ auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(5);
1422+ while (std::chrono::steady_clock::now() < deadline) {
1423+ auto snap = infoMgr.GetManagedPidCacheSnapshot();
1424+ if (snap.at(pid).borrow.slots.size() >= 2 &&
1425+ ubse::mem::controller::MockGetLastNumaCreateName() != firstDebtId) {
1426+ break;
1427+ }
1428+ std::this_thread::sleep_for(std::chrono::milliseconds(1));
1429+ }
1430+ snapshot = infoMgr.GetManagedPidCacheSnapshot();
1431+ ASSERT_EQ(snapshot.at(pid).borrow.slots.size(), 2u);
1432+ EXPECT_EQ(snapshot.at(pid).borrow.slots[0].migratedBytes, 10 * GB);
1433+ EXPECT_EQ(snapshot.at(pid).borrow.slots[0].status, BorrowSlotStatus::BORROWING);
1434+ EXPECT_EQ(snapshot.at(pid).borrow.slots[1].migratedBytes, 5 * GB);
1435+ EXPECT_EQ(snapshot.at(pid).borrow.slots[1].status, BorrowSlotStatus::BORROWING);
1436+ EXPECT_FALSE(snapshot.at(pid).borrow.slots[1].debtId.empty());
1437+ EXPECT_EQ(decision.GetPendingBorrowTotal(), 15 * GB);
1438+ 
1439+ ubse::mem::controller::MockReleaseNumaCreate();
1440+ deadline = std::chrono::steady_clock::now() + std::chrono::seconds(5);
1441+ while (std::chrono::steady_clock::now() < deadline) {
1442+ auto snap = infoMgr.GetManagedPidCacheSnapshot();
1443+ if (snap.at(pid).borrow.slots.size() >= 2 &&
1444+ snap.at(pid).borrow.slots[0].status == BorrowSlotStatus::COMPLETED &&
1445+ snap.at(pid).borrow.slots[1].status == BorrowSlotStatus::COMPLETED) {
1446+ break;
1447+ }
1448+ std::this_thread::sleep_for(std::chrono::milliseconds(1));
1449+ }
1450+ snapshot = infoMgr.GetManagedPidCacheSnapshot();
1451+ ASSERT_EQ(snapshot.at(pid).borrow.slots.size(), 2u);
1452+ EXPECT_EQ(snapshot.at(pid).borrow.slots[0].status, BorrowSlotStatus::COMPLETED);
1453+ EXPECT_EQ(snapshot.at(pid).borrow.slots[1].status, BorrowSlotStatus::COMPLETED);
1454+ EXPECT_EQ(snapshot.at(pid).borrow.currentRemote, 15 * GB);
1455+ EXPECT_EQ(decision.GetPendingBorrowTotal(), 0u);
1456+ 
1457+ MockNodeFree(60);
1458+ std::thread round3Thread([&decision]() { decision.OnDecisionTimer(); });
1459+ round3Thread.join();
1460+ ubse::task_executor::MockWaitExecutorIdle();
1461+ snapshot = infoMgr.GetManagedPidCacheSnapshot();
1462+ ASSERT_EQ(snapshot.at(pid).borrow.slots.size(), 2u);
1463+ EXPECT_EQ(snapshot.at(pid).borrow.currentRemote, 15 * GB);
1464+ EXPECT_EQ(decision.GetPendingBorrowTotal(), 0u);
1465+ 
1466+ ubse::task_executor::MockSetExecutorAsync(false);
1467+}
1468+ 
1469+TEST_F(TestProcessMemPidDecision, NextCycleBorrowsFullAmountNoReuse)
1470+{
1471+ const pid_t pid = 15970;
1472+ constexpr uint64_t maxGb = 32;
1473+ constexpr double ratio = 0.5;
1474+ AddManagedPid(pid, maxGb, ratio, 20);
1475+ 
1476+ auto& decision = ProcessMemPidDecision::GetInstance();
1477+ auto& infoMgr = ProcessMemPidInfoManager::GetInstance();
1478+ ubse::task_executor::MockSetExecutorAsync(true);
1479+ 
1480+ constexpr uint64_t gb2_5 = 2 * GB + GB / 2;
1481+ ubse::mem::controller::MockSetNumaCreateDesc(3, 7, 3 * GB);
1482+ MockNodeFreeBytes(THRESHOLD_BYTES - gb2_5);
1483+ std::thread round1Thread([&decision]() { decision.OnDecisionTimer(); });
1484+ round1Thread.join();
1485+ ubse::task_executor::MockWaitExecutorIdle();
1486+ auto snapshot = infoMgr.GetManagedPidCacheSnapshot();
1487+ ASSERT_EQ(snapshot.at(pid).borrow.slots.size(), 1u);
1488+ EXPECT_EQ(snapshot.at(pid).borrow.slots[0].capacity, 3 * GB);
1489+ EXPECT_EQ(snapshot.at(pid).borrow.slots[0].migratedBytes, gb2_5);
1490+ EXPECT_EQ(snapshot.at(pid).borrow.slots[0].remoteNumaId, 3);
1491+ EXPECT_EQ(snapshot.at(pid).borrow.slots[0].status, BorrowSlotStatus::COMPLETED);
1492+ EXPECT_EQ(snapshot.at(pid).borrow.currentRemote, gb2_5);
1493+ ASSERT_EQ(ubse::smap::MockGetMigrateCallCount(), 1u);
1494+ EXPECT_EQ(ubse::smap::MockGetMigrateTargetKb(pid, 3), gb2_5 / 1024);
1495+ std::string firstDebtId = snapshot.at(pid).borrow.slots[0].debtId;
1496+ 
1497+ ubse::mem::controller::MockSetNumaCreateDesc(9, 11, 0);
1498+ ubse::mem::controller::MockBlockNumaCreate();
1499+ MockNodeFreeBytes(THRESHOLD_BYTES - 4 * GB - GB / 2);
1500+ std::thread round2Thread([&decision]() { decision.OnDecisionTimer(); });
1501+ round2Thread.join();
1502+ ASSERT_TRUE(ubse::mem::controller::MockWaitNumaCreateEntered(5000));
1503+ 
1504+ ASSERT_EQ(ubse::smap::MockGetMigrateCallCount(), 1u);
1505+ EXPECT_EQ(ubse::smap::MockGetMigrateTargetKb(pid, 3), gb2_5 / 1024);
1506+ EXPECT_EQ(ubse::mem::controller::MockGetLastNumaCreateSize(), 4 * GB + GB / 2);
1507+ snapshot = infoMgr.GetManagedPidCacheSnapshot();
1508+ ASSERT_EQ(snapshot.at(pid).borrow.slots.size(), 2u);
1509+ EXPECT_EQ(snapshot.at(pid).borrow.slots[0].status, BorrowSlotStatus::COMPLETED);
1510+ EXPECT_EQ(snapshot.at(pid).borrow.slots[0].capacity, 3 * GB);
1511+ EXPECT_EQ(snapshot.at(pid).borrow.slots[0].migratedBytes, gb2_5);
1512+ EXPECT_EQ(snapshot.at(pid).borrow.slots[1].status, BorrowSlotStatus::BORROWING);
1513+ EXPECT_EQ(snapshot.at(pid).borrow.slots[1].migratedBytes, 4 * GB + GB / 2);
1514+ EXPECT_FALSE(snapshot.at(pid).borrow.slots[1].debtId.empty());
1515+ EXPECT_NE(snapshot.at(pid).borrow.slots[1].debtId, firstDebtId);
1516+ EXPECT_EQ(snapshot.at(pid).processStatus, ProcessStatus::BORROWING);
1517+ EXPECT_EQ(decision.GetPendingBorrowTotal(), 4 * GB + GB / 2);
1518+ 
1519+ ubse::mem::controller::MockReleaseNumaCreate();
1520+ ubse::task_executor::MockWaitExecutorIdle();
1521+ ASSERT_EQ(ubse::smap::MockGetMigrateCallCount(), 2u);
1522+ EXPECT_EQ(ubse::smap::MockGetMigrateTargetKb(pid, 3), gb2_5 / 1024);
1523+ EXPECT_EQ(ubse::smap::MockGetMigrateTargetKb(pid, 9), (4 * GB + GB / 2) / 1024);
1524+ snapshot = infoMgr.GetManagedPidCacheSnapshot();
1525+ ASSERT_EQ(snapshot.at(pid).borrow.slots.size(), 2u);
1526+ EXPECT_EQ(snapshot.at(pid).borrow.slots[0].migratedBytes, gb2_5);
1527+ EXPECT_EQ(snapshot.at(pid).borrow.slots[0].status, BorrowSlotStatus::COMPLETED);
1528+ EXPECT_EQ(snapshot.at(pid).borrow.slots[1].capacity, 4 * GB + GB / 2);
1529+ EXPECT_EQ(snapshot.at(pid).borrow.slots[1].migratedBytes, 4 * GB + GB / 2);
1530+ EXPECT_EQ(snapshot.at(pid).borrow.slots[1].remoteNumaId, 9);
1531+ 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);
1533+ EXPECT_EQ(decision.GetPendingBorrowTotal(), 0u);
1534+ EXPECT_EQ(snapshot.at(pid).processStatus, ProcessStatus::BORROWED);
1535+ 
1536+ ubse::task_executor::MockSetExecutorAsync(false);
1537+}
1538+ 
1539+TEST_F(TestProcessMemPidDecision, ReturningSlotExcludedFromMigrateTargets)
1540+{
1541+ const pid_t pid = 15970;
1542+ constexpr uint64_t maxGb = 32;
1543+ constexpr double ratio = 0.5;
1544+ constexpr uint64_t gb2_5 = 2 * GB + GB / 2;
1545+ AddManagedPid(pid, maxGb, ratio, 20);
1546+ 
1547+ BorrowState borrow;
1548+ BorrowSlot slot;
1549+ slot.debtId = "debt-returning";
1550+ slot.capacity = 3 * GB;
1551+ slot.migratedBytes = gb2_5;
1552+ slot.remoteNumaId = 3;
1553+ slot.status = BorrowSlotStatus::COMPLETED;
1554+ slot.returnStatus = ReturnStatus::RETURNING;
1555+ borrow.currentRemote = gb2_5;
1556+ borrow.slots.push_back(slot);
1557+ AddManagedPid(pid, maxGb, ratio, 20, borrow);
1558+ 
1559+ ubse::mem::controller::MockSetNumaCreateDesc(3, 7, 0);
1560+ MockNodeFreeBytes(THRESHOLD_BYTES - gb2_5);
1561+ 
1562+ ProcessMemPidDecision::GetInstance().OnDecisionTimer();
1563+ 
1564+ EXPECT_EQ(ubse::mem::controller::MockGetLastNumaCreateSize(), gb2_5);
1565+ EXPECT_EQ(ubse::smap::MockGetMigrateTargetKb(pid, 3), gb2_5 / 1024);
1566+ 
1567+ auto snapshot = ProcessMemPidInfoManager::GetInstance().GetManagedPidCacheSnapshot();
1568+ ASSERT_EQ(snapshot.at(pid).borrow.slots.size(), 2u);
1569+ EXPECT_EQ(snapshot.at(pid).borrow.slots[0].migratedBytes, gb2_5);
1570+ EXPECT_EQ(snapshot.at(pid).borrow.slots[0].returnStatus, ReturnStatus::RETURNING);
1571+ EXPECT_EQ(snapshot.at(pid).borrow.slots[1].migratedBytes, gb2_5);
1572+ EXPECT_EQ(snapshot.at(pid).borrow.slots[1].status, BorrowSlotStatus::COMPLETED);
1573+}
1574+ 
1575+TEST_F(TestProcessMemPidDecision, BorrowingSlotExcludedFromMigrateTargets)
1576+{
1577+ const pid_t pid = 15970;
1578+ constexpr uint64_t maxGb = 32;
1579+ constexpr double ratio = 0.5;
1580+ constexpr uint64_t gb2_5 = 2 * GB + GB / 2;
1581+ BorrowState inFlight;
1582+ BorrowSlot slot;
1583+ slot.debtId = "debt-inflight";
1584+ slot.migratedBytes = gb2_5;
1585+ slot.status = BorrowSlotStatus::BORROWING;
1586+ slot.borrowTime = std::chrono::steady_clock::now();
1587+ inFlight.slots.push_back(slot);
1588+ AddManagedPid(pid, maxGb, ratio, 20, inFlight);
1589+ 
1590+ auto& decision = ProcessMemPidDecision::GetInstance();
1591+ auto& infoMgr = ProcessMemPidInfoManager::GetInstance();
1592+ ubse::task_executor::MockSetExecutorAsync(true);
1593+ 
1594+ constexpr uint64_t gb2 = 2 * GB;
1595+ ubse::mem::controller::MockSetNumaCreateDesc(9, 11, 0);
1596+ MockNodeFreeBytes(THRESHOLD_BYTES - 4 * GB - GB / 2);
1597+ std::thread roundThread([&decision]() { decision.OnDecisionTimer(); });
1598+ roundThread.join();
1599+ ubse::task_executor::MockWaitExecutorIdle();
1600+ EXPECT_EQ(ubse::mem::controller::MockGetLastNumaCreateSize(), gb2);
1601+ auto snapshot = infoMgr.GetManagedPidCacheSnapshot();
1602+ ASSERT_EQ(snapshot.at(pid).borrow.slots.size(), 2u);
1603+ EXPECT_EQ(snapshot.at(pid).borrow.slots[0].capacity, 0u);
1604+ EXPECT_EQ(snapshot.at(pid).borrow.slots[0].migratedBytes, gb2_5);
1605+ EXPECT_EQ(snapshot.at(pid).borrow.slots[0].remoteNumaId, -1);
1606+ EXPECT_EQ(snapshot.at(pid).borrow.slots[0].status, BorrowSlotStatus::BORROWING);
1607+ EXPECT_EQ(snapshot.at(pid).borrow.slots[1].capacity, gb2);
1608+ EXPECT_EQ(snapshot.at(pid).borrow.slots[1].migratedBytes, gb2);
1609+ EXPECT_EQ(snapshot.at(pid).borrow.slots[1].remoteNumaId, 9);
1610+ EXPECT_EQ(snapshot.at(pid).borrow.slots[1].status, BorrowSlotStatus::COMPLETED);
1611+ EXPECT_EQ(snapshot.at(pid).borrow.currentRemote, gb2);
1612+ EXPECT_EQ(ubse::smap::MockGetMigrateTargetKb(pid, 3), 0u);
1613+ EXPECT_EQ(ubse::smap::MockGetMigrateTargetKb(pid, 9), gb2 / 1024);
1614+ auto calls = ubse::smap::MockGetMigrateCalls();
1615+ ASSERT_EQ(calls.size(), 1u);
1616+ EXPECT_EQ(calls[0].first, 9);
1617+ EXPECT_EQ(calls[0].second, gb2 / 1024);
1618+ EXPECT_EQ(decision.GetPendingBorrowTotal(), gb2_5);
1619+ EXPECT_EQ(snapshot.at(pid).processStatus, ProcessStatus::BORROWING);
1620+ 
1621+ ubse::task_executor::MockSetExecutorAsync(false);
1622+}
1623+ 
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+ 
1690+TEST_F(TestProcessMemPidDecision, BorrowingPidEligibleWithChangingNodeFree)
1691+{
1692+ const pid_t pidA = 15970;
1693+ constexpr uint64_t maxGb = 32;
1694+ constexpr double ratio = 0.5;
1695+ AddManagedPid(pidA, maxGb, ratio, 40);
1696+ 
1697+ ProcessMemPidBridge::rmrsMigrateOut = [](const std::vector<mempooling::smap::MigrateOutPayload>&, int) {
1698+ return 0;
1699+ };
1700+ 
1701+ auto& decision = ProcessMemPidDecision::GetInstance();
1702+ 
1703+ ubse::mem::controller::MockBlockNumaCreate();
1704+ MockNodeFree(40);
1705+ std::thread decisionThread([&decision]() { decision.OnDecisionTimer(); });
1706+ ASSERT_TRUE(ubse::mem::controller::MockWaitNumaCreateEntered(5000));
1707+ 
1708+ auto snapshot = ProcessMemPidInfoManager::GetInstance().GetManagedPidCacheSnapshot();
1709+ ASSERT_EQ(snapshot.at(pidA).borrow.slots.size(), 1u);
1710+ EXPECT_EQ(snapshot.at(pidA).borrow.slots[0].migratedBytes, 10 * GB);
1711+ EXPECT_EQ(snapshot.at(pidA).borrow.slots[0].status, BorrowSlotStatus::BORROWING);
1712+ EXPECT_EQ(snapshot.at(pidA).processStatus, ProcessStatus::BORROWING);
1713+ 
1714+ std::thread round2Thread([&decision]() { decision.OnDecisionTimer(); });
1715+ round2Thread.join();
1716+ snapshot = ProcessMemPidInfoManager::GetInstance().GetManagedPidCacheSnapshot();
1717+ ASSERT_EQ(snapshot.at(pidA).borrow.slots.size(), 1u);
1718+ EXPECT_EQ(snapshot.at(pidA).borrow.slots[0].status, BorrowSlotStatus::BORROWING);
1719+ EXPECT_EQ(decision.GetPendingBorrowTotal(), 10 * GB);
1720+ 
1721+ auto candidates = decision.BuildCandidates(2);
1722+ ASSERT_EQ(candidates.size(), 1u);
1723+ EXPECT_EQ(candidates[0].pid, pidA);
1724+ EXPECT_TRUE(candidates[0].hasActiveBorrow);
1725+ EXPECT_EQ(candidates[0].canMigrate, 10 * GB);
1726+ 
1727+ ubse::mem::controller::MockReleaseNumaCreate();
1728+ decisionThread.join();
1729+ snapshot = ProcessMemPidInfoManager::GetInstance().GetManagedPidCacheSnapshot();
1730+ ASSERT_EQ(snapshot.at(pidA).borrow.slots.size(), 1u);
1731+ EXPECT_EQ(snapshot.at(pidA).borrow.slots[0].status, BorrowSlotStatus::COMPLETED);
1732+ EXPECT_EQ(snapshot.at(pidA).borrow.currentRemote, 10 * GB);
1733+ 
1734+ MockNodeFree(45);
1735+ std::thread round3Thread([&decision]() { decision.OnDecisionTimer(); });
1736+ round3Thread.join();
1737+ snapshot = ProcessMemPidInfoManager::GetInstance().GetManagedPidCacheSnapshot();
1738+ ASSERT_EQ(snapshot.at(pidA).borrow.slots.size(), 2u);
1739+ EXPECT_EQ(snapshot.at(pidA).borrow.slots[1].migratedBytes, 5 * GB);
1740+ EXPECT_EQ(snapshot.at(pidA).borrow.slots[1].status, BorrowSlotStatus::COMPLETED);
1741+ EXPECT_EQ(snapshot.at(pidA).borrow.currentRemote, 15 * GB);
1742+ 
1743+ MockNodeFree(40);
1744+ std::thread round4Thread([&decision]() { decision.OnDecisionTimer(); });
1745+ round4Thread.join();
1746+ snapshot = ProcessMemPidInfoManager::GetInstance().GetManagedPidCacheSnapshot();
1747+ ASSERT_EQ(snapshot.at(pidA).borrow.slots.size(), 3u);
1748+ EXPECT_EQ(snapshot.at(pidA).borrow.slots[2].migratedBytes, 5 * GB);
1749+ EXPECT_EQ(snapshot.at(pidA).borrow.slots[2].status, BorrowSlotStatus::COMPLETED);
1750+ EXPECT_EQ(snapshot.at(pidA).borrow.currentRemote, 20 * GB);
1751+ 
1752+ std::thread round5Thread([&decision]() { decision.OnDecisionTimer(); });
1753+ round5Thread.join();
1754+ snapshot = ProcessMemPidInfoManager::GetInstance().GetManagedPidCacheSnapshot();
1755+ ASSERT_EQ(snapshot.at(pidA).borrow.slots.size(), 3u);
1756+ EXPECT_EQ(snapshot.at(pidA).borrow.currentRemote, 20 * GB);
1757+}
1758+ 
1759+TEST_F(TestProcessMemPidDecision, ShortageDeductsPendingBorrow)
1760+{
1761+ const pid_t pidA = 15970;
1762+ const pid_t pidB = 15971;
1763+ const pid_t pidC = 15972;
1764+ AddManagedPid(pidA, 32, 0.5, 40);
1765+ BorrowState borrowA;
1766+ BorrowSlot inFlight;
1767+ inFlight.debtId = "debt-a";
1768+ inFlight.migratedBytes = 15 * GB;
1769+ inFlight.capacity = 15 * GB;
1770+ inFlight.status = BorrowSlotStatus::BORROWING;
1771+ inFlight.borrowTime = std::chrono::steady_clock::now() + std::chrono::hours(1);
1772+ borrowA.slots = {inFlight};
1773+ ProcessMemPidInfoManager::GetInstance().UpdateManagedPidBorrowState(pidA, borrowA, ProcessStatus::BORROWING);
1774+ AddManagedPid(pidB, 32, 0.5, 30);
1775+ AddManagedPid(pidC, 32, 0.5, 30);
1776+ 
1777+ ProcessMemPidBridge::rmrsMigrateOut = [](const std::vector<mempooling::smap::MigrateOutPayload>&, int) {
1778+ return 0;
1779+ };
1780+ MockNodeFree(10);
1781+ 
1782+ ProcessMemPidDecision::GetInstance().OnDecisionTimer();
1783+ 
1784+ auto snapshot = ProcessMemPidInfoManager::GetInstance().GetManagedPidCacheSnapshot();
1785+ ASSERT_EQ(snapshot.at(pidB).borrow.slots.size(), 1u);
1786+ EXPECT_EQ(snapshot.at(pidB).borrow.slots[0].status, BorrowSlotStatus::COMPLETED);
1787+ ASSERT_EQ(snapshot.at(pidC).borrow.slots.size(), 1u);
1788+ EXPECT_EQ(snapshot.at(pidC).borrow.slots[0].status, BorrowSlotStatus::COMPLETED);
1789+ std::multiset<uint64_t> migrated = {snapshot.at(pidB).borrow.slots[0].migratedBytes,
1790+ snapshot.at(pidC).borrow.slots[0].migratedBytes};
1791+ EXPECT_EQ(migrated, (std::multiset<uint64_t>{15 * GB, 10 * GB}));
1792+ ASSERT_EQ(snapshot.at(pidA).borrow.slots.size(), 1u);
1793+ EXPECT_EQ(snapshot.at(pidA).borrow.slots[0].debtId, "debt-a");
1794+ EXPECT_EQ(snapshot.at(pidA).borrow.slots[0].status, BorrowSlotStatus::BORROWING);
1795+ EXPECT_EQ(ProcessMemPidDecision::GetInstance().GetPendingBorrowTotal(), 15 * GB);
1796+}
1797+} // namespace ubse::ut::process_mem
@@ -0,0 +1,48 @@
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+#ifndef TEST_PROCESS_MEM_PID_DECISION_H
14+#define TEST_PROCESS_MEM_PID_DECISION_H
15+ 
16+#include <atomic>
17+#include <chrono>
18+#include <condition_variable>
19+#include <cstdint>
20+#include <functional>
21+#include <map>
22+#include <mutex>
23+#include <string>
24+#include <thread>
25+#include <vector>
26+ 
27+#include "gtest/gtest.h"
28+ 
29+#include "mock/ubse/mock_control.h"
30+#include "mock/ubse/ubse_smap_mock.h"
31+#include "process_mem_pid_bridge.h"
32+#include "process_mem_pid_info_manager.h"
33+ 
34+#define private public
35+#include "process_mem_pid_decision.h"
36+#undef private
37+ 
38+namespace ubse::ut::process_mem {
39+ 
40+class TestProcessMemPidDecision : public testing::Test {
41+public:
42+ TestProcessMemPidDecision() = default;
43+ 
44+ void SetUp() override;
45+ void TearDown() override;
46+};
47+} // namespace ubse::ut::process_mem
48+#endif
@@ -16,6 +16,10 @@
16#include "gtest/gtest.h"16#include "gtest/gtest.h"
17#include "process_mem_pid_info_manager.h"17#include "process_mem_pid_info_manager.h"
18 18 
19+#define private public
20+#include "process_mem_pid_decision.h"
21+#undef private
22+ 
19namespace ubse::ut::process_mem {23namespace ubse::ut::process_mem {
20 24 
21class TestProcessMemPidInfoManager : public testing::Test {25class TestProcessMemPidInfoManager : public testing::Test {
@@ -19,138 +19,210 @@ void TestProcessMemPidManagerDef::SetUp() {}
19 19 
20void TestProcessMemPidManagerDef::TearDown() {}20void TestProcessMemPidManagerDef::TearDown() {}
21 21 
22-TEST_F(TestProcessMemPidManagerDef, SerializeConfigInfoWithoutSrcNuma)22+TEST_F(TestProcessMemPidManagerDef, ProcessMemNewConfigInfoDefaultValues)
23{23{
24- ProcessMemPidConfigInfo configInfo{};24+ ProcessMemNewConfigInfo config{};
25- configInfo.pid = 1234;25+ EXPECT_TRUE(config.isPid);
26- configInfo.evictThreshold = 80;26+ EXPECT_TRUE(config.identifier.empty());
27- configInfo.targetEvictThreshold = 70;27+ EXPECT_EQ(config.maxMemory, 0u);
28- configInfo.reclaimThreshold = 50;28+ EXPECT_DOUBLE_EQ(config.remoteRatio, 0.0);
29- configInfo.expectedMemoryUsage = 1024000;29+ EXPECT_EQ(config.startTime, 0u);
30- configInfo.srcNumaId = std::nullopt;30+}
31+ 
32+TEST_F(TestProcessMemPidManagerDef, SerializeProcessMemNewConfigInfoPidMode)
33+{
34+ ProcessMemNewConfigInfo config{};
35+ config.isPid = true;
36+ config.identifier = "1234";
37+ config.maxMemory = 1024000;
38+ config.remoteRatio = 0.5;
39+ config.startTime = 9999;
31 40 
32 ubse::serial::UbseSerialization serializer;41 ubse::serial::UbseSerialization serializer;
33- auto ret = configInfo.SerializeConfigInfo(serializer);42+ auto ret = config.Serialize(serializer);
34 EXPECT_EQ(ret, UBSE_OK);43 EXPECT_EQ(ret, UBSE_OK);
35 44 
36 ubse::serial::UbseDeSerialization deserializer(serializer.GetBuffer(), serializer.GetLength());45 ubse::serial::UbseDeSerialization deserializer(serializer.GetBuffer(), serializer.GetLength());
37- ProcessMemPidConfigInfo deserialized{};46+ ProcessMemNewConfigInfo deserialized{};
38- ret = deserialized.DeserializeConfigInfo(deserializer);47+ ret = deserialized.Deserialize(deserializer);
39 EXPECT_EQ(ret, UBSE_OK);48 EXPECT_EQ(ret, UBSE_OK);
40 49 
41- EXPECT_EQ(deserialized.pid, 1234);50+ EXPECT_TRUE(deserialized.isPid);
42- EXPECT_EQ(deserialized.evictThreshold, 80);51+ EXPECT_EQ(deserialized.identifier, "1234");
43- EXPECT_EQ(deserialized.targetEvictThreshold, 70);52+ EXPECT_EQ(deserialized.maxMemory, 1024000u);
44- EXPECT_EQ(deserialized.reclaimThreshold, 50);53+ EXPECT_DOUBLE_EQ(deserialized.remoteRatio, 0.5);
45- EXPECT_EQ(deserialized.expectedMemoryUsage, 1024000);54+ EXPECT_EQ(deserialized.startTime, 9999u);
46- EXPECT_FALSE(deserialized.srcNumaId.has_value());
47}55}
48 56 
49-TEST_F(TestProcessMemPidManagerDef, SerializeConfigInfoWithSrcNuma)57+TEST_F(TestProcessMemPidManagerDef, SerializeProcessMemNewConfigInfoNameMode)
50{58{
51- ProcessMemPidConfigInfo configInfo{};59+ ProcessMemNewConfigInfo config{};
52- configInfo.pid = 5678;60+ config.isPid = false;
53- configInfo.evictThreshold = 90;61+ config.identifier = "myproc";
54- configInfo.targetEvictThreshold = 60;62+ config.maxMemory = 2048000;
55- configInfo.reclaimThreshold = 40;63+ config.remoteRatio = 0.3;
56- configInfo.expectedMemoryUsage = 2048000;64+ config.startTime = 0;
57- configInfo.srcNumaId = 2;
58 65 
59 ubse::serial::UbseSerialization serializer;66 ubse::serial::UbseSerialization serializer;
60- auto ret = configInfo.SerializeConfigInfo(serializer);67+ auto ret = config.Serialize(serializer);
61 EXPECT_EQ(ret, UBSE_OK);68 EXPECT_EQ(ret, UBSE_OK);
62 69 
63 ubse::serial::UbseDeSerialization deserializer(serializer.GetBuffer(), serializer.GetLength());70 ubse::serial::UbseDeSerialization deserializer(serializer.GetBuffer(), serializer.GetLength());
64- ProcessMemPidConfigInfo deserialized{};71+ ProcessMemNewConfigInfo deserialized{};
65- ret = deserialized.DeserializeConfigInfo(deserializer);72+ ret = deserialized.Deserialize(deserializer);
66 EXPECT_EQ(ret, UBSE_OK);73 EXPECT_EQ(ret, UBSE_OK);
67 74 
68- EXPECT_EQ(deserialized.pid, 5678);75+ EXPECT_FALSE(deserialized.isPid);
69- EXPECT_EQ(deserialized.evictThreshold, 90);76+ EXPECT_EQ(deserialized.identifier, "myproc");
70- EXPECT_EQ(deserialized.targetEvictThreshold, 60);77+ EXPECT_EQ(deserialized.maxMemory, 2048000u);
71- EXPECT_EQ(deserialized.reclaimThreshold, 40);78+ EXPECT_DOUBLE_EQ(deserialized.remoteRatio, 0.3);
72- EXPECT_EQ(deserialized.expectedMemoryUsage, 2048000);
73- EXPECT_TRUE(deserialized.srcNumaId.has_value());
74- EXPECT_EQ(deserialized.srcNumaId.value(), 2u);
75}79}
76 80 
77-TEST_F(TestProcessMemPidManagerDef, SerializePidInfoWithoutSrcNuma)81+TEST_F(TestProcessMemPidManagerDef, SerializeProcessMemNewConfigInfoZeroValues)
78{82{
79- ProcessMemPidInfo pidInfo{};83+ ProcessMemNewConfigInfo config{};
80- pidInfo.ppid = 100;84+ config.isPid = true;
81- pidInfo.startTime = 9999;85+ config.identifier = "0";
82- pidInfo.configInfo.pid = 1234;86+ config.maxMemory = 0;
83- pidInfo.configInfo.evictThreshold = 80;87+ config.remoteRatio = 0.0;
84- pidInfo.configInfo.targetEvictThreshold = 70;
85- pidInfo.configInfo.reclaimThreshold = 50;
86- pidInfo.configInfo.expectedMemoryUsage = 1024000;
87- pidInfo.configInfo.srcNumaId = std::nullopt;
88 88 
89 ubse::serial::UbseSerialization serializer;89 ubse::serial::UbseSerialization serializer;
90- auto ret = pidInfo.SerializePidInfo(serializer);90+ auto ret = config.Serialize(serializer);
91 EXPECT_EQ(ret, UBSE_OK);91 EXPECT_EQ(ret, UBSE_OK);
92 92 
93 ubse::serial::UbseDeSerialization deserializer(serializer.GetBuffer(), serializer.GetLength());93 ubse::serial::UbseDeSerialization deserializer(serializer.GetBuffer(), serializer.GetLength());
94- ProcessMemPidInfo deserialized{};94+ ProcessMemNewConfigInfo deserialized{};
95- ret = deserialized.DeserializePidInfo(deserializer);95+ ret = deserialized.Deserialize(deserializer);
96 EXPECT_EQ(ret, UBSE_OK);96 EXPECT_EQ(ret, UBSE_OK);
97- 97+ EXPECT_TRUE(deserialized.isPid);
98- EXPECT_EQ(deserialized.startTime, 9999);98+ EXPECT_EQ(deserialized.identifier, "0");
99- EXPECT_EQ(deserialized.configInfo.pid, 1234);
100- EXPECT_EQ(deserialized.configInfo.evictThreshold, 80);
101- EXPECT_FALSE(deserialized.configInfo.srcNumaId.has_value());
102}99}
103 100 
104-TEST_F(TestProcessMemPidManagerDef, SerializePidInfoWithSrcNuma)101+TEST_F(TestProcessMemPidManagerDef, ProcessMemDisplayEntryDefaultValues)
105{102{
106- ProcessMemPidInfo pidInfo{};103+ ProcessMemDisplayEntry entry{};
107- pidInfo.ppid = 200;104+ EXPECT_EQ(entry.pid, 0);
108- pidInfo.startTime = 12345;105+ EXPECT_TRUE(entry.name.empty());
109- pidInfo.configInfo.pid = 5678;106+ EXPECT_EQ(entry.maxMemory, 0u);
110- pidInfo.configInfo.evictThreshold = 90;107+ EXPECT_DOUBLE_EQ(entry.remoteRatio, 0.0);
111- pidInfo.configInfo.srcNumaId = 3;108+}
109+ 
110+TEST_F(TestProcessMemPidManagerDef, SerializeProcessMemDisplayEntry)
111+{
112+ ProcessMemDisplayEntry entry{};
113+ entry.pid = 42;
114+ entry.name = "myproc";
115+ entry.maxMemory = 4096000;
116+ entry.remoteRatio = 0.75;
112 117 
113 ubse::serial::UbseSerialization serializer;118 ubse::serial::UbseSerialization serializer;
114- auto ret = pidInfo.SerializePidInfo(serializer);119+ auto ret = entry.Serialize(serializer);
115 EXPECT_EQ(ret, UBSE_OK);120 EXPECT_EQ(ret, UBSE_OK);
116 121 
117 ubse::serial::UbseDeSerialization deserializer(serializer.GetBuffer(), serializer.GetLength());122 ubse::serial::UbseDeSerialization deserializer(serializer.GetBuffer(), serializer.GetLength());
118- ProcessMemPidInfo deserialized{};123+ ProcessMemDisplayEntry deserialized{};
119- ret = deserialized.DeserializePidInfo(deserializer);124+ ret = deserialized.Deserialize(deserializer);
120 EXPECT_EQ(ret, UBSE_OK);125 EXPECT_EQ(ret, UBSE_OK);
121 126 
122- EXPECT_EQ(deserialized.startTime, 12345);127+ EXPECT_EQ(deserialized.pid, 42);
123- EXPECT_EQ(deserialized.configInfo.pid, 5678);128+ EXPECT_EQ(deserialized.name, "myproc");
124- EXPECT_TRUE(deserialized.configInfo.srcNumaId.has_value());129+ EXPECT_EQ(deserialized.maxMemory, 4096000u);
125- EXPECT_EQ(deserialized.configInfo.srcNumaId.value(), 3u);130+ EXPECT_DOUBLE_EQ(deserialized.remoteRatio, 0.75);
126}131}
127 132 
128-TEST_F(TestProcessMemPidManagerDef, ProcessMemPidInfoDefaultValues)133+TEST_F(TestProcessMemPidManagerDef, ParsePidFromIdentifierValid)
129{134{
130- ProcessMemPidInfo pidInfo{};135+ auto pid = ParsePidFromIdentifier("1234");
131- EXPECT_EQ(pidInfo.ppid, 0);136+ ASSERT_TRUE(pid.has_value());
132- EXPECT_EQ(pidInfo.startTime, 0);137+ EXPECT_EQ(pid.value(), 1234);
133- EXPECT_EQ(pidInfo.configInfo.pid, 0);
134- EXPECT_EQ(pidInfo.processStatus, ProcessStatus::IDLE);
135- EXPECT_EQ(pidInfo.memBorrowInfo.remoteNumaId, -1);
136- EXPECT_EQ(pidInfo.memBorrowInfo.exportSlotId, -1);
137- EXPECT_EQ(pidInfo.memBorrowInfo.importSocketId, -1);
138- EXPECT_TRUE(pidInfo.memBorrowInfo.debtInfos.empty());
139}138}
140 139 
141-TEST_F(TestProcessMemPidManagerDef, BorrowInfoDefaultValues)140+TEST_F(TestProcessMemPidManagerDef, ParsePidFromIdentifierInvalid)
142{141{
143- BorrowInfo borrowInfo{};142+ EXPECT_FALSE(ParsePidFromIdentifier("not_a_pid").has_value());
144- EXPECT_EQ(borrowInfo.remoteNumaId, -1);143+ EXPECT_FALSE(ParsePidFromIdentifier("").has_value());
145- EXPECT_EQ(borrowInfo.exportSlotId, -1);144+ EXPECT_FALSE(ParsePidFromIdentifier("999999999999999999999").has_value());
146- EXPECT_EQ(borrowInfo.importSocketId, -1);
147- EXPECT_TRUE(borrowInfo.debtInfos.empty());
148}145}
149 146 
150-TEST_F(TestProcessMemPidManagerDef, DebtInfoDefaultValues)147+TEST_F(TestProcessMemPidManagerDef, ParsePidFromIdentifierNonPositive)
151{148{
152- DebtInfo debtInfo{};149+ EXPECT_FALSE(ParsePidFromIdentifier("0").has_value());
153- EXPECT_EQ(debtInfo.status, BorrowStatus::COMPLETED);150+ EXPECT_FALSE(ParsePidFromIdentifier("-1").has_value());
151+}
152+ 
153+TEST_F(TestProcessMemPidManagerDef, ManagedPidEntryDefaultValues)
154+{
155+ ManagedPidEntry entry{};
156+ EXPECT_EQ(entry.pid, 0);
157+ EXPECT_EQ(entry.parentPid, 0);
158+ EXPECT_FALSE(entry.isChild);
159+ EXPECT_EQ(entry.sources, 0u);
160+ EXPECT_TRUE(entry.nameConfigName.empty());
161+ EXPECT_EQ(entry.maxMemory, 0u);
162+ EXPECT_DOUBLE_EQ(entry.remoteRatio, 0.0);
163+ EXPECT_EQ(entry.vmRss, 0u);
164+ EXPECT_EQ(entry.processStatus, ProcessStatus::IDLE);
165+ EXPECT_TRUE(entry.borrow.slots.empty());
166+}
167+ 
168+TEST_F(TestProcessMemPidManagerDef, ConfigSourceBitValues)
169+{
170+ EXPECT_EQ(static_cast<uint8_t>(ConfigSource::PID_CONFIG), 1u);
171+ EXPECT_EQ(static_cast<uint8_t>(ConfigSource::NAME_CONFIG), 2u);
172+}
173+ 
174+TEST_F(TestProcessMemPidManagerDef, BorrowSlotDefaultValues)
175+{
176+ BorrowSlot slot{};
177+ EXPECT_EQ(slot.migratedBytes, 0u);
178+ EXPECT_EQ(slot.exportSlotId, -1);
179+ EXPECT_TRUE(slot.debtId.empty());
180+ EXPECT_EQ(slot.srcNumaId, -1);
181+ EXPECT_EQ(slot.remoteNumaId, -1);
182+ EXPECT_EQ(slot.status, BorrowSlotStatus::BORROWING);
183+}
184+ 
185+TEST_F(TestProcessMemPidManagerDef, BorrowStateDefaultValues)
186+{
187+ BorrowState state{};
188+ EXPECT_EQ(state.currentRemote, 0u);
189+ EXPECT_TRUE(state.slots.empty());
190+ EXPECT_TRUE(state.remoteNumaMigrated.empty());
191+}
192+ 
193+TEST_F(TestProcessMemPidManagerDef, BorrowCandidateDefaultValues)
194+{
195+ BorrowCandidate candidate{};
196+ EXPECT_EQ(candidate.pid, 0);
197+ EXPECT_FALSE(candidate.isChild);
198+ EXPECT_EQ(candidate.actual, 0u);
199+ EXPECT_EQ(candidate.maxMemory, 0u);
200+ EXPECT_DOUBLE_EQ(candidate.remoteRatio, 0.0);
201+ EXPECT_EQ(candidate.currentRemote, 0u);
202+ EXPECT_EQ(candidate.canMigrate, 0u);
203+}
204+ 
205+TEST_F(TestProcessMemPidManagerDef, BorrowSlotStatusDistinct)
206+{
207+ EXPECT_NE(BorrowSlotStatus::BORROWING, BorrowSlotStatus::COMPLETED);
208+ EXPECT_NE(BorrowSlotStatus::COMPLETED, BorrowSlotStatus::FAILED);
209+ EXPECT_NE(BorrowSlotStatus::FAILED, BorrowSlotStatus::BORROWING);
210+}
211+ 
212+TEST_F(TestProcessMemPidManagerDef, KeyPrefixConstants)
213+{
214+ EXPECT_FALSE(PROC_MEM_PID_KEY_PREFIX.empty());
215+ EXPECT_EQ(PROC_MEM_PID_KEY_PREFIX.find("procMem_pid_"), 0u);
216+ EXPECT_FALSE(PROC_MEM_NAME_KEY_PREFIX.empty());
217+ EXPECT_EQ(PROC_MEM_NAME_KEY_PREFIX.find("procMem_name_"), 0u);
218+ EXPECT_NE(PROC_MEM_PID_KEY_PREFIX, PROC_MEM_NAME_KEY_PREFIX);
219+}
220+ 
221+TEST_F(TestProcessMemPidManagerDef, ProcessStatusEnumValues)
222+{
223+ EXPECT_NE(ProcessStatus::IDLE, ProcessStatus::BORROWING);
224+ EXPECT_NE(ProcessStatus::BORROWING, ProcessStatus::BORROWED);
225+ EXPECT_NE(ProcessStatus::IDLE, ProcessStatus::BORROWED);
154}226}
155 227 
156TEST_F(TestProcessMemPidManagerDef, ProcessMemUsrInfoStaticAssert)228TEST_F(TestProcessMemPidManagerDef, ProcessMemUsrInfoStaticAssert)
@@ -167,173 +239,4 @@ TEST_F(TestProcessMemPidManagerDef, ProcessMemUsrInfoDefaultValues)
167 EXPECT_EQ(usrInfo.startTime, 0);239 EXPECT_EQ(usrInfo.startTime, 0);
168 EXPECT_EQ(usrInfo.srcNuma, -1);240 EXPECT_EQ(usrInfo.srcNuma, -1);
169}241}
170- 
171-TEST_F(TestProcessMemPidManagerDef, SerializeDeserializeRoundTrip)
172-{
173- ProcessMemPidConfigInfo configInfo{};
174- configInfo.pid = 9999;
175- configInfo.evictThreshold = 85;
176- configInfo.targetEvictThreshold = 75;
177- configInfo.reclaimThreshold = 55;
178- configInfo.expectedMemoryUsage = 4096000;
179- configInfo.srcNumaId = 5;
180- 
181- ubse::serial::UbseSerialization serializer;
182- auto ret = configInfo.SerializeConfigInfo(serializer);
183- EXPECT_EQ(ret, UBSE_OK);
184- 
185- ubse::serial::UbseDeSerialization deserializer(serializer.GetBuffer(), serializer.GetLength());
186- ProcessMemPidConfigInfo deserialized{};
187- ret = deserialized.DeserializeConfigInfo(deserializer);
188- EXPECT_EQ(ret, UBSE_OK);
189- 
190- EXPECT_EQ(deserialized.pid, configInfo.pid);
191- EXPECT_EQ(deserialized.evictThreshold, configInfo.evictThreshold);
192- EXPECT_EQ(deserialized.targetEvictThreshold, configInfo.targetEvictThreshold);
193- EXPECT_EQ(deserialized.reclaimThreshold, configInfo.reclaimThreshold);
194- EXPECT_EQ(deserialized.expectedMemoryUsage, configInfo.expectedMemoryUsage);
195- EXPECT_EQ(deserialized.srcNumaId.value(), configInfo.srcNumaId.value());
196-}
197- 
198-// ==================== Edge case tests ====================
199- 
200-TEST_F(TestProcessMemPidManagerDef, SerializeConfigInfoZeroPid)
201-{
202- ProcessMemPidConfigInfo configInfo{};
203- configInfo.pid = 0;
204- configInfo.evictThreshold = 0;
205- configInfo.targetEvictThreshold = 0;
206- configInfo.reclaimThreshold = 0;
207- configInfo.expectedMemoryUsage = 0;
208- configInfo.srcNumaId = std::nullopt;
209- 
210- ubse::serial::UbseSerialization serializer;
211- auto ret = configInfo.SerializeConfigInfo(serializer);
212- EXPECT_EQ(ret, UBSE_OK);
213- 
214- ubse::serial::UbseDeSerialization deserializer(serializer.GetBuffer(), serializer.GetLength());
215- ProcessMemPidConfigInfo deserialized{};
216- ret = deserialized.DeserializeConfigInfo(deserializer);
217- EXPECT_EQ(ret, UBSE_OK);
218- 
219- EXPECT_EQ(deserialized.pid, 0);
220- EXPECT_EQ(deserialized.evictThreshold, 0);
221- EXPECT_EQ(deserialized.targetEvictThreshold, 0);
222- EXPECT_EQ(deserialized.reclaimThreshold, 0);
223- EXPECT_EQ(deserialized.expectedMemoryUsage, 0u);
224- EXPECT_FALSE(deserialized.srcNumaId.has_value());
225-}
226- 
227-TEST_F(TestProcessMemPidManagerDef, SerializeConfigInfoLargeValues)
228-{
229- ProcessMemPidConfigInfo configInfo{};
230- configInfo.pid = 65535;
231- configInfo.evictThreshold = 100;
232- configInfo.targetEvictThreshold = 100;
233- configInfo.reclaimThreshold = 100;
234- configInfo.expectedMemoryUsage = UINT64_MAX;
235- configInfo.srcNumaId = 0;
236- 
237- ubse::serial::UbseSerialization serializer;
238- auto ret = configInfo.SerializeConfigInfo(serializer);
239- EXPECT_EQ(ret, UBSE_OK);
240- 
241- ubse::serial::UbseDeSerialization deserializer(serializer.GetBuffer(), serializer.GetLength());
242- ProcessMemPidConfigInfo deserialized{};
243- ret = deserialized.DeserializeConfigInfo(deserializer);
244- EXPECT_EQ(ret, UBSE_OK);
245- 
246- EXPECT_EQ(deserialized.pid, 65535);
247- EXPECT_EQ(deserialized.evictThreshold, 100);
248- EXPECT_EQ(deserialized.expectedMemoryUsage, UINT64_MAX);
249- EXPECT_TRUE(deserialized.srcNumaId.has_value());
250- EXPECT_EQ(deserialized.srcNumaId.value(), 0u);
251-}
252- 
253-TEST_F(TestProcessMemPidManagerDef, SerializePidInfoZeroConfigInfo)
254-{
255- ProcessMemPidInfo pidInfo{};
256- pidInfo.ppid = 0;
257- pidInfo.startTime = 0;
258- pidInfo.configInfo.pid = 0;
259- pidInfo.configInfo.evictThreshold = 0;
260- pidInfo.configInfo.targetEvictThreshold = 0;
261- pidInfo.configInfo.reclaimThreshold = 0;
262- pidInfo.configInfo.expectedMemoryUsage = 0;
263- pidInfo.configInfo.srcNumaId = std::nullopt;
264- 
265- ubse::serial::UbseSerialization serializer;
266- auto ret = pidInfo.SerializePidInfo(serializer);
267- EXPECT_EQ(ret, UBSE_OK);
268- 
269- ubse::serial::UbseDeSerialization deserializer(serializer.GetBuffer(), serializer.GetLength());
270- ProcessMemPidInfo deserialized{};
271- ret = deserialized.DeserializePidInfo(deserializer);
272- EXPECT_EQ(ret, UBSE_OK);
273- 
274- EXPECT_EQ(deserialized.startTime, 0);
275- EXPECT_EQ(deserialized.configInfo.pid, 0);
276- EXPECT_FALSE(deserialized.configInfo.srcNumaId.has_value());
277-}
278- 
279-TEST_F(TestProcessMemPidManagerDef, ProcessStatusEnumValues)
280-{
281- EXPECT_NE(ProcessStatus::IDLE, ProcessStatus::FAULT);
282- EXPECT_NE(ProcessStatus::BORROWING, ProcessStatus::REPAYING);
283- EXPECT_NE(ProcessStatus::WAIT_BORROW, ProcessStatus::INACTIVE);
284-}
285- 
286-TEST_F(TestProcessMemPidManagerDef, BorrowStatusEnumValues)
287-{
288- EXPECT_NE(BorrowStatus::COMPLETED, BorrowStatus::CREATING);
289-}
290- 
291-TEST_F(TestProcessMemPidManagerDef, ConstantsAreValid)
292-{
293- EXPECT_EQ(PROCESS_MEM_MODULE_CODE, 123u);
294- EXPECT_EQ(PROCESS_MEM_SERVICE_ID, 11u);
295- EXPECT_EQ(INVALID_REMOTE_NUMA, 0u);
296-}
297- 
298-TEST_F(TestProcessMemPidManagerDef, ProcessMemNamePrefix)
299-{
300- EXPECT_FALSE(PROCESS_MEM_NAME_PREFIX.empty());
301- EXPECT_EQ(PROCESS_MEM_NAME_PREFIX.find("ProcessMem"), 0u);
302-}
303- 
304-TEST_F(TestProcessMemPidManagerDef, PidCollectInfoDefault)
305-{
306- PidCollectInfo info{};
307- EXPECT_TRUE(info.numaMemDistribution.empty());
308- EXPECT_TRUE(info.childrenInfo.empty());
309-}
310- 
311-TEST_F(TestProcessMemPidManagerDef, DoubleSerializeDeserialize)
312-{
313- ProcessMemPidConfigInfo configInfo{};
314- configInfo.pid = 1234;
315- configInfo.evictThreshold = 80;
316- configInfo.expectedMemoryUsage = 1024000;
317- configInfo.srcNumaId = 1;
318- 
319- // First serialize-deserialize
320- ubse::serial::UbseSerialization serializer1;
321- configInfo.SerializeConfigInfo(serializer1);
322- ubse::serial::UbseDeSerialization deserializer1(serializer1.GetBuffer(), serializer1.GetLength());
323- ProcessMemPidConfigInfo intermediate{};
324- intermediate.DeserializeConfigInfo(deserializer1);
325- 
326- // Second serialize-deserialize
327- ubse::serial::UbseSerialization serializer2;
328- intermediate.SerializeConfigInfo(serializer2);
329- ubse::serial::UbseDeSerialization deserializer2(serializer2.GetBuffer(), serializer2.GetLength());
330- ProcessMemPidConfigInfo final_{};
331- final_.DeserializeConfigInfo(deserializer2);
332- 
333- EXPECT_EQ(final_.pid, configInfo.pid);
334- EXPECT_EQ(final_.evictThreshold, configInfo.evictThreshold);
335- EXPECT_EQ(final_.expectedMemoryUsage, configInfo.expectedMemoryUsage);
336- EXPECT_TRUE(final_.srcNumaId.has_value());
337- EXPECT_EQ(final_.srcNumaId.value(), configInfo.srcNumaId.value());
338-}
339} // namespace ubse::ut::process_mem242} // namespace ubse::ut::process_mem
@@ -20,10 +20,10 @@ void TestProcessMemPlugin::SetUp() {}
20 20 
21void TestProcessMemPlugin::TearDown() {}21void TestProcessMemPlugin::TearDown() {}
22 22 
23-TEST_F(TestProcessMemPlugin, UbsePluginInitFailNoLibrary)23+TEST_F(TestProcessMemPlugin, UbsePluginInitToleratesMissingLibrary)
24{24{
25 uint32_t ret = UbsePluginInit(123);25 uint32_t ret = UbsePluginInit(123);
26- EXPECT_NE(ret, UBSE_OK);26+ EXPECT_EQ(ret, UBSE_OK);
27}27}
28 28 
29TEST_F(TestProcessMemPlugin, UbsePluginDeInit)29TEST_F(TestProcessMemPlugin, UbsePluginDeInit)