Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 4203d70f authored by Treehugger Robot's avatar Treehugger Robot Committed by Android (Google) Code Review
Browse files

Merge "vc tracing: integrate with perfetto" into main

parents 6400bf63 154e35b2
Loading
Loading
Loading
Loading
+2 −2
Original line number Diff line number Diff line
@@ -40,7 +40,7 @@ android_library {
    name: "motion_tool_lib",
    manifest: "AndroidManifest.xml",
    platform_apis: true,
    min_sdk_version: "26",
    min_sdk_version: "30",

    static_libs: [
        "androidx.core_core",
@@ -58,7 +58,7 @@ android_test {
    name: "motion_tool_lib_tests",
    manifest: "tests/AndroidManifest.xml",
    platform_apis: true,
    min_sdk_version: "26",
    min_sdk_version: "30",

    static_libs: [
        "androidx.core_core",
+3 −2
Original line number Diff line number Diff line
@@ -34,11 +34,12 @@ android_library {
    name: "view_capture",
    manifest: "AndroidManifest.xml",
    platform_apis: true,
    min_sdk_version: "26",
    min_sdk_version: "30",

    static_libs: [
        "androidx.core_core",
        "view_capture_proto",
        "perfetto_trace_javastream_protos_jarjar",
    ],

    srcs: [
@@ -51,7 +52,7 @@ android_test {
    name: "view_capture_tests",
    manifest: "tests/AndroidManifest.xml",
    platform_apis: true,
    min_sdk_version: "26",
    min_sdk_version: "30",

    static_libs: [
        "androidx.core_core",
+325 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2024 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.android.app.viewcapture

import android.content.Context
import android.internal.perfetto.protos.InternedDataOuterClass.InternedData
import android.internal.perfetto.protos.ProfileCommon.InternedString
import android.internal.perfetto.protos.TracePacketOuterClass.TracePacket
import android.internal.perfetto.protos.Viewcapture.ViewCapture as ViewCaptureMessage
import android.internal.perfetto.protos.WinscopeExtensionsImplOuterClass.WinscopeExtensionsImpl
import android.os.Trace
import android.tracing.perfetto.DataSourceParams
import android.tracing.perfetto.InitArguments
import android.tracing.perfetto.Producer
import android.util.proto.ProtoOutputStream
import androidx.annotation.WorkerThread
import java.util.concurrent.Executor
import java.util.concurrent.atomic.AtomicInteger

/**
 * ViewCapture that listens to Perfetto events (OnStart, OnStop, OnFlush) and continuously writes
 * captured frames to the Perfetto service (traced).
 */
internal class PerfettoViewCapture
internal constructor(private val context: Context, executor: Executor) :
    ViewCapture(RING_BUFFER_SIZE, DEFAULT_INIT_POOL_SIZE, executor) {

    private val mDataSource =
        ViewCaptureDataSource(
            { mActiveSessions.incrementAndGet() },
            {},
            { mActiveSessions.decrementAndGet() }
        )

    private val mActiveSessions = AtomicInteger(0)

    private val mViewIdProvider = ViewIdProvider(context.getResources())

    private var mSerializationCurrentId: Int = 0
    private var mSerializationCurrentView: ViewPropertyRef? = null

    inner class NewInternedStrings {
        val packageNames = mutableListOf<String>()
        val windowNames = mutableListOf<String>()
        val viewIds = mutableListOf<String>()
        val classNames = mutableListOf<String>()
    }

    init {
        Producer.init(InitArguments.DEFAULTS)
        val params =
            DataSourceParams.Builder()
                .setBufferExhaustedPolicy(
                    DataSourceParams.PERFETTO_DS_BUFFER_EXHAUSTED_POLICY_STALL_AND_ABORT
                )
                .setNoFlush(true)
                .setWillNotifyOnStop(false)
                .build()
        mDataSource.register(params)
    }

    @WorkerThread
    override fun onCapturedViewPropertiesBg(
        elapsedRealtimeNanos: Long,
        windowName: String,
        startFlattenedTree: ViewPropertyRef
    ) {
        Trace.beginSection("vc#onCapturedViewPropertiesBg")

        mDataSource.trace { ctx ->
            val newInternedStrings = NewInternedStrings()
            val os = ctx.newTracePacket()
            os.write(TracePacket.TIMESTAMP, elapsedRealtimeNanos)
            serializeViews(
                os,
                windowName,
                startFlattenedTree,
                ctx.incrementalState,
                newInternedStrings
            )
            serializeIncrementalState(os, ctx.incrementalState, newInternedStrings)
        }

        Trace.endSection()
    }

    private fun serializeViews(
        os: ProtoOutputStream,
        windowName: String,
        startFlattenedTree: ViewPropertyRef,
        incrementalState: ViewCaptureDataSource.IncrementalState,
        newInternedStrings: NewInternedStrings
    ) {
        mSerializationCurrentView = startFlattenedTree
        mSerializationCurrentId = 0

        val tokenExtensions = os.start(TracePacket.WINSCOPE_EXTENSIONS)
        val tokenViewCapture = os.start(WinscopeExtensionsImpl.VIEWCAPTURE)
        os.write(
            ViewCaptureMessage.PACKAGE_NAME_IID,
            internPackageName(context.packageName, incrementalState, newInternedStrings)
        )
        os.write(
            ViewCaptureMessage.WINDOW_NAME_IID,
            internWindowName(windowName, incrementalState, newInternedStrings)
        )
        serializeViewsRec(os, -1, incrementalState, newInternedStrings)
        os.end(tokenViewCapture)
        os.end(tokenExtensions)
    }

    private fun serializeViewsRec(
        os: ProtoOutputStream,
        parentId: Int,
        incrementalState: ViewCaptureDataSource.IncrementalState,
        newInternedStrings: NewInternedStrings
    ) {
        if (mSerializationCurrentView == null) {
            return
        }

        val id = mSerializationCurrentId
        val childCount = mSerializationCurrentView!!.childCount

        serializeView(
            os,
            mSerializationCurrentView!!,
            mSerializationCurrentId,
            parentId,
            incrementalState,
            newInternedStrings
        )

        ++mSerializationCurrentId
        mSerializationCurrentView = mSerializationCurrentView!!.next

        for (i in 0..childCount - 1) {
            serializeViewsRec(os, id, incrementalState, newInternedStrings)
        }
    }

    private fun serializeView(
        os: ProtoOutputStream,
        view: ViewPropertyRef,
        id: Int,
        parentId: Int,
        incrementalState: ViewCaptureDataSource.IncrementalState,
        newInternedStrings: NewInternedStrings
    ) {
        val token = os.start(ViewCaptureMessage.VIEWS)

        os.write(ViewCaptureMessage.View.ID, id)
        os.write(ViewCaptureMessage.View.PARENT_ID, parentId)
        os.write(ViewCaptureMessage.View.HASHCODE, view.hashCode)
        os.write(
            ViewCaptureMessage.View.VIEW_ID_IID,
            internViewId(mViewIdProvider.getName(view.id), incrementalState, newInternedStrings)
        )
        os.write(
            ViewCaptureMessage.View.CLASS_NAME_IID,
            internClassName(view.clazz.name, incrementalState, newInternedStrings)
        )

        os.write(ViewCaptureMessage.View.LEFT, view.left)
        os.write(ViewCaptureMessage.View.TOP, view.top)
        os.write(ViewCaptureMessage.View.WIDTH, view.right - view.left)
        os.write(ViewCaptureMessage.View.HEIGHT, view.bottom - view.top)
        os.write(ViewCaptureMessage.View.SCROLL_X, view.scrollX)
        os.write(ViewCaptureMessage.View.SCROLL_Y, view.scrollY)

        os.write(ViewCaptureMessage.View.TRANSLATION_X, view.translateX)
        os.write(ViewCaptureMessage.View.TRANSLATION_Y, view.translateY)
        os.write(ViewCaptureMessage.View.SCALE_X, view.scaleX)
        os.write(ViewCaptureMessage.View.SCALE_Y, view.scaleY)
        os.write(ViewCaptureMessage.View.ALPHA, view.alpha)

        os.write(ViewCaptureMessage.View.WILL_NOT_DRAW, view.willNotDraw)
        os.write(ViewCaptureMessage.View.CLIP_CHILDREN, view.clipChildren)
        os.write(ViewCaptureMessage.View.VISIBILITY, view.visibility)

        os.write(ViewCaptureMessage.View.ELEVATION, view.elevation)

        os.end(token)
    }

    private fun internClassName(
        string: String,
        incrementalState: ViewCaptureDataSource.IncrementalState,
        newInternedStrings: NewInternedStrings
    ): Int {
        return internString(
            string,
            incrementalState.mInternMapClassName,
            newInternedStrings.classNames
        )
    }

    private fun internPackageName(
        string: String,
        incrementalState: ViewCaptureDataSource.IncrementalState,
        newInternedStrings: NewInternedStrings
    ): Int {
        return internString(
            string,
            incrementalState.mInternMapPackageName,
            newInternedStrings.packageNames
        )
    }

    private fun internViewId(
        string: String,
        incrementalState: ViewCaptureDataSource.IncrementalState,
        newInternedStrings: NewInternedStrings
    ): Int {
        return internString(string, incrementalState.mInternMapViewId, newInternedStrings.viewIds)
    }

    private fun internWindowName(
        string: String,
        incrementalState: ViewCaptureDataSource.IncrementalState,
        newInternedStrings: NewInternedStrings
    ): Int {
        return internString(
            string,
            incrementalState.mInternMapWindowName,
            newInternedStrings.windowNames
        )
    }

    private fun internString(
        string: String,
        internMap: MutableMap<String, Int>,
        newInternedStrings: MutableList<String>
    ): Int {
        if (internMap.containsKey(string)) {
            return internMap[string]!!
        }

        // +1 to avoid intern ID = 0, because javastream optimizes out zero values
        // and the perfetto trace processor would not de-intern that string.
        val internId = internMap.size + 1

        internMap.put(string, internId)
        newInternedStrings.add(string)
        return internId
    }

    private fun serializeIncrementalState(
        os: ProtoOutputStream,
        incrementalState: ViewCaptureDataSource.IncrementalState,
        newInternedStrings: NewInternedStrings
    ) {
        var flags = TracePacket.SEQ_NEEDS_INCREMENTAL_STATE
        if (!incrementalState.mHasNotifiedClearedState) {
            flags = flags or TracePacket.SEQ_INCREMENTAL_STATE_CLEARED
            incrementalState.mHasNotifiedClearedState = true
        }
        os.write(TracePacket.SEQUENCE_FLAGS, flags)

        val token = os.start(TracePacket.INTERNED_DATA)
        serializeInternMap(
            os,
            InternedData.VIEWCAPTURE_CLASS_NAME,
            incrementalState.mInternMapClassName,
            newInternedStrings.classNames
        )
        serializeInternMap(
            os,
            InternedData.VIEWCAPTURE_PACKAGE_NAME,
            incrementalState.mInternMapPackageName,
            newInternedStrings.packageNames
        )
        serializeInternMap(
            os,
            InternedData.VIEWCAPTURE_VIEW_ID,
            incrementalState.mInternMapViewId,
            newInternedStrings.viewIds
        )
        serializeInternMap(
            os,
            InternedData.VIEWCAPTURE_WINDOW_NAME,
            incrementalState.mInternMapWindowName,
            newInternedStrings.windowNames
        )
        os.end(token)
    }

    private fun serializeInternMap(
        os: ProtoOutputStream,
        fieldId: Long,
        map: Map<String, Int>,
        newInternedStrings: List<String>
    ) {
        if (newInternedStrings.isEmpty()) {
            return
        }

        var currentInternId = map.size - newInternedStrings.size + 1
        for (internedString in newInternedStrings) {
            val token = os.start(fieldId)
            os.write(InternedString.IID, currentInternId++)
            os.write(InternedString.STR, internedString.toByteArray())
            os.end(token)
        }
    }

    companion object {
        // Keep two frames in the base class' ring buffer.
        // This is the minimum required by the current implementation to work.
        private val RING_BUFFER_SIZE = 2
    }
}
+13 −3
Original line number Diff line number Diff line
@@ -229,6 +229,10 @@ public abstract class ViewCapture {
                mBgExecutor);
    }

    @WorkerThread
    protected void onCapturedViewPropertiesBg(long elapsedRealtimeNanos, String windowName,
            ViewPropertyRef startFlattenedViewTree) {
    }

    /**
     * Once this window listener is attached to a window's root view, it traverses the entire
@@ -302,7 +306,7 @@ public abstract class ViewCapture {
         */
        @Override
        public void onDraw() {
            Trace.beginSection("view_capture");
            Trace.beginSection("vc#onDraw");
            captureViewTree(mRoot, mViewRef);
            ViewRef captured = mViewRef.next;
            if (captured != null) {
@@ -320,6 +324,8 @@ public abstract class ViewCapture {
         */
        @WorkerThread
        private void captureViewPropertiesBg(ViewRef viewRefStart) {
            Trace.beginSection("vc#captureViewPropertiesBg");

            long elapsedRealtimeNanos = viewRefStart.elapsedRealtimeNanos;
            mFrameIndexBg++;
            if (mFrameIndexBg >= mMemorySize) {
@@ -388,6 +394,10 @@ public abstract class ViewCapture {
                viewRefEnd = viewRefEnd.next;
            }
            mNodesBg[mFrameIndexBg] = resultStart;

            onCapturedViewPropertiesBg(elapsedRealtimeNanos, name, resultStart);

            Trace.endSection();
        }

        private @Nullable ViewPropertyRef findInLastFrame(int hashCode) {
@@ -508,7 +518,7 @@ public abstract class ViewCapture {
        }
    }

    private static class ViewPropertyRef {
    protected static class ViewPropertyRef {
        // We store reference in memory to avoid generating and storing too many strings
        public Class clazz;
        public int hashCode;
@@ -639,7 +649,7 @@ public abstract class ViewCapture {
        }
    }

    private static final class ViewIdProvider {
    protected static final class ViewIdProvider {

        private final SparseArray<String> mNames = new SparseArray<>();
        private final Resources mRes;
+78 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2024 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.app.viewcapture;

import android.tracing.perfetto.CreateIncrementalStateArgs;
import android.tracing.perfetto.DataSource;
import android.tracing.perfetto.DataSourceInstance;
import android.tracing.perfetto.FlushCallbackArguments;
import android.tracing.perfetto.StartCallbackArguments;
import android.tracing.perfetto.StopCallbackArguments;
import android.util.proto.ProtoInputStream;

import java.util.HashMap;
import java.util.Map;

class ViewCaptureDataSource
        extends DataSource<DataSourceInstance, Void, ViewCaptureDataSource.IncrementalState> {
    public static String DATA_SOURCE_NAME = "android.viewcapture";

    private final Runnable mOnStartStaticCallback;
    private final Runnable mOnFlushStaticCallback;
    private final Runnable mOnStopStaticCallback;

    ViewCaptureDataSource(Runnable onStart, Runnable onFlush, Runnable onStop) {
        super(DATA_SOURCE_NAME);
        this.mOnStartStaticCallback = onStart;
        this.mOnFlushStaticCallback = onFlush;
        this.mOnStopStaticCallback = onStop;
    }

    @Override
    public IncrementalState createIncrementalState(
            CreateIncrementalStateArgs<DataSourceInstance> args) {
        return new IncrementalState();
    }

    public static class IncrementalState {
        public final Map<String, Integer> mInternMapPackageName = new HashMap<>();
        public final Map<String, Integer> mInternMapWindowName = new HashMap<>();
        public final Map<String, Integer> mInternMapViewId = new HashMap<>();
        public final Map<String, Integer> mInternMapClassName = new HashMap<>();
        public boolean mHasNotifiedClearedState = false;
    }

    @Override
    public DataSourceInstance createInstance(ProtoInputStream configStream, int instanceIndex) {
        return new DataSourceInstance(this, instanceIndex) {
            @Override
            protected void onStart(StartCallbackArguments args) {
                mOnStartStaticCallback.run();
            }

            @Override
            protected void onFlush(FlushCallbackArguments args) {
                mOnFlushStaticCallback.run();
            }

            @Override
            protected void onStop(StopCallbackArguments args) {
                mOnStopStaticCallback.run();
            }
        };
    }
}
Loading