Skip to content

Commit e2b8d0c

Browse files
43jayclaude
andcommitted
chore(samples): Improve ProfilingActivity UI for Perfetto testing
- Show active profiler status line with (i) info button to show SDK config (sample rates, lifecycle mode, use-profiling-manager) - Conditionally show Start(Manual) or Start(Transaction) button based on profileLifecycle mode, since each is a no-op in the wrong mode - Hide duration seekbar in MANUAL mode (only affects transaction length) - Remove inline profiling result TextView; show results via Toast and in the (i) dialog instead - Apply AppTheme.Main to fix edge-to-edge clipping on API 35+ - Add indices to the bitmap list items so user can see the list view jumping around Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 38149e8 commit e2b8d0c

File tree

6 files changed

+102
-37
lines changed

6 files changed

+102
-37
lines changed

sentry-samples/sentry-samples-android/src/main/AndroidManifest.xml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,8 @@
6666

6767
<activity
6868
android:name=".ProfilingActivity"
69-
android:exported="false" />
69+
android:exported="false"
70+
android:theme="@style/AppTheme.Main" />
7071

7172
<activity
7273
android:name=".CustomTabsActivity"

sentry-samples/sentry-samples-android/src/main/java/io/sentry/samples/android/ProfilingActivity.kt

Lines changed: 51 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,11 @@ import android.view.View
66
import android.widget.SeekBar
77
import android.widget.Toast
88
import androidx.activity.OnBackPressedCallback
9+
import androidx.appcompat.app.AlertDialog
910
import androidx.appcompat.app.AppCompatActivity
1011
import androidx.recyclerview.widget.LinearLayoutManager
1112
import io.sentry.ITransaction
13+
import io.sentry.ProfileLifecycle
1214
import io.sentry.ProfilingTraceData
1315
import io.sentry.Sentry
1416
import io.sentry.SentryEnvelopeItem
@@ -24,6 +26,7 @@ class ProfilingActivity : AppCompatActivity() {
2426
private val executors = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors())
2527
private var profileFinished = true
2628
private var manualProfilingActive = false
29+
private var lastProfilingResult: String? = null
2730

