blob: 1ef15d544f6b8e752fdcf06e54a967b0d2816c19 [file] [log] [blame]
/*
* Copyright (C) 2010 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// TODO: find AIDL for ContentResolver. add queryExtensible() returning struct{Cursor?,String? singleRow}
// -- would have to lazily do real requery(), registerContentObserver(), etc
package com.android.rpc_performance;
import android.app.Activity;
import android.content.ComponentName;
import android.content.ContentQueryMap;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.IContentProvider;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.database.Cursor;
import android.database.SQLException;
import android.net.LocalSocket;
import android.net.LocalSocketAddress;
import android.net.Uri;
import android.os.Binder;
import android.os.Bundle;
import android.os.Debug;
import android.os.Handler;
import android.os.IBinder;
import android.os.Parcel;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemClock;
import android.os.StrictMode;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
import android.util.AndroidException;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.RandomAccessFile;
public class ProviderPerfActivity extends Activity {
private static final String TAG = "ProviderPerfActivity";
private static final Uri SYSTEM_SETTINGS_URI = Uri.parse("content://settings/system");
// No-op provider URLs:
private static final Uri CROSS_PROC_PROVIDER_URI = Uri.parse("content://com.android.rpc_performance/");
private static final Uri IN_PROC_PROVIDER_URI = Uri.parse("content://com.android.rpc_performance.local/");
private final Handler mHandler = new Handler();
private final static int LOOP_TIME_MILLIS = 2000;
private final static long LOOP_TIME_NANOS = (long) LOOP_TIME_MILLIS * 1000000L;
private IService mServiceStub = null;
private ServiceConnection serviceConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName name, IBinder service) {
mServiceStub = IService.Stub.asInterface(service);
Log.v(TAG, "Service bound");
}
public void onServiceDisconnected(ComponentName name) {
mServiceStub = null;
Log.v(TAG, "Service unbound");
};
};
private ContentResolver cr;
private int mIterations = 100;
private String mTraceName = null;
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
cr = getContentResolver();
setButtonAction(R.id.file_read_button, new Runnable() {
public void run() {
final float avgTime = fileReadLoop();
endAsyncOp(R.id.file_read_button, R.id.file_read_text, avgTime);
}});
setButtonAction(R.id.file_write_button, new Runnable() {
public void run() {
final float avgTime = fileWriteLoop();
endAsyncOp(R.id.file_write_button, R.id.file_write_text, avgTime);
}});
setButtonAction(R.id.settings_read_button, new Runnable() {
public void run() {
final float avgTime = settingsProviderLoop(MODE_READ, 0);
endAsyncOp(R.id.settings_read_button, R.id.settings_read_text, avgTime);
}});
setButtonAction(R.id.settings_sleep_button, new Runnable() {
public void run() {
final float avgTime = settingsProviderLoop(MODE_READ, 100);
endAsyncOp(R.id.settings_sleep_button, R.id.settings_sleep_text, avgTime);
}});
setButtonAction(R.id.settings_write_button, new Runnable() {
public void run() {
final float avgTime = settingsProviderLoop(MODE_WRITE, 0);
endAsyncOp(R.id.settings_write_button, R.id.settings_write_text, avgTime);
}});
setButtonAction(R.id.settings_writedup_button, new Runnable() {
public void run() {
final float avgTime = settingsProviderLoop(MODE_WRITE_DUP, 0);
endAsyncOp(R.id.settings_writedup_button, R.id.settings_writedup_text, avgTime);
}});
setButtonAction(R.id.dummy_lookup_button, new Runnable() {
public void run() {
final float avgTime = noOpProviderLoop(CROSS_PROC_PROVIDER_URI);
endAsyncOp(R.id.dummy_lookup_button, R.id.dummy_lookup_text, avgTime);
}});
setButtonAction(R.id.dummy_local_lookup_button, new Runnable() {
public void run() {
final float avgTime = noOpProviderLoop(IN_PROC_PROVIDER_URI);
endAsyncOp(R.id.dummy_local_lookup_button,
R.id.dummy_local_lookup_text, avgTime);
}});
setButtonAction(R.id.localsocket_button, new Runnable() {
public void run() {
final float avgTime = localSocketLoop();
endAsyncOp(R.id.localsocket_button, R.id.localsocket_text, avgTime);
}});
setButtonAction(R.id.service_button, new Runnable() {
public void run() {
final float avgTime = serviceLoop(null);
endAsyncOp(R.id.service_button, R.id.service_text, avgTime);
}});
setButtonAction(R.id.service2_button, new Runnable() {
public void run() {
final float avgTime = serviceLoop("xyzzy");
endAsyncOp(R.id.service2_button, R.id.service2_text, avgTime);
}});
setButtonAction(R.id.ping_media_button, new Runnable() {
public void run() {
final float avgTime = pingServiceLoop("media.player");
endAsyncOp(R.id.ping_media_button, R.id.ping_media_text, avgTime);
}});
setButtonAction(R.id.ping_activity_button, new Runnable() {
public void run() {
final float avgTime = pingServiceLoop("activity");
endAsyncOp(R.id.ping_activity_button, R.id.ping_activity_text, avgTime);
}});
setButtonAction(R.id.proc_button, new Runnable() {
public void run() {
final float avgTime = procLoop();
endAsyncOp(R.id.proc_button, R.id.proc_text, avgTime);
}});
setButtonAction(R.id.call_button, new Runnable() {
public void run() {
final float avgTime = callLoop("ringtone");
endAsyncOp(R.id.call_button, R.id.call_text, avgTime);
}});
setButtonAction(R.id.call2_button, new Runnable() {
public void run() {
final float avgTime = callLoop("XXXXXXXX"); // non-existent
endAsyncOp(R.id.call2_button, R.id.call2_text, avgTime);
}});
setButtonAction(R.id.obtain_button, new Runnable() {
public void run() {
final float avgTime = parcelLoop(true);
endAsyncOp(R.id.obtain_button, R.id.obtain_text, avgTime);
}});
setButtonAction(R.id.recycle_button, new Runnable() {
public void run() {
final float avgTime = parcelLoop(false);
endAsyncOp(R.id.recycle_button, R.id.recycle_text, avgTime);
}});
setButtonAction(R.id.strictmode_button, new Runnable() {
public void run() {
final float avgTime = strictModeLoop(true);
endAsyncOp(R.id.strictmode_button, R.id.strictmode_text, avgTime);
}});
setButtonAction(R.id.binderstrict_button, new Runnable() {
public void run() {
final float avgTime = strictModeLoop(false);
endAsyncOp(R.id.binderstrict_button, R.id.binderstrict_text, avgTime);
}});
}
@Override public void onResume() {
super.onResume();
bindService(new Intent(this, MiscService.class),
serviceConnection, Context.BIND_AUTO_CREATE);
}
@Override public void onPause() {
super.onPause();
if (serviceConnection != null) {
unbindService(serviceConnection);
}
}
private void setButtonAction(int button_id, final Runnable r) {
final Button button = (Button) findViewById(button_id);
button.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
button.requestFocus();
button.setEnabled(false);
TextView tvIter = (TextView) findViewById(R.id.iterations_edit);
try {
mIterations = Integer.parseInt(tvIter.getText().toString());
} catch (NumberFormatException e) {
Log.w(TAG, "Invalid iteration count", e);
if (tvIter != null) tvIter.setText(Integer.toString(mIterations));
}
TextView tvTrace = (TextView) findViewById(R.id.trace_edit);
String name = tvTrace.getText().toString();
if (name != null && name.length() > 0) {
mTraceName = name;
Debug.startMethodTracing(name);
}
new Thread(r).start();
}
});
}
private void endAsyncOp(final int button_id, final int text_id, final float avgTime) {
mHandler.post(new Runnable() {
public void run() {
Debug.stopMethodTracing();
findViewById(button_id).setEnabled(true);
setTextTime(text_id, avgTime);
}
});
}
private void setTextTime(int id, float avgTime) {
TextView tv = (TextView) findViewById(id);
if (tv == null) return;
String text = tv.getText().toString();
text = text.substring(0, text.indexOf(':') + 1) + "\n" + avgTime + " ms avg";
tv.setText(text);
}
private float fileReadLoop() {
RandomAccessFile raf = null;
File filename = getFileStreamPath("test.dat");
try {
long sumNanos = 0;
byte[] buf = new byte[512];
raf = new RandomAccessFile(filename, "rw");
raf.write(buf);
raf.close();
raf = null;
// The data's almost certainly cached -- it's not clear what we're testing here
raf = new RandomAccessFile(filename, "r");
for (int i = 0; i < mIterations; i++) {
long lastTime = System.nanoTime();
raf.seek(0);
raf.read(buf);
sumNanos += System.nanoTime() - lastTime;
}
return (float) sumNanos / Math.max(1.0f, (float) mIterations) / 1000000.0f;
} catch (IOException e) {
Log.e(TAG, "File read failed", e);
return 0;
} finally {
try { if (raf != null) raf.close(); } catch (IOException e) {}
}
}
private float fileWriteLoop() {
RandomAccessFile raf = null;
File filename = getFileStreamPath("test.dat");
try {
long sumNanos = 0;
byte[] buf = new byte[512];
for (int i = 0; i < mIterations; i++) {
for (int j = 0; j < buf.length; j++) buf[j] = (byte) (i + j);
long lastTime = System.nanoTime();
raf = new RandomAccessFile(filename, "rw");
raf.write(buf);
raf.close();
raf = null;
sumNanos += System.nanoTime() - lastTime;
}
return (float) sumNanos / Math.max(1.0f, (float) mIterations) / 1000000.0f;
} catch (IOException e) {
Log.e(TAG, "File read failed", e);
return 0;
} finally {
try { if (raf != null) raf.close(); } catch (IOException e) {}
}
}
// Returns average cross-process dummy query time in milliseconds.
private float noOpProviderLoop(Uri uri) {
long sumNanos = 0;
int failures = 0;
int total = 0;
for (int i = 0; i < mIterations; i++) {
long duration = doNoOpLookup(uri);
if (duration < 0) {
failures++;
} else {
total++;
sumNanos += duration;
}
}
float averageMillis = (float) sumNanos /
(float) (total != 0 ? total : 1) /
1000000.0f;
Log.v(TAG, "dummy loop: fails=" + failures + "; total=" + total + "; goodavg ms=" + averageMillis);
return averageMillis;
}
// Returns average cross-process dummy query time in milliseconds.
private float callLoop(String key) {
IContentProvider cp = cr.acquireProvider(SYSTEM_SETTINGS_URI.getAuthority());
long sumNanos = 0;
int total = 0;
try {
for (int i = 0; i < mIterations; i++) {
long lastTime = System.nanoTime();
Bundle b = cp.call("GET_system", key, null);
long nowTime = System.nanoTime();
total++;
sumNanos += (nowTime - lastTime);
}
} catch (RemoteException e) {
return -999.0f;
}
float averageMillis = (float) sumNanos /
(float) (total != 0 ? total : 1) /
1000000.0f;
Log.v(TAG, "call loop: avg_ms=" + averageMillis + "; calls=" + total);
return averageMillis;
}
// Returns average time to read a /proc file in milliseconds.
private float procLoop() {
long sumNanos = 0;
int total = 0;
File f = new File("/proc/self/cmdline");
byte[] buf = new byte[100];
String value = null;
try {
for (int i = 0; i < mIterations; i++) {
long lastTime = System.nanoTime();
FileInputStream is = new FileInputStream(f);
int readBytes = is.read(buf, 0, 100);
is.close();
//value = new String(buf, 0, readBytes);
long nowTime = System.nanoTime();
total++;
sumNanos += (nowTime - lastTime);
lastTime = nowTime;
}
} catch (IOException e) {
return -999.0f;
}
float averageMillis = (float) sumNanos /
(float) (total != 0 ? total : 1) /
1000000.0f;
Log.v(TAG, "proc loop: total: " + total + "; avg_ms=" + averageMillis + "; value=" + value);
return averageMillis;
}
private static final String[] IGNORED_COLUMN = {"ignored"};
// Returns nanoseconds.
private long doNoOpLookup(Uri uri) {
Cursor c = null;
try {
long startTime = System.nanoTime();
c = cr.query(uri,
IGNORED_COLUMN, //new String[]{"ignored"}, // but allocate it for apples-to-apples
"name=?",
IGNORED_COLUMN, // new String[]{"also_ignored"}, // also for equality in benchmarking
null /* sort order */);
if (c == null) {
Log.w(TAG, "cursor null");
return -1;
}
String value = c.moveToNext() ? c.getString(0) : null;
long duration = System.nanoTime() - startTime;
//Log.v(TAG, "got value: " + value + " in " + duration);
return duration;
} catch (SQLException e) {
Log.w(TAG, "sqlite exception: " + e);
return -1;
} finally {
if (c != null) c.close();
}
}
// Returns average cross-process dummy query time in milliseconds.
private float serviceLoop(String value) {
if (mServiceStub == null) {
Log.v(TAG, "No service stub.");
return -999;
}
String dummy = null;
try {
if (mTraceName != null) mServiceStub.startTracing(mTraceName + ".service");
long sumNanos = 0;
for (int i = 0; i < mIterations; i++) {
long lastTime = System.nanoTime();
if (value == null) {
mServiceStub.pingVoid();
} else {
value = mServiceStub.pingString(value);
}
sumNanos += System.nanoTime() - lastTime;
}
if (mTraceName != null) mServiceStub.stopTracing();
return (float) sumNanos / Math.max(1.0f, (float) mIterations) / 1000000.0f;
} catch (RemoteException e) {
Log.e(TAG, "Binder call failed", e);
return -999;
}
}
// Returns average cross-process binder ping time in milliseconds.
private float pingServiceLoop(String service) {
IBinder binder = ServiceManager.getService(service);
if (binder == null) {
Log.e(TAG, "Service missing: " + service);
return -1.0f;
}
long sumNanos = 0;
for (int i = 0; i < mIterations; i++) {
long lastTime = System.nanoTime();
if (!binder.pingBinder()) {
Log.e(TAG, "Error pinging service: " + service);
return -1.0f;
}
sumNanos += System.nanoTime() - lastTime;
}
return (float) sumNanos / Math.max(1.0f, (float) mIterations) / 1000000.0f;
}
// Returns average milliseconds.
private float localSocketLoop() {
LocalSocket socket = null;
try {
socket = new LocalSocket();
Log.v(TAG, "Connecting to socket...");
socket.connect(new LocalSocketAddress(MiscService.SOCKET_NAME));
Log.v(TAG, "Connected to socket.");
InputStream is = socket.getInputStream();
OutputStream os = socket.getOutputStream();
int count = 0;
long sumNanos = 0;
for (int i = 0; i < mIterations; i++) {
long beforeTime = System.nanoTime();
int expectByte = count & 0xff;
os.write(expectByte);
int gotBackByte = is.read();
long afterTime = System.nanoTime();
sumNanos += (afterTime - beforeTime);
if (gotBackByte != expectByte) {
Log.w(TAG, "Got wrong byte back. Got: " + gotBackByte
+ "; wanted=" + expectByte);
return -999.00f;
}
count++;
}
return count == 0 ? 0.0f : ((float) sumNanos / (float) count / 1000000.0f);
} catch (IOException e) {
Log.v(TAG, "error in localSocketLoop: " + e);
return -1.0f;
} finally {
if (socket != null) {
try { socket.close(); } catch (IOException e) {}
}
}
}
// Returns average operation time in milliseconds.
// obtain: true = measure obtain(), false = measure recycle()
private float parcelLoop(boolean obtain) {
long sumNanos = 0;
for (int i = 0; i < mIterations; i++) {
if (obtain) {
long lastTime = System.nanoTime();
Parcel p = Parcel.obtain();
sumNanos += System.nanoTime() - lastTime;
p.recycle();
} else {
Parcel p = Parcel.obtain();
long lastTime = System.nanoTime();
p.recycle();
sumNanos += System.nanoTime() - lastTime;
}
}
return (float) sumNanos / Math.max(1.0f, (float) mIterations) / 1000000.0f;
}
private float strictModeLoop(boolean full) {
StrictMode.ThreadPolicy oldPolicy = StrictMode.getThreadPolicy();
int oldPolicyMask = StrictMode.getThreadPolicyMask(); // hidden API
long sumNanos = 0;
StrictMode.ThreadPolicy policyA =
new StrictMode.ThreadPolicy.Builder().detectDiskReads().build();
StrictMode.ThreadPolicy policyB =
new StrictMode.ThreadPolicy.Builder().detectDiskWrites().build();
for (int i = 0; i < mIterations; i++) {
StrictMode.ThreadPolicy policy = ((i & 1) == 1) ? policyA : policyB;
int policyMask = ((i & 1) == 1) ? 1 : 2;
if (full) {
long lastTime = System.nanoTime();
StrictMode.setThreadPolicy(policy);
sumNanos += System.nanoTime() - lastTime;
} else {
long lastTime = System.nanoTime();
Binder.setThreadStrictModePolicy(policyMask);
sumNanos += System.nanoTime() - lastTime;
}
}
if (full) {
StrictMode.setThreadPolicy(oldPolicy);
} else {
Binder.setThreadStrictModePolicy(oldPolicyMask);
}
return (float) sumNanos / Math.max(1.0f, (float) mIterations) / 1000000.0f;
}
// Returns average milliseconds.
private static final int MODE_READ = 0;
private static final int MODE_WRITE = 1;
private static final int MODE_WRITE_DUP = 2;
private float settingsProviderLoop(int mode, long innerSleep) {
long sumMillis = 0;
int total = 0;
for (int i = 0; i < mIterations; i++) {
long duration = mode == MODE_READ ? settingsRead(innerSleep) : settingsWrite(mode);
if (duration < 0) {
return -999.0f;
}
total++;
sumMillis += duration;
}
float averageMillis = ((float) sumMillis / (float) (total != 0 ? total : 1));
Log.v(TAG, "settings provider; mode=" + mode + "; total=" + total +
"; goodavg_ms=" + averageMillis);
return averageMillis;
}
// Returns milliseconds taken, or -1 on failure.
private long settingsRead(long innerSleep) {
Cursor c = null;
try {
long startTime = SystemClock.uptimeMillis();
c = cr.query(SYSTEM_SETTINGS_URI,
new String[]{"value"},
"name=?",
new String[]{"airplane_mode_on"},
null /* sort order */);
if (c == null) {
Log.w(TAG, "cursor null");
return -1;
}
String value = c.moveToNext() ? c.getString(0) : null;
long duration = SystemClock.uptimeMillis() - startTime;
if (innerSleep > 0) {
try {
Thread.sleep(innerSleep);
} catch (InterruptedException e) {}
}
return duration;
} catch (SQLException e) {
Log.w(TAG, "sqlite exception: " + e);
return -1;
} finally {
if (c != null) c.close();
}
}
// Returns milliseconds taken, or -1 on failure.
private long settingsWrite(int mode) {
Cursor c = null;
long startTime = SystemClock.uptimeMillis();
// The database will take care of replacing duplicates.
try {
ContentValues values = new ContentValues();
values.put("name", "dummy_for_testing");
values.put("value", (mode == MODE_WRITE ? (""+startTime) : "foo"));
Uri uri = cr.insert(SYSTEM_SETTINGS_URI, values);
Log.v(TAG, "inserted uri: " + uri);
} catch (SQLException e) {
Log.w(TAG, "sqliteexception during write: " + e);
return -1;
}
long duration = SystemClock.uptimeMillis() - startTime;
return duration;
}
}