Skip to content

Commit dec0c66

Browse files
committed
Add ProfilingFragment into Sample Application
1 parent 4f0483e commit dec0c66

File tree

7 files changed

+470
-1
lines changed

7 files changed

+470
-1
lines changed

sample/kotlin/src/main/kotlin/com/datadog/android/sample/home/HomeFragment.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ internal class HomeFragment :
5858
R.id.navigation_crash -> R.id.fragment_crash
5959
R.id.navigation_traces -> R.id.fragment_trace
6060
R.id.navigation_otel_traces -> R.id.fragment_otel_traces
61+
R.id.navigation_profiling -> R.id.fragment_profiling
6162
R.id.navigation_vitals -> R.id.fragment_vitals
6263
R.id.navigation_webview -> R.id.fragment_webview
6364
R.id.navigation_data_list -> R.id.fragment_data_list
Lines changed: 199 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
1+
/*
2+
* Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0.
3+
* This product includes software developed at Datadog (https://www.datadoghq.com/).
4+
* Copyright 2016-Present Datadog, Inc.
5+
*/
6+
7+
package com.datadog.android.sample.profiling
8+
9+
import java.util.concurrent.atomic.AtomicBoolean
10+
import kotlin.math.cos
11+
import kotlin.math.pow
12+
import kotlin.math.sin
13+
import kotlin.math.sqrt
14+
import kotlin.random.Random
15+
16+
/**
17+
* Simulates heavy CPU computation for profiling purposes.
18+
* This class performs various CPU-intensive operations including:
19+
* - Complex mathematical calculations
20+
* - String manipulation
21+
* - List/array operations
22+
* - Prime number calculations
23+
*/
24+
@Suppress("MagicNumber", "TooManyFunctions")
25+
class HeavyCPUWork {
26+
27+
private val isRunning = AtomicBoolean(false)
28+
private var workerThread: Thread? = null
29+
30+
/**
31+
* Starts the heavy CPU computation on a background thread.
32+
*/
33+
fun start() {
34+
if (isRunning.getAndSet(true)) {
35+
// Already running
36+
return
37+
}
38+
39+
workerThread = Thread {
40+
while (isRunning.get()) {
41+
performHeavyComputation()
42+
}
43+
}.apply {
44+
name = "HeavyCPUWork-Thread"
45+
start()
46+
}
47+
}
48+
49+
/**
50+
* Stops the heavy CPU computation.
51+
*/
52+
fun stop() {
53+
isRunning.set(false)
54+
workerThread?.interrupt()
55+
workerThread = null
56+
}
57+
58+
/**
59+
* Performs various CPU-intensive operations.
60+
*/
61+
private fun performHeavyComputation() {
62+
// Mix of different CPU-heavy operations
63+
when (Random.nextInt(5)) {
64+
0 -> calculatePrimes(1000)
65+
1 -> performMatrixMultiplication(50)
66+
2 -> performStringOperations(1000)
67+
3 -> performComplexMath(10000)
68+
4 -> performListOperations(5000)
69+
}
70+
}
71+
72+
/**
73+
* Calculate prime numbers up to a limit.
74+
*/
75+
private fun calculatePrimes(limit: Int): List<Int> {
76+
val primes = mutableListOf<Int>()
77+
for (num in 2..limit) {
78+
if (isPrime(num)) {
79+
primes.add(num)
80+
}
81+
}
82+
return primes
83+
}
84+
85+
@Suppress("ReturnCount")
86+
private fun isPrime(n: Int): Boolean {
87+
if (n < 2) return false
88+
if (n == 2) return true
89+
if (n % 2 == 0) return false
90+
91+
val sqrt = sqrt(n.toDouble()).toInt()
92+
for (i in 3..sqrt step 2) {
93+
if (n % i == 0) return false
94+
}
95+
return true
96+
}
97+
98+
/**
99+
* Perform matrix multiplication.
100+
*/
101+
private fun performMatrixMultiplication(size: Int) {
102+
val matrix1 = Array(size) { DoubleArray(size) { Random.nextDouble() } }
103+
val matrix2 = Array(size) { DoubleArray(size) { Random.nextDouble() } }
104+
val result = Array(size) { DoubleArray(size) }
105+
106+
for (i in 0 until size) {
107+
for (j in 0 until size) {
108+
var sum = 0.0
109+
for (k in 0 until size) {
110+
sum += matrix1[i][k] * matrix2[k][j]
111+
}
112+
result[i][j] = sum
113+
}
114+
}
115+
}
116+
117+
/**
118+
* Perform intensive string operations.
119+
*/
120+
private fun performStringOperations(iterations: Int) {
121+
var result = ""
122+
for (i in 0 until iterations) {
123+
result = "Iteration $i: ${generateRandomString(10)}"
124+
result = result.reversed()
125+
result = result.uppercase()
126+
result = result.lowercase()
127+
128+
// String contains check
129+
if (result.contains("test")) {
130+
result = result.replace("test", "")
131+
}
132+
}
133+
}
134+
135+
private fun generateRandomString(length: Int): String {
136+
val chars = ('a'..'z') + ('A'..'Z') + ('0'..'9')
137+
return (1..length)
138+
.map { chars.random() }
139+
.joinToString("")
140+
}
141+
142+
/**
143+
* Perform complex mathematical calculations.
144+
*/
145+
private fun performComplexMath(iterations: Int) {
146+
var result = 0.0
147+
for (i in 0 until iterations) {
148+
val x = Random.nextDouble() * 100
149+
result += sin(x) * cos(x)
150+
result += sqrt(x.pow(2) + x.pow(3))
151+
result += x.pow(4) / (x + 1)
152+
153+
// Fibonacci-like calculation
154+
result += fibonacci(20)
155+
}
156+
}
157+
158+
private fun fibonacci(n: Int): Long {
159+
if (n <= 1) return n.toLong()
160+
var a = 0L
161+
var b = 1L
162+
for (i in 2..n) {
163+
val temp = a + b
164+
a = b
165+
b = temp
166+
}
167+
return b
168+
}
169+
170+
/**
171+
* Perform list/collection operations.
172+
*/
173+
private fun performListOperations(size: Int) {
174+
// Create and manipulate large lists
175+
val list = (0 until size).map { Random.nextInt(10000) }.toMutableList()
176+
177+
// Sorting
178+
list.sortDescending()
179+
list.shuffle()
180+
list.sort()
181+
182+
// Filtering and mapping
183+
val filtered = list.filter { it % 2 == 0 }
184+
filtered.map { it * 2 }
185+
186+
// Finding operations
187+
list.maxOrNull()
188+
list.minOrNull()
189+
list.sum()
190+
list.average()
191+
192+
// Group by operations
193+
list.groupBy { it % 10 }
194+
195+
// Distinct and contains
196+
list.distinct()
197+
list.contains(5000)
198+
}
199+
}
Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
/*
2+
* Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0.
3+
* This product includes software developed at Datadog (https://www.datadoghq.com/).
4+
* Copyright 2016-Present Datadog, Inc.
5+
*/
6+
7+
package com.datadog.android.sample.profiling
8+
9+
import android.os.Bundle
10+
import android.os.Handler
11+
import android.os.Looper
12+
import android.view.LayoutInflater
13+
import android.view.View
14+
import android.view.ViewGroup
15+
import android.widget.Button
16+
import android.widget.ProgressBar
17+
import androidx.fragment.app.Fragment
18+
import com.datadog.android.sample.R
19+
20+
@Suppress("MagicNumber")
21+
class ProfilingFragment : Fragment() {
22+
23+
private lateinit var button1s: Button
24+
private lateinit var button10s: Button
25+
private lateinit var button60s: Button
26+
27+
private lateinit var progressBar1s: ProgressBar
28+
private lateinit var progressBar10s: ProgressBar
29+
private lateinit var progressBar60s: ProgressBar
30+
31+
private val handler = Handler(Looper.getMainLooper())
32+
private var currentProfilingRunnable: Runnable? = null
33+
34+
private val heavyCPUWork = HeavyCPUWork()
35+
36+
override fun onCreateView(
37+
inflater: LayoutInflater,
38+
container: ViewGroup?,
39+
savedInstanceState: Bundle?
40+
): View? {
41+
return inflater.inflate(R.layout.fragment_profiling, container, false)
42+
}
43+
44+
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
45+
super.onViewCreated(view, savedInstanceState)
46+
47+
// Initialize views
48+
button1s = view.findViewById(R.id.button_profile_1s)
49+
button10s = view.findViewById(R.id.button_profile_10s)
50+
button60s = view.findViewById(R.id.button_profile_60s)
51+
52+
progressBar1s = view.findViewById(R.id.progress_bar_1s)
53+
progressBar10s = view.findViewById(R.id.progress_bar_10s)
54+
progressBar60s = view.findViewById(R.id.progress_bar_60s)
55+
56+
// Set click listeners
57+
button1s.setOnClickListener {
58+
startProfiling(duration = 1, progressBar = progressBar1s)
59+
}
60+
61+
button10s.setOnClickListener {
62+
startProfiling(duration = 10, progressBar = progressBar10s)
63+
}
64+
65+
button60s.setOnClickListener {
66+
startProfiling(duration = 60, progressBar = progressBar60s)
67+
}
68+
}
69+
70+
private fun startProfiling(duration: Int, progressBar: ProgressBar) {
71+
// Cancel any ongoing profiling
72+
currentProfilingRunnable?.let { handler.removeCallbacks(it) }
73+
74+
// Reset all progress bars
75+
resetAllProgressBars()
76+
77+
// Disable all buttons during profiling
78+
setAllButtonsEnabled(false)
79+
80+
// Start heavy CPU work for profiling
81+
heavyCPUWork.start()
82+
83+
// TODO RUM-13509: start custom profiling here.
84+
85+
// Track progress
86+
val startTime = System.currentTimeMillis()
87+
val durationMs = duration * 1000L
88+
val updateInterval = 50L // Update every 50ms for smooth animation
89+
90+
val profilingRunnable = object : Runnable {
91+
override fun run() {
92+
val elapsed = System.currentTimeMillis() - startTime
93+
val progress = ((elapsed.toFloat() / durationMs) * 100).toInt().coerceIn(0, 100)
94+
95+
progressBar.progress = progress
96+
97+
if (elapsed < durationMs) {
98+
// Continue updating progress
99+
handler.postDelayed(this, updateInterval)
100+
} else {
101+
// Profiling complete
102+
progressBar.progress = 100
103+
104+
// Stop heavy CPU work
105+
heavyCPUWork.stop()
106+
107+
// TODO RUM-13509: stop custom profiling here.
108+
109+
// Re-enable all buttons
110+
setAllButtonsEnabled(true)
111+
112+
// Reset progress bar after a short delay
113+
handler.postDelayed({
114+
progressBar.progress = 0
115+
}, 500)
116+
117+
currentProfilingRunnable = null
118+
}
119+
}
120+
}
121+
122+
currentProfilingRunnable = profilingRunnable
123+
handler.post(profilingRunnable)
124+
}
125+
126+
private fun resetAllProgressBars() {
127+
progressBar1s.progress = 0
128+
progressBar10s.progress = 0
129+
progressBar60s.progress = 0
130+
}
131+
132+
private fun setAllButtonsEnabled(enabled: Boolean) {
133+
button1s.isEnabled = enabled
134+
button10s.isEnabled = enabled
135+
button60s.isEnabled = enabled
136+
}
137+
138+
override fun onDestroyView() {
139+
super.onDestroyView()
140+
// Clean up any pending callbacks
141+
currentProfilingRunnable?.let { handler.removeCallbacks(it) }
142+
// Stop heavy CPU work if still running
143+
heavyCPUWork.stop()
144+
}
145+
}

