Skip to content
Open
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
1 change: 1 addition & 0 deletions papa-dev/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -40,4 +40,5 @@ dependencies {
compileOnly(Dependencies.Build.AndroidXAnnotation)

implementation(project(":papa"))
implementation(Dependencies.AndroidXTracing)
}
12 changes: 0 additions & 12 deletions papa-dev/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,6 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">

<application>
<!-- A receiver that makes the build shell profileable on broadcast, disabled by default. -->
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These are unused.

<receiver
android:name="papa.internal.ForceShellProfileableReceiver"
android:enabled="true"
android:exported="true">
<intent-filter>
<!-- FORCE_TRACEABLE is deprecated - use FORCE_SHELL_PROFILEABLE instead -->
<action android:name="papa.FORCE_TRACEABLE" />
<action android:name="papa.FORCE_SHELL_PROFILEABLE" />
</intent-filter>
</receiver>

<!-- A receiver that triggers a GC on broadcast, disabled by default. -->
<receiver
android:name="papa.internal.GcTriggerReceiver"
Expand Down

This file was deleted.

4 changes: 2 additions & 2 deletions papa-dev/src/main/java/papa/internal/GcTriggerReceiver.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,14 @@ import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.util.Log
import papa.safeTrace
import androidx.tracing.trace

internal class GcTriggerReceiver : BroadcastReceiver() {
override fun onReceive(
context: Context,
intent: Intent
) {
safeTrace("force gc") {
trace("force gc") {
Log.d("GcTriggerReceiver", "Triggering GC")
gc()
context.sendBroadcast(Intent("papa.GC_TRIGGERED"))
Expand Down
3 changes: 1 addition & 2 deletions papa-safetrace/api/papa-safetrace.api
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,6 @@ public final class papa/SafeTrace {
public static final fun endAsyncSection (Lkotlin/jvm/functions/Function0;)V
public static synthetic fun endAsyncSection$default (Ljava/lang/String;IILjava/lang/Object;)V
public static final fun endSection ()V
public static final fun forceShellProfileable ()V
public static final fun forceTraceable ()V
public static final fun isCurrentlyTracing ()Z
public static final fun isShellProfileable ()Z
public static final fun isTraceable ()Z
Expand All @@ -27,6 +25,7 @@ public final class papa/SafeTraceFunctionsKt {
public final class papa/SafeTraceSetup {
public static final field INSTANCE Lpapa/SafeTraceSetup;
public final fun cleanUpMainThreadSectionName (Ljava/lang/String;)Ljava/lang/String;
public final fun enableMainThreadMessageTracing ()V
public final fun getMainThreadSectionNameMapper ()Lpapa/SectionNameMapper;
public final fun init (Landroid/app/Application;)V
public final fun setMainThreadSectionNameMapper (Lpapa/SectionNameMapper;)V
Expand Down
58 changes: 23 additions & 35 deletions papa-safetrace/src/main/java/papa/SafeTrace.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,7 @@ import android.content.pm.ApplicationInfo
import android.os.Build
import papa.SafeTrace.MAX_LABEL_LENGTH
import papa.SafeTrace.beginSection
import papa.SafeTrace.isCurrentlyTracing
import papa.SafeTrace.isShellProfileable
import papa.internal.SafeTraceMainThreadMessages

/**
* This is a wrapper for [androidx.tracing.Trace] that should be used instead as [beginSection] and
Expand All @@ -27,13 +25,6 @@ object SafeTrace {
* This is true if the app manifest has the debuggable to true or if it includes the
* `<profileable android:shell="true"/>` on API 29+, which indicate an intention for this build
* to be a special build that you want to profile.
*
* You can force this to be true by calling [forceShellProfileable], which
* will enable app tracing even on non-shell-profileable builds.
*
* You can also trigger [forceShellProfileable] at runtime by sending a
* `papa.FORCE_SHELL_PROFILEABLE` broadcast. To support this you should add a
* dependency on the papa-dev artifact.
*/
@JvmStatic
val isShellProfileable: Boolean
Expand All @@ -48,30 +39,18 @@ object SafeTrace {
* Whether we are currently tracing, which determines whether calls to
* tracing functions will be forwarded to the Android tracing APIs.
*/
@Deprecated("Use androidx.tracing.Trace.isEnabled() instead", ReplaceWith("Trace.isEnabled()", "androidx.tracing.Trace"))
@JvmStatic
val isCurrentlyTracing: Boolean
get() = androidx.tracing.Trace.isEnabled()

@Deprecated("Use forceShellProfileable instead", ReplaceWith("forceShellProfileable()"))
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was unused. Rather than keep a deprecated version of it, I think it makes more sense to remove it entirely.

@JvmStatic
fun forceTraceable() = forceShellProfileable()

/**
* @see isShellProfileable
*/
@JvmStatic
fun forceShellProfileable() {
androidx.tracing.Trace.forceEnableAppTracing()
_isTraceable = true
SafeTraceMainThreadMessages.enableMainThreadMessageTracing()
}

@Suppress("NOTHING_TO_INLINE")
private inline fun isShellProfileableInlined(): Boolean {
// Prior to SafeTraceSetup.initDone we can't determine if the app is traceable or not, so we
// always return false, unless something called forceTraceable(). The first call after
// SafeTraceSetup.initDone becomes true will compute the actual value based on debuggable
// and profileable manifest flags, then cache it so that we don't need to recheck again.
// always return false. The first call after SafeTraceSetup.initDone
// becomes true will compute the actual value based on debuggable and
// profileable manifest flags, then cache it so that we don't need to
// recheck again.
return _isTraceable
?: if (SafeTraceSetup.initDone) {
val application = SafeTraceSetup.application
Expand All @@ -93,17 +72,19 @@ object SafeTrace {
* Writes a trace message to indicate that a given section of code has begun. This call must
* be followed by a corresponding call to {@link #endSection()} on the same thread.
*/
@Deprecated("Call androidx.tracing.Trace.beginSection instead", ReplaceWith("beginSection", "androidx.tracing.Trace.beginSection"))
@JvmStatic
fun beginSection(label: String) {
if (!isCurrentlyTracing) {
if (!androidx.tracing.Trace.isEnabled()) {
return
}
androidx.tracing.Trace.beginSection(label.take(MAX_LABEL_LENGTH))
}

@Deprecated("Call androidx.tracing.Trace.beginSection instead", ReplaceWith("beginSection", "androidx.tracing.Trace.beginSection"))
Copy link
Contributor Author

@tcmulcahy tcmulcahy Oct 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

androidx.tracing.Trace.beginSection doesn't support a lambda, but we could provide an extension function for that.

@JvmStatic
inline fun beginSection(crossinline labelLambda: () -> String) {
if (!isCurrentlyTracing) {
if (!androidx.tracing.Trace.isEnabled()) {
return
}
androidx.tracing.Trace.beginSection(labelLambda().take(MAX_LABEL_LENGTH))
Expand All @@ -113,8 +94,9 @@ object SafeTrace {
* Begins and ends a section immediately. Useful for reporting information in the trace.
*/
@JvmStatic
@Deprecated("Call androidx.tracing.Trace.beginSection/endSection instead")
fun logSection(label: String) {
if (!isCurrentlyTracing) {
if (!androidx.tracing.Trace.isEnabled()) {
return
}
androidx.tracing.Trace.beginSection(label.take(MAX_LABEL_LENGTH))
Expand All @@ -124,9 +106,10 @@ object SafeTrace {
/**
* @see [logSection]
*/
@Deprecated("Call androidx.tracing.Trace.beginSection/endSection instead")
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

logSection doesn't exist in Jetpack, but we could provide an extension function androidx.tracing.Trace.logSection to do this.

@JvmStatic
inline fun logSection(crossinline labelLambda: () -> String) {
if (!isCurrentlyTracing) {
if (!androidx.tracing.Trace.isEnabled()) {
return
}
val label = labelLambda().take(MAX_LABEL_LENGTH)
Expand All @@ -141,9 +124,10 @@ object SafeTrace {
* ensure that beginSection / endSection pairs are properly nested and called from the same
* thread.
*/
@Deprecated("Call androidx.tracing.Trace.endSection instead", ReplaceWith("endSection", "androidx.tracing.Trace.endSection"))
@JvmStatic
fun endSection() {
if (!isCurrentlyTracing) {
if (!androidx.tracing.Trace.isEnabled()) {
return
}
androidx.tracing.Trace.endSection()
Expand All @@ -152,11 +136,12 @@ object SafeTrace {
/**
* @see androidx.tracing.Trace.beginAsyncSection
*/
@Deprecated("Call androidx.tracing.Trace.beginAsyncSection instead", ReplaceWith("beginAsyncSection", "androidx.tracing.Trace.beginAsyncSection"))
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

androidx.tracing.Trace.beginAsyncSection doesn't support passing a lambda, but we could provide an extension function for that.

@JvmStatic
inline fun beginAsyncSection(
crossinline labelCookiePairLambda: () -> Pair<String, Int>
) {
if (!isCurrentlyTracing) {
if (!androidx.tracing.Trace.isEnabled()) {
return
}
val (label, cookie) = labelCookiePairLambda()
Expand All @@ -167,12 +152,13 @@ object SafeTrace {
* [cookie] defaults to 0 (cookie is used for async traces that overlap)
* @see androidx.tracing.Trace.beginAsyncSection
*/
@Deprecated("Call androidx.tracing.Trace.beginAsyncSection instead", ReplaceWith("beginAsyncSection", "androidx.tracing.Trace.beginAsyncSection"))
@JvmStatic
fun beginAsyncSection(
label: String,
cookie: Int = 0
) {
if (!isCurrentlyTracing) {
if (!androidx.tracing.Trace.isEnabled()) {
return
}
androidx.tracing.Trace.beginAsyncSection(label, cookie)
Expand All @@ -182,12 +168,13 @@ object SafeTrace {
* [cookie] defaults to 0 (cookie is used for async traces that overlap)
* @see androidx.tracing.Trace.endAsyncSection
*/
@Deprecated("Call androidx.tracing.Trace.endAsyncSection instead", ReplaceWith("endAsyncSection", "androidx.tracing.Trace.endAsyncSection"))
@JvmStatic
fun endAsyncSection(
label: String,
cookie: Int = 0
) {
if (!isCurrentlyTracing) {
if (!androidx.tracing.Trace.isEnabled()) {
return
}
androidx.tracing.Trace.endAsyncSection(label, cookie)
Expand All @@ -196,11 +183,12 @@ object SafeTrace {
/**
* @see androidx.tracing.Trace.beginAsyncSection
*/
@Deprecated("Call androidx.tracing.Trace.endAsyncSection instead", ReplaceWith("endAsyncSection", "androidx.tracing.Trace.endAsyncSection"))
@JvmStatic
inline fun endAsyncSection(
crossinline labelCookiePairLambda: () -> Pair<String, Int>
) {
if (!isCurrentlyTracing) {
if (!androidx.tracing.Trace.isEnabled()) {
return
}
val (label, cookie) = labelCookiePairLambda()
Expand Down
4 changes: 4 additions & 0 deletions papa-safetrace/src/main/java/papa/SafeTraceFunctions.kt
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import papa.SafeTrace.isTraceable
* [label] a string producing lambda if the label is computed dynamically. If the label isn't
* dynamic, use the [safeTrace] which directly takes a string instead.
*/
@Deprecated("Call androidx.tracing.trace instead", ReplaceWith("trace", "androidx.tracing.trace"))
@Suppress("DEPRECATION")
inline fun <T> safeTrace(
crossinline label: () -> String,
crossinline block: () -> T
Expand All @@ -26,6 +28,8 @@ inline fun <T> safeTrace(
/**
* Allows tracing of a block of code
*/
@Deprecated("Call androidx.tracing.trace instead", ReplaceWith("trace", "androidx.tracing.trace"))
@Suppress("DEPRECATION")
inline fun <T> safeTrace(
label: String,
crossinline block: () -> T
Expand Down
4 changes: 4 additions & 0 deletions papa-safetrace/src/main/java/papa/SafeTraceSetup.kt
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ object SafeTraceSetup {

fun init(application: Application) {
this.application = application
enableMainThreadMessageTracing()
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we still need the application field here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I need it until I remove all callers of SafeTrace.isTraceable. Then I'll remove it.


fun enableMainThreadMessageTracing() {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since we're deprecating forceEnableAppTracing, we need to expose a way for callers to invoke enableMainThreadMessageTracing directly.

SafeTraceMainThreadMessages.enableMainThreadMessageTracing()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we probably ought to make a public API for this, but ideally something cohesive not a random function of a SafeTrace object that's not about safe traces anymore

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about I move SafeTraceMainThreadMessages out of the internal package and make it public?

}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ internal object SafeTraceMainThreadMessages {
}
}

@Suppress("DEPRECATION")
private fun enableOnMainThread() {
if (!enabled && SafeTrace.isShellProfileable && traceMainThreadMessages) {
enabled = true
Expand Down
1 change: 1 addition & 0 deletions papa/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ dependencies {

implementation(Dependencies.Curtains)
implementation(Dependencies.AndroidXCore)
implementation(Dependencies.AndroidXTracing)

testImplementation(Dependencies.JUnit)
testImplementation(Dependencies.Mockito)
Expand Down
31 changes: 13 additions & 18 deletions papa/src/main/java/papa/InteractionRuleClient.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package papa

import androidx.tracing.Trace
import papa.InteractionUpdated.CanceledOnEvent
import papa.InteractionUpdated.CanceledOnRuleRemoved
import papa.InteractionUpdated.CanceledOnTimeout
Expand Down Expand Up @@ -119,18 +120,16 @@ private class InteractionEngine<ParentEventType : Any>(
* called with an unknown [Runnable].
*/
private val cancelOnTimeout: Runnable = Runnable {
SafeTrace.logSection {
"PAPA-cancel:timeout"
}
Trace.beginSection("PAPA-cancel:timeout")
Trace.endSection()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why not use the lambda version with {} ?

or at least bring back logSection as helper function here?

stopRunning()
trace.endTrace()
updateListener.onInteractionUpdate(CanceledOnTimeout(cancelTimeout, this))
}

fun cancelOnRuleRemoved() {
SafeTrace.logSection {
"PAPA-cancel:ruleRemoved"
}
Trace.beginSection("PAPA-cancel:ruleRemoved")
Trace.endSection()
stopRunning()
trace.endTrace()
updateListener.onInteractionUpdate(CanceledOnRuleRemoved(this))
Expand All @@ -153,19 +152,17 @@ private class InteractionEngine<ParentEventType : Any>(

override fun cancel(reason: String) {
val sentEvent = eventInScope!!
SafeTrace.logSection {
"PAPA-cancel:${sentEvent.event}:$reason"
}
Trace.beginSection("PAPA-cancel:${sentEvent.event}:$reason")
Trace.endSection()
stopRunning()
trace.endTrace()
updateListener.onInteractionUpdate(CanceledOnEvent(sentEvent, this, reason))
}

override fun finish(): FinishingInteraction<ParentEventType> {
val sentEvent = eventInScope!!
SafeTrace.logSection {
"PAPA-finishInteraction:${sentEvent.event}"
}
Trace.beginSection("PAPA-finishInteraction:${sentEvent.event}")
Trace.endSection()
stopRunning()
finishingInteractions += this
addRecordedEvent()
Expand All @@ -188,9 +185,8 @@ private class InteractionEngine<ParentEventType : Any>(

override fun recordEvent() {
val sentEvent = eventInScope!!
SafeTrace.logSection {
"PAPA-recordEvent:${sentEvent.event}"
}
Trace.beginSection("PAPA-recordEvent:${sentEvent.event}")
Trace.endSection()
addRecordedEvent()
updateListener.onInteractionUpdate(EventRecorded(sentEvent, this))
}
Expand Down Expand Up @@ -230,9 +226,8 @@ private class InteractionEngine<ParentEventType : Any>(
trace: InteractionTrace,
cancelTimeout: Duration
): RunningInteraction<ParentEventType> {
SafeTrace.logSection {
"PAPA-startInteraction:${sentEvent.event}"
}
Trace.beginSection("PAPA-startInteraction:${sentEvent.event}")
Trace.endSection()
val runningInteraction = RealRunningInteraction(
interactionTrigger = trigger,
trace = trace,
Expand Down
6 changes: 4 additions & 2 deletions papa/src/main/java/papa/InteractionTrace.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package papa

import androidx.tracing.Trace

fun interface InteractionTrace {
fun endTrace()

Expand All @@ -8,9 +10,9 @@ fun interface InteractionTrace {
name: String
): InteractionTrace {
val cookie = System.nanoTime().rem(Int.MAX_VALUE).toInt()
SafeTrace.beginAsyncSection(name, cookie)
Trace.beginAsyncSection(name, cookie)
return InteractionTrace {
SafeTrace.endAsyncSection(name, cookie)
Trace.endAsyncSection(name, cookie)
}
}
}
Expand Down
Loading
Loading