Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@

### Fixes

- Remove per-sample allocations from performance data collection ([#5933](https://github.com/getsentry/sentry-java/pull/5933))
- `PerformanceCollectionData` now stores its measurements as primitives instead of boxed types, and the 30s transaction timeout check reads the clock once per collection round instead of once per in-flight transaction
- Clear contexts when calling `Scope.clear()` ([#5902](https://github.com/getsentry/sentry-java/pull/5902))
- Preserve custom `Throwable` identities when R8 optimizes Android apps ([#5881](https://github.com/getsentry/sentry-java/pull/5881))

Expand Down
1 change: 1 addition & 0 deletions sentry-android-core/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ dependencies {
testImplementation(libs.androidx.test.ext.junit)
testImplementation(libs.androidx.test.runner)
testImplementation(libs.awaitility.kotlin)
testImplementation(libs.google.truth)
testImplementation(libs.mockito.kotlin)
testImplementation(libs.mockito.inline)
testImplementation(projects.sentryTestSupport)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -322,21 +322,21 @@ private void putPerformanceCollectionDataInMeasurements(
for (final @NotNull PerformanceCollectionData data : performanceCollectionData) {
final long nanoTimestamp = data.getNanoTimestamp();
final long relativeStartNs = nanoTimestamp + timestampDiff;
final @Nullable Double cpuUsagePercentage = data.getCpuUsagePercentage();
final @Nullable Long usedHeapMemory = data.getUsedHeapMemory();
final @Nullable Long usedNativeMemory = data.getUsedNativeMemory();

if (cpuUsagePercentage != null) {
if (data.hasCpuUsagePercentage()) {
cpuUsageMeasurements.add(
new ProfileMeasurementValue(relativeStartNs, cpuUsagePercentage, nanoTimestamp));
new ProfileMeasurementValue(
relativeStartNs, data.getCpuUsagePercentage(), nanoTimestamp));
}
if (usedHeapMemory != null) {
if (data.hasUsedHeapMemory()) {
memoryUsageMeasurements.add(
new ProfileMeasurementValue(relativeStartNs, usedHeapMemory, nanoTimestamp));
new ProfileMeasurementValue(
relativeStartNs, data.getUsedHeapMemory(), nanoTimestamp));
}
if (usedNativeMemory != null) {
if (data.hasUsedNativeMemory()) {
nativeMemoryUsageMeasurements.add(
new ProfileMeasurementValue(relativeStartNs, usedNativeMemory, nanoTimestamp));
new ProfileMeasurementValue(
relativeStartNs, data.getUsedNativeMemory(), nanoTimestamp));
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -609,21 +609,20 @@ private static void addPerformanceDataToMeasurements(
final long sampleElapsedRealtimeNanos = elapsedRealtimeNowNanos - nanosSinceSample;
final long relativeStartNs =
sampleElapsedRealtimeNanos - profileStartElapsedRealtimeNanos;
final @Nullable Double cpuUsagePercentage = data.getCpuUsagePercentage();
final @Nullable Long usedHeapMemory = data.getUsedHeapMemory();
final @Nullable Long usedNativeMemory = data.getUsedNativeMemory();

if (cpuUsagePercentage != null) {
if (data.hasCpuUsagePercentage()) {
cpuUsageMeasurements.addLast(
new ProfileMeasurementValue(relativeStartNs, cpuUsagePercentage, nanoTimestamp));
new ProfileMeasurementValue(
relativeStartNs, data.getCpuUsagePercentage(), nanoTimestamp));
}
if (usedHeapMemory != null) {
if (data.hasUsedHeapMemory()) {
memoryUsageMeasurements.addLast(
new ProfileMeasurementValue(relativeStartNs, usedHeapMemory, nanoTimestamp));
new ProfileMeasurementValue(
relativeStartNs, data.getUsedHeapMemory(), nanoTimestamp));
}
if (usedNativeMemory != null) {
if (data.hasUsedNativeMemory()) {
nativeMemoryUsageMeasurements.addLast(
new ProfileMeasurementValue(relativeStartNs, usedNativeMemory, nanoTimestamp));
new ProfileMeasurementValue(
relativeStartNs, data.getUsedNativeMemory(), nanoTimestamp));
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
package io.sentry.android.core

import com.google.common.truth.Truth.assertThat
import io.sentry.ILogger
import io.sentry.PerformanceCollectionData
import io.sentry.test.getCtor
import kotlin.test.Test
import kotlin.test.assertFailsWith
import kotlin.test.assertNotEquals
import kotlin.test.assertNotNull
import kotlin.test.assertNull
import org.mockito.kotlin.mock

class AndroidCpuCollectorTest {
Expand All @@ -30,7 +28,7 @@ class AndroidCpuCollectorTest {
fun `collect works only after setup`() {
val data = PerformanceCollectionData(10)
fixture.getSut().collect(data)
assertNull(data.cpuUsagePercentage)
assertThat(data.hasCpuUsagePercentage()).isFalse()
}

@Test
Expand All @@ -39,8 +37,7 @@ class AndroidCpuCollectorTest {
val collector = fixture.getSut()
collector.setup()
collector.collect(data)
val cpuData = data.cpuUsagePercentage
assertNotNull(cpuData)
assertNotEquals(0.0, cpuData)
assertThat(data.hasCpuUsagePercentage()).isTrue()
assertThat(data.cpuUsagePercentage).isNotEqualTo(0.0)
}
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
package io.sentry.android.core

import android.os.Debug
import com.google.common.truth.Truth.assertThat
import io.sentry.PerformanceCollectionData
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertNotEquals
import kotlin.test.assertNotNull

class AndroidMemoryCollectorTest {
private val fixture = Fixture()
Expand All @@ -21,10 +19,9 @@ class AndroidMemoryCollectorTest {
val usedNativeMemory = Debug.getNativeHeapSize() - Debug.getNativeHeapFreeSize()
val usedMemory = fixture.runtime.totalMemory() - fixture.runtime.freeMemory()
fixture.collector.collect(data)
assertNotNull(data.usedHeapMemory)
assertNotNull(data.usedNativeMemory)
assertNotEquals(-1, data.usedNativeMemory)
assertEquals(usedNativeMemory, data.usedNativeMemory)
assertEquals(usedMemory, data.usedHeapMemory)
assertThat(data.hasUsedHeapMemory()).isTrue()
assertThat(data.hasUsedNativeMemory()).isTrue()
assertThat(data.usedNativeMemory).isEqualTo(usedNativeMemory)
assertThat(data.usedHeapMemory).isEqualTo(usedMemory)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -111,11 +111,12 @@ class ChunkMeasurementCollectorTest {
*/
private fun futureFrameEndNanos() = System.nanoTime() + TimeUnit.MINUTES.toNanos(1)

/** A null measurement is left unset, as it would be by a collector that did not report it. */
private fun perfData(nanos: Long, cpu: Double?, heap: Long?, native: Long?) =
PerformanceCollectionData(nanos).apply {
cpuUsagePercentage = cpu
usedHeapMemory = heap
usedNativeMemory = native
cpu?.let { cpuUsagePercentage = it }
heap?.let { usedHeapMemory = it }
native?.let { usedNativeMemory = it }
}

private fun assertChunkCounts(
Expand Down
15 changes: 9 additions & 6 deletions sentry/api/sentry.api
Original file line number Diff line number Diff line change
Expand Up @@ -2095,13 +2095,16 @@ public final class io/sentry/OutboxSender : io/sentry/IEnvelopeSender {

public final class io/sentry/PerformanceCollectionData {
public fun <init> (J)V
public fun getCpuUsagePercentage ()Ljava/lang/Double;
public fun getCpuUsagePercentage ()D
public fun getNanoTimestamp ()J
public fun getUsedHeapMemory ()Ljava/lang/Long;
public fun getUsedNativeMemory ()Ljava/lang/Long;
public fun setCpuUsagePercentage (Ljava/lang/Double;)V
public fun setUsedHeapMemory (Ljava/lang/Long;)V
public fun setUsedNativeMemory (Ljava/lang/Long;)V
public fun getUsedHeapMemory ()J
public fun getUsedNativeMemory ()J
public fun hasCpuUsagePercentage ()Z
public fun hasUsedHeapMemory ()Z
public fun hasUsedNativeMemory ()Z
public fun setCpuUsagePercentage (D)V
public fun setUsedHeapMemory (J)V
public fun setUsedNativeMemory (J)V
}

public final class io/sentry/ProfileChunk : io/sentry/JsonSerializable, io/sentry/JsonUnknown {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ public void run() {
// Add the enriched tempData to all transactions/profiles/objects that collect data.
// Then Check if that object timed out.
for (CompositeData data : compositeDataMap.values()) {
if (data.addDataAndCheckTimeout(tempData)) {
if (data.addDataAndCheckTimeout(tempData, tempData.getNanoTimestamp())) {
// timed out
if (data.transaction != null) {
timedOutTransactions.add(data.transaction);
Expand Down Expand Up @@ -224,16 +224,19 @@ private CompositeData(final @Nullable ITransaction transaction) {
* Adds the data to the internal list of PerformanceCollectionData. Then it checks if data
* collection timed out (for transactions only).
*
* @param nowNanos the timestamp of the current collection, passed in so a single clock reading
* is shared by every transaction in this collection round.
* @return true if data collection timed out (for transactions only).
*/
boolean addDataAndCheckTimeout(final @NotNull PerformanceCollectionData data) {
boolean addDataAndCheckTimeout(
final @NotNull PerformanceCollectionData data, final long nowNanos) {
// stop() hands dataList out while this timer thread may still be writing to it, so consumers
// synchronize on the list while iterating. We must hold the same monitor here.
synchronized (dataList) {
dataList.add(data);
}
return transaction != null
&& options.getDateProvider().now().nanoTimestamp()
&& nowNanos
> startTimestamp
+ TimeUnit.MILLISECONDS.toNanos(TRANSACTION_COLLECTION_TIMEOUT_MILLIS);
}
Expand Down
47 changes: 37 additions & 10 deletions sentry/src/main/java/io/sentry/PerformanceCollectionData.java
Original file line number Diff line number Diff line change
@@ -1,44 +1,71 @@
package io.sentry;

import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Nullable;

/**
* Holds a single performance measurement sample.
*
* <p>Measurements are stored as primitives with a separate presence flag, rather than as boxed
* nullable types, because an instance is created every 100ms for as long as a transaction or
* profile chunk is running.
*/
@ApiStatus.Internal
public final class PerformanceCollectionData {
private @Nullable Double cpuUsagePercentage = null;
private @Nullable Long usedHeapMemory = null;
private @Nullable Long usedNativeMemory = null;
private double cpuUsagePercentage;
private boolean hasCpuUsagePercentage;
private long usedHeapMemory;
private boolean hasUsedHeapMemory;
private long usedNativeMemory;
private boolean hasUsedNativeMemory;
private final long nanoTimestamp;

public PerformanceCollectionData(final long nanoTimestamp) {
this.nanoTimestamp = nanoTimestamp;
}

/** Set the cpu usage percentage. */
public void setCpuUsagePercentage(final @Nullable Double cpuUsagePercentage) {
public void setCpuUsagePercentage(final double cpuUsagePercentage) {
this.cpuUsagePercentage = cpuUsagePercentage;
this.hasCpuUsagePercentage = true;
}

public @Nullable Double getCpuUsagePercentage() {
/** Only meaningful when {@link #hasCpuUsagePercentage()} is true. */
public double getCpuUsagePercentage() {
return cpuUsagePercentage;
}

public void setUsedHeapMemory(final @Nullable Long usedHeapMemory) {
public boolean hasCpuUsagePercentage() {
return hasCpuUsagePercentage;
}

public void setUsedHeapMemory(final long usedHeapMemory) {
this.usedHeapMemory = usedHeapMemory;
this.hasUsedHeapMemory = true;
}

public @Nullable Long getUsedHeapMemory() {
/** Only meaningful when {@link #hasUsedHeapMemory()} is true. */
public long getUsedHeapMemory() {
return usedHeapMemory;
}

public void setUsedNativeMemory(final @Nullable Long usedNativeMemory) {
public boolean hasUsedHeapMemory() {
return hasUsedHeapMemory;
}

public void setUsedNativeMemory(final long usedNativeMemory) {
this.usedNativeMemory = usedNativeMemory;
this.hasUsedNativeMemory = true;
}

public @Nullable Long getUsedNativeMemory() {
/** Only meaningful when {@link #hasUsedNativeMemory()} is true. */
public long getUsedNativeMemory() {
return usedNativeMemory;
}

public boolean hasUsedNativeMemory() {
return hasUsedNativeMemory;
}

public long getNanoTimestamp() {
return nanoTimestamp;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -173,12 +173,12 @@ class DefaultCompositePerformanceCollectorTest {
assertNotNull(data1)
assertNotNull(data2)
assertNotNull(data3)
assertFalse(data1.mapNotNull { it.usedHeapMemory }.isEmpty())
assertFalse(data1.mapNotNull { it.cpuUsagePercentage }.isEmpty())
assertFalse(data2.mapNotNull { it.usedHeapMemory }.isEmpty())
assertFalse(data2.mapNotNull { it.cpuUsagePercentage }.isEmpty())
assertFalse(data3.mapNotNull { it.usedHeapMemory }.isEmpty())
assertFalse(data3.mapNotNull { it.cpuUsagePercentage }.isEmpty())
assertTrue(data1.any { it.hasUsedHeapMemory() })
assertTrue(data1.any { it.hasCpuUsagePercentage() })
assertTrue(data2.any { it.hasUsedHeapMemory() })
assertTrue(data2.any { it.hasCpuUsagePercentage() })
assertTrue(data3.any { it.hasUsedHeapMemory() })
assertTrue(data3.any { it.hasCpuUsagePercentage() })
}

@Test
Expand Down Expand Up @@ -266,8 +266,8 @@ class DefaultCompositePerformanceCollectorTest {
Thread.sleep(300)
val data1 = collector.stop(fixture.transaction1)
assertNotNull(data1)
val memoryData = data1.map { it.usedHeapMemory }
val cpuData = data1.map { it.cpuUsagePercentage }
val memoryData = data1.filter { it.hasUsedHeapMemory() }
val cpuData = data1.filter { it.hasCpuUsagePercentage() }

// The data returned by the collector is not empty
assertFalse(memoryData.isEmpty())
Expand Down
10 changes: 5 additions & 5 deletions sentry/src/test/java/io/sentry/JavaMemoryCollectorTest.kt
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
package io.sentry

import com.google.common.truth.Truth.assertThat
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertNull

class JavaMemoryCollectorTest {
private val fixture = Fixture()
Expand All @@ -17,8 +16,9 @@ class JavaMemoryCollectorTest {
val data = PerformanceCollectionData(10)
val usedMemory = fixture.runtime.totalMemory() - fixture.runtime.freeMemory()
fixture.collector.collect(data)
assertNull(data.usedNativeMemory)
assertEquals(usedMemory, data.usedHeapMemory)
assertEquals(10, data.nanoTimestamp)
assertThat(data.hasUsedNativeMemory()).isFalse()
assertThat(data.hasUsedHeapMemory()).isTrue()
assertThat(data.usedHeapMemory).isEqualTo(usedMemory)
assertThat(data.nanoTimestamp).isEqualTo(10)
}
}
Loading