Skip to content

Commit cb58df3

Browse files
committed
removed lock from AndroidTransactionProfiler
1 parent 8c2f79b commit cb58df3

File tree

3 files changed

+132
-150
lines changed

3 files changed

+132
-150
lines changed

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -354,4 +354,8 @@ private void putPerformanceCollectionDataInMeasurements(
354354
}
355355
}
356356
}
357+
358+
boolean isRunning() {
359+
return isRunning;
360+
}
357361
}

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

Lines changed: 108 additions & 130 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,6 @@
55
import android.annotation.SuppressLint;
66
import android.content.Context;
77
import android.os.Build;
8-
import android.os.Process;
9-
import android.os.SystemClock;
108
import io.sentry.DateUtils;
119
import io.sentry.ILogger;
1210
import io.sentry.ISentryExecutorService;
@@ -26,9 +24,9 @@
2624
import java.util.ArrayList;
2725
import java.util.Date;
2826
import java.util.List;
27+
import java.util.concurrent.atomic.AtomicBoolean;
2928
import org.jetbrains.annotations.NotNull;
3029
import org.jetbrains.annotations.Nullable;
31-
import org.jetbrains.annotations.TestOnly;
3230

3331
final class AndroidTransactionProfiler implements ITransactionProfiler {
3432
private final @NotNull Context context;
@@ -38,11 +36,11 @@ final class AndroidTransactionProfiler implements ITransactionProfiler {
3836
private final int profilingTracesHz;
3937
private final @NotNull ISentryExecutorService executorService;
4038
private final @NotNull BuildInfoProvider buildInfoProvider;
41-
private boolean isInitialized = false;
42-
private int transactionsCounter = 0;
39+
private final @NotNull AtomicBoolean isInitialized = new AtomicBoolean(false);
40+
private final @NotNull AtomicBoolean isRunning = new AtomicBoolean(false);
4341
private final @NotNull SentryFrameMetricsCollector frameMetricsCollector;
4442
private @Nullable ProfilingTransactionData currentProfilingTransactionData;
45-
private @Nullable AndroidProfiler profiler = null;
43+
private volatile @Nullable AndroidProfiler profiler = null;
4644
private long profileStartNanos;
4745
private long profileStartCpuMillis;
4846
private @NotNull Date profileStartTimestamp;
@@ -91,10 +89,9 @@ public AndroidTransactionProfiler(
9189

9290
private void init() {
9391
// We initialize it only once
94-
if (isInitialized) {
92+
if (isInitialized.getAndSet(true)) {
9593
return;
9694
}
97-
isInitialized = true;
9895
if (!isProfilingEnabled) {
9996
logger.log(SentryLevel.INFO, "Profiling is disabled in options.");
10097
return;
@@ -124,22 +121,26 @@ private void init() {
124121

125122
@Override
126123
public void start() {
127-
try (final @NotNull ISentryLifecycleToken ignored = lock.acquire()) {
128-
// Debug.startMethodTracingSampling() is only available since Lollipop, but Android Profiler
129-
// causes crashes on api 21 -> https://github.com/getsentry/sentry-java/issues/3392
130-
if (buildInfoProvider.getSdkInfoVersion() < Build.VERSION_CODES.LOLLIPOP_MR1) return;
124+
// Debug.startMethodTracingSampling() is only available since Lollipop, but Android Profiler
125+
// causes crashes on api 21 -> https://github.com/getsentry/sentry-java/issues/3392
126+
if (buildInfoProvider.getSdkInfoVersion() < Build.VERSION_CODES.LOLLIPOP_MR1) return;
131127

132-
// Let's initialize trace folder and profiling interval
133-
init();
128+
// Let's initialize trace folder and profiling interval
129+
init();
134130

135-
transactionsCounter++;
136-
// When the first transaction is starting, we can start profiling
137-
if (transactionsCounter == 1 && onFirstStart()) {
131+
// When the first transaction is starting, we can start profiling
132+
if (!isRunning.getAndSet(true)) {
133+
if (onFirstStart()) {
138134
logger.log(SentryLevel.DEBUG, "Profiler started.");
139135
} else {
140-
transactionsCounter--;
141-
logger.log(
142-
SentryLevel.WARNING, "A profile is already running. This profile will be ignored.");
136+
// If profiler is not null and is running, it means that a profile is already running
137+
if (profiler != null && profiler.isRunning()) {
138+
logger.log(
139+
SentryLevel.WARNING, "A profile is already running. This profile will be ignored.");
140+
} else {
141+
// Otherwise we update the flag, because it means the profiler is not running
142+
isRunning.set(false);
143+
}
143144
}
144145
}
145146
}
@@ -164,11 +165,14 @@ private boolean onFirstStart() {
164165

165166
@Override
166167
public void bindTransaction(final @NotNull ITransaction transaction) {
167-
try (final @NotNull ISentryLifecycleToken ignored = lock.acquire()) {
168-
// If the profiler is running, but no profilingTransactionData is set, we bind it here
169-
if (transactionsCounter > 0 && currentProfilingTransactionData == null) {
170-
currentProfilingTransactionData =
171-
new ProfilingTransactionData(transaction, profileStartNanos, profileStartCpuMillis);
168+
// If the profiler is running, but no profilingTransactionData is set, we bind it here
169+
if (isRunning.get() && currentProfilingTransactionData == null) {
170+
try (final @NotNull ISentryLifecycleToken ignored = lock.acquire()) {
171+
// If the profiler is running, but no profilingTransactionData is set, we bind it here
172+
if (isRunning.get() && currentProfilingTransactionData == null) {
173+
currentProfilingTransactionData =
174+
new ProfilingTransactionData(transaction, profileStartNanos, profileStartCpuMillis);
175+
}
172176
}
173177
}
174178
}
@@ -178,15 +182,13 @@ public void bindTransaction(final @NotNull ITransaction transaction) {
178182
final @NotNull ITransaction transaction,
179183
final @Nullable List<PerformanceCollectionData> performanceCollectionData,
180184
final @NotNull SentryOptions options) {
181-
try (final @NotNull ISentryLifecycleToken ignored = lock.acquire()) {
182-
return onTransactionFinish(
183-
transaction.getName(),
184-
transaction.getEventId().toString(),
185-
transaction.getSpanContext().getTraceId().toString(),
186-
false,
187-
performanceCollectionData,
188-
options);
189-
}
185+
return onTransactionFinish(
186+
transaction.getName(),
187+
transaction.getEventId().toString(),
188+
transaction.getSpanContext().getTraceId().toString(),
189+
false,
190+
performanceCollectionData,
191+
options);
190192
}
191193

192194
@SuppressLint("NewApi")
@@ -197,20 +199,23 @@ public void bindTransaction(final @NotNull ITransaction transaction) {
197199
final boolean isTimeout,
198200
final @Nullable List<PerformanceCollectionData> performanceCollectionData,
199201
final @NotNull SentryOptions options) {
200-
try (final @NotNull ISentryLifecycleToken ignored = lock.acquire()) {
201-
// check if profiler was created
202-
if (profiler == null) {
203-
return null;
204-
}
205202

206-
// onTransactionStart() is only available since Lollipop_MR1
207-
// and SystemClock.elapsedRealtimeNanos() since Jelly Bean
208-
// and SUPPORTED_ABIS since KITKAT
209-
if (buildInfoProvider.getSdkInfoVersion() < Build.VERSION_CODES.LOLLIPOP_MR1) return null;
203+
// onTransactionStart() is only available since Lollipop_MR1
204+
// and SystemClock.elapsedRealtimeNanos() since Jelly Bean
205+
// and SUPPORTED_ABIS since KITKAT
206+
if (buildInfoProvider.getSdkInfoVersion() < Build.VERSION_CODES.LOLLIPOP_MR1) return null;
207+
208+
// check if profiler was created
209+
if (profiler == null) {
210+
return null;
211+
}
212+
213+
final ProfilingTransactionData txData;
214+
try (final @NotNull ISentryLifecycleToken ignored = lock.acquire()) {
215+
txData = currentProfilingTransactionData;
210216

211217
// Transaction finished, but it's not in the current profile
212-
if (currentProfilingTransactionData == null
213-
|| !currentProfilingTransactionData.getId().equals(transactionId)) {
218+
if (txData == null || !txData.getId().equals(transactionId)) {
214219
// A transaction is finishing, but it's not profiled. We can skip it
215220
logger.log(
216221
SentryLevel.INFO,
@@ -219,118 +224,91 @@ public void bindTransaction(final @NotNull ITransaction transaction) {
219224
traceId);
220225
return null;
221226
}
227+
currentProfilingTransactionData = null;
228+
}
222229

223-
if (transactionsCounter > 0) {
224-
transactionsCounter--;
225-
}
226-
227-
logger.log(SentryLevel.DEBUG, "Transaction %s (%s) finished.", transactionName, traceId);
228-
229-
if (transactionsCounter != 0) {
230-
// We notify the data referring to this transaction that it finished
231-
if (currentProfilingTransactionData != null) {
232-
currentProfilingTransactionData.notifyFinish(
233-
SystemClock.elapsedRealtimeNanos(),
234-
profileStartNanos,
235-
Process.getElapsedCpuTime(),
236-
profileStartCpuMillis);
237-
}
238-
return null;
239-
}
230+
logger.log(SentryLevel.DEBUG, "Transaction %s (%s) finished.", transactionName, traceId);
240231

241-
final AndroidProfiler.ProfileEndData endData =
242-
profiler.endAndCollect(false, performanceCollectionData);
243-
// check if profiler end successfully
244-
if (endData == null) {
245-
return null;
246-
}
232+
final AndroidProfiler.ProfileEndData endData =
233+
profiler.endAndCollect(false, performanceCollectionData);
247234

248-
long transactionDurationNanos = endData.endNanos - profileStartNanos;
235+
isRunning.set(false);
249236

250-
List<ProfilingTransactionData> transactionList = new ArrayList<>(1);
251-
final ProfilingTransactionData txData = currentProfilingTransactionData;
252-
if (txData != null) {
253-
transactionList.add(txData);
254-
}
255-
currentProfilingTransactionData = null;
256-
// We clear the counter in case of a timeout
257-
transactionsCounter = 0;
237+
// check if profiler end successfully
238+
if (endData == null) {
239+
return null;
240+
}
258241

259-
String totalMem = "0";
260-
final @Nullable Long memory =
261-
(options instanceof SentryAndroidOptions)
262-
? DeviceInfoUtil.getInstance(context, (SentryAndroidOptions) options).getTotalMemory()
263-
: null;
264-
if (memory != null) {
265-
totalMem = Long.toString(memory);
266-
}
267-
String[] abis = Build.SUPPORTED_ABIS;
242+
long transactionDurationNanos = endData.endNanos - profileStartNanos;
268243

269-
// We notify all transactions data that all transactions finished.
270-
// Some may not have been really finished, in case of a timeout
271-
for (ProfilingTransactionData t : transactionList) {
272-
t.notifyFinish(
273-
endData.endNanos, profileStartNanos, endData.endCpuMillis, profileStartCpuMillis);
274-
}
244+
final @NotNull List<ProfilingTransactionData> transactionList = new ArrayList<>(1);
245+
transactionList.add(txData);
246+
txData.notifyFinish(
247+
endData.endNanos, profileStartNanos, endData.endCpuMillis, profileStartCpuMillis);
275248

276-
// cpu max frequencies are read with a lambda because reading files is involved, so it will be
277-
// done in the background when the trace file is read
278-
return new ProfilingTraceData(
279-
endData.traceFile,
280-
profileStartTimestamp,
281-
transactionList,
282-
transactionName,
283-
transactionId,
284-
traceId,
285-
Long.toString(transactionDurationNanos),
286-
buildInfoProvider.getSdkInfoVersion(),
287-
abis != null && abis.length > 0 ? abis[0] : "",
288-
() -> CpuInfoUtils.getInstance().readMaxFrequencies(),
289-
buildInfoProvider.getManufacturer(),
290-
buildInfoProvider.getModel(),
291-
buildInfoProvider.getVersionRelease(),
292-
buildInfoProvider.isEmulator(),
293-
totalMem,
294-
options.getProguardUuid(),
295-
options.getRelease(),
296-
options.getEnvironment(),
297-
(endData.didTimeout || isTimeout)
298-
? ProfilingTraceData.TRUNCATION_REASON_TIMEOUT
299-
: ProfilingTraceData.TRUNCATION_REASON_NORMAL,
300-
endData.measurementsMap);
249+
String totalMem = "0";
250+
final @Nullable Long memory =
251+
(options instanceof SentryAndroidOptions)
252+
? DeviceInfoUtil.getInstance(context, (SentryAndroidOptions) options).getTotalMemory()
253+
: null;
254+
if (memory != null) {
255+
totalMem = Long.toString(memory);
301256
}
257+
final String[] abis = Build.SUPPORTED_ABIS;
258+
259+
// cpu max frequencies are read with a lambda because reading files is involved, so it will be
260+
// done in the background when the trace file is read
261+
return new ProfilingTraceData(
262+
endData.traceFile,
263+
profileStartTimestamp,
264+
transactionList,
265+
transactionName,
266+
transactionId,
267+
traceId,
268+
Long.toString(transactionDurationNanos),
269+
buildInfoProvider.getSdkInfoVersion(),
270+
abis != null && abis.length > 0 ? abis[0] : "",
271+
() -> CpuInfoUtils.getInstance().readMaxFrequencies(),
272+
buildInfoProvider.getManufacturer(),
273+
buildInfoProvider.getModel(),
274+
buildInfoProvider.getVersionRelease(),
275+
buildInfoProvider.isEmulator(),
276+
totalMem,
277+
options.getProguardUuid(),
278+
options.getRelease(),
279+
options.getEnvironment(),
280+
(endData.didTimeout || isTimeout)
281+
? ProfilingTraceData.TRUNCATION_REASON_TIMEOUT
282+
: ProfilingTraceData.TRUNCATION_REASON_NORMAL,
283+
endData.measurementsMap);
302284
}
303285

304286
@Override
305287
public boolean isRunning() {
306-
return transactionsCounter != 0;
288+
return isRunning.get();
307289
}
308290

309291
@Override
310292
public void close() {
293+
final @Nullable ProfilingTransactionData txData = currentProfilingTransactionData;
311294
// we stop profiling
312-
if (currentProfilingTransactionData != null) {
295+
if (txData != null) {
313296
onTransactionFinish(
314-
currentProfilingTransactionData.getName(),
315-
currentProfilingTransactionData.getId(),
316-
currentProfilingTransactionData.getTraceId(),
297+
txData.getName(),
298+
txData.getId(),
299+
txData.getTraceId(),
317300
true,
318301
null,
319302
ScopesAdapter.getInstance().getOptions());
320-
} else if (transactionsCounter != 0) {
303+
} else if (isRunning.get()) {
321304
// in case the app start profiling is running, and it's not bound to a transaction, we still
322-
// stop profiling, but we also have to manually update the counter.
323-
transactionsCounter--;
305+
// stop profiling, but we also have to manually update the flag.
306+
isRunning.set(false);
324307
}
325308

326309
// we have to first stop profiling otherwise we would lost the last profile
327310
if (profiler != null) {
328311
profiler.close();
329312
}
330313
}
331-
332-
@TestOnly
333-
int getTransactionsCounter() {
334-
return transactionsCounter;
335-
}
336314
}

0 commit comments

Comments
 (0)