# custom-probes

Content of the MCP resource `jprofiler://probes/custom` of the JProfiler MCP server, see the [tool and resource reference](../tools.md) for the full list of tools and resources.

# Custom probes

Custom probes are Java classes shipped with the profiled application that JProfiler records like built-in
probes: aggregated payload hot spots, call tree splitting, optionally individual events, control objects
and telemetries. This server records and queries them as `custom:<fully qualified probe class name>`
subsystems - the probe class name is the probe id in `recordingConfig.subsystems`, in `probeConfig`, in
the `subsystem` argument of the query datasets and in the `customProbes` section of check_status.

## When not to write a custom probe

A custom probe is code that has to be compiled and deployed with the profiled application. Cheaper
options first:

- The built-in probes cover common technologies (jdbc, jpa, http_server, http_client, mongo_db, kafka,
  files, sockets, processes and more) and need no code at all.
- To split the CPU call tree by a runtime value such as a URL or a tenant id, a
  `recordingConfig.advanced.splitMethods` script is enough.
- To turn selected methods into separately measured groups, use
  `recordingConfig.advanced.complexityMethods`.
- To record payload hot spots and events of application-specific methods without any deployment,
  define a script probe in `recordingConfig.advanced.scriptProbes`: the script text is compiled by
  this server and pushed to the profiled JVM, also on attach.

Write a custom probe when script probes are not enough: they cannot split the call tree, access return
values or create control objects, custom event types or telemetries.

## Embedded or injected

- **Embedded probe**: the application calls the probe API itself, for code you own. Extend
  `PayloadProbe` or `SplitProbe` from `com.jprofiler.api.probe.embedded` and bracket the measured
  operations with the static `Payload`/`Split` methods. Embedded probes self-register on the first API
  call and need no further deployment step. API reference: `jprofiler://api/probe-embedded`.
- **Injected probe**: bytecode instrumentation adds the measurement to methods of classes you may not
  own. Annotate a class with `@Probe` from `com.jprofiler.api.probe.injected` and declare interceptions.
  The agent must be told the probe class names, see deployment below. API reference:
  `jprofiler://api/probe-injected`.

## Build setup

Both APIs are published as Maven artifacts, `com.jprofiler:jprofiler-probe-embedded:<version>` and
`com.jprofiler:jprofiler-probe-injected:<version>` where `<version>` is the JProfiler version - use
`provided` scope, the profiling agent carries the implementation. The same JARs ship in the `api`
directory of a JProfiler installation. The API references show the exact coordinates.

## Minimal examples

Embedded payload probe:

```java
public class ImportProbe extends PayloadProbe {
    @Override
    public String getName() { return "Imports"; }

    @Telemetry("Open imports")
    public static int openImports() { return ImportService.openCount(); }
}

// in the application code, around the measured operation:
Payload.execute(ImportProbe.class, "import " + file, () -> doImport(file));
```

Injected probe intercepting a method without touching its code:

```java
@Probe(name = "Order processing")
public class OrderProbe {
    @PayloadInterception(invokeOn = InvocationType.ENTER, method = @MethodSpec(
        className = "com.example.Shop", methodName = "processOrder",
        parameterTypes = {"int"}, returnType = "void"))
    public static String payload(@Parameter(0) int orderId) { return "order " + orderId; }
}
```

## Deployment

Embedded probes need nothing extra: the probe class is on the application classpath and registers on
first use. Injected probes are discovered through the `-Djprofiler.customProbes=<probe classes>` VM
parameter:

- With `prepare_profiling`, set `"injected": true` in the probeConfig entry of every injected probe.
  The response then contains an `additionalJvmParameters` array with the discovery parameter - add each
  element as a separate VM argument to the Java command line, together with the returned `jvmParameter`.
- With `attach`, the server passes the discovery property into the running JVM for probes marked
  `"injected": true`, no command line change is needed.

`-Djprofiler.customProbes` names probe classes that are loaded from the class path of the application,
so the JAR with the probe classes must be on it. For probes shipped in a separate JAR that is not on
the class path, use `-Djprofiler.probeClassPath=<jar or directory>` instead: the agent scans those
paths itself and registers every probe class it finds, and the probe classes must then not be on the
class path.

## Recording and analysis

Record a custom probe like a built-in one:

```json
"recordingConfig": {
  "subsystems": ["cpu", "custom:com.example.ImportProbe"],
  "advanced": {"probeConfig": {"custom:com.example.ImportProbe": {"events": true}}}
}
```

`events` records individual events in addition to the default aggregated hot spots. Trigger start/stop
actions and the startRecording/stopRecording attached actions take the same subsystem name. Starting a
probe that is not registered yet is stored by the agent and applied when it registers, attach reports a
warning in that case. Application code can also start and stop the probe recording itself with
`Controller.startProbeRecording(probeClassName, ...)`, see `jprofiler://api/controller`.

Recorded custom probes are listed in check_status in the `customProbes` section with their display name
and recording state, and in `availableSubsystems`. The query datasets serve their data per subsystem:
`performance.hotspots` and `performance.callTree` (payload nodes appear as `custom_split` nodes),
`probe.events`, `probe.eventTimeline`, `probe.eventHistogram`, `probe.controlObjects` and
`telemetry.list` (custom probe telemetries with `custom:<class>/<telemetry>` ids). Custom probes do not
exist in JFR recordings.
