Monitoring and debugging Java code based on BTrace

Btrace is a dynamic code tracking tool in Java. By writing btrace script, it can dynamically inject tracking code into the byte code of the target application program. By modifying the byte code, it can achieve the purpose of monitoring, debugging and positioning problems, and it is a powerful tool to solve online problems.

Github home page of BTrace project https://github.com/btraceio/btrace , the sample code in this article https://github.com/cellei/btrace-practice

Quick start

Let's use the example in the BTrace User's Guide to make a quick start:

import com.sun.btrace.annotations.*;

@BTrace
public class HelloWorld {
    @OnMethod(
        clazz="java.lang.Thread",
        method="start",
        location = @Location(Kind.ENTRY)
    )
    public static void func() {
        BTraceUtils.println("about to start a thread!");
    }
}

After downloading and decompressing the BTrace package, add the bin directory into the environment variable, execute the command BTrace < pid > helloworld.java, pid is the process number of the Java target program, which can be queried by using jps or ps command. In this way, the BTrace script program is executed. Every time a thread is started, the "about to start a thread!" will be printed out. The BTrace script has been executed normally, and there is no need to restart the target Java program.

If you want to exit BTrace, press ctrl+c, and then select 1. You can also use the code BTraceUtils.Sys.exit(0) in the script to exit.

Briefly analyze this code, @ BTrace annotation indicates that this is a BTrace script, @ OnMethod annotation and the parameter list of func method (when overloaded), specify the target code to be tracked, which is called Probe Point. The code in func method body is Trace Action, which is the code executed after tracking the target code.

Btrace command format: btrace < PID > < btrace script >, the result will be printed directly to the command line, or the result can be output to a file using the turn command, for example, btrace < PID > HelloWorld. Java > btrace.log

If the executed btrace script is a ". java" source file, the btrace command will be compiled into bytecode first. It is recommended to precompile it into a ". class" bytecode file before executing the compiled btrace script.

When writing BTrace script and precompiling, it is necessary to introduce BTrace jar package in the form of Maven local jar package as follows:

<dependency>
    <groupId>com.sun.btrace</groupId>
    <artifactId>btrace-client</artifactId>
    <version>1.3.11.2</version>
    <type>jar</type>
    <scope>system</scope>
    <systemPath>${basedir}/src/main/resources/lib/btrace-client.jar</systemPath>
</dependency>

Usage restriction

Because the BTrace script will inject bytecode into the online code, even after exiting, the injected bytecode will not be recovered, and the injected monitoring code will still be executed, so there are some restrictions that must be observed when using BTrace. Because of these restrictions, BTrace can be used in online debugging more safely.

  • You cannot create objects, arrays, catch and throw exceptions in a BTrace script.
  • Cannot call instance or static method, only static method in com.sun.btrace.BTraceUtils can be called.
  • You cannot assign a class or object of the target program to a static or instance field.
  • Cannot define external, internal, anonymous, local class.
  • Synchronization synchronized code blocks and synchronization methods cannot be used.
  • Loop statements such as for, while, etc. cannot be used.
  • Cannot extend class, parent class must be Object, interface implementation is not allowed.
  • You cannot use the assert statement or the Class literal value, for example, Class < string > C = string.Class;

BTrace annotation

Annotations are all in the package com.sun.btrace.annotations, which are divided into class annotation, method annotation, method Parameter annotation and attribute annotation.

Class annotation

  • @BTrace: used to indicate that the Java class is a BTrace script program that is executed by the BTrace compiler and BTrace agent.
  • @DTrace and @ DTraceRef: they are used separately for Solaris systems. They are generally not used.

