Skip to content

Commit 96c5720

Browse files
committed
replaced synchronized blocks with AutoClosableReentrantLock in AndroidContinuousProfiler
Added "delayed" stop of profiler, which stops the profiler after the current chunk is finished Added default span data (profiler id, thread name and thread id) to transaction root span
1 parent 94079a4 commit 96c5720

File tree

5 files changed

+198
-177
lines changed

5 files changed

+198
-177
lines changed

sentry-android-core/src/main/java/io/sentry/android/core/AndroidContinuousProfiler.java

Lines changed: 128 additions & 109 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import io.sentry.ILogger;
1212
import io.sentry.IScopes;
1313
import io.sentry.ISentryExecutorService;
14+
import io.sentry.ISentryLifecycleToken;
1415
import io.sentry.NoOpScopes;
1516
import io.sentry.PerformanceCollectionData;
1617
import io.sentry.ProfileChunk;
@@ -24,6 +25,7 @@
2425
import io.sentry.android.core.internal.util.SentryFrameMetricsCollector;
2526
import io.sentry.protocol.SentryId;
2627
import io.sentry.transport.RateLimiter;
28+
import io.sentry.util.AutoClosableReentrantLock;
2729
import io.sentry.util.SentryRandom;
2830
import java.util.ArrayList;
2931
import java.util.List;
@@ -58,9 +60,13 @@ public class AndroidContinuousProfiler
5860
private final @NotNull AtomicBoolean isClosed = new AtomicBoolean(false);
5961
private @NotNull SentryDate startProfileChunkTimestamp = new SentryNanotimeDate();
6062
private boolean shouldSample = true;
63+
private boolean shouldStop = false;
6164
private boolean isSampled = false;
6265
private int rootSpanCounter = 0;
6366

