1/*
2 * Copyright (C) 2011 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
17package com.android.frameworkperf;
18
19import android.app.Activity;
20import android.content.ComponentName;
21import android.content.Intent;
22import android.content.ServiceConnection;
23import android.os.Binder;
24import android.os.Bundle;
25import android.os.Handler;
26import android.os.IBinder;
27import android.os.Message;
28import android.os.Messenger;
29import android.os.PowerManager;
30import android.os.RemoteException;
31import android.util.Log;
32import android.view.View;
33import android.view.WindowManager;
34import android.widget.AdapterView;
35import android.widget.ArrayAdapter;
36import android.widget.Button;
37import android.widget.CheckBox;
38import android.widget.Spinner;
39import android.widget.TextView;
40
41import java.util.ArrayList;
42
43/**
44 * So you thought sync used up your battery life.
45 */
46public class FrameworkPerfActivity extends Activity
47        implements AdapterView.OnItemSelectedListener {
48    static final String TAG = "Perf";
49    static final boolean DEBUG = false;
50
51    Spinner mFgSpinner;
52    Spinner mBgSpinner;
53    Spinner mLimitSpinner;
54    TextView mLimitLabel;
55    TextView mTestTime;
56    Button mStartButton;
57    Button mStopButton;
58    CheckBox mLocalCheckBox;
59    TextView mLog;
60    PowerManager.WakeLock mPartialWakeLock;
61
62    long mMaxRunTime = 5000;
63    boolean mLimitIsIterations;
64    boolean mStarted;
65
66    final String[] mAvailOpLabels;
67    final String[] mAvailOpDescriptions;
68    final String[] mLimitLabels = { "Time", "Iterations" };
69
70    int mFgTestIndex = -1;
71    int mBgTestIndex = -1;
72    TestService.Op mFgTest;
73    TestService.Op mBgTest;
74    int mCurOpIndex = 0;
75    TestConnection mCurConnection;
76    boolean mConnectionBound;
77
78    final ArrayList<RunResult> mResults = new ArrayList<RunResult>();
79
80    Object mResultNotifier = new Object();
81
82    class TestConnection implements ServiceConnection, IBinder.DeathRecipient {
83        Messenger mService;
84        boolean mLinked;
85
86        @Override public void onServiceConnected(ComponentName name, IBinder service) {
87            try {
88                if (!(service instanceof Binder)) {
89                    // If remote, we'll be killing ye.
90                    service.linkToDeath(this, 0);
91                    mLinked = true;
92                }
93                mService = new Messenger(service);
94                dispatchCurOp(this);
95            } catch (RemoteException e) {
96                // Whoops, service has disappeared...  try starting again.
97                Log.w(TAG, "Test service died, starting again");
98                startCurOp();
99            }
100        }
101
102        @Override public void onServiceDisconnected(ComponentName name) {
103        }
104
105        @Override public void binderDied() {
106            cleanup();
107            connectionDied(this);
108        }
109
110        void cleanup() {
111            if (mLinked) {
112                mLinked = false;
113                mService.getBinder().unlinkToDeath(this, 0);
114            }
115        }
116    }
117
118    static final int MSG_DO_NEXT_TEST = 1000;
119
120    final Handler mHandler = new Handler() {
121        @Override public void handleMessage(Message msg) {
122            switch (msg.what) {
123                case TestService.RES_TEST_FINISHED: {
124                    Bundle bundle = (Bundle)msg.obj;
125                    bundle.setClassLoader(getClassLoader());
126                    RunResult res = (RunResult)bundle.getParcelable("res");
127                    completeCurOp(res);
128                } break;
129                case MSG_DO_NEXT_TEST: {
130                    startCurOp();
131                } break;
132            }
133        }
134    };
135
136    final Messenger mMessenger = new Messenger(mHandler);
137
138    public FrameworkPerfActivity() {
139        mAvailOpLabels = new String[TestService.mAvailOps.length];
140        mAvailOpDescriptions = new String[TestService.mAvailOps.length];
141        for (int i=0; i<TestService.mAvailOps.length; i++) {
142            TestService.Op op = TestService.mAvailOps[i];
143            if (op == null) {
144                mAvailOpLabels[i] = "All";
145                mAvailOpDescriptions[i] = "All tests";
146            } else {
147                mAvailOpLabels[i] = op.getName();
148                if (mAvailOpLabels[i] == null) {
149                    mAvailOpLabels[i] = "Nothing";
150                }
151                mAvailOpDescriptions[i] = op.getLongName();
152            }
153        }
154    }
155
156    @Override
157    public void onCreate(Bundle savedInstanceState) {
158        super.onCreate(savedInstanceState);
159
160        // Set the layout for this activity.  You can find it
161        // in res/layout/hello_activity.xml
162        setContentView(R.layout.main);
163
164        mFgSpinner = (Spinner) findViewById(R.id.fgspinner);
165        ArrayAdapter<String> adapter = new ArrayAdapter<String>(this,
166                android.R.layout.simple_spinner_item, mAvailOpLabels);
167        adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
168        mFgSpinner.setAdapter(adapter);
169        mFgSpinner.setOnItemSelectedListener(this);
170        mBgSpinner = (Spinner) findViewById(R.id.bgspinner);
171        adapter = new ArrayAdapter<String>(this,
172                android.R.layout.simple_spinner_item, mAvailOpLabels);
173        adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
174        mBgSpinner.setAdapter(adapter);
175        mBgSpinner.setOnItemSelectedListener(this);
176        mLimitSpinner = (Spinner) findViewById(R.id.limitspinner);
177        adapter = new ArrayAdapter<String>(this,
178                android.R.layout.simple_spinner_item, mLimitLabels);
179        adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
180        mLimitSpinner.setAdapter(adapter);
181        mLimitSpinner.setOnItemSelectedListener(this);
182
183        mTestTime = (TextView)findViewById(R.id.testtime);
184        mLimitLabel = (TextView)findViewById(R.id.limitlabel);
185
186        mStartButton = (Button)findViewById(R.id.start);
187        mStartButton.setOnClickListener(new View.OnClickListener() {
188            @Override public void onClick(View v) {
189                startRunning();
190            }
191        });
192        mStopButton = (Button)findViewById(R.id.stop);
193        mStopButton.setOnClickListener(new View.OnClickListener() {
194            @Override public void onClick(View v) {
195                stopRunning();
196            }
197        });
198        mStopButton.setEnabled(false);
199        mLocalCheckBox = (CheckBox)findViewById(R.id.local);
200
201        mLog = (TextView)findViewById(R.id.log);
202
203        PowerManager pm = (PowerManager)getSystemService(POWER_SERVICE);
204        mPartialWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "Scheduler");
205        mPartialWakeLock.setReferenceCounted(false);
206    }
207
208    @Override
209    public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
210        if (parent == mFgSpinner || parent == mBgSpinner || parent == mLimitSpinner) {
211            TestService.Op op = TestService.mAvailOps[position];
212            if (parent == mFgSpinner) {
213                mFgTestIndex = position;
214                mFgTest = op;
215                ((TextView)findViewById(R.id.fgtext)).setText(mAvailOpDescriptions[position]);
216            } else if (parent == mBgSpinner) {
217                mBgTestIndex = position;
218                mBgTest = op;
219                ((TextView)findViewById(R.id.bgtext)).setText(mAvailOpDescriptions[position]);
220            } else if (parent == mLimitSpinner) {
221                mLimitIsIterations = (position != 0);
222                if (mLimitIsIterations) {
223                    mLimitLabel.setText("Iterations: ");
224                } else {
225                    mLimitLabel.setText("Test time (ms): ");
226                }
227            }
228        }
229    }
230
231    @Override
232    public void onNothingSelected(AdapterView<?> parent) {
233    }
234
235    @Override
236    public void onResume() {
237        super.onResume();
238    }
239
240    @Override
241    public void onDestroy() {
242        super.onDestroy();
243        stopRunning();
244        if (mPartialWakeLock.isHeld()) {
245            mPartialWakeLock.release();
246        }
247    }
248
249    void dispatchCurOp(TestConnection conn) {
250        if (mCurConnection != conn) {
251            Log.w(TAG, "Dispatching on invalid connection: " + conn);
252            return;
253        }
254        TestArgs args = new TestArgs();
255        if (mLimitIsIterations) {
256            args.maxOps = mMaxRunTime;
257        } else {
258            args.maxTime = mMaxRunTime;
259        }
260        if (mFgTestIndex == 0 && mBgTestIndex == 0) {
261            args.combOp = mCurOpIndex;
262        } else if (mFgTestIndex != 0 && mBgTestIndex != 0) {
263            args.fgOp = mFgTestIndex;
264            args.bgOp = mBgTestIndex;
265        } else {
266            // Skip null test.
267            if (mCurOpIndex == 0) {
268                mCurOpIndex = 1;
269            }
270            if (mFgTestIndex != 0) {
271                args.fgOp = mFgTestIndex;
272                args.bgOp = mCurOpIndex;
273            } else {
274                args.fgOp = mCurOpIndex;
275                args.bgOp = mFgTestIndex;
276            }
277        }
278        Bundle bundle = new Bundle();
279        bundle.putParcelable("args", args);
280        Message msg = Message.obtain(null, TestService.CMD_START_TEST, bundle);
281        msg.replyTo = mMessenger;
282        try {
283            conn.mService.send(msg);
284        } catch (RemoteException e) {
285            Log.w(TAG, "Failure communicating with service", e);
286        }
287    }
288
289    void completeCurOp(RunResult result) {
290        log(String.format("%s: fg=%d*%gms/op (%dms) / bg=%d*%gms/op (%dms)",
291                result.name, result.fgOps, result.getFgMsPerOp(), result.fgTime,
292                result.bgOps, result.getBgMsPerOp(), result.bgTime));
293        synchronized (mResults) {
294            mResults.add(result);
295        }
296        if (!mStarted) {
297            log("Stop");
298            stopRunning();
299            return;
300        }
301        if (mFgTest != null && mBgTest != null) {
302            log("Finished");
303            stopRunning();
304            return;
305        }
306        if (mFgTest == null && mBgTest == null) {
307            mCurOpIndex+=2;
308            if (mCurOpIndex >= TestService.mOpPairs.length) {
309                log("Finished");
310                stopRunning();
311                return;
312            }
313        } else {
314            mCurOpIndex++;
315            if (mCurOpIndex >= TestService.mAvailOps.length) {
316                log("Finished");
317                stopRunning();
318                return;
319            }
320        }
321        startCurOp();
322    }
323
324    void disconnect() {
325        final TestConnection conn = mCurConnection;
326        if (conn != null) {
327            if (DEBUG) {
328                RuntimeException here = new RuntimeException("here");
329                here.fillInStackTrace();
330                Log.i(TAG, "Unbinding " + conn, here);
331            }
332            if (mConnectionBound) {
333                unbindService(conn);
334                mConnectionBound = false;
335            }
336            if (conn.mLinked) {
337                Message msg = Message.obtain(null, TestService.CMD_TERMINATE);
338                try {
339                    conn.mService.send(msg);
340                    return;
341                } catch (RemoteException e) {
342                    Log.w(TAG, "Test service aleady died when terminating");
343                }
344            }
345            conn.cleanup();
346        }
347        connectionDied(conn);
348    }
349
350    void connectionDied(TestConnection conn) {
351        if (mCurConnection == conn) {
352            // Now that we know the test process has died, we can commence
353            // the next test.  Just give a little delay to allow the activity
354            // manager to know it has died as well (not a disaster if it hasn't
355            // yet, though).
356            if (mConnectionBound) {
357                unbindService(conn);
358            }
359            mCurConnection = null;
360            mHandler.sendMessageDelayed(Message.obtain(null, MSG_DO_NEXT_TEST), 100);
361        }
362    }
363
364    void startCurOp() {
365        if (DEBUG) Log.i(TAG, "startCurOp: mCurConnection=" + mCurConnection);
366        if (mCurConnection != null) {
367            disconnect();
368            return;
369        }
370        if (mStarted) {
371            mHandler.removeMessages(TestService.RES_TEST_FINISHED);
372            mHandler.removeMessages(TestService.RES_TERMINATED);
373            mHandler.removeMessages(MSG_DO_NEXT_TEST);
374            mCurConnection = new TestConnection();
375            Intent intent;
376            if (mLocalCheckBox.isChecked()) {
377                intent = new Intent(this, LocalTestService.class);
378            } else {
379                intent = new Intent(this, TestService.class);
380            }
381            if (DEBUG) {
382                RuntimeException here = new RuntimeException("here");
383                here.fillInStackTrace();
384                Log.i(TAG, "Binding " + mCurConnection, here);
385            }
386            bindService(intent, mCurConnection, BIND_AUTO_CREATE|BIND_IMPORTANT);
387            mConnectionBound = true;
388        }
389    }
390
391    void startRunning() {
392        if (!mStarted) {
393            log("Start");
394            mStarted = true;
395            mStartButton.setEnabled(false);
396            mStopButton.setEnabled(true);
397            mLocalCheckBox.setEnabled(false);
398            mTestTime.setEnabled(false);
399            mFgSpinner.setEnabled(false);
400            mBgSpinner.setEnabled(false);
401            mLimitSpinner.setEnabled(false);
402            updateWakeLock();
403            startService(new Intent(this, SchedulerService.class));
404            mCurOpIndex = 0;
405            mMaxRunTime = Integer.parseInt(mTestTime.getText().toString());
406            synchronized (mResults) {
407                mResults.clear();
408            }
409            startCurOp();
410        }
411    }
412
413    void stopRunning() {
414        if (mStarted) {
415            disconnect();
416            mStarted = false;
417            mStartButton.setEnabled(true);
418            mStopButton.setEnabled(false);
419            mLocalCheckBox.setEnabled(true);
420            mTestTime.setEnabled(true);
421            mFgSpinner.setEnabled(true);
422            mBgSpinner.setEnabled(true);
423            mLimitSpinner.setEnabled(true);
424            updateWakeLock();
425            stopService(new Intent(this, SchedulerService.class));
426            synchronized (mResults) {
427                for (int i=0; i<mResults.size(); i++) {
428                    RunResult result = mResults.get(i);
429                    float fgMsPerOp = result.getFgMsPerOp();
430                    float bgMsPerOp = result.getBgMsPerOp();
431                    String fgMsPerOpStr = fgMsPerOp != 0 ? Float.toString(fgMsPerOp) : "";
432                    String bgMsPerOpStr = bgMsPerOp != 0 ? Float.toString(bgMsPerOp) : "";
433                    Log.i("PerfRes", "\t" + result.name + "\t" + result.fgOps
434                            + "\t" + result.getFgMsPerOp() + "\t" + result.fgTime
435                            + "\t" + result.fgLongName + "\t" + result.bgOps
436                            + "\t" + result.getBgMsPerOp() + "\t" + result.bgTime
437                            + "\t" + result.bgLongName);
438                }
439            }
440            synchronized (mResultNotifier) {
441                mResultNotifier.notifyAll();
442            }
443        }
444    }
445
446    void updateWakeLock() {
447        if (mStarted) {
448            getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
449            if (!mPartialWakeLock.isHeld()) {
450                mPartialWakeLock.acquire();
451            }
452        } else {
453            getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
454            if (mPartialWakeLock.isHeld()) {
455                mPartialWakeLock.release();
456            }
457        }
458    }
459
460    void log(String s) {
461        mLog.setText(mLog.getText() + "\n" + s);
462        Log.i(TAG, s);
463    }
464}
465