Add framework event infra

Summary:
Added infra for collecting events from UI frameworks.
1. Framework event metadata captures all the static metadata around the event. This allows to us to not send the same metadata in every event as well as populate the monitoring drop down immediately. This is sent in init since this information is static
2. Framework event itself is quite bare at the moment. It will have thread and more attributes in the future

The UIdebugger litho support ulitity has been simplified now there are 3 extension points.

Context renamed to UIDContext since it is referenced in app initialisers where the android context is also imported and it create a naming collision

Reviewed By: lblasa

Differential Revision: D42606933

fbshipit-source-id: a419f3fd424c533d586813004c40b68feafd9a2e
This commit is contained in:
Luke De Feo
2023-03-01 08:49:49 -08:00
committed by Facebook GitHub Bot
parent 39b14fc428
commit 914b32c383
13 changed files with 82 additions and 54 deletions

View File

@@ -7,13 +7,10 @@
package com.facebook.flipper.plugins.uidebugger.litho
import com.facebook.flipper.plugins.uidebugger.descriptors.DescriptorRegister
import com.facebook.flipper.plugins.uidebugger.observers.TreeObserverFactory
import com.facebook.flipper.plugins.uidebugger.core.UIDContext
// this is not used internally
object UIDebuggerLithoSupport {
fun addDescriptors(register: DescriptorRegister) {}
fun addObserver(observerFactory: TreeObserverFactory) {}
fun enable(context: UIDContext) {}
}

View File

