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

Commit d4ea5e14 authored by Sudheer Shanka's avatar Sudheer Shanka
Browse files

Add perf test to measure duration of blob store digest computation.

+ Move BlobStoreTestUtils from cts/ to frameworks/base/tests/.

Bug: 148898557
Test: atest ./apct-tests/perftests/blobstore/src/com/android/perftests/blob/BlobStorePerfTests.java
Change-Id: I2de155d0c0c1fb602c57353ba4819bdc9cda8c0a
parent e53e1ed2
Loading
Loading
Loading
Loading
+28 −0
Original line number Diff line number Diff line
// Copyright (C) 2020 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.

android_test {
  name: "BlobStorePerfTests",
  srcs: ["src/**/*.java"],
  static_libs: [
    "BlobStoreTestUtils",
    "androidx.test.rules",
    "androidx.annotation_annotation",
    "apct-perftests-utils",
    "ub-uiautomator",
  ],
  platform_apis: true,
  test_suites: ["device-tests"],
  certificate: "platform",
}
 No newline at end of file
+27 −0
Original line number Diff line number Diff line
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2020 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.
-->

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
          package="com.android.perftests.blob">

    <application>
        <uses-library android:name="android.test.runner" />
    </application>

    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
                     android:targetPackage="com.android.perftests.blob"/>

</manifest>
 No newline at end of file
+28 −0
Original line number Diff line number Diff line
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2020 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.
-->
<configuration description="Runs BlobStorePerfTests metric instrumentation.">
    <option name="test-suite-tag" value="apct" />
    <option name="test-suite-tag" value="apct-metric-instrumentation" />
    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
        <option name="cleanup-apks" value="true" />
        <option name="test-file-name" value="BlobStorePerfTests.apk" />
    </target_preparer>

    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
        <option name="package" value="com.android.perftests.blob" />
        <option name="hidden-api-checks" value="false"/>
    </test>
</configuration>
 No newline at end of file
+120 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2020 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.perftests.blob;

import android.app.Instrumentation;
import android.app.UiAutomation;
import android.os.ParcelFileDescriptor;
import android.perftests.utils.TraceMarkParser;
import android.perftests.utils.TraceMarkParser.TraceMarkSlice;
import android.support.test.uiautomator.UiDevice;
import android.util.Log;

import androidx.test.platform.app.InstrumentationRegistry;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.List;
import java.util.function.BiConsumer;

// Copy of com.android.frameworks.perftests.am.util.AtraceUtils. TODO: avoid this duplication.
public class AtraceUtils {
    private static final String TAG = "AtraceUtils";
    private static final boolean VERBOSE = true;

    private static final String ATRACE_START = "atrace --async_start -b %d -c %s";
    private static final String ATRACE_DUMP = "atrace --async_dump";
    private static final String ATRACE_STOP = "atrace --async_stop";
    private static final int DEFAULT_ATRACE_BUF_SIZE = 1024;

    private UiAutomation mAutomation;
    private static AtraceUtils sUtils = null;
    private boolean mStarted = false;

    private AtraceUtils(Instrumentation instrumentation) {
        mAutomation = instrumentation.getUiAutomation();
    }

    public static AtraceUtils getInstance(Instrumentation instrumentation) {
        if (sUtils == null) {
            sUtils = new AtraceUtils(instrumentation);
        }
        return sUtils;
    }

    /**
     * @param categories The list of the categories to trace, separated with space.
     */
    public void startTrace(String categories) {
        synchronized (this) {
            if (mStarted) {
                throw new IllegalStateException("atrace already started");
            }
            runShellCommand(String.format(
                    ATRACE_START, DEFAULT_ATRACE_BUF_SIZE, categories));
            mStarted = true;
        }
    }

    public void stopTrace() {
        synchronized (this) {
            mStarted = false;
            runShellCommand(ATRACE_STOP);
        }
    }

