使用valgrind检查内存问题并且输出报告

valgrind内存泄漏分析

是在linux中检查内存泄漏的工具。当程序编写完之后我一般都会使用它来检查一次内存问题。基本上能杜绝服务器的内存泄漏问题(当然是面对C/C++这样的语言的)。
使用方式就是将程序编译好,然后通过valgrind来启动程序。当测试完全部的内存测试用例之后,让服务器程序正常退出。在最后结束的时候将会输出服务器的内存泄漏报告。不过valgrind对于protobuf,stl这样的3方库的兼容性不算太好,所以会造成输出一堆的still reachable字样。其实完全没有必要去纠结这些问题。可以肯定这些都是误判的。当你使用了这样的库的情况下,一般都会需要将这些检查option关闭掉。防止自己被爆出来的一堆的错误唬住了。信息太多反而阻碍自己的判断。
下面是一般启动程序使用的指令:

valgrind --log-file=./valgrind_report.log --leak-check=full --show-leak-kinds=all --show-reachable=no --track-origins=yes ./

这个里面使用到的option分别的意义:

–log-file
指定报告输出文件
–track-origins=yes
是否显示未定义的变量,在堆、栈中被定义没有被initialised的变量都被定义成origins。默认是关闭这个option的。
–show-leak-kinds=all
这里可以支持的选项有[definite|possible],一般只需要去关注definite(绝逼),possible是可能会存在。
–leak-check=full
当服务器退出时是否收集输出内存泄漏,选项有[no|summary|full]这个地方我们将其设置成全输出,默认将会使用summary方式。

输出报告之后重点看definite的,而且关于自己程序的。其次再去看possible的内存分配点的释放。

收集linux进程内存数据

对于在linux中的进程内存使用在/proc/[pid]/statm文件中其实是写了进程的内存使用情况。如果我们想去分析一个进程的内存长时间使用的趋势,可以在crontab中写一个定时运行的脚本通过读取这个文件来定时采集服务器的内存使用情况。如果服务器中安装了mysql数据库,可以使用命令行将这些数据入库。然后通过chart.js+php将数据通过图表方式呈现出来。数据收集了之后,其实也是没法子判断出数据的趋势。所以得让数据图形化。

/proc/[pid]/status文件中我们可以抓取服务器的内存相关的信息(单位为bytes):

VmPeak:   393532 kB
VmSize:   393532 kB
VmLck:         0 kB
VmPin:         0 kB
VmHWM:     35012 kB
VmRSS:     35012 kB
VmData:   328460 kB
VmStk:       268 kB
VmExe:      2248 kB
VmLib:     12836 kB
VmPTE:       228 kB
VmSwap:        0 kB

VmPeak = 虚存历史最高值
VmSize = 虚存
VmHWM = 物理内存最高值
VmRSS = 物理内存
VmData, VmStk, VmExe = 数据、栈和文本代码内存
VmLib = 共享库代码内存大小
VmPTE = 全部页表内存大小
VmLck = 锁定内存大小(参考:mlock)
VmSwap = 交换

/proc/[pid]/statm文件中对于内存的描述如下(度量单位是页):
提供某个pid下的内存使用情况。下面是每页的含义:

列编号名称详解
1size全部程序的大小,就是top中的VIRT虚存大小
2resident程序使用的实存,在top中的Res
3share共享页大小
4text文本(代码)
5lib库(Linux2.6无用)
6data数据和堆
7dt脏页大小(Linux 2.6无用)

为了直观理解的方便,我们可以使用bytes方式。
接下来就是将入库之后的内存做出曲线图了。这里需要使用的技术是apache+php+chart.js。
我们只需要编写一个页面来实现这个功能。在web世界里面,php是跑在服务器段的,它负责去在服务器中执行并且获得返回信息。并且通过打印字符串通过apache将这些http的文本发给browse。当browse拿到http文本,如果读取到了js这种前段的脚本,就会根据文本在本地绘画。我们这个示例就是通过php去读取mysql中我们已经收集到的某个服务器的memory的历史使用趋势数据。并且将数据写入到http中,并且填充一个js脚本。

python采集脚本

先请自己去创建一个数据库,然后就是将这个python写成一个crontab的东西吧。如果方便可以在自己的服务器里面部署一套来玩玩。