67+
private final AutoClosableReentrantLock lock = new AutoClosableReentrantLock();
68+
private final AutoClosableReentrantLock payloadLock = new AutoClosableReentrantLock();
69+
6470
public AndroidContinuousProfiler(
6571
final @NotNull BuildInfoProvider buildInfoProvider,
6672
final @NotNull SentryFrameMetricsCollector frameMetricsCollector,
@@ -106,42 +112,46 @@ private void init() {
106112
}
107113

108114
@Override
109-
public synchronized void startProfiler(
115+
public void startProfiler(
110116
final @NotNull ProfileLifecycle profileLifecycle,
111117
final @NotNull TracesSampler tracesSampler) {
112-
if (shouldSample) {
113-
isSampled = tracesSampler.sampleSessionProfile(SentryRandom.current().nextDouble());
114-
shouldSample = false;
115-
}
116-
if (!isSampled) {
117-
logger.log(SentryLevel.DEBUG, "Profiler was not started due to sampling decision.");
118-
return;
119-
}
120-
switch (profileLifecycle) {
121-
case TRACE:
122-
// rootSpanCounter should never be negative, unless the user changed profile lifecycle while
123-
// the profiler is running or close() is called. This is just a safety check.
124-
if (rootSpanCounter < 0) {
125-
rootSpanCounter = 0;
126-
}
127-
rootSpanCounter++;
128-
break;
129-
case MANUAL:
130-
// We check if the profiler is already running and log a message only in manual mode, since
131-
// in trace mode we can have multiple concurrent traces
132-
if (isRunning()) {
133-
logger.log(SentryLevel.DEBUG, "Profiler is already running.");
134-
return;
135-
}
136-
break;
137-
}
138-
if (!isRunning()) {
139-
logger.log(SentryLevel.DEBUG, "Started Profiler.");
140-
start();
118+
try (final @NotNull ISentryLifecycleToken ignored = lock.acquire()) {
119+
if (shouldSample) {
120+
isSampled = tracesSampler.sampleSessionProfile(SentryRandom.current().nextDouble());
121+
shouldSample = false;
122+
}
123+
if (!isSampled) {
124+
logger.log(SentryLevel.DEBUG, "Profiler was not started due to sampling decision.");
125+
return;
126+
}
127+
switch (profileLifecycle) {
128+
case TRACE:
129+
// rootSpanCounter should never be negative, unless the user changed profile lifecycle
130+
// while
131+
// the profiler is running or close() is called. This is just a safety check.
132+
if (rootSpanCounter < 0) {
133+
rootSpanCounter = 0;
134+
}
135+
rootSpanCounter++;
136+
break;
137+
case MANUAL:
138+
// We check if the profiler is already running and log a message only in manual mode,
139+
// since
140+
// in trace mode we can have multiple concurrent traces
141+
if (isRunning()) {
142+
logger.log(SentryLevel.DEBUG, "Profiler is already running.");
143+
return;
144+
}
145+
break;
146+
}
147+
if (!isRunning()) {
148+
logger.log(SentryLevel.DEBUG, "Started Profiler.");
149+
start();
150+
}
141151
}
142152
}
143153

144-
private synchronized void start() {
154+
private void start() {
145155
if ((scopes == null || scopes == NoOpScopes.getInstance())
146156
&& Sentry.getCurrentScopes() != NoOpScopes.getInstance()) {
147157
this.scopes = Sentry.getCurrentScopes();
@@ -213,103 +223,112 @@ private synchronized void start() {
213223
SentryLevel.ERROR,
214224
"Failed to schedule profiling chunk finish. Did you call Sentry.close()?",
215225
e);
226+
shouldStop = true;
216227
}
217228
}
218229

219230
@Override
220-
public synchronized void stopProfiler(final @NotNull ProfileLifecycle profileLifecycle) {
221-
switch (profileLifecycle) {
222-
case TRACE:
223-
rootSpanCounter--;
224-
// If there are active spans, and profile lifecycle is trace, we don't stop the profiler
225-
if (rootSpanCounter > 0) {
226-
return;
227-
}
228-
// rootSpanCounter should never be negative, unless the user changed profile lifecycle while
229-
// the profiler is running or close() is called. This is just a safety check.
230-
if (rootSpanCounter < 0) {
231-
rootSpanCounter = 0;
232-
}
233-
stop(false);
234-
break;
235-
case MANUAL:
236-
stop(false);
237-
break;
231+
public void stopProfiler(final @NotNull ProfileLifecycle profileLifecycle) {
232+
try (final @NotNull ISentryLifecycleToken ignored = lock.acquire()) {
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+
}
238251
}
239252
}
240253

241-
private synchronized void stop(final boolean restartProfiler) {
242-
if (stopFuture != null) {
243-
stopFuture.cancel(true);
244-
}
245-
// check if profiler was created and it's running
246-
if (profiler == null || !isRunning) {
247-
// When the profiler is stopped due to an error (e.g. offline or rate limited), reset the ids
248-
profilerId = SentryId.EMPTY_ID;
249-
chunkId = SentryId.EMPTY_ID;
250-
return;
251-
}
252-
253-
// onTransactionStart() is only available since Lollipop_MR1
254-
// and SystemClock.elapsedRealtimeNanos() since Jelly Bean
255-
if (buildInfoProvider.getSdkInfoVersion() < Build.VERSION_CODES.LOLLIPOP_MR1) {
256-
return;
257-
}
254+
private void stop(final boolean restartProfiler) {
255+
try (final @NotNull ISentryLifecycleToken ignored = lock.acquire()) {
256+
if (stopFuture != null) {
257+
stopFuture.cancel(true);
258+
}
259+
// check if profiler was created and it's running
260+
if (profiler == null || !isRunning) {
261+
// When the profiler is stopped due to an error (e.g. offline or rate limited), reset the
262+
// ids
263+
profilerId = SentryId.EMPTY_ID;
264+
chunkId = SentryId.EMPTY_ID;
265+
return;
266+
}
258267

259-
List<PerformanceCollectionData> performanceCollectionData = null;
260-
if (performanceCollector != null) {
261-
performanceCollectionData = performanceCollector.stop(chunkId.toString());
262-
}
268+
// onTransactionStart() is only available since Lollipop_MR1
269+
// and SystemClock.elapsedRealtimeNanos() since Jelly Bean
270+
if (buildInfoProvider.getSdkInfoVersion() < Build.VERSION_CODES.LOLLIPOP_MR1) {
271+
return;
272+
}
263273

264-
final AndroidProfiler.ProfileEndData endData =
265-
profiler.endAndCollect(false, performanceCollectionData);
274+
List<PerformanceCollectionData> performanceCollectionData = null;
275+
if (performanceCollector != null) {
276+
performanceCollectionData = performanceCollector.stop(chunkId.toString());
277+
}
266278

267-
// check if profiler end successfully
268-
if (endData == null) {
269-
logger.log(
270-
SentryLevel.ERROR,
271-
"An error occurred while collecting a profile chunk, and it won't be sent.");
272-
} else {
273-
// The scopes can be null if the profiler is started before the SDK is initialized (app start
274-
// profiling), meaning there's no scopes to send the chunks. In that case, we store the data
275-
// in a list and send it when the next chunk is finished.
276-
synchronized (payloadBuilders) {
277-
payloadBuilders.add(
278-
new ProfileChunk.Builder(
279-
profilerId,
280-
chunkId,
281-
endData.measurementsMap,
282-
endData.traceFile,
283-
startProfileChunkTimestamp));
279+
final AndroidProfiler.ProfileEndData endData =
280+
profiler.endAndCollect(false, performanceCollectionData);
281+
282+
// check if profiler end successfully
283+
if (endData == null) {
284+
logger.log(
285+
SentryLevel.ERROR,
286+
"An error occurred while collecting a profile chunk, and it won't be sent.");
287+
} else {
288+
// The scopes can be null if the profiler is started before the SDK is initialized (app
289+
// start profiling), meaning there's no scopes to send the chunks. In that case, we store
290+
// the data in a list and send it when the next chunk is finished.
291+
try (final @NotNull ISentryLifecycleToken ignored2 = payloadLock.acquire()) {
292+
payloadBuilders.add(
293+
new ProfileChunk.Builder(
294+
profilerId,
295+
chunkId,
296+
endData.measurementsMap,
297+
endData.traceFile,
298+
startProfileChunkTimestamp));
299+
}
284300
}
285-
}
286301

287-
isRunning = false;
288-
// A chunk is finished. Next chunk will have a different id.
289-
chunkId = SentryId.EMPTY_ID;
302+
isRunning = false;
303+
// A chunk is finished. Next chunk will have a different id.
304+
chunkId = SentryId.EMPTY_ID;
290305

291-
if (scopes != null) {
292-
sendChunks(scopes, scopes.getOptions());
293-
}
306+
if (scopes != null) {
307+
sendChunks(scopes, scopes.getOptions());
308+
}
294309

295-
if (restartProfiler) {
296-
logger.log(SentryLevel.DEBUG, "Profile chunk finished. Starting a new one.");
297-
start();
298-
} else {
299-
// When the profiler is stopped manually, we have to reset its id
300-
profilerId = SentryId.EMPTY_ID;
301-
logger.log(SentryLevel.DEBUG, "Profile chunk finished.");
310+
if (restartProfiler && !shouldStop) {
311+
logger.log(SentryLevel.DEBUG, "Profile chunk finished. Starting a new one.");
312+
start();
313+
} else {
314+
// When the profiler is stopped manually, we have to reset its id
315+
profilerId = SentryId.EMPTY_ID;
316+
logger.log(SentryLevel.DEBUG, "Profile chunk finished.");
317+
}
302318
}
303319
}
304320

305-
public synchronized void reevaluateSampling() {
321+
public void reevaluateSampling() {
306322
shouldSample = true;
307323
}
308324

309-
public synchronized void close() {
310-
rootSpanCounter = 0;
311-
stop(false);
312-
isClosed.set(true);
325+
public void close() {
326+
try (final @NotNull ISentryLifecycleToken ignored = lock.acquire()) {
327+
rootSpanCounter = 0;
328+
shouldStop = true;
329+
stop(false);
330+
isClosed.set(true);
331+
}
313332
}
314333

315334
@Override
@@ -328,7 +347,7 @@ private void sendChunks(final @NotNull IScopes scopes, final @NotNull SentryOpti
328347
return;
329348
}
330349
final ArrayList<ProfileChunk> payloads = new ArrayList<>(payloadBuilders.size());
331-
synchronized (payloadBuilders) {
350+
try (final @NotNull ISentryLifecycleToken ignored = payloadLock.acquire()) {
332351
for (ProfileChunk.Builder builder : payloadBuilders) {
333352
payloads.add(builder.build(options));
334353
}

0 commit comments

Comments
 (0)