Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Collect memory usage in transactions #2445

Merged
merged 5 commits into from
Jan 3, 2023
Merged
Show file tree
Hide file tree
Changes from 3 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
6 changes: 2 additions & 4 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,15 @@

## Unreleased

### Features

- Disable Android concurrent profiling ([#2434](https://github.com/getsentry/sentry-java/pull/2434))

### Fixes

- Use minSdk compatible `Objects` class ([#2436](https://github.com/getsentry/sentry-java/pull/2436))
- Prevent R8 from warning on missing classes, as we check for their presence at runtime ([#2439](https://github.com/getsentry/sentry-java/pull/2439))

### Features

- Collect memory usage in transactions ([#2445](https://github.com/getsentry/sentry-java/pull/2445))
- Disable Android concurrent profiling ([#2434](https://github.com/getsentry/sentry-java/pull/2434))
- Add logging for OpenTelemetry integration ([#2425](https://github.com/getsentry/sentry-java/pull/2425))
- Auto add `OpenTelemetryLinkErrorEventProcessor` for Spring Boot ([#2429](https://github.com/getsentry/sentry-java/pull/2429))

Expand Down
5 changes: 5 additions & 0 deletions sentry-android-core/api/sentry-android-core.api
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,11 @@ public final class io/sentry/android/core/AndroidLogger : io/sentry/ILogger {
public fun log (Lio/sentry/SentryLevel;Ljava/lang/Throwable;Ljava/lang/String;[Ljava/lang/Object;)V
}

public class io/sentry/android/core/AndroidMemoryCollector : io/sentry/IMemoryCollector {
public fun <init> ()V
public fun collect ()Lio/sentry/MemoryCollectionData;
}

public final class io/sentry/android/core/AnrIntegration : io/sentry/Integration, java/io/Closeable {
public fun <init> (Landroid/content/Context;)V
public fun close ()V
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package io.sentry.android.core;

import android.os.Debug;
import io.sentry.IMemoryCollector;
import io.sentry.MemoryCollectionData;
import org.jetbrains.annotations.ApiStatus;

@ApiStatus.Internal
public class AndroidMemoryCollector implements IMemoryCollector {
@Override
public MemoryCollectionData collect() {
long now = System.currentTimeMillis();
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is System.currentTimeMillis() good enough as timestamp? Or should I go with DateUtils.getCurrentDateTime().getTime()?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DateUtils is using Calendar under the hood which itself seems to be using System.currentTimeMillis(). Thus I'd stick to System.currentTimeMillis().

long usedMemory = Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
long usedNativeMemory = Debug.getNativeHeapSize() - Debug.getNativeHeapFreeSize();
return new MemoryCollectionData(now, usedMemory, usedNativeMemory);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,7 @@ static void initializeIntegrationsAndProcessors(
options.setGestureTargetLocators(gestureTargetLocators);
}
options.setMainThreadChecker(AndroidMainThreadChecker.getInstance());
options.setMemoryCollector(new AndroidMemoryCollector());
}

private static void installDefaultIntegrations(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package io.sentry.android.core

import android.os.Debug
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertNotEquals
import kotlin.test.assertNotNull

class AndroidMemoryCollectorTest {

private val fixture = Fixture()

private class Fixture {
val runtime: Runtime = Runtime.getRuntime()
val collector = AndroidMemoryCollector()
}

@Test
fun `when collect, both native and heap memory are collected`() {
val usedNativeMemory = Debug.getNativeHeapSize() - Debug.getNativeHeapFreeSize()
val usedMemory = fixture.runtime.totalMemory() - fixture.runtime.freeMemory()
val data = fixture.collector.collect()
assertNotNull(data)
assertNotEquals(-1, data.usedNativeMemory)
assertEquals(usedNativeMemory, data.usedNativeMemory)
assertEquals(usedMemory, data.usedHeapMemory)
assertNotEquals(0, data.timestamp)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -484,4 +484,11 @@ class AndroidOptionsInitializerTest {
assertTrue { fixture.sentryOptions.gestureTargetLocators[0] is AndroidViewGestureTargetLocator }
assertTrue { fixture.sentryOptions.gestureTargetLocators[1] is ComposeGestureTargetLocator }
}

@Test
fun `AndroidMemoryCollector is set to options`() {
fixture.initSut()

assertTrue { fixture.sentryOptions.memoryCollector is AndroidMemoryCollector }
}
}
30 changes: 30 additions & 0 deletions sentry/api/sentry.api
Original file line number Diff line number Diff line change
Expand Up @@ -441,6 +441,10 @@ public abstract interface class io/sentry/ILogger {
public abstract fun log (Lio/sentry/SentryLevel;Ljava/lang/Throwable;Ljava/lang/String;[Ljava/lang/Object;)V
}

public abstract interface class io/sentry/IMemoryCollector {
public abstract fun collect ()Lio/sentry/MemoryCollectionData;
}

public abstract interface class io/sentry/IScopeObserver {
public abstract fun addBreadcrumb (Lio/sentry/Breadcrumb;)V
public abstract fun removeExtra (Ljava/lang/String;)V
Expand Down Expand Up @@ -560,6 +564,11 @@ public final class io/sentry/IpAddressUtils {
public static fun isDefault (Ljava/lang/String;)Z
}

public final class io/sentry/JavaMemoryCollector : io/sentry/IMemoryCollector {
public fun <init> ()V
public fun collect ()Lio/sentry/MemoryCollectionData;
}

public abstract interface class io/sentry/JsonDeserializer {
public abstract fun deserialize (Lio/sentry/JsonObjectReader;Lio/sentry/ILogger;)Ljava/lang/Object;
}
Expand Down Expand Up @@ -681,6 +690,14 @@ public final class io/sentry/MeasurementUnit$Information : java/lang/Enum, io/se
public static fun values ()[Lio/sentry/MeasurementUnit$Information;
}

public final class io/sentry/MemoryCollectionData {
public fun <init> (JJ)V
public fun <init> (JJJ)V
public fun getTimestamp ()J
public fun getUsedHeapMemory ()J
public fun getUsedNativeMemory ()J
}

public final class io/sentry/NoOpEnvelopeReader : io/sentry/IEnvelopeReader {
public static fun getInstance ()Lio/sentry/NoOpEnvelopeReader;
public fun read (Ljava/io/InputStream;)Lio/sentry/SentryEnvelope;
Expand Down Expand Up @@ -738,6 +755,11 @@ public final class io/sentry/NoOpLogger : io/sentry/ILogger {
public fun log (Lio/sentry/SentryLevel;Ljava/lang/Throwable;Ljava/lang/String;[Ljava/lang/Object;)V
}

public final class io/sentry/NoOpMemoryCollector : io/sentry/IMemoryCollector {
public fun collect ()Lio/sentry/MemoryCollectionData;
public static fun getInstance ()Lio/sentry/NoOpMemoryCollector;
}

public final class io/sentry/NoOpSpan : io/sentry/ISpan {
public fun finish ()V
public fun finish (Lio/sentry/SpanStatus;)V
Expand Down Expand Up @@ -1411,6 +1433,7 @@ public class io/sentry/SentryOptions {
public fun getMaxRequestBodySize ()Lio/sentry/SentryOptions$RequestSize;
public fun getMaxSpans ()I
public fun getMaxTraceFileSize ()J
public fun getMemoryCollector ()Lio/sentry/IMemoryCollector;
public fun getModulesLoader ()Lio/sentry/internal/modules/IModulesLoader;
public fun getOutboxPath ()Ljava/lang/String;
public fun getProfilesSampleRate ()Ljava/lang/Double;
Expand Down Expand Up @@ -1498,6 +1521,7 @@ public class io/sentry/SentryOptions {
public fun setMaxRequestBodySize (Lio/sentry/SentryOptions$RequestSize;)V
public fun setMaxSpans (I)V
public fun setMaxTraceFileSize (J)V
public fun setMemoryCollector (Lio/sentry/IMemoryCollector;)V
public fun setModulesLoader (Lio/sentry/internal/modules/IModulesLoader;)V
public fun setPrintUncaughtStackTrace (Z)V
public fun setProfilesSampleRate (Ljava/lang/Double;)V
Expand Down Expand Up @@ -1944,6 +1968,12 @@ public final class io/sentry/TransactionOptions {
public fun setWaitForChildren (Z)V
}

public final class io/sentry/TransactionPerformanceCollector {
public fun <init> (Lio/sentry/SentryOptions;)V
public fun start (Lio/sentry/ITransaction;)V
public fun stop (Lio/sentry/ITransaction;)Ljava/util/List;
}

public final class io/sentry/TypeCheckHint {
public static final field ANDROID_ACTIVITY Ljava/lang/String;
public static final field ANDROID_CONFIGURATION Ljava/lang/String;
Expand Down
5 changes: 4 additions & 1 deletion sentry/src/main/java/io/sentry/Hub.java
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ public final class Hub implements IHub {
private final @NotNull TracesSampler tracesSampler;
private final @NotNull Map<Throwable, Pair<WeakReference<ISpan>, String>> throwableToSpan =
Collections.synchronizedMap(new WeakHashMap<>());
private final @NotNull TransactionPerformanceCollector transactionPerformanceCollector;

public Hub(final @NotNull SentryOptions options) {
this(options, createRootStackItem(options));
Expand All @@ -44,6 +45,7 @@ private Hub(final @NotNull SentryOptions options, final @NotNull Stack stack) {
this.tracesSampler = new TracesSampler(options);
this.stack = stack;
this.lastEventId = SentryId.EMPTY_ID;
this.transactionPerformanceCollector = new TransactionPerformanceCollector(options);

// Integrations will use this Hub instance once registered.
// Make sure Hub ready to be used then.
Expand Down Expand Up @@ -730,7 +732,8 @@ public void flush(long timeoutMillis) {
waitForChildren,
idleTimeout,
trimEnd,
transactionFinishedCallback);
transactionFinishedCallback,
transactionPerformanceCollector);

// The listener is called only if the transaction exists, as the transaction is needed to
// stop it
Expand Down
12 changes: 12 additions & 0 deletions sentry/src/main/java/io/sentry/IMemoryCollector.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package io.sentry;

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

/** Used for collecting data about memory load when a transaction is active. */
@ApiStatus.Internal
public interface IMemoryCollector {
/** Used for collecting data about memory load when a transaction is active. */
@Nullable
MemoryCollectionData collect();
}
18 changes: 18 additions & 0 deletions sentry/src/main/java/io/sentry/JavaMemoryCollector.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package io.sentry;

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

@ApiStatus.Internal
public final class JavaMemoryCollector implements IMemoryCollector {

private final @NotNull Runtime runtime = Runtime.getRuntime();

@Override
public @Nullable MemoryCollectionData collect() {
final long now = System.currentTimeMillis();
final long usedMemory = runtime.totalMemory() - runtime.freeMemory();
return new MemoryCollectionData(now, usedMemory);
}
}
33 changes: 33 additions & 0 deletions sentry/src/main/java/io/sentry/MemoryCollectionData.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package io.sentry;

import org.jetbrains.annotations.ApiStatus;

@ApiStatus.Internal
public final class MemoryCollectionData {
final long timestamp;
final long usedHeapMemory;
final long usedNativeMemory;

public MemoryCollectionData(
final long timestamp, final long usedHeapMemory, final long usedNativeMemory) {
this.timestamp = timestamp;
this.usedHeapMemory = usedHeapMemory;
this.usedNativeMemory = usedNativeMemory;
}

public MemoryCollectionData(final long timestamp, final long usedHeapMemory) {
this(timestamp, usedHeapMemory, -1);
}

public long getTimestamp() {
return timestamp;
}

public long getUsedHeapMemory() {
return usedHeapMemory;
}

public long getUsedNativeMemory() {
return usedNativeMemory;
}
}
21 changes: 21 additions & 0 deletions sentry/src/main/java/io/sentry/NoOpMemoryCollector.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package io.sentry;

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

@ApiStatus.Internal
public final class NoOpMemoryCollector implements IMemoryCollector {

private static final NoOpMemoryCollector instance = new NoOpMemoryCollector();

public static NoOpMemoryCollector getInstance() {
return instance;
}

private NoOpMemoryCollector() {}

@Override
public @Nullable MemoryCollectionData collect() {
return null;
}
}
4 changes: 4 additions & 0 deletions sentry/src/main/java/io/sentry/Sentry.java
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,10 @@ private static boolean initConfigurations(final @NotNull SentryOptions options)
options.setMainThreadChecker(MainThreadChecker.getInstance());
}

if (options.getMemoryCollector() instanceof NoOpMemoryCollector) {
options.setMemoryCollector(new JavaMemoryCollector());
}

return true;
}

Expand Down
23 changes: 23 additions & 0 deletions sentry/src/main/java/io/sentry/SentryOptions.java
Original file line number Diff line number Diff line change
Expand Up @@ -385,6 +385,8 @@ public class SentryOptions {

private @NotNull IMainThreadChecker mainThreadChecker = NoOpMainThreadChecker.getInstance();

private @NotNull IMemoryCollector memoryCollector = NoOpMemoryCollector.getInstance();

/**
* Adds an event processor
*
Expand Down Expand Up @@ -1876,6 +1878,27 @@ public void setMainThreadChecker(final @NotNull IMainThreadChecker mainThreadChe
this.mainThreadChecker = mainThreadChecker;
}

/**
* Gets the memory collector used to collect memory usage during while transaction runs.
*
* @return the memory collector.
*/
@ApiStatus.Internal
public @NotNull IMemoryCollector getMemoryCollector() {
return memoryCollector;
}

/**
* Sets the memory collector to collect memory usage during while transaction runs.
*
* @param memoryCollector - the memory collector. If null, a no op collector will be set.
*/
@ApiStatus.Internal
public void setMemoryCollector(final @Nullable IMemoryCollector memoryCollector) {
this.memoryCollector =
memoryCollector != null ? memoryCollector : NoOpMemoryCollector.getInstance();
}

/** The BeforeSend callback */
public interface BeforeSendCallback {

Expand Down
Loading