eBPF采集内存剩余空间的方法

传统内存统计方式

总内存计算

total = used + free + kernel_used

因为kernel_used 不能统计出来,所以这里直接加对不上。

Kernel的动态内存分配通过以下几种接口:

alloc_pages/__get_free_page: 以页为单位分配

vmalloc: 以字节为单位分配虚拟地址连续的内存块

kmalloc: 以字节为单位分配物理地址连续的内存块,它是以slab为基础的,使用slab层的general caches — 大小为2^n,名称是kmalloc-32、kmalloc-64等。

  • 通过slab层分配的内存会被精确统计。
  • 通过vmalloc、kmalloc分配的内存也有统计。
  • 而通过alloc_pages分配的内存不会自动统计,除非调用alloc_pages的内核模块或驱动程序主动进行统计,否则我们只能看到free memory减少了。

所以,kernel在分配内存时并没有完整记录,所以无法精确统计。

系统实际可使用内存

free不能代表全部可用的内存,系统中有些内存虽然已被使用但是可以回收的,比如cache/buffer、slab都有一部分可以回收,所以这部分可回收的内存加上MemFree才是系统可用的内存。

所以应该看的是available。

available = free + 可回收内存

传统统计方式在我们使用场景的缺陷

  • 传统方式必须依赖free工具
  • 我们必须轮询调用free然后解析
  • 即使系统内存足够用,我们也得一直查询

内存水位标记watermark介绍

Linux为内存的使用设置了三种内存水位标记:high、low、min。他们所标记的含义分别为:

  • 剩余内存在high以上表示内存剩余较多,目前内存使用压力不大。
  • high-low的范围表示目前剩余内存存在一定压力。
  • low-min表示内存开始有较大使用压力,剩余内存不多了。
  • min是最小的水位标记,当剩余内存达到这个状态时,就说明内存面临很大压力。
  • 小于min这部分内存,内核是保留给特定情况下使用的,一般不会分配。

linux内存回收机制

当系统剩余内存低于watermark[low]的时候,内核就会触发kswapd内核线程执行,进行内存回收。

如果一个任务需要分配内存,内存消耗导致剩余内存达到了或超过了watermark[min]时,就会触发直接回收(direct reclaim)

watermark相关值如何计算

依据 /proc/sys/vm/min_free_kbytes

$ cat /proc/sys/vm/min_free_kbytes 67584

这个参数本身决定了系统中每个zone的watermark[min]的值大小。

然后内核根据min的大小并参考每个zone的内存大小分别算出每个zone的low水位和high水位值。

一个zone的"low"和"high"的值都是根据它的"min"值算出来的,"low"比"min"的值大1/4左右,"high"比"min"的值大1/2左右,三者的比例关系大致是4:5:6。

所以,当前系统watermark[min] 的值为 67584 / 1024 = 66 M。

查看各个zone中的min

系统内存区域总共分为5个zone:DMA、DMA32、Normal,Movable,Device

查看zone中的min值命令如下:

cat /proc/zoneinfo

这些值以page为单位的,所以计算时要乘以4K,得出的67576基本等于min_free_kbytes的值67584

所以,计算方式:(16+996+15882)*4=67576。

首先得到总的min_free_kbytes,然后根据各个zone的物理大小,计算出各自z

one的min。

low和high值的计算

一个zone的"low"和"high"的值都是根据它的"min"值算出来的,"low"比"min"的值大1/4左右,"high"比"min"的值大1/2左右,三者的比例关系大致是4:5:6。

low的值

high的值,下面的值我们删除了一些,是经过删减的显示

$ cat /proc/zoneinfo | grep high high 24 high 1494 high 23822

验算:

15882/4=3970.5, 15882+3970 = 19852

15882/2 = 7941 ,15882+7941 = 23823

进过验算,确实是这样

内存回收策略

$ cat /proc/sys/vm/swappiness 60

这个文件是个百分比,默认值60,范围0~100.

swappiness的值是用来控制内存回收时,回收的匿名页更多一些还是回收的file cache更多一些 。

如果swappiness设置为100,那么匿名页和文件将用同样的优先级进行回收。

eBPF采集内存剩余空间原理

我们不需要时时刻刻去采集内存剩余空间,只有在内存剩余空间在watermark[low]时,才触发采集动作,这样可以有效避免大量无用的内存剩余数据。

# BPF program