Method annotation

  • @OnMethod: used to specify the target code for monitoring. It has three common attributes: clazz, method and location.
  1. The clazz attribute is used to specify the class name of the target code. It supports the form of fully qualified name / regular / parent class / annotation. Fully qualified names, such as java.lang.Thread; regular expression matching, such as / java\.lang \ \.. + /; parent class or interface form, such as + java.lang.Runnable, annotation form, such as @ javax.annotation.Resource. Inner classes are connected using the $symbol.
  2. The method attribute is used to specify the method name of the target code. It supports the form of fully qualified name / regular / annotation. In particular, when specifying the construction method, < init > is used. When the target class has overloaded methods, which target method is specified according to the parameter list of the method modified by @ OnMethod.
  3. The location property specifies the interception time of the target code, such as when entering the target method (default) or when the target method returns a value, which will be discussed in detail later.
  • @OnTimer: a method used to periodically execute the annotation decoration every N milliseconds. For example, @ OnTimer(4000) means to execute every 4000 milliseconds.
  • @OnError: when any other trace behavior in the BTrace script is abnormal, the annotated method will be executed.
  • @OnExit: when the BTrace script exits the BTrace command-line session with the code BTraceUtils.Sys.exit(0), the annotated method will be executed.
  • @OnEvent: when an external event occurs under the BTrace client command line, the method decorated by the annotation will be executed. The value of the annotation is the event name, and the default value is "SIGINT". Currently, when the ctrl+c operation is performed, the annotated method will be executed.
  • @OnLowMemory: used to monitor whether the heap memory in the JVM reaches the threshold value. It has two attributes: pool and threshold. Pool specifies whether to monitor the young generation or the old generation. Different GC algorithms will also differ. Threshold specifies the threshold size. For example, @ OnLowMemory(pool = "Tenured Gen",threshold=6000000)
  • @OnProbe: this annotation can be used to avoid using the internal class of BTrace script. It needs to be used with xml Mapping file. See User's Guide for details

Interception opportunity

@The location attribute of the OnMethod annotation indicates the time to intercept the target code. The following are frequently used:

  • Kind.ENTRY: it is executed when the target method is just entered. When no location is specified, this is the default value.
  • Kind.RETURN: the target method is executed when it returns. With the Method Parameter annotation @ Duration, you can get the execution time of the target method, in nanoseconds.
  • Kind. Thread: when an exception is thrown in the target method.
  • Kind.CATCH: when an exception is caught in the target method.
  • Kind.ERROR: the target method has an exception that has not been caught and thrown out of the method.
  • Kind.CALL: when the target method is called.
  • Kind.LINE: when a specific line of the target method is executed, a value of - 1 indicates all lines of code within the method.

Method Parameter annotation

  • @ProbeClassName: the class name of the target code, fully qualified name, defined in clazz of @ OnMethod.
  • @ProbeMethodName: the method name of the target code, defined in the method of @ OnMethod.
  • @Duration: execution time of the target method, used with Kind.RETURN.
  • @Return: return value of the target method, used with Kind.RETURN..
  • @Self: target instance, pointing to the value of this keyword.
  • @TargetInstance: used with Kind.CALL to represent the monitored call instance defined in location.
  • @TargetMethodOrField: used with Kind.CALL to represent monitored call methods and properties defined in location.

Attribute annotation

  • @TLS: ThreadLocal variable, which can be used to communicate and share variables among multiple interception methods, can only be accessed in interception methods annotated with @ OnMethod.
  • @Export: the annotated fields can be mapped to a JVM stat counter to expose to external JVM stat clients (such as jstat).

Use example

Get methods with execution time over 100ms

@BTrace
public class DurationDemo {
    @OnMethod(
            clazz="/com\\.cellei\\.btrace\\.practice\\.controller\\..*/",
            method="/.*/",
            location = @Location(Kind.RETURN)
    )
    public static void duration(@ProbeClassName String pcn, @ProbeMethodName String pmn, @Duration long duration) {
        //duration in nanoseconds
        if(duration > 1000000 * 100){
            BTraceUtils.println("DurationDemo.duration: " + pcn + "." + pmn +  ":" + (duration/1000000) + "ms");
        }
    }
}

Whether a row of the target method is executed

@BTrace
public class AllLines {
    @OnMethod(
            clazz="/com\\.cellei\\.btrace\\.practice\\.controller\\..*/",
            method = "createUser",
            location=@Location(value=Kind.LINE, line=24)
    )
    public static void applines(@ProbeClassName String pcn, @ProbeMethodName String pmn, int line) {
        BTraceUtils.println("AllLines.applines: " + pcn + "." + pmn +  ":" + line);
    }
}

Get the parameters of the target method

