项目上需要分析用户态程序的性能,开发人员一般的方式是在程序内部实现打点函数,记录当程序运行到该点的时间戳,通过比较两点之间的时间间隔来估计两点之间的时间消耗。这样一方面增加了开发的工作量,另外这些打点也会给业务带来额外性能消耗,是否有另外的方式来解决该问题呢?
前段时间在研究ftrace,发现其还有个uprobe特性,故名思意就是用户态的探针工具,今天尝试了下uprobe的使用,小结如下:
1.编写小测试程序如下:
#include <stdlib.h>
#include <stdio.h>
int count = 0;
void do_sth()
{
printf("current count = %d\n", count);
count++;
}
int main(int argc, char* argv[])
{
while(1)
{
do_sth();
}
return 0;
}
2.运行测试程序并找到需要probe的函数符号偏移量
root@X200:/home/kernel_test# objdump -t loop_print | grep do_sth
000000000040052d g F .text 000000000000002c do_sth
root@X200:/home/kernel_test# ./loop_print 1>/dev/null &
[1] 28481
root@X200:/home/kernel_test# pgrep loop_print
28481
root@X200:/home/kernel_test# man pgrep
root@X200:/home/kernel_test# cat /proc/28481/maps | grep loop_print | grep r-xp
00400000-00401000 r-xp 00000000 08:01 2885775 /home/kernel_test/loop_print
3.配置uprobe事件
echo 'p:do_sth /home/kernel_test/loop_print:0x52d %ip %ax' > /sys/kernel/debug/tracing/uprobe_events
echo 'r:do_sth_exit /home/kernel_test/loop_print:0x52d %ip %ax' >> /sys/kernel/debug/tracing/uprobe_events
uprobe的事件格式如下:
p[:[GRP/]EVENT] PATH:OFFSET [FETCHARGS] : Set a uprobe
r[:[GRP/]EVENT] PATH:OFFSET [FETCHARGS] : Set a return uprobe (uretprobe)
-:[GRP/]EVENT : Clear uprobe or uretprobe event
GRP : Group name. If omitted, "uprobes" is the default value.
EVENT : Event name. If omitted, the event name is generated based
on PATH+OFFSET.
PATH : Path to an executable or a library.
OFFSET : Offset where the probe is inserted.
FETCHARGS : Arguments. Each probe can have up to 128 args.
%REG : Fetch register REG
@ADDR : Fetch memory at ADDR (ADDR should be in userspace)
@+OFFSET : Fetch memory at OFFSET (OFFSET from same file as PATH)
$stackN : Fetch Nth entry of stack (N >= 0)
$stack : Fetch stack address.
$retval : Fetch return value.(*)
+|-offs(FETCHARG) : Fetch memory at FETCHARG +|- offs address.(**)
NAME=FETCHARG : Set NAME as the argument name of FETCHARG.
FETCHARG:TYPE : Set TYPE as the type of FETCHARG. Currently, basic types
(u8/u16/u32/u64/s8/s16/s32/s64), "string" and bitfield
are supported.
(*) only for return probe.
(**) this is useful for fetching a field of data structures.
其中FETCHARGS是每次执行到probe点的时候需要抓取的数据,其输出格式在/sys/kernel/debug/tracing/events/uprobes/do_sth/format中定义。
4.启动用户态探针
echo 1 > /sys/kernel/debug/tracing/events/uprobes/enable
5.查询探针结果
cat /sys/kernel/debug/tracing/trace | less
# tracer: nop
#
# entries-in-buffer/entries-written: 72053/1766702004 #P:2
#
# _-----=> irqs-off
# / _----=> need-resched
# | / _---=> hardirq/softirq
# || / _--=> preempt-depth
# ||| / delay
# TASK-PID CPU# |||| TIMESTAMP FUNCTION
# | | | |||| | |
loop_print-28481 [001] d... 5590.582582: do_sth: (0x40052d) arg1=0x40052d arg2=0x0
loop_print-28481 [001] d... 5590.582585: do_sth_exit: (0x400572 <- 0x40052d) arg1=0x400572 arg2=0x4df27b17
loop_print-28481 [001] d... 5590.582586: do_sth: (0x40052d) arg1=0x40052d arg2=0x0
loop_print-28481 [001] d... 5590.582589: do_sth_exit: (0x400572 <- 0x40052d) arg1=0x400572 arg2=0x4df27b18
loop_print-28481 [001] d... 5590.582590: do_sth: (0x40052d) arg1=0x40052d arg2=0x0
loop_print-28481 [001] d... 5590.582593: do_sth_exit: (0x400572 <- 0x40052d) arg1=0x400572 arg2=0x4df27b19
loop_print-28481 [001] d... 5590.582594: do_sth: (0x40052d) arg1=0x40052d arg2=0x0
loop_print-28481 [001] d... 5590.582597: do_sth_exit: (0x400572 <- 0x40052d) arg1=0x400572 arg2=0x4df27b1a
loop_print-28481 [001] d... 5590.582598: do_sth: (0x40052d) arg1=0x40052d arg2=0x0
loop_print-28481 [001] d... 5590.582600: do_sth_exit: (0x400572 <- 0x40052d) arg1=0x400572 arg2=0x4df27b1b
loop_print-28481 [001] d... 5590.582602: do_sth: (0x40052d) arg1=0x40052d arg2=0x0
loop_print-28481 [001] d... 5590.582604: do_sth_exit: (0x400572 <- 0x40052d) arg1=0x400572 arg2=0x4df27b1c
从以上输出可以看出进入函数和退出函数的时间轨迹。另外可以通过修改用户探针命中时指定的行为FETCHARGS来获取更多信息,比如观测全局变量变化等。
后注:
尝试了下uprobe的事件是否能和内核事件同时启用,发现启用一个另一个会自动关闭,因此无法看到进程包含核心态及用户态整体运行时间轨迹。