Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add profiling activity to Android sample app #2192

Merged
merged 3 commits into from
Aug 5, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,10 @@
android:name=".PermissionsActivity"
android:exported="false" />

<activity
android:name=".ProfilingActivity"
android:exported="false" />

<activity
android:name=".compose.ComposeActivity"
android:exported="false" />
Expand Down Expand Up @@ -100,7 +104,7 @@
<meta-data android:name="io.sentry.traces.sample-rate" android:value="1.0" />

<!-- how to enable profiling when starting transactions -->
<!-- <meta-data android:name="io.sentry.traces.profiling.sample-rate" android:value="1.0" />-->
<meta-data android:name="io.sentry.traces.profiling.sample-rate" android:value="1.0" />

<!-- how to disable the Activity auto instrumentation for tracing-->
<!-- <meta-data android:name="io.sentry.traces.activity.enable" android:value="false" />-->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,11 @@ protected void onCreate(Bundle savedInstanceState) {
startActivity(new Intent(this, ComposeActivity.class));
});

binding.openProfilingActivity.setOnClickListener(
view -> {
startActivity(new Intent(this, ProfilingActivity.class));
});

setContentView(binding.getRoot());
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
package io.sentry.samples.android

import android.os.Bundle
import android.view.View
import android.widget.SeekBar
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.recyclerview.widget.LinearLayoutManager
import io.sentry.ITransaction
import io.sentry.ProfilingTraceData
import io.sentry.Sentry
import io.sentry.SentryEnvelopeItem
import io.sentry.samples.android.databinding.ActivityProfilingBinding
import java.io.ByteArrayOutputStream
import java.io.File
import java.util.UUID
import java.util.concurrent.Executors
import java.util.zip.GZIPOutputStream

class ProfilingActivity : AppCompatActivity() {

private lateinit var binding: ActivityProfilingBinding
private val executors = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors())
private var profileFinished = false

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

binding = ActivityProfilingBinding.inflate(layoutInflater)

binding.profilingDurationSeekbar.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener {
override fun onProgressChanged(p0: SeekBar, p1: Int, p2: Boolean) {
val seconds = getProfileDuration(p0)
binding.profilingDurationText.text = getString(R.string.profiling_duration, seconds)
}
override fun onStartTrackingTouch(p0: SeekBar) {}
override fun onStopTrackingTouch(p0: SeekBar) {}
})
val initialDurationSeconds = getProfileDuration(binding.profilingDurationSeekbar)
binding.profilingDurationText.text = getString(R.string.profiling_duration, initialDurationSeconds)

binding.profilingThreadsSeekbar.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener {
override fun onProgressChanged(p0: SeekBar, p1: Int, p2: Boolean) {
val backgroundThreads = getBackgroundThreads(p0)
binding.profilingThreadsText.text = getString(R.string.profiling_threads, backgroundThreads)
}
override fun onStartTrackingTouch(p0: SeekBar) {}
override fun onStopTrackingTouch(p0: SeekBar) {}
})
val initialBackgroundThreads = getBackgroundThreads(binding.profilingThreadsSeekbar)
binding.profilingThreadsSeekbar.max = Runtime.getRuntime().availableProcessors() - 1
binding.profilingThreadsText.text = getString(R.string.profiling_threads, initialBackgroundThreads)

binding.profilingList.adapter = ProfilingListAdapter()
binding.profilingList.layoutManager = LinearLayoutManager(this)

binding.profilingStart.setOnClickListener {
binding.profilingProgressBar.visibility = View.VISIBLE
profileFinished = false
val seconds = getProfileDuration(binding.profilingDurationSeekbar)
val threads = getBackgroundThreads(binding.profilingThreadsSeekbar)
val t = Sentry.startTransaction("Profiling Test", "$seconds s - $threads threads")
repeat(threads) {
executors.submit { runMathOperations() }
}
executors.submit { swipeList() }
binding.profilingStart.postDelayed({ finishTransactionAndPrintResults(t) }, (seconds * 1000).toLong())
}
setContentView(binding.root)
}

private fun finishTransactionAndPrintResults(t: ITransaction) {
t.finish()
binding.profilingProgressBar.visibility = View.GONE
profileFinished = true
val profilesDirPath = Sentry.getCurrentHub().options.profilingTracesDirPath
if (profilesDirPath == null) {
Toast.makeText(this, R.string.profiling_running, Toast.LENGTH_SHORT).show()
return
}
// Get the last trace file, which is the current profile
val origProfileFile = File(profilesDirPath).listFiles()?.maxByOrNull { f -> f.lastModified() }
// Create a new profile file and copy the content of the original file into it
val profile = File(cacheDir, UUID.randomUUID().toString())
origProfileFile?.copyTo(profile)
val profileLength = profile.length()
val traceData = ProfilingTraceData(profile, t)
// Create envelope item from copied profile
val item =
SentryEnvelopeItem.fromProfilingTrace(traceData, Long.MAX_VALUE, Sentry.getCurrentHub().options.serializer)
val itemData = item.data

// Compress the envelope item using Gzip
val bos = ByteArrayOutputStream()
GZIPOutputStream(bos).bufferedWriter().use { it.write(String(itemData)) }

binding.profilingResult.text =
getString(R.string.profiling_result, profileLength, itemData.size, bos.toByteArray().size)
}

