Skip to content

Commit 2af2407

Browse files
authored
Added some unoptimized ways for testing the application performance
Feature/before unoptimized
2 parents 425136f + 2dee9b5 commit 2af2407

15 files changed

Lines changed: 900 additions & 31 deletions

app/src/main/AndroidManifest.xml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,10 @@
22
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
33
xmlns:tools="http://schemas.android.com/tools">
44

5+
<uses-permission android:name="android.permission.INTERNET" />
6+
57
<application
8+
android:name=".AndroidPerfLabApplication"
69
android:allowBackup="true"
710
android:dataExtractionRules="@xml/data_extraction_rules"
811
android:fullBackupContent="@xml/backup_rules"
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
package com.aquib.androidperflab
2+
3+
import android.app.Application
4+
import android.util.Log
5+
import com.aquib.androidperflab.sdk.FakeAnalyticsSdk
6+
import com.aquib.androidperflab.sdk.FakeCrashReportingSdk
7+
import com.aquib.androidperflab.sdk.FakeFeatureFlagsSdk
8+
import com.aquib.androidperflab.sdk.FakePerformanceMonitorSdk
9+
import com.aquib.androidperflab.sdk.FakeRemoteConfigSdk
10+
11+
class AndroidPerfLabApplication : Application() {
12+
13+
override fun onCreate() {
14+
super.onCreate()
15+
16+
// Intentionally bad: all five SDKs are initialised synchronously on the main thread.
17+
// Each blocks via Thread.sleep() to simulate the I/O, disk, and network work that
18+
// real SDKs perform. Total cold-start penalty ≈ 750 ms before the first frame.
19+
20+
val t0 = System.currentTimeMillis()
21+
22+
FakeCrashReportingSdk.init(this) // ~120 ms — must be first to catch early crashes
23+
Log.d("AppStartup", "CrashReporting ready +${System.currentTimeMillis() - t0}ms")
24+
25+
FakeAnalyticsSdk.init(this) // ~180 ms
26+
Log.d("AppStartup", "Analytics ready +${System.currentTimeMillis() - t0}ms")
27+
28+
FakeFeatureFlagsSdk.init(this) // ~150 ms
29+
Log.d("AppStartup", "FeatureFlags ready +${System.currentTimeMillis() - t0}ms")
30+
31+
FakeRemoteConfigSdk.init(this) // ~200 ms
32+
Log.d("AppStartup", "RemoteConfig ready +${System.currentTimeMillis() - t0}ms")
33+
34+
FakePerformanceMonitorSdk.init(this) // ~100 ms — last so it can baseline the others
35+
Log.d("AppStartup", "PerfMonitor ready +${System.currentTimeMillis() - t0}ms")
36+
37+
Log.d("AppStartup", "Application.onCreate() complete — total blocked=${System.currentTimeMillis() - t0}ms")
38+
}
39+
}

app/src/main/java/com/aquib/androidperflab/MainActivity.kt

Lines changed: 2 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,7 @@ import android.os.Bundle
44
import androidx.activity.ComponentActivity
55
import androidx.activity.compose.setContent
66
import androidx.activity.enableEdgeToEdge
7-
import androidx.compose.foundation.layout.fillMaxSize
8-
import androidx.compose.foundation.layout.padding
9-
import androidx.compose.material3.Scaffold
10-
import androidx.compose.material3.Text
11-
import androidx.compose.runtime.Composable
12-
import androidx.compose.ui.Modifier
13-
import androidx.compose.ui.tooling.preview.Preview
7+
import com.aquib.androidperflab.ui.HomeScreen
148
import com.aquib.androidperflab.ui.theme.AndroidPerfLabTheme
159

