diff --git a/android/plugins/litho/src/main/java/com/facebook/flipper/plugins/uidebugger/litho/descriptors/DebugComponentDescriptor.kt b/android/plugins/litho/src/main/java/com/facebook/flipper/plugins/uidebugger/litho/descriptors/DebugComponentDescriptor.kt index d88df74e4..035bcc698 100644 --- a/android/plugins/litho/src/main/java/com/facebook/flipper/plugins/uidebugger/litho/descriptors/DebugComponentDescriptor.kt +++ b/android/plugins/litho/src/main/java/com/facebook/flipper/plugins/uidebugger/litho/descriptors/DebugComponentDescriptor.kt @@ -15,6 +15,8 @@ import com.facebook.flipper.plugins.uidebugger.litho.descriptors.props.LayoutPro import com.facebook.flipper.plugins.uidebugger.model.Bounds import com.facebook.flipper.plugins.uidebugger.model.InspectableObject import com.facebook.flipper.plugins.uidebugger.model.MetadataId +import com.facebook.flipper.plugins.uidebugger.util.Deferred +import com.facebook.flipper.plugins.uidebugger.util.MaybeDeferred import com.facebook.litho.DebugComponent class DebugComponentDescriptor(val register: DescriptorRegister) : NodeDescriptor { @@ -67,19 +69,20 @@ class DebugComponentDescriptor(val register: DescriptorRegister) : NodeDescripto private val UserPropsId = MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, "Litho Props") - override fun getData(node: DebugComponent): Map { + override fun getData(node: DebugComponent): MaybeDeferred> { + return Deferred { + val attributeSections = mutableMapOf() - val attributeSections = mutableMapOf() + val layoutProps = LayoutPropExtractor.getProps(node) + attributeSections[LayoutId] = InspectableObject(layoutProps.toMap()) - val layoutProps = LayoutPropExtractor.getProps(node) - attributeSections[LayoutId] = InspectableObject(layoutProps.toMap()) + if (!node.canResolve()) { + val props = ComponentPropExtractor.getProps(node.component) + attributeSections[UserPropsId] = InspectableObject(props.toMap()) + } - if (!node.canResolve()) { - val props = ComponentPropExtractor.getProps(node.component) - attributeSections[UserPropsId] = InspectableObject(props.toMap()) + attributeSections } - - return attributeSections } override fun getBounds(node: DebugComponent): Bounds = diff --git a/android/src/main/java/com/facebook/flipper/plugins/uidebugger/core/NativeScanScheduler.kt b/android/src/main/java/com/facebook/flipper/plugins/uidebugger/core/NativeScanScheduler.kt index 582353bba..53eb74d07 100644 --- a/android/src/main/java/com/facebook/flipper/plugins/uidebugger/core/NativeScanScheduler.kt +++ b/android/src/main/java/com/facebook/flipper/plugins/uidebugger/core/NativeScanScheduler.kt @@ -19,13 +19,14 @@ import com.facebook.flipper.plugins.uidebugger.model.SubtreeUpdateEvent import com.facebook.flipper.plugins.uidebugger.observers.TreeObserverFactory import com.facebook.flipper.plugins.uidebugger.scheduler.Scheduler import com.facebook.flipper.plugins.uidebugger.traversal.PartialLayoutTraversal +import com.facebook.flipper.plugins.uidebugger.util.MaybeDeferred import kotlinx.serialization.json.Json data class ScanResult( val txId: Long, val scanStart: Long, val scanEnd: Long, - val nodes: List + val nodes: List> ) const val observerType = "FullScan" @@ -62,6 +63,9 @@ class NativeScanScheduler(val context: Context) : Scheduler.Task { } private fun sendSubtreeUpdate(input: ScanResult) { + val nodes = input.nodes.map { it.value() } + val deferredComputationComplete = System.currentTimeMillis() + val serialized = Json.encodeToString( SubtreeUpdateEvent.serializer(), @@ -69,7 +73,7 @@ class NativeScanScheduler(val context: Context) : Scheduler.Task { input.txId, observerType, ApplicationRefDescriptor.getId(context.applicationRef), - input.nodes)) + nodes)) val serializationEnd = System.currentTimeMillis() context.connectionRef.connection?.send( @@ -89,6 +93,7 @@ class NativeScanScheduler(val context: Context) : Scheduler.Task { input.scanStart, input.scanEnd, input.scanEnd, + deferredComputationComplete, serializationEnd, socketEnd, input.nodes.size))) diff --git a/android/src/main/java/com/facebook/flipper/plugins/uidebugger/descriptors/ChainedDescriptor.kt b/android/src/main/java/com/facebook/flipper/plugins/uidebugger/descriptors/ChainedDescriptor.kt index 164b4b686..42667e259 100644 --- a/android/src/main/java/com/facebook/flipper/plugins/uidebugger/descriptors/ChainedDescriptor.kt +++ b/android/src/main/java/com/facebook/flipper/plugins/uidebugger/descriptors/ChainedDescriptor.kt @@ -11,6 +11,8 @@ import android.graphics.Bitmap import com.facebook.flipper.plugins.uidebugger.model.Bounds import com.facebook.flipper.plugins.uidebugger.model.InspectableObject import com.facebook.flipper.plugins.uidebugger.model.MetadataId +import com.facebook.flipper.plugins.uidebugger.util.Immediate +import com.facebook.flipper.plugins.uidebugger.util.MaybeDeferred /** * A chained descriptor is a special type of descriptor that models the inheritance hierarchy in @@ -81,7 +83,7 @@ abstract class ChainedDescriptor : NodeDescriptor { open fun onGetChildren(node: T): List? = null - final override fun getData(node: T): Map { + final override fun getData(node: T): MaybeDeferred> { val builder = mutableMapOf() onGetData(node, builder) @@ -92,7 +94,7 @@ abstract class ChainedDescriptor : NodeDescriptor { curDescriptor = curDescriptor.mSuper } - return builder + return Immediate(builder) } /** diff --git a/android/src/main/java/com/facebook/flipper/plugins/uidebugger/descriptors/NodeDescriptor.kt b/android/src/main/java/com/facebook/flipper/plugins/uidebugger/descriptors/NodeDescriptor.kt index ac075a36f..af52ebf63 100644 --- a/android/src/main/java/com/facebook/flipper/plugins/uidebugger/descriptors/NodeDescriptor.kt +++ b/android/src/main/java/com/facebook/flipper/plugins/uidebugger/descriptors/NodeDescriptor.kt @@ -11,6 +11,7 @@ import android.graphics.Bitmap import com.facebook.flipper.plugins.uidebugger.model.Bounds import com.facebook.flipper.plugins.uidebugger.model.InspectableObject import com.facebook.flipper.plugins.uidebugger.model.MetadataId +import com.facebook.flipper.plugins.uidebugger.util.MaybeDeferred /* Descriptors are an extension point used during traversal to extract data out of arbitrary @@ -73,7 +74,7 @@ interface NodeDescriptor { * Get the data to show for this node in the sidebar of the inspector. The object will be shown in * order and with a header matching the given name. */ - fun getData(node: T): Map + fun getData(node: T): MaybeDeferred> /** * Set of tags to describe this node in an abstract way for the UI Unfortunately this can't be an diff --git a/android/src/main/java/com/facebook/flipper/plugins/uidebugger/descriptors/ObjectDescriptor.kt b/android/src/main/java/com/facebook/flipper/plugins/uidebugger/descriptors/ObjectDescriptor.kt index a909a4c1e..910a2b2e7 100644 --- a/android/src/main/java/com/facebook/flipper/plugins/uidebugger/descriptors/ObjectDescriptor.kt +++ b/android/src/main/java/com/facebook/flipper/plugins/uidebugger/descriptors/ObjectDescriptor.kt @@ -11,6 +11,7 @@ import android.graphics.Bitmap import com.facebook.flipper.plugins.uidebugger.model.Bounds import com.facebook.flipper.plugins.uidebugger.model.InspectableObject import com.facebook.flipper.plugins.uidebugger.model.MetadataId +import com.facebook.flipper.plugins.uidebugger.util.Immediate object ObjectDescriptor : NodeDescriptor { @@ -26,7 +27,7 @@ object ObjectDescriptor : NodeDescriptor { override fun getChildren(node: Any) = listOf() - override fun getData(node: Any) = mutableMapOf() + override fun getData(node: Any) = Immediate(mapOf()) override fun getBounds(node: Any): Bounds = Bounds(0, 0, 0, 0) diff --git a/android/src/main/java/com/facebook/flipper/plugins/uidebugger/descriptors/OffsetChildDescriptor.kt b/android/src/main/java/com/facebook/flipper/plugins/uidebugger/descriptors/OffsetChildDescriptor.kt index 04f57c17e..4278ca8e8 100644 --- a/android/src/main/java/com/facebook/flipper/plugins/uidebugger/descriptors/OffsetChildDescriptor.kt +++ b/android/src/main/java/com/facebook/flipper/plugins/uidebugger/descriptors/OffsetChildDescriptor.kt @@ -11,6 +11,7 @@ import android.graphics.Bitmap import com.facebook.flipper.plugins.uidebugger.model.Bounds import com.facebook.flipper.plugins.uidebugger.model.InspectableObject import com.facebook.flipper.plugins.uidebugger.model.MetadataId +import com.facebook.flipper.plugins.uidebugger.util.MaybeDeferred /** a drawable or view that is mounted, along with the correct descriptor */ class OffsetChild(val child: Any, val descriptor: NodeDescriptor, val x: Int, val y: Int) { @@ -37,7 +38,7 @@ object OffsetChildDescriptor : NodeDescriptor { override fun getActiveChild(node: OffsetChild): Any? = node.descriptor.getActiveChild(node.child) - override fun getData(node: OffsetChild): Map = + override fun getData(node: OffsetChild): MaybeDeferred> = node.descriptor.getData(node.child) override fun getTags(node: OffsetChild): Set = node.descriptor.getTags(node.child) diff --git a/android/src/main/java/com/facebook/flipper/plugins/uidebugger/model/Events.kt b/android/src/main/java/com/facebook/flipper/plugins/uidebugger/model/Events.kt index 6907441ca..ae6df92e5 100644 --- a/android/src/main/java/com/facebook/flipper/plugins/uidebugger/model/Events.kt +++ b/android/src/main/java/com/facebook/flipper/plugins/uidebugger/model/Events.kt @@ -58,6 +58,7 @@ data class PerfStatsEvent( val traversalComplete: Long, val snapshotComplete: Long, val queuingComplete: Long, + val deferredComputationComplete: Long, val serializationComplete: Long, val socketComplete: Long, val nodesCount: Int diff --git a/android/src/main/java/com/facebook/flipper/plugins/uidebugger/observers/TreeObserverManager.kt b/android/src/main/java/com/facebook/flipper/plugins/uidebugger/observers/TreeObserverManager.kt index 188f1158e..f5e1adfc8 100644 --- a/android/src/main/java/com/facebook/flipper/plugins/uidebugger/observers/TreeObserverManager.kt +++ b/android/src/main/java/com/facebook/flipper/plugins/uidebugger/observers/TreeObserverManager.kt @@ -23,6 +23,7 @@ import com.facebook.flipper.plugins.uidebugger.model.MetadataUpdateEvent import com.facebook.flipper.plugins.uidebugger.model.Node import com.facebook.flipper.plugins.uidebugger.model.PerfStatsEvent import com.facebook.flipper.plugins.uidebugger.model.SubtreeUpdateEvent +import com.facebook.flipper.plugins.uidebugger.util.MaybeDeferred import java.io.ByteArrayOutputStream import java.util.concurrent.atomic.AtomicInteger import kotlinx.coroutines.* @@ -37,7 +38,7 @@ data class CoordinateUpdate(val observerType: String, val nodeId: Id, val coordi data class SubtreeUpdate( val observerType: String, val rootId: Id, - val nodes: List, + val deferredNodes: List>, val startTime: Long, val traversalCompleteTime: Long, val snapshotComplete: Long, @@ -104,12 +105,13 @@ class TreeObserverManager(val context: Context) { sendMetadata() val serialized: String? + val nodes = treeUpdate.deferredNodes.map { it.value() } + val deferredComptationComplete = System.currentTimeMillis() if (treeUpdate.snapshot == null) { serialized = Json.encodeToString( SubtreeUpdateEvent.serializer(), - SubtreeUpdateEvent( - txId, treeUpdate.observerType, treeUpdate.rootId, treeUpdate.nodes)) + SubtreeUpdateEvent(txId, treeUpdate.observerType, treeUpdate.rootId, nodes)) } else { val stream = ByteArrayOutputStream() val base64Stream = Base64OutputStream(stream, Base64.DEFAULT) @@ -118,8 +120,7 @@ class TreeObserverManager(val context: Context) { serialized = Json.encodeToString( SubtreeUpdateEvent.serializer(), - SubtreeUpdateEvent( - txId, treeUpdate.observerType, treeUpdate.rootId, treeUpdate.nodes, snapshot)) + SubtreeUpdateEvent(txId, treeUpdate.observerType, treeUpdate.rootId, nodes, snapshot)) treeUpdate.snapshot.readyForReuse() } @@ -130,7 +131,7 @@ class TreeObserverManager(val context: Context) { val socketEnd = System.currentTimeMillis() Log.i( LogTag, - "Sent event for ${treeUpdate.observerType} root ID ${treeUpdate.rootId} nodes ${treeUpdate.nodes.size}") + "Sent event for ${treeUpdate.observerType} root ID ${treeUpdate.rootId} nodes ${nodes.size}") val perfStats = PerfStatsEvent( @@ -140,9 +141,10 @@ class TreeObserverManager(val context: Context) { traversalComplete = treeUpdate.traversalCompleteTime, snapshotComplete = treeUpdate.snapshotComplete, queuingComplete = onWorkerThread, + deferredComputationComplete = deferredComptationComplete, serializationComplete = serializationEnd, socketComplete = socketEnd, - nodesCount = treeUpdate.nodes.size) + nodesCount = nodes.size) context.connectionRef.connection?.send( PerfStatsEvent.name, Json.encodeToString(PerfStatsEvent.serializer(), perfStats)) diff --git a/android/src/main/java/com/facebook/flipper/plugins/uidebugger/traversal/PartialLayoutTraversal.kt b/android/src/main/java/com/facebook/flipper/plugins/uidebugger/traversal/PartialLayoutTraversal.kt index 26fd41dfc..1dde63542 100644 --- a/android/src/main/java/com/facebook/flipper/plugins/uidebugger/traversal/PartialLayoutTraversal.kt +++ b/android/src/main/java/com/facebook/flipper/plugins/uidebugger/traversal/PartialLayoutTraversal.kt @@ -14,6 +14,8 @@ import com.facebook.flipper.plugins.uidebugger.descriptors.Id import com.facebook.flipper.plugins.uidebugger.descriptors.NodeDescriptor import com.facebook.flipper.plugins.uidebugger.model.Node import com.facebook.flipper.plugins.uidebugger.observers.TreeObserverFactory +import com.facebook.flipper.plugins.uidebugger.util.Immediate +import com.facebook.flipper.plugins.uidebugger.util.MaybeDeferred /** * This will traverse the layout hierarchy until it sees a node that has an observer registered for @@ -29,9 +31,9 @@ class PartialLayoutTraversal( @Suppress("unchecked_cast") internal fun NodeDescriptor<*>.asAny(): NodeDescriptor = this as NodeDescriptor - fun traverse(root: Any): Pair, List> { + fun traverse(root: Any): Pair>, List> { - val visited = mutableListOf() + val visited = mutableListOf>() val observableRoots = mutableListOf() val stack = mutableListOf() @@ -53,15 +55,16 @@ class PartialLayoutTraversal( if (shallow.contains(node)) { visited.add( - Node( - descriptor.getId(node), - descriptor.getQualifiedName(node), - descriptor.getName(node), - emptyMap(), - descriptor.getBounds(node), - emptySet(), - emptyList(), - null)) + Immediate( + Node( + descriptor.getId(node), + descriptor.getQualifiedName(node), + descriptor.getName(node), + emptyMap(), + descriptor.getBounds(node), + emptySet(), + emptyList(), + null))) shallow.remove(node) continue @@ -93,15 +96,17 @@ class PartialLayoutTraversal( val bounds = descriptor.getBounds(node) val tags = descriptor.getTags(node) visited.add( - Node( - descriptor.getId(node), - descriptor.getQualifiedName(node), - descriptor.getName(node), - attributes, - bounds, - tags, - childrenIds, - activeChildId)) + attributes.map { attrs -> + Node( + descriptor.getId(node), + descriptor.getQualifiedName(node), + descriptor.getName(node), + attrs, + bounds, + tags, + childrenIds, + activeChildId) + }) } catch (exception: Exception) { Log.e(LogTag, "Error while processing node ${node.javaClass.name} $node", exception) } diff --git a/android/src/main/java/com/facebook/flipper/plugins/uidebugger/util/MaybeDeferred.kt b/android/src/main/java/com/facebook/flipper/plugins/uidebugger/util/MaybeDeferred.kt new file mode 100644 index 000000000..057a47209 --- /dev/null +++ b/android/src/main/java/com/facebook/flipper/plugins/uidebugger/util/MaybeDeferred.kt @@ -0,0 +1,32 @@ +/* + * 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.util + +/** + * This abstracts over a value which could be available immediately or calculated later. The use + * case is for shifting computation to a background thread. + */ +abstract class MaybeDeferred { + abstract fun value(): T + + /** similar to map on an Option or Functor in a functional language. */ + abstract fun map(fn: (T) -> U): MaybeDeferred +} + +class Immediate(private val value: T) : MaybeDeferred() { + override fun value(): T = value + + override fun map(fn: (T) -> U): MaybeDeferred = Immediate(fn(value)) +} + +class Deferred(private val lazyLoader: () -> T) : MaybeDeferred() { + override fun value(): T = lazyLoader() + override fun map(fn: (T) -> U): MaybeDeferred { + return Deferred { fn(lazyLoader()) } + } +} diff --git a/desktop/plugins/public/ui-debugger/components/PerfStats.tsx b/desktop/plugins/public/ui-debugger/components/PerfStats.tsx index 3743f243b..fbff575aa 100644 --- a/desktop/plugins/public/ui-debugger/components/PerfStats.tsx +++ b/desktop/plugins/public/ui-debugger/components/PerfStats.tsx @@ -63,11 +63,21 @@ const columns: DataTableColumn[] = [ return formatDiff(row.snapshotComplete, row.queuingComplete); }, }, + { + key: 'deferredComputationComplete', + title: 'Deferred processing time', + onRender: (row: PerfStatsEvent) => { + return formatDiff(row.queuingComplete, row.deferredComputationComplete); + }, + }, { key: 'serializationComplete', title: 'Serialization time', onRender: (row: PerfStatsEvent) => { - return formatDiff(row.queuingComplete, row.serializationComplete); + return formatDiff( + row.deferredComputationComplete, + row.serializationComplete, + ); }, }, { diff --git a/desktop/plugins/public/ui-debugger/types.tsx b/desktop/plugins/public/ui-debugger/types.tsx index a1557c729..efed0e407 100644 --- a/desktop/plugins/public/ui-debugger/types.tsx +++ b/desktop/plugins/public/ui-debugger/types.tsx @@ -40,8 +40,9 @@ export type PerfStatsEvent = { start: number; traversalComplete: number; snapshotComplete: number; - serializationComplete: number; queuingComplete: number; + deferredComputationComplete: number; + serializationComplete: number; socketComplete: number; nodesCount: number; };