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 package com.facebook.flipper.plugins.uidebugger.litho
import com.facebook.flipper.plugins.uidebugger.descriptors.DescriptorRegister import com.facebook.flipper.plugins.uidebugger.core.UIDContext
import com.facebook.flipper.plugins.uidebugger.observers.TreeObserverFactory
// this is not used internally // this is not used internally
object UIDebuggerLithoSupport { object UIDebuggerLithoSupport {
fun addDescriptors(register: DescriptorRegister) {} fun enable(context: UIDContext) {}
fun addObserver(observerFactory: TreeObserverFactory) {}
} }

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

View File

@@ -7,33 +7,19 @@
package com.facebook.flipper.plugins.uidebugger package com.facebook.flipper.plugins.uidebugger
import android.app.Application
import android.util.Log import android.util.Log
import com.facebook.flipper.core.FlipperConnection import com.facebook.flipper.core.FlipperConnection
import com.facebook.flipper.core.FlipperPlugin import com.facebook.flipper.core.FlipperPlugin
import com.facebook.flipper.plugins.uidebugger.core.* import com.facebook.flipper.plugins.uidebugger.core.*
import com.facebook.flipper.plugins.uidebugger.descriptors.ApplicationRefDescriptor 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.descriptors.MetadataRegister
import com.facebook.flipper.plugins.uidebugger.model.InitEvent import com.facebook.flipper.plugins.uidebugger.model.InitEvent
import com.facebook.flipper.plugins.uidebugger.model.MetadataUpdateEvent import com.facebook.flipper.plugins.uidebugger.model.MetadataUpdateEvent
import com.facebook.flipper.plugins.uidebugger.observers.TreeObserverFactory
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
const val LogTag = "ui-debugger" const val LogTag = "ui-debugger"
class UIDebuggerFlipperPlugin( class UIDebuggerFlipperPlugin(val context: UIDContext) : FlipperPlugin {
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())
init { init {
Log.i(LogTag, "Initializing ui-debugger") Log.i(LogTag, "Initializing ui-debugger")
@@ -53,7 +39,9 @@ class UIDebuggerFlipperPlugin(
InitEvent.name, InitEvent.name,
Json.encodeToString( Json.encodeToString(
InitEvent.serializer(), InitEvent.serializer(),
InitEvent(ApplicationRefDescriptor.getId(context.applicationRef)))) InitEvent(
ApplicationRefDescriptor.getId(context.applicationRef),
context.frameworkEventMetadata)))
connection.send( connection.send(
MetadataUpdateEvent.name, 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.FlipperReceiver
import com.facebook.flipper.core.FlipperResponder import com.facebook.flipper.core.FlipperResponder
import com.facebook.flipper.plugins.common.MainThreadFlipperReceiver 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 */ /** 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 */ /** The command identifier to respond to */
abstract fun identifier(): String abstract fun identifier(): String
/** Execute the command */ /** Execute the command */

View File

@@ -7,19 +7,22 @@
package com.facebook.flipper.plugins.uidebugger.core package com.facebook.flipper.plugins.uidebugger.core
import android.app.Application
import com.facebook.flipper.core.FlipperConnection import com.facebook.flipper.core.FlipperConnection
import com.facebook.flipper.plugins.uidebugger.common.BitmapPool import com.facebook.flipper.plugins.uidebugger.common.BitmapPool
import com.facebook.flipper.plugins.uidebugger.descriptors.DescriptorRegister 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.TreeObserverFactory
import com.facebook.flipper.plugins.uidebugger.observers.TreeObserverManager import com.facebook.flipper.plugins.uidebugger.observers.TreeObserverManager
import com.facebook.flipper.plugins.uidebugger.scheduler.SharedThrottle import com.facebook.flipper.plugins.uidebugger.scheduler.SharedThrottle
import com.facebook.flipper.plugins.uidebugger.traversal.PartialLayoutTraversal import com.facebook.flipper.plugins.uidebugger.traversal.PartialLayoutTraversal
data class Context( data class UIDContext(
val applicationRef: ApplicationRef, val applicationRef: ApplicationRef,
val connectionRef: ConnectionRef, val connectionRef: ConnectionRef,
val descriptorRegister: DescriptorRegister, val descriptorRegister: DescriptorRegister,
val observerFactory: TreeObserverFactory, val observerFactory: TreeObserverFactory,
val frameworkEventMetadata: MutableList<FrameworkEventMetadata>
) { ) {
val layoutTraversal: PartialLayoutTraversal = val layoutTraversal: PartialLayoutTraversal =
PartialLayoutTraversal(descriptorRegister, observerFactory) PartialLayoutTraversal(descriptorRegister, observerFactory)
@@ -27,6 +30,17 @@ data class Context(
val treeObserverManager = TreeObserverManager(this) val treeObserverManager = TreeObserverManager(this)
val sharedThrottle: SharedThrottle = SharedThrottle() val sharedThrottle: SharedThrottle = SharedThrottle()
val bitmapPool = BitmapPool() 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?) 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 import com.facebook.flipper.plugins.uidebugger.descriptors.Id
@kotlinx.serialization.Serializable @kotlinx.serialization.Serializable
data class InitEvent( data class InitEvent(val rootId: Id, val frameworkEventMetadata: List<FrameworkEventMetadata>) {
val rootId: Id,
) {
companion object { companion object {
const val name = "init" const val name = "init"
} }
@@ -31,7 +29,8 @@ data class SubtreeUpdateEvent(
val observerType: String, val observerType: String,
val rootId: Id, val rootId: Id,
val nodes: List<Node>, val nodes: List<Node>,
val snapshot: String? = null val snapshot: String?,
val frameworkEvents: List<FrameworkEvent>?
) { ) {
companion object { companion object {
const val name = "subtreeUpdate" 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 android.view.View
import com.facebook.flipper.plugins.uidebugger.LogTag import com.facebook.flipper.plugins.uidebugger.LogTag
import com.facebook.flipper.plugins.uidebugger.core.ApplicationRef 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.RootViewResolver
import com.facebook.flipper.plugins.uidebugger.core.UIDContext
import com.facebook.flipper.plugins.uidebugger.util.objectIdentity import com.facebook.flipper.plugins.uidebugger.util.objectIdentity
/** /**
* Responsible for observing the activity stack and managing the subscription to the top most * Responsible for observing the activity stack and managing the subscription to the top most
* content view (decor view) * content view (decor view)
*/ */
class ApplicationTreeObserver(val context: Context) : TreeObserver<ApplicationRef>() { class ApplicationTreeObserver(val context: UIDContext) : TreeObserver<ApplicationRef>() {
override val type = "Application" override val type = "Application"

View File

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

View File

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

View File

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

View File

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

View File

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