2831
override fun onCreate(savedInstanceState: Bundle?) {
2932
super.onCreate(savedInstanceState)
@@ -44,15 +47,49 @@ class ProfilingActivity : AppCompatActivity() {
4447
)
4548
binding = ActivityProfilingBinding.inflate(layoutInflater)
4649

47-
// Show which profiler backend is active
4850
val options = Sentry.getCurrentScopes().options
4951
val isPerfetto = options.isUseProfilingManager && Build.VERSION.SDK_INT >= 35
52+
val isContinuousEnabled = options.isContinuousProfilingEnabled
53+
val lifecycle = options.profileLifecycle
54+
55+
// Status line: summarize the active profiler
5056
binding.profilingStatus.text =
51-
if (isPerfetto) {
52-
getString(R.string.profiling_status_perfetto)
53-
} else {
54-
getString(R.string.profiling_status_legacy)
57+
when {
58+
!isContinuousEnabled -> getString(R.string.profiling_status_none)
59+
isPerfetto -> getString(R.string.profiling_status_perfetto)
60+
else -> getString(R.string.profiling_status_legacy)
61+
}
62+
63+
// Info button: show detailed config and last result in a dialog
64+
binding.profilingInfo.setOnClickListener {
65+
val config = buildString {
66+
appendLine("traces.profiling.lifecycle: ${lifecycle.name}")
67+
appendLine("profiling.use-profiling-manager: ${options.isUseProfilingManager}")
68+
appendLine("Build.VERSION.SDK_INT: ${Build.VERSION.SDK_INT}")
69+
appendLine("traces.profiling.session-sample-rate: ${options.profileSessionSampleRate}")
70+
appendLine("traces.sample-rate: ${options.tracesSampleRate}")
71+
if (lastProfilingResult != null) {
72+
appendLine()
73+
append(lastProfilingResult)
74+
}
5575
}
76+
AlertDialog.Builder(this)
77+
.setTitle(R.string.profiling_config_title)
78+
.setMessage(config)
79+
.setPositiveButton(android.R.string.ok, null)
80+
.show()
81+
}
82+
83+
// Show only the controls relevant to the current lifecycle mode
84+
when (lifecycle) {
85+
ProfileLifecycle.MANUAL -> {
86+
binding.profilingStartTransaction.visibility = View.GONE
87+
// Duration slider only controls transaction length — irrelevant for manual mode
88+
binding.profilingDurationText.visibility = View.GONE
89+
binding.profilingDurationSeekbar.visibility = View.GONE
90+
}
91+
ProfileLifecycle.TRACE -> binding.profilingStartTransactionManual.visibility = View.GONE
92+
}
5693

5794
binding.profilingDurationSeekbar.setOnSeekBarChangeListener(
5895
object : SeekBar.OnSeekBarChangeListener {
@@ -89,7 +126,7 @@ class ProfilingActivity : AppCompatActivity() {
89126
binding.profilingList.layoutManager = LinearLayoutManager(this)
90127

91128
// Transaction-based profiling (existing)
92-
binding.profilingStart.setOnClickListener {
129+
binding.profilingStartTransaction.setOnClickListener {
93130
binding.profilingProgressBar.visibility = View.VISIBLE
94131
profileFinished = false
95132
val seconds = getProfileDuration()
@@ -107,28 +144,28 @@ class ProfilingActivity : AppCompatActivity() {
107144
}
108145

109146
// Manual continuous profiling (exercises Perfetto path on API 35+)
110-
binding.profilingStartManual.setOnClickListener {
147+
binding.profilingStartTransactionManual.setOnClickListener {
111148
if (!manualProfilingActive) {
112149
Sentry.startProfiler()
113150
manualProfilingActive = true
114151
profileFinished = false
115-
binding.profilingStartManual.text = getString(R.string.profiling_stop_manual)
152+
binding.profilingStartTransactionManual.text = getString(R.string.profiling_stop_manual)
116153
binding.profilingProgressBar.visibility = View.VISIBLE
117154

118155
// Start background work to generate interesting profile data
119156
val threads = getBackgroundThreads()
120157
repeat(threads) { executors.submit { runMathOperations() } }
121158
executors.submit { swipeList() }
122159

123-
binding.profilingResult.text = getString(R.string.profiling_manual_started)
160+
Toast.makeText(this, R.string.profiling_manual_started, Toast.LENGTH_SHORT).show()
124161
} else {
125162
Sentry.stopProfiler()
126163
manualProfilingActive = false
127164
profileFinished = true
128-
binding.profilingStartManual.text = getString(R.string.profiling_start_manual)
165+
binding.profilingStartTransactionManual.text = getString(R.string.profiling_start_manual)
129166
binding.profilingProgressBar.visibility = View.GONE
130167

131-
binding.profilingResult.text = getString(R.string.profiling_manual_stopped)
168+
Toast.makeText(this, R.string.profiling_manual_stopped, Toast.LENGTH_SHORT).show()
132169
}
133170
}
134171

@@ -176,9 +213,10 @@ class ProfilingActivity : AppCompatActivity() {
176213
val bos = ByteArrayOutputStream()
177214
GZIPOutputStream(bos).bufferedWriter().use { it.write(String(itemData)) }
178215

216+
lastProfilingResult =
217+
getString(R.string.profiling_result, profileLength, itemData.size, bos.toByteArray().size)
179218
binding.root.post {
180-
binding.profilingResult.text =
181-
getString(R.string.profiling_result, profileLength, itemData.size, bos.toByteArray().size)
219+
Toast.makeText(this, "Profile captured — tap (i) for details", Toast.LENGTH_SHORT).show()
182220
}
183221
} catch (e: Exception) {
184222
e.printStackTrace()

sentry-samples/sentry-samples-android/src/main/java/io/sentry/samples/android/ProfilingListAdapter.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import android.graphics.Color
55
import android.view.LayoutInflater
66
import android.view.ViewGroup
77
import android.widget.ImageView
8+
import android.widget.TextView
89
import androidx.recyclerview.widget.RecyclerView
910
import io.sentry.samples.android.databinding.ProfilingItemListBinding
1011
import kotlin.random.Random
@@ -17,6 +18,7 @@ class ProfilingListAdapter : RecyclerView.Adapter<ViewHolder>() {
1718
}
1819

1920
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
21+
holder.indexView.text = "${position + 1}"
2022
holder.imageView.setImageBitmap(generateBitmap())
2123
}
2224

@@ -37,5 +39,6 @@ class ProfilingListAdapter : RecyclerView.Adapter<ViewHolder>() {
3739
}
3840

3941
class ViewHolder(binding: ProfilingItemListBinding) : RecyclerView.ViewHolder(binding.root) {
42+
val indexView: TextView = binding.benchmarkItemListIndex
4043
val imageView: ImageView = binding.benchmarkItemListImage
4144
}

sentry-samples/sentry-samples-android/src/main/res/layout/activity_profiling.xml

Lines changed: 29 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,33 @@
33
xmlns:tools="http://schemas.android.com/tools"
44
android:orientation="vertical"
55
android:layout_width="match_parent"
6-
android:layout_height="match_parent">
6+
android:layout_height="match_parent"
7+
android:fitsSystemWindows="true">
8+
9+
<LinearLayout
10+
android:layout_width="match_parent"
11+
android:layout_height="wrap_content"
12+
android:orientation="horizontal"
13+
android:gravity="center"
14+
android:paddingTop="8dp"
15+
android:paddingBottom="4dp">
16+
17+
<TextView
18+
android:id="@+id/profiling_status"
19+
android:layout_width="wrap_content"
20+
android:layout_height="wrap_content"
21+
android:textStyle="bold" />
22+
23+
<ImageButton
24+
android:id="@+id/profiling_info"
25+
android:layout_width="32dp"
26+
android:layout_height="32dp"
27+
android:layout_marginStart="4dp"
28+
android:src="@android:drawable/ic_dialog_info"
29+
android:background="?android:attr/selectableItemBackgroundBorderless"
30+
android:contentDescription="@string/profiling_config_title" />
31+
32+
</LinearLayout>
733

834
<TextView
935
android:id="@+id/profiling_duration_text"
@@ -31,36 +57,20 @@
3157
android:layout_height="wrap_content"
3258
android:max="4" />
3359

34-
<TextView
35-
android:id="@+id/profiling_result"
36-
android:layout_width="match_parent"
37-
android:layout_height="wrap_content"
38-
android:gravity="center"
39-
android:text="@string/profiling_result" />
40-
41-
<TextView
42-
android:id="@+id/profiling_status"
43-
android:layout_width="match_parent"
44-
android:layout_height="wrap_content"
45-
android:gravity="center"
46-
android:textStyle="bold"
47-
android:paddingTop="8dp"
48-
android:paddingBottom="4dp" />
49-
5060
<LinearLayout
5161
android:layout_width="match_parent"
5262
android:layout_height="wrap_content"
5363
android:orientation="horizontal"
5464
android:gravity="center">
5565

5666
<Button
57-
android:id="@+id/profiling_start"
67+
android:id="@+id/profiling_start_transaction"
5868
android:layout_width="wrap_content"
5969
android:layout_height="wrap_content"
6070
android:text="@string/profiling_start" />
6171

6272
<Button
63-
android:id="@+id/profiling_start_manual"
73+
android:id="@+id/profiling_start_transaction_manual"
6474
android:layout_width="wrap_content"
6575
android:layout_height="wrap_content"
6676
android:text="@string/profiling_start_manual" />

sentry-samples/sentry-samples-android/src/main/res/layout/profiling_item_list.xml

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,20 @@
44
android:layout_width="match_parent"
55
android:layout_height="wrap_content">
66

7+
<TextView
8+
android:id="@+id/benchmark_item_list_index"
9+
android:layout_width="wrap_content"
10+
android:layout_height="wrap_content"
11+
android:layout_gravity="center_vertical"
12+
android:paddingStart="8dp"
13+
android:paddingEnd="4dp"
14+
android:textStyle="bold"
15+
tools:text="42" />
16+
717
<ImageView
818
android:id="@+id/benchmark_item_list_image"
9-
android:layout_width="match_parent"
19+
android:layout_width="0dp"
20+
android:layout_weight="1"
1021
android:layout_height="wrap_content"
1122
android:scaleType="centerCrop"
1223
android:layout_margin="8dp"

sentry-samples/sentry-samples-android/src/main/res/values/strings.xml

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,14 +48,16 @@ Nulla interdum gravida augue, vel fringilla lorem bibendum vel. In hac habitasse
4848
<string name="camera_permission">Camera Permission</string>
4949
<string name="write_permission">Write External Storage Permission</string>
5050
<string name="profiling_duration">Duration of profile %.1f seconds</string>
51-
<string name="profiling_threads">Background threads to use: %d</string>
51+
<string name="profiling_threads">CPU load threads (fibonacci): %d</string>
5252
<string name="profiling_running">Profiling is running</string>
5353
<string name="profiling_no_dir_set">No profiling dir path set</string>
5454
<string name="profiling_start">Start (Transaction)</string>
5555
<string name="profiling_start_manual">Start (Manual)</string>
5656
<string name="profiling_stop_manual">Stop (Manual)</string>
57-
<string name="profiling_status_perfetto">Profiler: Perfetto (ProfilingManager API 35+)</string>
58-
<string name="profiling_status_legacy">Profiler: Legacy (simpleperf)</string>
57+
<string name="profiling_status_perfetto">Active Profiler: Perfetto (ProfilingManager)</string>
58+
<string name="profiling_status_legacy">Active Profiler: Legacy (simpleperf)</string>
59+
<string name="profiling_status_none">Active Profiler: None (disabled)</string>
60+
<string name="profiling_config_title">Profiling Configuration</string>
5961
<string name="profiling_manual_started">Manual profiler started</string>
6062
<string name="profiling_manual_stopped">Manual profiler stopped — chunk sent to Sentry</string>
6163
<string name="profiling_result">Profile trace file size = %d bytes \nItem payload size = %d bytes \nData sent to Sentry size = %d bytes</string>

0 commit comments

Comments
 (0)