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

Commit b8373c22 authored by Tomasz Mikolajewski's avatar Tomasz Mikolajewski
Browse files

Add a performance test for launching DocumentsUI.

The test launches the DocumentsUI as picker, then waits until the
main thread idles, which guarantees that roots are loaded and UI
rendered.

It confirms, that the recent system cache improves cold start
performance by around 24% on my configuration (from 1685ms to 1357ms).

Bug: 27370274
Change-Id: I738202ea434a7bfe7080fc0994f636ef0e7847cd
parent d262d6db
Loading
Loading
Loading
Loading
+18 −0
Original line number Diff line number Diff line
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)

LOCAL_MODULE_TAGS := tests
#LOCAL_SDK_VERSION := current

LOCAL_SRC_FILES := $(call all-java-files-under, src) \

LOCAL_JAVA_LIBRARIES := android-support-v4 android.test.runner
LOCAL_STATIC_JAVA_LIBRARIES := mockito-target ub-uiautomator

LOCAL_PACKAGE_NAME := DocumentsUIAppPerfTests
LOCAL_INSTRUMENTATION_FOR := DocumentsUI

LOCAL_CERTIFICATE := platform

include $(BUILD_PACKAGE)
+18 −0
Original line number Diff line number Diff line
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.android.documentsui.appperftests">

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

        <activity
            android:name="com.android.documentsui.LauncherActivity" />
    </application>

    <!-- This package instrumentates itself, so the DocumentsUI process can be killed without
         killing the testing package. -->
    <instrumentation android:name="android.test.InstrumentationTestRunner"
        android:targetPackage="com.android.documentsui.appperftests"
        android:label="App performance tests for DocumentsUI" />

</manifest>
+102 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2016 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.documentsui;

import android.app.Activity;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.os.Bundle;
import android.provider.DocumentsContract;
import android.support.test.uiautomator.UiDevice;
import android.test.InstrumentationTestCase;
import android.test.suitebuilder.annotation.LargeTest;
import android.util.Log;

import java.util.Arrays;
import java.util.List;
import java.util.concurrent.CountDownLatch;

@LargeTest
public class FilesAppPerfTest extends InstrumentationTestCase {

    // Keys used to report metrics to APCT.
    private static final String KEY_FILES_COLD_START_PERFORMANCE_MEDIAN =
            "files-cold-start-performance-median";
    private static final String KEY_FILES_WARM_START_PERFORMANCE_MEDIAN =
            "files-warm-start-performance-median";

    private static final String TARGET_PACKAGE = "com.android.documentsui";

    private static final int NUM_MEASUREMENTS = 10;

    private LauncherActivity mActivity;
    private UiDevice mDevice;

    @Override
    public void setUp() {
        mDevice = UiDevice.getInstance(getInstrumentation());
    }

    public void testFilesColdStartPerformance() throws Exception {
        runFilesStartPerformanceTest(true);
    }

    public void testFilesWarmStartPerformance() throws Exception {
        runFilesStartPerformanceTest(false);
    }

    public void runFilesStartPerformanceTest(boolean cold) throws Exception {
        long[] measurements = new long[NUM_MEASUREMENTS];
        for (int i = 0; i < NUM_MEASUREMENTS; i++) {
            if (cold) {
                // Kill all providers, as well as DocumentsUI to measure a cold start.
                killProviders();
                mDevice.executeShellCommand("am force-stop " + TARGET_PACKAGE);
            }
            mDevice.waitForIdle();

            LauncherActivity.testCaseLatch = new CountDownLatch(1);
            mActivity = launchActivity(getInstrumentation().getTargetContext().getPackageName(),
                    LauncherActivity.class, null);
            LauncherActivity.testCaseLatch.await();
            measurements[i] = LauncherActivity.measurement;
        }

        reportMetrics(cold ? KEY_FILES_COLD_START_PERFORMANCE_MEDIAN
                : KEY_FILES_WARM_START_PERFORMANCE_MEDIAN, measurements);
    }

    private void reportMetrics(String key, long[] measurements) {
        final Bundle status = new Bundle();
        Arrays.sort(measurements);
        final long median = measurements[NUM_MEASUREMENTS / 2 - 1];
        status.putDouble(key, median);

        getInstrumentation().sendStatus(Activity.RESULT_OK, status);
    }