    private String runShellCommand(String cmd) {
        try {
            return UiDevice.getInstance(
                    InstrumentationRegistry.getInstrumentation()).executeShellCommand(cmd);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * @param parser The function that can accept the buffer of atrace dump and parse it.
     * @param handler The parse result handler
     */
    public void performDump(TraceMarkParser parser,
            BiConsumer<String, List<TraceMarkSlice>> handler) {
        parser.reset();
        try {
            if (VERBOSE) {
                Log.i(TAG, "Collecting atrace dump...");
            }
            writeDataToBuf(mAutomation.executeShellCommand(ATRACE_DUMP), parser);
        } catch (IOException e) {
            Log.e(TAG, "Error in reading dump", e);
        }
        parser.forAllSlices(handler);
    }

    // The given file descriptor here will be closed by this function
    private void writeDataToBuf(ParcelFileDescriptor pfDescriptor,
            TraceMarkParser parser) throws IOException {
        InputStream inputStream = new ParcelFileDescriptor.AutoCloseInputStream(pfDescriptor);
        try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream))) {
            String line;
            while ((line = reader.readLine()) != null) {
                parser.visit(line);
            }
        }
    }
}
+146 −0
Original line number Diff line number Diff line
/*
 * Copyright 2020 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.perftests.blob;

import android.app.blob.BlobStoreManager;
import android.content.Context;
import android.perftests.utils.ManualBenchmarkState;
import android.perftests.utils.PerfManualStatusReporter;
import android.perftests.utils.TraceMarkParser;
import android.perftests.utils.TraceMarkParser.TraceMarkSlice;
import android.support.test.uiautomator.UiDevice;

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

import com.android.utils.blob.DummyBlobData;

import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;

@LargeTest
@RunWith(Parameterized.class)
public class BlobStorePerfTests {
    // From frameworks/native/cmds/atrace/atrace.cpp
    private static final String ATRACE_CATEGORY_SYSTEM_SERVER = "ss";
    // From f/b/apex/blobstore/service/java/com/android/server/blob/BlobStoreSession.java
    private static final String ATRACE_COMPUTE_DIGEST_PREFIX = "computeBlobDigest-";

    private Context mContext;
    private BlobStoreManager mBlobStoreManager;
    private AtraceUtils mAtraceUtils;
    private ManualBenchmarkState mState;

    @Rule
    public PerfManualStatusReporter mPerfManualStatusReporter = new PerfManualStatusReporter();

    @Parameterized.Parameter(0)
    public int fileSizeInMb;

    @Parameterized.Parameters(name = "{0}MB")
    public static Collection<Object[]> getParameters() {
        return Arrays.asList(new Object[][] {
                { 25 },
                { 50 },
                { 100 },
                { 200 },
        });
    }

    @Before
    public void setUp() {
        mContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
        mBlobStoreManager = (BlobStoreManager) mContext.getSystemService(
                Context.BLOB_STORE_SERVICE);
        mAtraceUtils = AtraceUtils.getInstance(InstrumentationRegistry.getInstrumentation());
        mState = mPerfManualStatusReporter.getBenchmarkState();
    }

    @After
    public void tearDown() {
        // TODO: Add a blob_store shell command to trigger idle maintenance to avoid hardcoding
        // job id like this.
        // From BlobStoreConfig.IDLE_JOB_ID = 191934935.
        runShellCommand("cmd jobscheduler run -f android 191934935");
    }

    @Test
    public void testComputeDigest() throws Exception {
        mAtraceUtils.startTrace(ATRACE_CATEGORY_SYSTEM_SERVER);
        try {
            final List<Long> durations = new ArrayList<>();
            final DummyBlobData blobData = prepareDataBlob(fileSizeInMb);
            final TraceMarkParser parser = new TraceMarkParser(
                    line -> line.name.startsWith(ATRACE_COMPUTE_DIGEST_PREFIX));
            while (mState.keepRunning(durations)) {
                commitBlob(blobData);

                durations.clear();
                collectDigestDurationsFromTrace(parser, durations);
                // get and delete blobId
            }
        } finally {
            mAtraceUtils.stopTrace();
        }
    }

    private void collectDigestDurationsFromTrace(TraceMarkParser parser, List<Long> durations) {
        mAtraceUtils.performDump(parser, (key, slices) -> {
            for (TraceMarkSlice slice : slices) {
                durations.add(TimeUnit.MICROSECONDS.toNanos(slice.getDurationInMicroseconds()));
            }
        });
    }

    private DummyBlobData prepareDataBlob(int fileSizeInMb) throws Exception {
        final DummyBlobData blobData = new DummyBlobData(mContext,
                fileSizeInMb * 1024 * 1024 /* bytes */);
        blobData.prepare();
        return blobData;
    }

    private void commitBlob(DummyBlobData blobData) throws Exception {
        final long sessionId = mBlobStoreManager.createSession(blobData.getBlobHandle());
        try (BlobStoreManager.Session session = mBlobStoreManager.openSession(sessionId)) {
            blobData.writeToSession(session);
            final CompletableFuture<Integer> callback = new CompletableFuture<>();
            session.commit(mContext.getMainExecutor(), callback::complete);
            // Ignore commit callback result.
            callback.get();
        }
    }

    private String runShellCommand(String cmd) {
        try {
            return UiDevice.getInstance(
                    InstrumentationRegistry.getInstrumentation()).executeShellCommand(cmd);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}
Loading