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.Binder;
42import android.os.Bundle;
43import android.os.Debug;
44import android.os.Handler;
45import android.os.IBinder;
46import android.os.Parcel;
47import android.os.RemoteException;
48import android.os.ServiceManager;
49import android.os.SystemClock;
50import android.os.StrictMode;
51import android.telephony.TelephonyManager;
52import android.text.TextUtils;
53import android.util.AndroidException;
54import android.util.Log;
55import android.view.View;
56import android.widget.Button;
57import android.widget.TextView;
58
59import java.io.File;
60import java.io.FileInputStream;
61import java.io.IOException;
62import java.io.InputStream;
63import java.io.OutputStream;
64import java.io.RandomAccessFile;
65
66public 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