#! /usr/bin/python
# -*- coding: utf8 -*-
import os,re


#  CREATE TABLE `MEM_LOG` (
#    `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
#    `pid` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '唯一id',
#    `VmPeak` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '虚存历史最高值',
#    `VmSize` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '虚存',
#    `VmHWM` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '物理内存最高值',
#    `VmRSS` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '物理内存',
#    `VmData` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '数据',
#    `VmStk` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '栈',
#    `VmExe` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '文本代码内存',
#    `VmLib` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '共享库代码内存大小',
#    `VmPTE` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '全部页表内存大小',
#    `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '刷新时间',
#    PRIMARY KEY (`id`)
#  ) ENGINE=InnoDB ;

fields = [ 'VmPeak', 'VmSize', 'VmHWM', 'VmRSS', 'VmData', 'VmStk', 'VmExe', 'VmLib', 'VmPTE' ]
def read_pid_memory_info(pid):
    fd = open("/proc/%d/status"%pid,"r")
    record = {}
    record[ 'pid' ] = pid
    for line in fd.readlines():
        for field in fields:
            r = re.compile('%s: * *([0-9]+) kB'%field)
            r_result = r.findall( line )
            if len(r_result) == 0:
                continue
            record[ field ] = int( r_result[0] ) 
            pass
        pass
    fd.close()
    return record

def insert_into_db( record ):
    field_str = ''
    value_str = ''
    for field in record:
        field_str = field_str + ',' + field
        value_str = value_str + ',' + str( record[ field ] )
        pass
    field_str = field_str[1:]
    value_str = value_str[1:]
    sql = 'insert into MEM_LOG ( %s ) VALUES ( %s );'%( field_str, value_str )
    print( sql )
    prompt = 'mysql -uroot -pyourpassword mem_log_db -e "%s"'%(sql)
    os.system( prompt )
    pass

pip = os.popen( "ps aux | grep your_system | grep -v grep | awk '{print $2}'" )
all_proc = []
for line in pip.readlines():
    pid = int( line.replace('\n','') )
    print "pid is: %d"%pid
    record = read_pid_memory_info(pid)
    insert_into_db( record )
    pass

让他每10分钟统计一条出来:

*/10 * * * * /home/sean/memory_spy/spy.py

注意需要将这个spy.py设置成一个可执行的程序。

连接MySQL读取数据


<?
 $mysql_server_name="127.0.0.1"; //数据库服务器名称
    $mysql_username="xxx"; // 连接数据库用户名
    $mysql_password="xxx"; // 连接数据库密码
    $mysql_database="xxx"; // 数据库的名字

    // 连接到数据库
    $conn=mysql_connect($mysql_server_name, $mysql_username,
                        $mysql_password);

     // 从表中提取信息的sql语句
    $strsql="SELECT * FROM `MEM_LOG`";
    // 执行sql查询
    $result=mysql_db_query($mysql_database, $strsql, $conn);
    // 获取查询结果
    $row=mysql_fetch_row($result);



    $labels = array();
    $vm = array();
    $rm = array();
    $lua = array();

    mysql_data_seek($result, 0);
    // 循环取出记录
    while ($row=mysql_fetch_row($result))
    {
        array_push($labels, $row[0]);
        array_push($vm, $row[1]);
        array_push($rm, $row[2]);
        array_push($lua, $row[3]);
    }

    // 释放资源
    mysql_free_result($result);
    // 关闭连接
    mysql_close($conn);  

创建js的画布

注意:这里是下载了一份Chart.js-1.0.2放到了站点的根目录。否则会报错说这个js找不到。

<script src="Chart.js-1.0.2/Chart.js">
</script>
<canvas id="myChart" width="1800" height="400"></canvas>

通过php来填充数据

这个具体的实例,可以去抄一下chart.js的官方的一些实例就能去做饼状图,具状图之类的了。

    echo json_encode($labels);
    echo '<br/>';
    echo '
