Skip to content

Add support for lazy loading Pyroscope SDK from Application ClassLoader#47

Open
dordor12 wants to merge 1 commit intografana:mainfrom
dordor12:feature/app-sdk-instrumentation
Open

Add support for lazy loading Pyroscope SDK from Application ClassLoader#47
dordor12 wants to merge 1 commit intografana:mainfrom
dordor12:feature/app-sdk-instrumentation

Conversation

@dordor12
Copy link

Fixes #46

Summary

This PR adds support for capturing Pyroscope SDK instances loaded by the Application ClassLoader, enabling integration with libraries like async-profiler-actuator-endpoint that provide profiling as a Spring Boot dependency.

Changes

New Configuration Option

Added otel.pyroscope.app.sdk.enabled configuration property (default: false):

-Dotel.pyroscope.app.sdk.enabled=true
# or
OTEL_PYROSCOPE_APP_SDK_ENABLED=true

Implementation

Uses OTel's bytecode instrumentation to lazily capture ProfilerSdk instances when they're constructed in the Application ClassLoader:

  1. OtelProfilerSdkBridge - Made fields static, added appSdkEnabled flag and setSdkInstance() method. Cached reflection methods for better performance. The instrumentation only captures the SDK when appSdkEnabled=true.

  2. ProfilerSdkInstrumentation - ByteBuddy instrumentation that hooks into ProfilerSdk constructor and calls OtelProfilerSdkBridge.setSdkInstance().

  3. PyroscopeSdkInstrumentationModule - Registers the instrumentation and injects OtelProfilerSdkBridge as a helper class into the Application ClassLoader.

  4. PyroscopeOtelConfiguration - Added appSdkEnabled configuration field.

  5. PyroscopeOtelAutoConfigurationCustomizerProvider - Sets OtelProfilerSdkBridge.appSdkEnabled flag. SystemClassLoader loading is preserved as fallback.

How It Works

┌─────────────────────────────────────────────────────────────┐
│                  OTel Agent ClassLoader                      │
│  ┌─────────────────────────────────────────────────────────┐│
│  │  PyroscopeOtelSpanProcessor                             ││
│  │    → Uses OtelProfilerSdkBridge (static reference)      ││
│  └─────────────────────────────────────────────────────────┘│
└─────────────────────────────────────────────────────────────┘
                            │
                            │ (reflection via cached methods)
                            ▼
┌─────────────────────────────────────────────────────────────┐
│                Application ClassLoader                       │
│  ┌─────────────────────────────────────────────────────────┐│
│  │  OtelProfilerSdkBridge (injected via Helper Injection)  ││
│  │    ← setSdkInstance() called on ProfilerSdk construct   ││
│  │    ← Only active when appSdkEnabled=true                ││
│  └─────────────────────────────────────────────────────────┘│
│                            ▲                                 │
│  ┌─────────────────────────────────────────────────────────┐│
│  │  io.pyroscope.javaagent.ProfilerSdk                     ││
│  │    (loaded as app dependency)                            ││
│  └─────────────────────────────────────────────────────────┘│
└─────────────────────────────────────────────────────────────┘

Files Changed

  • src/main/java/io/otel/pyroscope/OtelProfilerSdkBridge.java - Static fields, appSdkEnabled flag, cached methods, setSdkInstance()
  • src/main/java/io/otel/pyroscope/PyroscopeOtelConfiguration.java - Added appSdkEnabled
  • src/main/java/io/otel/pyroscope/PyroscopeOtelAutoConfigurationCustomizerProvider.java - Sets appSdkEnabled flag
  • src/main/java/io/otel/pyroscope/instrumentation/ProfilerSdkInstrumentation.java - New
  • src/main/java/io/otel/pyroscope/instrumentation/PyroscopeSdkInstrumentationModule.java - New
  • src/main/resources/META-INF/services/io.opentelemetry.javaagent.extension.instrumentation.InstrumentationModule - New

Usage

For applications using Pyroscope SDK as a dependency (e.g., with async-profiler-actuator-endpoint):

java -javaagent:opentelemetry-javaagent.jar \
     -Dotel.javaagent.extensions=pyroscope-otel.jar \
     -Dotel.pyroscope.app.sdk.enabled=true \
     -jar myapp.jar

Backwards Compatibility

  • Default behavior unchanged (appSdkEnabled=false)
  • When appSdkEnabled=false, instrumentation does not capture SDK
  • Existing Java Agent mode (SystemClassLoader) continues to work as fallback
  • Bundled mode continues to work

Testing

Span to Profile Link in Grafana

image

This adds support for capturing ProfilerSdk instances from the Application
ClassLoader, enabling span-to-profile correlation when using Pyroscope SDK
as an application dependency (e.g., via async-profiler-actuator-endpoint)
instead of the Pyroscope Java Agent.

Changes:
- Add ProfilerSdkInstrumentation to capture ProfilerSdk on construction
- Add PyroscopeSdkInstrumentationModule for OTel agent extension registration
- Add otel.pyroscope.app.sdk.enabled config option (default: false)
- Update OtelProfilerSdkBridge with static fields and setSdkInstance() method
- Cache reflection methods for better performance

When enabled, the instrumentation intercepts ProfilerSdk constructor calls
and captures the instance via OtelProfilerSdkBridge.setSdkInstance(),
allowing the span processor to call setTracingContext() for correlation.
@korniltsev-grafanista korniltsev-grafanista requested a review from a team February 17, 2026 07:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Support Pyroscope SDK loaded from Application ClassLoader

1 participant

Comments