利用Valgrind和gperftools解决内存问题

近期,在对于系统进行性能测试,暴露一些问题。在定位过程中尝试使用一些工具,有效的帮助识别问题,并且解决了问题。由于问题比较典型,分享给大家,以便大家遇到类似问题时,借鉴参考。

工具介绍


1 Valgrind

Valgrind是一套Linux下,开放源代码(GPL V2)的仿真调试工具的集合。Valgrind由内核(core)以及基于内核的其他调试工具组成。内核类似于一个框架(framework),它模拟了一个CPU环境,并提供服务给其他工具;而其他工具则类似于插件 (plug-in),利用内核提供的服务完成各种特定的内存调试任务。


利用Valgrind和gperftools解决内存问题_第1张图片
valgrind的结构图

其中:Memcheck。这是valgrind应用最广泛的工具,一个重量级的内存检查器,能够发现开发中绝大多数内存错误使用情况,比如:使用未初始化的内存,使用已经释放了的内存,内存访问越界等

2. Heap Checker

google-perftools 是一款针对 C/C++ 程序的性能分析工具,它是一个遵守 BSD 协议的开源项目。提供四个工具:包括tcmalloc,heap-profiler, heap-checker,CPU profiler。heap-checker专门检测内存泄漏,heap-profiler则是内存监控器,可以随时知道当前内存使用情况(程序中内存使用热点),当然也能检测内存泄漏。

3. getrusage

linux提供rusage结构,可以描述业务进程内存使用情况,类似ps aux。

利用工具定位解决内存问题


三个典型问题,处理过程如下:

1. new expression导致的内存泄漏

1.1 问题描述

从性能测试时,统计主机可用内存减少,明显存在内存泄漏。


利用Valgrind和gperftools解决内存问题_第2张图片
图2-1 12分钟主机内存变化情况

1.2 问题定义分析

在性能测试主机安装Valgrind来进行内存泄漏分析。利用Valgrind启动程序:

valgrind --tool=memcheck --leak-check=full --show-leak-kinds=all --trace-children=yes ZSmartOCPro IN 2>&1| tee ZSmartOCPro_IN.txt

发送业务消息,然后退出业务进程,可以生成分析文件。

利用Valgrind和gperftools解决内存问题_第3张图片
图2-2 三个业务包内存统计摘要

利用Valgrind和gperftools解决内存问题_第4张图片
图2-3 九个业务包内存统计摘要
利用Valgrind和gperftools解决内存问题_第5张图片
图2-4 可能性能泄露点

