使用Java編寫(xiě)一個(gè)簡(jiǎn)單的Web的監(jiān)控系統(tǒng)
公司的服務(wù)器需要實(shí)時(shí)監(jiān)控,而且當(dāng)用戶空間已經(jīng)滿了,操作失敗,或者出現(xiàn)程序Exception的時(shí)候就需要實(shí)時(shí)提醒,便于網(wǎng)管和程序員調(diào)式,這樣就把這個(gè)實(shí)時(shí)監(jiān)控系統(tǒng)分為了兩部分,
第一部分:實(shí)時(shí)系統(tǒng)監(jiān)控(cpu利用率,cpu溫度,總內(nèi)存大小,已使用內(nèi)存大小)
第二部分:實(shí)時(shí)告警
由于無(wú)刷新實(shí)時(shí)性,所以只能使用Ajax,這里沒(méi)有用到任何ajax框架,因?yàn)檎{(diào)用比較簡(jiǎn)單
大家知道,由于java的先天不足,對(duì)底層系統(tǒng)的調(diào)用和操作一般用jni來(lái)完成,特別是cpu溫度,你在window下是打死用命令行是得不到的, 但由于我們的服務(wù)器系統(tǒng)是linux,所以可以不調(diào)用jni完全用java的方式來(lái)得到系統(tǒng)信息,這里用到了runtime的exec()函數(shù),通過(guò)解析 本地命令調(diào)用的結(jié)果來(lái)查詢本地信息,
* 取得linux系統(tǒng)下的cpu、內(nèi)存信息
*
* */
public final class LinuxSystemTool
{
/**
* get memory by used info
*
* @return int[] result
* result.length==4;int[0]=MemTotal;int[1]=MemFree;int[2]=SwapTotal;int[3]=SwapFree;
* @throws IOException
* @throws InterruptedException
*/
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;
}
/**
* get memory by used info
*
* @return float efficiency
* @throws IOException
* @throws InterruptedException
*/
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));
}
}
這里的兩個(gè)方法,解釋一下,
方法1文件"/proc/meminfo"里面包含的就是內(nèi)存的信息,還包括了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
這樣可以用截取字符串的方法,來(lái)得到linux內(nèi)存信息.
方法2在文件"/proc/stat"里面就包含了CPU的信息。每一個(gè)CPU的每一tick用在什么地方都在這個(gè)文件里面記著。后面的數(shù)字含義分 別是: user、nice、sys、idle、iowait。有些版本的kernel沒(méi)有iowait這一項(xiàng)。這些數(shù)值表示從開(kāi)機(jī)到現(xiàn)在,CPU的每tick用 在了哪里。例如:
cpu0 256279030 0 11832528 1637168262
就是cpu0從開(kāi)機(jī)到現(xiàn)在有 256279030 tick用在了user消耗,11832528用在了sys消耗。所以如果想計(jì)算單位時(shí)間(例如1s)里面CPU的負(fù)載,那只需要計(jì)算1秒前后數(shù)值的差除以每一秒的tick數(shù)量就可以了。
ok這樣還剩下cpu溫度,怎么做呢
發(fā)現(xiàn)了一個(gè)文件"cat /proc/acpi/thermal_zone/THM/temperature";可以返回本機(jī)的linux溫度,
大概是這樣的:
temperature: 68C
但不是每臺(tái)linux機(jī)器都有這個(gè)THM你要確定你的linux加載了這個(gè)THM才能使用這個(gè)文件,這樣就用InputStreamReader(new FileInputStream(new File("/proc/acpi/thermal_zone/THM/temperature")), 去讀取這個(gè)文件,后面的相信大家一定會(huì)做了吧,就是把內(nèi)容讀出來(lái),然后分割字符串去得到這個(gè)68。ok,系統(tǒng)基本信息全部完成,然后ok現(xiàn)在就只有一件事就是用Ajax去調(diào)用這個(gè)類來(lái)得到 基本信息,然后返回到頁(yè)面上,Ajax的用法就不贅言了。
下面是系統(tǒng)監(jiān)控的效果,大概是Ajax每幾秒去linux下去取一次系統(tǒng)信息,然后顯示在jsp頁(yè)面上,以下是效果。

到這里第一部分系統(tǒng)監(jiān)控部分已經(jīng)完成,現(xiàn)在開(kāi)始完成實(shí)時(shí)告警部分,分析需求
1溫度和cpu超過(guò)額定值需要告警
2用戶操作系統(tǒng)失敗,用戶存儲(chǔ)空間不足也需要告警,還有我們公司的業(yè)務(wù)操作失敗告警,如果發(fā)生Exception也只能告警,當(dāng)然要把異常的堆棧的 信息保存在數(shù)據(jù)庫(kù)里,我就這樣設(shè)計(jì)如果用戶在操作中觸發(fā)了這些錯(cuò)誤,則保存在數(shù)據(jù)庫(kù)的告警表里,然后實(shí)時(shí)監(jiān)控的再取出來(lái)這些信息。
3告警是要實(shí)時(shí)的那么要怎么從告警表里查到當(dāng)前以后的數(shù)據(jù)呢,一開(kāi)始想到用當(dāng)前時(shí)間,在當(dāng)前時(shí)間加上Ajax發(fā)送時(shí)間間隔,select * from warnlist where date>new Date()+AjaxTime這種形式,后來(lái)發(fā)現(xiàn)時(shí)間是很不正確的,網(wǎng)絡(luò)延遲,程序處理時(shí)間,(cpu信息用了sleep函數(shù)),等等你常常會(huì)發(fā)現(xiàn)有些 告警信息被無(wú)情的放過(guò),而有的時(shí)候有重復(fù)數(shù)據(jù),這樣我想到了用id,每次進(jìn)入告警系統(tǒng)先查詢到最大的告警id,然后保存在session中,然后ajax 從數(shù)據(jù)庫(kù)里取告警信息的時(shí)候都查這個(gè)id之后的數(shù)據(jù)(就是進(jìn)入監(jiān)控系統(tǒng)后的最新數(shù)據(jù)),然后session再保存新的最大id,下次ajax取還是從這個(gè) session中取最大id,這樣信息就可以當(dāng)ajax取的時(shí)候都保證是最新的,而且沒(méi)有重復(fù),very good!就這樣做了
這樣設(shè)計(jì)了一張告警處理表
CREATE TABLE `warnlist` ( `Id` bigint (20) NOT NULL auto_increment, `warnleave` tinyint(2) NOT NULL default '0' ,//告警級(jí)別:告警的嚴(yán)重程度 `fromguy` varchar (20) NOT NULL ,//屬于哪個(gè)用戶哪個(gè)組織的告警 `warncontent` varchar (100) NOT NULL ,//告警內(nèi)容,比如cpu使用率超過(guò)80% `aviliablevalue` varchar (12) default NULL ,//允許值 比如85% `warnvalue` varchar (12) default NULL ,//告警值 80 `warntime` datetime NOT NULL ,//告警時(shí)間 `stackinfo` varchar (255) default NULL ,//異常的堆棧信息 `dealwith` tinyint(2) NOT NULL default '0' ,//處理結(jié)果 `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;
假設(shè)我ajax從系統(tǒng)取信息后,那么要寫(xiě)個(gè)邏輯,if(cpuTempature>75C)or if(cpuUserd>80%)則寫(xiě)入數(shù)據(jù)庫(kù),然后再查詢大于上一次發(fā)送Ajax數(shù)據(jù)庫(kù)的最大id的告警信息(這期間如果發(fā)生的以下錯(cuò)誤一并查 出:用戶存儲(chǔ)空間不足,還有我們公司的業(yè)務(wù)操作失敗告警,Exception等),循環(huán)插入一個(gè)xml解析類中,大概形式是這樣的Ajax返回這個(gè) xml,供頁(yè)面提取信息
< response > < cpuUsed > 67 </ cpuUsed > < cpuTemp > 76 < cpuTemp > < Memory > 1023422 </ Memory > < freeMemory > 43244 </ freeMemory > < wannlist > < warnid > 2 </ warnid > < warncontent > 系統(tǒng)存儲(chǔ)空間不足 </ warncontent > < fromguy > kakaluyi </ fromguy > .............. </ wanrlist > < warnlist > < warnid > 3 </ warnid > < warncontent > cpu溫度過(guò)高 </ warncontent > < fromguy > 系統(tǒng) </ fromguy > < orgid > 系統(tǒng) </ orgid > < warnvalue > 78 </ warnvalue > ............. </ warnlist > ........ </ response >
系統(tǒng)信息的顯示代碼,就是關(guān)聯(lián)上面那個(gè)圖片的:
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%" >
服務(wù)器CPU使用率:
</ td > < td class = "text" > < font color = "#FF0000" size = "+2" > < label id = "cpuUsed" > </ label > </ font > < 告警預(yù)定閥值: 80% > </ td > </ tr > .........
然后就是頁(yè)面展現(xiàn)的問(wèn)題了這里我用了dom節(jié)點(diǎn)的增刪,一個(gè)頁(yè)面保持50條記錄,如果超過(guò)50條則刪除以前的節(jié)點(diǎn),代碼為:
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) //如果大于50條,則查找告警列表的table,得到
告警信息的子節(jié)點(diǎn),然后刪除多余的最早的告警信息
{
var tbody=document.getElementsByTagName( 'table' )[4].childNodes[0];
for ( var i=1;i<trlength+length-50;i++)
{
var tr=tbody.childNodes[i];
tr.parentNode.removeChild(tr);
}
然后插入新的告警信息,
for ( var i=0;i<length;i++)
{
var onewarnlist=req.responseXML.getElementsByTagName( 'warnlist' )[i].childNodes;
if (onewarnlist[0].firstChild.nodeValue==0)
{
var leave= "企業(yè)級(jí)告警" ;
}
else {
var leave= "運(yùn)營(yíng)商級(jí)告警" ;
}
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= "<font color='red'>已處理</font>" ;
}
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 = '<font color="#FF0000">' +warnvalue+ '</font>' ;
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,一切大功告成,以下是最終效果

- 基于spring-boot和docker-java實(shí)現(xiàn)對(duì)docker容器的動(dòng)態(tài)管理和監(jiān)控功能[附完整源碼下載]
- Java實(shí)現(xiàn)實(shí)時(shí)監(jiān)控目錄下文件變化的方法
- Java使用WatchService監(jiān)控文件內(nèi)容變化的示例
- Java實(shí)時(shí)監(jiān)控日志文件并輸出的方法詳解
- java獲取redis日志信息與動(dòng)態(tài)監(jiān)控信息的方法
- java實(shí)現(xiàn)文件變化監(jiān)控的方法(推薦)
- SHELL腳本監(jiān)控JAVA進(jìn)程的代碼
- Java服務(wù)器主機(jī)信息監(jiān)控工具類的示例代碼
相關(guān)文章
解決創(chuàng)建springboot后啟動(dòng)報(bào)錯(cuò):Failed?to?bind?properties?under‘spri
在Spring?Boot項(xiàng)目中,application.properties和application.yml是用于配置參數(shù)的兩種文件格式,properties格式簡(jiǎn)潔但不支持層次結(jié)構(gòu),而yml格式支持層次性,可讀性更好,在yml文件中,要注意細(xì)節(jié),比如冒號(hào)后面需要空格2024-10-10
Java創(chuàng)建數(shù)組的3種方式代碼舉例
數(shù)組是相同類型數(shù)據(jù)的有序集合,數(shù)組描述的是若干個(gè)相同類型的數(shù)據(jù)按照一定的先后次序排列組合而成,其中每一個(gè)數(shù)據(jù)稱為數(shù)組的元素,可以通過(guò)下標(biāo)進(jìn)行訪問(wèn),這篇文章主要給大家介紹了關(guān)于Java創(chuàng)建數(shù)組的3種方式,需要的朋友可以參考下2024-01-01
spring項(xiàng)目對(duì)某條單據(jù)進(jìn)行加鎖處理的方法
這篇文章主要給大家介紹了關(guān)于spring項(xiàng)目對(duì)某條單據(jù)進(jìn)行加鎖處理的相關(guān)資料,用于對(duì)工單單據(jù)進(jìn)行加鎖和解鎖處理,以防止多用戶同時(shí)編輯同一單據(jù),前端傳遞參數(shù)包括單據(jù)ID、類型、鎖超時(shí)時(shí)間等,后端通過(guò)Redis實(shí)現(xiàn)鎖機(jī)制,需要的朋友可以參考下2024-11-11
Mybatis通過(guò)數(shù)據(jù)庫(kù)表自動(dòng)生成實(shí)體類和xml映射文件
這篇文章主要介紹了Mybatis通過(guò)數(shù)據(jù)庫(kù)表自動(dòng)生成實(shí)體類和xml映射文件的操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-07-07
java代碼如何實(shí)現(xiàn)存取數(shù)據(jù)庫(kù)的blob字段
這篇文章主要介紹了java代碼如何實(shí)現(xiàn)存取數(shù)據(jù)庫(kù)的blob字段問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2025-04-04
springboot-jta-atomikos多數(shù)據(jù)源事務(wù)管理實(shí)現(xiàn)
本文主要介紹了springboot-jta-atomikos多數(shù)據(jù)源事務(wù)管理實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-03-03
關(guān)于Redis鍵值出現(xiàn)\xac\xed\x00\x05t\x00&錯(cuò)誤的解決方法
這篇文章主要介紹了關(guān)于Redis鍵值出現(xiàn)\xac\xed\x00\x05t\x00&的解決方法,出現(xiàn)該問(wèn)題的原因是, redis template向redis存放使用java對(duì)象序列化的值,序列化方式和string的一般方式不同,需要的朋友可以參考下2023-08-08

