Injected Probes
Similarly to script probes, injected probes define interception handlers for selected methods. However, injected probes are developed outside the JProfiler GUI in your IDE and rely on the injected probe API that is provided by JProfiler. The API is licensed under the permissive Apache License, version 2.0, making it easy to distribute the associated artifacts.
The best way to get started with injected probes is to study the example in the
api/samples/simple-injected-probe
directory of your JProfiler installation.
Execute ../gradlew
in that directory to compile and run it. The gradle
build file build.gradle
contains further information about the sample. The example in
api/samples/advanced-injected-probe
shows more features of the probe system, including
control objects.
Development environment
The probe API that you need for developing an injected probe is contained in the single artifact with maven coordinates
group: com.jprofiler artifact: jprofiler-probe-injected version: <JProfiler version>
where the JProfiler version corresponding to this manual is 14.0.5.
Jar, source and javadoc artifacts are published to the repository at
https://maven.ej-technologies.com/repository
You can either add the probe API to your development class path with a build tool like Gradle or Maven, or use the JAR file
api/jprofiler-probe-injected.jar
in the JProfiler installation.
To browse the Javadoc, go to
api/javadoc/index.html
That javadoc combines the javadoc for
all APIs that are published by JProfiler. The overview for the com.jprofiler.api.probe.injected
package is a good starting point for exploring the API.
Probe structure
An injected probe is a class annotated with com.jprofiler.api.probe.injected.Probe
. The attributes
of that annotation expose configuration options for the entire probe. For example, if you create a lot of probe
events that are not interesting for individual inspection, the events
attribute allows you
to disable the probe events view and reduce overhead.
@Probe(name = "Foo", description = "Shows foo server requests", events = "false") public class FooProbe { ... }
To the probe class, you add specially annotated static methods in order to define interception handlers.
The PayloadInterception
annotation creates payloads while the SplitInterception
annotation splits the call tree. The return value of the handler is used as the payload or the split string,
depending on the annotation. Like for script probes, if you return null
, the interception has
no effect. Timing information is automatically calculated for the intercepted method.
@Probe(name = "FooBar") public class FooProbe { @PayloadInterception( invokeOn = InvocationType.ENTER, method = @MethodSpec(className = "com.bar.Database", methodName = "processQuery", parameterTypes = {"com.bar.Query"}, returnType = "void")) public static String fooRequest(@Parameter(0) Query query) { return query.getVerbose(); } @SplitInterception( method = @MethodSpec(className = "com.foo.Server", methodName = "handleRequest", parameterTypes = {"com.foo.Request"}, returnType = "void")) public static String barQuery(@Parameter(0) Request request) { return request.getPath(); } }
As you can see in the above example, both annotations have a method
attribute for defining the
intercepted methods with a MethodSpec
. In contrast to script probes, the MethodSpec
can have an empty class name, so all methods with a particular signature are intercepted, regardless of the class
name. Alternatively, you can use the subtypes
attribute of the MethodSpec
to intercept entire class hierarchies.
Unlike for script probes where all parameters are available automatically, the handler methods declare parameters
to request values of interest. Each parameter is annotated with an annotation from the
com.jprofiler.api.probe.injected.parameter
package, so the profiling agent knows which object
or primitive value has to be passed to the method. For example, annotating a parameter of the handler method with
@Parameter(0)
injects the first parameter of the intercepted method.
Method parameters of the intercepted method are available for all interception types. Payload interceptions
can access the return value with @ReturnValue
or a thrown exception with @ExceptionValue
if you tell the profiling agent to intercept the exit rather than the entry of the method. This is done with the
invokeOn
attribute of the PayloadInterception
annotation.
In contrast to script probes, injected probes handlers can be called for recursive invocations of the intercepted
method if you set the reentrant
attribute of the interception annotation to
true
. With a parameter of type ProbeContext
in your handler method, you
can control the probe's behavior for nested invocations by calling ProbeContext.getOuterPayload()
or ProbeContext.restartTiming()
.
Advanced interceptions
Sometimes a single interception is not sufficient to collect all necessary information for building the probe
string. For that purpose, your probe can contain an arbitrary number of interception handlers annotated with
Interception
that do not create payloads or splits. Information can be stored in static fields of
your probe class. For thread safety in a multi-threaded environment, you should use ThreadLocal
instances for storing reference types and the atomic numeric types from the
java.util.concurrent.atomic
package for maintaining counters.
Under some circumstances, you need interceptions for both method entry and method exit. A common case is if you
maintain state variables like inMethodCall
that modify the behavior of another interception.
You can set inMethodCall
to true
in the entry interception, which is the default
interception type. Now you define another static method directly below that interception and annotate it
with @AdditionalInterception(invokeOn = InvocationType.EXIT)
. The intercepted method is taken from
the interception handler above, so you do not have to specify it again. In the method body, you can
set your inMethodCall
variable to false
.
... private static final ThreadLocal<Boolean> inMethodCall = ThreadLocal.withInitial(() -> Boolean.FALSE); @Interception( invokeOn = InvocationType.ENTER, method = @MethodSpec(className = "com.foo.Server", methodName = "internalCall", parameterTypes = {"com.foo.Request"}, returnType = "void")) public static void guardEnter() { inMethodCall.set(Boolean.TRUE); } @AdditionalInterception(InvocationType.EXIT) public static void guardExit() { inMethodCall.set(Boolean.FALSE); } @SplitInterception( method = @MethodSpec(className = "com.foo.Server", methodName = "handleRequest", parameterTypes = {"com.foo.Request"}, returnType = "void"), reentrant = true) public static String splitRequest(@Parameter(0) Request request) { if (!inMethodCall.get()) { return request.getPath(); } else { return null; } } ...
You can see a working example of this use case in
api/samples/advanced-injected-probe/src/main/java/AdvancedAwtEventProbe.java
.
Control objects
The control objects view is not visible unless the controlObjects
attribute of the
Probe
annotation is set to true
. For working with control objects, you have to
obtain a ProbeContext
by declaring a parameter of that type in your handler method.
The sample code below shows how to open a control object and associate it with a probe event.
@Probe(name = "Foo", controlObjects = true, customTypes = MyEventTypes.class) public class FooProbe { @Interception( invokeOn = InvocationType.EXIT, method = @MethodSpec(className = "com.foo.ConnectionPool", methodName = "createConnection", parameterTypes = {}, returnType = "com.foo.Connection")) public static void openConnection(ProbeContext pc, @ReturnValue Connection c) { pc.openControlObject(c, c.getId()); } @PayloadInterception( invokeOn = InvocationType.EXIT, method = @MethodSpec(className = "com.foo.ConnectionPool", methodName = "createConnection", parameterTypes = {"com.foo.Query", "com.foo.Connection"}, returnType = "com.foo.Connection")) public static Payload handleQuery( ProbeContext pc, @Parameter(0) Query query, @Parameter(1) Connection c) { return pc.createPayload(query.getVerbose(), c, MyEventTypes.QUERY); } ... }
Control objects have a defined lifetime, and the probe view records their open and close times in the timeline and
the control objects view. If possible, you should open and close control objects explicitly by calling
ProbeContext.openControlObject()
and ProbeContext.closeControlObject()
.
Otherwise you have to declare a static method annotated with @ControlObjectName
that resolves the
display names of control objects.
Probe events can be associated with control objects if your handler method returns instances of
Payload
instead of String
. The ProbeContext.createPayload()
method takes a control object and a probe event type. The enum with the allowed event types has to be
registered with the customTypes
attribute of the Probe
annotation.
Control objects have to be specified at the start of the time measurement which corresponds to the method entry.
In some cases, the name of payload string will only be available at method exit because it depends on the
return value or other interceptions. In that case, you can use
ProbeContext.createPayloadWithDeferredName()
to create a payload object without a name.
Define an interception handler annotated with @AdditionalInterception(invokeOn = InvocationType.EXIT)
right below and return a String
from that method, it will then automatically be used as the
payload string.
Overriding the thread state
When measuring execution times for database drivers or native connectors to external resources, it sometimes becomes necessary to tell JProfiler to put some methods into a different thread state. For example, it is useful to have database calls in the "Net I/O" thread state. If the communication mechanism does not use the standard Java I/O facilities, but some native mechanism, this will not automatically be the case.
With a pair of ThreadState.NETIO.enter()
and ThreadState.exit()
calls, the profiling
agent adjusts the thread state accordingly.
... @Interception(invokeOn = InvocationType.ENTER, method = ...) public static void enterMethod(ProbeContext probeContext, @ThisValue JComponent component) { ThreadState.NETIO.enter(); } @AdditionalInterception(InvocationType.EXIT) public static void exitMethod() { ThreadState.exit(); } ...
Deployment
There are two ways to deploy injected probes, depending on whether you want to put them on the classpath or not. With the VM parameter
-Djprofiler.probeClassPath=...
a separate probe class path is set up by the profiling agent. The probe classpath can contain directories and class files, separated with ';' on Windows and ':' on other platforms. The profiling agent will scan the probe classpath and find all probe definitions.
If it's easier for you to place the probe classes on the classpath, you can set the VM parameter
-Djprofiler.customProbes=...