1 /* 2 * Copyright (C) 2009 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.quicksearchbox.benchmarks; 18 19 import android.app.Activity; 20 import android.app.SearchManager; 21 import android.app.SearchableInfo; 22 import android.content.ComponentName; 23 import android.content.ContentResolver; 24 import android.database.ContentObserver; 25 import android.database.Cursor; 26 import android.database.DataSetObserver; 27 import android.net.Uri; 28 import android.os.Bundle; 29 import android.os.Handler; 30 import android.os.Looper; 31 import android.util.Log; 32 33 import java.util.concurrent.ExecutorService; 34 import java.util.concurrent.Executors; 35 36 public abstract class SourceLatency extends Activity { 37 38 private static final String TAG = "SourceLatency"; 39 40 private SearchManager mSearchManager; 41 42 private ExecutorService mExecutorService; 43 44 @Override onCreate(Bundle savedInstanceState)45 protected void onCreate(Bundle savedInstanceState) { 46 super.onCreate(savedInstanceState); 47 48 mSearchManager = (SearchManager) getSystemService(SEARCH_SERVICE); 49 mExecutorService = Executors.newSingleThreadExecutor(); 50 } 51 52 @Override onResume()53 protected void onResume() { 54 super.onResume(); 55 56 // TODO: call finish() when all tasks are done 57 } 58 getSearchable(ComponentName componentName)59 private SearchableInfo getSearchable(ComponentName componentName) { 60 SearchableInfo searchable = mSearchManager.getSearchableInfo(componentName); 61 if (searchable == null || searchable.getSuggestAuthority() == null) { 62 throw new RuntimeException("Component is not searchable: " 63 + componentName.flattenToShortString()); 64 } 65 return searchable; 66 } 67 68 /** 69 * Keeps track of timings in nanoseconds. 70 */ 71 private static class ElapsedTime { 72 private long mTotal = 0; 73 private int mCount = 0; addTime(long time)74 public synchronized void addTime(long time) { 75 mTotal += time; 76 mCount++; 77 } getTotal()78 public synchronized long getTotal() { 79 return mTotal; 80 } getAverage()81 public synchronized long getAverage() { 82 return mTotal / mCount; 83 } getCount()84 public synchronized int getCount() { 85 return mCount; 86 } 87 } 88 checkSourceConcurrent(final String src, final ComponentName componentName, String query, long delay)89 public void checkSourceConcurrent(final String src, final ComponentName componentName, 90 String query, long delay) { 91 final ElapsedTime time = new ElapsedTime(); 92 final SearchableInfo searchable = getSearchable(componentName); 93 int length = query.length(); 94 for (int end = 0; end <= length; end++) { 95 final String prefix = query.substring(0, end); 96 (new Thread() { 97 @Override 98 public void run() { 99 long t = checkSourceInternal(src, searchable, prefix); 100 time.addTime(t); 101 } 102 }).start(); 103 try { 104 Thread.sleep(delay); 105 } catch (InterruptedException ex) { 106 Log.e(TAG, "sleep() in checkSourceConcurrent() interrupted."); 107 } 108 } 109 int count = length + 1; 110 // wait for all requests to finish 111 while (time.getCount() < count) { 112 try { 113 Thread.sleep(1000); 114 } catch (InterruptedException ex) { 115 Log.e(TAG, "sleep() in checkSourceConcurrent() interrupted."); 116 } 117 } 118 Log.d(TAG, src + "[DONE]: " + length + " queries in " + formatTime(time.getAverage()) 119 + " (average), " + formatTime(time.getTotal()) + " (total)"); 120 } 121 checkSource(String src, ComponentName componentName, String[] queries)122 public void checkSource(String src, ComponentName componentName, String[] queries) { 123 ElapsedTime time = new ElapsedTime(); 124 int count = queries.length; 125 for (int i = 0; i < queries.length; i++) { 126 long t = checkSource(src, componentName, queries[i]); 127 time.addTime(t); 128 } 129 Log.d(TAG, src + "[DONE]: " + count + " queries in " + formatTime(time.getAverage()) 130 + " (average), " + formatTime(time.getTotal()) + " (total)"); 131 } 132 checkSource(String src, ComponentName componentName, String query)133 public long checkSource(String src, ComponentName componentName, String query) { 134 SearchableInfo searchable = getSearchable(componentName); 135 return checkSourceInternal(src, searchable, query); 136 } 137 checkSourceInternal(String src, SearchableInfo searchable, String query)138 private long checkSourceInternal(String src, SearchableInfo searchable, String query) { 139 Cursor cursor = null; 140 try { 141 final long start = System.nanoTime(); 142 cursor = getSuggestions(searchable, query); 143 long end = System.nanoTime(); 144 long elapsed = end - start; 145 if (cursor == null) { 146 Log.d(TAG, src + ": null cursor in " + formatTime(elapsed) 147 + " for '" + query + "'"); 148 } else { 149 Log.d(TAG, src + ": " + cursor.getCount() + " rows in " + formatTime(elapsed) 150 + " for '" + query + "'"); 151 } 152 return elapsed; 153 } finally { 154 if (cursor != null) { 155 cursor.close(); 156 } 157 } 158 } 159 getSuggestions(SearchableInfo searchable, String query)160 public Cursor getSuggestions(SearchableInfo searchable, String query) { 161 return getSuggestions(searchable, query, -1); 162 } 163 getSuggestions(SearchableInfo searchable, String query, int limit)164 public Cursor getSuggestions(SearchableInfo searchable, String query, int limit) { 165 if (searchable == null) { 166 return null; 167 } 168 169 String authority = searchable.getSuggestAuthority(); 170 if (authority == null) { 171 return null; 172 } 173 174 Uri.Builder uriBuilder = new Uri.Builder() 175 .scheme(ContentResolver.SCHEME_CONTENT) 176 .authority(authority) 177 .query("") // TODO: Remove, workaround for a bug in Uri.writeToParcel() 178 .fragment(""); // TODO: Remove, workaround for a bug in Uri.writeToParcel() 179 180 // if content path provided, insert it now 181 final String contentPath = searchable.getSuggestPath(); 182 if (contentPath != null) { 183 uriBuilder.appendEncodedPath(contentPath); 184 } 185 186 // append standard suggestion query path 187 uriBuilder.appendPath(SearchManager.SUGGEST_URI_PATH_QUERY); 188 189 // get the query selection, may be null 190 String selection = searchable.getSuggestSelection(); 191 // inject query, either as selection args or inline 192 String[] selArgs = null; 193 if (selection != null) { // use selection if provided 194 selArgs = new String[] { query }; 195 } else { // no selection, use REST pattern 196 uriBuilder.appendPath(query); 197 } 198 199 if (limit > 0) { 200 uriBuilder.appendQueryParameter(SearchManager.SUGGEST_PARAMETER_LIMIT, 201 String.valueOf(limit)); 202 } 203 204 Uri uri = uriBuilder.build(); 205 206 // finally, make the query 207 return getContentResolver().query(uri, null, selection, selArgs, null); 208 } 209 formatTime(long ns)210 private static String formatTime(long ns) { 211 return (ns / 1000000.0d) + " ms"; 212 } 213 checkLiveSource(String src, ComponentName componentName, String query)214 public void checkLiveSource(String src, ComponentName componentName, String query) { 215 mExecutorService.submit(new LiveSourceCheck(src, componentName, query)); 216 } 217 218 private class LiveSourceCheck implements Runnable { 219 220 private String mSrc; 221 private SearchableInfo mSearchable; 222 private String mQuery; 223 private Handler mHandler = new Handler(Looper.getMainLooper()); 224 LiveSourceCheck(String src, ComponentName componentName, String query)225 public LiveSourceCheck(String src, ComponentName componentName, String query) { 226 mSrc = src; 227 mSearchable = mSearchManager.getSearchableInfo(componentName); 228 assert(mSearchable != null); 229 assert(mSearchable.getSuggestAuthority() != null); 230 mQuery = query; 231 } 232 run()233 public void run() { 234 Cursor cursor = null; 235 try { 236 final long start = System.nanoTime(); 237 cursor = getSuggestions(mSearchable, mQuery); 238 long end = System.nanoTime(); 239 long elapsed = (end - start); 240 if (cursor == null) { 241 Log.d(TAG, mSrc + ": null cursor in " + formatTime(elapsed) 242 + " for '" + mQuery + "'"); 243 } else { 244 Log.d(TAG, mSrc + ": " + cursor.getCount() + " rows in " + formatTime(elapsed) 245 + " for '" + mQuery + "'"); 246 cursor.registerContentObserver(new ChangeObserver(cursor)); 247 cursor.registerDataSetObserver(new MyDataSetObserver(mSrc, start, cursor)); 248 try { 249 Thread.sleep(2000); 250 } catch (InterruptedException ex) { 251 Log.d(TAG, mSrc + ": interrupted"); 252 } 253 } 254 } finally { 255 if (cursor != null) { 256 cursor.close(); 257 } 258 } 259 } 260 261 private class ChangeObserver extends ContentObserver { 262 private Cursor mCursor; 263 ChangeObserver(Cursor cursor)264 public ChangeObserver(Cursor cursor) { 265 super(mHandler); 266 mCursor = cursor; 267 } 268 269 @Override deliverSelfNotifications()270 public boolean deliverSelfNotifications() { 271 return true; 272 } 273 274 @Override onChange(boolean selfChange)275 public void onChange(boolean selfChange) { 276 mCursor.requery(); 277 } 278 } 279 280 private class MyDataSetObserver extends DataSetObserver { 281 private long mStart; 282 private Cursor mCursor; 283 private int mUpdateCount = 0; 284 MyDataSetObserver(String src, long start, Cursor cursor)285 public MyDataSetObserver(String src, long start, Cursor cursor) { 286 mSrc = src; 287 mStart = start; 288 mCursor = cursor; 289 } 290 291 @Override onChanged()292 public void onChanged() { 293 long end = System.nanoTime(); 294 long elapsed = end - mStart; 295 mUpdateCount++; 296 Log.d(TAG, mSrc + ", update " + mUpdateCount + ": " + mCursor.getCount() 297 + " rows in " + formatTime(elapsed)); 298 } 299 300 @Override onInvalidated()301 public void onInvalidated() { 302 Log.d(TAG, mSrc + ": invalidated"); 303 } 304 } 305 } 306 307 308 } 309