掌握 oeAware 的插件加载与实例使能;理解中断绑核解决什么问题;写一个网络程序验证中断位置对性能的影响。
手工绑核的局限
第 7 讲的 numactl 绑定很有效,但有个前提:你知道绑定策略是什么,而且负载不会变。
现实里负载会变。白天是读密集,晚上是写密集;高峰期线程数翻倍,低谷期只用两个核。静态绑定在负载变化时可能反而成为负担------比如把 128 个线程都绑到只有 48 个核的节点 0 上。
oeAware 是 openEuler 提供的低开销、感知式调优框架 ,它的思路是:让系统自己感知负载特征,动态调整资源分配。
oeAware 的架构
oeAware 把调优拆成三层,用订阅机制串联,每层做成可复用的插件(一个插件 = 一个 .so):
| 层级 | 职责 | 代表插件 |
|---|---|---|
| 采集层 | 收集系统/业务运行时数据 | libpmu.so(PMU 采样)、libsystem_collector.so、libdocker_collector.so |
| 感知层 | 分析数据、识别场景 | libthread_scenario.so(线程场景)、libanalysis_oeaware.so |
| 调优层 | 执行具体调优动作 | libsystem_tune.so、libtune_numa.so、libub_tune.so |
关键设计:插件之间通过 topic 订阅。采集插件发布数据,感知插件订阅后分析,调优插件再订阅感知结果。所以加载插件有依赖顺序,oeAware 会自动处理。
安装与启动
bash
# 安装(openEuler 24.03-LTS-SP3 起默认已安装)
sudo yum install oeAware-manager
# 启动服务
sudo systemctl start oeaware
# 修改 config.yaml 后必须重启才能生效
sudo systemctl restart oeaware
插件加载目录是 /usr/lib64/oeAware-plugin/。
权限 :官方明确 oeAware 当前限于 root 组操作(SDK 支持 root 和 oeaware 组)。文件权限被严格校验,不要修改 :插件文件 440、可执行文件 750、配置文件 640,改了权限会导致加载失败。
核心命令:oeawarectl
| 命令 | 作用 | 示例 |
|---|---|---|
-l/--load <插件> |
加载插件 | oeawarectl -l libthread_collect.so |
-r/--remove <插件> |
卸载插件 | oeawarectl -r libthread_collect.so |
-q/--query [插件] |
查询插件(不带参查全部) | oeawarectl -q |
-Q/--query-dep [实例] |
查询实例依赖关系 | oeawarectl -Q |
-e/--enable <实例> |
使能插件实例 | oeawarectl -e tune_numa_mem_access |
-d/--disable <实例> |
去使能 | oeawarectl -d tune_numa_mem_access |
--list |
列出可下载的包 | oeawarectl --list |
--info |
查询调优实例信息 | oeawarectl --info |
-i/--install <包名> |
安装插件包 | oeawarectl -i numafast |
analysis |
运行分析模式 | oeawarectl analysis -t 10 |
-q 和 -Q 的区别要分清 :-q 查的是已加载的插件 ,-Q 查的是运行中的实例及其依赖关系。后者更适合排查"为什么我的插件没生效"。
加载 NUMA 调优插件
libtune_numa.so 是外部插件,不在默认安装的 oeAware-manager 里,需要先装包:
bash
# 1) 安装 numafast 包(会一并带来 NUMA 相关插件)
oeawarectl -i numafast
# 2) 如未自动加载,手动加载
oeawarectl -l libtune_numa.so
# 3) 使能实例
oeawarectl -e tune_numa_mem_access
# 4) 验证依赖关系
oeawarectl -Q
四个 NUMA 相关调优实例
oeAware 提供了四个互补的 NUMA 调优实例:
| 实例 | 做什么 | 适用场景 |
|---|---|---|
tune_numa_mem_access |
周期性迁移线程和内存,减少跨 NUMA 访存 | 负载会变化的场景 |
numa_sched_tune |
让线程在生命周期内保持在同一 NUMA | 负载稳定的场景 |
scenario_numa |
感知跨 NUMA 访存占比(只观测,不调优) | 不确定该不该介入时先看数据 |
hardirq_tune |
把网卡队列中断绑定到使用它的业务所在的 NUMA | 网络密集型服务 |
选择依据 :负载稳定就绑死(numa_sched_tune);负载会变就动态迁移(tune_numa_mem_access);不确定就先观测(scenario_numa)。
查看和调整实例参数
bash
# 查看命令行形式的参数说明
oeawarectl -e tune_numa_mem_access -cmd "--help cmd"
# 查看 YAML 形式的参数(字段更多)
oeawarectl -e tune_numa_mem_access -cmd "--help yaml"
tune_numa_mem_access 的主要参数:
| 参数 | 含义 | 取值 |
|---|---|---|
-i/--sampling-interval |
采样间隔(ms) | 100, 100000,默认 100 |
-t/--sampling-times |
每次优化的采样次数 | 1, 1000,默认 10 |
-m/--tune-mode |
调优模式 | b(page+thread) / t(仅thread) / p(仅page) |
-w/--load-way |
负载分布方式 | b(节点间均衡) / c(集中到较少NUMA) |
--smt |
SMT 策略 | off / phy-first(默认) |
-w 的取舍 :b(均衡)适合吞吐型负载,c(集中)适合延迟敏感型------把线程集中到少数节点能减少跨节点通信,与第 7 讲 --interleave vs --membind 的取舍是同一个道理。
中断绑核:一个容易被忽略的瓶颈
网络数据包到达时,网卡产生中断,CPU 处理中断、把数据从内核态送到用户态,业务线程才能读到。
如果处理中断的核和运行业务的线程不在同一个 NUMA 节点,数据要跨节点搬运------延迟和带宽双重损失。
hardirq_tune 解决的就是这个。相关的还有:
| 实例 | 作用 |
|---|---|
hardirq_tune |
网卡队列中断绑定到使用它的业务所在 NUMA |
multi_net_path |
多路径网卡调优,每个中断只处理本 NUMA 的业务 |
disk_adapt_ic |
自适应磁盘中断聚合,动态调整 NVMe 中断合并参数(依赖 nvme-cli) |
disk_adapt_ic 的取舍:降低中断频率能提升吞吐,但会牺牲响应延迟。聚合多少个请求产生一次中断,需要按业务特性平衡------高吞吐存储和低延迟存储的答案不一样。
写一个程序观察中断行为
先看当前的中断分布:
bash
# 查看每个核上的中断计数(对应网卡)
cat /proc/interrupts | head -3
# 查看网卡队列与 CPU 的对应关系
ls /sys/class/net/*/queues/ 2>/dev/null | head
# 查看某个中断号的亲和性设置
cat /proc/irq/<IRQ号>/smp_affinity_list
/proc/interrupts 的输出第一行是 CPU 编号,下面每行是一个中断源。观察中断计数是否集中在少数几个核上------如果所有网络中断都落在 CPU 0,那就是明显的瓶颈。
写一个简单的网络程序来产生可观察的负载:
c
/*
* tcp_server.c ------ 简单的 TCP 回显服务器,用于观察网络中断分布
*
* 编译:gcc -O2 -o tcp_server tcp_server.c
* 运行:./tcp_server 9999
*
* 另开终端压测:for i in $(seq 1 20); do (echo hello | nc 127.0.0.1 9999) & done; wait
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netinet/in.h>
#define BUF_SIZE 4096
int main(int argc, char *argv[])
{
int port = (argc > 1) ? atoi(argv[1]) : 9999;
/* 1) 创建 TCP socket */
int listen_fd = socket(AF_INET, SOCK_STREAM, 0);
if (listen_fd < 0) { perror("socket"); return 1; }
/* 2) 允许端口复用,避免重启时 "Address already in use" */
int opt = 1;
setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
/* 3) 绑定地址与端口 */
struct sockaddr_in addr;
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = htonl(INADDR_ANY); /* 监听所有网卡 */
addr.sin_port = htons((uint16_t)port);
if (bind(listen_fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
perror("bind"); close(listen_fd); return 1;
}
/* 4) 开始监听,backlog 设为 128 */
if (listen(listen_fd, 128) < 0) { perror("listen"); close(listen_fd); return 1; }
printf("监听端口 %d...\n", port);
fflush(stdout);
char buf[BUF_SIZE];
/* 5) 主循环:接受连接并回显 */
for (;;) {
struct sockaddr_in peer;
socklen_t peer_len = sizeof(peer);
int conn_fd = accept(listen_fd, (struct sockaddr *)&peer, &peer_len);
if (conn_fd < 0) { perror("accept"); continue; }
/* 单线程逐个处理,逻辑简单、便于观察中断行为。
* 生产环境应该用 epoll 或线程池,这里故意简化。 */
ssize_t n;
while ((n = read(conn_fd, buf, sizeof(buf))) > 0) {
/* 原样写回。write 可能短写,这里简化处理。 */
if (write(conn_fd, buf, (size_t)n) != n) break;
}
close(conn_fd);
}
close(listen_fd);
return 0;
}
编译并运行:
bash
gcc -O2 -o tcp_server tcp_server.c
./tcp_server 9999 &
# 另开终端,观察中断分布
watch -n 1 'cat /proc/interrupts | grep -E "eth|ens|enp"'
再用 nc 或 curl 制造负载,看中断计数往哪些核上增长。记录中断集中的核,然后用 oeAware 或手动设置亲和性:
bash
# 手动把某个中断绑定到指定核(例如 IRQ 号 100,绑到 CPU 4)
echo 4 | sudo tee /proc/irq/100/smp_affinity_list
# 用 oeAware 自动做这件事
oeawarectl -e hardirq_tune
config.yaml:持久化配置
每次重启都要重新使能实例太麻烦。oeAware 通过 /etc/oeAware/config.yaml 持久化:
yaml
log_path: /var/log/oeAware
log_level: 1 # 1=DEBUG 2=INFO 3=WARN 4=ERROR
enable_list: # 默认使能的插件
- name: libtune_numa.so # 只写 name:使能该插件的所有实例
- name: libsystem_tune.so
instances: # 写 instances:只使能列出的实例
- stealtask_tune
- dynamic_smt_tune
plugin_list: # 可下载的包
- name: numafast
description: NUMA tuning package
url: https://example.com/numafast.rpm
注意日志级别的编码不同 :oeAware 的 config.yaml 用 1/2/3/4(1=DEBUG),而 devkit 和 ksys 用的是 0/1/2/3(0=DEBUG)。这个不一致很容易踩坑。
改完配置要 systemctl restart oeaware。
analysis 模式:先看数据再调优
如果不确定该使能哪些插件,先用分析模式:
bash
oeawarectl analysis -t 10
输出分三段:Data Analysis (数据分析)、Analysis Conclusion (结论)、Analysis Suggestion(建议)------工具会直接告诉你该使能什么。
主要参数:
| 参数 | 含义 |
|---|---|
-t/--time <s> |
分析时长(秒),范围 1--100,默认 30 |
-r/--realtime |
实时显示报告 |
-v/--verbose |
显示详细信息 |
--pid |
分析指定进程 |
--out-path |
报告输出路径 |
完整操作序列
bash
# ── 安装与启动 ──
sudo yum install oeAware-manager
sudo systemctl start oeaware
# ── 查看当前状态 ──
oeawarectl -q # 已加载的插件
oeawarectl --info # 调优实例信息
oeawarectl -Q # 运行中的实例及依赖
# ── 先用分析模式看数据 ──
oeawarectl analysis -t 10
# ── 按建议加载插件 ──
oeawarectl -i numafast
oeawarectl -e tune_numa_mem_access
oeawarectl -e hardirq_tune
# ── 查看参数 ──
oeawarectl -e tune_numa_mem_access -cmd "--help cmd"
# ── 用 KSYS 验证效果 ──
./ksys collect -d 30 -o /home/test/out/ ./your_app
# ── 停止 ──
oeawarectl -d tune_numa_mem_access
一个容易忽略的冲突
⚠️
libkperf同一时刻只允许一个进程调用。 这意味着:如果你的程序或perf命令正在采样 PMU,oeAware 的采集插件可能拿不到数据,反之亦然。
做性能测试时要避免同时跑 perf 和 oeAware 的 PMU 采集。这是排查"oeAware 没效果"时的常见原因。
动手练习
- 用
oeawarectl analysis -t 30分析你的机器,对比它给出的建议与你用numastat手工得出的结论。 - 运行
tcp_server并用nc制造负载,记录/proc/interrupts里网络中断集中的核;使能hardirq_tune后再观察一次分布变化。 - 分别用
-w b和-w c使能tune_numa_mem_access压测同一个服务,对比吞吐与延迟的差异。 - 同时跑
perf stat和 oeAware 的 PMU 采集,验证libkperf的独占限制会以什么形式暴露(报错或采不到数据)。
进阶与拓展
- 与 irqbalance 的关系 :irqbalance 是多数发行版默认启用的中断均衡守护进程,目标是把中断尽量均匀摊到各 CPU;
hardirq_tune的目标相反------按业务 NUMA 亲和把网卡中断集中到对的节点。两者都会改写中断亲和性,同启可能互相覆盖,实践上建议二选一(systemctl disable --now irqbalance后再使能hardirq_tune;具体冲突行为因版本而异,uncertain)。 - oeAware 插件生态 :插件以
.so形式分发,oeawarectl --list查可下载的包(如numafast),-i安装后按需加载;oeAware 另提供 SDK(支持 root 和 oeaware 组)开发自研采集/感知/调优插件,产出的.so放入/usr/lib64/oeAware-plugin/即可被加载。 - 中断之外的软件路径 :内核还有 RPS(软件层把收包处理分发到其他 CPU,网卡只有单队列时尤其有用)和 XPS(控制发送队列与 CPU 的对应关系),与中断绑核互补;
smp_affinity_list写 CPU 编号,姊妹文件smp_affinity用十六进制位掩码,脚本里别混用。 - 深入路径 :
/proc/interrupts各字段含义见man 5 proc;各调优实例参数的全量说明以官方 oeAware 用户指南为准(见参考来源)。
参考来源
- openEuler · oeAware 用户指南 --- 插件架构、oeawarectl 全部命令、config.yaml 格式、NUMA 调优实例与参数、libkperf 独占约束
- openEuler · 系统资源与性能 --- NUMA 概念与跨节点访问的代价