Skip to content

Commit a124443

Browse files
committed
Added memory metrics issues
1 parent ca8e2f8 commit a124443

6 files changed

Lines changed: 197 additions & 9 deletions

File tree

‎app/src/main/java/com/android/developers/androidify/MainActivity.kt‎

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,20 @@ class MainActivity : ComponentActivity() {
6565
}
6666
}
6767

68+
override fun onResume() {
69+
super.onResume()
70+
// Replicate a high foreground footprint of 350MB
71+
com.android.developers.androidify.util.MemorySimulator.startNativeBitmapBloat(targetMegaBytes = 350)
72+
// Simulate Java Heap Swap bloat
73+
com.android.developers.androidify.util.MemorySimulator.startJavaHeapSwapBloat(targetMegaBytes = 200)
74+
}
75+
76+
override fun onPause() {
77+
super.onPause()
78+
// Prevent background LMK (Low Memory Kills) during local testing
79+
com.android.developers.androidify.util.MemorySimulator.reset()
80+
}
81+
6882
override fun onAttachedToWindow() {
6983
super.onAttachedToWindow()
7084
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM) {
Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
package com.android.developers.androidify.util
2+
3+
import android.content.Context
4+
import android.graphics.Bitmap
5+
import android.os.Handler
6+
import android.os.Looper
7+
import android.webkit.WebView
8+
import java.io.File
9+
import java.io.RandomAccessFile
10+
import java.nio.ByteBuffer
11+
import java.nio.MappedByteBuffer
12+
import java.nio.channels.FileChannel
13+
import java.util.UUID
14+
15+
object MemorySimulator {
16+
private val javaHeapLeaker = mutableListOf<String>()
17+
private val bitmapPool = mutableListOf<Bitmap>()
18+
private val directBuffers = mutableListOf<ByteBuffer>()
19+
private val mappedBuffers = mutableListOf<MappedByteBuffer>()
20+
private val webViews = mutableListOf<WebView>()
21+
private val mainHandler = Handler(Looper.getMainLooper())
22+
23+
/**
24+
* Toggles massive Java Heap string allocations.
25+
* Excellent for forcing the OS to swap compressed pages to zRAM (Swap).
26+
*/
27+
fun startJavaHeapSwapBloat(targetMegaBytes: Int, intervalMs: Long = 100) {
28+
val bytesPerAlloc = 1024 * 1024 // 1 MB increments
29+
val totalLoops = targetMegaBytes
30+
31+
var allocated = 0
32+
mainHandler.post(object : Runnable {
33+
override fun run() {
34+
if (allocated < totalLoops) {
35+
val stringChunk = StringBuilder().apply {
36+
repeat(bytesPerAlloc / 2) { append(UUID.randomUUID().toString().take(2)) }
37+
}.toString()
38+
39+
javaHeapLeaker.add(stringChunk)
40+
allocated++
41+
mainHandler.postDelayed(this, intervalMs)
42+
}
43+
}
44+
})
45+
}
46+
47+
/**
48+
* Toggles Native Bitmap Allocation (Anon RSS).
49+
* Directly inflates the private dirty native heap without exhausting JVM heap limits.
50+
*/
51+
fun startNativeBitmapBloat(targetMegaBytes: Int, intervalMs: Long = 200) {
52+
val width = 512
53+
val height = 512
54+
var allocated = 0
55+
56+
mainHandler.post(object : Runnable {
57+
override fun run() {
58+
if (allocated < targetMegaBytes) {
59+
val bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
60+
bitmap.eraseColor(android.graphics.Color.RED)
61+
bitmapPool.add(bitmap)
62+
allocated++
63+
mainHandler.postDelayed(this, intervalMs)
64+
}
65+
}
66+
})
67+
}
68+
69+
/**
70+
* Toggles Direct Native Allocations (C/C++ malloc equivalent).
71+
* Bypasses the JVM heap completely and allocates raw native memory.
72+
*/
73+
fun startDirectNativeBloat(targetMegaBytes: Int, intervalMs: Long = 100) {
74+
var allocated = 0
75+
mainHandler.post(object : Runnable {
76+
override fun run() {
77+
if (allocated < targetMegaBytes) {
78+
// Allocate 1MB of direct native memory
79+
val buffer = ByteBuffer.allocateDirect(1024 * 1024)
80+
// Touch the memory so the OS maps it to physical RAM
81+
for (i in 0 until buffer.capacity() step 4096) {
82+
buffer.put(i, 1)
83+
}
84+
directBuffers.add(buffer)
85+
allocated++
86+
mainHandler.postDelayed(this, intervalMs)
87+
}
88+
}
89+
})
90+
}
91+
92+
/**
93+
* Toggles the WebView "Hidden Tab" trick.
94+
* Rapidly consumes massive amounts of memory via Chromium rendering processes.
95+
*/
96+
fun startWebViewBloat(context: Context, targetInstances: Int = 10, intervalMs: Long = 500) {
97+
var allocated = 0
98+
mainHandler.post(object : Runnable {
99+
override fun run() {
100+
if (allocated < targetInstances) {
101+
val webView = WebView(context).apply {
102+
settings.javaScriptEnabled = true
103+
// Load a heavy WebGL/rendering page (or just a heavy DOM)
104+
loadUrl("https://threejs.org/examples/#webgl_animation_keyframes")
105+
}
106+
webViews.add(webView)
107+
allocated++
108+
mainHandler.postDelayed(this, intervalMs)
109+
}
110+
}
111+
})
112+
}
113+
114+
/**
115+
* Memory maps massive files to inflate the virtual memory footprint.
116+
*/
117+
fun startFileMmapBloat(context: Context, targetMegaBytes: Int) {
118+
Thread {
119+
try {
120+
// Create a huge dummy file
121+
val dummyFile = File(context.cacheDir, "huge_dummy_file.tmp")
122+
val raf = RandomAccessFile(dummyFile, "rw")
123+
val targetBytes = targetMegaBytes * 1024L * 1024L
124+
raf.setLength(targetBytes)
125+
126+
// mmap the file into memory
127+
val channel = raf.channel
128+
val mappedBuffer = channel.map(FileChannel.MapMode.READ_WRITE, 0, targetBytes)
129+
130+
// Touch the bytes to force physical allocation
131+
for (i in 0 until targetBytes step 4096) {
132+
mappedBuffer.put(i.toInt(), 1.toByte())
133+
}
134+
135+
mappedBuffers.add(mappedBuffer)
136+
} catch (e: Exception) {
137+
e.printStackTrace()
138+
}
139+
}.start()
140+
}
141+
142+
/**
143+
* Clears all simulated allocations to allow the OS to reclaim space.
144+
*/
145+
fun reset() {
146+
mainHandler.removeCallbacksAndMessages(null)
147+
javaHeapLeaker.clear()
148+
149+
bitmapPool.forEach { it.recycle() }
150+
bitmapPool.clear()
151+
152+
directBuffers.clear()
153+
mappedBuffers.clear()
154+
155+
webViews.forEach {
156+
it.stopLoading()
157+
it.destroy()
158+
}
159+
webViews.clear()
160+
161+
System.gc()
162+
}
163+
}

‎benchmark/build.gradle.kts‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ android {
4747

4848
defaultConfig {
4949
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
50+
testInstrumentationRunnerArguments["androidx.benchmark.suppressErrors"] = "EMULATOR"
5051
}
5152

5253
buildTypes {

‎benchmark/src/main/kotlin/com/android/developers/androidify/benchmark/PromptBenchmark.kt‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ class PromptBenchmark {
6767
iterations = 5,
6868
setupBlock = {
6969
killProcess()
70-
}
70+
},
7171
) {
7272
uiAutomator {
7373
startApp(packageName = packageName)

‎benchmark/src/main/kotlin/com/android/developers/androidify/benchmark/StartupBenchmark.kt‎

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,20 @@ package com.android.developers.androidify.benchmark
1717

1818
import android.os.Build
1919
import androidx.annotation.RequiresApi
20+
import androidx.benchmark.ExperimentalBenchmarkConfigApi
21+
import androidx.benchmark.ExperimentalConfig
22+
import androidx.benchmark.MemoryProfilingConfig
23+
import androidx.benchmark.macro.ArtMetric
2024
import androidx.benchmark.macro.CompilationMode
2125
import androidx.benchmark.macro.ExperimentalMetricApi
2226
import androidx.benchmark.macro.FrameTimingMetric
2327
import androidx.benchmark.macro.MemoryUsageMetric
24-
import androidx.benchmark.macro.PowerMetric
2528
import androidx.benchmark.macro.StartupMode
2629
import androidx.benchmark.macro.StartupTimingMetric
2730
import androidx.benchmark.macro.junit4.MacrobenchmarkRule
31+
import androidx.benchmark.perfetto.ExperimentalPerfettoCaptureApi
2832
import androidx.test.ext.junit.runners.AndroidJUnit4
33+
import androidx.test.filters.SdkSuppress
2934
import androidx.test.uiautomator.uiAutomator
3035
import com.android.developers.androidify.classInitMetric
3136
import com.android.developers.androidify.jitCompilationMetric
@@ -34,7 +39,7 @@ import org.junit.Test
3439
import org.junit.runner.RunWith
3540

3641
@RunWith(AndroidJUnit4::class)
37-
@RequiresApi(Build.VERSION_CODES.Q)
42+
@SdkSuppress(minSdkVersion = Build.VERSION_CODES.Q)
3843
class StartupBenchmark {
3944
@get:Rule
4045
val benchmarkRule = MacrobenchmarkRule()
@@ -44,15 +49,23 @@ class StartupBenchmark {
4449

4550
@Test
4651
fun startupBaselineProfile() = startup(CompilationMode.DEFAULT)
47-
@OptIn(ExperimentalMetricApi::class)
52+
53+
@OptIn(
54+
ExperimentalMetricApi::class, ExperimentalBenchmarkConfigApi::class,
55+
ExperimentalPerfettoCaptureApi::class,
56+
)
4857
private fun startup(compilationMode: CompilationMode) = benchmarkRule.measureRepeated(
4958
packageName = "com.android.developers.androidify",
5059
metrics = listOf(
5160
StartupTimingMetric(),
5261
FrameTimingMetric(),
5362
MemoryUsageMetric(MemoryUsageMetric.Mode.Max, subMetrics = MemoryUsageMetric.SubMetric.entries),
54-
jitCompilationMetric,
55-
classInitMetric,
63+
),
64+
experimentalConfig = ExperimentalConfig(
65+
memoryProfilingConfig = MemoryProfilingConfig(
66+
isSampleArtHeapEnabled = true,
67+
isSampleNativeHeapEnabled = true,
68+
),
5669
),
5770
setupBlock = {
5871
killProcess()

‎feature/camera/src/main/java/com/android/developers/androidify/camera/CameraZoomToolbar.kt‎

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@ import androidx.compose.material3.Button
2323
import androidx.compose.material3.ButtonGroup
2424
import androidx.compose.material3.ButtonGroupDefaults
2525
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
26-
import androidx.compose.material3.LocalTextStyle
2726
import androidx.compose.material3.Text
2827
import androidx.compose.material3.ToggleButton
2928
import androidx.compose.material3.ToggleButtonDefaults
@@ -34,8 +33,6 @@ import androidx.compose.runtime.mutableFloatStateOf
3433
import androidx.compose.runtime.remember
3534
import androidx.compose.runtime.setValue
3635
import androidx.compose.ui.Modifier
37-
import androidx.compose.ui.platform.LocalDensity
38-
import androidx.compose.ui.text.rememberTextMeasurer
3936
import androidx.compose.ui.text.style.TextAlign
4037
import androidx.compose.ui.tooling.preview.Preview
4138
import com.android.developers.androidify.theme.AndroidifyTheme

0 commit comments

Comments
 (0)