Skip to content

Commit 543fc40

Browse files
committed
WIP continuous profiling in trace mode
1 parent 54c3a8f commit 543fc40

File tree

1 file changed

+65
-17
lines changed

1 file changed

+65
-17
lines changed

sentry-async-profiler/src/main/java/io/sentry/protocol/profiling/JavaContinuousProfiler.java

Lines changed: 65 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import static io.sentry.DataCategory.All;
44
import static io.sentry.IConnectionStatusProvider.ConnectionStatus.DISCONNECTED;
5+
import static java.util.concurrent.TimeUnit.SECONDS;
56

67
import io.sentry.DataCategory;
78
import io.sentry.IContinuousProfiler;
@@ -55,6 +56,7 @@ public final class JavaContinuousProfiler
5556
private @NotNull SentryId chunkId = SentryId.EMPTY_ID;
5657
private final @NotNull AtomicBoolean isClosed = new AtomicBoolean(false);
5758
private @NotNull SentryDate startProfileChunkTimestamp = new SentryNanotimeDate();
59+
private final @NotNull String profilingIntervalMicros;
5860

5961
private @NotNull String filename = "";
6062

@@ -77,6 +79,8 @@ public JavaContinuousProfiler(
7779
this.profilingTracesHz = profilingTracesHz;
7880
this.executorService = executorService;
7981
this.profiler = AsyncProfiler.getInstance();
82+
this.profilingIntervalMicros =
83+
String.format("%dus", (int) SECONDS.toMicros(1) / profilingTracesHz);
8084
}
8185

