memleak
功能
memleak是一个BCC工具,它跟踪内存分配和空闲事件以及分配堆栈跟踪,随着时间的推移可以显示长期幸存者:那些没有被释放的分配。
示例
例如,在bash shell进程中运行memleak
memleak -p 3126
Attaching to pid 3228, Ctrl+C to quit.
[09:14:15] Top 10 stacks with outstanding
allocations:
[...]
960 bytes in 1 allocations from stack
xrealloc+0x2a [bash]
strvec_resize+0x2b [bash]
maybe_make_export_env+0xa8 [bash]
execute_simple_command+0x269 [bash]
execute_command_internal+0x862 [bash]
execute_connection+0x109 [bash]
execute_command_internal+0xc18 [bash]
execute_command+0x6b [bash]
reader_loop+0x286 [bash]
main+0x969 [bash]
libc_start_main+0xe7 [libc-2.27.so]
[unknown]
1473 bytes in 51 allocations from stack
xmalloc+0x18 [bash]
make_env_array_from_var_list+0xc8
[bash]
make_var_export_array+0x3d [bash]
maybe_make_export_env+0x12b [bash]
execute_simple_command+0x269 [bash]
execute_command_internal+0x862 [bash]
execute_connection+0x109 [bash]
execute_command_internal+0xc18 [bash]
execute_command+0x6b [bash]
reader_loop+0x286 [bash]
main+0x969 [bash]
libc_start_main+0xe7 [libc-2.27.so]
[unknown]
[...]
默认情况下,它每5秒打印一次输出,显示分配堆栈和尚未释放的字节总数。最后一个堆栈显示,通过execute_command()和make_env_array_from_var_list()分配了1473个字节。
如果不提供-p PID, memleak 将跟踪内核分配:
memleak
Attaching to kernel allocators, Ctrl+C to quit.
[...]
[09:19:30] Top 10 stacks with outstanding
allocations:
[...]
15384576 bytes in 3756 allocations from
stack
alloc_pages_nodemask+0x209 [kernel]
alloc_pages_vma+0x88 [kernel]
handle_pte_fault+0x3bf [kernel]
handle_mm_fault+0x478 [kernel]
handle_mm_fault+0xb1 [kernel]
__do_page_fault+0x250 [kernel]
do_page_fault+0x2e [kernel]
page_fault+0x45 [kernel]
[...]
对于进程目标,memleak通过跟踪用户级分配函数来工作:malloc()、calloc()、free()等。对于内核,它使用kmem跟踪点:kmem:kmalloc、kmem:kfree等。命令行用法:
memleak [options] [-p PID] [-c COMMAND] [interval[count]]
Options include:
• -s RATE: sample one in every RATE allocations to lower
在每个比率分配中抽取一个样本以降低管理费用
• -o OLDER: prune allocations younger than OLDER in milliseconds.
以毫秒为单位删除更早期的分配
目前,这意味着memleak更多地是一个故障排除或调试工具,而不是日常的生产分析工具,直到uprobes的性能得到极大改善。
代码
#!/usr/bin/python
#
memleak Trace and display outstanding allocations to detect
memory leaks in user-mode processes and the kernel.
#
USAGE: memleak [-h] [-p PID] [-t] [-a] [-o OLDER] [-c COMMAND]
[--combined-only] [--wa-missing-free] [-s SAMPLE_RATE]
[-T TOP] [-z MIN_SIZE] [-Z MAX_SIZE] [-O OBJ]
[interval] [count]
#
Licensed under the Apache License, Version 2.0 (the "License")
Copyright (C) 2016 Sasha Goldshtein.
//python引入的模块和包
from bcc import BPF
from time import sleep
from datetime import datetime
import resource
import argparse
import subprocess
import os
import sys
class Allocation(object):
def init(self, stack, size):
//定义init函数,self代表自身,
self.stack = stack
self.count = 1
self.size = size
def update(self, size):
self.count += 1
self.size += size
def run_command_get_output(command):
p = subprocess.Popen(command.split(), stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
//commadn.split命令,stdout值是PIPE,则表示需要创建一个新的管道,stderr值是STDOUT,表示子进程的标准错误也输出到标准输出。
//split命令可以将一个大文件分割成很多个小文件,有时需要将文件分割成更小的片段,比如为提高可读性,生成日志等。
return iter(p.stdout.readline, b'')
//iter() 函数用来生成迭代器。
def run_command_get_pid(command):
p = subprocess.Popen(command.split())
return p.pid
//返回进程p的pid
examples = """
EXAMPLES:
./memleak -p $(pidof allocs)
Trace allocations and display a summary of "leaked" (outstanding)
allocations every 5 seconds
跟踪分配,并每5秒显示“泄漏”(未完成)分配的摘要
./memleak -p $(pidof allocs) -t
Trace allocations and display each individual allocator function call
跟踪分配并显示每个单独的分配器函数调用
./memleak -ap $(pidof allocs) 10
Trace allocations and display allocated addresses, sizes, and stacks
every 10 seconds for outstanding allocations
跟踪分配,并每10秒显示未完成分配的地址、大小和堆栈
./memleak -c "./allocs"
Run the specified command and trace its allocations
运行指定的命令并跟踪其分配
./memleak
Trace allocations in kernel mode and display a summary of outstanding
allocations every 5 seconds
跟踪内核模式下的分配,并每5秒显示未完成分配的摘要
./memleak -o 60000
Trace allocations in kernel mode and display a summary of outstanding
allocations that are at least one minute (60 seconds) old
跟踪内核模式下的分配,并显示至少一分钟(60秒)以前的未完成分配的摘要
./memleak -s 5
Trace roughly every 5th allocation, to reduce overhead
大约每5次跟踪一次,以减少开销
"""
description = """
Trace outstanding memory allocations that weren't freed.
Supports both user-mode allocations made with libc functions and kernel-mode
allocations made with kmalloc/kmem_cache_alloc/get_free_pages and corresponding
memory release functions.
功能描述:跟踪未释放的未完成的内存分配。支持使用libc函数和内核模式进行的用户模式分配
使用kmalloc / kmem_cache_alloc / get_free_pages和相应的分配内存释放功能。
"""
parser = argparse.ArgumentParser(description=description,
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog=examples)
"""
parser模块为Python的内部解析器和字节码编译器提供了一个接口。该接口的主要目的是
允许Python代码编辑Python表达式的分析树并从中创建可执行代码。这比试图将任意Python代码
片段解析并修改为字符串更好,因为解析是以与形成应用程序的代码相同的方式执行的。它也更快。
"""
"""
argparse是一个Python模块:命令行选项、参数和子命令解析器。
argparse 模块可以让人轻松编写用户友好的命令行接口。程序定义它需要的参数,然后 argparse 将
弄清如何从 sys.argv 解析出那些参数。 argparse 模块还会自动生成帮助和使用手册,并在用户给程序传入无效参数时报出错误信息。
description - 在参数帮助文档之前显示的文本(默认值:无)
formatter_class - 用于自定义帮助文档输出格式的类
RawDescriptionHelpFormatter 用于定制description和epilog,默认情况下description和epilog是自动换行的。
epilog - 在参数帮助文档之后显示的文本(默认值:无)
"""
parser.add_argument("-p", "--pid", type=int, default=-1,
help="the PID to trace; if not specified, trace kernel allocs")
根据PID去追溯;如果没有指定,则跟踪内核分配
//添加参数,定义程序需要的参数以及其默认值
'''
名字,type参数类型,default默认值,help:参数描述,metavar
选项字符串的名字 pid值,类型int,默认-1
'''
parser.add_argument("-t", "--trace", action="store_true",
help="print trace messages for each alloc/free call")
//action="store_true",存储True的值
打印每个alloc / free调用的跟踪消息
parser.add_argument("interval", nargs="?", default=5, type=int,
help="interval in seconds to print outstanding allocations")
//nargs - 应该读取的命令行参数个数,可以是具体的数字,或者是?号
打印未完成分配的时间间隔(以秒为单位)
parser.add_argument("count", nargs="?", type=int,
help="number of times to print the report before exiting")
退出前打印报表的次数
parser.add_argument("-a", "--show-allocs", default=False, action="store_true",
help="show allocation addresses and sizes as well as call stacks")
显示分配地址和大小以及调用堆栈
parser.add_argument("-o", "--older", default=500, type=int,
help="prune allocations younger than this age in milliseconds")
以毫秒为单位删除小于此年龄的分配
parser.add_argument("-c", "--command",
help