prog = """

#include <uapi/linux/ptrace.h>

#include <linux/mmzone.h>

#include <linux/sched.h>


 

#ifdef CONFIG_MMU

#define ALLOC_OOM 0x08

#else

#define ALLOC_OOM ALLOC_NO_WATERMARKS

#endif

#define ALLOC_HARDER 0x10 /* try to alloc harder */

#define ALLOC_HIGH 0x20 /* __GFP_HIGH set */

#define ALLOC_CPUSET 0x40 /* check for correct cpuset */

#define ALLOC_CMA 0x80 /* allow allocations from CMA areas */

// define output data structure in C

struct data_t {

u32 pid;

u32 order;

u64 ts;

long free_pages;

long min;

long lowmem_reserve;

char comm[TASK_COMM_LEN];

};

BPF_PERF_OUTPUT(events);

static long __zone_watermark_unusable_free(struct zone *z,

unsigned int order, unsigned int alloc_flags)

{

const bool alloc_harder = (alloc_flags & (ALLOC_HARDER|ALLOC_OOM));

long unusable_free = (1 << order) - 1;

/*

* If the caller does not have rights to ALLOC_HARDER then subtract

* the high-atomic reserves. This will over-estimate the size of the

* atomic reserve but it avoids a search.

*/

if (likely(!alloc_harder))

unusable_free += z->nr_reserved_highatomic;

#ifdef CONFIG_CMA

/* If allocation can't use CMA areas don't use free CMA pages */

if (!(alloc_flags & ALLOC_CMA))

unusable_free += zone_page_state(z, NR_FREE_CMA_PAGES);

#endif

return unusable_free;

}

int kprobe__zone_watermark_ok(struct pt_regs *ctx, struct zone *z, unsigned int order, unsigned long mark,

int highest_zoneidx, unsigned int alloc_flags, long free_pages)

{

//bpf_trace_printk("free_pages before = %ld, order = %ld \\n", free_pages, order);

long min = mark;

int o;

const bool alloc_harder = (alloc_flags & (ALLOC_HARDER|ALLOC_OOM));

struct data_t data = {};

/* free_pages may go negative - that's OK */

/* 计算内存剩余空间 */

free_pages -= __zone_watermark_unusable_free(z, order, alloc_flags);   

//bpf_trace_printk("free_pages after = %ld, order = %ld \\n", free_pages, order);

if (alloc_flags & ALLOC_HIGH)

min -= (unsigned long)min / 2;

if (unlikely(alloc_harder)) {

if (alloc_flags & ALLOC_OOM)

min -= (unsigned long)min / 2;

else

min -= (unsigned long)min / 4;

}

/*

* Check watermarks for an order-0 allocation request. If these

* are not met, then a high-order request also cannot go ahead

* even if a suitable page happened to be free.

*/

/* 当剩余内存数量大于 min与备份空间和,不进行上报流程,所以,这里进行了筛选 */

if (free_pages > min + z->lowmem_reserve[highest_zoneidx]) {

//bpf_trace_printk("free_pages after watermark check = %ld\\n", free_pages);

return 0;

}

//bpf_trace_printk("free_pages after watermark check = %ld, order = %ld \\n", free_pages, order);

//bpf_trace_printk("free_pages after watermark min = %ld, lowmem_reserve = %ld \\n", min, z->lowmem_reserve[highest_zoneidx]);

u64 id = bpf_get_current_pid_tgid();

data.pid = id >> 32; // PID is higher part

data.ts = bpf_ktime_get_ns() / 1000;

data.free_pages = free_pages;

data.order = order;

data.min = min;

data.lowmem_reserve = z->lowmem_reserve[highest_zoneidx];

bpf_get_current_comm(&data.comm, sizeof(data.comm));

events.perf_submit(ctx, &data, sizeof(data));

return 0;

}

"""

# load BPF program

b = BPF(text=prog)

# Attach Kprobe to the target function

# 我们hook __zone_watermark_ok 函数 

b.attach_kprobe(event="__zone_watermark_ok", fn_name="kprobe__zone_watermark_ok")               

# Print trace output

print("Tracing __zone_watermark_ok function. Hit Ctrl+C to exit.")

# header

print("%-20s %-20s %-16s %-16s %-10s %-16s %-16s" % ("TIME(s)", "COMM", "PID", "FREE(MB)", "ORDER", "MIN(MB)", "RESERVE(MB)"))

# process event

start = 0

def print_event(cpu, data, size):

event = b["events"].event(data)

time_s = (float(event.ts)) / 1000000

printb(b"%-20.9f %-20s %-16d %-16d %-10d %-16d %-16d" % (time_s, event.comm, event.pid, event.free_pages * 4 / 1024, event.order, event.min * 4 / 1024, event.lowmem_reserve * 4 / 1024))

# loop with callback to print_event

b["events"].open_perf_buffer(print_event)

while 1:

try:

b.perf_buffer_poll()

except KeyboardInterrupt:

exit()

原创文章,转载请注明出处

  • 7
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值