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