第一部分:实时系统监控(cpu利用率,cpu温度,总内存大小,已使用内存大小)
第二部分:实时告警
由于无刷新实时性,所以只能使用Ajax,这里没有用到任何ajax框架,因为调用比较简单
大家知道,由于java的先天不足,对底层系统的调用和操作一般用jni来完成,特别是cpu温度,你在window下是打死用命令行是得不到的,但由于我们的服务器系统是linux,所以可以不调用jni完全用java的方式来得到系统信息,这里用到了runtime的exec()函数,通过解析本地命令调用的结果来查询本地信息,
这里要感谢公司同事qinkun推荐ecsun兄的这篇文章http://papa.iteye.com/blog/220532,
- * 取得linux系统下的cpu、内存信息
- *
- * */
- public final class LinuxSystemTool
- {
-
-
-
-
-
-
-
-
- public static int[] getMemInfo() throws IOException, InterruptedException
- {
- File file = new File("/proc/meminfo");
- BufferedReader br = new BufferedReader(new InputStreamReader(
- new FileInputStream(file)));
- int[] result = new int[4];
- String str = null;
- StringTokenizer token = null;
- while((str = br.readLine()) != null)
- {
- token = new StringTokenizer(str);
- if(!token.hasMoreTokens())
- continue;
-
- str = token.nextToken();
- if(!token.hasMoreTokens())
- continue;
-
- if(str.equalsIgnoreCase("MemTotal:"))
- result[0] = Integer.parseInt(token.nextToken());
- else if(str.equalsIgnoreCase("MemFree:"))
- result[1] = Integer.parseInt(token.nextToken());
- else if(str.equalsIgnoreCase("SwapTotal:"))
- result[2] = Integer.parseInt(token.nextToken());
- else if(str.equalsIgnoreCase("SwapFree:"))
- result[3] = Integer.parseInt(token.nextToken());
- }
-
- return result;
- }
-
-
-
-
-
-
-
-
- public static float getCpuInfo() throws IOException, InterruptedException
- {
- File file = new File("/proc/stat");
- BufferedReader br = new BufferedReader(new InputStreamReader(
- new FileInputStream(file)));
- StringTokenizer token = new StringTokenizer(br.readLine());
- token.nextToken();
- int user1 = Integer.parseInt(token.nextToken());
- int nice1 = Integer.parseInt(token.nextToken());
- int sys1 = Integer.parseInt(token.nextToken());
- int idle1 = Integer.parseInt(token.nextToken());
-
- Thread.sleep(1000);
-
- br = new BufferedReader(
- new InputStreamReader(new FileInputStream(file)));
- token = new StringTokenizer(br.readLine());
- token.nextToken();
- int user2 = Integer.parseInt(token.nextToken());
- int nice2 = Integer.parseInt(token.nextToken());
- int sys2 = Integer.parseInt(token.nextToken());
- int idle2 = Integer.parseInt(token.nextToken());
-
- return (float)((user2 + sys2 + nice2) - (user1 + sys1 + nice1)) / (float)((user2 + nice2 + sys2 + idle2) - (user1 + nice1 + sys1 + idle1));
- }
- }
这里的两个方法,解释一下,
方法1文件"/proc/meminfo"里面包含的就是内存的信息,还包括了swap的信息。例如:
$ cat /proc/meminfo
total: used: free: shared: buffers: cached:
Mem: 1057009664 851668992 205340672 0 67616768 367820800
Swap: 2146787328 164429824 1982357504
MemTotal: 1032236 kB
MemFree: 200528 kB
MemShared: 0 kB
这样可以用截取字符串的方法,来得到linux内存信息.
方法2在文件"/proc/stat"里面就包含了CPU的信息。每一个CPU的每一tick用在什么地方都在这个文件里面记着。后面的数字含义分别是: user、nice、sys、idle、iowait。有些版本的kernel没有iowait这一项。这些数值表示从开机到现在,CPU的每tick用在了哪里。例如:
cpu0 256279030 0 11832528 1637168262
就是cpu0从开机到现在有 256279030 tick用在了user消耗,11832528用在了sys消耗。所以如果想计算单位时间(例如1s)里面CPU的负载,那只需要计算1秒前后数值的差除以每一秒的tick数量就可以了。
ok这样还剩下cpu温度,怎么做呢
发现了一个文件"cat /proc/acpi/thermal_zone/THM/temperature";可以返回本机的linux温度,
大概是这样的:temperature: 68C
但不是每台linux机器都有这个THM你要确定你的linux加载了这个THM才能使用这个文件,这样就用InputStreamReader(new FileInputStream(new File("/proc/acpi/thermal_zone/THM/temperature")),去读取这个文件,后面的相信大家一定会做了吧,就是把内容读出来,然后分割字符串去得到这个68。ok,系统基本信息全部完成,然后ok现在就只有一件事就是用Ajax去调用这个类来得到 基本信息,然后返回到页面上,Ajax的用法就不赘言了。
下面是系统监控的效果,大概是Ajax每几秒去linux下去取一次系统信息,然后显示在jsp页面上,以下是效果。
到这里第一部分系统监控部分已经完成,现在开始完成实时告警部分,分析需求
1温度和cpu超过额定值需要告警
2用户操作系统失败,用户存储空间不足也需要告警,还有我们公司的业务操作失败告警,如果发生Exception也只能告警,当然要把异常的堆栈的信息保存在数据库里,我就这样设计如果用户在操作中触发了这些错误,则保存在数据库的告警表里,然后实时监控的再取出来这些信息。
3告警是要实时的那么要怎么从告警表里查到当前以后的数据呢,一开始想到用当前时间,在当前时间加上Ajax发送时间间隔,select * from warnlist where date>new Date()+AjaxTime这种形式,后来发现时间是很不正确的,网络延迟,程序处理时间,(cpu信息用了sleep函数),等等你常常会发现有些告警信息被无情的放过,而有的时候有重复数据,这样我想到了用id,每次进入告警系统先查询到最大的告警id,然后保存在session中,然后ajax从数据库里取告警信息的时候都查这个id之后的数据(就是进入监控系统后的最新数据),然后session再保存新的最大id,下次ajax取还是从这个session中取最大id,这样信息就可以当ajax取的时候都保证是最新的,而且没有重复,very good!就这样做了
这样设计了一张告警处理表
- CREATE TABLE `warnlist` (
- `Id` bigint(20) NOT NULL auto_increment,
- `warnleave` tinyint(2) NOT NULL default '0',//告警级别:告警的严重程度
- `fromguy` varchar(20) NOT NULL,//属于哪个用户哪个组织的告警
- `warncontent` varchar(100) NOT NULL,//告警内容,比如cpu使用率超过80%
- `aviliablevalue` varchar(12) default NULL,//允许值 比如85%
- `warnvalue` varchar(12) default NULL,//告警值 80
- `warntime` datetime NOT NULL,//告警时间
- `stackinfo` varchar(255) default NULL,//异常的堆栈信息
- `dealwith` tinyint(2) NOT NULL default '0',//处理结果
- `version` int(11) default NULL,//version
- `organizerID` varchar(20) default NULL,//组织id
- `des` varchar(255) default NULL,
- PRIMARY KEY (`Id`)
- ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
假设我ajax从系统取信息后,那么要写个逻辑,if(cpuTempature>75C)or if(cpuUserd>80%)则写入数据库,然后再查询大于上一次发送Ajax数据库的最大id的告警信息(这期间如果发生的以下错误一并查出:用户存储空间不足,还有我们公司的业务操作失败告警,Exception等),循环插入一个xml解析类中,大概形式是这样的Ajax返回这个xml,供页面提取信息
- <response>
- <cpuUsed>67cpuUsed>
- <cpuTemp>76<cpuTemp>
- <Memory>1023422Memory>
- <freeMemory>43244freeMemory>
- <wannlist>
- <warnid>2warnid>
- <warncontent>系统存储空间不足warncontent>
- <fromguy>kakaluyifromguy>
- ..............
- wanrlist>
- <warnlist>
- <warnid>3warnid>
- <warncontent>cpu温度过高warncontent>
- <fromguy>系统fromguy>
- <orgid>系统orgid>
- <warnvalue>78warnvalue>
- .............
- warnlist>
- ........
-
- response>
系统信息的显示代码,就是关联上面那个图片的:
- var cpuUsed=req.responseXML.getElementsByTagName('cpuUsed')[0].firstChild.nodeValue;
- var totalMemory=req.responseXML.getElementsByTagName('totalMemory')[0].firstChild.nodeValue;
- var freeMemory=req.responseXML.getElementsByTagName('freeMemory')[0].firstChild.nodeValue;
- var cpuTemp=req.responseXML.getElementsByTagName('cpuTemp')[0].firstChild.nodeValue;
- $('cpuUsed').innerHTML=cpuUsed;
- $('totalMemory').innerHTML=totalMemory;
- $('freeMemory').innerHTML=freeMemory;
- $('cpuTemp').innerHTML=cpuTemp;
-
- //jsp
- <tr>
- <td class="label" width="20%">
- 服务器CPU使用率:td>
- <td class="text">
- <font color="#FF0000" size="+2"><label id="cpuUsed">label>
- font> < 告警预定阀值: 80% >
- td>
- tr>
- .........
然后就是页面展现的问题了这里我用了dom节点的增删,一个页面保持50条记录,如果超过50条则删除以前的节点,代码为:
- var length=req.responseXML.getElementsByTagName('warnlist').length;
- if(length>0)
- {
- var trlength=document.getElementsByTagName('table')[4].childNodes[0].childNodes.length;
-
- if(trlength+length-1>50)
- 告警信息的子节点,然后删除多余的最早的告警信息
- {
- var tbody=document.getElementsByTagName('table')[4].childNodes[0];
- for(var i=1;i
- {
- var tr=tbody.childNodes[i];
- tr.parentNode.removeChild(tr);
-
- }
然后插入新的告警信息,
- for(var i=0;i
- {
- var onewarnlist=req.responseXML.getElementsByTagName('warnlist')[i].childNodes;
- if(onewarnlist[0].firstChild.nodeValue==0)
- {
- var leave="企业级告警";
- }
- else {
- var leave="运营商级告警";
- }
- var from=onewarnlist[1].firstChild.nodeValue;
- var warncontent=onewarnlist[2].firstChild.nodeValue;
- var aviliablevalue=onewarnlist[3].firstChild.nodeValue;
- var warnvalue=onewarnlist[4].firstChild.nodeValue;
- var warntime=onewarnlist[5].firstChild.nodeValue;
- var id=onewarnlist[8].firstChild.nodeValue;
- if(onewarnlist[6].firstChild.nodeValue==0)
- {
- var dealwith="未处理" ;
- }
- else {
- var dealwith="已处理";
- }
- var table=document.getElementById('warntable');
- var tr=document.createElement('tr');
- if(x%2==1)
- {
- tr.style.backgroundColor="#BFD3F9"
- }
- else{
- tr.style.backgroundColor="#FBFCEB"
- }
- x++;
- table.appendChild(tr);
- var td=document.createElement('td');
- td.className ='listText';
- td.innerHTML =x;
- tr.appendChild(td);
- var td1=document.createElement('td');
- td1.className ='listText';
- td1.innerHTML = leave;
- tr.appendChild(td1);
- var td2=document.createElement('td');
- td2.className ='listText';
- td2.innerHTML = from;
- tr.appendChild(td2);
- var td3=document.createElement('td');
- td3.className ='listText';
- td3.innerHTML = warncontent;
- tr.appendChild(td3);6
- var td4=document.createElement('td');
- td4.className ='listText';
- td4.innerHTML = aviliablevalue;
- tr.appendChild(td4);
- var td5=document.createElement('td');
- td5.className ='listText';
- td5.innerHTML = ''+warnvalue+'';
- tr.appendChild(td5);
- var td6=document.createElement('td');
- td6.className ='listText';
- td6.innerHTML = warntime;
- tr.appendChild(td6);
- var td7=document.createElement('td');
- td7.className ='listText';
- td7.innerHTML = dealwith;
- tr.appendChild(td7);
- var td8=document.createElement('td');
- td8.className ='listText';
- td8.innerHTML = id;
- tr.appendChild(td8);
- }
ok,一切大功告成,以下是最终效果