当前位置: 首页 > 科技观察

一篇文章获取Perf和Gpertools

时间:2023-03-12 01:42:25 科技观察

本文转载请联系味小姐公众号。在Linux下开发是幸福的,尤其是当出现问题时。我永远不会忘记在Windows下应用程序出现问题时那种无助的感觉。Java提供了很多工具来处理故障排除和性能分析,例如jstat、jmap、jmc等。但大多数情况下,资源瓶颈在操作系统上。如果宿主机不能正常工作,在其上进行各种Java应用分析将变得毫无意义。这就是没有皮肤就没有头发的原因。做性能分析,Linux下有一个非常好用的工具,叫做perf。几乎每个发行版都有它的安装包。Perf诞生于2009年,是一个内核级工具;另外还有一个工具叫gperftools,是Google的产品,是应用级的产品。虽然都有perf这个词,但是使用场景和处理的问题也是不一样的。1.perf:CPUinflationtroubleshootingperf顾名思义就是用来做性能分析的。perf支持两种模式,计算模式和采样模式。比如perfstat使用的是计算方式,而perfrecord使用的是采样方式。以sampling为例,它的原理是这样的:每隔固定的时间,产生一个中断,然后统计对应的pid和function。采样并不能预测实际发生的事情,但是函数运行时间越长,被时钟中断的可能性就越大。鉴于perf最终显示统计数据,它的测量结果可以非常有信心。可以通过包管理工具轻松获取Perf。比如centos下,直接通过yuminstallperf安装。perf提供了很多命令,我们可以直接输入perf来输出这些选项。perf有很多功能,常用的有perflist、perfstat、perftop、perfrecord、perfreport等,这里举几个常用的例子来说明它的应用场景。使用以下脚本可以让某个核心的CPU占用率飙升到100%。cat/dev/zero>/dev/null使用如下脚本消耗CPU资源。(先获取cpu的核数,然后循环生成任务)。该脚本向/dev/null输出数据,因此只占用CPU资源,不占用任何I/O资源。foriin`seq1$(cat/proc/cpuinfo|grep"physicalid"|wc-l)`;doddif=/dev/zeroof=/dev/null&done让我们以下面的脚本来说明情况。从top的截图可以看到sy和ni的占用都达到了100%,我的脚本成功了!(温馨提示:执行脚本后,如果想杀掉这些进程,除了重启,也可以直接通过ps找到对应的进程,然后使用kill命令终止)。接下来使用record命令记录CPU使用率。从以上描述可知,统计结果为抽样结果。<>#perfrecord-a-ecycles-operf.perf-gsleep10[perfrecord:Wokenup55timestowritedata][perfrecord:Capturedandwritten14.282MBperf.perf(160302samples)]程序会运行10秒,然后输出采样结果到perf.perf文件。可以通过report命令显示统计结果。perfreport-iperf.perf可以看到CPU损耗大部分是dd命令造成的,甚至里面的调用树也能清晰显示。这在调试一些用C++语言编写的程序,或者调试jvm的一些内部行为时非常有用,因为它可以直接跟踪到系统调用级别。但是对于一些细节,如果对Linux内核不是很了解的话,上手会比较困难。所以一般情况下,我们只能粗略的定位出问题的模块,然后深入调试。perf还可以通过指定进程号来跟踪性能以获得性能数据。perftop-g-p23432。示例代码在了解了perf的基本用法之后,我们举一个在实践中经常遇到的例子来说明perf的使用。堆外内存是通过JNI等类库调用产生的内存,在实际排查中很难定位。包括JMC在内的传统工具无法快速有效地找到问题的根源。当钱路在他的比赛结束时,通常是perf上场的时间。为了演示这个过程,我特意做了一个非常精巧的JAVA代码。代码片段比较长,可以访问下面的gist链接,下面只解释关键代码。这段代码是一个典型的堆外内存泄漏问题。https://gist.github.com/lycying/70ff3897d8516011c7ffc702aa0d03c2使用com.sun.net.httpserver.HttpServer自带的simpleserver,可以非常简单的搭建一个server,我们可以通过requests改变一些应用的行为。使用以下JVM参数启动此代码。java-Xmx1G-Xmn1G\-XX:+AlwaysPreTouch\-XX:MaxMetaspaceSize=10M\-XX:MaxDirectMemorySize=10M\-XX:NativeMemoryTracking=detailLeakExample程序会人为制造暂停状态,具体测试步骤如下。当程序运行时间较短,内存使用率很快达到60%时,程序会自动挂起并启动perf进行采样,相当于出现问题时切入(perfrecord-g-p$pid)访问http://localhost:8888端口,内存阈值会提高到85%,内存会很快达到这个状态停止采样,生成perf数据,使用perfreport(perfreport-iperf.data),这将导致下图。但是我们无法从中获取有用的信息。方法不对?是的。要对内存进行采样,可以使用perfmemrecord命令,但该命令在大多数机器上不起作用,获取的信息有限。perf记录了CPU的性能数据,这里特别说明一下。只要是5%的使用率,我一般都会关注。一般情况下,占用的cpu时间片越多,证明使用的内存越多。但总有例外,比如频繁申请一个1bytemethodblock,和申请一次性1MBmethodblock是不一样的。所以perf能不能发现内存问题就看运气了。3.gperftools:找到堆外内存的罪魁祸首要找到内存问题,我们需要用到google的gperftools,我们主要使用它的HeapProfiler,非常强大。https://github.com/gperftools/gperftools有一种特殊的启动方式。安装成功后,只需要输出两个环境变量即可。mkdir-p/opt/testexportLD_PRELOAD=/usr/lib64/libtcmalloc.soexportHEAPPROFILE=/opt/test/heap在同一个终端,再次启动我们的应用,可以看到申请内存的动作都记录在test下选择目录目录。接下来,我们就可以使用pprof命令来分析这些文件了。cd/opt/testpprof-text*heap|head-n200使用这个工具,一眼就能追踪到内存最多的函数。立即找到函数java_java_util_zip_Inflater_init。这就是模拟内存泄漏的全过程,问题解决。GZIPInputStream使用Inflater申请堆外内存,Deflater释放内存,调用close()方法主动释放。如果忘记关闭,Inflater对象的生命会一直持续到下一次GC,有点像堆中的弱引用。在此过程中,堆外内存会不断增长。问题发生在我们的解压缩功能上。使用时,忘记关闭流。我们可以看看异常情况和正常情况的区别。这部分是忘记关闭流的功能。这在编码中经常发生。publicstaticStringdecompress(byte[]input)throwsException{ByteArrayOutputStreamout=newByteArrayOutputStream();copy(newGZIPInputStream(newByteArrayInputStream(input)),out);returnnewString(out.toByteArray());}下面是修改后的正常函数。publicstaticStringdecompress(byte[]input)throwsException{ByteArrayOutputStreamout=newByteArrayOutputStream();GZIPInputStreamgzip=newGZIPInputStream(newByteArrayInputStream(input));try{copy(gzip,out);returnnewString(fin.toByte}Array.z();ally{)关闭();}catch(Exceptionex){}try{out.close();}catch(Exceptionex){}}}4、题外使用pprof,还可以输出图形分析报告,需要安装图形生成工具graphviz,可以说是非常不错了。还有一点不得不提的是,虽然perf和gperftools对性能的影响不是特别大,但是线上环境尽量不要使用。根据我的实际使用经验,性能损失率在30%左右。如果你的问题可以重现,用常规方法无法解决,可以使用这些工具来分析。比如你有5个应用实例,你可以把20%的流量分配给专用机器,打开profile,相信你会很快定位到问题所在。作者简介:品味小姐姐(xjjdog),一个不允许程序员走弯路的公众号。专注于基础架构和Linux。十年架构,每天百亿流量,与你探讨高并发世界,给你不一样的滋味。我的个人微信xjjdog0,欢迎加好友进一步交流。