sample/kotlin/src/main/res/layout/fragment_home.xml

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,17 @@
7474
app:layout_constraintStart_toStartOf="parent"
7575
app:layout_constraintTop_toBottomOf="@id/navigation_traces"/>
7676

77+
<androidx.appcompat.widget.AppCompatButton
78+
android:id="@+id/navigation_profiling"
79+
android:layout_width="0dp"
80+
android:layout_height="wrap_content"
81+
android:layout_margin="8dp"
82+
android:text="@string/title_profiling"
83+
android:drawableStart="@drawable/baseline_radio_button_unchecked_24"
84+
app:layout_constraintEnd_toEndOf="parent"
85+
app:layout_constraintStart_toStartOf="parent"
86+
app:layout_constraintTop_toBottomOf="@id/navigation_otel_traces"/>
87+
7788
<androidx.appcompat.widget.AppCompatTextView
7889
android:id="@+id/rum_section"
7990
android:layout_width="0dp"
@@ -85,7 +96,7 @@
8596
android:textAppearance="@style/TextAppearance.AppCompat.Medium"
8697
app:layout_constraintEnd_toEndOf="parent"
8798
app:layout_constraintStart_toStartOf="parent"
88-
app:layout_constraintTop_toBottomOf="@id/navigation_otel_traces"/>
99+
app:layout_constraintTop_toBottomOf="@id/navigation_profiling"/>
89100

90101

91102
<androidx.appcompat.widget.AppCompatButton

0 commit comments

Comments
 (0)