8286
private void init() {
@@ -108,23 +112,42 @@ public void startProfiler(
108112
try (final @NotNull ISentryLifecycleToken ignored = lock.acquire()) {
109113
if (shouldSample) {
110114
isSampled = tracesSampler.sampleSessionProfile(SentryRandom.current().nextDouble());
111-
// Kepp TRUE for now
112-
// shouldSample = false;
115+
shouldSample = false;
113116
}
114117
if (!isSampled) {
115118
logger.log(SentryLevel.DEBUG, "Profiler was not started due to sampling decision.");
116119
return;
117120
}
118121

122+
switch (profileLifecycle) {
123+
case TRACE:
124+
// rootSpanCounter should never be negative, unless the user changed profile lifecycle
125+
// while
126+
// the profiler is running or close() is called. This is just a safety check.
127+
if (rootSpanCounter < 0) {
128+
rootSpanCounter = 0;
129+
}
130+
rootSpanCounter++;
131+
break;
132+
case MANUAL:
133+
// We check if the profiler is already running and log a message only in manual mode,
134+
// since
135+
// in trace mode we can have multiple concurrent traces
136+
if (isRunning()) {
137+
logger.log(SentryLevel.DEBUG, "Profiler is already running.");
138+
return;
139+
}
140+
break;
141+
}
142+
119143
if (!isRunning()) {
120144
logger.log(SentryLevel.DEBUG, "Started Profiler.");
121145
start();
122146
}
123147
}
124148
}
125149

126-
@SuppressWarnings("ReferenceEquality")
127-
private void start() {
150+
private void initScopes() {
128151
if ((scopes == null || scopes == NoOpScopes.getInstance())
129152
&& Sentry.getCurrentScopes() != NoOpScopes.getInstance()) {
130153
this.scopes = Sentry.forkedRootScopes("profiler");
@@ -133,13 +156,14 @@ private void start() {
133156
rateLimiter.addRateLimitObserver(this);
134157
}
135158
}
159+
}
160+
161+
@SuppressWarnings("ReferenceEquality")
162+
private void start() {
163+
initScopes();
136164

137165
// Let's initialize trace folder and profiling interval
138166
init();
139-
// init() didn't create profiler, should never happen
140-
if (profiler == null) {
141-
return;
142-
}
143167

144168
if (scopes != null) {
145169
final @Nullable RateLimiter rateLimiter = scopes.getRateLimiter();
@@ -152,6 +176,7 @@ private void start() {
152176
return;
153177
}
154178

179+
// TODO: Taken from the android profiler, do we need this on the JVM as well?
155180
// If device is offline, we don't start the profiler, to avoid flooding the cache
156181
if (scopes.getOptions().getConnectionStatusProvider().getConnectionStatus() == DISCONNECTED) {
157182
logger.log(SentryLevel.WARNING, "Device is offline. Stopping profiler.");
@@ -166,7 +191,13 @@ private void start() {
166191
filename = SentryUUID.generateSentryId() + ".jfr";
167192
final String startData;
168193
try {
169-
startData = profiler.execute("start,jfr,event=wall,file=" + filename);
194+
// final String command =
195+
// String.format("start,jfr,event=cpu,wall=%s,file=%s",profilingIntervalMicros, filename);
196+
final String command =
197+
String.format(
198+
"start,jfr,event=wall,interval=%s,file=%s", profilingIntervalMicros, filename);
199+
System.out.println(command);
200+
startData = profiler.execute(command);
170201
} catch (IOException e) {
171202
throw new RuntimeException(e);
172203
}
@@ -177,7 +208,7 @@ private void start() {
177208

178209
isRunning = true;
179210

180-
if (SentryId.EMPTY_ID.equals(profilerId)) {
211+
if (profilerId == SentryId.EMPTY_ID) {
181212
profilerId = new SentryId();
182213
}
183214

@@ -199,7 +230,24 @@ private void start() {
199230
@Override
200231
public void stopProfiler(final @NotNull ProfileLifecycle profileLifecycle) {
201232
try (final @NotNull ISentryLifecycleToken ignored = lock.acquire()) {
202-
shouldStop = true;
233+
switch (profileLifecycle) {
234+
case TRACE:
235+
rootSpanCounter--;
236+
// If there are active spans, and profile lifecycle is trace, we don't stop the profiler
237+
if (rootSpanCounter > 0) {
238+
return;
239+
}
240+
// rootSpanCounter should never be negative, unless the user changed profile lifecycle
241+
// while the profiler is running or close() is called. This is just a safety check.
242+
if (rootSpanCounter < 0) {
243+
rootSpanCounter = 0;
244+
}
245+
shouldStop = true;
246+
break;
247+
case MANUAL:
248+
shouldStop = true;
249+
break;
250+
}
203251
}
204252
}
205253

@@ -209,7 +257,7 @@ private void stop(final boolean restartProfiler) {
209257
stopFuture.cancel(true);
210258
}
211259
// check if profiler was created and it's running
212-
if (profiler == null || !isRunning) {
260+
if (!isRunning) {
213261
// When the profiler is stopped due to an error (e.g. offline or rate limited), reset the
214262
// ids
215263
profilerId = SentryId.EMPTY_ID;
@@ -333,11 +381,11 @@ public int getRootSpanCounter() {
333381
@Override
334382
public void onRateLimitChanged(@NotNull RateLimiter rateLimiter) {
335383
// We stop the profiler as soon as we are rate limited, to avoid the performance overhead
336-
// if (rateLimiter.isActiveForCategory(All)
337-
// || rateLimiter.isActiveForCategory(DataCategory.ProfileChunk)) {
338-
// logger.log(SentryLevel.WARNING, "SDK is rate limited. Stopping profiler.");
339-
// stop(false);
340-
// }
384+
if (rateLimiter.isActiveForCategory(All)
385+
|| rateLimiter.isActiveForCategory(DataCategory.ProfileChunk)) {
386+
logger.log(SentryLevel.WARNING, "SDK is rate limited. Stopping profiler.");
387+
stop(false);
388+
}
341389
// If we are not rate limited anymore, we don't do anything: the profile is broken, so it's
342390
// useless to restart it automatically
343391
}

0 commit comments

Comments
 (0)