    private void killProviders() throws Exception {
        final PackageManager pm = getInstrumentation().getContext().getPackageManager();
        final Intent intent = new Intent(DocumentsContract.PROVIDER_INTERFACE);
        final List<ResolveInfo> providers = pm.queryIntentContentProviders(intent, 0);
        for (ResolveInfo info : providers) {
            final String packageName = info.providerInfo.packageName;
            mDevice.executeShellCommand("am force-stop " + packageName);
        }
    }
}
+64 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2016 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.documentsui;

import static com.android.documentsui.Shared.EXTRA_BENCHMARK;

import android.app.Activity;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.os.Handler;
import android.util.Log;

import java.util.concurrent.CountDownLatch;

public class LauncherActivity extends Activity {
    private static final String TARGET_PACKAGE = "com.android.documentsui";
    private static final int BENCHMARK_REQUEST_CODE = 1986;

    public static CountDownLatch testCaseLatch = null;
    public static long measurement = -1;

    private long mStartTime = -1;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        new Handler().post(new Runnable() {
            @Override public void run() {
                final Intent intent = new Intent("android.intent.action.OPEN_DOCUMENT");
                intent.addCategory(Intent.CATEGORY_OPENABLE);
                intent.putExtra(EXTRA_BENCHMARK, true);
                intent.setType("*/*");

                mStartTime = System.currentTimeMillis();
                startActivityForResult(intent, BENCHMARK_REQUEST_CODE);
            }
        });
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        if (requestCode == BENCHMARK_REQUEST_CODE) {
            measurement = System.currentTimeMillis() - mStartTime;
            testCaseLatch.countDown();
            finish();
        }
    }
}
+43 −1
Original line number Diff line number Diff line
@@ -17,6 +17,7 @@
package com.android.documentsui;

import static com.android.documentsui.Shared.DEBUG;
import static com.android.documentsui.Shared.EXTRA_BENCHMARK;
import static com.android.documentsui.State.MODE_GRID;

import android.app.Activity;
@@ -30,6 +31,9 @@ import android.content.pm.ProviderInfo;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Handler;
import android.os.MessageQueue;
import android.os.MessageQueue.IdleHandler;
import android.provider.DocumentsContract;
import android.provider.DocumentsContract.Root;
import android.support.annotation.CallSuper;
@@ -60,6 +64,8 @@ import java.util.concurrent.Executor;
public abstract class BaseActivity extends Activity
        implements SearchManagerListener, NavigationView.Environment {

    private static final String BENCHMARK_TESTING_PACKAGE = "com.android.documentsui.appperftests";

    State mState;
    RootsCache mRoots;
    SearchViewManager mSearchManager;
@@ -92,11 +98,20 @@ public abstract class BaseActivity extends Activity
    public void onCreate(Bundle icicle) {
        super.onCreate(icicle);

        final Intent intent = getIntent();

        // If startup benchmark is requested by a whitelisted testing package, then close the
        // activity once idle, and notify the testing activity.
        if (intent.getBooleanExtra(EXTRA_BENCHMARK, false) &&
                BENCHMARK_TESTING_PACKAGE.equals(getCallingPackage())) {
            closeOnIdleForTesting();
        }

        setContentView(mLayoutId);

        mDrawer = DrawerController.create(this);
        mState = getState(icicle);
        Metrics.logActivityLaunch(this, mState, getIntent());
        Metrics.logActivityLaunch(this, mState, intent);

        mRoots = DocumentsApplication.getRootsCache(this);

@@ -668,6 +683,33 @@ public abstract class BaseActivity extends Activity
        }
    }

    /**
     * Closes the activity when it's idle. Used only for tests.
     */
    private void closeOnIdleForTesting() {
        addEventListener(new EventListener() {
            @Override
            public void onDirectoryNavigated(Uri uri) {
            }

            @Override
            public void onDirectoryLoaded(Uri uri) {
                getMainLooper().getQueue().addIdleHandler(new IdleHandler() {
                    @Override
                    public boolean queueIdle() {
                        setResult(RESULT_OK);
                        finish();
                        return false;
                    }
                });
                new Handler().post(new Runnable() {
                    @Override public void run() {
                    }
                });
            }
        });
    }

    private static final class HandleRootsChangedTask
            extends PairedTask<BaseActivity, RootInfo, RootInfo> {
        DocumentInfo mHome;
Loading