从对比数据看,肯定存在内存泄漏。在业务代码中,为了提高性能,设计上采用new expression(参考:http://en.cppreference.com/w/cpp/language/new)内存模式,即将使用内存管理起来,重复使用,减少频繁申请内存,从而提高性能。

利用Valgrind和gperftools解决内存问题_第6张图片
图2-5 new expression

DataNode管理的数据和其本身都是采用这种方式。理论上次内存不存在释放,也不应该有泄露。从分析日志来看,因为DataNode中存在map和vector中内存没有失败。map和vector的内存有stl自行管理,在DataNode对应的Clear函数,增加map和vector的clear调用,来释放内存。测试时发现,这样的修改并没有用,why?
map和vector的内存模式也是new expression模式,clear只是清理计数器,并没有减少size。只有在容器析构的时候,才会释放内存。
修改的方式:在调用DataNode的Clear方法的地方改为显示调用他的析构函数~DataNode()方式来析构其内部的map和vector成员。

1.3 测试验证

利用Valgrind和gperftools解决内存问题_第7张图片
图2-6 12分钟内存变化曲线

对比前面曲线,减少满了一些,说明修改有效。是否存在其他内存泄漏情况,使用Valgrind继续进行测试。


利用Valgrind和gperftools解决内存问题_第8张图片
图2-7 3个消息的泄露分析汇总
利用Valgrind和gperftools解决内存问题_第9张图片
图2-8 12个消息的泄露分析汇总

因为第三方库使用问题,存在内存泄漏,但是并其并没有发生变化。分析其输出其他内存信息,也没有可疑的地方,问题的彻底解决陷入迷茫。

2 double linked导致单元测试core

2.1 问题描述

在解决问题1时,曾经提交调用DataNode的Clear方法。在跑单元测试用例时,有少量的代码会core,位置为与修改无关的python调用处:


利用Valgrind和gperftools解决内存问题_第10张图片
图2-9 double linked 导致core

2.3 问题解决

以往的经验,double linked一般都是非法内存读写引起。使用Valgrind是可以检查出,确实DataNode相关的内存确实存在非法读写。
以前实现方式:DataNode管理类和对应buffermanager都是单例的,并且通过静态变量,而其析构不受程序员控制,其内存又有关联系统导致。
修改:DataNode管理类保持单例不变,buffermanager修改为DataNode管理类的成员,由其负责buffer的生命周期。
修改后,运行单元测试正常,问题解决。

3 内存问题终极定位

3.1 问题描述

如图2-6所示,在解决了内存泄漏以后,内存还是减少而且无法用valgrind检测,是为什么呢?

3.2 问题分析

valgrind的泄露是检测从启动到退出,检测出无主内存泄漏情况。而且使用Valgrind运行业务程序,程序存在很严重的卡顿,不适合长时间和大量压力运行。从分析来看,业务进程在运行过程中,可能存在不断申请内存,在退出过程中释放。例如map或者vector等容器。
初步探索:

  • 1 也尝试用getrusage来测试成内存,能发现内存变大,却无法定位位置。getrusage显示maxrss是以块方式显示。


    利用Valgrind和gperftools解决内存问题_第11张图片
    getrusage测试
  • 2 走查怀疑代码。因为代码量大,无目的的走查是很低效的。
  • 3 利用二分法屏蔽组件方式来缩小范围后,在走查。屏蔽代码可能导致业务不通,未必能找出问题。
    需要另辟蹊径,有同事推荐gperftools来一试。

3.3 问题解决

google-perftools提供heap_checker功能(https://gperftools.github.io/gperftools/heap_checker.html)。

  • 1 首先是安装gperftools。
  • 2 重新编译业务代码添加 -ltcmalloc 链接tcmalloc库。
  • 3 启动平台,因为进程需要单独启动。

env HEAPCHECK=normal HEAP_PROFILE_ALLOCATION_INTERVAL=104857600 HEAPPROFILE="/home/hunter/log/test.log" ZSmartOCPro IN

说明:HEAPPROFILE指定生成dump文件的位置;HEAP_PROFILE_ALLOCATION_INTERVAL,程序内存每增长这一数值之后就dump 一次内存,默认是1G, 104857600=100M
利用Valgrind和gperftools解决内存问题_第12张图片
内存dump文件
  • 4 利用pprof来分析内存文件

/usr/local/bin/pprof --text --base=/home/hunter/log/test.log.0003.heap /home/hunter/bin/ZSmartOCPro /home/hunter/log/test.log.0009.heap

利用Valgrind和gperftools解决内存问题_第13张图片
内存差异

很快定位出问题所在,和猜想一直。业务代码写过程漏了调用vector的复位,导致一直增长。

修改问题后重新测试后,发现解决内存差异问题。

/home/hunter/log>/usr/local/bin/pprof --text --base=/home/hunter/log/test.log.0020.heap /home/hunter/bin/ZSmartOCPro /home/hunter/log/test.log.0055.heap
Using local file /home/hunter/bin/ZSmartOCPro.
Using local file /home/hunter/log/test.log.0055.heap.
Total: -0.0 MB
0.0 -272.7% -272.7% 0.0 -272.7% std::_Vector_base::_M_create_storage (inline)
0.0 -218.2% -490.9% 0.0 -218.2% Json::Value::Value
0.0 -109.1% -600.0% 0.0 -109.1% TariffTemplate::Instance (inline)
0.0 -109.1% -709.1% 0.0 -109.1% list_resize (inline)
0.0 -109.1% -818.2% 0.0 -109.1% std::vector::push_back (inline)
0.0 -4.5% -822.7% 0.0 -4.5% std::__cxx11::basic_string::_M_mutate
0.0 -4.5% -827.3% 0.0 -4.5% zmq::msg_t::init_size
0.0 -0.0% -827.3% -0.0 109.1% AcmManager::Initialize
0.0 -0.0% -827.3% 0.0 -109.1% AcmManager::RetrieveAccumulation
0.0 -0.0% -827.3% 0.0 -109.1% AcmManager::RetrieveDataFromTableAcm

至此,问题彻底解决。

小结

因为工具实现原理的差异,在解决问题时也各有特长:

  • Valgrind:适合解决非法读写,无主内存泄露等问题定位。
  • gperftools:长期运行进程内存使用量应该是稳定的,gperftools适合发现运行过程中内存差异,提供泄露的函数范围。而且对影响影响小,适合高压力快速复现。
  • getrusage:虽然对于定位问题帮助不大,但是可以用以守护进程监控管理业务进程的内存使用情况。
    以上是使用工具解决问题的过程,供学习和参考。感谢同事协助一起定位和解决。

你可能感兴趣的:(利用Valgrind和gperftools解决内存问题)