From 3ce36619365df5b30bbf6b76c2ddfbb3bd688c48 Mon Sep 17 00:00:00 2001 From: Manoel Aranda Neto Date: Fri, 11 Jun 2021 14:38:06 +0200 Subject: [PATCH 01/23] Feat: Perf. for fragments --- .../fragment/FragmentLifecycleIntegration.kt | 24 ++-- .../SentryFragmentLifecycleCallbacks.kt | 51 +++++++- .../sentry-samples-android/build.gradle.kts | 1 + .../src/main/AndroidManifest.xml | 2 + .../sentry/samples/android/MainActivity.java | 5 + .../sentry/samples/android/MyApplication.java | 2 +- .../samples/android/ThirdActivityFragment.kt | 41 +++++++ .../sentry/samples/android/ThirdFragment.kt | 12 ++ .../src/main/res/layout/activity_main.xml | 113 ++++++++++-------- .../res/layout/activity_third_fragment.xml | 13 ++ .../src/main/res/layout/third_fragment.xml | 10 ++ .../src/main/res/values/strings.xml | 1 + 12 files changed, 217 insertions(+), 58 deletions(-) create mode 100644 sentry-samples/sentry-samples-android/src/main/java/io/sentry/samples/android/ThirdActivityFragment.kt create mode 100644 sentry-samples/sentry-samples-android/src/main/java/io/sentry/samples/android/ThirdFragment.kt create mode 100644 sentry-samples/sentry-samples-android/src/main/res/layout/activity_third_fragment.xml create mode 100644 sentry-samples/sentry-samples-android/src/main/res/layout/third_fragment.xml diff --git a/sentry-android-fragment/src/main/java/io/sentry/android/fragment/FragmentLifecycleIntegration.kt b/sentry-android-fragment/src/main/java/io/sentry/android/fragment/FragmentLifecycleIntegration.kt index a265b66614..3d3235cae2 100644 --- a/sentry-android-fragment/src/main/java/io/sentry/android/fragment/FragmentLifecycleIntegration.kt +++ b/sentry-android-fragment/src/main/java/io/sentry/android/fragment/FragmentLifecycleIntegration.kt @@ -6,32 +6,37 @@ import android.app.Application.ActivityLifecycleCallbacks import android.os.Bundle import androidx.fragment.app.FragmentActivity import io.sentry.IHub -import io.sentry.ILogger import io.sentry.Integration import io.sentry.SentryLevel.DEBUG import io.sentry.SentryOptions import java.io.Closeable -class FragmentLifecycleIntegration(private val application: Application) : +// also add an options to record breadcrumbs or not +class FragmentLifecycleIntegration( + private val application: Application, + private val enableAutoFragmentLifecycleTracing: Boolean) : ActivityLifecycleCallbacks, Integration, Closeable { + // should it be enabled by default? + constructor(application: Application) : this(application, false) + private lateinit var hub: IHub - private lateinit var logger: ILogger + private lateinit var options: SentryOptions override fun register(hub: IHub, options: SentryOptions) { this.hub = hub - this.logger = options.logger + this.options = options application.registerActivityLifecycleCallbacks(this) - logger.log(DEBUG, "FragmentLifecycleIntegration installed.") + options.logger.log(DEBUG, "FragmentLifecycleIntegration installed.") } override fun close() { application.unregisterActivityLifecycleCallbacks(this) - if (::logger.isInitialized) { - logger.log(DEBUG, "FragmentLifecycleIntegration removed.") + if (::options.isInitialized) { + options.logger.log(DEBUG, "FragmentLifecycleIntegration removed.") } } @@ -39,7 +44,10 @@ class FragmentLifecycleIntegration(private val application: Application) : (activity as? FragmentActivity) ?.supportFragmentManager ?.registerFragmentLifecycleCallbacks( - SentryFragmentLifecycleCallbacks(hub), + SentryFragmentLifecycleCallbacks( + hub = hub, + performanceEnabled = options.isTracingEnabled, + enableAutoFragmentLifecycleTracing = enableAutoFragmentLifecycleTracing), true ) } diff --git a/sentry-android-fragment/src/main/java/io/sentry/android/fragment/SentryFragmentLifecycleCallbacks.kt b/sentry-android-fragment/src/main/java/io/sentry/android/fragment/SentryFragmentLifecycleCallbacks.kt index 55ad38e0d7..f1ac453843 100644 --- a/sentry-android-fragment/src/main/java/io/sentry/android/fragment/SentryFragmentLifecycleCallbacks.kt +++ b/sentry-android-fragment/src/main/java/io/sentry/android/fragment/SentryFragmentLifecycleCallbacks.kt @@ -9,13 +9,22 @@ import androidx.fragment.app.FragmentManager.FragmentLifecycleCallbacks import io.sentry.Breadcrumb import io.sentry.HubAdapter import io.sentry.IHub +import io.sentry.ISpan +import io.sentry.Sentry import io.sentry.SentryLevel.INFO +import io.sentry.SpanStatus @Suppress("TooManyFunctions") class SentryFragmentLifecycleCallbacks( - private val hub: IHub = HubAdapter.getInstance() + private val hub: IHub = HubAdapter.getInstance(), + performanceEnabled: Boolean = false, + enableAutoFragmentLifecycleTracing: Boolean = false ) : FragmentLifecycleCallbacks() { + private val isPerformanceEnabled = performanceEnabled && enableAutoFragmentLifecycleTracing + + private val fragmentsWithOngoingTransactions = mutableMapOf() + override fun onFragmentAttached( fragmentManager: FragmentManager, fragment: Fragment, @@ -38,6 +47,8 @@ class SentryFragmentLifecycleCallbacks( savedInstanceState: Bundle? ) { addBreadcrumb(fragment, "created") + + startTracing(fragment) } override fun onFragmentViewCreated( @@ -55,6 +66,9 @@ class SentryFragmentLifecycleCallbacks( override fun onFragmentResumed(fragmentManager: FragmentManager, fragment: Fragment) { addBreadcrumb(fragment, "resumed") + + // ideally it should be post resumed + stopTracing(fragment) } override fun onFragmentPaused(fragmentManager: FragmentManager, fragment: Fragment) { @@ -71,6 +85,8 @@ class SentryFragmentLifecycleCallbacks( override fun onFragmentDestroyed(fragmentManager: FragmentManager, fragment: Fragment) { addBreadcrumb(fragment, "destroyed") + + stopTracing(fragment) } override fun onFragmentDetached(fragmentManager: FragmentManager, fragment: Fragment) { @@ -91,4 +107,37 @@ class SentryFragmentLifecycleCallbacks( private fun getFragmentName(fragment: Fragment): String { return fragment.javaClass.simpleName } + + private fun isRunningSpan(fragment: Fragment): Boolean = + fragmentsWithOngoingTransactions.containsKey(fragment) + + private fun startTracing(fragment: Fragment) { + if (!isPerformanceEnabled || isRunningSpan(fragment)) { + return + } + + val currentSpan = Sentry.getSpan() + // or ui.load too? + // should be a span of the activity transaction or its own transaction? + val span = currentSpan?.startChild("fragment.load", getFragmentName(fragment)) + span?.let { + fragmentsWithOngoingTransactions.put(fragment, it) + } + } + + private fun stopTracing(fragment: Fragment) { + if (!isPerformanceEnabled || !isRunningSpan(fragment)) { + return + } + + val span = fragmentsWithOngoingTransactions[fragment] + span?.let { + var status = it.status + if (status == null) { + status = SpanStatus.OK + } + it.finish(status) + fragmentsWithOngoingTransactions.remove(fragment) + } + } } diff --git a/sentry-samples/sentry-samples-android/build.gradle.kts b/sentry-samples/sentry-samples-android/build.gradle.kts index a67076268e..38d4531dc4 100644 --- a/sentry-samples/sentry-samples-android/build.gradle.kts +++ b/sentry-samples/sentry-samples-android/build.gradle.kts @@ -93,6 +93,7 @@ dependencies { implementation(project(":sentry-android")) implementation(project(":sentry-android-okhttp")) implementation(project(":sentry-android-fragment")) + implementation(Config.Libs.fragment) // how to exclude androidx if release health feature is disabled // implementation(project(":sentry-android")) { diff --git a/sentry-samples/sentry-samples-android/src/main/AndroidManifest.xml b/sentry-samples/sentry-samples-android/src/main/AndroidManifest.xml index 2ed1391d49..9c58f983aa 100644 --- a/sentry-samples/sentry-samples-android/src/main/AndroidManifest.xml +++ b/sentry-samples/sentry-samples-android/src/main/AndroidManifest.xml @@ -33,6 +33,8 @@ + + diff --git a/sentry-samples/sentry-samples-android/src/main/java/io/sentry/samples/android/MainActivity.java b/sentry-samples/sentry-samples-android/src/main/java/io/sentry/samples/android/MainActivity.java index 820bf8a354..74bb1b8e4b 100644 --- a/sentry-samples/sentry-samples-android/src/main/java/io/sentry/samples/android/MainActivity.java +++ b/sentry-samples/sentry-samples-android/src/main/java/io/sentry/samples/android/MainActivity.java @@ -159,6 +159,11 @@ protected void onCreate(Bundle savedInstanceState) { binding.openSampleFragment.setOnClickListener( view -> SampleFragment.newInstance().show(getSupportFragmentManager(), null)); + binding.openThirdFragment.setOnClickListener( + view -> { + startActivity(new Intent(this, ThirdActivityFragment.class)); + }); + setContentView(binding.getRoot()); } diff --git a/sentry-samples/sentry-samples-android/src/main/java/io/sentry/samples/android/MyApplication.java b/sentry-samples/sentry-samples-android/src/main/java/io/sentry/samples/android/MyApplication.java index 810e4264fe..42f256c395 100644 --- a/sentry-samples/sentry-samples-android/src/main/java/io/sentry/samples/android/MyApplication.java +++ b/sentry-samples/sentry-samples-android/src/main/java/io/sentry/samples/android/MyApplication.java @@ -24,7 +24,7 @@ public void onCreate() { // return event; // }); // options.setAnrTimeoutIntervalMillis(2000); - options.addIntegration(new FragmentLifecycleIntegration(MyApplication.this)); + options.addIntegration(new FragmentLifecycleIntegration(MyApplication.this, true)); }); } diff --git a/sentry-samples/sentry-samples-android/src/main/java/io/sentry/samples/android/ThirdActivityFragment.kt b/sentry-samples/sentry-samples-android/src/main/java/io/sentry/samples/android/ThirdActivityFragment.kt new file mode 100644 index 0000000000..9eb74e5635 --- /dev/null +++ b/sentry-samples/sentry-samples-android/src/main/java/io/sentry/samples/android/ThirdActivityFragment.kt @@ -0,0 +1,41 @@ +package io.sentry.samples.android + +import android.os.Bundle +import androidx.appcompat.app.AppCompatActivity +import androidx.core.os.bundleOf +import androidx.fragment.app.add +import androidx.fragment.app.commit +import io.sentry.Sentry +import io.sentry.SpanStatus +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch + +class ThirdActivityFragment : AppCompatActivity(R.layout.activity_third_fragment) { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + if (savedInstanceState == null) { + val bundle = bundleOf("some_int" to 0) + supportFragmentManager.commit { + setReorderingAllowed(true) + add(R.id.fragment_container_view, args = bundle) + } + } + } + + override fun onResume() { + super.onResume() + // this is called before fragments are fully rendered, as they have their own lifecycle + // activity is only responsible of rendering the containers for fragments (FragmentContainerView) + // so I cant finish my transaction here manually. + // we'd need idle transactions here, I will simulate with a delayed coroutines + + GlobalScope.launch { + delay(1500L) + val span = Sentry.getSpan() + span?.finish(SpanStatus.OK) + } + } +} diff --git a/sentry-samples/sentry-samples-android/src/main/java/io/sentry/samples/android/ThirdFragment.kt b/sentry-samples/sentry-samples-android/src/main/java/io/sentry/samples/android/ThirdFragment.kt new file mode 100644 index 0000000000..0c7b3aa2a8 --- /dev/null +++ b/sentry-samples/sentry-samples-android/src/main/java/io/sentry/samples/android/ThirdFragment.kt @@ -0,0 +1,12 @@ +package io.sentry.samples.android + +import android.os.Bundle +import android.view.View +import androidx.fragment.app.Fragment + +class ThirdFragment : Fragment(R.layout.third_fragment) { + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + val someInt = requireArguments().getInt("some_int") + } +} \ No newline at end of file diff --git a/sentry-samples/sentry-samples-android/src/main/res/layout/activity_main.xml b/sentry-samples/sentry-samples-android/src/main/res/layout/activity_main.xml index 827228e4b6..c4ddea059f 100644 --- a/sentry-samples/sentry-samples-android/src/main/res/layout/activity_main.xml +++ b/sentry-samples/sentry-samples-android/src/main/res/layout/activity_main.xml @@ -6,70 +6,87 @@ android:orientation="vertical" tools:context=".MainActivity"> -