Sorry, your browser cannot access this site
This page requires browser support (enable) JavaScript
Learn more >

为了能在Linux上面用java获取系统的状态数值,我自己设计了一个SystemStatus工具。用于获取系统的CPU占用内存状态之类的。考虑到一般只用Linux服务器当J2EE容器服务器,所以这个工具只适配Linux系统。

##I.偶遇Process类
上网找不到什么合适的工具,只发现了别人的代码。原理是利用ProcessBuilder.start()和Runtime.exec()其中一个启动top之类的命令行工具,获取一个Process对象后对输出的字符进行处理,从而得出系统的状态参数。后来自己动手实验和询问朋友发现,不同的top版本,参数列表不同,这影响到程序的通用性(我在Mac OS X上开发,然而服务器是openSUSE)。最后我选择自己参照这个原理设计一个。

首先写一个不断Print出top的信息的线程,研究它输出的字符串。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
Thread thread = new Thread(){
public void run(){
Process process = null;
try {
process = new ProcessBuilder("top","-l","0","-n","0").start();
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(process.getInputStream()));
while (true){
String _line;
do{
_line = bufferedReader.readLine();
System.out.println(_line);
}while (!"".equals(_line));
sleep(1000);
}
} catch (IOException | InterruptedException e) {
e.printStackTrace();
}finally {
if(process != null){
process.destroy();
}
}
}
};
thread.setDaemon(true);
thread.start();

在Mac OS X上输出是这个样子的

1
2
3
4
5
6
7
8
9
10
11
12
Processes: 306 total, 2 running, 4 stuck, 300 sleeping, 1538 threads 
2016/03/19 01:48:27
Load Avg: 2.54, 2.02, 1.85
CPU usage: 7.52% user, 7.52% sys, 84.95% idle
SharedLibs: 143M resident, 21M data, 12M linkedit.
MemRegions: 72549 total, 2138M resident, 70M private, 801M shared.
PhysMem: 8153M used (2315M wired), 37M unused.
VM: 873G vsize, 527M framework vsize, 6078855(0) swapins, 6516083(0) swapouts.
Networks: packets: 6311608/2054M in, 4686357/778M out.
Disks: 1729987/56G read, 1830126/68G written.


除了时间之外,每行开头都有描述+分号的头,然后是数据+单位+描述,而且后面带两空行。

但是我首先想到的问题不是怎么处理它们,而是资源消耗。其实这个问题我早就开始考虑了。

  1. 首先是创建线程的成本。这个成本很大,占用10%以上的CPU,但只是在一开始Create的时候产生,之后CPU的占用几乎是top的基本占用,这是基本成本,可以不用削减,因此也不可以用“查询一次调用一次命令”的方法。

  2. 然后是线程循环读取BufferedReader时候的成本。我在查找资料的时候,从 关于java中BufferedReader的read()及readLine()方法的使用心得 中了解到,BufferedReader.readLine()是一个阻塞函数,因此并不需要自己写代码判断是否是空行然后挂起线程。

    误以为readLine()是读取到没有数据时就返回null(因为其它read方法当读到没有数据时返回-1),而实际上readLine()是一个阻塞函数,当没有数据读取时,就一直会阻塞在那,而不是返回null;因为readLine()阻塞后,System.out.println(message)这句根本就不会执行到,所以在接收端就不会有东西输出。

  3. 还有就是BufferedReader缓冲区满问题,我并没有这方面的开发经验,不知道BufferedReader的缓冲区会不会满,会的话,满了之后发生什么事。依旧上网查资料。从java io系列23之 BufferedReader详解的源码分析里得出,BufferedReader是不会爆的……然后我就继续向上查找,InputStreamReader呢?但是发现,已经到了尽头了(JVM托管的东西,就先不要操心用下去,出了问题再处理,这样能省点时间嗯……)所以可以放心继续用下去。

于是开始设计获取的整个过程

  • 首先有一个模型类,去盛放这些数据
  • 然后有一个对应的工厂,去产生数据

但是问题来了,输出结果依赖top的版本。为了以后的兼容性,我是先有个服务线程,SystemStatusSession,把输出结果通过render整理到HashMap后再用依赖配置的工厂类序列化数据到模型类。然后不同的top版本命令格式也不同,所以有一个命令参数传进去才能让top有正确的输出。但前期为了降低难度,先不用render和依赖配置的工厂类去实现数据的序列化。暂时有以下的类

  • [Interface]TopCommand 生成top的命令用,参数类,用的时候再写实现类
  • [Class]SystemStatusSession 这个类负责使用render整理top的输出到HashMap,一个top线程对应一个状态会话。
  • [Class]SystemStatus 盛放数据用的模型类
  • [Class]SystemStatusFactory 工厂类,依赖配置序列化HashMap的数据到模型类

##II.但是……

当我尝试使用Linux上的top开发这个工具的时候,遇到了个很严重的问题……Linux的top并不能像OS X上的那样指定输出几行,然后就GG了,而且top这个工具,最新版的顶层直接变成了进度条样式而且无法更改。