@@ -22,6 +22,7 @@ import com.facebook.flipper.plugins.network.NetworkFlipperPlugin;
import com.facebook.flipper.plugins.sharedpreferences.SharedPreferencesFlipperPlugin;
import com.facebook.flipper.plugins.sharedpreferences.SharedPreferencesFlipperPlugin.SharedPreferencesDescriptor;
import com.facebook.flipper.plugins.uidebugger.UIDebuggerFlipperPlugin;
import com.facebook.flipper.plugins.uidebugger.core.UIDContext;
import com.facebook.flipper.plugins.uidebugger.descriptors.DescriptorRegister;
import com.facebook.flipper.plugins.uidebugger.litho.UIDebuggerLithoSupport;
import com.facebook.flipper.plugins.uidebugger.observers.TreeObserverFactory;
@@ -62,12 +63,10 @@ public final class FlipperInitializer {
DescriptorRegister descriptorRegister = DescriptorRegister.Companion.withDefaults();
TreeObserverFactory treeObserverFactory = TreeObserverFactory.Companion.withDefaults();
UIDebuggerLithoSupport.INSTANCE.addDescriptors(descriptorRegister);
UIDebuggerLithoSupport.INSTANCE.addObserver(treeObserverFactory);
UIDContext uidContext = UIDContext.Companion.create((Application) context);
UIDebuggerLithoSupport.INSTANCE.enable(uidContext);
client.addPlugin(
new UIDebuggerFlipperPlugin(
(Application) context, descriptorRegister, treeObserverFactory));
client.addPlugin(new UIDebuggerFlipperPlugin(uidContext));
client.start();
final OkHttpClient okHttpClient =

View File

@@ -7,33 +7,19 @@
package com.facebook.flipper.plugins.uidebugger
import android.app.Application
import android.util.Log
import com.facebook.flipper.core.FlipperConnection
import com.facebook.flipper.core.FlipperPlugin
import com.facebook.flipper.plugins.uidebugger.core.*
import com.facebook.flipper.plugins.uidebugger.descriptors.ApplicationRefDescriptor
import com.facebook.flipper.plugins.uidebugger.descriptors.DescriptorRegister
import com.facebook.flipper.plugins.uidebugger.descriptors.MetadataRegister
import com.facebook.flipper.plugins.uidebugger.model.InitEvent
import com.facebook.flipper.plugins.uidebugger.model.MetadataUpdateEvent
import com.facebook.flipper.plugins.uidebugger.observers.TreeObserverFactory
import kotlinx.serialization.json.Json
const val LogTag = "ui-debugger"
class UIDebuggerFlipperPlugin(
val application: Application,
descriptorRegister: DescriptorRegister?,
observerFactory: TreeObserverFactory?
) : FlipperPlugin {
private val context: Context =
Context(
ApplicationRef(application),
ConnectionRef(null),
descriptorRegister = descriptorRegister ?: DescriptorRegister.withDefaults(),
observerFactory = observerFactory ?: TreeObserverFactory.withDefaults())
class UIDebuggerFlipperPlugin(val context: UIDContext) : FlipperPlugin {
init {
Log.i(LogTag, "Initializing ui-debugger")
@@ -53,7 +39,9 @@ class UIDebuggerFlipperPlugin(
InitEvent.name,
Json.encodeToString(
InitEvent.serializer(),
InitEvent(ApplicationRefDescriptor.getId(context.applicationRef))))
InitEvent(
ApplicationRefDescriptor.getId(context.applicationRef),
context.frameworkEventMetadata)))
connection.send(
MetadataUpdateEvent.name,

View File

@@ -11,10 +11,10 @@ import com.facebook.flipper.core.FlipperObject
import com.facebook.flipper.core.FlipperReceiver
import com.facebook.flipper.core.FlipperResponder
import com.facebook.flipper.plugins.common.MainThreadFlipperReceiver
import com.facebook.flipper.plugins.uidebugger.core.Context
import com.facebook.flipper.plugins.uidebugger.core.UIDContext
/** An interface for extensions to the UIDebugger plugin */
abstract class Command(val context: Context) {
abstract class Command(val context: UIDContext) {
/** The command identifier to respond to */
abstract fun identifier(): String
/** Execute the command */

View File

@@ -7,19 +7,22 @@
package com.facebook.flipper.plugins.uidebugger.core
import android.app.Application
import com.facebook.flipper.core.FlipperConnection
import com.facebook.flipper.plugins.uidebugger.common.BitmapPool
import com.facebook.flipper.plugins.uidebugger.descriptors.DescriptorRegister
import com.facebook.flipper.plugins.uidebugger.model.FrameworkEventMetadata
import com.facebook.flipper.plugins.uidebugger.observers.TreeObserverFactory
import com.facebook.flipper.plugins.uidebugger.observers.TreeObserverManager
import com.facebook.flipper.plugins.uidebugger.scheduler.SharedThrottle
import com.facebook.flipper.plugins.uidebugger.traversal.PartialLayoutTraversal
data class Context(
data class UIDContext(
val applicationRef: ApplicationRef,
val connectionRef: ConnectionRef,
val descriptorRegister: DescriptorRegister,
val observerFactory: TreeObserverFactory,
val frameworkEventMetadata: MutableList<FrameworkEventMetadata>
) {
val layoutTraversal: PartialLayoutTraversal =
PartialLayoutTraversal(descriptorRegister, observerFactory)
@@ -27,6 +30,17 @@ data class Context(
val treeObserverManager = TreeObserverManager(this)
val sharedThrottle: SharedThrottle = SharedThrottle()
val bitmapPool = BitmapPool()
companion object {
fun create(application: Application): UIDContext {
return UIDContext(
ApplicationRef(application),
ConnectionRef(null),
descriptorRegister = DescriptorRegister.withDefaults(),
observerFactory = TreeObserverFactory.withDefaults(),
frameworkEventMetadata = mutableListOf())
}
}
}
data class ConnectionRef(var connection: FlipperConnection?)

View File

@@ -10,9 +10,7 @@ package com.facebook.flipper.plugins.uidebugger.model
import com.facebook.flipper.plugins.uidebugger.descriptors.Id
@kotlinx.serialization.Serializable
data class InitEvent(
val rootId: Id,
) {
data class InitEvent(val rootId: Id, val frameworkEventMetadata: List<FrameworkEventMetadata>) {
companion object {
const val name = "init"
}
@@ -31,7 +29,8 @@ data class SubtreeUpdateEvent(
val observerType: String,
val rootId: Id,
val nodes: List<Node>,
val snapshot: String? = null
val snapshot: String?,
val frameworkEvents: List<FrameworkEvent>?
) {
companion object {
const val name = "subtreeUpdate"

View File

@@ -0,0 +1,23 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.flipper.plugins.uidebugger.model
import com.facebook.flipper.plugins.uidebugger.descriptors.Id
@kotlinx.serialization.Serializable
data class FrameworkEventMetadata(
val type: String,
val documentation: String,
)
@kotlinx.serialization.Serializable
data class FrameworkEvent(
val nodeId: Id,
val type: String,
val timestamp: Long,
)

View File

@@ -11,15 +11,15 @@ import android.util.Log
import android.view.View
import com.facebook.flipper.plugins.uidebugger.LogTag
import com.facebook.flipper.plugins.uidebugger.core.ApplicationRef
import com.facebook.flipper.plugins.uidebugger.core.Context
import com.facebook.flipper.plugins.uidebugger.core.RootViewResolver
import com.facebook.flipper.plugins.uidebugger.core.UIDContext
import com.facebook.flipper.plugins.uidebugger.util.objectIdentity
/**
* Responsible for observing the activity stack and managing the subscription to the top most
* content view (decor view)
*/
class ApplicationTreeObserver(val context: Context) : TreeObserver<ApplicationRef>() {
class ApplicationTreeObserver(val context: UIDContext) : TreeObserver<ApplicationRef>() {
override val type = "Application"

View File

@@ -12,14 +12,14 @@ import android.view.View
import android.view.ViewTreeObserver
import com.facebook.flipper.plugins.uidebugger.LogTag
import com.facebook.flipper.plugins.uidebugger.common.BitmapPool
import com.facebook.flipper.plugins.uidebugger.core.Context
import com.facebook.flipper.plugins.uidebugger.core.UIDContext
import com.facebook.flipper.plugins.uidebugger.util.objectIdentity
import java.lang.ref.WeakReference
typealias DecorView = View
/** Responsible for subscribing to updates to the content view of an activity */
class DecorViewObserver(val context: Context) : TreeObserver<DecorView>() {
class DecorViewObserver(val context: UIDContext) : TreeObserver<DecorView>() {
private var nodeRef: WeakReference<View>? = null
private var preDrawListener: ViewTreeObserver.OnPreDrawListener? = null
@@ -78,7 +78,7 @@ object DecorViewTreeObserverBuilder : TreeObserverBuilder<DecorView> {
return node.javaClass.simpleName.contains("DecorView")
}
override fun build(context: Context): TreeObserver<DecorView> {
override fun build(context: UIDContext): TreeObserver<DecorView> {
Log.i(LogTag, "Building DecorView observer")
return DecorViewObserver(context)
}

View File

@@ -10,9 +10,10 @@ package com.facebook.flipper.plugins.uidebugger.observers
import android.util.Log
import com.facebook.flipper.plugins.uidebugger.LogTag
import com.facebook.flipper.plugins.uidebugger.common.BitmapPool
import com.facebook.flipper.plugins.uidebugger.core.Context
import com.facebook.flipper.plugins.uidebugger.core.UIDContext
import com.facebook.flipper.plugins.uidebugger.descriptors.Id
import com.facebook.flipper.plugins.uidebugger.descriptors.NodeDescriptor
import com.facebook.flipper.plugins.uidebugger.model.FrameworkEvent
import com.facebook.flipper.plugins.uidebugger.util.objectIdentity
/*
@@ -40,9 +41,10 @@ abstract class TreeObserver<T> {
/** Traverses the layout hierarchy while managing any encountered child observers. */
fun traverseAndSend(
context: Context,
context: UIDContext,
root: Any,
snapshotBitmap: BitmapPool.ReusableBitmap? = null
snapshotBitmap: BitmapPool.ReusableBitmap? = null,
frameworkEvents: List<FrameworkEvent>? = null
) {
val startTimestamp = System.currentTimeMillis()
val (visitedNodes, observableRoots) = context.layoutTraversal.traverse(root)
@@ -97,6 +99,7 @@ abstract class TreeObserver<T> {
startTimestamp,
traversalCompleteTime,
snapshotCompleteTime,
frameworkEvents,
snapshotBitmap))
}

View File

@@ -7,11 +7,11 @@
package com.facebook.flipper.plugins.uidebugger.observers
import com.facebook.flipper.plugins.uidebugger.core.Context
import com.facebook.flipper.plugins.uidebugger.core.UIDContext
interface TreeObserverBuilder<T> {
fun canBuildFor(node: Any): Boolean
fun build(context: Context): TreeObserver<T>
fun build(context: UIDContext): TreeObserver<T>
}
class TreeObserverFactory {
@@ -28,7 +28,7 @@ class TreeObserverFactory {
}
// TODO: Not very efficient, need to cache this. Builders cannot be removed.
fun createObserver(node: Any, context: Context): TreeObserver<*>? {
fun createObserver(node: Any, context: UIDContext): TreeObserver<*>? {
return builders.find { it.canBuildFor(node) }?.build(context)
}

View File

@@ -16,9 +16,10 @@ import android.util.Log
import android.view.Choreographer
import com.facebook.flipper.plugins.uidebugger.LogTag
import com.facebook.flipper.plugins.uidebugger.common.BitmapPool
import com.facebook.flipper.plugins.uidebugger.core.Context
import com.facebook.flipper.plugins.uidebugger.core.UIDContext
import com.facebook.flipper.plugins.uidebugger.descriptors.Id
import com.facebook.flipper.plugins.uidebugger.descriptors.MetadataRegister
import com.facebook.flipper.plugins.uidebugger.model.FrameworkEvent
import com.facebook.flipper.plugins.uidebugger.model.MetadataUpdateEvent
import com.facebook.flipper.plugins.uidebugger.model.Node
import com.facebook.flipper.plugins.uidebugger.model.PerfStatsEvent
@@ -37,13 +38,14 @@ data class SubtreeUpdate(
val startTime: Long,
val traversalCompleteTime: Long,
val snapshotComplete: Long,
val frameworkEvents: List<FrameworkEvent>?,
val snapshot: BitmapPool.ReusableBitmap?
)
data class BatchedUpdate(val updates: List<SubtreeUpdate>, val frameTimeMs: Long)
/** Holds the root observer and manages sending updates to desktop */
class TreeObserverManager(val context: Context) {
class TreeObserverManager(val context: UIDContext) {
private val rootObserver = ApplicationTreeObserver(context)
private lateinit var batchedUpdates: Channel<BatchedUpdate>
@@ -104,6 +106,7 @@ class TreeObserverManager(val context: Context) {
val onWorkerThread = System.currentTimeMillis()
val nodes = batchedUpdate.updates.flatMap { it.deferredNodes.map { it.value() } }
val frameworkEvents = batchedUpdate.updates.flatMap { it.frameworkEvents ?: listOf() }
val snapshotUpdate = batchedUpdate.updates.find { it.snapshot != null }
val deferredComptationComplete = System.currentTimeMillis()
@@ -116,13 +119,20 @@ class TreeObserverManager(val context: Context) {
snapshotUpdate.snapshot.readyForReuse()
}
// it is important this comes after deferred processing since the deferred processing can create
// metadata
sendMetadata()
val serialized =
Json.encodeToString(
SubtreeUpdateEvent.serializer(),
SubtreeUpdateEvent(
batchedUpdate.frameTimeMs, "batched", snapshotUpdate?.rootId ?: 1, nodes, snapshot))
batchedUpdate.frameTimeMs,
"batched",
snapshotUpdate?.rootId ?: 1,
nodes,
snapshot,
frameworkEvents))
val serializationEnd = System.currentTimeMillis()

View File

@@ -8,8 +8,7 @@
package com.facebook.flipper.plugins.uidebugger
import com.facebook.flipper.plugins.uidebugger.core.ApplicationRef
import com.facebook.flipper.plugins.uidebugger.descriptors.DescriptorRegister
import com.facebook.flipper.plugins.uidebugger.observers.TreeObserverFactory
import com.facebook.flipper.plugins.uidebugger.core.UIDContext
import org.junit.Assert
import org.junit.Before
import org.junit.Test
@@ -32,11 +31,7 @@ class UIDebuggerFlipperPluginTest {
@Throws(Exception::class)
@Test
fun emptyTest() {
var plugin =
UIDebuggerFlipperPlugin(
app,
DescriptorRegister.Companion.withDefaults(),
TreeObserverFactory.Companion.withDefaults())
var plugin = UIDebuggerFlipperPlugin(UIDContext.create(app))
Assert.assertNotNull(plugin)
}
}