This repository has been archived on 2025-09-14. You can view files and clone it, but cannot push or open issues or pull requests.
Files
zhangyang-variable-monitor/README.md
2023-11-28 02:52:45 -05:00

250 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

## Variable Monitor
changelog
```log
11.9 多个变量监控支持
11.10 按照 pid 区分不同内核结构, 支持每个进程单独申请取消自己的监控.
11.13 用户接口 cancel_all_watch -> cancel_watch, 每个进程互不干扰.
11.28 完全重构,更新文档.
```
## 说明
监控 数值变量(给定 地址,长度), 达到设定条件打印系统内 Task 信息(用户态堆栈/内核态堆栈/调用链信息).
- 支持多进程, 单个进程退出时,取消该进程的所有监控.
- 相同定时间隔会分配到同一个定时器,一个定时器最多监控 32 个变量,全局最多 128 个定时器.
- 以上数量限制定义在 `source/module/monitor_timer.h`.
- `testcase/helloworld.c` 有测试到单进程 2049 个变量;
文件结构
```log
├── build // output
├── source // all source code
│ ├── buffer // 模块与用户空间通信的缓冲区
│ ├── module // 模块代码
│ ├── uapi // 用户空间接口
│ ├── ucli // 用户空间命令行工具
│ └── ucli_py // 用户空间命令行 python (仅测试用,待完成)
│ └── libunwind // python 解析堆栈信息移植库
├── testcase // 测试用例
└── tools // 测试工具
```
## 使用
设定对变量监控有两种函数: 宏定义 或 定义 watch_arg 结构体
- 都需要添加 `source/uapi` 下的头文件 `#include "monitor_user.h"`
需要取消监控时调用 `cancel_watch();` variant_monitor 会取消该进程所有监控.
- 当进程退出后,也会执行相同的操作,取消该进程所有监控.
- 因此调用 `cancel_watch();` 是个可选项,但依然建议调用以避免可能的内存泄漏.
获取 Task 信息是一项耗时操作,这里使用了 workqueue 处理,且一次处理后该定时器重启间隔默认为 5s.
- 此值可以在 `/proc/variable_monitor/dump_reset_sec` 查看和修改.
### 挂载驱动
项目根目录
```bash
# 编译加载模块
make && insmod source/variable_monitor.ko
# 卸载模块,清理编译文件
# rmmod source/variable_monitor.ko && make clean
# 仅在 `kernel 5.17.15-1.el8.x86_64` 测试,其他内核版本未测试.
```
### 宏定义
示例如 `testcase/helloworld.c`, 对常见数值类型宏定义 方便使用:
- 其他类型见 `source/uapi/monitor_user_sw.h`
```c
// 传入变量名 | 地址 | 阈值
START_WATCH_INT("temp", &temp, 150);
START_WATCH_INT_LESS("temp", &temp, 150);
```
默认情况下,使用宏定义 定时器的时间间隔为 10us; 此值可以在 `/proc/variable_monitor/def_interval_ns` 查看和修改.
### watch_arg 结构体
如果需要对定时间隔等有更多控制,请定义 watch_arg 结构体,start_watch 启动监控:
- 对每个需要监控的变量 设置: 名称 && 地址 && 长度, 设置阈值, 比较方式, 定时器间隔(ns) 等.
- `start_watch(watch_arg);` 启动监控
- 需要取消监控时调用 `cancel_watch();`
```c
// start_watch 传入的是 watch_arg 结构体.各个字段意义如下
// - name 限制 `MAX_NAME_LEN`(15) 个有效字符
typedef struct
{
pid_t task_id; // current process id
char name[MAX_NAME_LEN + 1]; // name (15+1)
void *ptr; // virtual address
int length_byte; // byte
long long threshold; // threshold value
unsigned char unsigned_flag; // unsigned flag (true: unsigned, false: signed)
unsigned char greater_flag; // reverse flag (true: >, false: <)
unsigned long time_ns; // timer interval (ns)
} watch_arg;
//一个初始化示例
watch_args = (watch_arg){
.task_id = getpid(),
.ptr = &temp,
.name = "temp",
.length_byte = sizeof(int),
.threshold = 150,
.unsigned_flag = 0,
.greater_flag = 1,
.time_ns = 2000 + 5000
};
start_watch(watch_args);
```
### 打印输出
定时器不断按照设定间隔轮询变量,当达到设定条件时,采集此时系统内符合要求的 Task 信息(用户态堆栈/内核态堆栈/调用链信息).
- `dmesg` 可以查看到具体的超出设定条件的变量信息;
- Task 信息被输出到缓存区,使用 ucli 工具查看.
`dmesg` 打印示例如下
```log
[42865.640988] -------------------------------------
[42865.640992] -----------variable monitor----------
[42865.640993] 超出阈值1701141698684973655
[42865.640994] : pid: 63936, name: temp0, ptr: 00000000bade6e61, threshold:110
[42865.648068] -------------------------------------
[42875.640703] -------------------------------------
[42875.640706] -----------variable monitor----------
[42875.640706] 超出阈值1701141708684881779
[42875.640708] : pid: 63936, name: temp0, ptr: 00000000bade6e61, threshold:110
[42875.640710] : pid: 63936, name: temp1, ptr: 00000000ee645b96, threshold:111
[42875.640711] : pid: 63936, name: temp2, ptr: 00000000f62b7afe, threshold:112
[42875.640711] : pid: 63936, name: temp3, ptr: 00000000d100fa3c, threshold:113
[42875.640712] : pid: 63936, name: temp4, ptr: 000000006d31cae1, threshold:114
[42875.640712] : pid: 63936, name: temp5, ptr: 00000000723c7a2a, threshold:115
[42875.640713] : pid: 63936, name: temp6, ptr: 0000000026ef6e83, threshold:116
[42875.640714] : pid: 63936, name: temp7, ptr: 00000000fc1e5d5e, threshold:117
[42875.640714] : pid: 63936, name: temp8, ptr: 0000000069b2666e, threshold:118
[42875.640715] : pid: 63936, name: temp9, ptr: 000000000176263d, threshold:119
[42875.648023] -------------------------------------
```
默认情况下 `ucli` 编译后在 build 文件夹下
`ucli > output`
- ucli 会将缓存区内容解析后输出到 `output` 文件中.
- **此操作会清空缓存区**
`ucli` 工具输出示例如下(详情见 output_example)
- userstack 是 testcase 下的堆栈信息测试程序.
```log
##CGROUP:[/] 51666 [510] 采样命中[D]
进程信息: [/ / userstack] PID 51666 / 51666
##C++ pid 51666
用户态堆栈SP7ffcd5822298, BP:2, IP:7f071c720838
#~ 0x7f071c720838 __GI___nanosleep ([symbol])
#~ 0x7f071c72076e __sleep ([symbol])
#~ 0x400a08 customFunction1 ([symbol])
#~ 0x400a64 customFunction3 ([symbol])
#~ 0x400a42 customFunction2 ([symbol])
#~ 0x400a21 customFunction1 ([symbol])
#~ 0x400a64 customFunction3 ([symbol])
#~ 0x400a42 customFunction2 ([symbol])
#~ 0x400a21 customFunction1 ([symbol])
#~ 0x400a64 customFunction3 ([symbol])
#~ 0x400a42 customFunction2 ([symbol])
#~ 0x400a21 customFunction1 ([symbol])
#~ 0x400a64 customFunction3 ([symbol])
#~ 0x400a42 customFunction2 ([symbol])
#~ 0x400a21 customFunction1 ([symbol])
#~ 0x400a64 customFunction3 ([symbol])
#~ 0x400a42 customFunction2 ([symbol])
#~ 0x400a21 customFunction1 ([symbol])
#~ 0x400a64 customFunction3 ([symbol])
#~ 0x400a42 customFunction2 ([symbol])
#~ 0x400a21 customFunction1 ([symbol])
#~ 0x400a64 customFunction3 ([symbol])
#~ 0x400a42 customFunction2 ([symbol])
#~ 0x400a21 customFunction1 ([symbol])
#~ 0x400a64 customFunction3 ([symbol])
#~ 0x400a42 customFunction2 ([symbol])
#~ 0x400a21 customFunction1 ([symbol])
#~ 0x400a64 customFunction3 ([symbol])
#~ 0x400a42 customFunction2 ([symbol])
#~ 0x400a21 customFunction1 ([symbol])
#~ 0x400a64 customFunction3 ([symbol])
#~ 0x400a42 customFunction2 ([symbol])
#~ 0x400a21 customFunction1 ([symbol])
#~ 0x400a75 main ([symbol])
#~ 0x7f071c661d85 __libc_start_main ([symbol])
#~ 0x40081e _start ([symbol])
内核态堆栈:
#@ 0xffffffff811730dd hrtimer_nanosleep ([kernel.kallsyms])
#@ 0xffffffff811733a6 __x64_sys_nanosleep ([kernel.kallsyms])
#@ 0xffffffff819fa117 do_syscall_64 ([kernel.kallsyms])
#@ 0xffffffff81c0007c entry_SYSCALL_64_after_hwframe ([kernel.kallsyms])
#@ 0xffffffff819fa117 do_syscall_64 ([kernel.kallsyms])
#@ 0xffffffff81c0007c entry_SYSCALL_64_after_hwframe ([kernel.kallsyms])
#@ 0xffffffff819fa117 do_syscall_64 ([kernel.kallsyms])
#@ 0xffffffff81c0007c entry_SYSCALL_64_after_hwframe ([kernel.kallsyms])
#* 0xffffffffffffff userstack (UNKNOWN)
进程链信息:
#^ 0xffffffffffffff ./build/userstack (UNKNOWN)
#^ 0xffffffffffffff /bin/bash --init-file /root/.vscode-server-insiders/cli/servers/Insiders-ca9da6c177fc4cf7429e1d0c1c52f710d6d953c6/server/out/vs/workbench/contrib/terminal/browser/media/shellIntegration-bash.sh (UNKNOWN)
#^ 0xffffffffffffff /root/.vscode-server-insiders/cli/servers/Insiders-ca9da6c177fc4cf7429e1d0c1c52f710d6d953c6/server/node /root/.vscode-server-insiders/cli/servers/Insiders-ca9da6c177fc4cf7429e1d0c1c52f710d6d953c6/server/out/bootstrap-fork --type=ptyHost --logsPath /root/ (UNKNOWN)
#^ 0xffffffffffffff /root/.vscode-server-insiders/cli/servers/Insiders-ca9da6c177fc4cf7429e1d0c1c52f710d6d953c6/server/node /root/.vscode-server-insiders/cli/servers/Insiders-ca9da6c177fc4cf7429e1d0c1c52f710d6d953c6/server/out/server-main.js --connection-token=remotessh --a (UNKNOWN)
#^ 0xffffffffffffff sh /root/.vscode-server-insiders/cli/servers/Insiders-ca9da6c177fc4cf7429e1d0c1c52f710d6d953c6/server/bin/code-server-insiders --connection-token=remotessh --accept-server-license-terms --start-server --enable-remote-auto-shutdown --socket-path=/tmp/code (UNKNOWN)
#^ 0xffffffffffffff /root/.vscode-server-insiders/code-insiders-ca9da6c177fc4cf7429e1d0c1c52f710d6d953c6 command-shell --cli-data-dir /root/.vscode-server-insiders/cli --on-port --require-token b5a047063eb7 (UNKNOWN)
#^ 0xffffffffffffff /usr/lib/systemd/systemd --switched-root --system --deserialize 17 (UNKNOWN)
##
```
## demo
usercase 文件夹下
- `helloworld.c`: 测试大量变量监控
- `userstack.c`: 测试用户态堆栈输出
- `hptest.c`: 测试 hugePage 挂载
## 其他
程序分为两部分: 字符设备 和 用户空间接口, 两者通过 ioctl 通信.
用户空间地址访问
- 用户程序传入的变量 虚拟地址, 使用 `get_user_pages_remote` 获取地址所在内存页, `kmap` 将其映射到内核.
- 192.168.40.204 环境下,HugeTLB Pages 测试挂载正常.
- 内存页地址 + 偏移量存入定时器对应的 `kernel_watch_arg` 中, hrTimer 轮询时访问 `kernel_watch_arg` 得到真实值.
定时器分组
- hrTimer 数据结构定义在全局数组 `kernel_wtimer_list`.分配定时器时,会检查遍历 `kernel_wtimer_list` 比较定时器间隔,
- 相同定时间隔的 watch 分配到同一组,对应同一个 hrTimer.
- 若一个定时器监控变量数量超过 `TIMER_MAX_WATCH_NUM` (32),则会创建一个新的 hrTimer.
- hrTimer 的总数量(`kernel_wtimer_list` 数组长度)限制是 `MAX_TIMER_NUM`(128).
内存页 mount/unmount
- `get_user_pages_remote`/ `kmap` 会增加对应的计数,需要对等的 `put_page`/`kunmap`.
- 一个模块内全局链表 `watch_local_memory_list` 存储每一个成功挂载的变量对应的 page 和 kt,执行字符设备的 close 操作时,遍历并卸载.
variable monitor 添加/删除
- kernel_watch_arg 数据结构中有 pid 的成员变量,但添加变量监控时,不按照进程区分.
- 删除时遍历全部监控变量,比较 pid.
- 删除造成的缺位,将最后的变量移动到空位, sentinel--; hrTimer 同理.
堆栈输出条件: 条件参考自 [diagnose-tools::load.c](https://github.com/alibaba/diagnose-tools/blob/e285bc4626a7d207eabd4a69cb276e1a3b1b7c76/SOURCE/module/kernel/load.c#L209)
- `TASK` 要满足 TASK_RUNNING 和 `__task_contributes_to_load``TASK_IDLE`(可能有阻塞进程).
- `__task_contributes_to_load` 对应内核宏 `task_contributes_to_loa`.
```c
// https://www.spinics.net/lists/kernel/msg3582022.html
// remove from 5.8.rc3,but it still work
// whether the task contributes to the load
#define __task_contributes_to_load(task) \
((READ_ONCE(task->__state) & TASK_UNINTERRUPTIBLE) != 0 && (task->flags & PF_FROZEN) == 0 && \
(READ_ONCE(task->__state) & TASK_NOLOAD) == 0)
```