private fun swipeList() {
while (!profileFinished) {
if ((binding.profilingList.layoutManager as? LinearLayoutManager)?.findFirstVisibleItemPosition() == 0) {
binding.profilingList.smoothScrollToPosition(100)
} else {
binding.profilingList.smoothScrollToPosition(0)
}
Thread.sleep(3000)
}
}

private fun runMathOperations() {
while (!profileFinished) {
fibonacci(25)
}
}

private fun fibonacci(n: Int): Int {
return when {
profileFinished -> n // If we destroy the activity we stop this function
n <= 1 -> 1
else -> fibonacci(n - 1) + fibonacci(n - 2)
}
}

override fun onResume() {
super.onResume()
Sentry.getSpan()?.finish()
}

override fun onBackPressed() {
if (profileFinished) {
super.onBackPressed()
} else {
Toast.makeText(this, R.string.profiling_running, Toast.LENGTH_SHORT).show()
}
}

private fun getProfileDuration(s: SeekBar): Float {
// Minimum duration of the profile is 100 milliseconds
return s.progress / 10.0F + 0.1F
}

private fun getBackgroundThreads(s: SeekBar): Int {
// Minimum duration of the profile is 100 milliseconds
return s.progress.coerceIn(0, Runtime.getRuntime().availableProcessors() - 1)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package io.sentry.samples.android

import android.graphics.Bitmap
import android.graphics.Color
import android.view.LayoutInflater
import android.view.ViewGroup
import android.widget.ImageView
import androidx.recyclerview.widget.RecyclerView
import io.sentry.samples.android.databinding.ProfilingItemListBinding
import kotlin.random.Random

class ProfilingListAdapter : RecyclerView.Adapter<ViewHolder>() {

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val binding = ProfilingItemListBinding.inflate(LayoutInflater.from(parent.context), parent, false)
return ViewHolder(binding)
}

override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.imageView.setImageBitmap(generateBitmap())
}

@Suppress("MagicNumber")
private fun generateBitmap(): Bitmap {
val bitmapSize = 128
val colors = (0 until (bitmapSize * bitmapSize)).map {
Color.rgb(Random.nextInt(256), Random.nextInt(256), Random.nextInt(256))
}.toIntArray()
return Bitmap.createBitmap(colors, bitmapSize, bitmapSize, Bitmap.Config.ARGB_8888)
}

// Disables view recycling.
override fun getItemViewType(position: Int): Int = position

override fun getItemCount(): Int = 200
}

class ViewHolder(binding: ProfilingItemListBinding) : RecyclerView.ViewHolder(binding.root) {
val imageView: ImageView = binding.benchmarkItemListImage
}
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,12 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/open_compose_activity"/>

<Button
android:id="@+id/open_profiling_activity"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/open_profiling_activity"/>
</LinearLayout>

</ScrollView>
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">

<TextView
android:id="@+id/profiling_duration_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:text="@string/profiling_duration" />

<SeekBar
android:id="@+id/profiling_duration_seekbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:max="300" />

<TextView
android:id="@+id/profiling_threads_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:text="@string/profiling_threads" />

<SeekBar
android:id="@+id/profiling_threads_seekbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:max="4" />

<TextView
android:id="@+id/profiling_result"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:text="@string/profiling_result" />

<Button
android:id="@+id/profiling_start"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/profiling_start" />

<ProgressBar
android:id="@+id/profiling_progress_bar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone" />

<androidx.recyclerview.widget.RecyclerView
android:id="@+id/profiling_list"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:listitem="@layout/profiling_item_list" />

</LinearLayout>
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content">

<ImageView
android:id="@+id/benchmark_item_list_image"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:scaleType="centerCrop"
android:layout_margin="8dp"
tools:src="@android:color/darker_gray" />

</LinearLayout>
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
<string name="open_gestures_activity">Open Gestures Activity</string>
<string name="open_permissions_activity">Open Permissions Activity</string>
<string name="open_compose_activity">Open Compose Activity</string>
<string name="open_profiling_activity">Open Profiling Activity</string>
<string name="test_timber_integration">Test Timber</string>
<string name="back_main">Back to Main Activity</string>
<string name="tap_me">text</string>
Expand All @@ -34,4 +35,9 @@ Nulla interdum gravida augue, vel fringilla lorem bibendum vel. In hac habitasse
</string>
<string name="camera_permission">Camera Permission</string>
<string name="write_permission">Write External Storage Permission</string>
<string name="profiling_duration">Duration of profile %.1f seconds</string>
<string name="profiling_threads">Background threads to use: %d</string>
<string name="profiling_running">Profiling is running</string>
<string name="profiling_start">Start Profiling</string>
<string name="profiling_result">Profile trace file size = %d bytes \nItem payload size = %d bytes \nData sent to Sentry size = %d bytes</string>
</resources>