The monitoring feature in GlassFish makes use of several external dependencies to implement the monitoring framework. These include:
- BTrace - a dynamic tracing tool for Java
- JSDT - Java Statically-Defined Tracing , a feature in JDK7 accessed via com.sun.tracing and sun.tracing that enables monitoring probes to be exposed through dtrace.
- ASM - a Java bytecode manipulation and analysis framework
This page explains in detail how the GlassFish monitoring framework makes use of these APIs.
The following diagram shows how BTrace is used within the monitoring framework.
We use BTrace for byte code modification. It goes in through the agent interface (same as a debugger), and modifies existing code so that it calls the instrumented class rather than the original code. We generate a new BTrace client class with the same methods as the probe. The new class is submitted to BTrace.
The BTrace library is referenced by the following classes in GlassFish: BtraceClientGenerator and FlashlightProbeClientMediator, which are both part of the flashlight/framwork module. The references are a run-time only, not compile time. GlassFish uses the btrace-agent.jar and btrace-boot.jar files at runtime.
The BtraceClientGenerator uses the ASM library to generate code that references the BTrace classes, and the FlashlightProbeClientMediator loads the BTrace classes using dynamic class loading. The following BTrace classes and methods are referenced:
- com.sun.btrace.annotations.BTrace annotation
- com.sun.btrace.annotations.OnMethod annotation
- com.sun.btrace.agent.Main class, handleFlashLightClient method (this is a private interface into BTrace for use only by GlassFish)
The two annotations are used in dynamically generated code. The Main.handleFlashLightClient method is called to submit the dynamically generated code to BTrace.
Here's how monitoring works using BTrace.
- A monitoring probe is defined within the GlassFish code using @Probe and @ProbeProvider annotations.
- The probe is registered with GlassFish by calling ProbeProviderFactory.getProbeProvider which through injection is actually a reference to the FlashlightProbeProviderFactory class. This class sets up a data structure based on FlashlightProbe, however, the instrumented code is not actually modified until there is a listener for the probes.
- Sometime later, a listener for a probe is registered (such as the asadmin monitor or run-script program). This eventually calls the FlashlightProbeClientMediator.registerListener method.
- The FlashlightProbeClientMediator calls BTraceClientGenerator to transform the probes.
- The BTraceClientGenerator looks at the @Probe annotations in the instrumented code.
- This information is used with ASM to generate a method for each problem that calls invokeProbe. These methods are annotated with @BTrace and @OnMethod.
- The generated code is submitted to the BTrace agent.
- The BTrace agent modifies the targeted classes and methods so that the generated code is called.
- The generated code calls ProbeRegistry.invokeProbe which then triggers the listeners on the probe to get called.
The BTrace functionality that is critical to GlassFish is the ability to submit a specification of a probe point to BTrace, with some code to execute when that probe point is executed, and BTrace modifies the Java bytecode so that the specified code is called.
There are many other features of BTrace that are not used by GlassFish, such as the ability to gather statistics on method calls and make it available via JMX. BTrace has many more annotations; GlassFish only only uses @OnMethod. BTrace has a regular expression mechanism for selecting the methods that are to be instrumented.
The following diagram shows how JSDT is used within the monitoring framework.
The JSDT interface consists of the com.sun.tracing and sun.tracing packages that are part of JDK 7. The GlassFish source code makes no direct compile-time references to these packages. Rather, at run-time, Java bytecode that references these packages is generated using ASM.
The two GlassFish classes involved with this are DTraceContractImpl and DTraceInterfaceBuilder.
The DTraceInterfaceBuilder class is responsible for building interfaces that implement com.sun.tracing.Provider that correspond to the monitoring probe provider classes. For each Provider, DTraceInterfaceBuilder creates an interface called ProviderName_GLASSFISH_DTRACE where _ProviderName is the name of the provider class. This class has an empty method for each probe that is defined by the provider. The class and each of the methods is annotated with the annotations that are defined by JSDT, specifically, @ProviderName, @ModuleName, @FunctionName, and @ProbeName.
The DTraceContractImpl class uses reflection to access the com.sun.tracing.ProviderFactory and com.sun.tracing.Provider classes. It calls (using reflection) ProviderFactory.getDefaultFactory and then it calls ProviderFactory.createProvider on that object, passing in the class that is returned by DTraceInterfaceBuilder. Doing this causes the DTrace probes that are defined by the annotations used by DTraceInterfaceBuilder to show up as probes to the dtrace program.
The next step is to arrange that the methods in the class produced by DTraceInterfaceBuilder actually get called. This is done by the FlashlightProbeProviderFactory. When DTrace is enabled, it calls DTraceContractImpl.getProvider to get an instance of the class that is generated by DTraceInterfaceBuilder. This object and the appropriate method is then registered with the corresponding FlashlightProbe, such that the method can be obtained via the FlashlightProbe.getDTraceMethod method. Each FlashlightProbe has a list of ProbeClientInvokers that it calls each time the probe is fired. One of these invokers is the DTraceClientInvoker which uses the getDTraceMethod to get the method to call when the probe is fired. The DTraceClientInvoker is created by the ProbeClientInvokerFactory.createDTraceInvoker method which is called by FlashlightProbeClientMediator.registerDTraceListener. This latter method also adds the DTraceClientInvoker as a invoker for the FlashlightProbe.
In summary, the GlassFish monitoring framework depends on being able to create an interface with methods, such that when those methods are called, a DTrace probe is triggered.
There are three different things that can be setup for monitoring namely
- instances of a Java Class
- a Java interface
- an XML description
Btrace instruments byte-code for an existing class. In the case of (1) above – we already have a class with methods that are ready for instrumenting. No need for ASM.
In case 3 we have an XML description of an interface. We use ASM to generate a class corresponding to this description. Then we create an instance of this class and submit it to BTrace for instrumenting as in case 1.
In case 2 we use ASM in order to create an implementing class for the given interface. Then we create an instance of it and Btrace instruments it.