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