Merge "Add CursorWindow perf tests"
diff --git a/apct-tests/perftests/core/src/android/database/CursorWindowPerfTest.java b/apct-tests/perftests/core/src/android/database/CursorWindowPerfTest.java
new file mode 100644
index 0000000..897d0ae
--- /dev/null
+++ b/apct-tests/perftests/core/src/android/database/CursorWindowPerfTest.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2017 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 android.database;
+
+import static org.junit.Assert.assertTrue;
+
+import android.content.Context;
+import android.database.sqlite.SQLiteCursor;
+import android.database.sqlite.SQLiteDatabase;
+import android.perftests.utils.BenchmarkState;
+import android.perftests.utils.PerfStatusReporter;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.LargeTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@LargeTest
+public class CursorWindowPerfTest {
+ @Rule
+ public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
+
+ private static Context getContext() {
+ return InstrumentationRegistry.getTargetContext();
+ }
+
+ private static final String DB_NAME = CursorWindowPerfTest.class.toString();
+
+ private static SQLiteDatabase sDatabase;
+
+ @BeforeClass
+ public static void setup() {
+ getContext().deleteDatabase(DB_NAME);
+ sDatabase = getContext().openOrCreateDatabase(DB_NAME, Context.MODE_PRIVATE, null);
+
+ for (TableHelper helper : TableHelper.TABLE_HELPERS) {
+ sDatabase.execSQL(helper.createSql());
+ final String insert = helper.insertSql();
+
+ // this test only needs 1 row
+ sDatabase.execSQL(insert, helper.createItem(0));
+ }
+
+ }
+
+ @AfterClass
+ public static void teardown() {
+ getContext().deleteDatabase(DB_NAME);
+ }
+
+ @Test
+ public void loadInt() {
+ loadRowFromCursorWindow(TableHelper.INT_1, false);
+ }
+
+ @Test
+ public void loadInt_doubleRef() {
+ loadRowFromCursorWindow(TableHelper.INT_1, true);
+ }
+
+ @Test
+ public void load10Ints() {
+ loadRowFromCursorWindow(TableHelper.INT_10, false);
+ }
+
+ @Test
+ public void loadUser() {
+ loadRowFromCursorWindow(TableHelper.USER, false);
+ }
+
+ private void loadRowFromCursorWindow(TableHelper helper, boolean doubleRef) {
+ try (Cursor cursor = sDatabase.rawQuery(helper.readSql(), new String[0])) {
+ TableHelper.CursorReader reader = helper.createReader(cursor);
+
+ SQLiteCursor sqLiteCursor = (SQLiteCursor) cursor;
+
+ sqLiteCursor.getCount(); // load one window
+ CursorWindow window = sqLiteCursor.getWindow();
+ assertTrue("must have enough rows", window.getNumRows() >= 1);
+ int start = window.getStartPosition();
+
+ BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+
+ if (!doubleRef) {
+ // normal access
+ while (state.keepRunning()) {
+ cursor.moveToPosition(start);
+ reader.read();
+ }
+ } else {
+ // add an extra window acquire/release to measure overhead
+ while (state.keepRunning()) {
+ cursor.moveToPosition(start);
+ try {
+ window.acquireReference();
+ reader.read();
+ } finally {
+ window.releaseReference();
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/apct-tests/perftests/core/src/android/database/TableHelper.java b/apct-tests/perftests/core/src/android/database/TableHelper.java
new file mode 100644
index 0000000..48f3781
--- /dev/null
+++ b/apct-tests/perftests/core/src/android/database/TableHelper.java
@@ -0,0 +1,223 @@
+/*
+ * Copyright (C) 2017 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 android.database;
+
+import java.util.Date;
+import java.util.UUID;
+
+/**
+ * Helper class for creating and querying data from a database in performance tests.
+ *
+ * Subclasses can define different table/query formats.
+ */
+public abstract class TableHelper {
+
+ public interface CursorReader {
+ void read();
+ }
+
+ public abstract String createSql();
+ public abstract String insertSql();
+ public abstract Object[] createItem(int id);
+ public abstract String readSql();
+ public abstract CursorReader createReader(Cursor cursor);
+
+ /**
+ * 1 column, single integer
+ */
+ public static TableHelper INT_1 = new TableHelper() {
+ @Override
+ public String createSql() {
+ return "CREATE TABLE `Int1` ("
+ + "`a` INTEGER,"
+ + " PRIMARY KEY(`a`))";
+ }
+
+ @Override
+ public String insertSql() {
+ return "INSERT INTO `Int1`(`a`)"
+ + " VALUES (?)";
+ }
+
+ @Override
+ public Object[] createItem(int id) {
+ return new Object[] {
+ id,
+ };
+ }
+
+ @Override
+ public String readSql() {
+ return "SELECT * from Int1";
+ }
+
+ @Override
+ public CursorReader createReader(final Cursor cursor) {
+ final int cursorIndexOfA = cursor.getColumnIndexOrThrow("a");
+ return () -> {
+ cursor.getInt(cursorIndexOfA);
+ };
+ }
+ };
+ /**
+ * 10 columns of integers
+ */
+ public static TableHelper INT_10 = new TableHelper() {
+ @Override
+ public String createSql() {
+ return "CREATE TABLE `Int10` ("
+ + "`a` INTEGER,"
+ + " `b` INTEGER,"
+ + " `c` INTEGER,"
+ + " `d` INTEGER,"
+ + " `e` INTEGER,"
+ + " `f` INTEGER,"
+ + " `g` INTEGER,"
+ + " `h` INTEGER,"
+ + " `i` INTEGER,"
+ + " `j` INTEGER,"
+ + " PRIMARY KEY(`a`))";
+ }
+
+ @Override
+ public String insertSql() {
+ return "INSERT INTO `Int10`(`a`,`b`,`c`,`d`,`e`,`f`,`g`,`h`,`i`,`j`)"
+ + " VALUES (?,?,?,?,?,?,?,?,?,?)";
+ }
+
+ @Override
+ public Object[] createItem(int id) {
+ return new Object[] {
+ id,
+ id + 1,
+ id + 2,
+ id + 3,
+ id + 4,
+ id + 5,
+ id + 6,
+ id + 7,
+ id + 8,
+ id + 9,
+ };
+ }
+
+ @Override
+ public String readSql() {
+ return "SELECT * from Int10";
+ }
+
+ @Override
+ public CursorReader createReader(final Cursor cursor) {
+ final int cursorIndexOfA = cursor.getColumnIndexOrThrow("a");
+ final int cursorIndexOfB = cursor.getColumnIndexOrThrow("b");
+ final int cursorIndexOfC = cursor.getColumnIndexOrThrow("c");
+ final int cursorIndexOfD = cursor.getColumnIndexOrThrow("d");
+ final int cursorIndexOfE = cursor.getColumnIndexOrThrow("e");
+ final int cursorIndexOfF = cursor.getColumnIndexOrThrow("f");
+ final int cursorIndexOfG = cursor.getColumnIndexOrThrow("g");
+ final int cursorIndexOfH = cursor.getColumnIndexOrThrow("h");
+ final int cursorIndexOfI = cursor.getColumnIndexOrThrow("i");
+ final int cursorIndexOfJ = cursor.getColumnIndexOrThrow("j");
+ return () -> {
+ cursor.getInt(cursorIndexOfA);
+ cursor.getInt(cursorIndexOfB);
+ cursor.getInt(cursorIndexOfC);
+ cursor.getInt(cursorIndexOfD);
+ cursor.getInt(cursorIndexOfE);
+ cursor.getInt(cursorIndexOfF);
+ cursor.getInt(cursorIndexOfG);
+ cursor.getInt(cursorIndexOfH);
+ cursor.getInt(cursorIndexOfI);
+ cursor.getInt(cursorIndexOfJ);
+ };
+ }
+ };
+
+ /**
+ * Mock up of 'user' table with various ints/strings
+ */
+ public static TableHelper USER = new TableHelper() {
+ @Override
+ public String createSql() {
+ return "CREATE TABLE `User` ("
+ + "`mId` INTEGER,"
+ + " `mName` TEXT,"
+ + " `mLastName` TEXT,"
+ + " `mAge` INTEGER,"
+ + " `mAdmin` INTEGER,"
+ + " `mWeight` DOUBLE,"
+ + " `mBirthday` INTEGER,"
+ + " `mMoreText` TEXT,"
+ + " PRIMARY KEY(`mId`))";
+ }
+
+ @Override
+ public String insertSql() {
+ return "INSERT INTO `User`(`mId`,`mName`,`mLastName`,`mAge`,"
+ + "`mAdmin`,`mWeight`,`mBirthday`,`mMoreText`) VALUES (?,?,?,?,?,?,?,?)";
+ }
+
+ @Override
+ public Object[] createItem(int id) {
+ return new Object[] {
+ id,
+ UUID.randomUUID().toString(),
+ UUID.randomUUID().toString(),
+ (int) (10 + Math.random() * 50),
+ 0,
+ (float)0,
+ new Date().getTime(),
+ UUID.randomUUID().toString(),
+ };
+ }
+
+ @Override
+ public String readSql() {
+ return "SELECT * from User";
+ }
+
+ @Override
+ public CursorReader createReader(final Cursor cursor) {
+ final int cursorIndexOfMId = cursor.getColumnIndexOrThrow("mId");
+ final int cursorIndexOfMName = cursor.getColumnIndexOrThrow("mName");
+ final int cursorIndexOfMLastName = cursor.getColumnIndexOrThrow("mLastName");
+ final int cursorIndexOfMAge = cursor.getColumnIndexOrThrow("mAge");
+ final int cursorIndexOfMAdmin = cursor.getColumnIndexOrThrow("mAdmin");
+ final int cursorIndexOfMWeight = cursor.getColumnIndexOrThrow("mWeight");
+ final int cursorIndexOfMBirthday = cursor.getColumnIndexOrThrow("mBirthday");
+ final int cursorIndexOfMMoreTextField = cursor.getColumnIndexOrThrow("mMoreText");
+ return () -> {
+ cursor.getInt(cursorIndexOfMId);
+ cursor.getString(cursorIndexOfMName);
+ cursor.getString(cursorIndexOfMLastName);
+ cursor.getInt(cursorIndexOfMAge);
+ cursor.getInt(cursorIndexOfMAdmin);
+ cursor.getFloat(cursorIndexOfMWeight);
+ if (!cursor.isNull(cursorIndexOfMBirthday)) {
+ cursor.getLong(cursorIndexOfMBirthday);
+ }
+ cursor.getString(cursorIndexOfMMoreTextField);
+ };
+ }
+ };
+
+ public static TableHelper[] TABLE_HELPERS = new TableHelper[] {
+ INT_1,
+ INT_10,
+ USER,
+ };
+}