最近重新设计一个程序,这个程序有一个功能是监视其他进程的内存使用情况,并且在满足一定条件的时候促发警告给用户。一般的条件是,内存超过某个阀值。可是如果希望这个条件可以复杂些,譬如超过2G时并且持续增长2天,就告警;或者如果小于1G,并且在10天以内总体趋势是增长的,也告警。总之,这个条件可以支持很复杂的条件,并且需要动态修改(主模块不重启的情况下),所以我的选择是结合Java和Jython。其实Java上面可以选用JRuby, Scala, Groovy或者Javascript,选择Jython的理由有以下几点:
[1] 单位有一些项目已经使用了Python;
[2] Scala和Groovy会的人太少,太前卫了;
[3] Javascript很好,不过考虑到也许今后所有Java的程序扩展都会使用一种脚本语言,明显Javascript系统的交互能力太差。譬如链接数据库,处理文件啊
[4] Ruby是我太难割舍的,因为我喜欢Ruby,其实Ruby企业版已经比较稳定了,而且JRuby也有官方的支持。只不过,单位中已经有项目使用了Python,如果冒然的引入Ruby,势必会增加系统的复杂度。
[5] 即使有JPerl,JTcl,我也不会选用。原因很简单,我需要更好的模块化面向对象的脚本,面向对象不是银弹,可是大家都很熟悉面向对象了,而且实践证明面向对象鼓励大家写出好维护的代码(结合设计模式)。
[6] C++可以和Python集成(Boost.Python)(当然了,Lua更方便与C++集成,可是Lua会的人也太少)。
[7] 可以用Java写,然后用反射,可以做。不过我觉得脚本更好,省掉了编译的过程。(因为在现场,不能保证你的Unix上面装了JDK,只能保证一定有JRE)。
[8] 使用的是Java5,不是Java6,所以JSR233(好像是这个吧)不是直接可以用的。
因为这个项目是单位的项目,所以我把我做得Prestudy的小程序拿出来和大家共享一下解决方案。
我总喜欢用计算器这样的小例子来做PreStudy,呵呵。
首先设计一个计算器的接口ICalc(用Java写的), 如下:
package com.gmail.at.ankyhe.calc;
public interface ICalc {
void pushData(int[] data);
int getResult();
}
package com.gmail.at.ankyhe.calc;
public interface ICalc {
void pushData(int[] data);
int getResult();
}
很显然,这个接口里面有两个函数。第一个函数是pushData,主模块调用这个函数把数据送给具体的Calc类(这个用Jython实现),然后通过getResult()得到结果,思路很简单。为了能够动态的发现更新的文件,我们把送数据给Calc并且从Calc得到结果的任务放到一个独立的线程里面,如下:
package com.gmail.at.ankyhe.calc;
import java.util.Arrays;
import java.util.Date;
import java.util.Random;
import java.util.concurrent.TimeUnit;
public class CalcTask implements Runnable {
public CalcTask(ICalc aCalc) {
calc = aCalc;
rand = new Random((new Date()).getTime());
arr = new int[3];
}
public ICalc setCalc(ICalc aCalc) {
ICalc tmp = calc;
synchronized(this) {
calc = aCalc;
}
return tmp;
}
public void run() {
while (true) {
for(int i = 0 ; i < arr.length; ++i) {
arr[i] = rand.nextInt() % 50;
}
int rst = 0;
synchronized (this) {
calc.pushData(arr);
rst = calc.getResult();
}
System.out.println(String.format("Input: %s --- Output: %d",
Arrays.toString(arr), rst));
try {
TimeUnit.SECONDS.sleep(50);
} catch (InterruptedException ex) {
ex.printStackTrace();
}
}
}
private ICalc calc;
private Random rand;
private int[] arr;
}
然后主线程里面负责扫描Jython脚本和加载它,如下:
package com.gmail.at.ankyhe.calc;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.Reader;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import org.python.util.PythonInterpreter;
public class Main {
public static ICalc getCalc() {
Reader file = null;
file = null;
try {
file = new FileReader(filename);
} catch(FileNotFoundException ex) {
System.out.println("Can't find the file.");
System.exit(1);
}
try {
Reader in = new BufferedReader(file);
PythonInterpreter interpreter = new PythonInterpreter();
org.python.core.PyObject code = interpreter.compile(in);
interpreter.exec(code);
ICalc calc = (ICalc)interpreter.get("calc", ICalc.class);
return calc;
} finally {
try {
file.close();
} catch(IOException ex) {
System.out.print("Fail to close file");
System.exit(1);
}
}
}
public static long getLastModified() {
long rst = 0L;
File f = null;
try {
f = new File(filename);
rst = f.lastModified();
} catch(NullPointerException ex) {
ex.printStackTrace();
rst = -1;
}
return rst;
}
public static void main(String[] args) throws InterruptedException {
// initialization
if (args.length == 0) {
System.out.println("Usage java -jar calc.jar <scriptfile>.");
System.exit(1);
}
filename = args[0];
ICalc calc = null;
if (lastmodified == 0) {
lastmodified = getLastModified();
if (lastmodified == -1) {
System.out
.println(String.format("The file %s doesn't exit.", filename));
}
calc = getCalc();
}
if(calc == null) {
System.out.print(String.format("The script file has no valid calc.",
filename));
System.exit(2);
}
// launch calc task
CalcTask ct = new CalcTask(calc);
ExecutorService es = Executors.newCachedThreadPool();
es.execute(ct);
es.shutdown();
// main thread check the new script every 10 seconds
while(true) {
long modified = getLastModified();
if (modified <= lastmodified) {
// do nothing
}
else {
lastmodified = getLastModified();
ICalc newCalc = getCalc();
if (newCalc == null) {
System.out.print(String.format("The new script file has no valid calc.",
filename));
}
else {
System.out.println("Ready to use new script file.");
ct.setCalc(newCalc);
System.out.println("Use new script file.");
}
}
TimeUnit.SECONDS.sleep(10);
}
}
public static String filename = null;
public static long lastmodified = 0;
}
核心的加载代码就是下面这一段:
PythonInterpreter interpreter = new PythonInterpreter();
org.python.core.PyObject code = interpreter.compile(in);
interpreter.exec(code);
ICalc calc = (ICalc)interpreter.get("calc", ICalc.class);
注意,在Jython脚本中的calc一定是一个ICalc子类的对象,否则上面最后一句会抛出异常。Jython代码如下:
import sys
# add sys path
def addSysPath():
sys.path.append("/Users/AnkyHe/proj/java/CalcPlugin/icalc.jar")
addSysPath()
from com.gmail.at.ankyhe.calc import ICalc
class MyCalc(ICalc):
def __init__(self):
pass
def pushData(self, arr):
self.arr = arr
def getResult(self):
return sum(self.arr)
#return min(self.arr)
calc = MyCalc()
'''
if __name__ == '__main__':
mycalc = MyCalc()
arr = [1, -100, 20]
mycalc.pushData(arr)
if sum(arr) == mycalc.getResult():
print('true')
else:
print('false')
print(mycalc.getResult())
'''
sys.path.append是把接口ICalc的jar文件加入到Jython的使用环境中,这样才可以import ICalc。注意最后的if __name__ == '__main__', 这里体现了用Jython的一个优点,可以通过Jython做测试。因为这只是一个Presutdy,主要任务是验证一下这个流程,所以可能有一些错误处理没有写得很全面。源代码和可执行文件(我在Mac上编译运行通过了)如附件,执行的时候请用:
java -cp ./icalc.jar -jar calcplugin.jar script/mycalc.py
编译的时候使用把icalc.jar和jython.jar加入到classpath中。
然后你修改mycalc.py,可以看到输出结果的改变。