/* * 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.react.fabric import com.facebook.react.bridge.ReactMarker.FabricMarkerListener import com.facebook.react.bridge.ReactMarkerConstants import kotlin.jvm.JvmField public class DevToolsReactPerfLogger : FabricMarkerListener { private val fabricCommitMarkers = mutableMapOf() private val devToolsReactPerfLoggerListeners = mutableListOf() public fun interface DevToolsReactPerfLoggerListener { public fun onFabricCommitEnd(commitPoint: FabricCommitPoint) } internal class FabricCommitPointData(val timeStamp: Long, val counter: Int) public class FabricCommitPoint internal constructor(commitNumber: Int) { public val commitNumber: Long = commitNumber.toLong() private val points = mutableMapOf() internal fun addPoint(key: ReactMarkerConstants, data: FabricCommitPointData) { points[key] = data } private fun getTimestamp(marker: ReactMarkerConstants): Long { val data = points[marker] return data?.timeStamp ?: -1 } private fun getCounter(marker: ReactMarkerConstants): Int = points[marker]?.counter ?: 0 public val commitStart: Long get() = getTimestamp(ReactMarkerConstants.FABRIC_COMMIT_START) public val commitEnd: Long get() = getTimestamp(ReactMarkerConstants.FABRIC_COMMIT_END) public val finishTransactionStart: Long get() = getTimestamp(ReactMarkerConstants.FABRIC_FINISH_TRANSACTION_START) public val finishTransactionEnd: Long get() = getTimestamp(ReactMarkerConstants.FABRIC_FINISH_TRANSACTION_END) public val diffStart: Long get() = getTimestamp(ReactMarkerConstants.FABRIC_DIFF_START) public val diffEnd: Long get() = getTimestamp(ReactMarkerConstants.FABRIC_DIFF_END) public val layoutStart: Long get() = getTimestamp(ReactMarkerConstants.FABRIC_LAYOUT_START) public val layoutEnd: Long get() = getTimestamp(ReactMarkerConstants.FABRIC_LAYOUT_END) public val affectedLayoutNodesCount: Int get() = getCounter(ReactMarkerConstants.FABRIC_LAYOUT_AFFECTED_NODES) public val affectedLayoutNodesCountTime: Long get() = getTimestamp(ReactMarkerConstants.FABRIC_LAYOUT_AFFECTED_NODES) public val batchExecutionStart: Long get() = getTimestamp(ReactMarkerConstants.FABRIC_BATCH_EXECUTION_START) public val batchExecutionEnd: Long get() = getTimestamp(ReactMarkerConstants.FABRIC_BATCH_EXECUTION_END) public val updateUIMainThreadStart: Long get() = getTimestamp(ReactMarkerConstants.FABRIC_UPDATE_UI_MAIN_THREAD_START) public val updateUIMainThreadEnd: Long get() = getTimestamp(ReactMarkerConstants.FABRIC_UPDATE_UI_MAIN_THREAD_END) public val commitDuration: Long // Duration calculations get() = commitEnd - commitStart public val layoutDuration: Long get() = layoutEnd - layoutStart public val diffDuration: Long get() = diffEnd - diffStart public val transactionEndDuration: Long get() = finishTransactionEnd - finishTransactionStart public val batchExecutionDuration: Long get() = batchExecutionEnd - batchExecutionStart override fun toString(): String { return "FabricCommitPoint{mCommitNumber=$commitNumber, mPoints=$points}" } } public fun addDevToolsReactPerfLoggerListener(listener: DevToolsReactPerfLoggerListener): Unit { devToolsReactPerfLoggerListeners.add(listener) } public fun removeDevToolsReactPerfLoggerListener( listener: DevToolsReactPerfLoggerListener ): Unit { devToolsReactPerfLoggerListeners.remove(listener) } override fun logFabricMarker( name: ReactMarkerConstants, tag: String?, instanceKey: Int, timestamp: Long, ) { logFabricMarker(name, tag, instanceKey, timestamp, 0) } override fun logFabricMarker( name: ReactMarkerConstants, tag: String?, instanceKey: Int, timestamp: Long, counter: Int, ) { if (isFabricCommitMarker(name)) { var commitPoint = fabricCommitMarkers[instanceKey] if (commitPoint == null) { commitPoint = FabricCommitPoint(instanceKey) fabricCommitMarkers[instanceKey] = commitPoint } commitPoint.addPoint(name, FabricCommitPointData(timestamp, counter)) if (name == ReactMarkerConstants.FABRIC_BATCH_EXECUTION_END && timestamp > 0) { onFabricCommitEnd(commitPoint) fabricCommitMarkers.remove(instanceKey) } } } private fun onFabricCommitEnd(commitPoint: FabricCommitPoint) { for (listener in devToolsReactPerfLoggerListeners) { listener.onFabricCommitEnd(commitPoint) } } private companion object { @JvmField internal val streamingCommitStats: LongStreamingStats = LongStreamingStats() @JvmField internal val streamingLayoutStats: LongStreamingStats = LongStreamingStats() @JvmField internal val streamingDiffStats: LongStreamingStats = LongStreamingStats() @JvmField internal val streamingTransactionEndStats: LongStreamingStats = LongStreamingStats() @JvmField internal val streamingBatchExecutionStats: LongStreamingStats = LongStreamingStats() private fun isFabricCommitMarker(name: ReactMarkerConstants): Boolean = name == ReactMarkerConstants.FABRIC_COMMIT_START || name == ReactMarkerConstants.FABRIC_COMMIT_END || name == ReactMarkerConstants.FABRIC_FINISH_TRANSACTION_START || name == ReactMarkerConstants.FABRIC_FINISH_TRANSACTION_END || name == ReactMarkerConstants.FABRIC_DIFF_START || name == ReactMarkerConstants.FABRIC_DIFF_END || name == ReactMarkerConstants.FABRIC_LAYOUT_START || name == ReactMarkerConstants.FABRIC_LAYOUT_END || name == ReactMarkerConstants.FABRIC_BATCH_EXECUTION_START || name == ReactMarkerConstants.FABRIC_BATCH_EXECUTION_END || name == ReactMarkerConstants.FABRIC_UPDATE_UI_MAIN_THREAD_START || name == ReactMarkerConstants.FABRIC_UPDATE_UI_MAIN_THREAD_END || name == ReactMarkerConstants.FABRIC_LAYOUT_AFFECTED_NODES } }