1610
class MainActivity : ComponentActivity() {
@@ -19,29 +13,8 @@ class MainActivity : ComponentActivity() {
1913
enableEdgeToEdge()
2014
setContent {
2115
AndroidPerfLabTheme {
22-
Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
23-
Greeting(
24-
name = "Android",
25-
modifier = Modifier.padding(innerPadding)
26-
)
27-
}
16+
HomeScreen()
2817
}
2918
}
3019
}
31-
}
32-
33-
@Composable
34-
fun Greeting(name: String, modifier: Modifier = Modifier) {
35-
Text(
36-
text = "Hello $name!",
37-
modifier = modifier
38-
)
39-
}
40-
41-
@Preview(showBackground = true)
42-
@Composable
43-
fun GreetingPreview() {
44-
AndroidPerfLabTheme {
45-
Greeting("Android")
46-
}
4720
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package com.aquib.androidperflab.sdk
2+
3+
import android.content.Context
4+
import android.os.Build
5+
import android.util.Log
6+
7+
object FakeAnalyticsSdk {
8+
9+
// Simulates: opening a local SQLite event queue, reading persisted user identity from
10+
// SharedPreferences, building a device fingerprint, and doing a blocking fetch of
11+
// the remote analytics endpoint — all on the main thread.
12+
fun init(context: Context) {
13+
// Simulate: hydrating the in-memory event schema (heavy map allocation)
14+
val eventSchema = HashMap<String, String>(512)
15+
repeat(500) { i -> eventSchema["event_type_$i"] = "schema_v2_$i" }
16+
17+
// Simulate: reading / writing user identity from SharedPreferences
18+
val prefs = context.getSharedPreferences("fake_analytics", Context.MODE_PRIVATE)
19+
val userId = prefs.getString("user_id", null) ?: run {
20+
val generated = System.nanoTime().toString(16)
21+
// commit() instead of apply() — blocks until disk write completes
22+
prefs.edit().putString("user_id", generated).commit()
23+
generated
24+
}
25+
val sessionIndex = prefs.getInt("session_index", 0) + 1
26+
prefs.edit().putInt("session_index", sessionIndex).commit()
27+
28+
// Simulate: device fingerprinting (string concat over multiple Build fields)
29+
@Suppress("DEPRECATION")
30+
val fingerprint = buildString {
31+
append(Build.MANUFACTURER); append('_')
32+
append(Build.MODEL); append('_')
33+
append(Build.DEVICE); append('_')
34+
append(Build.FINGERPRINT.takeLast(12))
35+
}
36+
37+
// Simulate: blocking endpoint resolution + SDK handshake over network
38+
Thread.sleep(180L)
39+
40+
Log.d("FakeAnalyticsSdk", "init complete — user=$userId session=$sessionIndex fp=$fingerprint schema=${eventSchema.size} entries")
41+
}
42+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package com.aquib.androidperflab.sdk
2+
3+
import android.content.Context
4+
import android.util.Log
5+
import java.io.File
6+
7+
object FakeCrashReportingSdk {
8+
9+
// Simulates: scanning the cache directory for pending crash dumps left by a previous
10+
// session, installing an UncaughtExceptionHandler, and uploading any found dumps
11+
// synchronously before the app is considered "ready".
12+
fun init(context: Context) {
13+
// Simulate: scanning cache dir for pending crash dump files
14+
val cacheDir = context.cacheDir
15+
val crashDumps = cacheDir.listFiles { f -> f.name.startsWith("crash_") }
16+
?.toList()
17+
?: emptyList()
18+
19+
// Simulate: parsing each crash dump (string + I/O work per file)
20+
val parsedReports = crashDumps.map { file ->
21+
buildString {
22+
append("report["); append(file.name); append("]: ")
23+
append(file.length()); append(" bytes, modified=")
24+
append(file.lastModified())
25+
}
26+
}
27+
28+
// Simulate: writing a session sentinel file so the next launch can detect
29+
// whether this session ended cleanly
30+
val sentinel = File(cacheDir, "crash_sentinel_${System.currentTimeMillis()}.tmp")
31+
sentinel.createNewFile()
32+
33+
// Simulate: registering the uncaught exception handler (involves thread locking)
34+
val previousHandler = Thread.getDefaultUncaughtExceptionHandler()
35+
Thread.setDefaultUncaughtExceptionHandler { thread, throwable ->
36+
Log.e("FakeCrashReportingSdk", "Uncaught exception on ${thread.name}", throwable)
37+
previousHandler?.uncaughtException(thread, throwable)
38+
}
39+
40+
// Simulate: blocking upload of any pending crash reports to the backend
41+
Thread.sleep(120L)
42+
43+
Log.d("FakeCrashReportingSdk", "init complete — found ${crashDumps.size} pending dumps, parsed ${parsedReports.size} reports")
44+
}
45+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
package com.aquib.androidperflab.sdk
2+
3+
import android.content.Context
4+
import android.util.Log
5+
6+
object FakeFeatureFlagsSdk {
7+
8+
private val flags = HashMap<String, Boolean>()
9+
10+
// Simulates: parsing a large bundled JSON payload of flag definitions, evaluating
11+
// per-user targeting rules against locally stored user attributes, and persisting
12+
// the resolved flag state — all synchronously on the main thread.
13+
fun init(context: Context) {
14+
// Simulate: deserialising bundled flag defaults (CPU-bound JSON parsing)
15+
val rawPayload = buildString {
16+
repeat(200) { i ->
17+
append("""{"flag":"feature_$i","enabled":${i % 3 != 0},"rollout":${(i * 7) % 100}},""")
18+
}
19+
}
20+
21+
// Simulate: evaluating targeting rules for each flag
22+
val prefs = context.getSharedPreferences("fake_feature_flags", Context.MODE_PRIVATE)
23+
val userSegment = prefs.getInt("user_segment", (System.nanoTime() % 100).toInt())
24+
prefs.edit().putInt("user_segment", userSegment).commit()
25+
26+
repeat(200) { i ->
27+
val rollout = (i * 7) % 100
28+
flags["feature_$i"] = (i % 3 != 0) && (userSegment < rollout)
29+
}
30+
31+
val enabledCount = flags.values.count { it }
32+
33+
// Simulate: blocking network sync to fetch any server-side flag overrides
34+
Thread.sleep(150L)
35+
36+
Log.d("FakeFeatureFlagsSdk", "init complete — ${flags.size} flags resolved, $enabledCount enabled for segment=$userSegment payload=${rawPayload.length} chars")
37+
}
38+
39+
fun isEnabled(flag: String): Boolean = flags[flag] ?: false
40+
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
package com.aquib.androidperflab.sdk
2+
3+
import android.app.ActivityManager
4+
import android.content.Context
5+
import android.os.Debug
6+
import android.os.Process
7+
import android.util.Log
8+
9+
object FakePerformanceMonitorSdk {
10+
11+
// Simulates: snapshotting baseline memory stats, reading /proc/self/status for CPU
12+
// accounting, and installing a frame-timing callback — all synchronously on the main
13+
// thread before the first frame is drawn.
14+
fun init(context: Context) {
15+
// Simulate: collecting baseline memory snapshot
16+
val runtime = Runtime.getRuntime()
17+
val totalMemory = runtime.totalMemory()
18+
val freeMemory = runtime.freeMemory()
19+
val nativeHeap = Debug.getNativeHeapSize()
20+
21+
// Simulate: reading ActivityManager memory info
22+
val activityManager = context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
23+
val memInfo = ActivityManager.MemoryInfo()
24+
activityManager.getMemoryInfo(memInfo)
25+
26+
// Simulate: reading per-process CPU stats from procfs (string parsing)
27+
val pid = Process.myPid()
28+
val procStatLines = buildString {
29+
repeat(80) { i -> append("cpu_stat_field_${i}=${pid + i}\n") }
30+
}.lines()
31+
32+
// Simulate: building baseline metric registry (100 named counters)
33+
val metrics = HashMap<String, Long>(128)
34+
repeat(100) { i ->
35+
metrics["metric_$i"] = System.nanoTime() + i
36+
}
37+
38+
// Simulate: GC pause measurement — forces a GC and measures its cost
39+
val gcBefore = Debug.getRuntimeStat("art.gc.gc-count")
40+
System.gc()
41+
val gcAfter = Debug.getRuntimeStat("art.gc.gc-count")
42+
43+
// Simulate: installing frame-timing infrastructure (method tracing warmup)
44+
Thread.sleep(100L)
45+
46+
Log.d(
47+
"FakePerfMonitorSdk",
48+
"init complete — heap=${totalMemory / 1024}KB free=${freeMemory / 1024}KB " +
49+
"nativeHeap=${nativeHeap / 1024}KB availMem=${memInfo.availMem / 1024}KB " +
50+
"procStatLines=${procStatLines.size} metrics=${metrics.size} gc=$gcBefore$gcAfter",
51+
)
52+
}
53+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
package com.aquib.androidperflab.sdk
2+
3+
import android.content.Context
4+
import android.util.Log
5+
6+
object FakeRemoteConfigSdk {
7+
8+
private val config = HashMap<String, String>()
9+
10+
// Simulates: reading the last-fetched remote config blob from SharedPreferences,
11+
// validating an HMAC checksum, deserialising key-value pairs, and resolving
12+
// parameter overrides — synchronously blocking the main thread.
13+
fun init(context: Context) {
14+
val prefs = context.getSharedPreferences("fake_remote_config", Context.MODE_PRIVATE)
15+
16+
// Simulate: reading + validating a previously cached config blob
17+
val cachedBlob = prefs.getString("config_blob", null)
18+
val storedChecksum = prefs.getString("config_checksum", "")
19+
20+
if (cachedBlob != null) {
21+
// Simulate: checksum validation (string hashing)
22+
val computedChecksum = cachedBlob.fold(0L) { acc, c -> acc * 31 + c.code }.toString(16)
23+
val valid = computedChecksum == storedChecksum
24+
Log.d("FakeRemoteConfigSdk", "cache checksum valid=$valid")
25+
}
26+
27+
// Simulate: building a new in-memory config with 150 keys
28+
val newBlob = buildString {
29+
repeat(150) { i ->
30+
val key = "config_key_$i"
31+
val value = "value_${i}_${System.nanoTime() % 1000}"
32+
config[key] = value
33+
append("$key=$value;")
34+
}
35+
}
36+
val checksum = newBlob.fold(0L) { acc, c -> acc * 31 + c.code }.toString(16)
37+
prefs.edit()
38+
.putString("config_blob", newBlob)
39+
.putString("config_checksum", checksum)
40+
.commit()
41+
42+
// Simulate: blocking fetch of fresh config values from the remote endpoint
43+
Thread.sleep(200L)
44+
45+
Log.d("FakeRemoteConfigSdk", "init complete — ${config.size} keys loaded, checksum=$checksum")
46+
}
47+
48+
fun getString(key: String, default: String = ""): String = config[key] ?: default
49+
}

gradle/libs.versions.toml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@ benchmarkMacro = "1.5.0-alpha05"
1717
profileinstaller = "1.4.1"
1818
uiautomator = "2.3.0"
1919

20+
# Coil
21+
coil = "3.0.4"
22+
2023
# Test
2124
junit = "4.13.2"
2225
junitVersion = "1.2.1"
@@ -50,6 +53,10 @@ androidx-test-uiautomator = { group = "androidx.test.uiautomator", name =
5053
# Baseline Profiles — consumer side (installed into the APK)
5154
androidx-profileinstaller = { group = "androidx.profileinstaller", name = "profileinstaller", version.ref = "profileinstaller" }
5255

56+
# Coil
57+
coil-compose = { group = "io.coil-kt.coil3", name = "coil-compose", version.ref = "coil" }
58+
coil-network-okhttp = { group = "io.coil-kt.coil3", name = "coil-network-okhttp", version.ref = "coil" }
59+
5360
# Test
5461
junit = { group = "junit", name = "junit", version.ref = "junit" }
5562
androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" }

ui/build.gradle.kts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ dependencies {
2525
implementation(platform(libs.androidx.compose.bom))
2626
implementation(libs.bundles.compose.ui)
2727
implementation(libs.androidx.core.ktx)
28+
implementation(libs.coil.compose)
29+
implementation(libs.coil.network.okhttp)
2830

2931
debugImplementation(libs.androidx.compose.ui.tooling)
3032

0 commit comments

Comments
 (0)