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

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

Merge "wm tracing: fix race condition in tests" into main

parents be5f4e7e b7e5a652
Loading
Loading
Loading
Loading
+2 −4
Original line number Original line Diff line number Diff line
@@ -38,8 +38,6 @@ import java.util.concurrent.atomic.AtomicBoolean;


public final class WindowTracingDataSource extends DataSource<WindowTracingDataSource.Instance,
public final class WindowTracingDataSource extends DataSource<WindowTracingDataSource.Instance,
        WindowTracingDataSource.TlsState, Void> {
        WindowTracingDataSource.TlsState, Void> {
    public static final String DATA_SOURCE_NAME = "android.windowmanager";

    public static class TlsState {
    public static class TlsState {
        public final Config mConfig;
        public final Config mConfig;
        public final AtomicBoolean mIsStarting = new AtomicBoolean(true);
        public final AtomicBoolean mIsStarting = new AtomicBoolean(true);
@@ -78,8 +76,8 @@ public final class WindowTracingDataSource extends DataSource<WindowTracingDataS
    @NonNull
    @NonNull
    private final WeakReference<WindowTracingPerfetto> mWindowTracing;
    private final WeakReference<WindowTracingPerfetto> mWindowTracing;


    public WindowTracingDataSource(WindowTracingPerfetto windowTracing) {
    public WindowTracingDataSource(WindowTracingPerfetto windowTracing, String dataSourceName) {
        super(DATA_SOURCE_NAME);
        super(dataSourceName);
        mWindowTracing = new WeakReference<>(windowTracing);
        mWindowTracing = new WeakReference<>(windowTracing);


        Producer.init(InitArguments.DEFAULTS);
        Producer.init(InitArguments.DEFAULTS);
+5 −3
Original line number Original line Diff line number Diff line
@@ -32,19 +32,21 @@ import java.util.concurrent.atomic.AtomicInteger;


class WindowTracingPerfetto extends WindowTracing {
class WindowTracingPerfetto extends WindowTracing {
    private static final String TAG = "WindowTracing";
    private static final String TAG = "WindowTracing";
    private static final String PRODUCTION_DATA_SOURCE_NAME = "android.windowmanager";


    private final AtomicInteger mCountSessionsOnFrame = new AtomicInteger();
    private final AtomicInteger mCountSessionsOnFrame = new AtomicInteger();
    private final AtomicInteger mCountSessionsOnTransaction = new AtomicInteger();
    private final AtomicInteger mCountSessionsOnTransaction = new AtomicInteger();
    private final WindowTracingDataSource mDataSource = new WindowTracingDataSource(this);
    private final WindowTracingDataSource mDataSource;


    WindowTracingPerfetto(WindowManagerService service, Choreographer choreographer) {
    WindowTracingPerfetto(WindowManagerService service, Choreographer choreographer) {
        this(service, choreographer, service.mGlobalLock);
        this(service, choreographer, service.mGlobalLock, PRODUCTION_DATA_SOURCE_NAME);
    }
    }


    @VisibleForTesting
    @VisibleForTesting
    WindowTracingPerfetto(WindowManagerService service, Choreographer choreographer,
    WindowTracingPerfetto(WindowManagerService service, Choreographer choreographer,
            WindowManagerGlobalLock globalLock) {
            WindowManagerGlobalLock globalLock, String dataSourceName) {
        super(service, choreographer, globalLock);
        super(service, choreographer, globalLock);
        mDataSource = new WindowTracingDataSource(this, dataSourceName);
    }
    }


    @Override
    @Override
+118 −37
Original line number Original line Diff line number Diff line
/*
/*
 * Copyright (C) 2017 The Android Open Source Project
 * Copyright (C) 2024 The Android Open Source Project
 *
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * you may not use this file except in compliance with the License.
@@ -28,6 +28,7 @@ import static org.mockito.ArgumentMatchers.eq;
import static java.io.File.createTempFile;
import static java.io.File.createTempFile;
import static java.nio.file.Files.createTempDirectory;
import static java.nio.file.Files.createTempDirectory;


import android.os.ParcelFileDescriptor;
import android.platform.test.annotations.Presubmit;
import android.platform.test.annotations.Presubmit;
import android.tools.ScenarioBuilder;
import android.tools.ScenarioBuilder;
import android.tools.traces.io.ResultWriter;
import android.tools.traces.io.ResultWriter;
@@ -35,107 +36,187 @@ import android.tools.traces.monitors.PerfettoTraceMonitor;
import android.view.Choreographer;
import android.view.Choreographer;


import androidx.test.filters.SmallTest;
import androidx.test.filters.SmallTest;
import androidx.test.platform.app.InstrumentationRegistry;

import com.google.protobuf.InvalidProtocolBufferException;


import org.junit.After;
import org.junit.After;
import org.junit.Before;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.Test;
import org.mockito.Mockito;
import org.mockito.Mockito;


import perfetto.protos.PerfettoConfig.TracingServiceState;
import perfetto.protos.PerfettoConfig.WindowManagerConfig.LogFrequency;
import perfetto.protos.PerfettoConfig.WindowManagerConfig.LogFrequency;


import java.io.FileInputStream;
import java.io.IOException;
import java.util.Optional;

/**
/**
 * Test class for {@link WindowTracingPerfetto}.
 * Test class for {@link WindowTracingPerfetto}.
 */
 */
@SmallTest
@SmallTest
@Presubmit
@Presubmit
public class WindowTracingPerfettoTest {
public class WindowTracingPerfettoTest {
    private WindowManagerService mWmMock;
    private static final String TEST_DATA_SOURCE_NAME = "android.windowmanager.test";
    private Choreographer mChoreographer;
    private WindowTracing mWindowTracing;
    private PerfettoTraceMonitor mTraceMonitor;
    private ResultWriter mWriter;


    @Before
    private static WindowManagerService sWmMock;
    public void setUp() throws Exception {
    private static Choreographer sChoreographer;
        mWmMock = Mockito.mock(WindowManagerService.class);
    private static WindowTracing sWindowTracing;
        Mockito.doNothing().when(mWmMock).dumpDebugLocked(Mockito.any(), Mockito.anyInt());


        mChoreographer = Mockito.mock(Choreographer.class);
    private PerfettoTraceMonitor mTraceMonitor;


        mWindowTracing = new WindowTracingPerfetto(mWmMock, mChoreographer,
    @BeforeClass
                new WindowManagerGlobalLock());
    public static void setUpOnce() throws Exception {
        sWmMock = Mockito.mock(WindowManagerService.class);
        Mockito.doNothing().when(sWmMock).dumpDebugLocked(Mockito.any(), Mockito.anyInt());
        sChoreographer = Mockito.mock(Choreographer.class);
        sWindowTracing = new WindowTracingPerfetto(sWmMock, sChoreographer,
                new WindowManagerGlobalLock(), TEST_DATA_SOURCE_NAME);
        waitDataSourceIsAvailable();
    }


        mWriter = new ResultWriter()
    @Before
            .forScenario(new ScenarioBuilder()
    public void setUp() throws IOException {
                    .forClass(createTempFile("temp", "").getName()).build())
        Mockito.clearInvocations(sWmMock);
            .withOutputDir(createTempDirectory("temp").toFile())
            .setRunComplete();
    }
    }


    @After
    @After
    public void tearDown() throws Exception {
    public void tearDown() throws IOException {
        stopTracing();
        stopTracing();
    }
    }


    @Test
    @Test
    public void isEnabled_returnsFalseByDefault() {
    public void isEnabled_returnsFalseByDefault() {
        assertFalse(mWindowTracing.isEnabled());
        assertFalse(sWindowTracing.isEnabled());
    }
    }


    @Test
    @Test
    public void isEnabled_returnsTrueAfterStartThenFalseAfterStop() {
    public void isEnabled_returnsTrueAfterStartThenFalseAfterStop() throws IOException {
        startTracing(false);
        startTracing(false);
        assertTrue(mWindowTracing.isEnabled());
        assertTrue(sWindowTracing.isEnabled());


        stopTracing();
        stopTracing();
        assertFalse(mWindowTracing.isEnabled());
        assertFalse(sWindowTracing.isEnabled());
    }
    }


    @Test
    @Test
    public void trace_ignoresLogStateCalls_ifTracingIsDisabled() {
    public void trace_ignoresLogStateCalls_ifTracingIsDisabled() {
        mWindowTracing.logState("where");
        sWindowTracing.logState("where");
        verifyZeroInteractions(mWmMock);
        verifyZeroInteractions(sWmMock);
    }
    }


    @Test
    @Test
    public void trace_writesInitialStateSnapshot_whenTracingStarts() throws Exception {
    public void trace_writesInitialStateSnapshot_whenTracingStarts() {
        startTracing(false);
        startTracing(false);
        verify(mWmMock, times(1)).dumpDebugLocked(any(), eq(WindowTracingLogLevel.ALL));
        verify(sWmMock, times(1)).dumpDebugLocked(any(), eq(WindowTracingLogLevel.ALL));
    }
    }


    @Test
    @Test
    public void trace_writesStateSnapshot_onLogStateCall() throws Exception {
    public void trace_writesStateSnapshot_onLogStateCall() {
        startTracing(false);
        startTracing(false);
        mWindowTracing.logState("where");
        sWindowTracing.logState("where");
        verify(mWmMock, times(2)).dumpDebugLocked(any(), eq(WindowTracingLogLevel.ALL));
        verify(sWmMock, times(2)).dumpDebugLocked(any(), eq(WindowTracingLogLevel.ALL));
    }
    }


    @Test
    @Test
    public void dump_writesOneSingleStateSnapshot() throws Exception {
    public void dump_writesOneSingleStateSnapshot() {
        startTracing(true);
        startTracing(true);
        mWindowTracing.logState("where");
        sWindowTracing.logState("where");
        verify(mWmMock, times(1)).dumpDebugLocked(any(), eq(WindowTracingLogLevel.ALL));
        verify(sWmMock, times(1)).dumpDebugLocked(any(), eq(WindowTracingLogLevel.ALL));
    }
    }


    private void startTracing(boolean isDump) {
    private void startTracing(boolean isDump) {
        if (isDump) {
        if (isDump) {
            mTraceMonitor = PerfettoTraceMonitor
            mTraceMonitor = PerfettoTraceMonitor
                    .newBuilder()
                    .newBuilder()
                    .enableWindowManagerDump()
                    .enableWindowManagerDump(TEST_DATA_SOURCE_NAME)
                    .build();
                    .build();
        } else {
        } else {
            mTraceMonitor = PerfettoTraceMonitor
            mTraceMonitor = PerfettoTraceMonitor
                    .newBuilder()
                    .newBuilder()
                    .enableWindowManagerTrace(LogFrequency.LOG_FREQUENCY_TRANSACTION)
                    .enableWindowManagerTrace(LogFrequency.LOG_FREQUENCY_TRANSACTION,
                            TEST_DATA_SOURCE_NAME)
                    .build();
                    .build();
        }
        }
        mTraceMonitor.start();
        mTraceMonitor.start();
    }
    }


    private void stopTracing() {
    private void stopTracing() throws IOException {
        if (mTraceMonitor == null || !mTraceMonitor.isEnabled()) {
        if (mTraceMonitor == null || !mTraceMonitor.isEnabled()) {
            return;
            return;
        }
        }
        mTraceMonitor.stop(mWriter);

        ResultWriter writer = new ResultWriter()
                .forScenario(new ScenarioBuilder()
                        .forClass(createTempFile("temp", "").getName()).build())
                .withOutputDir(createTempDirectory("temp").toFile())
                .setRunComplete();

        mTraceMonitor.stop(writer);
    }

    private static void waitDataSourceIsAvailable() {
        final int timeoutMs = 10000;
        final int busyWaitIntervalMs = 100;

        int elapsedMs = 0;

        while (!isDataSourceAvailable()) {
            try {
                Thread.sleep(busyWaitIntervalMs);
                elapsedMs += busyWaitIntervalMs;
                if (elapsedMs >= timeoutMs) {
                    throw new RuntimeException("Data source didn't become available."
                            + " Waited for: " + timeoutMs + " ms");
                }
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }

    private static boolean isDataSourceAvailable() {
        byte[] proto = executeShellCommand("perfetto --query-raw");

        try {
            TracingServiceState state = TracingServiceState.parseFrom(proto);

            Optional<Integer> producerId = Optional.empty();

            for (TracingServiceState.Producer producer : state.getProducersList()) {
                if (producer.getPid() == android.os.Process.myPid()) {
                    producerId = Optional.of(producer.getId());
                    break;
                }
            }

            if (producerId.isEmpty()) {
                return false;
            }

            for (TracingServiceState.DataSource ds : state.getDataSourcesList()) {
                if (ds.getDsDescriptor().getName().equals(TEST_DATA_SOURCE_NAME)
                        && ds.getProducerId() == producerId.get()) {
                    return true;
                }
            }
        } catch (InvalidProtocolBufferException e) {
            throw new RuntimeException(e);
        }

        return false;
    }

    private static byte[] executeShellCommand(String command) {
        try {
            ParcelFileDescriptor fd = InstrumentationRegistry.getInstrumentation().getUiAutomation()
                    .executeShellCommand(command);
            FileInputStream is = new ParcelFileDescriptor.AutoCloseInputStream(fd);
            return is.readAllBytes();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
    }
}
}