Skip to content
Merged
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
### Performance

- Reduce allocations while collecting cpu usage during transactions by reading the process cpu time via `Process.getElapsedCpuTime()` instead of parsing `/proc/self/stat` (33.6kB to 16 bytes per sample on a Pixel 3) ([#5926](https://github.com/getsentry/sentry-java/pull/5926))
- Store performance measurements as primitives, removing a boxed allocation per measurement per performance sample ([#5935](https://github.com/getsentry/sentry-java/pull/5935))

### Dependencies

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
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