最近挖坑用到的一些性能调试工具
话说之前一段时间,一只在找为啥用了netty后性能反倒没有之前好的问题。
然后就想办法找到底是哪里慢了。期间用到了如下几个工具
java 火焰图
之前在网上看到 别人 用 火焰图分析java的文章, 于是就想拿来试试看。
不过翻译的文章使用的还是 Brendan Gregg 使用的老方法,新方法(更方便)在 [netflix 的 blog] (http://techblog.netflix.com/2015/07/java-in-flames.html)上.
跟着官方教程来,中间没遇到什么坑,只不过得到的结果是 cpu 大部分用在了 encode decode 的 string.getByte上,当时就想着很正常。从这个上看不出什么。。
火焰图的作用是用来查看cpu 热点在哪儿。当时我一直想是不是我在某个潜在的地方踩了坑,在哪里阻塞了。而火焰图在这方面帮不了我。
aspectj
然后就在想,我想要一个工具,最要能将所有的我自己写的代码的每个方法前后都记录一个 time start, time end, 用来统计每个函数花费了多少时间。
由于之前已经做 metric 时借鉴(copy) 了 netflix 的 hystrix 的 metric 方式,只不过将原来的filter 模型, 就想着直接用aop 改成 切面模型对 所有 自己定义的包名的child包下所有的函数 都 pointcut
下。
再将 thisJoinPoint.getSignature
作为key,各自分配一个 Histogram,纪录每次的执行花费时间。
代码很简单,实现在这里
这次得出了, 比较接近的答案,发现慢在自定义的 decoder encoder 上面。但是具体时慢在第几行,还是不知道。
于是就考虑着是不是再把netty 的包也 pointcut 下,但是如果这样的话,
- 人肉分析肯定是不行的了。得弄个类似火焰图的可视化才行,尼玛 又是个大工程了。
- 不知道这么多函数内存够不够。。
这时 意外的再微博上 发现了这个, 并在地铁上看的时候意外发现好几个案例都用了叫 jmc 的工具发现内存问题。
jmc
jmc 全称 Java Mission Control
虽然很多人都用这个查到了内存问题啥的,但是他的功能远远不止内存。
简单说下他的强大的功能
- 内存上,可以统计出在ThreadLocal / 非ThreadLocal 中新申请的
哪个
对象 占了总申请对象的百分比,以及 对象 是在哪个函数被申请的,以及改函数的调用连。。 - cpu上,和内存类似,可以找出是什么包 是热点,并对堆栈进行追踪
- Thread上,同上
- IO, 系统, 事件 我都没用到。
然后,瞬间发现了问题所在, 并且是个超级低级的错误
1 | LOG.debug("decode bytebuf is:\n{}", ByteBufUtil.prettyHexDump(buf)); |
卧槽,每次encode,decode都执行了 ByteBufUtil.prettyHexDump,fuck!!
总结 jmc 神器,在jdk7u40 后才有,java8 之后的可以少敲一个 入参,还有最好的文档在 jmc 自带的文档
配置远程调试最简单的方式就是把所有安全相关的都关掉 = =
1 | java -Dcom.sun.management.jmxremote.port=7091 |
随想
话说既然有了 jmc,并且我猜测 netflix 献上已经开始 java8 了,为啥弄哪个火焰图呢?
我在使用了火焰图后事后发现,其实火焰图也能看出上面的问题,但是太隐蔽了,因为从java层到虚拟机层 再到 system call 调用链太深了,而火焰图是根据 高度来找cpu 热点的。而热点都细的的和什么一样。。。肉眼好难分辨 谁更高一些 = =
不过,他有一个好处,也是最最重要的是对性能基本无伤,可以在线上直接使用。
根据 netflix 自己说的,java 8 提供的 这个 选项 -XX:+PreserveFramePointer
仅仅损伤 0% - 3%
, 线上完全可以放心大胆的开着。然后系统有问题了,直接画个火焰图看看是哪里的问题。
flame in python
话说,今天去了 pycon,收获了 eleme 的 大神的大开脑洞的分享 他们是怎么做profiling 的
我们公司,java6 的古老体系,用的 aspectj 切每个关键函数的 time cost
这么做确实可以做到对业务方无侵入,但是其实还是要织入很多代码的,这些代码还是会或多或少占用些资源,当然这不是关键,关键是,如果profileing的代码更新了,业务代码也要跟着更新。
而他们直接用 systemtab 用来 probe c 的方法 probe python,不用侵入业务方资源,业务方也不用事先准备什么,直接可以 profile。
虽然它们用这个解决的问题是因为 他们用的 gevent 无法直接用 cprofile 找瓶颈。
不过,他的埋点是依赖了 redhat 或者 centos 上面的自带的python 会帮你埋点,(不然需要自己手动改python源码埋点,或者用python_debug跑 不过这个方式不可能在线上跑)
下面基本摘自他的ppt
redhat/centos/fedora 的埋点是在每个 python 函数的 entry 以及 return 得地方纪录了3个参数
1 | python.function.entry filename:string functname:string lineno:long |
依赖这个,于是自己写 stp 可以用到
1 | global pystack |
1 | stap -x PID gevent.stp "/lib64/libpython2.7.so.1.0" "/lib64/python2.7/site-packages/greenlet.so" |
然后就能看到
1 | 120133424 => 1470406350825948 gunicorn: worke(1804): <= get in /lib64/python2.7/_abcoll.py:365 |
类似这样的log
ps 为什么可以这么弄,因为 cPython 其实就是 c, 这里的 cPython 指非pypy 的官方pyhton
后记
话说,然后我将他的代码在我公司的测试环境(centos6.5) 试了下,发现不行,貌似我们的 centos 并没有给python事先埋点 orz,应该并非所有的redhat系都埋点了。 orzzzz
1 | [root@test132 python-systemtap]# stap -x 45742 gevent2.stp "/usr/lib64/libpython2.6.so.1.0" "/usr/lib64/python2.6/site-packages/greenlet.so" |
reference
http://techblog.netflix.com/2015/07/java-in-flames.html
http://www.ibm.com/developerworks/cn/java/j-lo-performance-analysissy-tools3/index.html