summaryrefslogtreecommitdiff
path: root/android/database
diff options
context:
space:
mode:
authorJustin Klaassen <justinklaassen@google.com>2017-10-24 19:50:40 -0400
committerJustin Klaassen <justinklaassen@google.com>2017-10-24 19:50:40 -0400
commit47ed54e5d312f899507d28d6e95ccc18a0de19fe (patch)
tree7a2d435c55c36fbc1d07e895bd0c68b18f84e12c /android/database
parent07f9f65561c2b81bcd189b895b31bb2ad0438d74 (diff)
downloadandroid-28-47ed54e5d312f899507d28d6e95ccc18a0de19fe.tar.gz
Import Android SDK Platform P [4413397]
/google/data/ro/projects/android/fetch_artifact \ --bid 4413397 \ --target sdk_phone_armv7-win_sdk \ sdk-repo-linux-sources-4413397.zip AndroidVersion.ApiLevel has been modified to appear as 28 Change-Id: I3cf1f7c36e61c090dcc7de7bcfa812ef2bf96c00
Diffstat (limited to 'android/database')
-rw-r--r--android/database/CursorWindow.java45
-rw-r--r--android/database/SQLiteDatabaseIoPerfTest.java176
-rw-r--r--android/database/SQLiteDatabasePerfTest.java223
3 files changed, 432 insertions, 12 deletions
diff --git a/android/database/CursorWindow.java b/android/database/CursorWindow.java
index a75372ff..f84ec65f 100644
--- a/android/database/CursorWindow.java
+++ b/android/database/CursorWindow.java
@@ -16,8 +16,7 @@
package android.database;
-import dalvik.system.CloseGuard;
-
+import android.annotation.BytesLong;
import android.content.res.Resources;
import android.database.sqlite.SQLiteClosable;
import android.database.sqlite.SQLiteException;
@@ -26,8 +25,10 @@ import android.os.Parcel;
import android.os.Parcelable;
import android.os.Process;
import android.util.Log;
-import android.util.SparseIntArray;
import android.util.LongSparseArray;
+import android.util.SparseIntArray;
+
+import dalvik.system.CloseGuard;
/**
* A buffer containing multiple cursor rows.
@@ -94,19 +95,29 @@ public class CursorWindow extends SQLiteClosable implements Parcelable {
* @param name The name of the cursor window, or null if none.
*/
public CursorWindow(String name) {
+ this(name, getCursorWindowSize());
+ }
+
+ /**
+ * Creates a new empty cursor window and gives it a name.
+ * <p>
+ * The cursor initially has no rows or columns. Call {@link #setNumColumns(int)} to
+ * set the number of columns before adding any rows to the cursor.
+ * </p>
+ *
+ * @param name The name of the cursor window, or null if none.
+ * @param windowSizeBytes Size of cursor window in bytes.
+ * <p><strong>Note:</strong> Memory is dynamically allocated as data rows are added to the
+ * window. Depending on the amount of data stored, the actual amount of memory allocated can be
+ * lower than specified size, but cannot exceed it.
+ */
+ public CursorWindow(String name, @BytesLong long windowSizeBytes) {
mStartPos = 0;
mName = name != null && name.length() != 0 ? name : "<unnamed>";
- if (sCursorWindowSize < 0) {
- /** The cursor window size. resource xml file specifies the value in kB.
- * convert it to bytes here by multiplying with 1024.
- */
- sCursorWindowSize = Resources.getSystem().getInteger(
- com.android.internal.R.integer.config_cursorWindowSize) * 1024;
- }
- mWindowPtr = nativeCreate(mName, sCursorWindowSize);
+ mWindowPtr = nativeCreate(mName, (int) windowSizeBytes);
if (mWindowPtr == 0) {
throw new CursorWindowAllocationException("Cursor window allocation of " +
- (sCursorWindowSize / 1024) + " kb failed. " + printStats());
+ windowSizeBytes + " bytes failed. " + printStats());
}
mCloseGuard.open("close");
recordNewWindow(Binder.getCallingPid(), mWindowPtr);
@@ -773,6 +784,16 @@ public class CursorWindow extends SQLiteClosable implements Parcelable {
return "# Open Cursors=" + total + s;
}
+ private static int getCursorWindowSize() {
+ if (sCursorWindowSize < 0) {
+ // The cursor window size. resource xml file specifies the value in kB.
+ // convert it to bytes here by multiplying with 1024.
+ sCursorWindowSize = Resources.getSystem().getInteger(
+ com.android.internal.R.integer.config_cursorWindowSize) * 1024;
+ }
+ return sCursorWindowSize;
+ }
+
@Override
public String toString() {
return getName() + " {" + Long.toHexString(mWindowPtr) + "}";
diff --git a/android/database/SQLiteDatabaseIoPerfTest.java b/android/database/SQLiteDatabaseIoPerfTest.java
new file mode 100644
index 00000000..7c5316d2
--- /dev/null
+++ b/android/database/SQLiteDatabaseIoPerfTest.java
@@ -0,0 +1,176 @@
+/*
+ * 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 android.app.Activity;
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.sqlite.SQLiteDatabase;
+import android.os.Bundle;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.LargeTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.util.ArrayMap;
+import android.util.Log;
+
+import com.android.internal.util.Preconditions;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.util.List;
+import java.util.Map;
+
+import static org.junit.Assert.assertEquals;
+
+/**
+ * Performance tests for measuring amount of data written during typical DB operations
+ *
+ * <p>To run: bit CorePerfTests:android.database.SQLiteDatabaseIoPerfTest
+ */
+@RunWith(AndroidJUnit4.class)
+@LargeTest
+public class SQLiteDatabaseIoPerfTest {
+ private static final String TAG = "SQLiteDatabaseIoPerfTest";
+ private static final String DB_NAME = "db_io_perftest";
+ private static final int DEFAULT_DATASET_SIZE = 500;
+
+ private Long mWriteBytes;
+
+ private SQLiteDatabase mDatabase;
+ private Context mContext;
+
+ @Before
+ public void setUp() {
+ mContext = InstrumentationRegistry.getTargetContext();
+ mContext.deleteDatabase(DB_NAME);
+ mDatabase = mContext.openOrCreateDatabase(DB_NAME, Context.MODE_PRIVATE, null);
+ mDatabase.execSQL("CREATE TABLE T1 "
+ + "(_ID INTEGER PRIMARY KEY, COL_A INTEGER, COL_B VARCHAR(100), COL_C REAL)");
+ }
+
+ @After
+ public void tearDown() {
+ mDatabase.close();
+ mContext.deleteDatabase(DB_NAME);
+ }
+
+ @Test
+ public void testDatabaseModifications() {
+ startMeasuringWrites();
+ ContentValues cv = new ContentValues();
+ String[] whereArg = new String[1];
+ for (int i = 0; i < DEFAULT_DATASET_SIZE; i++) {
+ cv.put("_ID", i);
+ cv.put("COL_A", i);
+ cv.put("COL_B", "NewValue");
+ cv.put("COL_C", 1.0);
+ assertEquals(i, mDatabase.insert("T1", null, cv));
+ }
+ cv = new ContentValues();
+ for (int i = 0; i < DEFAULT_DATASET_SIZE; i++) {
+ cv.put("COL_B", "UpdatedValue");
+ cv.put("COL_C", 1.1);
+ whereArg[0] = String.valueOf(i);
+ assertEquals(1, mDatabase.update("T1", cv, "_ID=?", whereArg));
+ }
+ for (int i = 0; i < DEFAULT_DATASET_SIZE; i++) {
+ whereArg[0] = String.valueOf(i);
+ assertEquals(1, mDatabase.delete("T1", "_ID=?", whereArg));
+ }
+ // Make sure all changes are written to disk
+ mDatabase.close();
+ long bytes = endMeasuringWrites();
+ sendResults("testDatabaseModifications" , bytes);
+ }
+
+ @Test
+ public void testInsertsWithTransactions() {
+ startMeasuringWrites();
+ final int txSize = 10;
+ ContentValues cv = new ContentValues();
+ for (int i = 0; i < DEFAULT_DATASET_SIZE * 5; i++) {
+ if (i % txSize == 0) {
+ mDatabase.beginTransaction();
+ }
+ if (i % txSize == txSize-1) {
+ mDatabase.setTransactionSuccessful();
+ mDatabase.endTransaction();
+
+ }
+ cv.put("_ID", i);
+ cv.put("COL_A", i);
+ cv.put("COL_B", "NewValue");
+ cv.put("COL_C", 1.0);
+ assertEquals(i, mDatabase.insert("T1", null, cv));
+ }
+ // Make sure all changes are written to disk
+ mDatabase.close();
+ long bytes = endMeasuringWrites();
+ sendResults("testInsertsWithTransactions" , bytes);
+ }
+
+ private void startMeasuringWrites() {
+ Preconditions.checkState(mWriteBytes == null, "Measurement already started");
+ mWriteBytes = getIoStats().get("write_bytes");
+ }
+
+ private long endMeasuringWrites() {
+ Preconditions.checkState(mWriteBytes != null, "Measurement wasn't started");
+ Long newWriteBytes = getIoStats().get("write_bytes");
+ return newWriteBytes - mWriteBytes;
+ }
+
+ private void sendResults(String testName, long writeBytes) {
+ Log.i(TAG, testName + " write_bytes: " + writeBytes);
+ Bundle status = new Bundle();
+ status.putLong("write_bytes", writeBytes);
+ InstrumentationRegistry.getInstrumentation().sendStatus(Activity.RESULT_OK, status);
+ }
+
+ private static Map<String, Long> getIoStats() {
+ String ioStat = "/proc/self/io";
+ Map<String, Long> results = new ArrayMap<>();
+ try {
+ List<String> lines = Files.readAllLines(new File(ioStat).toPath());
+ for (String line : lines) {
+ line = line.trim();
+ String[] split = line.split(":");
+ if (split.length == 2) {
+ try {
+ String key = split[0].trim();
+ Long value = Long.valueOf(split[1].trim());
+ results.put(key, value);
+ } catch (NumberFormatException e) {
+ Log.e(TAG, "Cannot parse number from " + line);
+ }
+ } else if (line.isEmpty()) {
+ Log.e(TAG, "Cannot parse line " + line);
+ }
+ }
+ } catch (IOException e) {
+ Log.e(TAG, "Can't read: " + ioStat, e);
+ }
+ return results;
+ }
+
+}
diff --git a/android/database/SQLiteDatabasePerfTest.java b/android/database/SQLiteDatabasePerfTest.java
new file mode 100644
index 00000000..7a32c0cc
--- /dev/null
+++ b/android/database/SQLiteDatabasePerfTest.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 android.content.ContentValues;
+import android.content.Context;
+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.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Random;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Performance tests for typical CRUD operations and loading rows into the Cursor
+ *
+ * <p>To run: bit CorePerfTests:android.database.SQLiteDatabasePerfTest
+ */
+@RunWith(AndroidJUnit4.class)
+@LargeTest
+public class SQLiteDatabasePerfTest {
+ // TODO b/64262688 Add Concurrency tests to compare WAL vs DELETE read/write
+ private static final String DB_NAME = "dbperftest";
+ private static final int DEFAULT_DATASET_SIZE = 1000;
+
+ @Rule
+ public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
+ private SQLiteDatabase mDatabase;
+ private Context mContext;
+
+ @Before
+ public void setUp() {
+ mContext = InstrumentationRegistry.getTargetContext();
+ mContext.deleteDatabase(DB_NAME);
+ mDatabase = mContext.openOrCreateDatabase(DB_NAME, Context.MODE_PRIVATE, null);
+ mDatabase.execSQL("CREATE TABLE T1 "
+ + "(_ID INTEGER PRIMARY KEY, COL_A INTEGER, COL_B VARCHAR(100), COL_C REAL)");
+ mDatabase.execSQL("CREATE TABLE T2 ("
+ + "_ID INTEGER PRIMARY KEY, COL_A VARCHAR(100), T1_ID INTEGER,"
+ + "FOREIGN KEY(T1_ID) REFERENCES T1 (_ID))");
+ }
+
+ @After
+ public void tearDown() {
+ mDatabase.close();
+ mContext.deleteDatabase(DB_NAME);
+ }
+
+ @Test
+ public void testSelect() {
+ insertT1TestDataSet();
+
+ BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+
+ Random rnd = new Random(0);
+ while (state.keepRunning()) {
+ int index = rnd.nextInt(DEFAULT_DATASET_SIZE);
+ try (Cursor cursor = mDatabase.rawQuery("SELECT _ID, COL_A, COL_B, COL_C FROM T1 "
+ + "WHERE _ID=?", new String[]{String.valueOf(index)})) {
+ assertTrue(cursor.moveToNext());
+ assertEquals(index, cursor.getInt(0));
+ assertEquals(index, cursor.getInt(1));
+ assertEquals("T1Value" + index, cursor.getString(2));
+ assertEquals(1.1 * index, cursor.getDouble(3), 0.0000001d);
+ }
+ }
+ }
+
+ @Test
+ public void testSelectMultipleRows() {
+ insertT1TestDataSet();
+
+ BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ Random rnd = new Random(0);
+ final int querySize = 50;
+ while (state.keepRunning()) {
+ int index = rnd.nextInt(DEFAULT_DATASET_SIZE - querySize - 1);
+ try (Cursor cursor = mDatabase.rawQuery("SELECT _ID, COL_A, COL_B, COL_C FROM T1 "
+ + "WHERE _ID BETWEEN ? and ? ORDER BY _ID",
+ new String[]{String.valueOf(index), String.valueOf(index + querySize - 1)})) {
+ int i = 0;
+ while(cursor.moveToNext()) {
+ assertEquals(index, cursor.getInt(0));
+ assertEquals(index, cursor.getInt(1));
+ assertEquals("T1Value" + index, cursor.getString(2));
+ assertEquals(1.1 * index, cursor.getDouble(3), 0.0000001d);
+ index++;
+ i++;
+ }
+ assertEquals(querySize, i);
+ }
+ }
+ }
+
+ @Test
+ public void testInnerJoin() {
+ mDatabase.setForeignKeyConstraintsEnabled(true);
+ mDatabase.beginTransaction();
+ insertT1TestDataSet();
+ insertT2TestDataSet();
+ mDatabase.setTransactionSuccessful();
+ mDatabase.endTransaction();
+
+ BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+
+ Random rnd = new Random(0);
+ while (state.keepRunning()) {
+ int index = rnd.nextInt(1000);
+ try (Cursor cursor = mDatabase.rawQuery(
+ "SELECT T1._ID, T1.COL_A, T1.COL_B, T1.COL_C, T2.COL_A FROM T1 "
+ + "INNER JOIN T2 on T2.T1_ID=T1._ID WHERE T1._ID = ?",
+ new String[]{String.valueOf(index)})) {
+ assertTrue(cursor.moveToNext());
+ assertEquals(index, cursor.getInt(0));
+ assertEquals(index, cursor.getInt(1));
+ assertEquals("T1Value" + index, cursor.getString(2));
+ assertEquals(1.1 * index, cursor.getDouble(3), 0.0000001d);
+ assertEquals("T2Value" + index, cursor.getString(4));
+ }
+ }
+ }
+
+ @Test
+ public void testInsert() {
+ insertT1TestDataSet();
+
+ BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+
+ ContentValues cv = new ContentValues();
+ cv.put("_ID", DEFAULT_DATASET_SIZE);
+ cv.put("COL_B", "NewValue");
+ cv.put("COL_C", 1.1);
+ String[] deleteArgs = new String[]{String.valueOf(DEFAULT_DATASET_SIZE)};
+ while (state.keepRunning()) {
+ assertEquals(DEFAULT_DATASET_SIZE, mDatabase.insert("T1", null, cv));
+ state.pauseTiming();
+ assertEquals(1, mDatabase.delete("T1", "_ID=?", deleteArgs));
+ state.resumeTiming();
+ }
+ }
+
+ @Test
+ public void testDelete() {
+ insertT1TestDataSet();
+ BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ String[] deleteArgs = new String[]{String.valueOf(DEFAULT_DATASET_SIZE)};
+ Object[] insertsArgs = new Object[]{DEFAULT_DATASET_SIZE, DEFAULT_DATASET_SIZE,
+ "ValueToDelete", 1.1};
+
+ while (state.keepRunning()) {
+ state.pauseTiming();
+ mDatabase.execSQL("INSERT INTO T1 VALUES (?, ?, ?, ?)", insertsArgs);
+ state.resumeTiming();
+ assertEquals(1, mDatabase.delete("T1", "_ID=?", deleteArgs));
+ }
+ }
+
+ @Test
+ public void testUpdate() {
+ insertT1TestDataSet();
+ BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+
+ Random rnd = new Random(0);
+ int i = 0;
+ ContentValues cv = new ContentValues();
+ String[] argArray = new String[1];
+ while (state.keepRunning()) {
+ int id = rnd.nextInt(DEFAULT_DATASET_SIZE);
+ cv.put("COL_A", i);
+ cv.put("COL_B", "UpdatedValue");
+ cv.put("COL_C", i);
+ argArray[0] = String.valueOf(id);
+ assertEquals(1, mDatabase.update("T1", cv, "_ID=?", argArray));
+ i++;
+ }
+ }
+
+ private void insertT1TestDataSet() {
+ mDatabase.beginTransaction();
+ for (int i = 0; i < DEFAULT_DATASET_SIZE; i++) {
+ mDatabase.execSQL("INSERT INTO T1 VALUES (?, ?, ?, ?)",
+ new Object[]{i, i, "T1Value" + i, i * 1.1});
+ }
+ mDatabase.setTransactionSuccessful();
+ mDatabase.endTransaction();
+ }
+
+ private void insertT2TestDataSet() {
+ mDatabase.beginTransaction();
+ for (int i = 0; i < DEFAULT_DATASET_SIZE; i++) {
+ mDatabase.execSQL("INSERT INTO T2 VALUES (?, ?, ?)",
+ new Object[]{i, "T2Value" + i, i});
+ }
+ mDatabase.setTransactionSuccessful();
+ mDatabase.endTransaction();
+ }
+}
+