michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: package org.mozilla.gecko.tests; michael@0: michael@0: import java.io.File; michael@0: import java.util.Random; michael@0: import java.util.UUID; michael@0: michael@0: import org.mozilla.gecko.db.BrowserContract; michael@0: import org.mozilla.gecko.db.BrowserProvider; michael@0: import org.mozilla.gecko.db.LocalBrowserDB; michael@0: import org.mozilla.gecko.util.FileUtils; michael@0: michael@0: import android.content.ContentProvider; michael@0: import android.content.ContentProviderClient; michael@0: import android.content.ContentResolver; michael@0: import android.content.ContentValues; michael@0: import android.database.Cursor; michael@0: import android.database.sqlite.SQLiteDatabase; michael@0: import android.net.Uri; michael@0: import android.os.SystemClock; michael@0: import android.util.Log; michael@0: michael@0: /** michael@0: * This test is meant to exercise the performance of Fennec's history and michael@0: * bookmarks content provider. michael@0: * michael@0: * It does not extend ContentProviderTest because that class is unable to michael@0: * accurately assess the performance of the ContentProvider -- it's a second michael@0: * instance of a class that's only supposed to exist once, wrapped in a bunch of michael@0: * junk. michael@0: * michael@0: * Instead, we directly use the existing ContentProvider, accessing a new michael@0: * profile directory that we initialize via BrowserDB. michael@0: */ michael@0: public class testBrowserProviderPerf extends BaseRobocopTest { michael@0: private final int NUMBER_OF_BASIC_HISTORY_URLS = 10000; michael@0: private final int NUMBER_OF_BASIC_BOOKMARK_URLS = 500; michael@0: private final int NUMBER_OF_COMBINED_URLS = 500; michael@0: michael@0: private final int NUMBER_OF_KNOWN_URLS = 200; michael@0: private final int BATCH_SIZE = 500; michael@0: michael@0: // Include spaces in prefix to test performance querying with michael@0: // multiple constraint words. michael@0: private final String KNOWN_PREFIX = "my mozilla test "; michael@0: michael@0: private Random mGenerator; michael@0: michael@0: private final String MOBILE_FOLDER_GUID = "mobile"; michael@0: private long mMobileFolderId; michael@0: private ContentResolver mResolver; michael@0: private String mProfile; michael@0: private Uri mHistoryURI; michael@0: private Uri mBookmarksURI; michael@0: private Uri mFaviconsURI; michael@0: michael@0: @Override michael@0: protected Type getTestType() { michael@0: return Type.TALOS; michael@0: } michael@0: michael@0: private void loadMobileFolderId() throws Exception { michael@0: Cursor c = mResolver.query(mBookmarksURI, null, michael@0: BrowserContract.Bookmarks.GUID + " = ?", michael@0: new String[] { MOBILE_FOLDER_GUID }, michael@0: null); michael@0: c.moveToFirst(); michael@0: mMobileFolderId = c.getLong(c.getColumnIndex(BrowserContract.Bookmarks._ID)); michael@0: michael@0: c.close(); michael@0: } michael@0: michael@0: private ContentValues createBookmarkEntry(String title, String url, long parentId, michael@0: int type, int position, String tags, String description, String keyword) throws Exception { michael@0: ContentValues bookmark = new ContentValues(); michael@0: michael@0: bookmark.put(BrowserContract.Bookmarks.TITLE, title); michael@0: bookmark.put(BrowserContract.Bookmarks.URL, url); michael@0: bookmark.put(BrowserContract.Bookmarks.PARENT, parentId); michael@0: bookmark.put(BrowserContract.Bookmarks.TYPE, type); michael@0: bookmark.put(BrowserContract.Bookmarks.POSITION, position); michael@0: bookmark.put(BrowserContract.Bookmarks.TAGS, tags); michael@0: bookmark.put(BrowserContract.Bookmarks.DESCRIPTION, description); michael@0: bookmark.put(BrowserContract.Bookmarks.KEYWORD, keyword); michael@0: michael@0: return bookmark; michael@0: } michael@0: michael@0: private ContentValues createBookmarkEntryWithUrl(String url) throws Exception { michael@0: return createBookmarkEntry(url, url, mMobileFolderId, michael@0: BrowserContract.Bookmarks.TYPE_BOOKMARK, 0, "tags", "description", "keyword"); michael@0: } michael@0: michael@0: private ContentValues createRandomBookmarkEntry() throws Exception { michael@0: return createRandomBookmarkEntry(""); michael@0: } michael@0: michael@0: private ContentValues createRandomBookmarkEntry(String knownPrefix) throws Exception { michael@0: String randomStr = createRandomUrl(knownPrefix); michael@0: return createBookmarkEntryWithUrl(randomStr); michael@0: } michael@0: michael@0: private ContentValues createHistoryEntry(String title, String url, int visits, michael@0: long lastVisited) throws Exception { michael@0: ContentValues historyEntry = new ContentValues(); michael@0: michael@0: historyEntry.put(BrowserContract.History.TITLE, title); michael@0: historyEntry.put(BrowserContract.History.URL, url); michael@0: historyEntry.put(BrowserContract.History.VISITS, visits); michael@0: historyEntry.put(BrowserContract.History.DATE_LAST_VISITED, lastVisited); michael@0: michael@0: return historyEntry; michael@0: } michael@0: michael@0: private ContentValues createHistoryEntryWithUrl(String url) throws Exception { michael@0: int visits = mGenerator.nextInt(500); michael@0: return createHistoryEntry(url, url, visits, michael@0: System.currentTimeMillis()); michael@0: } michael@0: michael@0: private ContentValues createRandomHistoryEntry() throws Exception { michael@0: return createRandomHistoryEntry(""); michael@0: } michael@0: michael@0: private ContentValues createRandomHistoryEntry(String knownPrefix) throws Exception { michael@0: String randomStr = createRandomUrl(knownPrefix); michael@0: return createHistoryEntryWithUrl(randomStr); michael@0: } michael@0: michael@0: private ContentValues createFaviconEntryWithUrl(String url) throws Exception { michael@0: ContentValues faviconEntry = new ContentValues(); michael@0: michael@0: faviconEntry.put(BrowserContract.Favicons.URL, url + "/favicon.ico"); michael@0: faviconEntry.put(BrowserContract.Favicons.PAGE_URL, url); michael@0: faviconEntry.put(BrowserContract.Favicons.DATA, url.getBytes("UTF8")); michael@0: michael@0: return faviconEntry; michael@0: } michael@0: michael@0: private String createRandomUrl(String knownPrefix) throws Exception { michael@0: return knownPrefix + UUID.randomUUID().toString(); michael@0: } michael@0: michael@0: private void addTonsOfUrls() throws Exception { michael@0: // Create some random bookmark entries. michael@0: ContentValues[] bookmarkEntries = new ContentValues[BATCH_SIZE]; michael@0: michael@0: for (int i = 0; i < NUMBER_OF_BASIC_BOOKMARK_URLS / BATCH_SIZE; i++) { michael@0: bookmarkEntries = new ContentValues[BATCH_SIZE]; michael@0: michael@0: for (int j = 0; j < BATCH_SIZE; j++) { michael@0: bookmarkEntries[j] = createRandomBookmarkEntry(); michael@0: } michael@0: michael@0: mResolver.bulkInsert(mBookmarksURI, bookmarkEntries); michael@0: } michael@0: michael@0: // Create some random history entries. michael@0: ContentValues[] historyEntries = new ContentValues[BATCH_SIZE]; michael@0: ContentValues[] faviconEntries = new ContentValues[BATCH_SIZE]; michael@0: michael@0: for (int i = 0; i < NUMBER_OF_BASIC_HISTORY_URLS / BATCH_SIZE; i++) { michael@0: historyEntries = new ContentValues[BATCH_SIZE]; michael@0: faviconEntries = new ContentValues[BATCH_SIZE]; michael@0: michael@0: for (int j = 0; j < BATCH_SIZE; j++) { michael@0: historyEntries[j] = createRandomHistoryEntry(); michael@0: faviconEntries[j] = createFaviconEntryWithUrl(historyEntries[j].getAsString(BrowserContract.History.URL)); michael@0: } michael@0: michael@0: mResolver.bulkInsert(mHistoryURI, historyEntries); michael@0: mResolver.bulkInsert(mFaviconsURI, faviconEntries); michael@0: } michael@0: michael@0: michael@0: // Create random bookmark/history entries with the same URL. michael@0: for (int i = 0; i < NUMBER_OF_COMBINED_URLS / BATCH_SIZE; i++) { michael@0: bookmarkEntries = new ContentValues[BATCH_SIZE]; michael@0: historyEntries = new ContentValues[BATCH_SIZE]; michael@0: michael@0: for (int j = 0; j < BATCH_SIZE; j++) { michael@0: String url = createRandomUrl(""); michael@0: bookmarkEntries[j] = createBookmarkEntryWithUrl(url); michael@0: historyEntries[j] = createHistoryEntryWithUrl(url); michael@0: faviconEntries[j] = createFaviconEntryWithUrl(url); michael@0: } michael@0: michael@0: mResolver.bulkInsert(mBookmarksURI, bookmarkEntries); michael@0: mResolver.bulkInsert(mHistoryURI, historyEntries); michael@0: mResolver.bulkInsert(mFaviconsURI, faviconEntries); michael@0: } michael@0: michael@0: // Create some history entries with a known prefix. michael@0: historyEntries = new ContentValues[NUMBER_OF_KNOWN_URLS]; michael@0: faviconEntries = new ContentValues[NUMBER_OF_KNOWN_URLS]; michael@0: for (int i = 0; i < NUMBER_OF_KNOWN_URLS; i++) { michael@0: historyEntries[i] = createRandomHistoryEntry(KNOWN_PREFIX); michael@0: faviconEntries[i] = createFaviconEntryWithUrl(historyEntries[i].getAsString(BrowserContract.History.URL)); michael@0: } michael@0: michael@0: mResolver.bulkInsert(mHistoryURI, historyEntries); michael@0: mResolver.bulkInsert(mFaviconsURI, faviconEntries); michael@0: } michael@0: michael@0: @Override michael@0: public void setUp() throws Exception { michael@0: super.setUp(); michael@0: michael@0: mProfile = "prof" + System.currentTimeMillis(); michael@0: michael@0: mHistoryURI = prepUri(BrowserContract.History.CONTENT_URI); michael@0: mBookmarksURI = prepUri(BrowserContract.Bookmarks.CONTENT_URI); michael@0: mFaviconsURI = prepUri(BrowserContract.Favicons.CONTENT_URI); michael@0: michael@0: mResolver = getActivity().getApplicationContext().getContentResolver(); michael@0: michael@0: mGenerator = new Random(19580427); michael@0: } michael@0: michael@0: @Override michael@0: public void tearDown() { michael@0: final ContentProviderClient client = mResolver.acquireContentProviderClient(mBookmarksURI); michael@0: try { michael@0: final ContentProvider cp = client.getLocalContentProvider(); michael@0: final BrowserProvider bp = ((BrowserProvider) cp); michael@0: michael@0: // This will be the DB we were just testing. michael@0: final SQLiteDatabase db = bp.getWritableDatabaseForTesting(mBookmarksURI); michael@0: try { michael@0: db.close(); michael@0: } catch (Throwable e) { michael@0: // Nothing we can do. michael@0: } michael@0: } finally { michael@0: try { michael@0: client.release(); michael@0: } catch (Throwable e) { michael@0: // Still go ahead and try to delete the profile. michael@0: } michael@0: michael@0: try { michael@0: FileUtils.delTree(new File(mProfile), null, true); michael@0: } catch (Exception e) { michael@0: Log.w("GeckoTest", "Unable to delete profile " + mProfile, e); michael@0: } michael@0: } michael@0: } michael@0: michael@0: public Uri prepUri(Uri uri) { michael@0: return uri.buildUpon() michael@0: .appendQueryParameter(BrowserContract.PARAM_PROFILE, mProfile) michael@0: .appendQueryParameter(BrowserContract.PARAM_IS_SYNC, "1") // So we don't trigger a sync. michael@0: .build(); michael@0: } michael@0: michael@0: /** michael@0: * This method: michael@0: * michael@0: * * Adds a bunch of test data via the ContentProvider API. michael@0: * * Runs a single query against that test data via BrowserDB. michael@0: * * Reports timing for Talos. michael@0: */ michael@0: public void testBrowserProviderQueryPerf() throws Exception { michael@0: // We add at least this many results. michael@0: final int limit = 100; michael@0: michael@0: // Make sure we're querying the right profile. michael@0: final LocalBrowserDB db = new LocalBrowserDB(mProfile); michael@0: michael@0: final Cursor before = db.filter(mResolver, KNOWN_PREFIX, limit); michael@0: try { michael@0: mAsserter.is(before.getCount(), 0, "Starts empty"); michael@0: } finally { michael@0: before.close(); michael@0: } michael@0: michael@0: // Add data. michael@0: loadMobileFolderId(); michael@0: addTonsOfUrls(); michael@0: michael@0: // Wait for a little while after inserting data. We do this because michael@0: // this test launches about:home, and Top Sites watches for DB changes. michael@0: // We don't have a good way for it to only watch changes related to michael@0: // its current profile, nor is it convenient for us to launch a different michael@0: // activity that doesn't depend on the DB. michael@0: // We can fix this by: michael@0: // * Adjusting the provider interface to allow a "don't notify" param. michael@0: // * Adjusting the interface schema to include the profile in the path, michael@0: // and only observe the correct path. michael@0: // * Launching a different activity. michael@0: Thread.sleep(5000); michael@0: michael@0: // Time the query. michael@0: final long start = SystemClock.uptimeMillis(); michael@0: final Cursor c = db.filter(mResolver, KNOWN_PREFIX, limit); michael@0: michael@0: try { michael@0: final int count = c.getCount(); michael@0: final long end = SystemClock.uptimeMillis(); michael@0: michael@0: mAsserter.is(count, limit, "Retrieved results"); michael@0: mAsserter.dumpLog("Results: " + count); michael@0: mAsserter.dumpLog("__start_report" + Long.toString(end - start) + "__end_report"); michael@0: mAsserter.dumpLog("__startTimestamp" + Long.toString(end - start) + "__endTimestamp"); michael@0: } finally { michael@0: c.close(); michael@0: } michael@0: } michael@0: }