Skip to content
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
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))

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