java程序运行监控byteman 系列文章
1、【java程序运行监控byteman】之安装部署和入门示例
2、【java程序运行监控byteman】使用示例(运行中方法耗时监控、javaagent监控、jvm监控、规则的检查、是否运行以及查看等)
3、【java程序运行监控byteman】Byteman脚本详解及javaagent和byteman结合使用详细示例
本文介绍了byteman的其他几种应用场景及示例,比如javaagent、监控jvm、bmjava命令、如何查看运行的规则、检查规则的正确性、检查规则是否在运行中等。
本文分为2个部分,即运行中方法耗时监控和其他示例。
一、 统计方法耗时(程序运行中)
该类是实时显示控制台输入的结果以及输入的是end时退出。
程序运行中是表示程序处于运行中进行某些方法的耗时监控。
1、创建示例类
package com.win.byteman;
import java.io.BufferedReader;
import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
/**
* @author alan 2018年12月26日
*/
public class WorkerDemo {
public static void main(String[] args) {
WorkerDemo workerDemo = new WorkerDemo();
DataInputStream dataInputStream = new DataInputStream(System.in);
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(dataInputStream));
try {
String next;
while ((next = bufferedReader.readLine()) != null) {
System.out.println(
"==========================================开始调用==========================================");
workerDemo.working(next);
System.out.println(
"==========================================结束调用==========================================");
if (next.contains("end")) {
break;
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
private void working(String input) {
System.out.println("输入内容:" + input);
// try {
// TimeUnit.SECONDS.sleep(2);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
}
}
2、建立监控
1)、获取运行程序的进程号
在cmd中输入jps,结果如下图:
2)、建立监控
在cmd中执行命令
bminstall -b -Dorg.jboss.byteman.tranform.all 13456
3、编制脚本
WorkerDemo.btm
文件存储在:D:\application\byteman-download-4.0.5\sample\testing
规则如下:
RULE trace worker time start
CLASS WorkerDemo
METHOD working(String)
AT ENTRY
IF TRUE
DO createTimer($0)
ENDRULE
RULE trace worker time stop
CLASS WorkerDemo
METHOD working(String)
AT EXIT
IF TRUE
DO traceln(" working method spent [" + getElapsedTimeFromTimer($0) + "] ms ");
deleteTimer($0);
ENDRULE
4、检测脚本
脚本检测需要建立起脚本与需要监控的类之间的关系。
目录需要切换到classes所在的目录,btm文件所在的目录以实际的为准,本示例中所在的地址是D:\application\byteman-download-4.0.5\sample\testing。
在cmd中将目录cd到classes所在的目录,然后执行命令
bmcheck -p com.win.byteman -cp . D:\application\byteman-download-4.0.5\sample\testing\WorkerDemo.btm
如果执行成功则如下图所示
否则则可能有异常抛出,例如
该异常的原因是在btm所在的目录找不到classes对应的包目录。
当然,如果方便起见,也可以将btm放在与classes目录下,统一切换到classes目录下操作即可。
5、提交脚本
同样,需要在classes目录下提交脚本,执行命令
bmsubmit -i D:\application\byteman-download-4.0.5\sample\testing\WorkerDemo.btm
如果成功则如下图所示
6、验证
在运行程序控制台输入任意参数,查看其结果
7、卸载脚本
卸载脚本就是不再对程序进行监控。在cmd中执行命令
bmsubmit -u D:\application\byteman-download-4.0.5\sample\testing\WorkerDemo.btm
二、其他示例
1、javaagent示例(程序未运行)
1)、创建示例类
package com.win.byteman;
class App1
{
public static void main(String[] args)
{
for (int i = 0; i < args.length; i++) {
System.out.println(args[i]);
}
}
}
2)、编译及测试
通过cmd命令行进行编译。
cd到classes所在的目录(即包文件夹所在的目录)或者直接到类所在的目录进行编译,即
D:\application\byteman-download-4.0.5\sample\testing
或
D:\application\byteman-download-4.0.5\sample\testing\com\win\byteman
执行编译命令:
Javac com/win/byteman/App1.java
# 或
Java App1.java
完成上述步骤后,在cmd中运行编译后的classes文件,执行命令:
Java com.win.byteman.App1 testing
执行java命令时需要带上包名,同时需要切换目录至classes所在的目录
3)、编制脚本
该脚本放在与classes目录下,当然也可以放在其他地方,但是在执行脚本的时候需要加上脚本所在目录。
脚本中如果有中文,会出现乱码,具体不知道是规则脚本中不能有中文字符还是其他的原因。
脚本名称:app1.btm
RULE trace main entry
CLASS App1
METHOD main
AT ENTRY
IF true
DO traceln("entering main")
ENDRULE
RULE trace main exit
CLASS App1
METHOD main
AT EXIT
IF true
DO traceln("exiting main")
ENDRULE
关于脚本说明:
该脚本有针对App1类的main方法两个规则。
第一个规则是进入该方法时输出:entering main
第二个规则是退出该方法时输出:exiting main
This script contains two rules both of which specify code to be injected into METHOD main of CLASS AppMain.
The first rule is injected AT ENTRY i.e. at the start of the method. The code to be injected is the IF DO part. In the first rule it calls builtin operation traceln(String) which prints its argument to System.out() followed by a newline .
The second rule is injected AT EXIT i.e. at the point(s) where control returns from method main.
Both rules specify condition IF true which means the DO part is always executed. You can supply a different Java expression if you want to compute whether or not the actions following DO are run.
4)、运行脚本
运行脚本的命令根据实际需要而定。
运行命令时需要关注btm文件所在的位置,本示例是放在classes所在的目录,如果不在该目录下则直接带上绝对地址即可。
当然在运行脚本前也可以通过bmcheck命令检查脚本是否合法,具体参考本文的第一个示例中的检测脚本章节。
The -javaagent option used to run the progarm with these Byteman rules is supported by all the main desktop and server JVMs. The syntax is
-javaagent : path_to_agent_jar = agent_option1 , agent_option_2 , . . .
The Linux command to run the program with Byteman and this rule set is
java -javaagent:${BYTEMAN_HOME}/lib/byteman.jar=script:app1.btm com.win.byteman.App1 foo bar baz
or on Windows
java -javaagent:%BYTEMAN_HOME%\lib\byteman.jar=script:app1.btm com.win.byteman.App1 testing again
命令说明:
告诉虚拟机在哪里找到agent的jar,本机是%BYTEMAN_HOME%\lib\byteman.jar
本示例中仅是the = sign,指定规则script:app1.btm。agent通过加载的btm文件读取操作,如果需要加载更多的btm文件则配置更多的btm文件即可。
-javaagent是java命令的一个参数,com.win.byteman.App1 foo bar baz 是java执行的具体的类及传递的参数。
The value after the : tells the JVM where to find the Byteman agent jar, ${BYTEMAN_HOME}/lib/byteman.jar. We only supply one agent option following the = sign, script:app1.btm, specifying the location of the Byteman rule script. The Byteman agent reads this option then loads and injects the rules from file app1.btm. If we wanted to load more than one script we could provide multiple script:file agent options, separated by a comma.
When you pass -javaagent on the command line the Byteman agent starts executing during JVM startup before the application is run. So, it will read the rules and inject side effects into App1.main() before this method gets called. The output should be as follows.
5)、测试
运行脚本的结果正常的情况如下图:
异常的结果(之一)如下图:
2、监控jvm的类
1)、创建示例类
package com.win.byteman;
class App2
{
public static void main(String[] args)
{
for (int i = 0; i < args.length; i++) {
final String arg = args[i];
Thread thread = new Thread(arg) {
public void run() {
System.out.println(arg);
}
};
thread.start();
try {
thread.join();
} catch (Exception e) {
}
}
}
}
2)、编制脚本
该脚本放在与classes目录下,当然也可以放在其他地方,但是在执行脚本的时候需要加上脚本所在目录。
脚本名称:thread.btm
RULE trace thread start
CLASS java.lang.Thread
METHOD start()
IF true
DO traceln("*** start for thread: "+ $0.getName())
ENDRULE
规则说明:
该规则是监控Thread的start方法,输出跟踪的内容。
This rule is injected into METHOD start() of the JVM runtime CLASS java.lang.Thread. It prints a trace message pasted together using the String + operator. Now start() is an instance method so when it is called there is a specific Thread instance which is the target for the method call.
The special variable $0 can be used to refer to this target object. start() has no arguments but in other cases where there are the arguments to the call which triggers the rule they can be referenced using $1, $2 etc.
Byteman knows that $0 references a Thread object so it type checks the method invocation $0.getName() and verifies that the result type is String. The injected code makes a call this method, appends the result to the constant String and passes the result to method traceln() to be written to System.out.
3)、运行脚本
运行脚本,由于是监控jvm的类,所以需要按照如下的命令进行。
To run this program using the new script we modify the script: agent option to reference file thread.btm. But that’s not enough if we want to inject code into JVM classes. We need to provide some extra options/arguments. On Linux:
java -javaagent:${BYTEMAN_HOME}/lib/byteman.jar=script:thread.btm,boot:${BYTEMAN_HOME}/lib/byteman.jar -Dorg.jboss.byteman.transform.all com.win.byteman.App2 foo bar baz
or on Windows:
java -javaagent:%BYTEMAN_HOME%\lib\byteman.jar=script:thread.btm,boot:%BYTEMAN_HOME%\lib\byteman.jar -Dorg.jboss.byteman.transform.all com.win.byteman.App2 foo bar baz
本示例是针对jvm的Thread进行监控,所以通过bootstarp classloader加载。
Class Thread is a JVM class which means it gets loaded by the bootstrap classloader. Byteman can only inject code into this class if the Byteman agent classes are also loaded by the bootstrap classloader. The extra agent option boot:${BYTEMAN_HOME}/lib/byteman.jar is appended after the script option (note the comma used as a separator). This makes sure that the byteman agent jar is installed into the bootstrap classpath.
Class Thread also happens to be in package java.lang. Normally Byteman is super-cautious and will not inject code into this package in case it beaks the JVM. If you really want to change methods of classes in this package you need to define the system property org.jboss.byteman.transform.all.
4)、验证
运行脚本的结果正常的情况如下图:
3、Bmjava命令的应用
例子参考监控jvm示例和本文中的统计方法耗时示例。
On Linux you can use the command script bmjava.sh to wrap up the -javaagent options for you. It looks very much like the java command but it accepts Byteman rule scripts on the command line and bundles them up as -javaagent script: options. It also automatically bundles in the byteman jar using the boot: agent option. So, using bmjava.sh the previous command line simplifies to
bmjava.sh -i thread.btm com.win.byteman.App2 foo bar baz
Notice that the flag used there is -i for load.
On Windows if you are using byteman release 2.0.1 or later there is an equivalent script called bmjava.bat which you can execute using command bjava. So the previous command simplifies to
bmjava -i thread.btm com.win.byteman.App2 foo bar baz
Note that you need to add the installed bin directory ${BYTEMAN_HOME}/bin to your path in orderto be able to execute the script by name (on windows use %BYETMAN_HOME%/bin)
4、注入正在运行的类(jvm类)
本示例是监控运行中的Thread,监控普通的类是否可以使用本方式没有测试(理论上是可以的),同时,监控运行中的类也可以采用本文的统计方法耗时(程序运行中)部分的说明。
1)、创建示例类
package com.win.byteman;
import java.io.DataInputStream;
class App3
{
public static void main(String[] args)
{
try {
DataInputStream in = new DataInputStream(System.in);
String next = in.readLine();
while (next != null && next.length() > 0 && !next.contains("end")) {
final String arg = next;
Thread thread = new Thread(arg) {
public void run() {
System.out.println(arg);
}
};
thread.start();
try {
thread.join();
} catch (Exception e){
}
next = in.readLine();
}
} catch (Exception e) {
}
}
}
编译略。
2)、编制脚本
该文件在classes所在目录。
脚本名称:thread.btm
RULE trace thread start
CLASS java.lang.Thread
METHOD start()
IF true
DO traceln("----------------------- start for thread: "+ $0.getName())
ENDRULE
3)、建立监控
本示例中建立监控的方式,与本文中其他的方式有所不同,理论上应该都可以,但没有做测试。
The agent listener is enabled on the java command line using agent option listener:true. On Linux the command is this
java -javaagent:${BYTEMAN_HOME}/lib/byteman.jar=listener:true,boot:${BYTEMAN_HOME}/lib/byteman.jar -Dorg.jboss.byteman.transform.all com.win.byteman.App3
or on Windows
java -javaagent:%BYTEMAN_HOME%\lib\byteman.jar=listener:true,boot:%BYTEMAN_HOME%\lib\byteman.jar -Dorg.jboss.byteman.transform.all com.win.byteman.App3
建立监控成功后测试,如下图:
4)、提交脚本
java -classpath %BYTEMAN_HOME%\lib\byteman-submit.jar org.jboss.byteman.agent.submit.Submit -l thread.btm
提交成功,则出现如下界面
5)、验证
在cmd中输入需要测试的内容,测试内容如下图红框中所示
5、如何查看在运行的规则
Running bmsubmit.sh again we can see that the rule has been successfully compiled.
执行命令:
bmsubmit.sh
输出:
# File thread.btm line 4
RULE trace thread start
CLASS java.lang.Thread
METHOD start()
AT ENTRY
IF true
DO traceln("*** start for thread: "+ $0.getName())
ENDRULE
Transformed in:
loader: sun.misc.Launcher$AppClassLoader@5acac268
trigger method: java.lang.Thread.start() void
compiled successfully
This time there is an extra line at the end of the output saying compiled successfully. This means the rule has been type-checked and executed. Type checking only happens when the rule is first triggered – in this case when Thread.start() is called after typing in the line containing the word baz. If type checking had failed then execution of the injected code would be inhibited and the listing would include details of the type error. Injected code is only removed when the rule is unloaded.
6、如何在一个运行的程序中安装一个代理
参考本文的使用示例中统计方法耗时(运行中)的例子。
7、如何检查规则的正确性
使用bmcheck命令进行检查。
1)、示例规则-thread.btm
由于本示例使用的是Thread,jvm的类,所以不需要。
RULE trace thread start
CLASS java.lang.Thread
METHOD start()
IF true
DO traceln("----------------------- start for thread: "+ $0.getName())
ENDRULE
另外一种形式的规则设置
RULE trace thread start
CLASS Thread
METHOD start()
IF true
DO traceln("----------------------- start for thread: "+ $0.getName())
ENDRULE
On Linux you run command script bmcheck.sh
>bmcheck.sh thread.btm
#或者
>bmcheck.sh -p java.lang -cp . thread.btm
checking rule trace thread start
parsed rule “trace thread start” for class java.lang.Thread
type checked rule “trace thread start”
TestScript: no errors
On Windows if you are usiing release 2.0.1 or later use the equivalent script bmcheck.bat.
> bmcheck thread.btm
# 或者
> bmcheck -p java.lang -cp . thread.btm
checking rule trace thread start
parsed rule “trace thread start” for class java.lang.Thread
type checked rule “trace thread start”
TestScript: no errors
2)、示例规则-App1.btm
示例类:
package com.win.byteman;
class App1
{
public static void main(String[] args)
{
for (int i = 0; i < args.length; i++) {
System.out.println(args[i]);
}
}
}
编译完成后,做下面的操作。
This rule does not use the fully qualified name of the target class. That’s ok because the agent notices when com.win.byteman.App1 gets loaded and realises that the rule applies to it. But when we are checking rules offline the application is not run so Byteman does not know which package to use to lookup the class mentioned in the rule. The -p flag can be used to suggest one or more package prefixes for the type checker to try.
RULE trace main entry
CLASS App1
METHOD main
AT ENTRY
IF true
DO traceln("进入 main")
ENDRULE
RULE trace main exit
CLASS App1
METHOD main
AT EXIT
IF true
DO traceln("退出 main")
ENDRULE
或者另外一种形式
RULE trace main entry
CLASS com.win.byteman.App1
METHOD main
AT ENTRY
IF true
DO traceln("进入 main")
ENDRULE
RULE trace main exit
CLASS com.win.byteman.App1
METHOD main
AT EXIT
IF true
DO traceln("退出 main")
ENDRULE
执行如下命令
bmcheck -p com.win.byteman -cp . app1.btm
#或者
bmcheck app1.btm
3)、验证
略
8、如何确定规则是否运行
1)、示例类
package com.win.byteman;
class App1
{
public static void main(String[] args)
{
for (int i = 0; i < args.length; i++) {
System.out.println(args[i]);
}
}
}
2)、规则
app1.btm
RULE trace main entry
CLASS com.win.byteman.App1
METHOD main
AT ENTRY
IF true
DO traceln("entry main")
ENDRULE
RULE trace main exit
CLASS com.win.byteman.App1
METHOD main
AT EXIT
IF true
DO traceln("exit main")
ENDRULE
Sometimes you may be unsure whether your rules are being injected correctly. Perhaps the target method is never executed. Perhaps the rule condition is always false. Or maybe a parse or type error is stopping them being executed. If you run Byteman in verbose mode you will see trace output telling you what Byteman is doing. Verbose mode is enabled by setting a system property
执行命令:
java -Dorg.jboss.byteman.verbose -javaagent:%BYTEMAN_HOME%/lib/byteman.jar=script:app1.btm com.win.byteman.App1 foo bar baz
The trace is quite verbose but all the information you need is there if you look for it.
- When class AppMain is loaded the trigger locations for each rule is identified and the rule code is injected
Next the main method gets called and you can see an execute message for the entry rule (you can ignore the messages about helper activation and rule installation just now or read the Ptrogrammer’s Guide if you want them explained)
- The execute message is followed by the the enter rule’s trace message.
- The main method prints all its its ouptut
- Just before it returns there is another execute message for the exit rule (again, just ignore the messages about rule installation)
- The second execute message is followed by the exit rule’s trace message .
以上,本文介绍了byteman的其他几种应用场景及示例,比如javaagent、监控jvm、bmjava命令、如何查看运行的规则、检查规则的正确性、检查规则是否在运行中等。