Collectd对KVM虚拟机内存监控的测试与优化
2016-05-31崔广章杜程刘睿
崔广章 杜程 刘睿
摘 要:研究旨在通过修改Collectd源码来验证其现有的内存监控项目数据的准确性,完善内存相关监控项。文章首先将Collectd进行部署,观察其对KVM虚拟机内存监控的效果,然后通过对KVM虚拟机内存监控的源码分析,验证监控图上展示的相关内存监控项目的数据准确性,并根据其结果决定修改源码来完善相关内存监控项的可行性。通过文中的分析得出结论:只有在Libvirt对KVM虚拟机内存监控相关功能进行API优化之后,Collectd对KVM虚拟机的内存监控才能够得到完善。从而为不用在KVM虚拟机里安装任何东西的情况下实现对其内存的监控找到了一种切实可行的方法。
关键词:Collectd;KVM;内存监控;准确性
中图分类号:TP319 文献标识码:A 文章编号:2095-1302(2016)05-00-05
0 引 言
随着互联网技术的快速发展,各种云计算平台也应运而生。国外有AbiCloud、Hadoop(Apache基金会)、Eucalyptus 项目(加利福尼亚大学)、MongoDB(10gen)、Enomalism弹性计算平台等,国内有阿里巴巴的阿里云、中国移动的BigCloude-大云平台、盛大云等。虚拟化是云计算平台的基础,虚拟机的稳定性直接影响到云计算平台的性能,这就迫切需要一套能够对虚拟机进行实时监控的系统。但是,目前还没有一套针对该问题成熟的监控系统方案,本文用Collectd对KVM(Kernel-based Virtual Machine,KVM)虚拟机进行监控。
1 Collectd对KVM虚拟机监控的测试
1.1 关于Collectd
Collectd是一个守护(daemon)进程,通过调用Libvirt-API来周期性地收集系统的各项数据,然后以一定的格式(比如以RRD形式)将数据进行存储,这些数据可以用来找到当前系统性能的瓶颈。
Libvirt是一套免费、开源的支持Linux主流虚拟化工具的C函数库,其旨在为包括Xen在内的各种虚拟化工具提供一套方便、可靠的编程接口,支持与C、C++、Ruby、Python等多种主流开发语言的绑定。当前主流Linux平台上默认的虚拟化管理工具virt-manager(图形化)、virt-install(命令行模式)等均基于Libvirt开发而成。Libvirt库是一种实现Linux虚拟化功能的Linux? API,它支持各种虚拟机监控程序,包括Xen和KVM以及QEMU和用于其他操作系统的一些虚拟产品。Collectd通过Libvirt监控虚拟机的原理图如图1所示。
图1 Collectd通过Libvirt监控虚拟机原理图
通过Collectd对KVM虚拟机进行监控的优势是:首先,Collectd只需要安装在宿主机(Host)的底层,不必在虚拟机内部安装代理(Agent),这样就避免了监控程序对虚拟机的污染,使监测到的虚拟机的各项数据更加精确;其次,由于监控程序是安装在宿主机的底层,用户(虚拟机的使用者)没有权限来关闭该监控程序,这样就避免了用户将监控程序关闭而造成的监控系统监控不到虚拟机问题的发生。
1.2 测试环境
本文通过两台DELL服务器部署Collectd进行测试,一台服务器上部署Collectd计算节点,另一台服务器部署Collectd控制节点。部署计算节点的服务器同时安装KVM,并通过KVM在该服务器上创建10台虚拟机。测试环境的详细说明如表1所列。
控制节点与计算节点配置文件如表2所列。
表2 配置文件详解
IP 节点类型 关键配置项
10.10.10.20 计算节点 Hostname "compute1"
FQDNLookup true
Interval 10
Timeout 2
ReadThreads 5
LoadPlugin logfile
LoadPlugin syslog
LogLevel info
LoadPlugin cpu
LoadPlugin disk
LoadPlugin libvirt
LoadPlugin load
LoadPlugin memory
LoadPlugin network
LoadPlugin ping
LoadPlugin swap
PrintSeverity false
value_t values[1]; //创建只有一个元素的value_t结构体数组
value_list_t vl = VALUE_LIST_INIT; //创建并初始化一个value_list_t链表
init_value_list (&vl, dom); 用虚拟机dom给链表vl赋值
values[0].gauge = memory; //用内存memory给结构体数组values的0号元素的gauge赋值
vl.values = values; //用结构体数组给链表vl的values赋值
vl.values_len = 1; //给链表vl的values_len赋值为1
sstrncpy (vl.type, “memory”, sizeof (vl.type)); //表明监控指标类型为内存
//表明监控指标为tags指明的监控项
sstrncpy (vl.type_instance, tags[tag_index], sizeof (vl.type_instance));
plugin_dispatch_values (&vl); //将vl的数据传给系统
}
场景函数详解如下:
for (i = 0; i < nr_domains; ++i) {
virDomainInfo info;//定义一个虚拟机信息结构体
virVcpuInfoPtr vinfo = NULL;
virDomainMemoryStatPtr minfo = NULL;//定义一个虚拟机内存结构体指针
int status;
int j;
//调用virDomainGetInfo(virDomainPtr domain, virDomainInfoPtr info)给info赋值
status = virDomainGetInfo (domains[i], &info);
……
cpu_submit (info.cpuTime, domains[i], “virt_cpu_total”);
//将虚拟机地址以及虚拟机的总内存信息传给memory_submit (gauge_t memory(内存值), virDomainPtr dom(虚拟机地址))
memory_submit ((gauge_t) info.memory * 1024, domains[i]);
vinfo = malloc (info.nrVirtCpu * sizeof (vinfo[0]));
……
//给虚拟机内存状态结构体指针minfo分配内存
minfo = malloc (VIR_DOMAIN_MEMORY_STAT_NR * sizeof (virDomainMemoryStatStruct));
if (minfo == NULL) {
ERROR (“virt plugin: malloc failed.”);
continue;
}
//调用函数virDomainMemoryStats (virDomainPtr dom, virDomainMemoryStatPtr stats,
unsigned int nr_stats, unsigned int flags)来给虚拟机内存状态指针minfo赋值并返回对应的内存状态个数
status =virDomainMemoryStats (domains[i], minfo, VIR_DOMAIN_MEMORY_STAT_NR, 0);for (j = 0; j < status; j++) {
//将从函数virDomainMemoryStats(…)得到的状态标签及对应的内存值传给函数memory_stats_submit(…)
memory_stats_submit ((gauge_t) minfo[j].val * 1024, domains[i], minfo[j].tag);
}
sfree (minfo);
}
虚拟机信息结构体详解如下所示:
struct virDomainInfo {
unsigned charstate;//虚拟机的运行状态
unsigned longmaxMem;//虚拟机的最大内存
unsigned longmemory;//虚拟机的已用内存
unsigned shortnrVirtCpu;//虚拟机的虚拟CPU个数
unsigned long longcpuTime;//虚拟机的纳秒级时间片
}
虚拟机内存状态结构体及结构体指针详解如下:
typedef virDomainMemoryStatStruct * virDomainMemoryStatPtr
struct virDomainMemoryStatStruct {
int tag;//内存项标签
unsigned long long val;//对应tag的内存值
}
虚拟机内存状态函数详解如下:
int virDomainMemoryStats (virDomainPtr dom(虚拟机指针),
virDomainMemoryStatPtr stats(虚拟机内存状态指针),
unsigned int nr_stats(虚拟机状态数),
unsigned int flags(官方推荐填0))
nr_stats个虚拟机内存状态会被对应的虚拟机赋值,但该函数只返回目前Libvirt版本支持的数据。
2.2 内存监控源码优化
由对源码的分析可知,在collectd/src/virt.c的源码中,对内存(总内存除外)进行监控的关键函数见表3所列。
表3 内存监控关键函数详解
函数原形 函数作用
Int virDomainGetInfo(virDomainPtr domain, virDomainInfoPtr info) 用指定的虚拟机给虚拟机信息结构体指针赋值,从而得到虚拟机的总内存
Static void memory_submit (gauge_t memory(内存值), virDomainPtr dom(虚拟机地址)) 将由函数Int virDomainGetInfo(…)得到的虚拟机总内存及对应虚拟机传递给系统(Collectd)
Int virDomainMemoryStats (virDomainPtr dom, virDomainMemoryStatPtr stats,
unsigned int nr_stats, unsigned int flags) 用虚拟机指针对应的虚拟机给虚拟机内存状态结构体指针对应的结构体赋值,并返回对应虚拟机的内存状态个数
static void memory_stats_submit (gauge_t memory, virDomainPtr dom, int tag_index)
将调用函数Int virDomainMemoryStats(…)得到的函数虚拟机内存项目标签及对应的内存值传递给系统(Collectd)
基于相关源码分析,结合优化目标,通过如下步骤进行优化:
(1) 对collectd/src/virt.c中调用Lbvirt-API的int virDomainMemoryStats (virDomainPtr dom, virDomainMemoryStatPtr stats, unsigned int nr_stats, unsigned int flags)函数返回了几个tag及判断tag对应的内存键控制是否正确;
(2) 在步骤(1)得到解决的前提下,本文再通过在collectd/src/virt.c中加入想要监控和展示的相关内存项的代码来实现增加相关监控项的优化目标。
通过在表5场景函数详解中增加打印函数将调用Libvirt-API的int virDomainMemoryStats (virDomainPtr dom, virDomainMemoryStatPtr stats, unsigned int nr_stats, unsigned int flags)函数返回的tag值并将对应的内存值打印出来,具体修改如下:
for (i = 0; i < nr_domains; ++i) {
virDomainInfo info;
virVcpuInfoPtr vinfo = NULL;
virDomainMemoryStatPtr minfo = NULL;
int status;
int j;
status = virDomainGetInfo (domains[i], &info);
……
status =virDomainMemoryStats (domains[i], minfo, VIR_DOMAIN_MEMORY_STAT_NR, 0);
if (status < 0) {
ERROR (“virt plugin: virDomainMemoryStats failed with status %i.”, status);
sfree (minfo);
continue;
}
//在下面的for循环中添加文件打印函数来调用Libvirt-API的int virDomainMemoryStats (virDomainPtr dom, virDomainMemoryStatPtr stats, unsigned int nr_stats, unsigned int flags)函数返回的tag值并将对应的内存值打印出来
for (j = 0; j < status; j++) {
FILE *fp;//定义一个文件描述符
fp=fopen(“/root/test.txt”,”a”);//在/root下打开一个可进行内容追加的test.txt文件
//将tag及对应内存值写入test.txt文件
fprintf(fp,”tag:%d\t的memory值:%d\n”,minfo[j].tag,minfo[j].val);
fclose(fp);//将打开的文件关闭
memory_stats_submit ((gauge_t) minfo[j].val * 1024, domains[i], minfo[j].tag);
}
sfree (minfo);
}
除了添加数值打印函数外,还要在collectd/src/virt.c的头文件中添加:#include
图3 打印输出的tag值及对应的内存值
图3只是截取了test.txt中的一部分,因为tag:0、tag:7、tag:6是一个循环,本文所示的图3中只截取了众多循环当中的5个循环。
分析图3可知,调用Libvirt-API的int virDomainMemoryStats (virDomainPtr dom, virDomainMemoryStatPtr stats, unsigned int nr_stats, unsigned int flags)函数返回的tag值只有0、7、6这三个,传递给函数static void memory_stats_submit (gauge_t memory, virDomainPtr dom, int tag_index)对应的tag是swap_in、rss、actual_balloon,与虚拟机内存监控图上列出的一致,但是这三个tag值对应的内存值一直不变,这是虚拟机内存监控图上相关监控项目一直是直线的根本原因。
3 结 语
根据内存监控源码优化的分析,得出如下结果:
(1)内存监控图上的相关曲线之所以一直是直线,是因为调用Libvirt-API的int virDomainMemoryStats (virDomainPtr dom, virDomainMemoryStatPtr stats, unsigned int nr_stats, unsigned int flags)函数取出的值都是一直不变的,并非因为图形展示或其他原因造成的;
(2)调用Libvirt-API的int virDomainMemoryStats (virDomainPtr dom, virDomainMemoryStatPtr stats, unsigned int nr_stats, unsigned int flags)函数得到的tag值只有0、7、6三个而且都已经在内存监控图上得到展示,所以目前无法修改源码添加其他内存监控项。
通过以上分析,本文得出结论:在Libvirt对KVM虚拟机内存监控的相关API功能得到实现与优化之后,Collectd对KVM虚拟机的内存监控项目就能补全,监控结果的准确性就能得到保障。也就是说本文为不用在KVM虚拟机里安装任何东西的情况下实现对其内存进行监控找到了一种切实可行的方法。
参考文献
[1]汤儒,李秦伟.Openstack云环境中KVM虚拟机性能分析[J].微型机与应用,2013,32(23):94-96,100.
[2]邓文洋.虚拟化环境下的多机内存优化技术研究与实现[D].北京:首都师范大学,2013.
[3]杜炜.KVM客户机主动共享的内存超量使用策略研究[D].杭州:杭州电子科技大学,2013.
[4]时卫东.基于内核的虚拟机的研究[D].长春:吉林大学,2011.
[5]王春光.虚拟操作系统行为监控技术的研究与实现[D].长沙:国防科学技术大学,2009.
[6] libvirt部分API介绍[EB/OL].2015.06, http://www.bubuko.com/infodetail-903462.html