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下的内存使用情况。下面是每页的含义:
列编号 | 名称 | 详解 |
---|---|---|
1 | size | 全部程序的大小,就是top中的VIRT虚存大小 |
2 | resident | 程序使用的实存,在top中的Res |
3 | share | 共享页大小 |
4 | text | 文本(代码) |
5 | lib | 库(Linux2.6无用) |
6 | data | 数据和堆 |
7 | dt | 脏页大小(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
最后如果你想将一个服务器的内存吃准了,其实还是来自于平日里对于服务器的把控。如同编制竹篓子,平时下功夫檫亮眼睛去多检查就扎实了。即使崩了也不会一泻千里的坏掉。