Skip to content

Commit b049670

Browse files
authored
Send transaction memory stats in profile payload (#2447)
* Added list of MemoryCollectionData to Android profiles payload as measurements * AndroidTransactionProfiler now takes a PerformanceCollectionData onTransactionFinish * Updated profile measurement keys and units
1 parent 690f9dd commit b049670

File tree

16 files changed

+203
-44
lines changed

16 files changed

+203
-44
lines changed

CHANGELOG.md

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

55
### Features
66

7+
- Send transaction memory stats in profile payload ([#2447](https://github.com/getsentry/sentry-java/pull/2447))
78
- Add cpu usage collection ([#2462](https://github.com/getsentry/sentry-java/pull/2462))
89
- Improve ANR implementation: ([#2475](https://github.com/getsentry/sentry-java/pull/2475))
910
- Add `abnormal_mechanism` to sessions for ANR rate calculation

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

Lines changed: 19 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -71,16 +71,16 @@ public void collect(
7171
if (buildInfoProvider.getSdkInfoVersion() < Build.VERSION_CODES.LOLLIPOP || !isEnabled) {
7272
return;
7373
}
74-
long nowNanos = SystemClock.elapsedRealtimeNanos();
75-
long realTimeNanosDiff = nowNanos - lastRealtimeNanos;
74+
final long nowNanos = SystemClock.elapsedRealtimeNanos();
75+
final long realTimeNanosDiff = nowNanos - lastRealtimeNanos;
7676
lastRealtimeNanos = nowNanos;
77-
long cpuNanos = readTotalCpuNanos();
78-
long cpuNanosDiff = cpuNanos - lastCpuNanos;
77+
final long cpuNanos = readTotalCpuNanos();
78+
final long cpuNanosDiff = cpuNanos - lastCpuNanos;
7979
lastCpuNanos = cpuNanos;
8080
// Later we need to divide the percentage by the number of cores, otherwise we could
8181
// get a percentage value higher than 1. We also want to send the percentage as a
8282
// number from 0 to 100, so we are going to multiply it by 100
83-
double cpuUsagePercentage = cpuNanosDiff / (double) realTimeNanosDiff;
83+
final double cpuUsagePercentage = cpuNanosDiff / (double) realTimeNanosDiff;
8484

8585
CpuCollectionData cpuData =
8686
new CpuCollectionData(
@@ -106,15 +106,20 @@ private long readTotalCpuNanos() {
106106
if (stat != null) {
107107
stat = stat.trim();
108108
String[] stats = stat.split("[\n\t\r ]");
109-
// Amount of clock ticks this process has been scheduled in user mode
110-
long uTime = Long.parseLong(stats[13]);
111-
// Amount of clock ticks this process has been scheduled in kernel mode
112-
long sTime = Long.parseLong(stats[14]);
113-
// Amount of clock ticks this process' waited-for children has been scheduled in user mode
114-
long cuTime = Long.parseLong(stats[15]);
115-
// Amount of clock ticks this process' waited-for children has been scheduled in kernel mode
116-
long csTime = Long.parseLong(stats[16]);
117-
return (long) ((uTime + sTime + cuTime + csTime) * nanosecondsPerClockTick);
109+
try {
110+
// Amount of clock ticks this process has been scheduled in user mode
111+
long uTime = Long.parseLong(stats[13]);
112+
// Amount of clock ticks this process has been scheduled in kernel mode
113+
long sTime = Long.parseLong(stats[14]);
114+
// Amount of clock ticks this process' waited-for children has been scheduled in user mode
115+
long cuTime = Long.parseLong(stats[15]);
116+
// Amount of clock ticks this process' waited-for children has been scheduled in kernel mode
117+
long csTime = Long.parseLong(stats[16]);
118+
return (long) ((uTime + sTime + cuTime + csTime) * nanosecondsPerClockTick);
119+
} catch (NumberFormatException e) {
120+
logger.log(SentryLevel.ERROR, "Error parsing /proc/self/stat file.", e);
121+
return 0;
122+
}
118123
}
119124
return 0;
120125
}

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

Lines changed: 47 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
import io.sentry.IHub;
1616
import io.sentry.ITransaction;
1717
import io.sentry.ITransactionProfiler;
18+
import io.sentry.MemoryCollectionData;
19+
import io.sentry.PerformanceCollectionData;
1820
import io.sentry.ProfilingTraceData;
1921
import io.sentry.ProfilingTransactionData;
2022
import io.sentry.SentryLevel;
@@ -233,7 +235,7 @@ public void onFrameMetricCollected(
233235
options
234236
.getExecutorService()
235237
.schedule(
236-
() -> timedOutProfilingData = onTransactionFinish(transaction, true),
238+
() -> timedOutProfilingData = onTransactionFinish(transaction, true, null),
237239
PROFILING_TIMEOUT_MILLIS);
238240

239241
transactionStartNanos = SystemClock.elapsedRealtimeNanos();
@@ -247,11 +249,12 @@ public void onFrameMetricCollected(
247249

248250
@Override
249251
public @Nullable synchronized ProfilingTraceData onTransactionFinish(
250-
final @NotNull ITransaction transaction) {
252+
final @NotNull ITransaction transaction,
253+
final @Nullable PerformanceCollectionData performanceCollectionData) {
251254
try {
252255
return options
253256
.getExecutorService()
254-
.submit(() -> onTransactionFinish(transaction, false))
257+
.submit(() -> onTransactionFinish(transaction, false, performanceCollectionData))
255258
.get();
256259
} catch (ExecutionException e) {
257260
options.getLogger().log(SentryLevel.ERROR, "Error finishing profiling: ", e);
@@ -263,7 +266,9 @@ public void onFrameMetricCollected(
263266

264267
@SuppressLint("NewApi")
265268
private @Nullable ProfilingTraceData onTransactionFinish(
266-
final @NotNull ITransaction transaction, final boolean isTimeout) {
269+
final @NotNull ITransaction transaction,
270+
final boolean isTimeout,
271+
final @Nullable PerformanceCollectionData performanceCollectionData) {
267272

268273
// onTransactionStart() is only available since Lollipop
269274
// and SystemClock.elapsedRealtimeNanos() since Jelly Bean
@@ -383,6 +388,7 @@ public void onFrameMetricCollected(
383388
ProfileMeasurement.ID_SCREEN_FRAME_RATES,
384389
new ProfileMeasurement(ProfileMeasurement.UNIT_HZ, screenFrameRateMeasurements));
385390
}
391+
putPerformanceCollectionDataInMeasurements(performanceCollectionData);
386392

387393
// cpu max frequencies are read with a lambda because reading files is involved, so it will be
388394
// done in the background when the trace file is read
@@ -408,6 +414,43 @@ public void onFrameMetricCollected(
408414
measurementsMap);
409415
}
410416

417+
private void putPerformanceCollectionDataInMeasurements(
418+
final @Nullable PerformanceCollectionData performanceCollectionData) {
419+
if (performanceCollectionData != null) {
420+
List<MemoryCollectionData> memoryCollectionData = performanceCollectionData.getMemoryData();
421+
final @NotNull ArrayDeque<ProfileMeasurementValue> memoryUsageMeasurements =
422+
new ArrayDeque<>();
423+
final @NotNull ArrayDeque<ProfileMeasurementValue> nativeMemoryUsageMeasurements =
424+
new ArrayDeque<>();
425+
for (MemoryCollectionData memoryData : memoryCollectionData) {
426+
if (memoryData.getUsedHeapMemory() > -1) {
427+
memoryUsageMeasurements.add(
428+
new ProfileMeasurementValue(
429+
TimeUnit.MILLISECONDS.toNanos(memoryData.getTimestampMillis())
430+
- transactionStartNanos,
431+
memoryData.getUsedHeapMemory()));
432+
}
433+
if (memoryData.getUsedNativeMemory() > -1) {
434+
nativeMemoryUsageMeasurements.add(
435+
new ProfileMeasurementValue(
436+
TimeUnit.MILLISECONDS.toNanos(memoryData.getTimestampMillis())
437+
- transactionStartNanos,
438+
memoryData.getUsedNativeMemory()));
439+
}
440+
}
441+
if (!memoryUsageMeasurements.isEmpty()) {
442+
measurementsMap.put(
443+
ProfileMeasurement.ID_MEMORY_FOOTPRINT,
444+
new ProfileMeasurement(ProfileMeasurement.UNIT_BYTES, memoryUsageMeasurements));
445+
}
446+
if (!nativeMemoryUsageMeasurements.isEmpty()) {
447+
measurementsMap.put(
448+
ProfileMeasurement.ID_MEMORY_NATIVE_FOOTPRINT,
449+
new ProfileMeasurement(ProfileMeasurement.UNIT_BYTES, nativeMemoryUsageMeasurements));
450+
}
451+
}
452+
}
453+
411454
/**
412455
* Get MemoryInfo object representing the memory state of the application.
413456
*

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

Lines changed: 48 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,14 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
77
import io.sentry.IHub
88
import io.sentry.ILogger
99
import io.sentry.ISentryExecutorService
10+
import io.sentry.MemoryCollectionData
11+
import io.sentry.PerformanceCollectionData
1012
import io.sentry.ProfilingTraceData
1113
import io.sentry.SentryLevel
1214
import io.sentry.SentryTracer
1315
import io.sentry.TransactionContext
1416
import io.sentry.android.core.internal.util.SentryFrameMetricsCollector
17+
import io.sentry.profilemeasurements.ProfileMeasurement
1518
import io.sentry.test.getCtor
1619
import org.junit.runner.RunWith
1720
import org.mockito.kotlin.any
@@ -28,8 +31,10 @@ import java.util.concurrent.FutureTask
2831
import kotlin.test.AfterTest
2932
import kotlin.test.BeforeTest
3033
import kotlin.test.Test
34+
import kotlin.test.assertContentEquals
3135
import kotlin.test.assertEquals
3236
import kotlin.test.assertFailsWith
37+
import kotlin.test.assertFalse
3338
import kotlin.test.assertNotNull
3439
import kotlin.test.assertNull
3540

@@ -145,7 +150,7 @@ class AndroidTransactionProfilerTest {
145150
fun `profiler profiles current transaction`() {
146151
val profiler = fixture.getSut(context)
147152
profiler.onTransactionStart(fixture.transaction1)
148-
val profilingTraceData = profiler.onTransactionFinish(fixture.transaction1)
153+
val profilingTraceData = profiler.onTransactionFinish(fixture.transaction1, null)
149154

150155
assertNotNull(profilingTraceData)
151156
assertEquals(profilingTraceData.transactionId, fixture.transaction1.eventId.toString())
@@ -158,7 +163,7 @@ class AndroidTransactionProfilerTest {
158163
}
159164
val profiler = fixture.getSut(context, buildInfo)
160165
profiler.onTransactionStart(fixture.transaction1)
161-
val profilingTraceData = profiler.onTransactionFinish(fixture.transaction1)
166+
val profilingTraceData = profiler.onTransactionFinish(fixture.transaction1, null)
162167
assertNull(profilingTraceData)
163168
}
164169

@@ -169,7 +174,7 @@ class AndroidTransactionProfilerTest {
169174
}
170175
val profiler = fixture.getSut(context)
171176
profiler.onTransactionStart(fixture.transaction1)
172-
val profilingTraceData = profiler.onTransactionFinish(fixture.transaction1)
177+
val profilingTraceData = profiler.onTransactionFinish(fixture.transaction1, null)
173178
assertNull(profilingTraceData)
174179
}
175180

@@ -242,7 +247,7 @@ class AndroidTransactionProfilerTest {
242247
}
243248
val profiler = fixture.getSut(context)
244249
profiler.onTransactionStart(fixture.transaction1)
245-
val profilingTraceData = profiler.onTransactionFinish(fixture.transaction1)
250+
val profilingTraceData = profiler.onTransactionFinish(fixture.transaction1, null)
246251
assertNull(profilingTraceData)
247252
}
248253

@@ -253,7 +258,7 @@ class AndroidTransactionProfilerTest {
253258
}
254259
val profiler = fixture.getSut(context)
255260
profiler.onTransactionStart(fixture.transaction1)
256-
val profilingTraceData = profiler.onTransactionFinish(fixture.transaction1)
261+
val profilingTraceData = profiler.onTransactionFinish(fixture.transaction1, null)
257262
assertNull(profilingTraceData)
258263
}
259264

@@ -264,7 +269,7 @@ class AndroidTransactionProfilerTest {
264269
}
265270
val profiler = fixture.getSut(context)
266271
profiler.onTransactionStart(fixture.transaction1)
267-
val profilingTraceData = profiler.onTransactionFinish(fixture.transaction1)
272+
val profilingTraceData = profiler.onTransactionFinish(fixture.transaction1, null)
268273
assertNull(profilingTraceData)
269274
}
270275

@@ -275,7 +280,7 @@ class AndroidTransactionProfilerTest {
275280
}
276281
val profiler = fixture.getSut(context)
277282
profiler.onTransactionStart(fixture.transaction1)
278-
val traceData = profiler.onTransactionFinish(fixture.transaction1)
283+
val traceData = profiler.onTransactionFinish(fixture.transaction1, null)
279284
assertNotNull(traceData)
280285
}
281286

@@ -287,15 +292,15 @@ class AndroidTransactionProfilerTest {
287292
whenever(mockExecutorService.submit(any<Callable<*>>())).thenReturn(mock())
288293
profiler.onTransactionStart(fixture.transaction1)
289294
verify(mockExecutorService).submit(any<Runnable>())
290-
val profilingTraceData: ProfilingTraceData? = profiler.onTransactionFinish(fixture.transaction1)
295+
val profilingTraceData: ProfilingTraceData? = profiler.onTransactionFinish(fixture.transaction1, null)
291296
assertNull(profilingTraceData)
292297
verify(mockExecutorService).submit(any<Callable<*>>())
293298
}
294299

295300
@Test
296301
fun `onTransactionFinish works only if previously started`() {
297302
val profiler = fixture.getSut(context)
298-
val profilingTraceData = profiler.onTransactionFinish(fixture.transaction1)
303+
val profilingTraceData = profiler.onTransactionFinish(fixture.transaction1, null)
299304
assertNull(profilingTraceData)
300305
}
301306

@@ -310,7 +315,7 @@ class AndroidTransactionProfilerTest {
310315
fixture.lastScheduledRunnable?.run()
311316

312317
// First transaction finishes: timed out data is returned
313-
val profilingTraceData = profiler.onTransactionFinish(fixture.transaction1)
318+
val profilingTraceData = profiler.onTransactionFinish(fixture.transaction1, null)
314319
assertEquals(profilingTraceData!!.transactionId, fixture.transaction1.eventId.toString())
315320
assertEquals(ProfilingTraceData.TRUNCATION_REASON_TIMEOUT, profilingTraceData.truncationReason)
316321
}
@@ -321,10 +326,10 @@ class AndroidTransactionProfilerTest {
321326
profiler.onTransactionStart(fixture.transaction1)
322327
profiler.onTransactionStart(fixture.transaction2)
323328

324-
var profilingTraceData = profiler.onTransactionFinish(fixture.transaction2)
329+
var profilingTraceData = profiler.onTransactionFinish(fixture.transaction2, null)
325330
assertNull(profilingTraceData)
326331

327-
profilingTraceData = profiler.onTransactionFinish(fixture.transaction1)
332+
profilingTraceData = profiler.onTransactionFinish(fixture.transaction1, null)
328333
assertNotNull(profilingTraceData)
329334
assertEquals(profilingTraceData.transactionId, fixture.transaction1.eventId.toString())
330335
}
@@ -333,7 +338,7 @@ class AndroidTransactionProfilerTest {
333338
fun `profiling trace data contains release field`() {
334339
val profiler = fixture.getSut(context)
335340
profiler.onTransactionStart(fixture.transaction1)
336-
val profilingTraceData = profiler.onTransactionFinish(fixture.transaction1)
341+
val profilingTraceData = profiler.onTransactionFinish(fixture.transaction1, null)
337342
assertNotNull(profilingTraceData!!.release)
338343
assertEquals(fixture.options.release, profilingTraceData.release)
339344
}
@@ -353,7 +358,36 @@ class AndroidTransactionProfilerTest {
353358
whenever(fixture.frameMetricsCollector.startCollection(any())).thenReturn(frameMetricsCollectorId)
354359
profiler.onTransactionStart(fixture.transaction1)
355360
profiler.onTransactionStart(fixture.transaction2)
356-
profiler.onTransactionFinish(fixture.transaction1)
361+
profiler.onTransactionFinish(fixture.transaction1, null)
357362
verify(fixture.frameMetricsCollector).stopCollection(frameMetricsCollectorId)
358363
}
364+
365+
@Test
366+
fun `profiler does not includes memory measurements when null is passed on transaction finish`() {
367+
val profiler = fixture.getSut(context)
368+
profiler.onTransactionStart(fixture.transaction1)
369+
val data = profiler.onTransactionFinish(fixture.transaction1, null)
370+
assertFalse(data!!.measurementsMap.containsKey(ProfileMeasurement.ID_MEMORY_FOOTPRINT))
371+
assertFalse(data.measurementsMap.containsKey(ProfileMeasurement.ID_MEMORY_NATIVE_FOOTPRINT))
372+
}
373+
374+
@Test
375+
fun `profiler includes memory measurements when passed on transaction finish`() {
376+
val profiler = fixture.getSut(context)
377+
val memoryCollectionData = PerformanceCollectionData()
378+
memoryCollectionData.addMemoryData(MemoryCollectionData(1, 2, 3))
379+
memoryCollectionData.commitData()
380+
memoryCollectionData.addMemoryData(MemoryCollectionData(2, 3, 4))
381+
memoryCollectionData.commitData()
382+
profiler.onTransactionStart(fixture.transaction1)
383+
val data = profiler.onTransactionFinish(fixture.transaction1, memoryCollectionData)
384+
assertContentEquals(
385+
listOf(2.0, 3.0),
386+
data!!.measurementsMap[ProfileMeasurement.ID_MEMORY_FOOTPRINT]!!.values.map { it.value }
387+
)
388+
assertContentEquals(
389+
listOf(3.0, 4.0),
390+
data.measurementsMap[ProfileMeasurement.ID_MEMORY_NATIVE_FOOTPRINT]!!.values.map { it.value }
391+
)
392+
}
359393
}

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

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package io.sentry.android.core
33
import io.sentry.ITransaction
44
import io.sentry.ITransactionProfiler
55
import io.sentry.NoOpTransactionProfiler
6+
import io.sentry.PerformanceCollectionData
67
import io.sentry.ProfilingTraceData
78
import io.sentry.protocol.DebugImage
89
import kotlin.test.Test
@@ -110,6 +111,9 @@ class SentryAndroidOptionsTest {
110111

111112
private class CustomTransactionProfiler : ITransactionProfiler {
112113
override fun onTransactionStart(transaction: ITransaction) {}
113-
override fun onTransactionFinish(transaction: ITransaction): ProfilingTraceData? = null
114+
override fun onTransactionFinish(
115+
transaction: ITransaction,
116+
memoryCollectionData: PerformanceCollectionData?
117+
): ProfilingTraceData? = null
114118
}
115119
}

sentry/api/sentry.api

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -565,7 +565,7 @@ public abstract interface class io/sentry/ITransaction : io/sentry/ISpan {
565565
}
566566

567567
public abstract interface class io/sentry/ITransactionProfiler {
568-
public abstract fun onTransactionFinish (Lio/sentry/ITransaction;)Lio/sentry/ProfilingTraceData;
568+
public abstract fun onTransactionFinish (Lio/sentry/ITransaction;Lio/sentry/PerformanceCollectionData;)Lio/sentry/ProfilingTraceData;
569569
public abstract fun onTransactionStart (Lio/sentry/ITransaction;)V
570570
}
571571

@@ -855,7 +855,7 @@ public final class io/sentry/NoOpTransaction : io/sentry/ITransaction {
855855

856856
public final class io/sentry/NoOpTransactionProfiler : io/sentry/ITransactionProfiler {
857857
public static fun getInstance ()Lio/sentry/NoOpTransactionProfiler;
858-
public fun onTransactionFinish (Lio/sentry/ITransaction;)Lio/sentry/ProfilingTraceData;
858+
public fun onTransactionFinish (Lio/sentry/ITransaction;Lio/sentry/PerformanceCollectionData;)Lio/sentry/ProfilingTraceData;
859859
public fun onTransactionStart (Lio/sentry/ITransaction;)V
860860
}
861861

@@ -2430,9 +2430,12 @@ public final class io/sentry/internal/modules/ResourcesModulesLoader : io/sentry
24302430

24312431
public final class io/sentry/profilemeasurements/ProfileMeasurement : io/sentry/JsonSerializable, io/sentry/JsonUnknown {
24322432
public static final field ID_FROZEN_FRAME_RENDERS Ljava/lang/String;
2433+
public static final field ID_MEMORY_FOOTPRINT Ljava/lang/String;
2434+
public static final field ID_MEMORY_NATIVE_FOOTPRINT Ljava/lang/String;
24332435
public static final field ID_SCREEN_FRAME_RATES Ljava/lang/String;
24342436
public static final field ID_SLOW_FRAME_RENDERS Ljava/lang/String;
24352437
public static final field ID_UNKNOWN Ljava/lang/String;
2438+
public static final field UNIT_BYTES Ljava/lang/String;
24362439
public static final field UNIT_HZ Ljava/lang/String;
24372440
public static final field UNIT_NANOSECONDS Ljava/lang/String;
24382441
public static final field UNIT_UNKNOWN Ljava/lang/String;
@@ -2466,6 +2469,7 @@ public final class io/sentry/profilemeasurements/ProfileMeasurementValue : io/se
24662469
public fun <init> (Ljava/lang/Long;Ljava/lang/Number;)V
24672470
public fun equals (Ljava/lang/Object;)Z
24682471
public fun getUnknown ()Ljava/util/Map;
2472+
public fun getValue ()D
24692473
public fun hashCode ()I
24702474
public fun serialize (Lio/sentry/JsonObjectWriter;Lio/sentry/ILogger;)V
24712475
public fun setUnknown (Ljava/util/Map;)V

sentry/src/main/java/io/sentry/ITransactionProfiler.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,5 +10,6 @@ public interface ITransactionProfiler {
1010
void onTransactionStart(@NotNull ITransaction transaction);
1111

1212
@Nullable
13-
ProfilingTraceData onTransactionFinish(@NotNull ITransaction transaction);
13+
ProfilingTraceData onTransactionFinish(
14+
@NotNull ITransaction transaction, @Nullable PerformanceCollectionData memoryCollectionData);
1415
}

0 commit comments

Comments
 (0)