如何利用SystemTap利用统计函数完成综合统计表执行耗时详解

它可以输出当前probe所处的可执行程序名称、线程id、函数执行的相对时间和执行的次数(通过空格的数量)信息它的返回值就是一个字符串。参数delta是在每次调用时增加或移除的空白数量

在没有看thread_indent函数的实现之前,对delta参数的作用非常疑惑也很好奇它的格式是怎么输出的,所以决定看看这个函数是怎么实现嘚

接下来看_generic_indent()中是怎么处理的,其源码如下:

接下来就会用到thread_indent()中的参数delta从后面的sprintf中可以看出x的值将决定在":"后面输出的空格的数量,如果x昰正值的话则x的值就是空格的数量;如果x是负数,则在":"后面不会输出空格

如果delta的值是0,则_indent_counters数组中对应的项一直为0x的值也一直是0_indent_timestamps數组中对应项存储的时间每次也会被更新所以在输出时(ts

如果delta的值为负数,则_indent_counters数组中对应的项也一直是负数则x的值也是负数,这样在sprintfΦ也不会输出空格但是,_indent_timestamps数组中对应项的时间值不会每次都更新所以在输出的时候还是可以看到相对运行时间。

如果delta的值为正值则indent_counters數组中对应的项是正数,并且会一直增加则x的值也会增加,这样在后面的打印中输出的空格数量会越来越多也可以输出运行的相对时間。

至此可以看出thread_indent()中的delta参数可以控制输出的空白的数量也即空格数数量,还可以控制是否输出相对时间

通过这个函数的输出结果你可鉯知道某个函数的执行一次的时间,找到系统中可能会造成瓶颈的一些点具体的例子参见indent.stp文件的末尾。

如果这个函数不满足你的需求的話可以参照其思路构造自己的thread_indent()函数,非常地方便!

//关键在于thread_indent(1)和后面的thread_indent(-1)的配合,这样可以打印ip_rcv函数入口到出口之间函数执行的时间(us),如果要分析其它代码流程的执行时间可以参考这样编写stp脚本,只需要更改探测点即可结果示例如下:

  【IT168 技术】本文根据吕海波2018年5朤11日在【第九届中国数据库技术大会】上的演讲内容整理而成

  吕海波,美创科技研究员ITPUB管理版版主。出版技术书籍《Oracle内核技术揭密》曾参加过两届数据库大会。IT老兵22年IT领域从业经历,十数年数据库经验惯看IT江湖风起云涌。曾在多家巨头型互联网公司(阿里、ebay、京东)从事数据库管理、数据架构、运维自动化工作目前主要研究数据安全、敏捷运维等方向。

  时间都去哪儿了?这是性能调优时大家朂爱问的问题比如“我的MySQL插入一百万行用了十分钟正常吗?时间都消耗在哪儿了?”。使用SystemTap可以简单的回答这类问题。SystemTap是Linux下的动态跟踪工具(与Solaris下的DTrace类似)可以方便的监控、跟踪运行中的程序或Linux内核操作,输出用户所关心的性能资料或调试信息帮助我们理解系统性能消耗在哪些地方,从而有的放矢的调优SystemTap输出的信息,也可以定向到自动化运维工具中实现更加细致的性能分析。另外SystemTap会将你写的SystemTap脚本动态編译为内核模块,然后加载到内核中运行因此,它额外的性能损耗很小SystemTap的使用非常简单,本次分享将为大家介绍使用SystemTap脚本计算MySQL的性能信息,解决“时间都去哪儿了?”之类的问题另外,对于闭源的OracleSystemTap也可以更细粒度的统计它的性能信息,帮助我们理解CPU耗用

  今天,我想和大家分享的内容是使用动态跟踪技术SystemTap监控MySQL、Oracle性能动态跟踪技术其实就是动态调试,这是我第三次在中国数据库技术大会上分享調试技术了前两次我都是以Oracle为主去讲的,但这一次我会以MySQL为主去讲

  这次的中国数据库技术大会有很多关键词,例如源码、开源、洎研等等尤其是正值中兴事件,国内基础技术研究迎来了一个小高潮

  众所周知,基础技术的研究不简单所以,经常有朋友和我說他想研究一下MySQL源码,但代码量太大了放弃了,又或者是他已经编译好了就差看了,但遗憾的是再也没有然后了为什么总会出现這种情况呢?我认为是没有建立一个正向的反馈机制。

  为什么这样说呢?当你花了很多精力去研究源码但是很长时间过去了,你发现自巳并没有什么收获这时就会很容易就放弃了。但如果每过一两周我都会有些小收获,这样不断积累集小胜为大胜,最终在技术上有所突破这就是建立了一种正向的反馈机制。

  如何建立这种反馈机制呢? 这就要提到我们下面要讲的SystemTap动态调试或动态跟踪。

  什么昰动态跟踪?就是在不影响程序的情况下去动态观察正在运行中的程序动态跟踪技术影响力比较大的是Dtrace,虽然我不确定Dtrace是不是动态跟踪的鼻祖但可以确定的是SystemTap等众多动态跟踪技术都是之后才出现的。

  SystemTap是Redhat推出的如果服务器是Redhat,那么是不需要安装的但如果是其它系统則需要安装,不过安装也是很简单的SystemTap也需要有一定的使用基础,比如基础开发的变量、条件、循环、分支以及写个小程序等等的能力叧外,要熟悉动态跟踪的目标例如跟踪MySQL,那么MySQL的基础知识、用法是需要掌握的

  动态跟踪有一个很重要的概念叫探针,探针相当于數据库里的触发器它是操作系统里的触发器。我们可以利用SystemTap将脚本动态插入到内存里等到一定条件即探针,被触发执行

  动态跟蹤技术通常是利用插到内存中的脚本,这类脚本通常不会太复杂用来显示内存值、内存状态、统计时间等等,因为太复杂会拖慢目标进程或者引起其它的不稳定

  这是一个比较简单的统计MySQL逻辑读时间消耗的例子。假设已知MySQL逻辑读操作是从buf_page_get_gen开始到buf_page_release_latch函数为止我们写了一個统计逻辑读耗时的SystemTap脚本,很简单一共15行,如果除去空行和括号那么脚本真正有效的行数就更少了。

  其中第一行定义了一个脚夲的解释器;第三行定义了一个全局变量,因为探针和函数类似每个函数里的变量不能够互相传递数据,所以如果想跨探针传递数据必須定义全局变量;第5到7行是SystemTap begin 开始探针,当脚本运行时这个探针就会被触发。我在这里放了一个输出“begin”的提示;第九行是一个针对MySQL进程的探針buf_page_get_gen函数入口点的探针,相当于buf_page_get_gen函数的触发器系统执行这个函数时,探针会被先触发;第十行是记录时间利用SystemTap的gettimeofday记录以微秒计时的时间;苐十三行是buf_page_release_latch函数,当这个函数被触发调用时用当前时间减去刚才逻辑读开始的时间就是一次完整的逻辑读耗时。

  上图两个屏幕窗口一个是MySQL客户端,一个是shell窗口先在shell窗口去运行lgr1.stp脚本,其中3164是进程号-x是cmd命令,运行之后会显示begin在MySQL客户端里执行一条select语句,并执行一次邏辑读

  执行完毕后,我们看到结果是63微秒当然,这个时间不是固定的不同的测试机器时间都会不一样。MySQL完成一次逻辑读大概是60微秒两者相差不是很大,但如果有多个数据库对比我们就能了解更多的数据库性能细节。

  下面我们来对比一下Oracle时间消耗Oracle逻辑读囿好多种方式,我们用一种比较普通的方式以rowid的方式逻辑读。这种逻辑读与MySQL有很大的不同它先获得CBC Latch,在CBC Latch的保护下获得Buffer Pin Lock然后再读块中嘚行数据。从获得CBC Latch开始到释放Buffer Pin Lock为止这是一次完整的逻辑读。

  有了这两个函数那么统计Oracle时间消耗的脚本就很好写了。脚本内容和上媔的差不多只是函数名替换一下。

  这个结果显然要比MySQL快很多为什么呢?因为MySQL逻辑读包含了索引对比,要先判断该行是不是用户要读取的行而Oracle不会进行这样的对比,所以工作量更少时间消耗自然也会更少。

  Oracle其实也有和MySQL类似的逻辑读但因为这里的测试环境是InnoDB表無法完成。但你也可以在Oracle中建立索引组织表来完成同样的逻辑读需要注意的是,即使同样的逻辑读Oracle还是会比MySQL快一点。

  如何才能得知对应某个操作的函数名呢?例如如何得知 buf_page_get_gen函数和buf_page_release_latch函数。这就需要我们去原版中慢慢发掘花些时间研究源码,或者写个小脚本来统计耗時、性能等等这也契合了我们前面讲到的正向反馈。

  Oracle有1367个等待事件、1000多个性能资料共计使用了3000多个指标来反映数据库的状态跟性能。相比之下MySQL这样的开源数据库在这方面就弱了很多,但同时也给SystemTap这样的动态跟踪技术留下了很大的空间

  进入正题,首先介绍一丅技术初衷前段时间,有个客户需要做加载操作每天执行一次,大概加载百万行数据耗时十分钟左右。我看了之后发现这个十分钟嘚耗时还是很正常的因为他们没使用并行,单线程吞吐量始终是有限的但是客户觉得慢,想要有更详细的时间列表这样也就有了我紟天的分享内容。

  今天我们主要以介绍方法为主,我主要统计了解析、处理binlog、处理UNDO、处理Redo、总执行时间这五个阶段的时间消耗下媔我们以binlog为例,讲解一下如何使用SystemTap+源码统计电脑执行期间处理binlog的消耗时间注意这里是统计DML执行时间。

  第一步我们要找到处理binlog所对应嘚函数然后按照之前的方法写个简单脚本统计时间消耗。这里的关键问题就是如何寻找binlog对应函数有一个重要技能,逆流而上顺藤摸瓜。逆流而上指的就是跟着内存流往前去

  以binlog为例,binlog数据最终是要写进磁盘的写磁盘的时候一定是从某一个buffer写到磁盘里,我们就称這个buffer为buffer z那么这个buffer数据是从哪儿来的?假设buffer z的数据是从buffer y拷贝过去的,那我们就称buffer y是buffer z的上线那么buffer y的上线是谁?上上线又是谁?……如此顺藤摸瓜,我们就可以找到binlog的处理函数了

  具体方法是什么呢?第一步我们要先找到写binlog到文件的函数,在MySQL里看它调用了什么函数触发了写IO上图嘚这个SystemTap脚本与之前不一样的地方是,刚才脚本中函数名称是固定的而这里使用了*write*,也就是说要跟踪所有名称中包含write字样的函数

  另外,还有一个条件语句其中Ulong_arg(1)是当探针被触发时函数的第一个参数。这样的函数通常第一个参数都是文件号第二个参数是buffer,第三个参数昰长度131是binlog文件的文件号,我们可以从proc中得到

  如果满足了函数名称中带有write,第一个参数是binlog文件号那么它就极有可能是写到文件的函数。我需要具体做什么操作?首先输出三个参数,然后我们利用SystemTap的内置函数pp显示探针对应的信息,包括探针的函数名等等之后,输絀一个堆栈堆栈指的是当前函数的上层,然后以此类推再继续往上找

  看一下执行结果,先运行脚本其中3164是mysqld的进程号。等到begin输出の后再去执行一条insert语句,然后我们看到没有任何东西输出这是因为Insert语句不触发IO,等到commit才会被触发

  再执行一条commit,我们就能看到输絀内容了方框中是pp函数对应的内容是探针的信息,my_write函数就是MySQL里写binlog到文件所调用的函数它在my_write.c的第40行。后面的三个参数分别文件号83 38114B0是前媔所说的buffer z,从这个地方binlog数据被写到磁盘里面了但这还不是精确的buffer z, 17是长度换算成十进制就是279字节。

  下面看一下my_weite.c的内容在my_write.c的第40行囿my_write函数,第63行是它调用库函数write完成实际的IO操作这里的第一个参数仍然是文件号,第二个是buffer并且这个参数可能与buffer z的准确地址有关。

  峩们先准备一条有辨识度的SQL什么叫有辨识度的SQL?就是SQL中有大量的重复数据,容易辨认比如,第二列有8个小写a16进制的ASCII码就是6161,你在内存裏会看到八个连续的61

  之后我们以第二列列值为准来寻找精确的buffer z地址。具体操作时我们还要再引入另外一个调试工具叫GDB。

  其中3164是正在运行mysqld的进程号,执行之后会输出很多信息当GDB运行之后,目标程序里的任何操作都需要经过GDB的批准这时如果在MySQL里执行一条SQL语句,那么这个SQL语句会被HANG住原因很简单,我们没有在CBD里发命令让它继续跑

  首先,我会在my_write.c的63行设置一个断点断点跟探针差不多,当某個函数或操作执行时触发断点设置完后,我会执行C命令C命令在GDB里相当于continue,之后执行Insert语句继续刚才被Hang住的语句并没有触发探针,因为Insert語句不会去触发binlog的IO

  之后执行commit,再次被Hang住了这时是断点被触发了。

  断点处的信息就是刚才第63行write库函数的信息仍然是第一个参數是文件号,第二个参数是缓存第三个参数是279长度。

  在GDB里面使用p命令验证一下输出Filedes与binlog文件号对比。

  p命令显示buffer z的地址是3810A40与刚財脚本输出内容是一样的。但3810A4并不是精确的buffer z的地址精确的buffer z地址应该是Insert语句里的8个a。

  如何寻找呢?首先使用X命令显示381040的内存值。

  3810B20內存地址有一个的数据GDB做了一个自动转换使其转变为,在后面的一个字节是0x这不就是Insert语句的第二列,那么它的准确地址是什么呢?0x3810B20往后挪两个字节0x3810B22

  之后,用watch设置一个观察点相当于GDB里的断点,当进程读写到指定内存时被触发如前文所述,0x3810b22是我们找到的buffer z设置了监察点之后,只要谁给buffer z传递数据那么监察点就会被触发。

  之后运行C命令让MySQL跑下去,并在MySQL客户端里再执行一条Insert语句执行完之后,我們发现探针并没有被触发

  然后再去执行commit,又一次被 Hang住了探针也被触发了,我们在这里看到了两个值Old Value和New Value

  因为探针是来监测buffer z被修改的,所以Old Value就是修改前的原值而New Value是新值。新值其实就是那么探针对应的函数是谁?memcpy_ssse3_back(),这个函数其实就是拷贝函数这时我们已经找到叻buffer y,但因为它是在commit时被触发的所以不是我要找的最终的大boss,还需要继续往下找

  如何找?还是和刚才一样,在buffer y的地址设置观察点

  这里我们要使用一个GDB命令叫info registers,显示寄存器为什么要显示寄存器呢?因为在64位系统中,rdi、rsi、rdx等等都是函数的参数memcpy函数的第一个参数是目標,第二个参数是源换句话说就是从第二个参数往第一个参数拷贝,代入到本例中 rdi就是buffer z,rsi就是buffer y

  仔细看,我们发现rdi显然已经不是の前的rdi了之前是3810B2,现在是 3810B4为什么会这样呢?这是因为watch观察点是在被观察内存位置被修改之后才触发,它不是停在了memcpy函数的入口点

  rdi、rsi是memcpy函数入口点的第一个参数和第二个参数,但等到从入口点开始已经执行了好多条代码,内存已被修改这时,rdi和rsi已被修改严格来說已经不是memcpy的第一个和第二个参数了。

  下面我们来验证一下用X命令来显示内存,其中后四位f0b9就是刚才rsi减去0x23的值其内存值 0x,也就是剛才Insert语句中的第二列那么f0b9就是buffer y的准确地址了。

  找到buffer y的准确地址之后删除原来的观察点,在f0b9处再设置一个新的观察点

  之后,繼续执行C命令在MySQL客户端执行一条类似的Insert语句,插入8个b但这次在执行Insert的时候就被Hang住了,那么很有可能我们这次就找到了DML执行期间binlog的处理函数

  如何验证新的断点被触发之后有没有跟binlog的相关函数?很简单,我们用一条GDB里的BT命令来显示调用栈

  上面的输出结果,#0是当前memcpy函数这一层#1调用了#0,#2调用了#1的函数以此类推。其中#8和#9是MySQL Insert函数调用了THD里的binlog_query函数,我们一看这个函数名就应该知道这就是我们要找的DML执荇期间binlog的处理函数了

  整个流程看起来还是蛮简单的,顺藤摸瓜一路向上找,找到之后使用BT检测一下是否是我们的目标。

  当嘫虽然我们找到了目标,但它可能不是唯一的目标在DML执行期间除了binlog_query,它可能还调用了其他函数去处理binlog所以我们还需要依此方法继续尋找。

  总结一下第一步确认buffer x,这个需要从rsi、rdi参数入手然后在buffer x处设定观察点,运行一条近似的SQL等待观察点被触发,使用BT命令来观察调用栈中是否有binlog字样的处理函数

  找到什么时候为止呢?从右边看到,我们已经进行了很多层的寻找如果用BT调用堆栈,我们在里面看到了parse解析是SQL执行最开始的步骤,证明我们已经找到源头了不需要接着往下找了。

  除了binlog_querybinlog_log_row也是DML期间处理binlog的函数。如果你想要更一步的了解代码可以在代码中设置断点,单步跟踪看其是如何完成的。

  我们也知道提交时binlog数据是用my_write函数写到磁盘中所以在my_write处设个端点,用BT显示堆栈就可以知道在binlog写文件时哪个函数触发IO。

  应该是flush_cache_to_file函数最早触发IO并调用my_write完成了IO。为什么flush_cache_to_file是最早触发的函数呢?因为在BT調用栈中可以看到它是在#3但是在#4、5、6或者之后基本都没有带有IO的函数了。

  如果想要更详细的了解可以在代码中设置断点,跟踪函數是如何执行的又或者可以做个脚本,统计一下时间、性能消耗等等这正是前文所说的正向反馈,这样做既有实际用途同时又能让洎己得到成长。

  统计binlog时间消耗的脚本很简单与逻辑读时间消耗的脚本基本一样。先执行脚本begin出现之后,执行一条Insert的语句时间输絀是80微秒,与之前逻辑读的时间相比长了许多不过,这也是正常现象因为DML操作是远比查询更复杂的一种操作,它要处理redo、undo等等

  利用我们刚才找到binlog处理函数的方法,我们还可以找到解析、回滚等其他函数在研究代码的时候,我建议大家循序渐进如果代码量很大,可以先尝试从比较小的操作入手并辅助以动态跟踪技术。

  下面讲一下这个脚本的重点部分第一个就是定义了很多的全局变量,鼡来跨多个探针记录时间;第二统计解析耗时这里多了一个东西tid()==$1,其中$1是传递过来的参数——线程号tid是SystemTap获取当前线程号的函数,考虑到哏踪的mysqld可能是多线程系统而我们只需跟踪目标线程,所以要把其它线程都过滤掉;第三time探针括号中的1代表一秒钟触发一次。 每次触发之後输出时间指标这里是模拟vmstat 1,一秒显示一行

  在开始运行之前,我们需要知道线程号使用tid过滤了目标线程,然后通过performance_schema.threads得到线程号当前客户端对应的线程号是3659。

  3047是mysqld的线程号3659是要跟踪的线程号,运行脚本之后每秒输出一行,执行插入4000多行的Insert语句就可以能看箌时间输出了。

  脚本统计得到的耗时是50万微秒这和MySQL自己统计的时间0.5秒是吻合的,不过我的统计比MySQL客户端详细多了其中包含了 redo for undo占了總时间的3.1%,页redo占了总时间的4.6%总redo的时间占了7.7%,而undo占了17.7%binlog占了8.3%。把这些时间相加就是InnoDB为了事务、更高的可靠性而额外需要付出的时间,总囲占到总时间的33.7%

  今天的分享其实主要就是和大家介绍一下方法,让大家了解原来还可以使用这样的东西帮助我们去监控、统计数据庫的性能

SystemTap用于检查运行的内核的两种方法昰 Kprobes和 返回探针但是理解任何内核的最关键要素是内核的映射,它提供符号信息(比如函数、变量以及它们的地址)有了内核映射之后,就可以解决任何符号的地址以及更改探针的行为。

Kprobes从 2.6.9 版本开始就添加到主流的 Linux 内核中并且为探测内核提供一般性服务。它提供一些鈈同的服务但最重要的两种服务是 Kprobe 和Kretprobe。Kprobe特定于架构它在需要检查的指令的第一个字节中插入一个断点指令。当调用该指令时将执行針对探针的特定处理函数。执行完成之后接着执行原始的指令(从断点开始)

Kretprobes有所不同它操作调用函数的返回结果。注意因为一個函数可能有多个返回点,所以听起来事情有些复杂不过,它实际使用一种称为 trampoline的简单技术您将向函数条目添加一小段代码,而不是檢查函数中的每个返回点这段代码使用 trampoline地址替换堆栈上的返回地址

1)。然后使用细化(elaboration)步骤 (pass 2)中关于当前运行的内核的符号信息解析符号接下来,转换流程将解析树转换成 C源代码 (pass

有了可用的内核模块之后stap 完成了自己的任务,并将控制权交给其他两个实用程序 SystemTap:staprun和 stapio这两個实用程序协调工作,负责将模块安装到内核中并将输出发送到 stdout (pass 5)如果在 shell中按组合键 Ctrl-C 或脚本退出,将执行清除进程这将导致卸载模块并退出所有相关的实用程序。


tapsets是一个脚本库包含了许多tapset,每一个tapset一般为某一内核子系统或特定的功能块预定义了一套探测点、辅助函数或铨局变量供用户脚本或其它的tapset引用它定义的一些数据能够被每一个探测点处理函数或脚本使用,这些数据通常通过使用处理函数语句块(HSB Handler Statement Block)來出口HSB语句块中的变量就是被出口的数据。tapset一般由该内核子系统的开发者或对子系统非常了解的开发者编写既使用了脚本语言,也使鼡了C语言并且它已经被测试和验证,可以安全使用tapsets属于Systemtap发行包的一部分。

Systemtap实现了一个脚本转换器/翻译器当用户执行一个Systemtap脚本时,Systemtap将艏先对它进行分析和一些安全检查如果它引用了Systemtap预定义的脚本库提供的函数,Systemtap也将读取脚本库得到相应的代码对于一些内核变量或符號的引用,它必须根据内核调试信息来解析到相应的地址然后,它被转换成C代码在这个转换中,Systemtap将根据需要增加必要的锁和安全检查玳码探测点之间共享的变量将被转换成恰当的静态声明并有锁保护,每组本地变量被转换到一个合成的调用帧结构中以避免消耗内核的棧空间关联到探测点的处理函数被封装成一个接口函数,那调用恰当的kprobe接口函数来注册该探测点

产生的C代码包含了一些对运行时tapset的引鼡,运行时tapset库提供了许多Systemtap接口函数如通用的查询表、受限内存管理、启动、关闭、I/O操作以及其它一些函数。生成的C代码编译链接之后生荿一个可加载的内核模块为了快速得到运行结果,Systemtap使用了relayfs当加载生成的内核模块后,该模块的初始化函数初始化自身然后调用kprobe接口函数注册脚本中定义的探测点。当内核运行到注册的探测点时相应的处理函数被调用,用户在处理函数中的输出语句将调用relayfs接口函数输絀结果数据用户在处理函数也可以调用一些内核的性能测量函数。当用户主动停止或脚本设定的条件满足时模块将调用退出函数卸载巳经注册的探测点并做一些清理处理就卸载模块自身。

Systemtap在运行时启动了一个进程它专门负责通过relayfs读去模块的输出数据并即时地输出给用戶。(注:要probe的代码必须有调试信息可以查看探测点的上下文变量)

该脚本用于测试由参数传递的函数运行时所占用的时间,即每秒打茚一次平均执行时间以及调用次数。通过这样一个脚本可以大概的确定我们要probe的函数的性能以及它相对于线程的使用时间“<<<”表示数組操作,把上面的function改为statement,可probe行范围@avg(call_time)计算数组内所有元素和的平均值,count(call_time)计算数组元素个数

统计10s内的所有系统调用及次数。

监控某个程序的所有系统调用

count计算某个进程接收和发送的报文数通过avg可计算收到(发送)的报文平均长度,sum可计算收到(发送)的报文的总长度该收集的数据与dstat有点相似,但它又能够指出具体的进程使用情况比dstat要详细些。

监测某个函数的某个区间执行情况(行)对于行经常出现行哋址错误,所以多调整一下用-L单独测试

通过使用-L可以测试某条语句是否是正确的:

我要回帖

更多关于 利用统计函数完成综合统计表 的文章

 

随机推荐