Skip to content

Commit df386ef

Browse files
authored
fix(breadcrumbs): Improve low memory breadcrumb capturing (#4325)
* Improve low memory breadcrumb capturing * Changelog * Debounce low memory breadcrumbs
1 parent f80b9e8 commit df386ef

File tree

3 files changed

+57
-40
lines changed

3 files changed

+57
-40
lines changed

CHANGELOG.md

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

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

1112
## 8.7.0

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

Lines changed: 31 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
import io.sentry.Integration;
1313
import io.sentry.SentryLevel;
1414
import io.sentry.SentryOptions;
15+
import io.sentry.android.core.internal.util.AndroidCurrentDateProvider;
16+
import io.sentry.android.core.internal.util.Debouncer;
1517
import io.sentry.android.core.internal.util.DeviceOrientations;
1618
import io.sentry.protocol.Device;
1719
import io.sentry.util.Objects;
@@ -24,10 +26,17 @@
2426
public final class AppComponentsBreadcrumbsIntegration
2527
implements Integration, Closeable, ComponentCallbacks2 {
2628

29+
private static final long DEBOUNCE_WAIT_TIME_MS = 60 * 1000;
30+
// pre-allocate hint to avoid creating it every time for the low memory case
31+
private static final @NotNull Hint EMPTY_HINT = new Hint();
32+
2733
private final @NotNull Context context;
2834
private @Nullable IScopes scopes;
2935
private @Nullable SentryAndroidOptions options;
3036

37+
private final @NotNull Debouncer trimMemoryDebouncer =
38+
new Debouncer(AndroidCurrentDateProvider.getInstance(), DEBOUNCE_WAIT_TIME_MS, 0);
39+
3140
public AppComponentsBreadcrumbsIntegration(final @NotNull Context context) {
3241
this.context =
3342
Objects.requireNonNull(ContextUtils.getApplicationContext(context), "Context is required");
@@ -91,42 +100,43 @@ public void onConfigurationChanged(@NotNull Configuration newConfig) {
91100

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

98106
@Override
99107
public void onTrimMemory(final int level) {
108+
if (level < TRIM_MEMORY_BACKGROUND) {
109+
// only add breadcrumb if TRIM_MEMORY_BACKGROUND, TRIM_MEMORY_MODERATE or
110+
// TRIM_MEMORY_COMPLETE.
111+
// Release as much memory as the process can.
112+
113+
// TRIM_MEMORY_UI_HIDDEN, TRIM_MEMORY_RUNNING_MODERATE, TRIM_MEMORY_RUNNING_LOW and
114+
// TRIM_MEMORY_RUNNING_CRITICAL.
115+
// Release any memory that your app doesn't need to run.
116+
// So they are still not so critical at the point of killing the process.
117+
// https://developer.android.com/topic/performance/memory
118+
return;
119+
}
120+
121+
if (trimMemoryDebouncer.checkForDebounce()) {
122+
// if we received trim_memory within 1 minute time, ignore this call
123+
return;
124+
}
125+
100126
final long now = System.currentTimeMillis();
101127
executeInBackground(() -> captureLowMemoryBreadcrumb(now, level));
102128
}
103129

104-
private void captureLowMemoryBreadcrumb(final long timeMs, final @Nullable Integer level) {
130+
private void captureLowMemoryBreadcrumb(final long timeMs, final int level) {
105131
if (scopes != null) {
106132
final Breadcrumb breadcrumb = new Breadcrumb(timeMs);
107-
if (level != null) {
108-
// only add breadcrumb if TRIM_MEMORY_BACKGROUND, TRIM_MEMORY_MODERATE or
109-
// TRIM_MEMORY_COMPLETE.
110-
// Release as much memory as the process can.
111-
112-
// TRIM_MEMORY_UI_HIDDEN, TRIM_MEMORY_RUNNING_MODERATE, TRIM_MEMORY_RUNNING_LOW and
113-
// TRIM_MEMORY_RUNNING_CRITICAL.
114-
// Release any memory that your app doesn't need to run.
115-
// So they are still not so critical at the point of killing the process.
116-
// https://developer.android.com/topic/performance/memory
117-
118-
if (level < TRIM_MEMORY_BACKGROUND) {
119-
return;
120-
}
121-
breadcrumb.setData("level", level);
122-
}
123-
124133
breadcrumb.setType("system");
125134
breadcrumb.setCategory("device.event");
126135
breadcrumb.setMessage("Low memory");
127136
breadcrumb.setData("action", "LOW_MEMORY");
137+
breadcrumb.setData("level", level);
128138
breadcrumb.setLevel(SentryLevel.WARNING);
129-
scopes.addBreadcrumb(breadcrumb);
139+
scopes.addBreadcrumb(breadcrumb, EMPTY_HINT);
130140
}
131141
}
132142

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

Lines changed: 25 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import org.mockito.kotlin.check
1515
import org.mockito.kotlin.mock
1616
import org.mockito.kotlin.never
1717
import org.mockito.kotlin.verify
18+
import org.mockito.kotlin.verifyNoMoreInteractions
1819
import org.mockito.kotlin.whenever
1920
import java.lang.NullPointerException
2021
import kotlin.test.Test
@@ -95,24 +96,6 @@ class AppComponentsBreadcrumbsIntegrationTest {
9596
sut.close()
9697
}
9798

98-
@Test
99-
fun `When low memory event, a breadcrumb with type, category and level should be set`() {
100-
val sut = fixture.getSut()
101-
val options = SentryAndroidOptions().apply {
102-
executorService = ImmediateExecutorService()
103-
}
104-
val scopes = mock<IScopes>()
105-
sut.register(scopes, options)
106-
sut.onLowMemory()
107-
verify(scopes).addBreadcrumb(
108-
check<Breadcrumb> {
109-
assertEquals("device.event", it.category)
110-
assertEquals("system", it.type)
111-
assertEquals(SentryLevel.WARNING, it.level)
112-
}
113-
)
114-
}
115-
11699
@Test
117100
fun `When trim memory event with level, a breadcrumb with type, category and level should be set`() {
118101
val sut = fixture.getSut()
@@ -127,7 +110,8 @@ class AppComponentsBreadcrumbsIntegrationTest {
127110
assertEquals("device.event", it.category)
128111
assertEquals("system", it.type)
129112
assertEquals(SentryLevel.WARNING, it.level)
130-
}
113+
},
114+
anyOrNull()
131115
)
132116
}
133117

@@ -162,4 +146,26 @@ class AppComponentsBreadcrumbsIntegrationTest {
162146
anyOrNull()
163147
)
164148
}
149+
150+
@Test
151+
fun `low memory changes are debounced`() {
152+
val sut = fixture.getSut()
153+
154+
val scopes = mock<IScopes>()
155+
val options = SentryAndroidOptions().apply {
156+
executorService = ImmediateExecutorService()
157+
}
158+
sut.register(scopes, options)
159+
sut.onTrimMemory(ComponentCallbacks2.TRIM_MEMORY_BACKGROUND)
160+
sut.onTrimMemory(ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL)
161+
162+
// should only add the first crumb
163+
verify(scopes).addBreadcrumb(
164+
check<Breadcrumb> {
165+
assertEquals(it.data["level"], 40)
166+
},
167+
anyOrNull()
168+
)
169+
verifyNoMoreInteractions(scopes)
170+
}
165171
}

0 commit comments

Comments
 (0)