为了能在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.
除了时间之外,每行开头都有描述+分号的头,然后是数据+单位+描述,而且后面带两空行。
但是我首先想到的问题不是怎么处理它们,而是资源消耗。其实这个问题我早就开始考虑了。
首先是创建线程的成本。这个成本很大,占用10%以上的CPU,但只是在一开始Create的时候产生,之后CPU的占用几乎是top的基本占用,这是基本成本,可以不用削减,因此也不可以用“查询一次调用一次命令”的方法。
然后是线程循环读取BufferedReader时候的成本。我在查找资料的时候,从 关于java中BufferedReader的read()及readLine()方法的使用心得 中了解到,BufferedReader.readLine()是一个阻塞函数,因此并不需要自己写代码判断是否是空行然后挂起线程。
误以为readLine()是读取到没有数据时就返回null(因为其它read方法当读到没有数据时返回-1),而实际上readLine()是一个阻塞函数,当没有数据读取时,就一直会阻塞在那,而不是返回null;因为readLine()阻塞后,System.out.println(message)这句根本就不会执行到,所以在接收端就不会有东西输出。
还有就是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实现