@BTrace
public class ArgArray {
    @OnMethod(
            clazz="/com\\.cellei\\.btrace\\.practice\\.controller\\..*/",
            method="updateUser",
            location = @Location(Kind.ENTRY)
    )
    public static void userArg(@ProbeClassName String pcn, @ProbeMethodName String pmn, String id, User user) {
        //Only reflections in BTraceUtils can be used
        Field filed = BTraceUtils.field("com.cellei.btrace.practice.model.User", "name");
        BTraceUtils.println("ArgArray.userArg<id>: " + id);
        BTraceUtils.println("ArgArray.userArg<user>: " + user);
        BTraceUtils.printFields(user);
        BTraceUtils.println("ArgArray.userArg<user.name>:" + BTraceUtils.get(filed, user));
    }
}

Get the parameters of construction method and call stack

@BTrace
public class Constructor {
    @OnMethod(
            clazz="com.cellei.btrace.practice.model.User",
            method="<init>"
    )
    public static void anyRead(@ProbeClassName String pcn, @ProbeMethodName String pmn, AnyType[] args) {
        BTraceUtils.println(pcn+","+pmn);
        BTraceUtils.printArray(args);
        BTraceUtils.jstack();
    }
}

When the target method is overloaded

@BTrace
public class Overload {

    @OnMethod(
            clazz = "com.cellei.btrace.practice.controller.AppController",
            method = "getUser"
    )
    public static void oneArg(@ProbeClassName String pcn, @ProbeMethodName String pmn, String id) {
        BTraceUtils.println("Overload.oneArg: " + id);
    }

    @OnMethod(
            clazz = "com.cellei.btrace.practice.controller.AppController",
            method = "getUser"
    )
    public static void twoArg(@ProbeClassName String pcn, @ProbeMethodName String pmn, String name, Integer age) {
        BTraceUtils.println("Overload.twoArg: " + name + ":" + age);
    }
}

Whether there is deadlock

@BTrace
public class Deadlock {
    @OnTimer(4000)
    public static void print() {
        BTraceUtils.deadlocks();
    }
}

Get environment variables and JVM parameters

@BTrace
public class JInfo {
    static {
        BTraceUtils.println("System Properties:");
        BTraceUtils.printProperties();
        BTraceUtils.println("VM Flags:");
        BTraceUtils.printVmArguments();
        BTraceUtils.println("OS Enviroment:");
        BTraceUtils.printEnv();
        BTraceUtils.exit(0);
    }
}

Catch exception stack

@BTrace
public class OnThrow {
    @TLS
    static Throwable currentException;

    @OnMethod(
            clazz="java.lang.Throwable",
            method="<init>"
    )
    public static void onthrow(@Self Throwable self) {
        currentException = self;
    }

    @OnMethod(
            clazz="java.lang.Throwable",
            method="<init>"
    )
    public static void onthrow1(@Self Throwable self, String s) {
        currentException = self;
    }

    @OnMethod(
            clazz="java.lang.Throwable",
            method="<init>"
    )
    public static void onthrow1(@Self Throwable self, String s, Throwable cause) {
        currentException = self;
    }

    @OnMethod(
            clazz="java.lang.Throwable",
            method="<init>"
    )
    public static void onthrow2(@Self Throwable self, Throwable cause) {
        currentException = self;
    }

    @OnMethod(
            clazz="java.lang.Throwable",
            method="<init>",
            location=@Location(Kind.RETURN)
    )
    public static void onthrowreturn() {
        if (currentException != null) {
            BTraceUtils.Threads.jstack(currentException);
            BTraceUtils.println("=====================");
            currentException = null;
        }
    }
}

Exit in 5 seconds

@BTrace
public class ProbeExit {
    private static volatile int i;

    @OnExit
    public static void onexit(int code) {
        BTraceUtils.println("BTrace program exits!");
    }
    
    @OnTimer(1000)
    public static void ontime() {
        BTraceUtils.println("hello");
        i++;
        if (i == 5) {
            BTraceUtils.Sys.exit(0);
        }
    }
}

Keywords: Java Attribute jvm github

Added by marty on Thu, 27 Feb 2020 05:24:48 +0200