Initial UsageStatsDatabase Perf tests

A suite of simple performance test for UsageStatsDatabase. They measure
the time it take to write and read UsageEvents to and from a file.

Bug: 110428559
Test: atest UsageStatsPerfTests
Change-Id: If1558515e1da9e22fb56bc13f8e89c10c51a1625
diff --git a/services/usage/java/com/android/server/usage/IntervalStats.java b/services/usage/java/com/android/server/usage/IntervalStats.java
index 0dce738..4b7e21f 100644
--- a/services/usage/java/com/android/server/usage/IntervalStats.java
+++ b/services/usage/java/com/android/server/usage/IntervalStats.java
@@ -27,7 +27,9 @@
 
 import java.util.List;
 
-class IntervalStats {
+import com.android.internal.annotations.VisibleForTesting;
+
+public class IntervalStats {
     public long beginTime;
     public long endTime;
     public long lastTimeSaved;
@@ -149,7 +151,11 @@
                 && eventType != UsageEvents.Event.STANDBY_BUCKET_CHANGED;
     }
 
-    void update(String packageName, long timeStamp, int eventType) {
+    /**
+     * @hide
+     */
+    @VisibleForTesting
+    public void update(String packageName, long timeStamp, int eventType) {
         UsageStats usageStats = getOrCreateUsageStats(packageName);
 
         // TODO(adamlesinski): Ensure that we recover from incorrect event sequences
diff --git a/services/usage/java/com/android/server/usage/UsageStatsDatabase.java b/services/usage/java/com/android/server/usage/UsageStatsDatabase.java
index 9705469..5ab5dc2 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsDatabase.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsDatabase.java
@@ -42,7 +42,7 @@
 /**
  * Provides an interface to query for UsageStat data from an XML database.
  */
-class UsageStatsDatabase {
+public class UsageStatsDatabase {
     private static final int CURRENT_VERSION = 3;
 
     // Current version of the backup schema
@@ -369,7 +369,7 @@
     /**
      * Figures out what to extract from the given IntervalStats object.
      */
-    interface StatCombiner<T> {
+    public interface StatCombiner<T> {
 
         /**
          * Implementations should extract interesting from <code>stats</code> and add it
diff --git a/tests/UsageStatsPerfTests/Android.mk b/tests/UsageStatsPerfTests/Android.mk
new file mode 100644
index 0000000..cd29b51
--- /dev/null
+++ b/tests/UsageStatsPerfTests/Android.mk
@@ -0,0 +1,34 @@
+# Copyright (C) 2018 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.
+
+LOCAL_PATH := $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_SRC_FILES := \
+    $(call all-java-files-under, src)
+
+LOCAL_STATIC_JAVA_LIBRARIES := \
+    android-support-test \
+    apct-perftests-utils \
+    services.usage
+
+LOCAL_PACKAGE_NAME := UsageStatsPerfTests
+LOCAL_PRIVATE_PLATFORM_APIS := true
+
+# For android.permission.FORCE_STOP_PACKAGES permission
+LOCAL_CERTIFICATE := platform
+
+include $(BUILD_PACKAGE)
diff --git a/tests/UsageStatsPerfTests/AndroidManifest.xml b/tests/UsageStatsPerfTests/AndroidManifest.xml
new file mode 100644
index 0000000..596a79c
--- /dev/null
+++ b/tests/UsageStatsPerfTests/AndroidManifest.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2018 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.frameworks.perftests.usage">
+    <uses-sdk
+            android:minSdkVersion="21" />
+    <uses-permission android:name="android.permission.DUMP" />
+    <uses-permission android:name="android.permission.FORCE_STOP_PACKAGES" />
+
+    <application>
+        <uses-library android:name="android.test.runner" />
+    </application>
+
+    <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
+                     android:targetPackage="com.android.frameworks.perftests.usage"/>
+</manifest>
diff --git a/tests/UsageStatsPerfTests/AndroidTest.xml b/tests/UsageStatsPerfTests/AndroidTest.xml
new file mode 100644
index 0000000..c9b51dc
--- /dev/null
+++ b/tests/UsageStatsPerfTests/AndroidTest.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2018 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 UsageStats Performance Tests">
+    <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup">
+        <option name="test-file-name" value="UsageStatsPerfTests.apk"/>
+        <option name="cleanup-apks" value="true"/>
+    </target_preparer>
+
+    <option name="test-suite-tag" value="apct"/>
+    <option name="test-tag" value="UsageStatsPerfTests"/>
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest">
+        <option name="package" value="com.android.frameworks.perftests.usage"/>
+        <option name="runner" value="android.support.test.runner.AndroidJUnitRunner"/>
+    </test>
+</configuration>
\ No newline at end of file
diff --git a/tests/UsageStatsPerfTests/src/com/android/frameworks/perftests/usage/tests/UsageStatsDatabasePerfTest.java b/tests/UsageStatsPerfTests/src/com/android/frameworks/perftests/usage/tests/UsageStatsDatabasePerfTest.java
new file mode 100644
index 0000000..8467bee
--- /dev/null
+++ b/tests/UsageStatsPerfTests/src/com/android/frameworks/perftests/usage/tests/UsageStatsDatabasePerfTest.java
@@ -0,0 +1,183 @@
+/*
+ * Copyright (C) 2018 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.frameworks.perftests.usage.tests;
+
+import static junit.framework.Assert.assertEquals;
+
+import com.android.server.usage.UsageStatsDatabase;
+import com.android.server.usage.UsageStatsDatabase.StatCombiner;
+import com.android.server.usage.IntervalStats;
+
+import android.app.usage.EventList;
+import android.app.usage.UsageEvents;
+import android.app.usage.UsageStatsManager;
+import android.content.Context;
+import android.os.SystemClock;
+import android.perftests.utils.ManualBenchmarkState;
+import android.perftests.utils.PerfManualStatusReporter;
+import android.support.test.filters.LargeTest;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.BeforeClass;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.List;
+
+@RunWith(AndroidJUnit4.class)
+@LargeTest
+public class UsageStatsDatabasePerfTest {
+    protected static Context sContext;
+    private static UsageStatsDatabase sUsageStatsDatabase;
+    private static File mTestDir;
+
+    // Represents how many apps might have used in a day by a user with a few apps
+    final static int FEW_PKGS = 10;
+    // Represent how many apps might have used in a day by a user with many apps
+    final static int MANY_PKGS = 50;
+    // Represents how many usage events per app a device might have with light usage
+    final static int LIGHT_USE = 10;
+    // Represents how many usage events per app a device might have with heavy usage
+    final static int HEAVY_USE = 50;
+
+    private static final StatCombiner<UsageEvents.Event> sUsageStatsCombiner =
+            new StatCombiner<UsageEvents.Event>() {
+                @Override
+                public void combine(IntervalStats stats, boolean mutable,
+                        List<UsageEvents.Event> accResult) {
+                    final int size = stats.events.size();
+                    for (int i = 0; i < size; i++) {
+                        accResult.add(stats.events.get(i));
+                    }
+                }
+            };
+
+
+    @Rule
+    public PerfManualStatusReporter mPerfManualStatusReporter = new PerfManualStatusReporter();
+
+    @BeforeClass
+    public static void setUpOnce() {
+        sContext = InstrumentationRegistry.getTargetContext();
+        mTestDir = new File(sContext.getFilesDir(), "UsageStatsDatabasePerfTest");
+        sUsageStatsDatabase = new UsageStatsDatabase(mTestDir);
+        sUsageStatsDatabase.init(1);
+    }
+
+    private static void populateIntervalStats(IntervalStats intervalStats, int packageCount,
+            int eventsPerPackage) {
+        if (intervalStats.events == null) {
+            intervalStats.events = new EventList();
+        }
+        for (int pkg = 0; pkg < packageCount; pkg++) {
+            UsageEvents.Event event = new UsageEvents.Event();
+            event.mPackage = "fake.package.name" + pkg;
+            event.mTimeStamp = 1;
+            event.mEventType = UsageEvents.Event.MOVE_TO_FOREGROUND;
+            for (int evt = 0; evt < eventsPerPackage; evt++) {
+                intervalStats.events.insert(event);
+                intervalStats.update(event.mPackage, event.mTimeStamp, event.mEventType);
+            }
+        }
+    }
+
+    private static void clearUsageStatsFiles() {
+        File[] intervalDirs = mTestDir.listFiles();
+        for (File intervalDir : intervalDirs) {
+            if (intervalDir.isDirectory()) {
+                File[] usageFiles = intervalDir.listFiles();
+                for (File f : usageFiles) {
+                    f.delete();
+                }
+            }
+        }
+    }
+
+    private void runQueryUsageStatsTest(int packageCount, int eventsPerPackage) throws IOException {
+        final ManualBenchmarkState benchmarkState = mPerfManualStatusReporter.getBenchmarkState();
+        IntervalStats intervalStats = new IntervalStats();
+        populateIntervalStats(intervalStats, packageCount, eventsPerPackage);
+        sUsageStatsDatabase.putUsageStats(0, intervalStats);
+        long elapsedTimeNs = 0;
+        while (benchmarkState.keepRunning(elapsedTimeNs)) {
+            final long startTime = SystemClock.elapsedRealtimeNanos();
+            List<UsageEvents.Event> temp = sUsageStatsDatabase.queryUsageStats(
+                    UsageStatsManager.INTERVAL_DAILY, 0, 2, sUsageStatsCombiner);
+            final long endTime = SystemClock.elapsedRealtimeNanos();
+            elapsedTimeNs = endTime - startTime;
+            assertEquals(packageCount * eventsPerPackage, temp.size());
+        }
+    }
+
+    private void runPutUsageStatsTest(int packageCount, int eventsPerPackage) throws IOException {
+        final ManualBenchmarkState benchmarkState = mPerfManualStatusReporter.getBenchmarkState();
+        IntervalStats intervalStats = new IntervalStats();
+        populateIntervalStats(intervalStats, packageCount, eventsPerPackage);
+        long elapsedTimeNs = 0;
+        while (benchmarkState.keepRunning(elapsedTimeNs)) {
+            final long startTime = SystemClock.elapsedRealtimeNanos();
+            sUsageStatsDatabase.putUsageStats(0, intervalStats);
+            final long endTime = SystemClock.elapsedRealtimeNanos();
+            elapsedTimeNs = endTime - startTime;
+            clearUsageStatsFiles();
+        }
+    }
+
+    @Test
+    public void testQueryUsageStats_FewPkgsLightUse() throws IOException {
+        runQueryUsageStatsTest(FEW_PKGS, LIGHT_USE);
+    }
+
+    @Test
+    public void testPutUsageStats_FewPkgsLightUse() throws IOException {
+        runPutUsageStatsTest(FEW_PKGS, LIGHT_USE);
+    }
+
+    @Test
+    public void testQueryUsageStats_FewPkgsHeavyUse() throws IOException {
+        runQueryUsageStatsTest(FEW_PKGS, HEAVY_USE);
+    }
+
+    @Test
+    public void testPutUsageStats_FewPkgsHeavyUse() throws IOException {
+        runPutUsageStatsTest(FEW_PKGS, HEAVY_USE);
+    }
+
+    @Test
+    public void testQueryUsageStats_ManyPkgsLightUse() throws IOException {
+        runQueryUsageStatsTest(MANY_PKGS, LIGHT_USE);
+    }
+
+    @Test
+    public void testPutUsageStats_ManyPkgsLightUse() throws IOException {
+        runPutUsageStatsTest(MANY_PKGS, LIGHT_USE);
+    }
+
+    @Test
+    public void testQueryUsageStats_ManyPkgsHeavyUse() throws IOException {
+        runQueryUsageStatsTest(MANY_PKGS, HEAVY_USE);
+    }
+
+    @Test
+    public void testPutUsageStats_ManyPkgsHeavyUse() throws IOException {
+        runPutUsageStatsTest(MANY_PKGS, HEAVY_USE);
+    }
+}