Skip to content

Commit f6a135d

Browse files
authored
Fix transaction performance collector oom (#2505)
* moved synchronized() bit later in stop() method in DefaultTransactionPerformanceCollector * PerformanceCollectionData now contains single values instead of lists and removed the commit() method * TransactionPerformanceCollector: - is now set in Android options - a NoOpTransactionPerformanceCollector is set in Java options - deletes data after 30 seconds - collect data only if a profile is sampled
1 parent 7da2a31 commit f6a135d

28 files changed

+218
-205
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
### Fixes
66

7+
- Fix transaction performance collector oom ([#2505](https://github.com/getsentry/sentry-java/pull/2505))
78
- Remove authority from URLs sent to Sentry ([#2366](https://github.com/getsentry/sentry-java/pull/2366))
89
- Fix `sentry-bom` containing incorrect artifacts ([#2504](https://github.com/getsentry/sentry-java/pull/2504))
910

sentry-android-core/api/sentry-android-core.api

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ public final class io/sentry/android/core/ActivityLifecycleIntegration : android
2525

2626
public final class io/sentry/android/core/AndroidCpuCollector : io/sentry/ICollector {
2727
public fun <init> (Lio/sentry/ILogger;Lio/sentry/android/core/BuildInfoProvider;)V
28-
public fun collect (Ljava/lang/Iterable;)V
28+
public fun collect (Lio/sentry/PerformanceCollectionData;)V
2929
public fun setup ()V
3030
}
3131

@@ -45,7 +45,7 @@ public final class io/sentry/android/core/AndroidLogger : io/sentry/ILogger {
4545

4646
public class io/sentry/android/core/AndroidMemoryCollector : io/sentry/ICollector {
4747
public fun <init> ()V
48-
public fun collect (Ljava/lang/Iterable;)V
48+
public fun collect (Lio/sentry/PerformanceCollectionData;)V
4949
public fun setup ()V
5050
}
5151

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

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -66,8 +66,7 @@ public void setup() {
6666

6767
@SuppressLint("NewApi")
6868
@Override
69-
public void collect(
70-
@NotNull final Iterable<PerformanceCollectionData> performanceCollectionData) {
69+
public void collect(final @NotNull PerformanceCollectionData performanceCollectionData) {
7170
if (buildInfoProvider.getSdkInfoVersion() < Build.VERSION_CODES.LOLLIPOP || !isEnabled) {
7271
return;
7372
}
@@ -86,9 +85,7 @@ public void collect(
8685
new CpuCollectionData(
8786
System.currentTimeMillis(), (cpuUsagePercentage / (double) numCores) * 100.0);
8887

89-
for (PerformanceCollectionData data : performanceCollectionData) {
90-
data.addCpuData(cpuData);
91-
}
88+
performanceCollectionData.addCpuData(cpuData);
9289
}
9390

9491
/** Read the /proc/self/stat file and parses the result. */

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

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,11 @@ public class AndroidMemoryCollector implements ICollector {
1414
public void setup() {}
1515

1616
@Override
17-
public void collect(@NotNull Iterable<PerformanceCollectionData> performanceCollectionData) {
17+
public void collect(final @NotNull PerformanceCollectionData performanceCollectionData) {
1818
long now = System.currentTimeMillis();
1919
long usedMemory = Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
2020
long usedNativeMemory = Debug.getNativeHeapSize() - Debug.getNativeHeapFreeSize();
2121
MemoryCollectionData memoryData = new MemoryCollectionData(now, usedMemory, usedNativeMemory);
22-
for (PerformanceCollectionData data : performanceCollectionData) {
23-
data.addMemoryData(memoryData);
24-
}
22+
performanceCollectionData.addMemoryData(memoryData);
2523
}
2624
}

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import android.content.pm.PackageInfo;
88
import android.content.res.AssetManager;
99
import android.os.Build;
10+
import io.sentry.DefaultTransactionPerformanceCollector;
1011
import io.sentry.ILogger;
1112
import io.sentry.SendFireAndForgetEnvelopeSender;
1213
import io.sentry.SendFireAndForgetOutboxSender;
@@ -168,6 +169,7 @@ static void initializeIntegrationsAndProcessors(
168169
options.addCollector(new AndroidMemoryCollector());
169170
options.addCollector(new AndroidCpuCollector(options.getLogger(), buildInfoProvider));
170171
}
172+
options.setTransactionPerformanceCollector(new DefaultTransactionPerformanceCollector(options));
171173
}
172174

173175
private static void installDefaultIntegrations(

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

Lines changed: 19 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -251,7 +251,7 @@ public void onFrameMetricCollected(
251251
@Override
252252
public @Nullable synchronized ProfilingTraceData onTransactionFinish(
253253
final @NotNull ITransaction transaction,
254-
final @Nullable PerformanceCollectionData performanceCollectionData) {
254+
final @Nullable List<PerformanceCollectionData> performanceCollectionData) {
255255
try {
256256
return options
257257
.getExecutorService()
@@ -269,7 +269,7 @@ public void onFrameMetricCollected(
269269
private @Nullable ProfilingTraceData onTransactionFinish(
270270
final @NotNull ITransaction transaction,
271271
final boolean isTimeout,
272-
final @Nullable PerformanceCollectionData performanceCollectionData) {
272+
final @Nullable List<PerformanceCollectionData> performanceCollectionData) {
273273

274274
// onTransactionStart() is only available since Lollipop
275275
// and SystemClock.elapsedRealtimeNanos() since Jelly Bean
@@ -416,28 +416,32 @@ public void onFrameMetricCollected(
416416
}
417417

418418
private void putPerformanceCollectionDataInMeasurements(
419-
final @Nullable PerformanceCollectionData performanceCollectionData) {
419+
final @Nullable List<PerformanceCollectionData> performanceCollectionData) {
420420
if (performanceCollectionData != null) {
421421
final @NotNull ArrayDeque<ProfileMeasurementValue> memoryUsageMeasurements =
422-
new ArrayDeque<>();
422+
new ArrayDeque<>(performanceCollectionData.size());
423423
final @NotNull ArrayDeque<ProfileMeasurementValue> nativeMemoryUsageMeasurements =
424-
new ArrayDeque<>();
425-
final @NotNull ArrayDeque<ProfileMeasurementValue> cpuUsageMeasurements = new ArrayDeque<>();
426-
for (CpuCollectionData cpuData : performanceCollectionData.getCpuData()) {
427-
cpuUsageMeasurements.add(
428-
new ProfileMeasurementValue(
429-
TimeUnit.MILLISECONDS.toNanos(cpuData.getTimestampMillis()) - transactionStartNanos,
430-
cpuData.getCpuUsagePercentage()));
431-
}
432-
for (MemoryCollectionData memoryData : performanceCollectionData.getMemoryData()) {
433-
if (memoryData.getUsedHeapMemory() > -1) {
424+
new ArrayDeque<>(performanceCollectionData.size());
425+
final @NotNull ArrayDeque<ProfileMeasurementValue> cpuUsageMeasurements =
426+
new ArrayDeque<>(performanceCollectionData.size());
427+
for (PerformanceCollectionData performanceData : performanceCollectionData) {
428+
CpuCollectionData cpuData = performanceData.getCpuData();
429+
MemoryCollectionData memoryData = performanceData.getMemoryData();
430+
if (cpuData != null) {
431+
cpuUsageMeasurements.add(
432+
new ProfileMeasurementValue(
433+
TimeUnit.MILLISECONDS.toNanos(cpuData.getTimestampMillis())
434+
- transactionStartNanos,
435+
cpuData.getCpuUsagePercentage()));
436+
}
437+
if (memoryData != null && memoryData.getUsedHeapMemory() > -1) {
434438
memoryUsageMeasurements.add(
435439
new ProfileMeasurementValue(
436440
TimeUnit.MILLISECONDS.toNanos(memoryData.getTimestampMillis())
437441
- transactionStartNanos,
438442
memoryData.getUsedHeapMemory()));
439443
}
440-
if (memoryData.getUsedNativeMemory() > -1) {
444+
if (memoryData != null && memoryData.getUsedNativeMemory() > -1) {
441445
nativeMemoryUsageMeasurements.add(
442446
new ProfileMeasurementValue(
443447
TimeUnit.MILLISECONDS.toNanos(memoryData.getTimestampMillis())

sentry-android-core/src/test/java/io/sentry/android/core/AndroidCpuCollectorTest.kt

Lines changed: 9 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,9 @@ import org.mockito.kotlin.mock
88
import org.mockito.kotlin.whenever
99
import kotlin.test.Test
1010
import kotlin.test.assertFailsWith
11-
import kotlin.test.assertFalse
1211
import kotlin.test.assertNotEquals
1312
import kotlin.test.assertNotNull
14-
import kotlin.test.assertTrue
13+
import kotlin.test.assertNull
1514

1615
class AndroidCpuCollectorTest {
1716

@@ -43,23 +42,20 @@ class AndroidCpuCollectorTest {
4342
@Test
4443
fun `collect works only after setup`() {
4544
val data = PerformanceCollectionData()
46-
fixture.getSut().collect(listOf(data))
47-
data.commitData()
48-
assertTrue(data.cpuData.isEmpty())
45+
fixture.getSut().collect(data)
46+
assertNull(data.cpuData)
4947
}
5048

5149
@Test
5250
fun `when collect cpu is collected`() {
5351
val data = PerformanceCollectionData()
5452
val collector = fixture.getSut()
5553
collector.setup()
56-
collector.collect(listOf(data))
57-
data.commitData()
54+
collector.collect(data)
5855
val cpuData = data.cpuData
59-
assertNotNull(data.cpuData)
60-
assertFalse(data.cpuData.isEmpty())
61-
assertNotEquals(0.0, cpuData[0].cpuUsagePercentage)
62-
assertNotEquals(0, cpuData[0].timestampMillis)
56+
assertNotNull(cpuData)
57+
assertNotEquals(0.0, cpuData.cpuUsagePercentage)
58+
assertNotEquals(0, cpuData.timestampMillis)
6359
}
6460

6561
@Test
@@ -69,8 +65,7 @@ class AndroidCpuCollectorTest {
6965
whenever(mockBuildInfoProvider.sdkInfoVersion).thenReturn(Build.VERSION_CODES.KITKAT)
7066
val collector = fixture.getSut(mockBuildInfoProvider)
7167
collector.setup()
72-
collector.collect(listOf(data))
73-
data.commitData()
74-
assertTrue(data.cpuData.isEmpty())
68+
collector.collect(data)
69+
assertNull(data.cpuData)
7570
}
7671
}

sentry-android-core/src/test/java/io/sentry/android/core/AndroidMemoryCollectorTest.kt

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,11 @@ class AndroidMemoryCollectorTest {
1818

1919
@Test
2020
fun `when collect, both native and heap memory are collected`() {
21-
val performanceCollectionData = PerformanceCollectionData()
22-
val data = listOf(performanceCollectionData)
21+
val data = PerformanceCollectionData()
2322
val usedNativeMemory = Debug.getNativeHeapSize() - Debug.getNativeHeapFreeSize()
2423
val usedMemory = fixture.runtime.totalMemory() - fixture.runtime.freeMemory()
2524
fixture.collector.collect(data)
26-
performanceCollectionData.commitData()
27-
val memoryData = performanceCollectionData.memoryData.firstOrNull()
25+
val memoryData = data.memoryData
2826
assertNotNull(memoryData)
2927
assertNotEquals(-1, memoryData.usedNativeMemory)
3028
assertEquals(usedNativeMemory, memoryData.usedNativeMemory)

sentry-android-core/src/test/java/io/sentry/android/core/AndroidOptionsInitializerTest.kt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import android.content.Context
44
import android.os.Bundle
55
import androidx.test.core.app.ApplicationProvider
66
import androidx.test.ext.junit.runners.AndroidJUnit4
7+
import io.sentry.DefaultTransactionPerformanceCollector
78
import io.sentry.ILogger
89
import io.sentry.MainEventProcessor
910
import io.sentry.SentryOptions
@@ -24,6 +25,7 @@ import kotlin.test.BeforeTest
2425
import kotlin.test.Test
2526
import kotlin.test.assertEquals
2627
import kotlin.test.assertFalse
28+
import kotlin.test.assertIs
2729
import kotlin.test.assertNotNull
2830
import kotlin.test.assertNull
2931
import kotlin.test.assertTrue
@@ -525,4 +527,11 @@ class AndroidOptionsInitializerTest {
525527

526528
assertTrue { fixture.sentryOptions.collectors.any { it is AndroidCpuCollector } }
527529
}
530+
531+
@Test
532+
fun `DefaultTransactionPerformanceCollector is set to options`() {
533+
fixture.initSut()
534+
535+
assertIs<DefaultTransactionPerformanceCollector>(fixture.sentryOptions.transactionPerformanceCollector)
536+
}
528537
}

sentry-android-core/src/test/java/io/sentry/android/core/AndroidTransactionProfilerTest.kt

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -376,14 +376,18 @@ class AndroidTransactionProfilerTest {
376376
@Test
377377
fun `profiler includes performance measurements when passed on transaction finish`() {
378378
val profiler = fixture.getSut(context)
379-
val memoryCollectionData = PerformanceCollectionData()
380-
memoryCollectionData.addMemoryData(MemoryCollectionData(1, 2, 3))
381-
memoryCollectionData.addCpuData(CpuCollectionData(1, 1.4))
382-
memoryCollectionData.commitData()
383-
memoryCollectionData.addMemoryData(MemoryCollectionData(2, 3, 4))
384-
memoryCollectionData.commitData()
379+
val performanceCollectionData = ArrayList<PerformanceCollectionData>()
380+
var singleData = PerformanceCollectionData()
381+
singleData.addMemoryData(MemoryCollectionData(1, 2, 3))
382+
singleData.addCpuData(CpuCollectionData(1, 1.4))
383+
performanceCollectionData.add(singleData)
384+
385+
singleData = PerformanceCollectionData()
386+
singleData.addMemoryData(MemoryCollectionData(2, 3, 4))
387+
performanceCollectionData.add(singleData)
388+
385389
profiler.onTransactionStart(fixture.transaction1)
386-
val data = profiler.onTransactionFinish(fixture.transaction1, memoryCollectionData)
390+
val data = profiler.onTransactionFinish(fixture.transaction1, performanceCollectionData)
387391
assertContentEquals(
388392
listOf(1.4),
389393
data!!.measurementsMap[ProfileMeasurement.ID_CPU_USAGE]!!.values.map { it.value }

0 commit comments

Comments
 (0)