其实只要像我之前说的那样,用render/factory模式,是可以通过配置文件适配各个版本的top的。但是,这样会产生大量的配置文件。而且工厂运行起来复杂:首先判断top版本,获取配置文件,然后各种循环各种判断,把top的输出字符串转换成类。就是为了获取个状态,付出这么大的代价,最后又影响了这个状态,怎么越来越有量子物理的感觉了……

##III.抛弃Top,使用Python脚本

最后又一头扎进Google的怀里继续找……

查看了很多的方法之后,我退回去想:既然是通过Process去执行一些东西得到返回值,那么……我想起了我在学Python,网页上的系统状态显示的视线也是用的脚本语言,那就干脆用Python写一个好了。询问朋友,得知Python的psutil,用这个玩意获取这些系统信息,完全就是一句话的事情。当我使用了一下psutil之后简直就是想揍自己……不过在搞技术的道路上谁会一帆风顺呢~

psutil的文档在这里:psutil 4.1.0 : Python Package Index

根据文档,我写了个小Demo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#!/usr/bin/python
import psutil
import time
import os
import sys

try:
while 1:
print "cpu_percent:",psutil.cpu_percent(interval=1)
print "logical_cpu_count:",psutil.cpu_count()
print "real_cpu_count:",psutil.cpu_count(logical=False)
v_memstatus=psutil.virtual_memory()
print "memory_virtual_total:",v_memstatus.total
print "memory_virtual_abailable:",v_memstatus.available
print "memory_virtual_percent:",v_memstatus.percent
print "memory_virtual_used:",v_memstatus.used
print "memory_virtual_free:",v_memstatus.free
print "memory_virtual_active:",v_memstatus.active
print "memory_virtual_inactive:",v_memstatus.inactive
#print "memory_virtual_buffers =",v_memstatus.buffers
s_memstatus=psutil.swap_memory()
print "memory_swap_total:",s_memstatus.total
print "memory_swap_used:",s_memstatus.used
print "memory_swap_free:",s_memstatus.free
print "memory_swap_percent:",s_memstatus.percent
sys.stdout.flush();
except (KeyboardInterrupt, SystemExit):
pass

代码大致上没啥问题,最重点的一句是sys.stdout.flush();,我就是卡在这里一段时间。从关于Java调用外部程序即时输出的一些收获中得知,原来是Python的程序要主动调用flush()才能把输出从缓冲区输出。

里面那句“经过测试,我们发现一个问题,如果外部程序在输出信息时,没有用flush也会出现问题”点醒了我。我才发现原来是python和C程序中使用print或者printf输出也是有缓冲机制的。所以process的getInputStream()并不能立即获得输出结果(因为此时结果还保留在C或python进程输出的缓冲区中),所以需要在python中的print后面加入sys.stdout.flush()才行。如此,问题解决。

psutil的安装需要python-devel(或者叫python-dev),gcc也是必须的,我的服务器没有,所以才意识到这个问题。

因为输出格式完全可以自定义,所以可以省去render,直接把这些整理到hashmap里,最后session的线程代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
private class QueryServerThread extends Thread{
HashMap<String,String> map = infoMap;

boolean isRun = true;

public void run(){
try {
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(topProcess.getInputStream()));
String _line;
while (isRun){
do{
_line = bufferedReader.readLine();
if(_line == null) break;
_line = _line.trim().replace("\"","");
String[] strings = _line.split(":");
map.put(strings[0],strings[1]);
}while (true);
sleep(700);
}
} catch (IOException | InterruptedException e) {
e.printStackTrace();
} finally {
if(topProcess != null){
topProcess.destroy();
}
}
}
}

写一个main来测试一下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public static void main(String[] args) {
Thread thread = new Thread(){
public void run(){
try {
SystemStatusSession systemStatusSession = new SystemStatusSession("ex-lib/iostatu.py");
HashMap<String,String> hashMap = systemStatusSession.getInfoMap();
while (true){
for(String s : hashMap.keySet()){
System.out.printf("Key:%s | Value:%s\n",s,hashMap.get(s));
}
sleep(1000);
}
}catch (Exception e){
e.printStackTrace();
}
}
};
thread.start();
}

输出结果

1
2
3
4
5
6
7
8
9
10
Key:cpu_percent | Value: 12.2
Key:memory_virtual_inactive | Value: 2061193216
Key:memory_virtual_percent | Value: 75.5
Key:logical_cpu_count | Value: 4
Key:memory_swap_total | Value: 2147483648
Key:memory_virtual_abailable | Value: 2108739584
Key:memory_virtual_free | Value: 47546368
Key:memory_virtual_used | Value: 6812692480
Key:memory_virtual_total | Value: 8589934592
Key:real_cpu_count | Value: 2

这样的话,就可以很方便得用工厂来创建SystemStatus类了。虽说不算灵活,但是只要更改这个python脚本就能获得各种系统参数。

##IV.抛弃Java,全用Python实现

评论