|
1 /* This Source Code Form is subject to the terms of the Mozilla Public |
|
2 * License, v. 2.0. If a copy of the MPL was not distributed with this |
|
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
4 |
|
5 package org.mozilla.gecko.tests; |
|
6 |
|
7 import java.io.File; |
|
8 import java.util.Random; |
|
9 import java.util.UUID; |
|
10 |
|
11 import org.mozilla.gecko.db.BrowserContract; |
|
12 import org.mozilla.gecko.db.BrowserProvider; |
|
13 import org.mozilla.gecko.db.LocalBrowserDB; |
|
14 import org.mozilla.gecko.util.FileUtils; |
|
15 |
|
16 import android.content.ContentProvider; |
|
17 import android.content.ContentProviderClient; |
|
18 import android.content.ContentResolver; |
|
19 import android.content.ContentValues; |
|
20 import android.database.Cursor; |
|
21 import android.database.sqlite.SQLiteDatabase; |
|
22 import android.net.Uri; |
|
23 import android.os.SystemClock; |
|
24 import android.util.Log; |
|
25 |
|
26 /** |
|
27 * This test is meant to exercise the performance of Fennec's history and |
|
28 * bookmarks content provider. |
|
29 * |
|
30 * It does not extend ContentProviderTest because that class is unable to |
|
31 * accurately assess the performance of the ContentProvider -- it's a second |
|
32 * instance of a class that's only supposed to exist once, wrapped in a bunch of |
|
33 * junk. |
|
34 * |
|
35 * Instead, we directly use the existing ContentProvider, accessing a new |
|
36 * profile directory that we initialize via BrowserDB. |
|
37 */ |
|
38 public class testBrowserProviderPerf extends BaseRobocopTest { |
|
39 private final int NUMBER_OF_BASIC_HISTORY_URLS = 10000; |
|
40 private final int NUMBER_OF_BASIC_BOOKMARK_URLS = 500; |
|
41 private final int NUMBER_OF_COMBINED_URLS = 500; |
|
42 |
|
43 private final int NUMBER_OF_KNOWN_URLS = 200; |
|
44 private final int BATCH_SIZE = 500; |
|
45 |
|
46 // Include spaces in prefix to test performance querying with |
|
47 // multiple constraint words. |
|
48 private final String KNOWN_PREFIX = "my mozilla test "; |
|
49 |
|
50 private Random mGenerator; |
|
51 |
|
52 private final String MOBILE_FOLDER_GUID = "mobile"; |
|
53 private long mMobileFolderId; |
|
54 private ContentResolver mResolver; |
|
55 private String mProfile; |
|
56 private Uri mHistoryURI; |
|
57 private Uri mBookmarksURI; |
|
58 private Uri mFaviconsURI; |
|
59 |
|
60 @Override |
|
61 protected Type getTestType() { |
|
62 return Type.TALOS; |
|
63 } |
|
64 |
|
65 private void loadMobileFolderId() throws Exception { |
|
66 Cursor c = mResolver.query(mBookmarksURI, null, |
|
67 BrowserContract.Bookmarks.GUID + " = ?", |
|
68 new String[] { MOBILE_FOLDER_GUID }, |
|
69 null); |
|
70 c.moveToFirst(); |
|
71 mMobileFolderId = c.getLong(c.getColumnIndex(BrowserContract.Bookmarks._ID)); |
|
72 |
|
73 c.close(); |
|
74 } |
|
75 |
|
76 private ContentValues createBookmarkEntry(String title, String url, long parentId, |
|
77 int type, int position, String tags, String description, String keyword) throws Exception { |
|
78 ContentValues bookmark = new ContentValues(); |
|
79 |
|
80 bookmark.put(BrowserContract.Bookmarks.TITLE, title); |
|
81 bookmark.put(BrowserContract.Bookmarks.URL, url); |
|
82 bookmark.put(BrowserContract.Bookmarks.PARENT, parentId); |
|
83 bookmark.put(BrowserContract.Bookmarks.TYPE, type); |
|
84 bookmark.put(BrowserContract.Bookmarks.POSITION, position); |
|
85 bookmark.put(BrowserContract.Bookmarks.TAGS, tags); |
|
86 bookmark.put(BrowserContract.Bookmarks.DESCRIPTION, description); |
|
87 bookmark.put(BrowserContract.Bookmarks.KEYWORD, keyword); |
|
88 |
|
89 return bookmark; |
|
90 } |
|
91 |
|
92 private ContentValues createBookmarkEntryWithUrl(String url) throws Exception { |
|
93 return createBookmarkEntry(url, url, mMobileFolderId, |
|
94 BrowserContract.Bookmarks.TYPE_BOOKMARK, 0, "tags", "description", "keyword"); |
|
95 } |
|
96 |
|
97 private ContentValues createRandomBookmarkEntry() throws Exception { |
|
98 return createRandomBookmarkEntry(""); |
|
99 } |
|
100 |
|
101 private ContentValues createRandomBookmarkEntry(String knownPrefix) throws Exception { |
|
102 String randomStr = createRandomUrl(knownPrefix); |
|
103 return createBookmarkEntryWithUrl(randomStr); |
|
104 } |
|
105 |
|
106 private ContentValues createHistoryEntry(String title, String url, int visits, |
|
107 long lastVisited) throws Exception { |
|
108 ContentValues historyEntry = new ContentValues(); |
|
109 |
|
110 historyEntry.put(BrowserContract.History.TITLE, title); |
|
111 historyEntry.put(BrowserContract.History.URL, url); |
|
112 historyEntry.put(BrowserContract.History.VISITS, visits); |
|
113 historyEntry.put(BrowserContract.History.DATE_LAST_VISITED, lastVisited); |
|
114 |
|
115 return historyEntry; |
|
116 } |
|
117 |
|
118 private ContentValues createHistoryEntryWithUrl(String url) throws Exception { |
|
119 int visits = mGenerator.nextInt(500); |
|
120 return createHistoryEntry(url, url, visits, |
|
121 System.currentTimeMillis()); |
|
122 } |
|
123 |
|
124 private ContentValues createRandomHistoryEntry() throws Exception { |
|
125 return createRandomHistoryEntry(""); |
|
126 } |
|
127 |
|
128 private ContentValues createRandomHistoryEntry(String knownPrefix) throws Exception { |
|
129 String randomStr = createRandomUrl(knownPrefix); |
|
130 return createHistoryEntryWithUrl(randomStr); |
|
131 } |
|
132 |
|
133 private ContentValues createFaviconEntryWithUrl(String url) throws Exception { |
|
134 ContentValues faviconEntry = new ContentValues(); |
|
135 |
|
136 faviconEntry.put(BrowserContract.Favicons.URL, url + "/favicon.ico"); |
|
137 faviconEntry.put(BrowserContract.Favicons.PAGE_URL, url); |
|
138 faviconEntry.put(BrowserContract.Favicons.DATA, url.getBytes("UTF8")); |
|
139 |
|
140 return faviconEntry; |
|
141 } |
|
142 |
|
143 private String createRandomUrl(String knownPrefix) throws Exception { |
|
144 return knownPrefix + UUID.randomUUID().toString(); |
|
145 } |
|
146 |
|
147 private void addTonsOfUrls() throws Exception { |
|
148 // Create some random bookmark entries. |
|
149 ContentValues[] bookmarkEntries = new ContentValues[BATCH_SIZE]; |
|
150 |
|
151 for (int i = 0; i < NUMBER_OF_BASIC_BOOKMARK_URLS / BATCH_SIZE; i++) { |
|
152 bookmarkEntries = new ContentValues[BATCH_SIZE]; |
|
153 |
|
154 for (int j = 0; j < BATCH_SIZE; j++) { |
|
155 bookmarkEntries[j] = createRandomBookmarkEntry(); |
|
156 } |
|
157 |
|
158 mResolver.bulkInsert(mBookmarksURI, bookmarkEntries); |
|
159 } |
|
160 |
|
161 // Create some random history entries. |
|
162 ContentValues[] historyEntries = new ContentValues[BATCH_SIZE]; |
|
163 ContentValues[] faviconEntries = new ContentValues[BATCH_SIZE]; |
|
164 |
|
165 for (int i = 0; i < NUMBER_OF_BASIC_HISTORY_URLS / BATCH_SIZE; i++) { |
|
166 historyEntries = new ContentValues[BATCH_SIZE]; |
|
167 faviconEntries = new ContentValues[BATCH_SIZE]; |
|
168 |
|
169 for (int j = 0; j < BATCH_SIZE; j++) { |
|
170 historyEntries[j] = createRandomHistoryEntry(); |
|
171 faviconEntries[j] = createFaviconEntryWithUrl(historyEntries[j].getAsString(BrowserContract.History.URL)); |
|
172 } |
|
173 |
|
174 mResolver.bulkInsert(mHistoryURI, historyEntries); |
|
175 mResolver.bulkInsert(mFaviconsURI, faviconEntries); |
|
176 } |
|
177 |
|
178 |
|
179 // Create random bookmark/history entries with the same URL. |
|
180 for (int i = 0; i < NUMBER_OF_COMBINED_URLS / BATCH_SIZE; i++) { |
|
181 bookmarkEntries = new ContentValues[BATCH_SIZE]; |
|
182 historyEntries = new ContentValues[BATCH_SIZE]; |
|
183 |
|
184 for (int j = 0; j < BATCH_SIZE; j++) { |
|
185 String url = createRandomUrl(""); |
|
186 bookmarkEntries[j] = createBookmarkEntryWithUrl(url); |
|
187 historyEntries[j] = createHistoryEntryWithUrl(url); |
|
188 faviconEntries[j] = createFaviconEntryWithUrl(url); |
|
189 } |
|
190 |
|
191 mResolver.bulkInsert(mBookmarksURI, bookmarkEntries); |
|
192 mResolver.bulkInsert(mHistoryURI, historyEntries); |
|
193 mResolver.bulkInsert(mFaviconsURI, faviconEntries); |
|
194 } |
|
195 |
|
196 // Create some history entries with a known prefix. |
|
197 historyEntries = new ContentValues[NUMBER_OF_KNOWN_URLS]; |
|
198 faviconEntries = new ContentValues[NUMBER_OF_KNOWN_URLS]; |
|
199 for (int i = 0; i < NUMBER_OF_KNOWN_URLS; i++) { |
|
200 historyEntries[i] = createRandomHistoryEntry(KNOWN_PREFIX); |
|
201 faviconEntries[i] = createFaviconEntryWithUrl(historyEntries[i].getAsString(BrowserContract.History.URL)); |
|
202 } |
|
203 |
|
204 mResolver.bulkInsert(mHistoryURI, historyEntries); |
|
205 mResolver.bulkInsert(mFaviconsURI, faviconEntries); |
|
206 } |
|
207 |
|
208 @Override |
|
209 public void setUp() throws Exception { |
|
210 super.setUp(); |
|
211 |
|
212 mProfile = "prof" + System.currentTimeMillis(); |
|
213 |
|
214 mHistoryURI = prepUri(BrowserContract.History.CONTENT_URI); |
|
215 mBookmarksURI = prepUri(BrowserContract.Bookmarks.CONTENT_URI); |
|
216 mFaviconsURI = prepUri(BrowserContract.Favicons.CONTENT_URI); |
|
217 |
|
218 mResolver = getActivity().getApplicationContext().getContentResolver(); |
|
219 |
|
220 mGenerator = new Random(19580427); |
|
221 } |
|
222 |
|
223 @Override |
|
224 public void tearDown() { |
|
225 final ContentProviderClient client = mResolver.acquireContentProviderClient(mBookmarksURI); |
|
226 try { |
|
227 final ContentProvider cp = client.getLocalContentProvider(); |
|
228 final BrowserProvider bp = ((BrowserProvider) cp); |
|
229 |
|
230 // This will be the DB we were just testing. |
|
231 final SQLiteDatabase db = bp.getWritableDatabaseForTesting(mBookmarksURI); |
|
232 try { |
|
233 db.close(); |
|
234 } catch (Throwable e) { |
|
235 // Nothing we can do. |
|
236 } |
|
237 } finally { |
|
238 try { |
|
239 client.release(); |
|
240 } catch (Throwable e) { |
|
241 // Still go ahead and try to delete the profile. |
|
242 } |
|
243 |
|
244 try { |
|
245 FileUtils.delTree(new File(mProfile), null, true); |
|
246 } catch (Exception e) { |
|
247 Log.w("GeckoTest", "Unable to delete profile " + mProfile, e); |
|
248 } |
|
249 } |
|
250 } |
|
251 |
|
252 public Uri prepUri(Uri uri) { |
|
253 return uri.buildUpon() |
|
254 .appendQueryParameter(BrowserContract.PARAM_PROFILE, mProfile) |
|
255 .appendQueryParameter(BrowserContract.PARAM_IS_SYNC, "1") // So we don't trigger a sync. |
|
256 .build(); |
|
257 } |
|
258 |
|
259 /** |
|
260 * This method: |
|
261 * |
|
262 * * Adds a bunch of test data via the ContentProvider API. |
|
263 * * Runs a single query against that test data via BrowserDB. |
|
264 * * Reports timing for Talos. |
|
265 */ |
|
266 public void testBrowserProviderQueryPerf() throws Exception { |
|
267 // We add at least this many results. |
|
268 final int limit = 100; |
|
269 |
|
270 // Make sure we're querying the right profile. |
|
271 final LocalBrowserDB db = new LocalBrowserDB(mProfile); |
|
272 |
|
273 final Cursor before = db.filter(mResolver, KNOWN_PREFIX, limit); |
|
274 try { |
|
275 mAsserter.is(before.getCount(), 0, "Starts empty"); |
|
276 } finally { |
|
277 before.close(); |
|
278 } |
|
279 |
|
280 // Add data. |
|
281 loadMobileFolderId(); |
|
282 addTonsOfUrls(); |
|
283 |
|
284 // Wait for a little while after inserting data. We do this because |
|
285 // this test launches about:home, and Top Sites watches for DB changes. |
|
286 // We don't have a good way for it to only watch changes related to |
|
287 // its current profile, nor is it convenient for us to launch a different |
|
288 // activity that doesn't depend on the DB. |
|
289 // We can fix this by: |
|
290 // * Adjusting the provider interface to allow a "don't notify" param. |
|
291 // * Adjusting the interface schema to include the profile in the path, |
|
292 // and only observe the correct path. |
|
293 // * Launching a different activity. |
|
294 Thread.sleep(5000); |
|
295 |
|
296 // Time the query. |
|
297 final long start = SystemClock.uptimeMillis(); |
|
298 final Cursor c = db.filter(mResolver, KNOWN_PREFIX, limit); |
|
299 |
|
300 try { |
|
301 final int count = c.getCount(); |
|
302 final long end = SystemClock.uptimeMillis(); |
|
303 |
|
304 mAsserter.is(count, limit, "Retrieved results"); |
|
305 mAsserter.dumpLog("Results: " + count); |
|
306 mAsserter.dumpLog("__start_report" + Long.toString(end - start) + "__end_report"); |
|
307 mAsserter.dumpLog("__startTimestamp" + Long.toString(end - start) + "__endTimestamp"); |
|
308 } finally { |
|
309 c.close(); |
|
310 } |
|
311 } |
|
312 } |