Skip to content

Commit 574d4cc

Browse files
committed
add cursor rules for the profiling feature
1 parent e59e22a commit 574d4cc

File tree

1 file changed

+133
-0
lines changed

1 file changed

+133
-0
lines changed

.cursor/rules/profiling.mdc

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
---
2+
alwaysApply: false
3+
description: Java SDK Profiling
4+
---
5+
# Java SDK Profiling
6+
7+
The Sentry Java SDK provides continuous profiling through the `sentry-async-profiler` module, which integrates async-profiler for low-overhead CPU profiling.
8+
9+
## Module Structure
10+
11+
- **`sentry-async-profiler`**: Standalone module containing async-profiler integration
12+
- Uses Java ServiceLoader pattern for discovery
13+
- No direct dependency from core `sentry` module
14+
- Opt-in by adding module as dependency
15+
16+
- **`sentry` core abstractions**:
17+
- `IContinuousProfiler`: Interface for profiler implementations
18+
- `ProfileChunk`: Profile data structure sent to Sentry
19+
- `IProfileConverter`: Converts JFR files to Sentry format
20+
- `ProfileLifecycle`: Controls lifecycle (MANUAL vs TRACE)
21+
- `ProfilingServiceLoader`: ServiceLoader discovery
22+
23+
## Key Classes
24+
25+
### `JavaContinuousProfiler` (sentry-async-profiler)
26+
- Wraps native async-profiler library
27+
- Writes JFR files to `profilingTracesDirPath`
28+
- Rotates chunks periodically (`MAX_CHUNK_DURATION_MILLIS`)
29+
- Implements `RateLimiter.IRateLimitObserver` for rate limiting
30+
- Maintains `rootSpanCounter` for TRACE mode lifecycle
31+
32+
### `ProfileChunk`
33+
- Contains profiler ID (session-level, persists across chunks), chunk ID, JFR file reference
34+
- Built using `ProfileChunk.Builder`
35+
- JFR file converted to `SentryProfile` before sending
36+
37+
### `ProfileLifecycle`
38+
- `MANUAL`: Explicit `Sentry.startProfiler()` / `stopProfiler()` calls
39+
- `TRACE`: Automatic, tied to active sampled root spans
40+
41+
## Configuration
42+
43+
- **`profilesSampleRate`**: Sample rate (0.0 to 1.0). If set with `tracesSampleRate`, enables transaction profiling. If set alone, enables continuous profiling.
44+
- **`profileLifecycle`**: `ProfileLifecycle.MANUAL` (default) or `ProfileLifecycle.TRACE`
45+
- **`cacheDirPath`**: Directory for JFR files (required)
46+
- **`profilingTracesHz`**: Sampling frequency in Hz (default: 101)
47+
48+
Example:
49+
```java
50+
options.setProfilesSampleRate(1.0);
51+
options.setCacheDirPath("/tmp/sentry-cache");
52+
options.setProfileLifecycle(ProfileLifecycle.MANUAL);
53+
```
54+
55+
## How It Works
56+
57+
### Initialization
58+
`ProfilingServiceLoader.loadContinuousProfiler()` uses ServiceLoader to find `AsyncProfilerContinuousProfilerProvider`, which instantiates `JavaContinuousProfiler`.
59+
60+
### Profiling Flow
61+
62+
**Start**:
63+
- Sampling decision via `TracesSampler`
64+
- Rate limit check (abort if active)
65+
- Generate JFR filename: `<cacheDirPath>/<UUID>.jfr`
66+
- Execute async-profiler: `start,jfr,event=wall,nobatch,interval=<interval>,file=<path>`
67+
- Schedule chunk rotation (default: 10 seconds)
68+
69+
**Chunk Rotation**:
70+
- Stop profiler and validate JFR file
71+
- Create `ProfileChunk.Builder` with profiler ID, chunk ID, file, timestamp, platform
72+
- Store in `payloadBuilders` list
73+
- Send chunks if scopes available
74+
- Restart profiler for next chunk
75+
76+
**Stop**:
77+
- MANUAL: Stop without restart, reset profiler ID
78+
- TRACE: Decrement `rootSpanCounter`, stop only when counter reaches 0
79+
80+
### Sending
81+
- Chunks in `payloadBuilders` built via `builder.build(options)`
82+
- Captured via `scopes.captureProfileChunk(chunk)`
83+
- JFR converted to `SentryProfile` using `IProfileConverter`
84+
- Sent as envelope to Sentry
85+
86+
## TRACE Mode Lifecycle
87+
- `rootSpanCounter` incremented when sampled root span starts
88+
- `rootSpanCounter` decremented when root span finishes
89+
- Profiler runs while counter > 0
90+
- Allows multiple concurrent transactions to share profiler session
91+
92+
## Rate Limiting and Offline
93+
94+
### Rate Limiting
95+
- Registers as `RateLimiter.IRateLimitObserver`
96+
- When rate limited for `ProfileChunk` or `All`:
97+
- Stops immediately without restart
98+
- Discards current chunk
99+
- Resets profiler ID
100+
- Checked before starting
101+
- Does NOT auto-restart when rate limit expires
102+
103+
### Offline Behavior
104+
- JFR files written to `cacheDirPath`, marked `deleteOnExit()`
105+
- `ProfileChunk.Builder` buffered in `payloadBuilders` if offline
106+
- Sent when SDK comes online, files deleted after successful send
107+
- Profiler can start before SDK initialized - chunks buffered until scopes available (`initScopes()`)
108+
109+
## Platform Differences
110+
111+
### JVM (sentry-async-profiler)
112+
- Native async-profiler library
113+
- Platform: "java"
114+
- Chunk ID always `EMPTY_ID`
115+
116+
### Android (sentry-android-core)
117+
- `AndroidContinuousProfiler` with `Debug.startMethodTracingSampling()`
118+
- Longer chunk duration (60s vs 10s for JVM)
119+
- Includes measurements (frames, memory)
120+
- Platform: "android"
121+
122+
## Extending
123+
124+
Implement `IContinuousProfiler` and `JavaContinuousProfilerProvider`, register in `META-INF/services/io.sentry.profiling.JavaContinuousProfilerProvider`.
125+
126+
Implement `IProfileConverter` and `JavaProfileConverterProvider`, register in `META-INF/services/io.sentry.profiling.JavaProfileConverterProvider`.
127+
128+
## Code Locations
129+
130+
- `sentry/src/main/java/io/sentry/IContinuousProfiler.java`
131+
- `sentry/src/main/java/io/sentry/ProfileChunk.java`
132+
- `sentry-async-profiler/src/main/java/io/sentry/asyncprofiler/profiling/JavaContinuousProfiler.java`
133+
- `sentry-async-profiler/src/main/java/io/sentry/asyncprofiler/convert/JfrAsyncProfilerToSentryProfileConverter.java`

0 commit comments

Comments
 (0)