Embedded Probes
If you control the source code of the software component that is the target of your probe, you should write an embedded probe instead of an injected probe.
Most of the initial effort when writing an injected probe goes into specifying the intercepted methods and selecting the injected objects as method parameters for the handler method. With embedded probes, this is not necessary because you can call the embedded probe API directly from the monitored methods. Another advantage of embedded probes is that deployment is automatic. Probes ship together with your software and appear in the JProfiler UI when the application is profiled. The only dependency you have to ship is a small JAR file licensed under the Apache 2.0 License that mainly consists of empty method bodies serving as hooks for the profiling agent.
Development environment
The development environment is the same as for injected probes, with the difference that the artifact name
is jprofiler-probe-embedded
instead of jprofiler-probe-injected
and
that you ship the JAR file with your application instead of developing the probe in a separate project.
The probe API that you need for adding an embedded probe to your software component is contained in the single
JAR artifact. In the javadoc, start with the package overview for
com.jprofiler.api.probe.embedded
when you explore the API.
Just like for injected probes, there are two examples for embedded probes as well. In
api/samples/simple-embedded-probe
, there is an example that gets you started with writing an embedded
probe. Execute ../gradlew
in that directory to compile and run it and study the gradle
build file build.gradle
to understand the execution environment. For more features, including
control objects, go to the example in api/samples/advanced-embedded-probe
.
Payload probes
Similar to injected probes, you still need a probe class for configuration purposes. The probe class must
extend com.jprofiler.api.probe.embedded.PayloadProbe
or
com.jprofiler.api.probe.embedded.SplitProbe
, depending on whether your probe collects
payloads or splits the call tree. With the injected probe API, you use different annotations on the handler
methods for payload collection and splitting. The embedded probe API, on the other hand, has no handler
methods and needs to shift this configuration to the probe class itself.
public class FooPayloadProbe extends PayloadProbe { @Override public String getName() { return "Foo queries"; } @Override public String getDescription() { return "Records foo queries"; } }
Whereas injected probes use annotations for configuration, you configure embedded probes by overriding
methods from the base class of the probe. For a payload probe, the only abstract method is getName()
,
all other methods have a default implementation that you can override if required. For example, if you want to
disable the events view to reduce overhead, you can override isEvents()
to return false
.
For collecting payloads and measuring their associated timing you use a pair of Payload.enter()
and Payload.exit()
calls
public void measuredCall(String query) { Payload.enter(FooPayloadProbe.class); try { performWork(); } finally { Payload.exit(query); } }
The Payload.enter()
call receives the probe class as an argument, so the profiling agent knows
which probe is the target of the call, the Payload.exit()
call is automatically associated with the
same probe and receives the payload string as an argument. If you miss an exit call, the call tree would be broken,
so this should always be done in a finally clause of a try block.
If the measured code block does not produce any value, you can call the Payload.execute
method instead
which takes the payload string and a Runnable
. With Java 8+, lambdas or method references make this
method invocation very concise.
public void measuredCall(String query) { Payload.execute(FooPayloadProbe.class, query, this::performWork); }
The payload string must be known in advance in that case. There is also a version of execute
that
takes a Callable
.
public QueryResult measuredCall(String query) throws Exception { return Payload.execute(PayloadProbe.class, query, () -> query.execute()); }
The problem with the signatures that take a Callable
is that Callable.call()
throws
a checked Exception
and so you have to either catch it or declare it on the containing method.
Control objects
Payload probes can open and close control objects by calling the appropriate methods in the
Payload
class. They are associated with probe events by passing them to a version of the
Payload.enter()
or Payload.execute()
methods that take a control object and a
custom event type.
public void measuredCall(String query, Connection connection) { Payload.enter(FooPayloadProbe.class, connection, MyEventTypes.QUERY); try { performWork(); } finally { Payload.exit(query); } }
The control object view must be explicitly enabled in the probe configuration, and custom event types must be registered in the probe class as well.
public class FooPayloadProbe extends PayloadProbe { @Override public String getName() { return "Foo queries"; } @Override public String getDescription() { return "Records foo queries"; } @Override public boolean isControlObjects() { return true; } @Override public Class<? extends Enum> getCustomTypes() { return Connection.class; } }
If you do not explicitly open and close your control objects, the probe class must override
getControlObjectName
in order to resolve display names for all control objects.
Split probes
The split probe base class has no abstract methods, because it can be used to just split the call tree without adding a probe view. In that case, the minimal probe definition is just
public class FooSplitProbe extends SplitProbe {}
One important configuration for split probes is whether they should be reentrant. By default, only the top-level
call is split. If you would like to split recursive calls as well, override isReentrant()
to
return true
. Split probes can also create a probe view and publish the split strings as payloads
if you override isPayloads()
to return true
in the probe class.
To perform a split, make a pair of calls to Split.enter()
and Split.exit()
.
public void splitMethod(String parameter) { Split.enter(FooSplitProbe.class, parameter); try { performWork(parameter); } finally { Split.exit(); } }
Contrary to to payload collection, the split string has to be passed to the Split.enter()
method
together with the probe class. Again, it is important that Split.exit()
is called reliably, so it
should be in a finally clause of a try block. Split
also offers execute()
methods
with Runnable
and Callable
arguments that perform the split with a single call.
Telemetries
It is particularly convenient to publish telemetries for embedded probes, because being in the same classpath
you can directly access all static methods in your application. Just like for injected probes, annotate static
public methods in your probe configuration class with @Telemetry
and return a numeric value.
See the chapter on probe concepts for more information. The
@Telemetry
annotations of the embedded and the injected probe APIs are equivalent, they are
just in different packages.
Another parallel functionality between embedded and injected probe API is the ability to modify the thread state
with the ThreadState
class. Again, the class is present in both APIs with different packages.
Deployment
There are no special steps necessary to enable embedded probes when profiling with the JProfiler UI.
However, the probe will only be registered when the first call into Payload
or
Split
is made. Only at that point will the associated probe view be created in JProfiler.
If you prefer the probe view to be visible from the beginning, as is the case for built-in and injected probes,
you can call
PayloadProbe.register(FooPayloadProbe.class);
for payload probes and
SplitProbe.register(FooSplitProbe.class);
for split probes.
You may be considering whether to call the methods of Payload
and Split
conditionally, maybe controlled by a command line switch in order to minimize overhead.
However, this is generally not necessary because the method bodies are empty. Without the profiling agent
attached, no overhead is incurred apart from the construction of the payload string. Considering that probe events
should not be generated on a microscopic scale, they will be created relatively rarely, so that building the
payload string should be a comparatively insignificant effort.
Another concern for containers may be that you do not want to expose external dependencies on the class path. A user of your container could also use the embedded probe API which would lead to a conflict. In that case, you can shade the embedded probe API into your own package. JProfiler will still recognize the shaded package and instrument the API classes correctly. If build-time shading is not practical, you can extract the source archive and make the classes part of your project.