<script type="text/javascript">
var data = {
            labels: ' . " " .  json_encode($labels) . " " .  ',
            datasets: [
            {
            label: "Vm",
            fillColor: "rgba(220,220,220,0.2)",
            strokeColor: "rgba(220,220,220,1)",
            pointColor: "rgba(220,220,220,1)",
            pointStrokeColor: "#fff",
            pointHighlightFill: "#fff",
            pointHighlightStroke: "rgba(220,220,220,1)",
            data: ' . " " .  json_encode($vm) . " " .  '
            },
            {
            label: "rm",
            fillColor: "rgba(151,187,205,0.2)",
            strokeColor: "rgba(151,187,205,1)",
            pointColor: "rgba(151,187,205,1)",
            pointStrokeColor: "#fff",
            pointHighlightFill: "#fff",
            pointHighlightStroke: "rgba(151,187,205,1)",
            data: ' . " " .  json_encode($rm) . " " . '
            },
            {
            label: "lua",
            fillColor: "rgba(120,120,120,0.2)",
            strokeColor: "rgba(120,120,120,1)",
            pointColor: "rgba(120,120,120,1)",
            pointStrokeColor: "#fff",
            pointHighlightFill: "#fff",
            pointHighlightStroke: "rgba(220,220,220,1)",
            data: ' . " " .  json_encode($lua) . " " .  '
            }
            ]
            };
</script>';

设置图表的option,并且将数据画到画布上


<script type="text/javascript">
var options ={

            ///Boolean - Whether grid lines are shown across the chart
            scaleShowGridLines : true,

            //String - Colour of the grid lines
            scaleGridLineColor : "rgba(0,0,0,.05)",

            //Number - Width of the grid lines
            scaleGridLineWidth : 1,

            //Boolean - Whether to show horizontal lines (except X axis)
            scaleShowHorizontalLines: true,

            //Boolean - Whether to show vertical lines (except Y axis)
            scaleShowVerticalLines: true,

            //Boolean - Whether the line is curved between points
            bezierCurve : true,

            //Number - Tension of the bezier curve between points
            bezierCurveTension : 0.4,

            //Boolean - Whether to show a dot for each point
            pointDot : true,

            //Number - Radius of each point dot in pixels
            pointDotRadius : 4,

            //Number - Pixel width of point dot stroke
            pointDotStrokeWidth : 1,

            //Number - amount extra to add to the radius to cater for hit detection outside the drawn point
            pointHitDetectionRadius : 20,

            //Boolean - Whether to show a stroke for datasets
            datasetStroke : true,

            //Number - Pixel width of dataset stroke
            datasetStrokeWidth : 2,

            //Boolean - Whether to fill the dataset with a colour
            datasetFill : true,


            };


                    var ctx = document.getElementById("myChart").getContext("2d");
var myLineChart = new Chart(ctx).Line(data, options);

</script>

内存使用图

conclusion

最后如果你想将一个服务器的内存吃准了,其实还是来自于平日里对于服务器的把控。如同编制竹篓子,平时下功夫檫亮眼睛去多检查就扎实了。即使崩了也不会一泻千里的坏掉。

Valgrind 是一个用于检测程序中各种错误的工具集,它包括多个程序调试和分析工具,其中 memcheck 是用于检测内存错误的一个工具。Valgrind 通过在运行时记录程序对内存的所有操作来检测内存泄漏,它能够在程序运行时检查内存泄漏、越界访问、非法释放等问题。以下是使用 Valgrind 检测内存泄漏的基本步骤: 1. 安装 Valgrind:首先需要在系统中安装 Valgrind。大多数 Linux 发行版都提供了包管理器来安装 Valgrind,例如在 Ubuntu 上可以通过 apt-get 安装。 2. 运行 Valgrind:安装完成后,可以在命令行中使用 Valgrind 对目标程序进行分析。命令的基本格式如下: ``` valgrind --leak-check=full --show-leak-kinds=all <程序路径> ``` 其中 `--leak-check=full` 表示进行全面的内存泄漏检查,`--show-leak-kinds=all` 表示显示所有类型的内存泄漏信息。 3. 分析输出结果:Valgrind 运行程序后会产生报告,其中会详细列出内存泄漏的位置和信息。输出的信息一般包括以下几部分: - 哪些部分的内存没有被释放(泄漏的内存块数量、大小等)。 - 泄漏内存的源代码位置(文件名和行号)。 - 可能的泄漏原因和相关调用栈信息。 4. 查找并修复内存泄漏:根据 Valgrind 提供的报告,定位到源代码中对应的区域,检查并修复内存泄漏问题。 5. 验证修复:修复内存泄漏后,可以再次使用 Valgrind 进行验证,确保问题已经被解决。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值