docs/BTraceExtensionDevelopmentGuide.md
BTrace extensions provide reusable services that can be injected into BTrace scripts. This guide covers the recommended, plugin-based workflow using a single Gradle module with two source sets (api, impl). The plugin separates artifacts, generates metadata, shades implementation dependencies, and prepares distributables.
For API authoring rules that the build verifies, see docs/ExtensionInterfaceRules.md.
Extensions are isolated while exposing only their API to scripts:
Bootstrap ClassLoader
├── JRE classes
├── btrace-boot.jar (BTrace core + extension APIs)
└── Extension ClassLoaders (isolated)
├── Extension 1 (e.g., btrace-metrics)
├── Extension 2 (e.g., btrace-statsd)
└── Extension N (your extension)
Script ClassLoader (parent = null)
├── Script classes
└── Accesses extensions via invokedynamic bridge
Use a single Gradle module with two source sets:
your-extension/
├── build.gradle
└── src/
├── api/java/... (public API visible to scripts; JDK-only deps)
├── api/resources/...
├── impl/java/... (implementation; can use external libraries)
└── impl/resources/...
Apply the BTrace Gradle Extension Plugin and configure your extension via btraceExtension:
plugins {
id("org.openjdk.btrace.extension") version "<btraceVersion>"
}
repositories { mavenCentral() }
java {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
dependencies {
// Keep API free of external library types
// Put all runtime libs under Impl (the plugin will shade them)
}
btraceExtension {
id = "org.example.myext" // required: globally unique extension ID
name = "My Extension" // optional
description = "Does useful things" // optional
// Service interfaces that can be injected into scripts
// Auto-detected from @ServiceDescriptor, or declare explicitly:
services = [ "org.example.myext.api.MyService" ]
// Shade Impl dependencies to avoid conflicts
shadedPackages = [
"com.example.dep" : "org.example.myext.shaded.dep"
]
// Permissions
scanPermissions = true // default: infer from Impl bytecode + classpath
requiredPermissions = [ ] // optional additions/overrides
// Optional: other extension IDs you depend on
requiresExtensions = [ ]
}
Alternative to the DSL:
@ExtensionDescriptor annotation in your API package’s package-info.java.btrace-core/src/main/java/org/openjdk/btrace/core/extensions/ExtensionDescriptor.java.name, version, description, minBTraceVersion, dependencies, permissions.btraceExtension block remains the canonical source for manifest values; @ExtensionDescriptor mainly assists tooling and validates that declared permissions are covered by scanning or requiredPermissions.Outputs produced by the plugin:
build/libs/<name>-<version>-api.jar (manifest + properties with extension metadata)build/libs/<name>-<version>-impl.jar (shadowed/minimized, isolated at runtime)build/distributions/<name>-<version>-extension.zip (bundles API + Impl)Advanced (optional) knobs in btraceExtension:
autoApplyShadow (default true): auto-apply Shadow plugin if not applied.nullableAnnotations/nonnullAnnotations: additional nullability annotations (FQCN) for API linting.nullabilitySeverity (off|warn|error): nullability lint severity.shimabilitySeverity (warn|error): shim-compatibility lint severity.apiCtorSeverity (off|warn|error): flag public constructors in API classes.generateShimsReachableOnly (default true): generate shims only for interfaces reachable from declared services.Define injectable service interfaces. Use the descriptors to help discovery and permission modeling.
package org.example.myext.api;
import org.openjdk.btrace.core.extensions.Permission;
import org.openjdk.btrace.core.extensions.ServiceDescriptor;
@ServiceDescriptor(permissions = { Permission.THREADS })
public interface MyService {
MyMetric metric(String name);
}
Keep API signatures to JDK and your own API types; avoid external library types.
Provide concrete implementations and extend Extension to access the runtime context when needed.
package org.example.myext.impl;
import org.openjdk.btrace.core.extensions.Extension;
import org.example.myext.api.MyService;
public final class MyServiceImpl extends Extension implements MyService {
public MyServiceImpl() {}
// implement API methods...
}
The plugin shades external libraries present in Impl according to shadedPackages.
package btrace;
import org.openjdk.btrace.core.annotations.BTrace;
import org.openjdk.btrace.core.annotations.Injected;
import org.openjdk.btrace.core.annotations.OnMethod;
import org.example.myext.api.MyService;
@BTrace
public class MyProbe {
@Injected
private static MyService svc;
@OnMethod(clazz = "com.example.App", method = "doWork")
public static void onDoWork() {
svc.metric("work");
}
}
The plugin writes extension metadata into the API JAR manifest and a dedicated properties file; manual manifest editing is not needed. Key attributes include:
BTrace-Extension-Id, BTrace-Extension-Name, BTrace-Extension-DescriptionBTrace-Extension-Services (service interfaces)BTrace-Extension-Permissions (merged from scan + explicit requiredPermissions)BTrace-Extension-Requires (dependent extension IDs)BTrace-Extension-Impl (Impl artifact coordinates/path)BTrace-Shaded-Packages (diagnostic relocations)Permission configuration:
btraceExtension {
// Disable inference and declare explicitly (optional)
// scanPermissions = false
requiredPermissions = [ "NETWORK", "THREADS" ]
}
At runtime, the agent consults this metadata to validate and enforce permissions.
shadedPackages to relocate and avoid conflicts.Build artifacts:
build/libs/<name>-<version>-api.jarbuild/libs/<name>-<version>-impl.jarbuild/distributions/<name>-<version>-extension.zipInstall by copying the ZIP contents (API + Impl) into an extensions directory:
# System-wide
unzip your-extension-<version>-extension.zip -d "$BTRACE_HOME/extensions/"
# User-specific
mkdir -p "$HOME/.btrace/extensions"
unzip your-extension-<version>-extension.zip -d "$HOME/.btrace/extensions/"
Discovery locations:
$BTRACE_HOME/extensions/*.jar~/.btrace/extensions/*.jarConfiguration: $BTRACE_HOME/conf/extensions.conf
autoload = true
repositories = [ "${btrace.home}/extensions", "${user.home}/.btrace/extensions" ]
integration-tests.src/api and src/implorg.openjdk.btrace.extension pluginbtraceExtension.id, declare/annotate servicesshadedPackages; optionally tune requiredPermissions// API
@ServiceDescriptor
public interface MetricsService {
HistogramConfigBuilder newHistogramConfig();
HistogramMetric histogram(String name, HistogramConfig cfg);
}
public interface HistogramConfig {}
public interface HistogramConfigBuilder {
HistogramConfigBuilder lowestDiscernibleValue(long v);
HistogramConfigBuilder highestTrackableValue(long v);
HistogramConfigBuilder significantDigits(int d);
HistogramConfig build();
}
// Probe (no `new` in scripts)
@BTrace
class HistoProbe {
@Injected static MetricsService metrics;
}
src/api/java as an interface.btraceExtension.id/services are set or APIs are annotated.shadedPackages for third-party libraries.Use a single module with api and impl source sets and the BTrace extension plugin to produce clean, isolated, and self-describing extensions. The plugin handles artifact separation, metadata, permissions, shading, and packaging, so you can focus on a stable API and solid implementation.