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 @@ -6,6 +6,7 @@

- Use thread context classloader when available ([#4320](https://github.com/getsentry/sentry-java/pull/4320))
- This ensures correct resource loading in environments like Spring Boot where the thread context classloader is used for resource loading.
- Improve low memory breadcrumb capturing ([#4325](https://github.com/getsentry/sentry-java/pull/4325))
- Fix do not initialize SDK for Jetpack Compose Preview builds ([#4324](https://github.com/getsentry/sentry-java/pull/4324))

## 8.7.0
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
import io.sentry.Integration;
import io.sentry.SentryLevel;
import io.sentry.SentryOptions;
import io.sentry.android.core.internal.util.AndroidCurrentDateProvider;
import io.sentry.android.core.internal.util.Debouncer;
import io.sentry.android.core.internal.util.DeviceOrientations;
import io.sentry.protocol.Device;
import io.sentry.util.Objects;
Expand All @@ -24,10 +26,17 @@
public final class AppComponentsBreadcrumbsIntegration
implements Integration, Closeable, ComponentCallbacks2 {

private static final long DEBOUNCE_WAIT_TIME_MS = 60 * 1000;
// pre-allocate hint to avoid creating it every time for the low memory case
private static final @NotNull Hint EMPTY_HINT = new Hint();

private final @NotNull Context context;
private @Nullable IScopes scopes;
private @Nullable SentryAndroidOptions options;

private final @NotNull Debouncer trimMemoryDebouncer =
new Debouncer(AndroidCurrentDateProvider.getInstance(), DEBOUNCE_WAIT_TIME_MS, 0);

public AppComponentsBreadcrumbsIntegration(final @NotNull Context context) {
this.context =
Objects.requireNonNull(ContextUtils.getApplicationContext(context), "Context is required");
Expand Down Expand Up @@ -91,42 +100,43 @@ public void onConfigurationChanged(@NotNull Configuration newConfig) {

@Override
public void onLowMemory() {
final long now = System.currentTimeMillis();
executeInBackground(() -> captureLowMemoryBreadcrumb(now, null));
// we do this in onTrimMemory below already, this is legacy API (14 or below)
}

@Override
public void onTrimMemory(final int level) {
if (level < TRIM_MEMORY_BACKGROUND) {
// only add breadcrumb if TRIM_MEMORY_BACKGROUND, TRIM_MEMORY_MODERATE or
// TRIM_MEMORY_COMPLETE.
// Release as much memory as the process can.

// TRIM_MEMORY_UI_HIDDEN, TRIM_MEMORY_RUNNING_MODERATE, TRIM_MEMORY_RUNNING_LOW and
// TRIM_MEMORY_RUNNING_CRITICAL.
// Release any memory that your app doesn't need to run.
// So they are still not so critical at the point of killing the process.
// https://developer.android.com/topic/performance/memory
return;
}

if (trimMemoryDebouncer.checkForDebounce()) {
// if we received trim_memory within 1 minute time, ignore this call
return;
}

final long now = System.currentTimeMillis();
executeInBackground(() -> captureLowMemoryBreadcrumb(now, level));
}

private void captureLowMemoryBreadcrumb(final long timeMs, final @Nullable Integer level) {
private void captureLowMemoryBreadcrumb(final long timeMs, final int level) {
if (scopes != null) {
final Breadcrumb breadcrumb = new Breadcrumb(timeMs);
if (level != null) {
// only add breadcrumb if TRIM_MEMORY_BACKGROUND, TRIM_MEMORY_MODERATE or
// TRIM_MEMORY_COMPLETE.
// Release as much memory as the process can.

// TRIM_MEMORY_UI_HIDDEN, TRIM_MEMORY_RUNNING_MODERATE, TRIM_MEMORY_RUNNING_LOW and
// TRIM_MEMORY_RUNNING_CRITICAL.
// Release any memory that your app doesn't need to run.
// So they are still not so critical at the point of killing the process.
// https://developer.android.com/topic/performance/memory

if (level < TRIM_MEMORY_BACKGROUND) {
return;
}
breadcrumb.setData("level", level);
}

breadcrumb.setType("system");
breadcrumb.setCategory("device.event");
breadcrumb.setMessage("Low memory");
breadcrumb.setData("action", "LOW_MEMORY");
breadcrumb.setData("level", level);
breadcrumb.setLevel(SentryLevel.WARNING);
scopes.addBreadcrumb(breadcrumb);
scopes.addBreadcrumb(breadcrumb, EMPTY_HINT);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import org.mockito.kotlin.check
import org.mockito.kotlin.mock
import org.mockito.kotlin.never
import org.mockito.kotlin.verify
import org.mockito.kotlin.verifyNoMoreInteractions
import org.mockito.kotlin.whenever
import java.lang.NullPointerException
import kotlin.test.Test
Expand Down Expand Up @@ -95,24 +96,6 @@ class AppComponentsBreadcrumbsIntegrationTest {
sut.close()
}

@Test
fun `When low memory event, a breadcrumb with type, category and level should be set`() {
val sut = fixture.getSut()
val options = SentryAndroidOptions().apply {
executorService = ImmediateExecutorService()
}
val scopes = mock<IScopes>()
sut.register(scopes, options)
sut.onLowMemory()
verify(scopes).addBreadcrumb(
check<Breadcrumb> {
assertEquals("device.event", it.category)
assertEquals("system", it.type)
assertEquals(SentryLevel.WARNING, it.level)
}
)
}

@Test
fun `When trim memory event with level, a breadcrumb with type, category and level should be set`() {
val sut = fixture.getSut()
Expand All @@ -127,7 +110,8 @@ class AppComponentsBreadcrumbsIntegrationTest {
assertEquals("device.event", it.category)
assertEquals("system", it.type)
assertEquals(SentryLevel.WARNING, it.level)
}
},
anyOrNull()
)
}

Expand Down Expand Up @@ -162,4 +146,26 @@ class AppComponentsBreadcrumbsIntegrationTest {
anyOrNull()
)
}

@Test
fun `low memory changes are debounced`() {
val sut = fixture.getSut()

val scopes = mock<IScopes>()
val options = SentryAndroidOptions().apply {
executorService = ImmediateExecutorService()
}
sut.register(scopes, options)
sut.onTrimMemory(ComponentCallbacks2.TRIM_MEMORY_BACKGROUND)
sut.onTrimMemory(ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL)

// should only add the first crumb
verify(scopes).addBreadcrumb(
check<Breadcrumb> {
assertEquals(it.data["level"], 40)
},
anyOrNull()
)
verifyNoMoreInteractions(scopes)
}
}
Loading