ProviderPerfActivity.java revision 80df0a552ca1c8bf2eb26ee79f1ffd9a641f3564
1/* 2 * Copyright (C) 2010 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 18// TODO: find AIDL for ContentResolver. add queryExtensible() returning struct{Cursor?,String? singleRow} 19// -- would have to lazily do real requery(), registerContentObserver(), etc 20 21package com.android.rpc_performance; 22 23import android.app.Activity; 24import android.content.ComponentName; 25import android.content.ContentQueryMap; 26import android.content.ContentResolver; 27import android.content.ContentValues; 28import android.content.IContentProvider; 29import android.content.Context; 30import android.content.Intent; 31import android.content.ServiceConnection; 32import android.content.pm.PackageManager; 33import android.content.pm.ResolveInfo; 34import android.content.res.Configuration; 35import android.content.res.Resources; 36import android.database.Cursor; 37import android.database.SQLException; 38import android.net.LocalSocket; 39import android.net.LocalSocketAddress; 40import android.net.Uri; 41import android.os.Bundle; 42import android.os.Handler; 43import android.os.IBinder; 44import android.os.RemoteException; 45import android.os.SystemClock; 46import android.telephony.TelephonyManager; 47import android.text.TextUtils; 48import android.util.AndroidException; 49import android.util.Config; 50import android.util.Log; 51import android.view.View; 52import android.widget.Button; 53import android.widget.TextView; 54 55import java.io.File; 56import java.io.FileInputStream; 57import java.io.IOException; 58import java.io.InputStream; 59import java.io.OutputStream; 60import java.io.RandomAccessFile; 61 62public class ProviderPerfActivity extends Activity { 63 64 private static final String TAG = "ProviderPerfActivity"; 65 private static final Uri SYSTEM_SETTINGS_URI = Uri.parse("content://settings/system"); 66 67 // No-op provider URLs: 68 private static final Uri CROSS_PROC_PROVIDER_URI = Uri.parse("content://com.android.rpc_performance/"); 69 private static final Uri IN_PROC_PROVIDER_URI = Uri.parse("content://com.android.rpc_performance.local/"); 70 71 private final Handler mHandler = new Handler(); 72 private final static int LOOP_TIME_MILLIS = 2000; 73 private final static long LOOP_TIME_NANOS = (long) LOOP_TIME_MILLIS * 1000000L; 74 75 private IService mServiceStub = null; 76 private ServiceConnection serviceConnection = new ServiceConnection() { 77 public void onServiceConnected(ComponentName name, IBinder service) { 78 mServiceStub = IService.Stub.asInterface(service); 79 Log.v(TAG, "Service bound"); 80 } 81 82 public void onServiceDisconnected(ComponentName name) { 83 mServiceStub = null; 84 Log.v(TAG, "Service unbound"); 85 }; 86 }; 87 88 89 private ContentResolver cr; 90 private int mIterations = 100; 91 92 /** Called when the activity is first created. */ 93 @Override 94 public void onCreate(Bundle savedInstanceState) { 95 super.onCreate(savedInstanceState); 96 setContentView(R.layout.main); 97 98 cr = getContentResolver(); 99 100 setButtonAction(R.id.file_read_button, new Runnable() { 101 public void run() { 102 final float avgTime = fileReadLoop(); 103 endAsyncOp(R.id.file_read_button, R.id.file_read_text, avgTime); 104 }}); 105 106 setButtonAction(R.id.file_write_button, new Runnable() { 107 public void run() { 108 final float avgTime = fileWriteLoop(); 109 endAsyncOp(R.id.file_write_button, R.id.file_write_text, avgTime); 110 }}); 111 112 setButtonAction(R.id.settings_read_button, new Runnable() { 113 public void run() { 114 final float avgTime = settingsProviderLoop(MODE_READ, 0); 115 endAsyncOp(R.id.settings_read_button, R.id.settings_read_text, avgTime); 116 }}); 117 118 setButtonAction(R.id.settings_sleep_button, new Runnable() { 119 public void run() { 120 final float avgTime = settingsProviderLoop(MODE_READ, 100); 121 endAsyncOp(R.id.settings_sleep_button, R.id.settings_sleep_text, avgTime); 122 }}); 123 124 setButtonAction(R.id.settings_write_button, new Runnable() { 125 public void run() { 126 final float avgTime = settingsProviderLoop(MODE_WRITE, 0); 127 endAsyncOp(R.id.settings_write_button, R.id.settings_write_text, avgTime); 128 }}); 129 130 setButtonAction(R.id.settings_writedup_button, new Runnable() { 131 public void run() { 132 final float avgTime = settingsProviderLoop(MODE_WRITE_DUP, 0); 133 endAsyncOp(R.id.settings_writedup_button, R.id.settings_writedup_text, avgTime); 134 }}); 135 136 setButtonAction(R.id.dummy_lookup_button, new Runnable() { 137 public void run() { 138 final float avgTime = noOpProviderLoop(CROSS_PROC_PROVIDER_URI); 139 endAsyncOp(R.id.dummy_lookup_button, R.id.dummy_lookup_text, avgTime); 140 }}); 141 142 setButtonAction(R.id.dummy_local_lookup_button, new Runnable() { 143 public void run() { 144 final float avgTime = noOpProviderLoop(IN_PROC_PROVIDER_URI); 145 endAsyncOp(R.id.dummy_local_lookup_button, 146 R.id.dummy_local_lookup_text, avgTime); 147 }}); 148 149 setButtonAction(R.id.localsocket_button, new Runnable() { 150 public void run() { 151 final float avgTime = localSocketLoop(); 152 endAsyncOp(R.id.localsocket_button, R.id.localsocket_text, avgTime); 153 }}); 154 155 setButtonAction(R.id.service_button, new Runnable() { 156 public void run() { 157 final float avgTime = serviceLoop(0); 158 endAsyncOp(R.id.service_button, R.id.service_text, avgTime); 159 }}); 160 161 setButtonAction(R.id.service2_button, new Runnable() { 162 public void run() { 163 final float avgTime = serviceLoop(1); 164 endAsyncOp(R.id.service2_button, R.id.service2_text, avgTime); 165 }}); 166 167 setButtonAction(R.id.proc_button, new Runnable() { 168 public void run() { 169 final float avgTime = procLoop(); 170 endAsyncOp(R.id.proc_button, R.id.proc_text, avgTime); 171 }}); 172 173 setButtonAction(R.id.call_button, new Runnable() { 174 public void run() { 175 final float avgTime = callLoop("ringtone"); 176 endAsyncOp(R.id.call_button, R.id.call_text, avgTime); 177 }}); 178 179 setButtonAction(R.id.call2_button, new Runnable() { 180 public void run() { 181 final float avgTime = callLoop("XXXXXXXX"); // non-existent 182 endAsyncOp(R.id.call2_button, R.id.call2_text, avgTime); 183 }}); 184 } 185 186 @Override public void onResume() { 187 super.onResume(); 188 189 bindService(new Intent(this, MiscService.class), 190 serviceConnection, Context.BIND_AUTO_CREATE); 191 } 192 193 @Override public void onPause() { 194 super.onPause(); 195 if (serviceConnection != null) { 196 unbindService(serviceConnection); 197 } 198 } 199 200 private void setButtonAction(int button_id, final Runnable r) { 201 final Button button = (Button) findViewById(button_id); 202 if (button == null) { 203 Log.w(TAG, "Bogus button ID: " + button_id); 204 return; 205 } 206 button.setOnClickListener(new View.OnClickListener() { 207 public void onClick(View v) { 208 TextView tv = (TextView) findViewById(R.id.iterations_edit); 209 if (tv != null) { 210 try { 211 mIterations = Integer.parseInt(tv.getText().toString()); 212 } catch (NumberFormatException e) { 213 Log.w(TAG, "Invalid iteration count", e); 214 if (tv != null) tv.setText(Integer.toString(mIterations)); 215 } 216 } 217 218 button.setEnabled(false); 219 new Thread(r).start(); 220 } 221 }); 222 } 223 224 private void endAsyncOp(final int button_id, final int text_id, final float avgTime) { 225 mHandler.post(new Runnable() { 226 public void run() { 227 Button button = (Button) findViewById(button_id); 228 button.setEnabled(true); 229 setTextTime(text_id, avgTime); 230 }}); 231 } 232 233 private void setTextTime(int id, float avgTime) { 234 TextView tv = (TextView) findViewById(id); 235 if (tv == null) return; 236 String text = tv.getText().toString(); 237 text = text.substring(0, text.indexOf(':') + 1) + "\n" + avgTime + " ms avg"; 238 tv.setText(text); 239 } 240 241 private float fileReadLoop() { 242 RandomAccessFile raf = null; 243 File filename = getFileStreamPath("test.dat"); 244 try { 245 long sumNanos = 0; 246 byte[] buf = new byte[512]; 247 248 raf = new RandomAccessFile(filename, "rw"); 249 raf.write(buf); 250 raf.close(); 251 raf = null; 252 253 // The data's almost certainly cached -- it's not clear what we're testing here 254 raf = new RandomAccessFile(filename, "r"); 255 for (int i = 0; i < mIterations; i++) { 256 long lastTime = System.nanoTime(); 257 raf.seek(0); 258 raf.read(buf); 259 sumNanos += System.nanoTime() - lastTime; 260 } 261 262 return (float) sumNanos / Math.max(1.0f, (float) mIterations) / 1000000.0f; 263 } catch (IOException e) { 264 Log.e(TAG, "File read failed", e); 265 return 0; 266 } finally { 267 try { if (raf != null) raf.close(); } catch (IOException e) {} 268 } 269 } 270 271 private float fileWriteLoop() { 272 RandomAccessFile raf = null; 273 File filename = getFileStreamPath("test.dat"); 274 try { 275 long sumNanos = 0; 276 byte[] buf = new byte[512]; 277 for (int i = 0; i < mIterations; i++) { 278 for (int j = 0; j < buf.length; j++) buf[j] = (byte) (i + j); 279 long lastTime = System.nanoTime(); 280 raf = new RandomAccessFile(filename, "rw"); 281 raf.write(buf); 282 raf.close(); 283 raf = null; 284 sumNanos += System.nanoTime() - lastTime; 285 } 286 287 return (float) sumNanos / Math.max(1.0f, (float) mIterations) / 1000000.0f; 288 } catch (IOException e) { 289 Log.e(TAG, "File read failed", e); 290 return 0; 291 } finally { 292 try { if (raf != null) raf.close(); } catch (IOException e) {} 293 } 294 } 295 296 // Returns average cross-process dummy query time in milliseconds. 297 private float noOpProviderLoop(Uri uri) { 298 long sumNanos = 0; 299 int failures = 0; 300 int total = 0; 301 for (int i = 0; i < mIterations; i++) { 302 long duration = doNoOpLookup(uri); 303 if (duration < 0) { 304 failures++; 305 } else { 306 total++; 307 sumNanos += duration; 308 } 309 } 310 float averageMillis = (float) sumNanos / 311 (float) (total != 0 ? total : 1) / 312 1000000.0f; 313 Log.v(TAG, "dummy loop: fails=" + failures + "; total=" + total + "; goodavg ms=" + averageMillis); 314 return averageMillis; 315 } 316 317 // Returns average cross-process dummy query time in milliseconds. 318 private float callLoop(String key) { 319 IContentProvider cp = cr.acquireProvider(SYSTEM_SETTINGS_URI.getAuthority()); 320 321 long sumNanos = 0; 322 int total = 0; 323 324 try { 325 for (int i = 0; i < mIterations; i++) { 326 long lastTime = System.nanoTime(); 327 Bundle b = cp.call("GET_system", key, null); 328 long nowTime = System.nanoTime(); 329 total++; 330 sumNanos += (nowTime - lastTime); 331 } 332 } catch (RemoteException e) { 333 return -999.0f; 334 } 335 float averageMillis = (float) sumNanos / 336 (float) (total != 0 ? total : 1) / 337 1000000.0f; 338 Log.v(TAG, "call loop: avg_ms=" + averageMillis + "; calls=" + total); 339 return averageMillis; 340 } 341 342 // Returns average time to read a /proc file in milliseconds. 343 private float procLoop() { 344 long sumNanos = 0; 345 int total = 0; 346 File f = new File("/proc/self/cmdline"); 347 byte[] buf = new byte[100]; 348 String value = null; 349 try { 350 for (int i = 0; i < mIterations; i++) { 351 long lastTime = System.nanoTime(); 352 FileInputStream is = new FileInputStream(f); 353 int readBytes = is.read(buf, 0, 100); 354 is.close(); 355 //value = new String(buf, 0, readBytes); 356 long nowTime = System.nanoTime(); 357 total++; 358 sumNanos += (nowTime - lastTime); 359 lastTime = nowTime; 360 } 361 } catch (IOException e) { 362 return -999.0f; 363 } 364 float averageMillis = (float) sumNanos / 365 (float) (total != 0 ? total : 1) / 366 1000000.0f; 367 Log.v(TAG, "proc loop: total: " + total + "; avg_ms=" + averageMillis + "; value=" + value); 368 return averageMillis; 369 } 370 371 private static final String[] IGNORED_COLUMN = {"ignored"}; 372 373 // Returns nanoseconds. 374 private long doNoOpLookup(Uri uri) { 375 Cursor c = null; 376 try { 377 long startTime = System.nanoTime(); 378 c = cr.query(uri, 379 IGNORED_COLUMN, //new String[]{"ignored"}, // but allocate it for apples-to-apples 380 "name=?", 381 IGNORED_COLUMN, // new String[]{"also_ignored"}, // also for equality in benchmarking 382 null /* sort order */); 383 if (c == null) { 384 Log.w(TAG, "cursor null"); 385 return -1; 386 } 387 String value = c.moveToNext() ? c.getString(0) : null; 388 long duration = System.nanoTime() - startTime; 389 //Log.v(TAG, "got value: " + value + " in " + duration); 390 return duration; 391 } catch (SQLException e) { 392 Log.w(TAG, "sqlite exception: " + e); 393 return -1; 394 } finally { 395 if (c != null) c.close(); 396 } 397 } 398 399 // Returns average cross-process dummy query time in milliseconds. 400 private float serviceLoop(int amtEncoding) { 401 if (mServiceStub == null) { 402 Log.v(TAG, "No service stub."); 403 return -999; 404 } 405 String dummy = null; 406 try { 407 long sumNanos = 0; 408 int count = 0; 409 for (int i = 0; i < mIterations; i++) { 410 long lastTime = System.nanoTime(); 411 if (amtEncoding == 0) { 412 mServiceStub.pingVoid(); 413 } else { 414 dummy = mServiceStub.pingString(dummy); 415 } 416 long curTime = System.nanoTime(); 417 long duration = curTime - lastTime; 418 count++; 419 sumNanos += duration; 420 } 421 float averageMillis = (float) sumNanos / (float) (count != 0 ? count : 1) / 1000000.0f; 422 Log.v(TAG, "service loop: total: " + count + "; avg_ms=" + averageMillis); 423 return averageMillis; 424 } catch (RemoteException e) { 425 Log.e(TAG, "error in service loop: " + e); 426 return -999.0f; 427 } 428 } 429 430 // Returns average milliseconds. 431 private float localSocketLoop() { 432 LocalSocket socket = null; 433 try { 434 socket = new LocalSocket(); 435 Log.v(TAG, "Connecting to socket..."); 436 socket.connect(new LocalSocketAddress(MiscService.SOCKET_NAME)); 437 Log.v(TAG, "Connected to socket."); 438 InputStream is = socket.getInputStream(); 439 OutputStream os = socket.getOutputStream(); 440 441 int count = 0; 442 long sumNanos = 0; 443 for (int i = 0; i < mIterations; i++) { 444 long beforeTime = System.nanoTime(); 445 int expectByte = count & 0xff; 446 os.write(expectByte); 447 int gotBackByte = is.read(); 448 449 long afterTime = System.nanoTime(); 450 sumNanos += (afterTime - beforeTime); 451 452 if (gotBackByte != expectByte) { 453 Log.w(TAG, "Got wrong byte back. Got: " + gotBackByte 454 + "; wanted=" + expectByte); 455 return -999.00f; 456 } 457 count++; 458 } 459 return count == 0 ? 0.0f : ((float) sumNanos / (float) count / 1000000.0f); 460 } catch (IOException e) { 461 Log.v(TAG, "error in localSocketLoop: " + e); 462 return -1.0f; 463 } finally { 464 if (socket != null) { 465 try { socket.close(); } catch (IOException e) {} 466 } 467 } 468 } 469 470 // Returns average milliseconds. 471 private static final int MODE_READ = 0; 472 private static final int MODE_WRITE = 1; 473 private static final int MODE_WRITE_DUP = 2; 474 475 private float settingsProviderLoop(int mode, long innerSleep) { 476 long sumMillis = 0; 477 int total = 0; 478 for (int i = 0; i < mIterations; i++) { 479 long duration = mode == MODE_READ ? settingsRead(innerSleep) : settingsWrite(mode); 480 if (duration < 0) { 481 return -999.0f; 482 } 483 total++; 484 sumMillis += duration; 485 } 486 float averageMillis = ((float) sumMillis / (float) (total != 0 ? total : 1)); 487 Log.v(TAG, "settings provider; mode=" + mode + "; total=" + total + 488 "; goodavg_ms=" + averageMillis); 489 return averageMillis; 490 } 491 492 // Returns milliseconds taken, or -1 on failure. 493 private long settingsRead(long innerSleep) { 494 Cursor c = null; 495 try { 496 long startTime = SystemClock.uptimeMillis(); 497 c = cr.query(SYSTEM_SETTINGS_URI, 498 new String[]{"value"}, 499 "name=?", 500 new String[]{"airplane_mode_on"}, 501 null /* sort order */); 502 if (c == null) { 503 Log.w(TAG, "cursor null"); 504 return -1; 505 } 506 String value = c.moveToNext() ? c.getString(0) : null; 507 long duration = SystemClock.uptimeMillis() - startTime; 508 if (innerSleep > 0) { 509 try { 510 Thread.sleep(innerSleep); 511 } catch (InterruptedException e) {} 512 } 513 return duration; 514 } catch (SQLException e) { 515 Log.w(TAG, "sqlite exception: " + e); 516 return -1; 517 } finally { 518 if (c != null) c.close(); 519 } 520 } 521 522 // Returns milliseconds taken, or -1 on failure. 523 private long settingsWrite(int mode) { 524 Cursor c = null; 525 long startTime = SystemClock.uptimeMillis(); 526 // The database will take care of replacing duplicates. 527 try { 528 ContentValues values = new ContentValues(); 529 values.put("name", "dummy_for_testing"); 530 values.put("value", (mode == MODE_WRITE ? (""+startTime) : "foo")); 531 Uri uri = cr.insert(SYSTEM_SETTINGS_URI, values); 532 Log.v(TAG, "inserted uri: " + uri); 533 } catch (SQLException e) { 534 Log.w(TAG, "sqliteexception during write: " + e); 535 return -1; 536 } 537 long duration = SystemClock.uptimeMillis() - startTime; 538 return duration; 539 } 540} 541