diff --git a/tests/JankBench/Android.mk b/tests/JankBench/Android.mk new file mode 100644 index 0000000000000..12568a09e71ec --- /dev/null +++ b/tests/JankBench/Android.mk @@ -0,0 +1,38 @@ +LOCAL_PATH:= $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_MODULE_TAGS := tests + +LOCAL_MANIFEST_FILE := app/src/main/AndroidManifest.xml + +LOCAL_SDK_VERSION := current + +LOCAL_USE_AAPT2 := true + +# omit gradle 'build' dir +LOCAL_SRC_FILES := $(call all-java-files-under,app/src/main/java) + +# use appcompat/support lib from the tree, so improvements/ +# regressions are reflected in test data +LOCAL_RESOURCE_DIR := \ + $(LOCAL_PATH)/app/src/main/res \ + + +LOCAL_STATIC_ANDROID_LIBRARIES := \ + android-support-design \ + android-support-v4 \ + android-support-v7-appcompat \ + android-support-v7-cardview \ + android-support-v7-recyclerview \ + android-support-v17-leanback \ + +LOCAL_STATIC_JAVA_LIBRARIES := \ + apache-commons-math \ + junit + + +LOCAL_PACKAGE_NAME := JankBench + +LOCAL_COMPATIBILITY_SUITE := device-tests + +include $(BUILD_PACKAGE) diff --git a/tests/JankBench/app/src/androidTest/java/com/android/benchmark/ApplicationTest.java b/tests/JankBench/app/src/androidTest/java/com/android/benchmark/ApplicationTest.java new file mode 100644 index 0000000000000..79aff900fd0eb --- /dev/null +++ b/tests/JankBench/app/src/androidTest/java/com/android/benchmark/ApplicationTest.java @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2015 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. + */ + +/* + * Copyright (C) 2015 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. + * + */ + +package com.android.benchmark; + +import android.app.Application; +import android.test.ApplicationTestCase; + +/** + * Testing Fundamentals + */ +public class ApplicationTest extends ApplicationTestCase { + public ApplicationTest() { + super(Application.class); + } +} diff --git a/tests/JankBench/app/src/main/AndroidManifest.xml b/tests/JankBench/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000000000..58aa66fcd05d9 --- /dev/null +++ b/tests/JankBench/app/src/main/AndroidManifest.xml @@ -0,0 +1,62 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/tests/JankBench/app/src/main/java/com/android/benchmark/app/BenchmarkDashboardFragment.java b/tests/JankBench/app/src/main/java/com/android/benchmark/app/BenchmarkDashboardFragment.java new file mode 100644 index 0000000000000..b0a97ae0995bd --- /dev/null +++ b/tests/JankBench/app/src/main/java/com/android/benchmark/app/BenchmarkDashboardFragment.java @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2015 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. + * + */ + +package com.android.benchmark.app; + +import android.support.v4.app.Fragment; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import com.android.benchmark.R; + +/** + * Fragment for the Benchmark dashboard + */ +public class BenchmarkDashboardFragment extends Fragment { + + public BenchmarkDashboardFragment() { + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + return inflater.inflate(R.layout.fragment_dashboard, container, false); + } +} diff --git a/tests/JankBench/app/src/main/java/com/android/benchmark/app/BenchmarkListAdapter.java b/tests/JankBench/app/src/main/java/com/android/benchmark/app/BenchmarkListAdapter.java new file mode 100644 index 0000000000000..7419b30814f66 --- /dev/null +++ b/tests/JankBench/app/src/main/java/com/android/benchmark/app/BenchmarkListAdapter.java @@ -0,0 +1,126 @@ +/* + * Copyright (C) 2015 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. + * + */ + +package com.android.benchmark.app; + +import android.graphics.Typeface; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.BaseExpandableListAdapter; +import android.widget.CheckBox; +import android.widget.TextView; + +import com.android.benchmark.registry.BenchmarkGroup; +import com.android.benchmark.registry.BenchmarkRegistry; +import com.android.benchmark.R; + +/** + * + */ +public class BenchmarkListAdapter extends BaseExpandableListAdapter { + + private final LayoutInflater mInflater; + private final BenchmarkRegistry mRegistry; + + BenchmarkListAdapter(LayoutInflater inflater, + BenchmarkRegistry registry) { + mInflater = inflater; + mRegistry = registry; + } + + @Override + public int getGroupCount() { + return mRegistry.getGroupCount(); + } + + @Override + public int getChildrenCount(int groupPosition) { + return mRegistry.getBenchmarkCount(groupPosition); + } + + @Override + public Object getGroup(int groupPosition) { + return mRegistry.getBenchmarkGroup(groupPosition); + } + + @Override + public Object getChild(int groupPosition, int childPosition) { + BenchmarkGroup benchmarkGroup = mRegistry.getBenchmarkGroup(groupPosition); + + if (benchmarkGroup != null) { + return benchmarkGroup.getBenchmarks()[childPosition]; + } + + return null; + } + + @Override + public long getGroupId(int groupPosition) { + return groupPosition; + } + + @Override + public long getChildId(int groupPosition, int childPosition) { + return childPosition; + } + + @Override + public boolean hasStableIds() { + return false; + } + + @Override + public View getGroupView(int groupPosition, boolean isExpanded, View convertView, ViewGroup parent) { + BenchmarkGroup group = (BenchmarkGroup) getGroup(groupPosition); + if (convertView == null) { + convertView = mInflater.inflate(R.layout.benchmark_list_group_row, null); + } + + TextView title = (TextView) convertView.findViewById(R.id.group_name); + title.setTypeface(null, Typeface.BOLD); + title.setText(group.getTitle()); + return convertView; + } + + @Override + public View getChildView(int groupPosition, int childPosition, boolean isLastChild, + View convertView, ViewGroup parent) { + BenchmarkGroup.Benchmark benchmark = + (BenchmarkGroup.Benchmark) getChild(groupPosition, childPosition); + if (convertView == null) { + convertView = mInflater.inflate(R.layout.benchmark_list_item, null); + } + + TextView name = (TextView) convertView.findViewById(R.id.benchmark_name); + name.setText(benchmark.getName()); + CheckBox enabledBox = (CheckBox) convertView.findViewById(R.id.benchmark_enable_checkbox); + enabledBox.setOnClickListener(benchmark); + enabledBox.setChecked(benchmark.isEnabled()); + + return convertView; + } + + @Override + public boolean isChildSelectable(int groupPosition, int childPosition) { + return true; + } + + public int getChildrenHeight() { + // TODO + return 1024; + } +} diff --git a/tests/JankBench/app/src/main/java/com/android/benchmark/app/HomeActivity.java b/tests/JankBench/app/src/main/java/com/android/benchmark/app/HomeActivity.java new file mode 100644 index 0000000000000..79bafd62e2a2c --- /dev/null +++ b/tests/JankBench/app/src/main/java/com/android/benchmark/app/HomeActivity.java @@ -0,0 +1,150 @@ +/* + * Copyright (C) 2015 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. + * + */ + +package com.android.benchmark.app; + +import android.content.Intent; +import android.os.AsyncTask; +import android.os.Bundle; +import android.support.design.widget.FloatingActionButton; +import android.support.v7.app.AppCompatActivity; +import android.support.v7.widget.Toolbar; +import android.view.LayoutInflater; +import android.view.View; +import android.view.Menu; +import android.view.MenuItem; +import android.view.ViewGroup; +import android.widget.Button; +import android.widget.ExpandableListView; +import android.widget.Toast; + +import com.android.benchmark.registry.BenchmarkRegistry; +import com.android.benchmark.R; +import com.android.benchmark.results.GlobalResultsStore; + +import java.io.IOException; +import java.util.LinkedList; +import java.util.Queue; + +public class HomeActivity extends AppCompatActivity implements Button.OnClickListener { + + private FloatingActionButton mStartButton; + private BenchmarkRegistry mRegistry; + private Queue mRunnableBenchmarks; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_home); + + Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); + setSupportActionBar(toolbar); + + mStartButton = (FloatingActionButton) findViewById(R.id.start_button); + mStartButton.setActivated(true); + mStartButton.setOnClickListener(this); + + mRegistry = new BenchmarkRegistry(this); + + mRunnableBenchmarks = new LinkedList<>(); + + ExpandableListView listView = (ExpandableListView) findViewById(R.id.test_list); + BenchmarkListAdapter adapter = + new BenchmarkListAdapter(LayoutInflater.from(this), mRegistry); + listView.setAdapter(adapter); + + adapter.notifyDataSetChanged(); + ViewGroup.LayoutParams layoutParams = listView.getLayoutParams(); + layoutParams.height = 2048; + listView.setLayoutParams(layoutParams); + listView.requestLayout(); + System.out.println(System.getProperties().stringPropertyNames()); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + // Inflate the menu; this adds items to the action bar if it is present. + getMenuInflater().inflate(R.menu.menu_main, menu); + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + // Handle action bar item clicks here. The action bar will + // automatically handle clicks on the Home/Up button, so long + // as you specify a parent activity in AndroidManifest.xml. + int id = item.getItemId(); + + //noinspection SimplifiableIfStatement + if (id == R.id.action_settings) { + new AsyncTask() { + @Override + protected Void doInBackground(Void... voids) { + try { + HomeActivity.this.runOnUiThread(new Runnable() { + @Override + public void run() { + Toast.makeText(HomeActivity.this, "Exporting...", Toast.LENGTH_LONG).show(); + } + }); + GlobalResultsStore.getInstance(HomeActivity.this).exportToCsv(); + } catch (IOException e) { + e.printStackTrace(); + } + return null; + } + + @Override + protected void onPostExecute(Void aVoid) { + HomeActivity.this.runOnUiThread(new Runnable() { + @Override + public void run() { + Toast.makeText(HomeActivity.this, "Done", Toast.LENGTH_LONG).show(); + } + }); + } + }.execute(); + + return true; + } + + return super.onOptionsItemSelected(item); + } + + @Override + public void onClick(View v) { + final int groupCount = mRegistry.getGroupCount(); + for (int i = 0; i < groupCount; i++) { + + Intent intent = mRegistry.getBenchmarkGroup(i).getIntent(); + if (intent != null) { + mRunnableBenchmarks.add(intent); + } + } + + handleNextBenchmark(); + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + + } + + private void handleNextBenchmark() { + Intent nextIntent = mRunnableBenchmarks.peek(); + startActivityForResult(nextIntent, 0); + } +} diff --git a/tests/JankBench/app/src/main/java/com/android/benchmark/app/PerfTimeline.java b/tests/JankBench/app/src/main/java/com/android/benchmark/app/PerfTimeline.java new file mode 100644 index 0000000000000..1c82d6db3483d --- /dev/null +++ b/tests/JankBench/app/src/main/java/com/android/benchmark/app/PerfTimeline.java @@ -0,0 +1,224 @@ +/* + * Copyright (C) 2015 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. + */ + +package com.android.benchmark.app; + +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.*; +import android.text.TextPaint; +import android.util.AttributeSet; +import android.view.View; + +import com.android.benchmark.R; + + +/** + * TODO: document your custom view class. + */ +public class PerfTimeline extends View { + private String mExampleString; // TODO: use a default from R.string... + private int mExampleColor = Color.RED; // TODO: use a default from R.color... + private float mExampleDimension = 300; // TODO: use a default from R.dimen... + + private TextPaint mTextPaint; + private float mTextWidth; + private float mTextHeight; + + private Paint mPaintBaseLow; + private Paint mPaintBaseHigh; + private Paint mPaintValue; + + + public float[] mLinesLow; + public float[] mLinesHigh; + public float[] mLinesValue; + + public PerfTimeline(Context context) { + super(context); + init(null, 0); + } + + public PerfTimeline(Context context, AttributeSet attrs) { + super(context, attrs); + init(attrs, 0); + } + + public PerfTimeline(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + init(attrs, defStyle); + } + + private void init(AttributeSet attrs, int defStyle) { + // Load attributes + final TypedArray a = getContext().obtainStyledAttributes( + attrs, R.styleable.PerfTimeline, defStyle, 0); + + mExampleString = "xx";//a.getString(R.styleable.PerfTimeline_exampleString, "xx"); + mExampleColor = a.getColor(R.styleable.PerfTimeline_exampleColor, mExampleColor); + // Use getDimensionPixelSize or getDimensionPixelOffset when dealing with + // values that should fall on pixel boundaries. + mExampleDimension = a.getDimension( + R.styleable.PerfTimeline_exampleDimension, + mExampleDimension); + + a.recycle(); + + // Set up a default TextPaint object + mTextPaint = new TextPaint(); + mTextPaint.setFlags(Paint.ANTI_ALIAS_FLAG); + mTextPaint.setTextAlign(Paint.Align.LEFT); + + // Update TextPaint and text measurements from attributes + invalidateTextPaintAndMeasurements(); + + mPaintBaseLow = new Paint(Paint.ANTI_ALIAS_FLAG); + mPaintBaseLow.setStyle(Paint.Style.FILL); + mPaintBaseLow.setColor(0xff000000); + + mPaintBaseHigh = new Paint(Paint.ANTI_ALIAS_FLAG); + mPaintBaseHigh.setStyle(Paint.Style.FILL); + mPaintBaseHigh.setColor(0x7f7f7f7f); + + mPaintValue = new Paint(Paint.ANTI_ALIAS_FLAG); + mPaintValue.setStyle(Paint.Style.FILL); + mPaintValue.setColor(0x7fff0000); + + } + + private void invalidateTextPaintAndMeasurements() { + mTextPaint.setTextSize(mExampleDimension); + mTextPaint.setColor(mExampleColor); + mTextWidth = mTextPaint.measureText(mExampleString); + + Paint.FontMetrics fontMetrics = mTextPaint.getFontMetrics(); + mTextHeight = fontMetrics.bottom; + } + + @Override + protected void onDraw(Canvas canvas) { + super.onDraw(canvas); + + // TODO: consider storing these as member variables to reduce + // allocations per draw cycle. + int paddingLeft = getPaddingLeft(); + int paddingTop = getPaddingTop(); + int paddingRight = getPaddingRight(); + int paddingBottom = getPaddingBottom(); + + int contentWidth = getWidth() - paddingLeft - paddingRight; + int contentHeight = getHeight() - paddingTop - paddingBottom; + + // Draw the text. + //canvas.drawText(mExampleString, + // paddingLeft + (contentWidth - mTextWidth) / 2, + // paddingTop + (contentHeight + mTextHeight) / 2, + // mTextPaint); + + + + + // Draw the shadow + //RectF rf = new RectF(10.f, 10.f, 100.f, 100.f); + //canvas.drawOval(rf, mShadowPaint); + + if (mLinesLow != null) { + canvas.drawLines(mLinesLow, mPaintBaseLow); + } + if (mLinesHigh != null) { + canvas.drawLines(mLinesHigh, mPaintBaseHigh); + } + if (mLinesValue != null) { + canvas.drawLines(mLinesValue, mPaintValue); + } + + +/* + // Draw the pie slices + for (int i = 0; i < mData.size(); ++i) { + Item it = mData.get(i); + mPiePaint.setShader(it.mShader); + canvas.drawArc(mBounds, + 360 - it.mEndAngle, + it.mEndAngle - it.mStartAngle, + true, mPiePaint); + } +*/ + // Draw the pointer + //canvas.drawLine(mTextX, mPointerY, mPointerX, mPointerY, mTextPaint); + //canvas.drawCircle(mPointerX, mPointerY, mPointerSize, mTextPaint); + } + + /** + * Gets the example string attribute value. + * + * @return The example string attribute value. + */ + public String getExampleString() { + return mExampleString; + } + + /** + * Sets the view's example string attribute value. In the example view, this string + * is the text to draw. + * + * @param exampleString The example string attribute value to use. + */ + public void setExampleString(String exampleString) { + mExampleString = exampleString; + invalidateTextPaintAndMeasurements(); + } + + /** + * Gets the example color attribute value. + * + * @return The example color attribute value. + */ + public int getExampleColor() { + return mExampleColor; + } + + /** + * Sets the view's example color attribute value. In the example view, this color + * is the font color. + * + * @param exampleColor The example color attribute value to use. + */ + public void setExampleColor(int exampleColor) { + mExampleColor = exampleColor; + invalidateTextPaintAndMeasurements(); + } + + /** + * Gets the example dimension attribute value. + * + * @return The example dimension attribute value. + */ + public float getExampleDimension() { + return mExampleDimension; + } + + /** + * Sets the view's example dimension attribute value. In the example view, this dimension + * is the font size. + * + * @param exampleDimension The example dimension attribute value to use. + */ + public void setExampleDimension(float exampleDimension) { + mExampleDimension = exampleDimension; + invalidateTextPaintAndMeasurements(); + } +} diff --git a/tests/JankBench/app/src/main/java/com/android/benchmark/app/RunLocalBenchmarksActivity.java b/tests/JankBench/app/src/main/java/com/android/benchmark/app/RunLocalBenchmarksActivity.java new file mode 100644 index 0000000000000..7641d0095a70f --- /dev/null +++ b/tests/JankBench/app/src/main/java/com/android/benchmark/app/RunLocalBenchmarksActivity.java @@ -0,0 +1,415 @@ +/* + * Copyright (C) 2015 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. + * + */ + +package com.android.benchmark.app; + +import android.content.Intent; +import android.os.AsyncTask; +import android.os.Bundle; +import android.os.Handler; +import android.support.v4.app.FragmentManager; +import android.support.v4.app.FragmentTransaction; +import android.support.v4.app.ListFragment; +import android.support.v7.app.AppCompatActivity; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.BaseAdapter; +import android.widget.ListView; +import android.widget.TextView; + +import com.android.benchmark.R; +import com.android.benchmark.registry.BenchmarkGroup; +import com.android.benchmark.registry.BenchmarkRegistry; +import com.android.benchmark.results.GlobalResultsStore; +import com.android.benchmark.results.UiBenchmarkResult; +import com.android.benchmark.synthetic.MemoryActivity; +import com.android.benchmark.ui.BitmapUploadActivity; +import com.android.benchmark.ui.EditTextInputActivity; +import com.android.benchmark.ui.FullScreenOverdrawActivity; +import com.android.benchmark.ui.ImageListViewScrollActivity; +import com.android.benchmark.ui.ListViewScrollActivity; +import com.android.benchmark.ui.ShadowGridActivity; +import com.android.benchmark.ui.TextScrollActivity; + +import org.apache.commons.math.stat.descriptive.SummaryStatistics; + +import java.util.ArrayList; +import java.util.HashMap; + +public class RunLocalBenchmarksActivity extends AppCompatActivity { + + public static final int RUN_COUNT = 5; + + private ArrayList mBenchmarksToRun; + private int mBenchmarkCursor; + private int mCurrentRunId; + private boolean mFinish; + + private Handler mHandler = new Handler(); + + private static final int[] ALL_TESTS = new int[] { + R.id.benchmark_list_view_scroll, + R.id.benchmark_image_list_view_scroll, + R.id.benchmark_shadow_grid, + R.id.benchmark_text_high_hitrate, + R.id.benchmark_text_low_hitrate, + R.id.benchmark_edit_text_input, + R.id.benchmark_overdraw, + }; + + public static class LocalBenchmarksList extends ListFragment { + private ArrayList mBenchmarks; + private int mRunId; + + public void setBenchmarks(ArrayList benchmarks) { + mBenchmarks = benchmarks; + } + + public void setRunId(int id) { + mRunId = id; + } + + @Override + public void onListItemClick(ListView l, View v, int position, long id) { + if (getActivity().findViewById(R.id.list_fragment_container) != null) { + FragmentManager fm = getActivity().getSupportFragmentManager(); + UiResultsFragment resultsView = new UiResultsFragment(); + String testName = BenchmarkRegistry.getBenchmarkName(v.getContext(), + mBenchmarks.get(position).id); + resultsView.setRunInfo(testName, mRunId); + FragmentTransaction fragmentTransaction = fm.beginTransaction(); + fragmentTransaction.replace(R.id.list_fragment_container, resultsView); + fragmentTransaction.addToBackStack(null); + fragmentTransaction.commit(); + } + } + } + + + private class LocalBenchmark { + int id; + int runCount = 0; + int totalCount = 0; + ArrayList mResultsUri = new ArrayList<>(); + + LocalBenchmark(int id, int runCount) { + this.id = id; + this.runCount = 0; + this.totalCount = runCount; + } + + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_running_list); + + initLocalBenchmarks(getIntent()); + + if (findViewById(R.id.list_fragment_container) != null) { + FragmentManager fm = getSupportFragmentManager(); + LocalBenchmarksList listView = new LocalBenchmarksList(); + listView.setListAdapter(new LocalBenchmarksListAdapter(LayoutInflater.from(this))); + listView.setBenchmarks(mBenchmarksToRun); + listView.setRunId(mCurrentRunId); + fm.beginTransaction().add(R.id.list_fragment_container, listView).commit(); + } + + TextView scoreView = (TextView) findViewById(R.id.score_text_view); + scoreView.setText("Running tests!"); + } + + private int translateBenchmarkIndex(int index) { + if (index >= 0 && index < ALL_TESTS.length) { + return ALL_TESTS[index]; + } + + return -1; + } + + private void initLocalBenchmarks(Intent intent) { + mBenchmarksToRun = new ArrayList<>(); + int[] enabledIds = intent.getIntArrayExtra(BenchmarkGroup.BENCHMARK_EXTRA_ENABLED_TESTS); + int runCount = intent.getIntExtra(BenchmarkGroup.BENCHMARK_EXTRA_RUN_COUNT, RUN_COUNT); + mFinish = intent.getBooleanExtra(BenchmarkGroup.BENCHMARK_EXTRA_FINISH, false); + + if (enabledIds == null) { + // run all tests + enabledIds = ALL_TESTS; + } + + StringBuilder idString = new StringBuilder(); + idString.append(runCount); + idString.append(System.currentTimeMillis()); + + for (int i = 0; i < enabledIds.length; i++) { + int id = enabledIds[i]; + System.out.println("considering " + id); + if (!isValidBenchmark(id)) { + System.out.println("not valid " + id); + id = translateBenchmarkIndex(id); + System.out.println("got out " + id); + System.out.println("expected: " + R.id.benchmark_overdraw); + } + + if (isValidBenchmark(id)) { + int localRunCount = runCount; + if (isCompute(id)) { + localRunCount = 1; + } + mBenchmarksToRun.add(new LocalBenchmark(id, localRunCount)); + idString.append(id); + } + } + + mBenchmarkCursor = 0; + mCurrentRunId = idString.toString().hashCode(); + } + + private boolean isCompute(int id) { + switch (id) { + case R.id.benchmark_cpu_gflops: + case R.id.benchmark_cpu_heat_soak: + case R.id.benchmark_memory_bandwidth: + case R.id.benchmark_memory_latency: + case R.id.benchmark_power_management: + return true; + default: + return false; + } + } + + private static boolean isValidBenchmark(int benchmarkId) { + switch (benchmarkId) { + case R.id.benchmark_list_view_scroll: + case R.id.benchmark_image_list_view_scroll: + case R.id.benchmark_shadow_grid: + case R.id.benchmark_text_high_hitrate: + case R.id.benchmark_text_low_hitrate: + case R.id.benchmark_edit_text_input: + case R.id.benchmark_overdraw: + case R.id.benchmark_memory_bandwidth: + case R.id.benchmark_memory_latency: + case R.id.benchmark_power_management: + case R.id.benchmark_cpu_heat_soak: + case R.id.benchmark_cpu_gflops: + return true; + default: + return false; + } + } + + @Override + protected void onResume() { + super.onResume(); + mHandler.postDelayed(new Runnable() { + @Override + public void run() { + runNextBenchmark(); + } + }, 1000); + } + + private void computeOverallScore() { + final TextView scoreView = (TextView) findViewById(R.id.score_text_view); + scoreView.setText("Computing score..."); + new AsyncTask() { + @Override + protected Integer doInBackground(Void... voids) { + GlobalResultsStore gsr = + GlobalResultsStore.getInstance(RunLocalBenchmarksActivity.this); + ArrayList testLevelScores = new ArrayList<>(); + final SummaryStatistics stats = new SummaryStatistics(); + for (LocalBenchmark b : mBenchmarksToRun) { + HashMap> detailedResults = + gsr.loadDetailedResults(mCurrentRunId); + for (ArrayList testResult : detailedResults.values()) { + for (UiBenchmarkResult res : testResult) { + int score = res.getScore(); + if (score == 0) { + score = 1; + } + stats.addValue(score); + } + + testLevelScores.add(stats.getGeometricMean()); + stats.clear(); + } + + } + + for (double score : testLevelScores) { + stats.addValue(score); + } + + return (int)Math.round(stats.getGeometricMean()); + } + + @Override + protected void onPostExecute(Integer score) { + TextView view = (TextView) + RunLocalBenchmarksActivity.this.findViewById(R.id.score_text_view); + view.setText("Score: " + score); + } + }.execute(); + } + + private void runNextBenchmark() { + LocalBenchmark benchmark = mBenchmarksToRun.get(mBenchmarkCursor); + boolean runAgain = false; + + if (benchmark.runCount < benchmark.totalCount) { + runBenchmarkForId(mBenchmarksToRun.get(mBenchmarkCursor).id, benchmark.runCount++); + } else if (mBenchmarkCursor + 1 < mBenchmarksToRun.size()) { + mBenchmarkCursor++; + benchmark = mBenchmarksToRun.get(mBenchmarkCursor); + runBenchmarkForId(benchmark.id, benchmark.runCount++); + } else if (runAgain) { + mBenchmarkCursor = 0; + initLocalBenchmarks(getIntent()); + + runBenchmarkForId(mBenchmarksToRun.get(mBenchmarkCursor).id, benchmark.runCount); + } else if (mFinish) { + finish(); + } else { + Log.i("BENCH", "BenchmarkDone!"); + computeOverallScore(); + } + } + + private void runBenchmarkForId(int id, int iteration) { + Intent intent; + int syntheticTestId = -1; + + System.out.println("iteration: " + iteration); + + switch (id) { + case R.id.benchmark_list_view_scroll: + intent = new Intent(getApplicationContext(), ListViewScrollActivity.class); + break; + case R.id.benchmark_image_list_view_scroll: + intent = new Intent(getApplicationContext(), ImageListViewScrollActivity.class); + break; + case R.id.benchmark_shadow_grid: + intent = new Intent(getApplicationContext(), ShadowGridActivity.class); + break; + case R.id.benchmark_text_high_hitrate: + intent = new Intent(getApplicationContext(), TextScrollActivity.class); + intent.putExtra(TextScrollActivity.EXTRA_HIT_RATE, 80); + intent.putExtra(BenchmarkRegistry.EXTRA_ID, id); + break; + case R.id.benchmark_text_low_hitrate: + intent = new Intent(getApplicationContext(), TextScrollActivity.class); + intent.putExtra(TextScrollActivity.EXTRA_HIT_RATE, 20); + intent.putExtra(BenchmarkRegistry.EXTRA_ID, id); + break; + case R.id.benchmark_edit_text_input: + intent = new Intent(getApplicationContext(), EditTextInputActivity.class); + break; + case R.id.benchmark_overdraw: + intent = new Intent(getApplicationContext(), BitmapUploadActivity.class); + break; + case R.id.benchmark_memory_bandwidth: + syntheticTestId = 0; + intent = new Intent(getApplicationContext(), MemoryActivity.class); + intent.putExtra("test", syntheticTestId); + break; + case R.id.benchmark_memory_latency: + syntheticTestId = 1; + intent = new Intent(getApplicationContext(), MemoryActivity.class); + intent.putExtra("test", syntheticTestId); + break; + case R.id.benchmark_power_management: + syntheticTestId = 2; + intent = new Intent(getApplicationContext(), MemoryActivity.class); + intent.putExtra("test", syntheticTestId); + break; + case R.id.benchmark_cpu_heat_soak: + syntheticTestId = 3; + intent = new Intent(getApplicationContext(), MemoryActivity.class); + intent.putExtra("test", syntheticTestId); + break; + case R.id.benchmark_cpu_gflops: + syntheticTestId = 4; + intent = new Intent(getApplicationContext(), MemoryActivity.class); + intent.putExtra("test", syntheticTestId); + break; + + default: + intent = null; + } + + if (intent != null) { + intent.putExtra("com.android.benchmark.RUN_ID", mCurrentRunId); + intent.putExtra("com.android.benchmark.ITERATION", iteration); + startActivityForResult(intent, id & 0xffff, null); + } + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + switch (requestCode) { + case R.id.benchmark_shadow_grid: + case R.id.benchmark_list_view_scroll: + case R.id.benchmark_image_list_view_scroll: + case R.id.benchmark_text_high_hitrate: + case R.id.benchmark_text_low_hitrate: + case R.id.benchmark_edit_text_input: + break; + default: + } + } + + class LocalBenchmarksListAdapter extends BaseAdapter { + + private final LayoutInflater mInflater; + + LocalBenchmarksListAdapter(LayoutInflater inflater) { + mInflater = inflater; + } + + @Override + public int getCount() { + return mBenchmarksToRun.size(); + } + + @Override + public Object getItem(int i) { + return mBenchmarksToRun.get(i); + } + + @Override + public long getItemId(int i) { + return mBenchmarksToRun.get(i).id; + } + + @Override + public View getView(int i, View convertView, ViewGroup parent) { + if (convertView == null) { + convertView = mInflater.inflate(R.layout.running_benchmark_list_item, null); + } + + TextView name = (TextView) convertView.findViewById(R.id.benchmark_name); + name.setText(BenchmarkRegistry.getBenchmarkName( + RunLocalBenchmarksActivity.this, mBenchmarksToRun.get(i).id)); + return convertView; + } + + } +} diff --git a/tests/JankBench/app/src/main/java/com/android/benchmark/app/UiResultsFragment.java b/tests/JankBench/app/src/main/java/com/android/benchmark/app/UiResultsFragment.java new file mode 100644 index 0000000000000..56e94d538ab5f --- /dev/null +++ b/tests/JankBench/app/src/main/java/com/android/benchmark/app/UiResultsFragment.java @@ -0,0 +1,167 @@ +/* + * Copyright (C) 2015 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. + */ + +package com.android.benchmark.app; + +import android.annotation.TargetApi; +import android.os.AsyncTask; +import android.os.Bundle; +import android.support.annotation.Nullable; +import android.support.v4.app.ListFragment; +import android.util.Log; +import android.view.FrameMetrics; +import android.widget.SimpleAdapter; + +import com.android.benchmark.R; +import com.android.benchmark.registry.BenchmarkGroup; +import com.android.benchmark.registry.BenchmarkRegistry; +import com.android.benchmark.results.GlobalResultsStore; +import com.android.benchmark.results.UiBenchmarkResult; + +import org.apache.commons.math.stat.descriptive.SummaryStatistics; + +import java.io.FileWriter; +import java.io.IOException; +import java.net.URI; +import java.text.DecimalFormat; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; + +@TargetApi(24) +public class UiResultsFragment extends ListFragment { + private static final String TAG = "UiResultsFragment"; + private static final int NUM_FIELDS = 20; + + private ArrayList mResults = new ArrayList<>(); + + private AsyncTask>> mLoadScoresTask = + new AsyncTask>>() { + @Override + protected ArrayList> doInBackground(Void... voids) { + String[] data; + if (mResults.size() == 0 || mResults.get(0) == null) { + data = new String[] { + "No metrics reported", "" + }; + } else { + data = new String[NUM_FIELDS * (1 + mResults.size()) + 2]; + SummaryStatistics stats = new SummaryStatistics(); + int totalFrameCount = 0; + double totalAvgFrameDuration = 0; + double total99FrameDuration = 0; + double total95FrameDuration = 0; + double total90FrameDuration = 0; + double totalLongestFrame = 0; + double totalShortestFrame = 0; + + for (int i = 0; i < mResults.size(); i++) { + int start = (i * NUM_FIELDS) + + NUM_FIELDS; + data[(start++)] = "Iteration"; + data[(start++)] = "" + i; + data[(start++)] = "Total Frames"; + int currentFrameCount = mResults.get(i).getTotalFrameCount(); + totalFrameCount += currentFrameCount; + data[(start++)] = Integer.toString(currentFrameCount); + data[(start++)] = "Average frame duration:"; + double currentAvgFrameDuration = mResults.get(i).getAverage(FrameMetrics.TOTAL_DURATION); + totalAvgFrameDuration += currentAvgFrameDuration; + data[(start++)] = String.format("%.2f", currentAvgFrameDuration); + data[(start++)] = "Frame duration 99th:"; + double current99FrameDuration = mResults.get(i).getPercentile(FrameMetrics.TOTAL_DURATION, 99); + total99FrameDuration += current99FrameDuration; + data[(start++)] = String.format("%.2f", current99FrameDuration); + data[(start++)] = "Frame duration 95th:"; + double current95FrameDuration = mResults.get(i).getPercentile(FrameMetrics.TOTAL_DURATION, 95); + total95FrameDuration += current95FrameDuration; + data[(start++)] = String.format("%.2f", current95FrameDuration); + data[(start++)] = "Frame duration 90th:"; + double current90FrameDuration = mResults.get(i).getPercentile(FrameMetrics.TOTAL_DURATION, 90); + total90FrameDuration += current90FrameDuration; + data[(start++)] = String.format("%.2f", current90FrameDuration); + data[(start++)] = "Longest frame:"; + double longestFrame = mResults.get(i).getMaximum(FrameMetrics.TOTAL_DURATION); + if (totalLongestFrame == 0 || longestFrame > totalLongestFrame) { + totalLongestFrame = longestFrame; + } + data[(start++)] = String.format("%.2f", longestFrame); + data[(start++)] = "Shortest frame:"; + double shortestFrame = mResults.get(i).getMinimum(FrameMetrics.TOTAL_DURATION); + if (totalShortestFrame == 0 || totalShortestFrame > shortestFrame) { + totalShortestFrame = shortestFrame; + } + data[(start++)] = String.format("%.2f", shortestFrame); + data[(start++)] = "Score:"; + double score = mResults.get(i).getScore(); + stats.addValue(score); + data[(start++)] = String.format("%.2f", score); + data[(start++)] = "=============="; + data[(start++)] = "============================"; + }; + + int start = 0; + data[0] = "Overall: "; + data[1] = ""; + data[(start++)] = "Total Frames"; + data[(start++)] = Integer.toString(totalFrameCount); + data[(start++)] = "Average frame duration:"; + data[(start++)] = String.format("%.2f", totalAvgFrameDuration / mResults.size()); + data[(start++)] = "Frame duration 99th:"; + data[(start++)] = String.format("%.2f", total99FrameDuration / mResults.size()); + data[(start++)] = "Frame duration 95th:"; + data[(start++)] = String.format("%.2f", total95FrameDuration / mResults.size()); + data[(start++)] = "Frame duration 90th:"; + data[(start++)] = String.format("%.2f", total90FrameDuration / mResults.size()); + data[(start++)] = "Longest frame:"; + data[(start++)] = String.format("%.2f", totalLongestFrame); + data[(start++)] = "Shortest frame:"; + data[(start++)] = String.format("%.2f", totalShortestFrame); + data[(start++)] = "Score:"; + data[(start++)] = String.format("%.2f", stats.getGeometricMean()); + data[(start++)] = "=============="; + data[(start++)] = "============================"; + } + + ArrayList> dataMap = new ArrayList<>(); + for (int i = 0; i < data.length - 1; i += 2) { + HashMap map = new HashMap<>(); + map.put("name", data[i]); + map.put("value", data[i + 1]); + dataMap.add(map); + } + + return dataMap; + } + + @Override + protected void onPostExecute(ArrayList> dataMap) { + setListAdapter(new SimpleAdapter(getActivity(), dataMap, R.layout.results_list_item, + new String[] {"name", "value"}, new int[] { R.id.result_name, R.id.result_value })); + setListShown(true); + } + }; + + @Override + public void onActivityCreated(@Nullable Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + setListShown(false); + mLoadScoresTask.execute(); + } + + public void setRunInfo(String name, int runId) { + mResults = GlobalResultsStore.getInstance(getActivity()).loadTestResults(name, runId); + } +} diff --git a/tests/JankBench/app/src/main/java/com/android/benchmark/registry/BenchmarkCategory.java b/tests/JankBench/app/src/main/java/com/android/benchmark/registry/BenchmarkCategory.java new file mode 100644 index 0000000000000..d91e5798f05b5 --- /dev/null +++ b/tests/JankBench/app/src/main/java/com/android/benchmark/registry/BenchmarkCategory.java @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2015 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. + * + */ + +package com.android.benchmark.registry; + +import android.support.annotation.IntDef; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * Represents the category of a particular benchmark. + */ +@Retention(RetentionPolicy.SOURCE) +@IntDef({BenchmarkCategory.GENERIC, BenchmarkCategory.UI, BenchmarkCategory.COMPUTE}) +@interface BenchmarkCategory { + int GENERIC = 0; + int UI = 1; + int COMPUTE = 2; +} diff --git a/tests/JankBench/app/src/main/java/com/android/benchmark/registry/BenchmarkGroup.java b/tests/JankBench/app/src/main/java/com/android/benchmark/registry/BenchmarkGroup.java new file mode 100644 index 0000000000000..4cb7716ee5e9b --- /dev/null +++ b/tests/JankBench/app/src/main/java/com/android/benchmark/registry/BenchmarkGroup.java @@ -0,0 +1,151 @@ +/* + * Copyright (C) 2015 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. + * + */ + +package com.android.benchmark.registry; + +import android.content.ComponentName; +import android.content.Intent; +import android.view.View; +import android.widget.CheckBox; + +/** + * Logical grouping of benchmarks + */ +public class BenchmarkGroup { + public static final String BENCHMARK_EXTRA_ENABLED_TESTS = + "com.android.benchmark.EXTRA_ENABLED_BENCHMARK_IDS"; + + public static final String BENCHMARK_EXTRA_RUN_COUNT = + "com.android.benchmark.EXTRA_RUN_COUNT"; + public static final String BENCHMARK_EXTRA_FINISH = "com.android.benchmark.FINISH_WHEN_DONE"; + + public static class Benchmark implements CheckBox.OnClickListener { + /** The name of this individual benchmark test */ + private final String mName; + + /** The category of this individual benchmark test */ + private final @BenchmarkCategory int mCategory; + + /** Human-readable description of the benchmark */ + private final String mDescription; + + private final int mId; + + private boolean mEnabled; + + Benchmark(int id, String name, @BenchmarkCategory int category, String description) { + mId = id; + mName = name; + mCategory = category; + mDescription = description; + mEnabled = true; + } + + public boolean isEnabled() { return mEnabled; } + + public void setEnabled(boolean enabled) { mEnabled = enabled; } + + public int getId() { return mId; } + + public String getDescription() { return mDescription; } + + public @BenchmarkCategory int getCategory() { return mCategory; } + + public String getName() { return mName; } + + @Override + public void onClick(View view) { + setEnabled(((CheckBox)view).isChecked()); + } + } + + /** + * Component for this benchmark group. + */ + private final ComponentName mComponentName; + + /** + * Benchmark title, showed in the {@link android.widget.ListView} + */ + private final String mTitle; + + /** + * List of all benchmarks exported by this group + */ + private final Benchmark[] mBenchmarks; + + /** + * The intent to launch the benchmark + */ + private final Intent mIntent; + + /** Human-readable description of the benchmark group */ + private final String mDescription; + + BenchmarkGroup(ComponentName componentName, String title, + String description, Benchmark[] benchmarks, Intent intent) { + mComponentName = componentName; + mTitle = title; + mBenchmarks = benchmarks; + mDescription = description; + mIntent = intent; + } + + public Intent getIntent() { + int[] enabledBenchmarksIds = getEnabledBenchmarksIds(); + if (enabledBenchmarksIds.length != 0) { + mIntent.putExtra(BENCHMARK_EXTRA_ENABLED_TESTS, enabledBenchmarksIds); + return mIntent; + } + + return null; + } + + public ComponentName getComponentName() { + return mComponentName; + } + + public String getTitle() { + return mTitle; + } + + public Benchmark[] getBenchmarks() { + return mBenchmarks; + } + + public String getDescription() { + return mDescription; + } + + private int[] getEnabledBenchmarksIds() { + int enabledBenchmarkCount = 0; + for (int i = 0; i < mBenchmarks.length; i++) { + if (mBenchmarks[i].isEnabled()) { + enabledBenchmarkCount++; + } + } + + int writeIndex = 0; + int[] enabledBenchmarks = new int[enabledBenchmarkCount]; + for (int i = 0; i < mBenchmarks.length; i++) { + if (mBenchmarks[i].isEnabled()) { + enabledBenchmarks[writeIndex++] = mBenchmarks[i].getId(); + } + } + + return enabledBenchmarks; + } +} diff --git a/tests/JankBench/app/src/main/java/com/android/benchmark/registry/BenchmarkRegistry.java b/tests/JankBench/app/src/main/java/com/android/benchmark/registry/BenchmarkRegistry.java new file mode 100644 index 0000000000000..89c6aedd8b5c6 --- /dev/null +++ b/tests/JankBench/app/src/main/java/com/android/benchmark/registry/BenchmarkRegistry.java @@ -0,0 +1,236 @@ +/* + * Copyright (C) 2015 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. + * + */ + +package com.android.benchmark.registry; + +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.pm.ActivityInfo; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.content.res.Resources; +import android.content.res.TypedArray; +import android.content.res.XmlResourceParser; +import android.util.AttributeSet; +import android.util.SparseArray; +import android.util.Xml; + +import com.android.benchmark.R; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +/** + */ +public class BenchmarkRegistry { + + /** Metadata key for benchmark XML data */ + private static final String BENCHMARK_GROUP_META_KEY = + "com.android.benchmark.benchmark_group"; + + /** Intent action specifying an activity that runs a single benchmark test. */ + private static final String ACTION_BENCHMARK = "com.android.benchmark.ACTION_BENCHMARK"; + public static final String EXTRA_ID = "com.android.benchmark.EXTRA_ID"; + + private static final String TAG_BENCHMARK_GROUP = "com.android.benchmark.BenchmarkGroup"; + private static final String TAG_BENCHMARK = "com.android.benchmark.Benchmark"; + + private List mGroups; + + private final Context mContext; + + public BenchmarkRegistry(Context context) { + mContext = context; + mGroups = new ArrayList<>(); + loadBenchmarks(); + } + + private Intent getIntentFromInfo(ActivityInfo inf) { + Intent intent = new Intent(); + intent.setClassName(inf.packageName, inf.name); + return intent; + } + + public void loadBenchmarks() { + Intent intent = new Intent(ACTION_BENCHMARK); + intent.setPackage(mContext.getPackageName()); + + PackageManager pm = mContext.getPackageManager(); + List resolveInfos = pm.queryIntentActivities(intent, + PackageManager.GET_ACTIVITIES | PackageManager.GET_META_DATA); + + for (ResolveInfo inf : resolveInfos) { + List groups = parseBenchmarkGroup(inf.activityInfo); + if (groups != null) { + mGroups.addAll(groups); + } + } + } + + private boolean seekToTag(XmlPullParser parser, String tag) + throws XmlPullParserException, IOException { + int eventType = parser.getEventType(); + while (eventType != XmlPullParser.START_TAG && eventType != XmlPullParser.END_DOCUMENT) { + eventType = parser.next(); + } + return eventType != XmlPullParser.END_DOCUMENT && tag.equals(parser.getName()); + } + + @BenchmarkCategory int getCategory(int category) { + switch (category) { + case BenchmarkCategory.COMPUTE: + return BenchmarkCategory.COMPUTE; + case BenchmarkCategory.UI: + return BenchmarkCategory.UI; + default: + return BenchmarkCategory.GENERIC; + } + } + + private List parseBenchmarkGroup(ActivityInfo activityInfo) { + PackageManager pm = mContext.getPackageManager(); + + ComponentName componentName = new ComponentName( + activityInfo.packageName, activityInfo.name); + + SparseArray> benchmarks = new SparseArray<>(); + String groupName, groupDescription; + try (XmlResourceParser parser = activityInfo.loadXmlMetaData(pm, BENCHMARK_GROUP_META_KEY)) { + + if (!seekToTag(parser, TAG_BENCHMARK_GROUP)) { + return null; + } + + Resources res = pm.getResourcesForActivity(componentName); + AttributeSet attributeSet = Xml.asAttributeSet(parser); + TypedArray groupAttribs = res.obtainAttributes(attributeSet, R.styleable.BenchmarkGroup); + + groupName = groupAttribs.getString(R.styleable.BenchmarkGroup_name); + groupDescription = groupAttribs.getString(R.styleable.BenchmarkGroup_description); + groupAttribs.recycle(); + parser.next(); + + while (seekToTag(parser, TAG_BENCHMARK)) { + TypedArray benchAttribs = + res.obtainAttributes(Xml.asAttributeSet(parser), R.styleable.Benchmark); + int id = benchAttribs.getResourceId(R.styleable.Benchmark_id, -1); + String testName = benchAttribs.getString(R.styleable.Benchmark_name); + String testDescription = benchAttribs.getString(R.styleable.Benchmark_description); + int testCategory = benchAttribs.getInt(R.styleable.Benchmark_category, + BenchmarkCategory.GENERIC); + int category = getCategory(testCategory); + BenchmarkGroup.Benchmark benchmark = new BenchmarkGroup.Benchmark( + id, testName, category, testDescription); + List benches = benchmarks.get(category); + if (benches == null) { + benches = new ArrayList<>(); + benchmarks.append(category, benches); + } + + benches.add(benchmark); + + benchAttribs.recycle(); + parser.next(); + } + } catch (PackageManager.NameNotFoundException | XmlPullParserException | IOException e) { + return null; + } + + List result = new ArrayList<>(); + Intent testIntent = getIntentFromInfo(activityInfo); + for (int i = 0; i < benchmarks.size(); i++) { + int cat = benchmarks.keyAt(i); + List thisGroup = benchmarks.get(cat); + BenchmarkGroup.Benchmark[] benchmarkArray = + new BenchmarkGroup.Benchmark[thisGroup.size()]; + thisGroup.toArray(benchmarkArray); + result.add(new BenchmarkGroup(componentName, + groupName + " - " + getCategoryString(cat), groupDescription, benchmarkArray, + testIntent)); + } + + return result; + } + + public int getGroupCount() { + return mGroups.size(); + } + + public int getBenchmarkCount(int benchmarkIndex) { + BenchmarkGroup group = getBenchmarkGroup(benchmarkIndex); + if (group != null) { + return group.getBenchmarks().length; + } + return 0; + } + + public BenchmarkGroup getBenchmarkGroup(int benchmarkIndex) { + if (benchmarkIndex >= mGroups.size()) { + return null; + } + + return mGroups.get(benchmarkIndex); + } + + public static String getCategoryString(int category) { + switch (category) { + case BenchmarkCategory.UI: + return "UI"; + case BenchmarkCategory.COMPUTE: + return "Compute"; + case BenchmarkCategory.GENERIC: + return "Generic"; + default: + return ""; + } + } + + public static String getBenchmarkName(Context context, int benchmarkId) { + switch (benchmarkId) { + case R.id.benchmark_list_view_scroll: + return context.getString(R.string.list_view_scroll_name); + case R.id.benchmark_image_list_view_scroll: + return context.getString(R.string.image_list_view_scroll_name); + case R.id.benchmark_shadow_grid: + return context.getString(R.string.shadow_grid_name); + case R.id.benchmark_text_high_hitrate: + return context.getString(R.string.text_high_hitrate_name); + case R.id.benchmark_text_low_hitrate: + return context.getString(R.string.text_low_hitrate_name); + case R.id.benchmark_edit_text_input: + return context.getString(R.string.edit_text_input_name); + case R.id.benchmark_memory_bandwidth: + return context.getString(R.string.memory_bandwidth_name); + case R.id.benchmark_memory_latency: + return context.getString(R.string.memory_latency_name); + case R.id.benchmark_power_management: + return context.getString(R.string.power_management_name); + case R.id.benchmark_cpu_heat_soak: + return context.getString(R.string.cpu_heat_soak_name); + case R.id.benchmark_cpu_gflops: + return context.getString(R.string.cpu_gflops_name); + case R.id.benchmark_overdraw: + return context.getString(R.string.overdraw_name); + default: + return "Some Benchmark"; + } + } +} diff --git a/tests/JankBench/app/src/main/java/com/android/benchmark/results/GlobalResultsStore.java b/tests/JankBench/app/src/main/java/com/android/benchmark/results/GlobalResultsStore.java new file mode 100644 index 0000000000000..5d0cba278053a --- /dev/null +++ b/tests/JankBench/app/src/main/java/com/android/benchmark/results/GlobalResultsStore.java @@ -0,0 +1,389 @@ +/* + * Copyright (C) 2015 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. + */ + +package com.android.benchmark.results; + +import android.content.ContentValues; +import android.content.Context; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteOpenHelper; +import android.view.FrameMetrics; +import android.widget.Toast; + +import org.apache.commons.math.stat.descriptive.DescriptiveStatistics; + +import java.io.FileWriter; +import java.io.IOException; +import java.text.DateFormat; +import java.util.ArrayList; +import java.util.Date; +import java.util.HashMap; + +public class GlobalResultsStore extends SQLiteOpenHelper { + private static final int VERSION = 2; + + private static GlobalResultsStore sInstance; + private static final String UI_RESULTS_TABLE = "ui_results"; + + private final Context mContext; + + private GlobalResultsStore(Context context) { + super(context, "BenchmarkResults", null, VERSION); + mContext = context; + } + + public static GlobalResultsStore getInstance(Context context) { + if (sInstance == null) { + sInstance = new GlobalResultsStore(context.getApplicationContext()); + } + + return sInstance; + } + + @Override + public void onCreate(SQLiteDatabase sqLiteDatabase) { + sqLiteDatabase.execSQL("CREATE TABLE " + UI_RESULTS_TABLE + " (" + + " _id INTEGER PRIMARY KEY AUTOINCREMENT," + + " name TEXT," + + " run_id INTEGER," + + " iteration INTEGER," + + " timestamp TEXT," + + " unknown_delay REAL," + + " input REAL," + + " animation REAL," + + " layout REAL," + + " draw REAL," + + " sync REAL," + + " command_issue REAL," + + " swap_buffers REAL," + + " total_duration REAL," + + " jank_frame BOOLEAN, " + + " device_charging INTEGER);"); + } + + public void storeRunResults(String testName, int runId, int iteration, + UiBenchmarkResult result) { + SQLiteDatabase db = getWritableDatabase(); + db.beginTransaction(); + + try { + String date = DateFormat.getDateTimeInstance().format(new Date()); + int jankIndexIndex = 0; + int[] sortedJankIndices = result.getSortedJankFrameIndices(); + int totalFrameCount = result.getTotalFrameCount(); + for (int frameIdx = 0; frameIdx < totalFrameCount; frameIdx++) { + ContentValues cv = new ContentValues(); + cv.put("name", testName); + cv.put("run_id", runId); + cv.put("iteration", iteration); + cv.put("timestamp", date); + cv.put("unknown_delay", + result.getMetricAtIndex(frameIdx, FrameMetrics.UNKNOWN_DELAY_DURATION)); + cv.put("input", + result.getMetricAtIndex(frameIdx, FrameMetrics.INPUT_HANDLING_DURATION)); + cv.put("animation", + result.getMetricAtIndex(frameIdx, FrameMetrics.ANIMATION_DURATION)); + cv.put("layout", + result.getMetricAtIndex(frameIdx, FrameMetrics.LAYOUT_MEASURE_DURATION)); + cv.put("draw", + result.getMetricAtIndex(frameIdx, FrameMetrics.DRAW_DURATION)); + cv.put("sync", + result.getMetricAtIndex(frameIdx, FrameMetrics.SYNC_DURATION)); + cv.put("command_issue", + result.getMetricAtIndex(frameIdx, FrameMetrics.COMMAND_ISSUE_DURATION)); + cv.put("swap_buffers", + result.getMetricAtIndex(frameIdx, FrameMetrics.SWAP_BUFFERS_DURATION)); + cv.put("total_duration", + result.getMetricAtIndex(frameIdx, FrameMetrics.TOTAL_DURATION)); + if (jankIndexIndex < sortedJankIndices.length && + sortedJankIndices[jankIndexIndex] == frameIdx) { + jankIndexIndex++; + cv.put("jank_frame", true); + } else { + cv.put("jank_frame", false); + } + db.insert(UI_RESULTS_TABLE, null, cv); + } + db.setTransactionSuccessful(); + Toast.makeText(mContext, "Score: " + result.getScore() + + " Jank: " + (100 * sortedJankIndices.length) / (float) totalFrameCount + "%", + Toast.LENGTH_LONG).show(); + } finally { + db.endTransaction(); + } + + } + + public ArrayList loadTestResults(String testName, int runId) { + SQLiteDatabase db = getReadableDatabase(); + ArrayList resultList = new ArrayList<>(); + try { + String[] columnsToQuery = new String[] { + "name", + "run_id", + "iteration", + "unknown_delay", + "input", + "animation", + "layout", + "draw", + "sync", + "command_issue", + "swap_buffers", + "total_duration", + }; + + Cursor cursor = db.query( + UI_RESULTS_TABLE, columnsToQuery, "run_id=? AND name=?", + new String[] { Integer.toString(runId), testName }, null, null, "iteration"); + + double[] values = new double[columnsToQuery.length - 3]; + + while (cursor.moveToNext()) { + int iteration = cursor.getInt(cursor.getColumnIndexOrThrow("iteration")); + + values[0] = cursor.getDouble( + cursor.getColumnIndexOrThrow("unknown_delay")); + values[1] = cursor.getDouble( + cursor.getColumnIndexOrThrow("input")); + values[2] = cursor.getDouble( + cursor.getColumnIndexOrThrow("animation")); + values[3] = cursor.getDouble( + cursor.getColumnIndexOrThrow("layout")); + values[4] = cursor.getDouble( + cursor.getColumnIndexOrThrow("draw")); + values[5] = cursor.getDouble( + cursor.getColumnIndexOrThrow("sync")); + values[6] = cursor.getDouble( + cursor.getColumnIndexOrThrow("command_issue")); + values[7] = cursor.getDouble( + cursor.getColumnIndexOrThrow("swap_buffers")); + values[8] = cursor.getDouble( + cursor.getColumnIndexOrThrow("total_duration")); + + UiBenchmarkResult iterationResult; + if (resultList.size() == iteration) { + iterationResult = new UiBenchmarkResult(values); + resultList.add(iteration, iterationResult); + } else { + iterationResult = resultList.get(iteration); + iterationResult.update(values); + } + } + + cursor.close(); + } finally { + db.close(); + } + + int total = resultList.get(0).getTotalFrameCount(); + for (int i = 0; i < total; i++) { + System.out.println(""+ resultList.get(0).getMetricAtIndex(0, FrameMetrics.TOTAL_DURATION)); + } + + return resultList; + } + + public HashMap> loadDetailedResults(int runId) { + SQLiteDatabase db = getReadableDatabase(); + HashMap> results = new HashMap<>(); + try { + String[] columnsToQuery = new String[] { + "name", + "run_id", + "iteration", + "unknown_delay", + "input", + "animation", + "layout", + "draw", + "sync", + "command_issue", + "swap_buffers", + "total_duration", + }; + + Cursor cursor = db.query( + UI_RESULTS_TABLE, columnsToQuery, "run_id=?", + new String[] { Integer.toString(runId) }, null, null, "name, iteration"); + + double[] values = new double[columnsToQuery.length - 3]; + while (cursor.moveToNext()) { + int iteration = cursor.getInt(cursor.getColumnIndexOrThrow("iteration")); + String name = cursor.getString(cursor.getColumnIndexOrThrow("name")); + ArrayList resultList = results.get(name); + if (resultList == null) { + resultList = new ArrayList<>(); + results.put(name, resultList); + } + + values[0] = cursor.getDouble( + cursor.getColumnIndexOrThrow("unknown_delay")); + values[1] = cursor.getDouble( + cursor.getColumnIndexOrThrow("input")); + values[2] = cursor.getDouble( + cursor.getColumnIndexOrThrow("animation")); + values[3] = cursor.getDouble( + cursor.getColumnIndexOrThrow("layout")); + values[4] = cursor.getDouble( + cursor.getColumnIndexOrThrow("draw")); + values[5] = cursor.getDouble( + cursor.getColumnIndexOrThrow("sync")); + values[6] = cursor.getDouble( + cursor.getColumnIndexOrThrow("command_issue")); + values[7] = cursor.getDouble( + cursor.getColumnIndexOrThrow("swap_buffers")); + values[8] = cursor.getDouble( + cursor.getColumnIndexOrThrow("total_duration")); + values[8] = cursor.getDouble( + cursor.getColumnIndexOrThrow("total_duration")); + + UiBenchmarkResult iterationResult; + if (resultList.size() == iteration) { + iterationResult = new UiBenchmarkResult(values); + resultList.add(iterationResult); + } else { + iterationResult = resultList.get(iteration); + iterationResult.update(values); + } + } + + cursor.close(); + } finally { + db.close(); + } + + return results; + } + + public void exportToCsv() throws IOException { + String path = mContext.getFilesDir() + "/results-" + System.currentTimeMillis() + ".csv"; + SQLiteDatabase db = getReadableDatabase(); + + // stats across metrics for each run and each test + HashMap stats = new HashMap<>(); + + Cursor runIdCursor = db.query( + UI_RESULTS_TABLE, new String[] { "run_id" }, null, null, "run_id", null, null); + + while (runIdCursor.moveToNext()) { + + int runId = runIdCursor.getInt(runIdCursor.getColumnIndexOrThrow("run_id")); + HashMap> detailedResults = + loadDetailedResults(runId); + + writeRawResults(runId, detailedResults); + + DescriptiveStatistics overall = new DescriptiveStatistics(); + try (FileWriter writer = new FileWriter(path, true)) { + writer.write("Run ID, " + runId + "\n"); + writer.write("Test, Iteration, Score, Jank Penalty, Consistency Bonus, 95th, " + + "90th\n"); + for (String testName : detailedResults.keySet()) { + ArrayList results = detailedResults.get(testName); + DescriptiveStatistics scoreStats = new DescriptiveStatistics(); + DescriptiveStatistics jankPenalty = new DescriptiveStatistics(); + DescriptiveStatistics consistencyBonus = new DescriptiveStatistics(); + for (int i = 0; i < results.size(); i++) { + UiBenchmarkResult result = results.get(i); + int score = result.getScore(); + scoreStats.addValue(score); + overall.addValue(score); + jankPenalty.addValue(result.getJankPenalty()); + consistencyBonus.addValue(result.getConsistencyBonus()); + + writer.write(testName); + writer.write(","); + writer.write("" + i); + writer.write(","); + writer.write("" + score); + writer.write(","); + writer.write("" + result.getJankPenalty()); + writer.write(","); + writer.write("" + result.getConsistencyBonus()); + writer.write(","); + writer.write(Double.toString( + result.getPercentile(FrameMetrics.TOTAL_DURATION, 95))); + writer.write(","); + writer.write(Double.toString( + result.getPercentile(FrameMetrics.TOTAL_DURATION, 90))); + writer.write("\n"); + } + + writer.write("Score CV," + + (100 * scoreStats.getStandardDeviation() + / scoreStats.getMean()) + "%\n"); + writer.write("Jank Penalty CV, " + + (100 * jankPenalty.getStandardDeviation() + / jankPenalty.getMean()) + "%\n"); + writer.write("Consistency Bonus CV, " + + (100 * consistencyBonus.getStandardDeviation() + / consistencyBonus.getMean()) + "%\n"); + writer.write("\n"); + } + + writer.write("Overall Score CV," + + (100 * overall.getStandardDeviation() / overall.getMean()) + "%\n"); + writer.flush(); + } + } + + runIdCursor.close(); + } + + private void writeRawResults(int runId, + HashMap> detailedResults) { + StringBuilder path = new StringBuilder(); + path.append(mContext.getFilesDir()); + path.append("/"); + path.append(Integer.toString(runId)); + path.append(".csv"); + try (FileWriter writer = new FileWriter(path.toString())) { + for (String test : detailedResults.keySet()) { + writer.write("Test, " + test + "\n"); + writer.write("iteration, unknown delay, input, animation, layout, draw, sync, " + + "command issue, swap buffers\n"); + ArrayList runs = detailedResults.get(test); + for (int i = 0; i < runs.size(); i++) { + UiBenchmarkResult run = runs.get(i); + for (int j = 0; j < run.getTotalFrameCount(); j++) { + writer.write(Integer.toString(i) + "," + + run.getMetricAtIndex(j, FrameMetrics.UNKNOWN_DELAY_DURATION) + "," + + run.getMetricAtIndex(j, FrameMetrics.INPUT_HANDLING_DURATION) + "," + + run.getMetricAtIndex(j, FrameMetrics.ANIMATION_DURATION) + "," + + run.getMetricAtIndex(j, FrameMetrics.LAYOUT_MEASURE_DURATION) + "," + + run.getMetricAtIndex(j, FrameMetrics.DRAW_DURATION) + "," + + run.getMetricAtIndex(j, FrameMetrics.SYNC_DURATION) + "," + + run.getMetricAtIndex(j, FrameMetrics.COMMAND_ISSUE_DURATION) + "," + + run.getMetricAtIndex(j, FrameMetrics.SWAP_BUFFERS_DURATION) + "," + + run.getMetricAtIndex(j, FrameMetrics.TOTAL_DURATION) + "\n"); + } + } + } + } catch (IOException e) { + e.printStackTrace(); + } + } + + @Override + public void onUpgrade(SQLiteDatabase sqLiteDatabase, int oldVersion, int currentVersion) { + if (oldVersion < VERSION) { + sqLiteDatabase.execSQL("ALTER TABLE " + + UI_RESULTS_TABLE + " ADD COLUMN timestamp TEXT;"); + } + } +} diff --git a/tests/JankBench/app/src/main/java/com/android/benchmark/results/UiBenchmarkResult.java b/tests/JankBench/app/src/main/java/com/android/benchmark/results/UiBenchmarkResult.java new file mode 100644 index 0000000000000..da6e05ac051bb --- /dev/null +++ b/tests/JankBench/app/src/main/java/com/android/benchmark/results/UiBenchmarkResult.java @@ -0,0 +1,245 @@ +/* + * Copyright (C) 2015 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. + */ + +package com.android.benchmark.results; + +import android.annotation.TargetApi; +import android.view.FrameMetrics; + +import org.apache.commons.math.stat.descriptive.DescriptiveStatistics; +import org.apache.commons.math.stat.descriptive.SummaryStatistics; + +import java.util.ArrayList; +import java.util.List; + +/** + * Utility for storing and analyzing UI benchmark results. + */ +@TargetApi(24) +public class UiBenchmarkResult { + private static final int BASE_SCORE = 100; + private static final int ZERO_SCORE_TOTAL_DURATION_MS = 32; + private static final int JANK_PENALTY_THRESHOLD_MS = 12; + private static final int ZERO_SCORE_ABOVE_THRESHOLD_MS = + ZERO_SCORE_TOTAL_DURATION_MS - JANK_PENALTY_THRESHOLD_MS; + private static final double JANK_PENALTY_PER_MS_ABOVE_THRESHOLD = + BASE_SCORE / (double)ZERO_SCORE_ABOVE_THRESHOLD_MS; + private static final int CONSISTENCY_BONUS_MAX = 100; + + private static final int METRIC_WAS_JANKY = -1; + + private static final int[] METRICS = new int[] { + FrameMetrics.UNKNOWN_DELAY_DURATION, + FrameMetrics.INPUT_HANDLING_DURATION, + FrameMetrics.ANIMATION_DURATION, + FrameMetrics.LAYOUT_MEASURE_DURATION, + FrameMetrics.DRAW_DURATION, + FrameMetrics.SYNC_DURATION, + FrameMetrics.COMMAND_ISSUE_DURATION, + FrameMetrics.SWAP_BUFFERS_DURATION, + FrameMetrics.TOTAL_DURATION, + }; + public static final int FRAME_PERIOD_MS = 16; + + private final DescriptiveStatistics[] mStoredStatistics; + + public UiBenchmarkResult(List instances) { + mStoredStatistics = new DescriptiveStatistics[METRICS.length]; + insertMetrics(instances); + } + + public UiBenchmarkResult(double[] values) { + mStoredStatistics = new DescriptiveStatistics[METRICS.length]; + insertValues(values); + } + + public void update(List instances) { + insertMetrics(instances); + } + + public void update(double[] values) { + insertValues(values); + } + + public double getAverage(int id) { + int pos = getMetricPosition(id); + return mStoredStatistics[pos].getMean(); + } + + public double getMinimum(int id) { + int pos = getMetricPosition(id); + return mStoredStatistics[pos].getMin(); + } + + public double getMaximum(int id) { + int pos = getMetricPosition(id); + return mStoredStatistics[pos].getMax(); + } + + public int getMaximumIndex(int id) { + int pos = getMetricPosition(id); + double[] storedMetrics = mStoredStatistics[pos].getValues(); + int maxIdx = 0; + for (int i = 0; i < storedMetrics.length; i++) { + if (storedMetrics[i] >= storedMetrics[maxIdx]) { + maxIdx = i; + } + } + + return maxIdx; + } + + public double getMetricAtIndex(int index, int metricId) { + return mStoredStatistics[getMetricPosition(metricId)].getElement(index); + } + + public double getPercentile(int id, int percentile) { + if (percentile > 100) percentile = 100; + if (percentile < 0) percentile = 0; + + int metricPos = getMetricPosition(id); + return mStoredStatistics[metricPos].getPercentile(percentile); + } + + public int getTotalFrameCount() { + if (mStoredStatistics.length == 0) { + return 0; + } + + return (int) mStoredStatistics[0].getN(); + } + + public int getScore() { + SummaryStatistics badFramesStats = new SummaryStatistics(); + + int totalFrameCount = getTotalFrameCount(); + for (int i = 0; i < totalFrameCount; i++) { + double totalDuration = getMetricAtIndex(i, FrameMetrics.TOTAL_DURATION); + if (totalDuration >= 12) { + badFramesStats.addValue(totalDuration); + } + } + + int length = getSortedJankFrameIndices().length; + double jankFrameCount = 100 * length / (double) totalFrameCount; + + System.out.println("Mean: " + badFramesStats.getMean() + " JankP: " + jankFrameCount + + " StdDev: " + badFramesStats.getStandardDeviation() + + " Count Bad: " + badFramesStats.getN() + " Count Jank: " + length); + + return (int) Math.round( + (badFramesStats.getMean()) * jankFrameCount * badFramesStats.getStandardDeviation()); + } + + public int getJankPenalty() { + double total95th = mStoredStatistics[getMetricPosition(FrameMetrics.TOTAL_DURATION)] + .getPercentile(95); + System.out.println("95: " + total95th); + double aboveThreshold = total95th - JANK_PENALTY_THRESHOLD_MS; + if (aboveThreshold <= 0) { + return 0; + } + + if (aboveThreshold > ZERO_SCORE_ABOVE_THRESHOLD_MS) { + return BASE_SCORE; + } + + return (int) Math.ceil(JANK_PENALTY_PER_MS_ABOVE_THRESHOLD * aboveThreshold); + } + + public int getConsistencyBonus() { + DescriptiveStatistics totalDurationStats = + mStoredStatistics[getMetricPosition(FrameMetrics.TOTAL_DURATION)]; + + double standardDeviation = totalDurationStats.getStandardDeviation(); + if (standardDeviation == 0) { + return CONSISTENCY_BONUS_MAX; + } + + // 1 / CV of the total duration. + double bonus = totalDurationStats.getMean() / standardDeviation; + return (int) Math.min(Math.round(bonus), CONSISTENCY_BONUS_MAX); + } + + public int[] getSortedJankFrameIndices() { + ArrayList jankFrameIndices = new ArrayList<>(); + boolean tripleBuffered = false; + int totalFrameCount = getTotalFrameCount(); + int totalDurationPos = getMetricPosition(FrameMetrics.TOTAL_DURATION); + + for (int i = 0; i < totalFrameCount; i++) { + double thisDuration = mStoredStatistics[totalDurationPos].getElement(i); + if (!tripleBuffered) { + if (thisDuration > FRAME_PERIOD_MS) { + tripleBuffered = true; + jankFrameIndices.add(i); + } + } else { + if (thisDuration > 2 * FRAME_PERIOD_MS) { + tripleBuffered = false; + jankFrameIndices.add(i); + } + } + } + + int[] res = new int[jankFrameIndices.size()]; + int i = 0; + for (Integer index : jankFrameIndices) { + res[i++] = index; + } + return res; + } + + private int getMetricPosition(int id) { + for (int i = 0; i < METRICS.length; i++) { + if (id == METRICS[i]) { + return i; + } + } + + return -1; + } + + private void insertMetrics(List instances) { + for (FrameMetrics frame : instances) { + for (int i = 0; i < METRICS.length; i++) { + DescriptiveStatistics stats = mStoredStatistics[i]; + if (stats == null) { + stats = new DescriptiveStatistics(); + mStoredStatistics[i] = stats; + } + + mStoredStatistics[i].addValue(frame.getMetric(METRICS[i]) / (double) 1000000); + } + } + } + + private void insertValues(double[] values) { + if (values.length != METRICS.length) { + throw new IllegalArgumentException("invalid values array"); + } + + for (int i = 0; i < values.length; i++) { + DescriptiveStatistics stats = mStoredStatistics[i]; + if (stats == null) { + stats = new DescriptiveStatistics(); + mStoredStatistics[i] = stats; + } + + mStoredStatistics[i].addValue(values[i]); + } + } + } diff --git a/tests/JankBench/app/src/main/java/com/android/benchmark/synthetic/MemoryActivity.java b/tests/JankBench/app/src/main/java/com/android/benchmark/synthetic/MemoryActivity.java new file mode 100644 index 0000000000000..aba16d596e6f0 --- /dev/null +++ b/tests/JankBench/app/src/main/java/com/android/benchmark/synthetic/MemoryActivity.java @@ -0,0 +1,137 @@ +/* + * Copyright (C) 2015 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. + */ + +package com.android.benchmark.synthetic; + +import android.app.Activity; +import android.content.Intent; +import android.os.Bundle; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.view.WindowManager; +import android.widget.TextView; + +import com.android.benchmark.R; +import com.android.benchmark.app.PerfTimeline; + +import junit.framework.Test; + + +public class MemoryActivity extends Activity { + private TextView mTextStatus; + private TextView mTextMin; + private TextView mTextMax; + private TextView mTextTypical; + private PerfTimeline mTimeline; + + TestInterface mTI; + int mActiveTest; + + private class SyntheticTestCallback extends TestInterface.TestResultCallback { + @Override + void onTestResult(int command, float result) { + Intent resultIntent = new Intent(); + resultIntent.putExtra("com.android.benchmark.synthetic.TEST_RESULT", result); + setResult(RESULT_OK, resultIntent); + finish(); + } + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_memory); + + mTextStatus = (TextView) findViewById(R.id.textView_status); + mTextMin = (TextView) findViewById(R.id.textView_min); + mTextMax = (TextView) findViewById(R.id.textView_max); + mTextTypical = (TextView) findViewById(R.id.textView_typical); + + mTimeline = (PerfTimeline) findViewById(R.id.mem_timeline); + + mTI = new TestInterface(mTimeline, 2, new SyntheticTestCallback()); + mTI.mTextMax = mTextMax; + mTI.mTextMin = mTextMin; + mTI.mTextStatus = mTextStatus; + mTI.mTextTypical = mTextTypical; + + mTimeline.mLinesLow = mTI.mLinesLow; + mTimeline.mLinesHigh = mTI.mLinesHigh; + mTimeline.mLinesValue = mTI.mLinesValue; + + getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + } + + @Override + protected void onResume() { + super.onResume(); + Intent i = getIntent(); + mActiveTest = i.getIntExtra("test", 0); + + switch (mActiveTest) { + case 0: + mTI.runMemoryBandwidth(); + break; + case 1: + mTI.runMemoryLatency(); + break; + case 2: + mTI.runPowerManagement(); + break; + case 3: + mTI.runCPUHeatSoak(); + break; + case 4: + mTI.runCPUGFlops(); + break; + default: + break; + + } + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + // Inflate the menu; this adds items to the action bar if it is present. + getMenuInflater().inflate(R.menu.menu_memory, menu); + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + // Handle action bar item clicks here. The action bar will + // automatically handle clicks on the Home/Up button, so long + // as you specify a parent activity in AndroidManifest.xml. + int id = item.getItemId(); + + //noinspection SimplifiableIfStatement + if (id == R.id.action_settings) { + return true; + } + + return super.onOptionsItemSelected(item); + } + + public void onCpuBandwidth(View v) { + + + } + + + + +} diff --git a/tests/JankBench/app/src/main/java/com/android/benchmark/synthetic/TestInterface.java b/tests/JankBench/app/src/main/java/com/android/benchmark/synthetic/TestInterface.java new file mode 100644 index 0000000000000..8f083a27be8dc --- /dev/null +++ b/tests/JankBench/app/src/main/java/com/android/benchmark/synthetic/TestInterface.java @@ -0,0 +1,451 @@ +/* + * Copyright (C) 2015 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. + */ + +package com.android.benchmark.synthetic; + +import android.view.View; +import android.widget.TextView; + +import org.apache.commons.math.stat.StatUtils; +import org.apache.commons.math.stat.descriptive.SummaryStatistics; + +import java.util.LinkedList; +import java.util.Queue; + + +public class TestInterface { + native long nInit(long options); + native long nDestroy(long b); + native float nGetData(long b, float[] data); + native boolean nRunPowerManagementTest(long b, long options); + native boolean nRunCPUHeatSoakTest(long b, long options); + + native boolean nMemTestStart(long b); + native float nMemTestBandwidth(long b, long size); + native float nMemTestLatency(long b, long size); + native void nMemTestEnd(long b); + + native float nGFlopsTest(long b, long opt); + + public static class TestResultCallback { + void onTestResult(int command, float result) { } + } + + static { + System.loadLibrary("nativebench"); + } + + float[] mLinesLow; + float[] mLinesHigh; + float[] mLinesValue; + TextView mTextStatus; + TextView mTextMin; + TextView mTextMax; + TextView mTextTypical; + + private View mViewToUpdate; + + private LooperThread mLT; + + TestInterface(View v, int runtimeSeconds, TestResultCallback callback) { + int buckets = runtimeSeconds * 1000; + mLinesLow = new float[buckets * 4]; + mLinesHigh = new float[buckets * 4]; + mLinesValue = new float[buckets * 4]; + mViewToUpdate = v; + + mLT = new LooperThread(this, callback); + mLT.start(); + } + + static class LooperThread extends Thread { + public static final int CommandExit = 1; + public static final int TestPowerManagement = 2; + public static final int TestMemoryBandwidth = 3; + public static final int TestMemoryLatency = 4; + public static final int TestHeatSoak = 5; + public static final int TestGFlops = 6; + + private volatile boolean mRun = true; + private TestInterface mTI; + private TestResultCallback mCallback; + + Queue mCommandQueue = new LinkedList(); + + LooperThread(TestInterface ti, TestResultCallback callback) { + super("BenchmarkTestThread"); + mTI = ti; + mCallback = callback; + } + + void runCommand(int command) { + Integer i = Integer.valueOf(command); + + synchronized (this) { + mCommandQueue.add(i); + notifyAll(); + } + } + + public void run() { + long b = mTI.nInit(0); + if (b == 0) { + return; + } + + while (mRun) { + int command = 0; + synchronized (this) { + if (mCommandQueue.isEmpty()) { + try { + wait(); + } catch (InterruptedException e) { + } + } + + if (!mCommandQueue.isEmpty()) { + command = mCommandQueue.remove(); + } + } + + switch (command) { + case CommandExit: + mRun = false; + break; + case TestPowerManagement: + float score = mTI.testPowerManagement(b); + mCallback.onTestResult(command, 0); + break; + case TestMemoryBandwidth: + mTI.testCPUMemoryBandwidth(b); + break; + case TestMemoryLatency: + mTI.testCPUMemoryLatency(b); + break; + case TestHeatSoak: + mTI.testCPUHeatSoak(b); + break; + case TestGFlops: + mTI.testCPUGFlops(b); + break; + + } + + //mViewToUpdate.post(new Runnable() { + // public void run() { + // mViewToUpdate.invalidate(); + //} + //}); + } + + mTI.nDestroy(b); + } + + void exit() { + mRun = false; + } + } + + void postTextToView(TextView v, String s) { + final TextView tv = v; + final String ts = s; + + v.post(new Runnable() { + public void run() { + tv.setText(ts); + } + }); + + } + + float calcAverage(float[] data) { + float total = 0.f; + for (int ct=0; ct < data.length; ct++) { + total += data[ct]; + } + return total / data.length; + } + + void makeGraph(float[] data, float[] lines) { + for (int ct = 0; ct < data.length; ct++) { + lines[ct * 4 + 0] = (float)ct; + lines[ct * 4 + 1] = 500.f - data[ct]; + lines[ct * 4 + 2] = (float)ct; + lines[ct * 4 + 3] = 500.f; + } + } + + float testPowerManagement(long b) { + float[] dat = new float[mLinesLow.length / 4]; + postTextToView(mTextStatus, "Running single-threaded"); + nRunPowerManagementTest(b, 1); + nGetData(b, dat); + makeGraph(dat, mLinesLow); + mViewToUpdate.postInvalidate(); + float avgMin = calcAverage(dat); + + postTextToView(mTextMin, "Single threaded " + avgMin + " per second"); + + postTextToView(mTextStatus, "Running multi-threaded"); + nRunPowerManagementTest(b, 4); + nGetData(b, dat); + makeGraph(dat, mLinesHigh); + mViewToUpdate.postInvalidate(); + float avgMax = calcAverage(dat); + postTextToView(mTextMax, "Multi threaded " + avgMax + " per second"); + + postTextToView(mTextStatus, "Running typical"); + nRunPowerManagementTest(b, 0); + nGetData(b, dat); + makeGraph(dat, mLinesValue); + mViewToUpdate.postInvalidate(); + float avgTypical = calcAverage(dat); + + float ofIdeal = avgTypical / (avgMax + avgMin) * 200.f; + postTextToView(mTextTypical, String.format("Typical mix (50/50) %%%2.0f of ideal", ofIdeal)); + return ofIdeal * (avgMax + avgMin); + } + + float testCPUHeatSoak(long b) { + float[] dat = new float[1000]; + postTextToView(mTextStatus, "Running heat soak test"); + for (int t = 0; t < 1000; t++) { + mLinesLow[t * 4 + 0] = (float)t; + mLinesLow[t * 4 + 1] = 498.f; + mLinesLow[t * 4 + 2] = (float)t; + mLinesLow[t * 4 + 3] = 500.f; + } + + float peak = 0.f; + float total = 0.f; + float dThroughput = 0; + float prev = 0; + SummaryStatistics stats = new SummaryStatistics(); + for (int t = 0; t < 1000; t++) { + nRunCPUHeatSoakTest(b, 1); + nGetData(b, dat); + + float p = calcAverage(dat); + if (prev != 0) { + dThroughput += (prev - p); + } + + prev = p; + + mLinesLow[t * 4 + 1] = 499.f - p; + if (peak < p) { + peak = p; + } + for (float f : dat) { + stats.addValue(f); + } + + total += p; + + mViewToUpdate.postInvalidate(); + postTextToView(mTextMin, "Peak " + peak + " per second"); + postTextToView(mTextMax, "Current " + p + " per second"); + postTextToView(mTextTypical, "Average " + (total / (t + 1)) + " per second"); + } + + + float decreaseOverTime = dThroughput / 1000; + + System.out.println("dthroughput/dt: " + decreaseOverTime); + + float score = (float) (stats.getMean() / (stats.getStandardDeviation() * decreaseOverTime)); + + postTextToView(mTextStatus, "Score: " + score); + return score; + } + + void testCPUMemoryBandwidth(long b) { + int[] sizeK = {1, 2, 3, 4, 5, 6, 7, + 8, 10, 12, 14, 16, 20, 24, 28, + 32, 40, 48, 56, 64, 80, 96, 112, + 128, 160, 192, 224, 256, 320, 384, 448, + 512, 640, 768, 896, 1024, 1280, 1536, 1792, + 2048, 2560, 3584, 4096, 5120, 6144, 7168, + 8192, 10240, 12288, 14336, 16384 + }; + final int subSteps = 15; + float[] results = new float[sizeK.length * subSteps]; + + nMemTestStart(b); + + float[] dat = new float[1000]; + postTextToView(mTextStatus, "Running Memory Bandwidth test"); + for (int t = 0; t < 1000; t++) { + mLinesLow[t * 4 + 0] = (float)t; + mLinesLow[t * 4 + 1] = 498.f; + mLinesLow[t * 4 + 2] = (float)t; + mLinesLow[t * 4 + 3] = 500.f; + } + + for (int i = 0; i < sizeK.length; i++) { + postTextToView(mTextStatus, "Running " + sizeK[i] + " K"); + + float rtot = 0.f; + for (int j = 0; j < subSteps; j++) { + float ret = nMemTestBandwidth(b, sizeK[i] * 1024); + rtot += ret; + results[i * subSteps + j] = ret; + mLinesLow[(i * subSteps + j) * 4 + 1] = 499.f - (results[i*15+j] * 20.f); + mViewToUpdate.postInvalidate(); + } + rtot /= subSteps; + + if (sizeK[i] == 2) { + postTextToView(mTextMin, "2K " + rtot + " GB/s"); + } + if (sizeK[i] == 128) { + postTextToView(mTextMax, "128K " + rtot + " GB/s"); + } + if (sizeK[i] == 8192) { + postTextToView(mTextTypical, "8M " + rtot + " GB/s"); + } + + } + + nMemTestEnd(b); + postTextToView(mTextStatus, "Done"); + } + + void testCPUMemoryLatency(long b) { + int[] sizeK = {1, 2, 3, 4, 5, 6, 7, + 8, 10, 12, 14, 16, 20, 24, 28, + 32, 40, 48, 56, 64, 80, 96, 112, + 128, 160, 192, 224, 256, 320, 384, 448, + 512, 640, 768, 896, 1024, 1280, 1536, 1792, + 2048, 2560, 3584, 4096, 5120, 6144, 7168, + 8192, 10240, 12288, 14336, 16384 + }; + final int subSteps = 15; + float[] results = new float[sizeK.length * subSteps]; + + nMemTestStart(b); + + float[] dat = new float[1000]; + postTextToView(mTextStatus, "Running Memory Latency test"); + for (int t = 0; t < 1000; t++) { + mLinesLow[t * 4 + 0] = (float)t; + mLinesLow[t * 4 + 1] = 498.f; + mLinesLow[t * 4 + 2] = (float)t; + mLinesLow[t * 4 + 3] = 500.f; + } + + for (int i = 0; i < sizeK.length; i++) { + postTextToView(mTextStatus, "Running " + sizeK[i] + " K"); + + float rtot = 0.f; + for (int j = 0; j < subSteps; j++) { + float ret = nMemTestLatency(b, sizeK[i] * 1024); + rtot += ret; + results[i * subSteps + j] = ret; + + if (ret > 400.f) ret = 400.f; + if (ret < 0.f) ret = 0.f; + mLinesLow[(i * subSteps + j) * 4 + 1] = 499.f - ret; + //android.util.Log.e("bench", "test bw " + sizeK[i] + " - " + ret); + mViewToUpdate.postInvalidate(); + } + rtot /= subSteps; + + if (sizeK[i] == 2) { + postTextToView(mTextMin, "2K " + rtot + " ns"); + } + if (sizeK[i] == 128) { + postTextToView(mTextMax, "128K " + rtot + " ns"); + } + if (sizeK[i] == 8192) { + postTextToView(mTextTypical, "8M " + rtot + " ns"); + } + + } + + nMemTestEnd(b); + postTextToView(mTextStatus, "Done"); + } + + void testCPUGFlops(long b) { + int[] sizeK = {1, 2, 3, 4, 5, 6, 7 + }; + final int subSteps = 15; + float[] results = new float[sizeK.length * subSteps]; + + nMemTestStart(b); + + float[] dat = new float[1000]; + postTextToView(mTextStatus, "Running Memory Latency test"); + for (int t = 0; t < 1000; t++) { + mLinesLow[t * 4 + 0] = (float)t; + mLinesLow[t * 4 + 1] = 498.f; + mLinesLow[t * 4 + 2] = (float)t; + mLinesLow[t * 4 + 3] = 500.f; + } + + for (int i = 0; i < sizeK.length; i++) { + postTextToView(mTextStatus, "Running " + sizeK[i] + " K"); + + float rtot = 0.f; + for (int j = 0; j < subSteps; j++) { + float ret = nGFlopsTest(b, sizeK[i] * 1024); + rtot += ret; + results[i * subSteps + j] = ret; + + if (ret > 400.f) ret = 400.f; + if (ret < 0.f) ret = 0.f; + mLinesLow[(i * subSteps + j) * 4 + 1] = 499.f - ret; + mViewToUpdate.postInvalidate(); + } + rtot /= subSteps; + + if (sizeK[i] == 2) { + postTextToView(mTextMin, "2K " + rtot + " ns"); + } + if (sizeK[i] == 128) { + postTextToView(mTextMax, "128K " + rtot + " ns"); + } + if (sizeK[i] == 8192) { + postTextToView(mTextTypical, "8M " + rtot + " ns"); + } + + } + + nMemTestEnd(b); + postTextToView(mTextStatus, "Done"); + } + + public void runPowerManagement() { + mLT.runCommand(mLT.TestPowerManagement); + } + + public void runMemoryBandwidth() { + mLT.runCommand(mLT.TestMemoryBandwidth); + } + + public void runMemoryLatency() { + mLT.runCommand(mLT.TestMemoryLatency); + } + + public void runCPUHeatSoak() { + mLT.runCommand(mLT.TestHeatSoak); + } + + public void runCPUGFlops() { + mLT.runCommand(mLT.TestGFlops); + } +} diff --git a/tests/JankBench/app/src/main/java/com/android/benchmark/ui/BitmapUploadActivity.java b/tests/JankBench/app/src/main/java/com/android/benchmark/ui/BitmapUploadActivity.java new file mode 100644 index 0000000000000..f6a528a8a9668 --- /dev/null +++ b/tests/JankBench/app/src/main/java/com/android/benchmark/ui/BitmapUploadActivity.java @@ -0,0 +1,157 @@ +/* + * Copyright (C) 2015 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. + */ + +package com.android.benchmark.ui; + +import android.animation.ObjectAnimator; +import android.animation.ValueAnimator; +import android.content.Context; +import android.content.Intent; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Rect; +import android.os.Bundle; +import android.support.v7.app.AppCompatActivity; +import android.util.AttributeSet; +import android.util.DisplayMetrics; +import android.view.MotionEvent; +import android.view.View; + +import com.android.benchmark.R; +import com.android.benchmark.ui.automation.Automator; +import com.android.benchmark.ui.automation.Interaction; + +/** + * + */ +public class BitmapUploadActivity extends AppCompatActivity { + private Automator mAutomator; + + public static class UploadView extends View { + private int mColorValue; + private Bitmap mBitmap; + private final DisplayMetrics mMetrics = new DisplayMetrics(); + private final Rect mRect = new Rect(); + + public UploadView(Context context, AttributeSet attrs) { + super(context, attrs); + } + + @SuppressWarnings("unused") + public void setColorValue(int colorValue) { + if (colorValue == mColorValue) return; + + mColorValue = colorValue; + + // modify the bitmap's color to ensure it's uploaded to the GPU + mBitmap.eraseColor(Color.rgb(mColorValue, 255 - mColorValue, 255)); + + invalidate(); + } + + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + + getDisplay().getMetrics(mMetrics); + int minDisplayDimen = Math.min(mMetrics.widthPixels, mMetrics.heightPixels); + int bitmapSize = Math.min((int) (minDisplayDimen * 0.75), 720); + if (mBitmap == null + || mBitmap.getWidth() != bitmapSize + || mBitmap.getHeight() != bitmapSize) { + mBitmap = Bitmap.createBitmap(bitmapSize, bitmapSize, Bitmap.Config.ARGB_8888); + } + } + + @Override + protected void onDraw(Canvas canvas) { + if (mBitmap != null) { + mRect.set(0, 0, getWidth(), getHeight()); + canvas.drawBitmap(mBitmap, null, mRect, null); + } + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + // animate color to force bitmap uploads + return super.onTouchEvent(event); + } + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_bitmap_upload); + + final View uploadRoot = findViewById(R.id.upload_root); + uploadRoot.setKeepScreenOn(true); + uploadRoot.setOnTouchListener(new View.OnTouchListener() { + @Override + public boolean onTouch(View view, MotionEvent motionEvent) { + UploadView uploadView = (UploadView) findViewById(R.id.upload_view); + ObjectAnimator colorValueAnimator = + ObjectAnimator.ofInt(uploadView, "colorValue", 0, 255); + colorValueAnimator.setRepeatMode(ValueAnimator.REVERSE); + colorValueAnimator.setRepeatCount(100); + colorValueAnimator.start(); + + // animate scene root to guarantee there's a minimum amount of GPU rendering work + ObjectAnimator yAnimator = ObjectAnimator.ofFloat( + view, "translationY", 0, 100); + yAnimator.setRepeatMode(ValueAnimator.REVERSE); + yAnimator.setRepeatCount(100); + yAnimator.start(); + + return true; + } + }); + + final UploadView uploadView = (UploadView) findViewById(R.id.upload_view); + final int runId = getIntent().getIntExtra("com.android.benchmark.RUN_ID", 0); + final int iteration = getIntent().getIntExtra("com.android.benchmark.ITERATION", -1); + + mAutomator = new Automator("BMUpload", runId, iteration, getWindow(), + new Automator.AutomateCallback() { + @Override + public void onPostAutomate() { + Intent result = new Intent(); + setResult(RESULT_OK, result); + finish(); + } + + @Override + public void onAutomate() { + int[] coordinates = new int[2]; + uploadRoot.getLocationOnScreen(coordinates); + + int x = coordinates[0]; + int y = coordinates[1]; + + float width = uploadRoot.getWidth(); + float height = uploadRoot.getHeight(); + + float middleX = (x + width) / 5; + float middleY = (y + height) / 5; + + addInteraction(Interaction.newTap(middleX, middleY)); + } + }); + + mAutomator.start(); + } + +} diff --git a/tests/JankBench/app/src/main/java/com/android/benchmark/ui/EditTextInputActivity.java b/tests/JankBench/app/src/main/java/com/android/benchmark/ui/EditTextInputActivity.java new file mode 100644 index 0000000000000..ea6fb58f4775e --- /dev/null +++ b/tests/JankBench/app/src/main/java/com/android/benchmark/ui/EditTextInputActivity.java @@ -0,0 +1,160 @@ +/* + * Copyright (C) 2015 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. + */ + +package com.android.benchmark.ui; + +import android.content.Intent; +import android.os.Bundle; +import android.support.v7.app.ActionBar; +import android.support.v7.app.AppCompatActivity; +import android.view.KeyEvent; +import android.widget.EditText; + +import com.android.benchmark.R; +import com.android.benchmark.ui.automation.Automator; +import com.android.benchmark.ui.automation.Interaction; + +public class EditTextInputActivity extends AppCompatActivity { + + private Automator mAutomator; + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + final EditText editText = new EditText(this); + final int runId = getIntent().getIntExtra("com.android.benchmark.RUN_ID", 0); + final int iteration = getIntent().getIntExtra("com.android.benchmark.ITERATION", -1); + + editText.setWidth(400); + editText.setHeight(200); + setContentView(editText); + + String testName = getString(R.string.edit_text_input_name); + + ActionBar actionBar = getSupportActionBar(); + if (actionBar != null) { + actionBar.setTitle(testName); + } + + mAutomator = new Automator(testName, runId, iteration, getWindow(), + new Automator.AutomateCallback() { + @Override + public void onPostAutomate() { + Intent result = new Intent(); + setResult(RESULT_OK, result); + finish(); + } + + @Override + public void onAutomate() { + + int[] coordinates = new int[2]; + editText.getLocationOnScreen(coordinates); + + int x = coordinates[0]; + int y = coordinates[1]; + + float width = editText.getWidth(); + float height = editText.getHeight(); + + float middleX = (x + width) / 2; + float middleY = (y + height) / 2; + + Interaction tap = Interaction.newTap(middleX, middleY); + addInteraction(tap); + + int[] alphabet = { + KeyEvent.KEYCODE_A, + KeyEvent.KEYCODE_B, + KeyEvent.KEYCODE_C, + KeyEvent.KEYCODE_D, + KeyEvent.KEYCODE_E, + KeyEvent.KEYCODE_F, + KeyEvent.KEYCODE_G, + KeyEvent.KEYCODE_H, + KeyEvent.KEYCODE_I, + KeyEvent.KEYCODE_J, + KeyEvent.KEYCODE_K, + KeyEvent.KEYCODE_L, + KeyEvent.KEYCODE_M, + KeyEvent.KEYCODE_N, + KeyEvent.KEYCODE_O, + KeyEvent.KEYCODE_P, + KeyEvent.KEYCODE_Q, + KeyEvent.KEYCODE_R, + KeyEvent.KEYCODE_S, + KeyEvent.KEYCODE_T, + KeyEvent.KEYCODE_U, + KeyEvent.KEYCODE_V, + KeyEvent.KEYCODE_W, + KeyEvent.KEYCODE_X, + KeyEvent.KEYCODE_Y, + KeyEvent.KEYCODE_Z, + KeyEvent.KEYCODE_SPACE + }; + Interaction typeAlphabet = Interaction.newKeyInput(new int[] { + KeyEvent.KEYCODE_A, + KeyEvent.KEYCODE_B, + KeyEvent.KEYCODE_C, + KeyEvent.KEYCODE_D, + KeyEvent.KEYCODE_E, + KeyEvent.KEYCODE_F, + KeyEvent.KEYCODE_G, + KeyEvent.KEYCODE_H, + KeyEvent.KEYCODE_I, + KeyEvent.KEYCODE_J, + KeyEvent.KEYCODE_K, + KeyEvent.KEYCODE_L, + KeyEvent.KEYCODE_M, + KeyEvent.KEYCODE_N, + KeyEvent.KEYCODE_O, + KeyEvent.KEYCODE_P, + KeyEvent.KEYCODE_Q, + KeyEvent.KEYCODE_R, + KeyEvent.KEYCODE_S, + KeyEvent.KEYCODE_T, + KeyEvent.KEYCODE_U, + KeyEvent.KEYCODE_V, + KeyEvent.KEYCODE_W, + KeyEvent.KEYCODE_X, + KeyEvent.KEYCODE_Y, + KeyEvent.KEYCODE_Z, + KeyEvent.KEYCODE_SPACE, + }); + + for (int i = 0; i < 5; i++) { + addInteraction(typeAlphabet); + } + } + }); + mAutomator.start(); + } + + @Override + protected void onPause() { + super.onPause(); + if (mAutomator != null) { + mAutomator.cancel(); + mAutomator = null; + } + } + + private String getRunFilename() { + StringBuilder builder = new StringBuilder(); + builder.append(getClass().getSimpleName()); + builder.append(System.currentTimeMillis()); + return builder.toString(); + } +} diff --git a/tests/JankBench/app/src/main/java/com/android/benchmark/ui/FullScreenOverdrawActivity.java b/tests/JankBench/app/src/main/java/com/android/benchmark/ui/FullScreenOverdrawActivity.java new file mode 100644 index 0000000000000..95fce3834f9ba --- /dev/null +++ b/tests/JankBench/app/src/main/java/com/android/benchmark/ui/FullScreenOverdrawActivity.java @@ -0,0 +1,115 @@ +/* + * Copyright (C) 2015 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. + */ + +package com.android.benchmark.ui; + +import android.animation.ObjectAnimator; +import android.animation.ValueAnimator; +import android.content.Context; +import android.content.Intent; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.support.v7.app.AppCompatActivity; +import android.os.Bundle; +import android.view.MotionEvent; +import android.view.View; + +import com.android.benchmark.R; +import com.android.benchmark.registry.BenchmarkRegistry; +import com.android.benchmark.ui.automation.Automator; +import com.android.benchmark.ui.automation.Interaction; + +public class FullScreenOverdrawActivity extends AppCompatActivity { + + private Automator mAutomator; + + private class OverdrawView extends View { + Paint paint = new Paint(); + int mColorValue = 0; + + public OverdrawView(Context context) { + super(context); + } + + @SuppressWarnings("unused") + public void setColorValue(int colorValue) { + mColorValue = colorValue; + invalidate(); + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + ObjectAnimator objectAnimator = ObjectAnimator.ofInt(this, "colorValue", 0, 255); + objectAnimator.setRepeatMode(ValueAnimator.REVERSE); + objectAnimator.setRepeatCount(100); + objectAnimator.start(); + return super.onTouchEvent(event); + } + + @Override + protected void onDraw(Canvas canvas) { + paint.setColor(Color.rgb(mColorValue, 255 - mColorValue, 255)); + + for (int i = 0; i < 10; i++) { + canvas.drawRect(0, 0, getWidth(), getHeight(), paint); + } + } + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + final OverdrawView overdrawView = new OverdrawView(this); + overdrawView.setKeepScreenOn(true); + setContentView(overdrawView); + + final int runId = getIntent().getIntExtra("com.android.benchmark.RUN_ID", 0); + final int iteration = getIntent().getIntExtra("com.android.benchmark.ITERATION", -1); + + String name = BenchmarkRegistry.getBenchmarkName(this, R.id.benchmark_overdraw); + + mAutomator = new Automator(name, runId, iteration, getWindow(), + new Automator.AutomateCallback() { + @Override + public void onPostAutomate() { + Intent result = new Intent(); + setResult(RESULT_OK, result); + finish(); + } + + @Override + public void onAutomate() { + int[] coordinates = new int[2]; + overdrawView.getLocationOnScreen(coordinates); + + int x = coordinates[0]; + int y = coordinates[1]; + + float width = overdrawView.getWidth(); + float height = overdrawView.getHeight(); + + float middleX = (x + width) / 5; + float middleY = (y + height) / 5; + + addInteraction(Interaction.newTap(middleX, middleY)); + } + }); + + mAutomator.start(); + } +} diff --git a/tests/JankBench/app/src/main/java/com/android/benchmark/ui/ImageListViewScrollActivity.java b/tests/JankBench/app/src/main/java/com/android/benchmark/ui/ImageListViewScrollActivity.java new file mode 100644 index 0000000000000..4644ea1b2980e --- /dev/null +++ b/tests/JankBench/app/src/main/java/com/android/benchmark/ui/ImageListViewScrollActivity.java @@ -0,0 +1,162 @@ +/* + * Copyright (C) 2015 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. + */ + +package com.android.benchmark.ui; + +import android.graphics.Bitmap; +import android.os.AsyncTask; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.BaseAdapter; +import android.widget.ImageView; +import android.widget.ListAdapter; +import android.widget.TextView; + +import com.android.benchmark.R; + +import java.lang.ref.WeakReference; +import java.util.HashMap; + +public class ImageListViewScrollActivity extends ListViewScrollActivity { + + private static final int LIST_SIZE = 100; + + private static final int[] IMG_RES_ID = new int[]{ + R.drawable.img1, + R.drawable.img2, + R.drawable.img3, + R.drawable.img4, + R.drawable.img1, + R.drawable.img2, + R.drawable.img3, + R.drawable.img4, + R.drawable.img1, + R.drawable.img2, + R.drawable.img3, + R.drawable.img4, + R.drawable.img1, + R.drawable.img2, + R.drawable.img3, + R.drawable.img4, + }; + + private static Bitmap[] mBitmapCache = new Bitmap[IMG_RES_ID.length]; + + private static final String[] WORDS = Utils.buildStringList(LIST_SIZE); + + private HashMap mInFlight = new HashMap<>(); + + @Override + protected ListAdapter createListAdapter() { + return new ImageListAdapter(); + } + + @Override + protected String getName() { + return getString(R.string.image_list_view_scroll_name); + } + + class BitmapWorkerTask extends AsyncTask { + private final WeakReference imageViewReference; + private int data = 0; + private int cacheIdx = 0; + volatile boolean cancelled = false; + + public BitmapWorkerTask(ImageView imageView, int cacheIdx) { + // Use a WeakReference to ensure the ImageView can be garbage collected + imageViewReference = new WeakReference<>(imageView); + this.cacheIdx = cacheIdx; + } + + // Decode image in background. + @Override + protected Bitmap doInBackground(Integer... params) { + data = params[0]; + return Utils.decodeSampledBitmapFromResource(getResources(), data, 100, 100); + } + + // Once complete, see if ImageView is still around and set bitmap. + @Override + protected void onPostExecute(Bitmap bitmap) { + if (bitmap != null) { + final ImageView imageView = imageViewReference.get(); + if (imageView != null) { + if (!cancelled) { + imageView.setImageBitmap(bitmap); + } + mBitmapCache[cacheIdx] = bitmap; + } + } + } + } + + @Override + protected void onPause() { + super.onPause(); + for (int i = 0; i < mBitmapCache.length; i++) { + mBitmapCache[i] = null; + } + } + + class ImageListAdapter extends BaseAdapter { + + @Override + public int getCount() { + return LIST_SIZE; + } + + @Override + public Object getItem(int postition) { + return null; + } + + @Override + public long getItemId(int postition) { + return postition; + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + if (convertView == null) { + convertView = LayoutInflater.from(getBaseContext()) + .inflate(R.layout.image_scroll_list_item, parent, false); + } + + ImageView imageView = (ImageView) convertView.findViewById(R.id.image_scroll_image); + BitmapWorkerTask inFlight = mInFlight.get(convertView); + if (inFlight != null) { + inFlight.cancelled = true; + mInFlight.remove(convertView); + } + + int cacheIdx = position % IMG_RES_ID.length; + Bitmap bitmap = mBitmapCache[(cacheIdx)]; + if (bitmap == null) { + BitmapWorkerTask bitmapWorkerTask = new BitmapWorkerTask(imageView, cacheIdx); + bitmapWorkerTask.execute(IMG_RES_ID[(cacheIdx)]); + mInFlight.put(convertView, bitmapWorkerTask); + } + + imageView.setImageBitmap(bitmap); + + TextView textView = (TextView) convertView.findViewById(R.id.image_scroll_text); + textView.setText(WORDS[position]); + + return convertView; + } + } +} diff --git a/tests/JankBench/app/src/main/java/com/android/benchmark/ui/ListActivityBase.java b/tests/JankBench/app/src/main/java/com/android/benchmark/ui/ListActivityBase.java new file mode 100644 index 0000000000000..b973bc76c13fa --- /dev/null +++ b/tests/JankBench/app/src/main/java/com/android/benchmark/ui/ListActivityBase.java @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2015 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. + * + */ + +package com.android.benchmark.ui; + +import android.app.ActionBar; +import android.os.Bundle; +import android.support.v4.app.FragmentManager; +import android.support.v4.app.ListFragment; +import android.support.v7.app.AppCompatActivity; +import android.view.Window; +import android.widget.ListAdapter; + +import com.android.benchmark.R; + +/** + * Simple list activity base class + */ +public abstract class ListActivityBase extends AppCompatActivity { + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_list_fragment); + + ActionBar actionBar = getActionBar(); + if (actionBar != null) { + actionBar.setTitle(getName()); + } + + if (findViewById(R.id.list_fragment_container) != null) { + FragmentManager fm = getSupportFragmentManager(); + ListFragment listView = new ListFragment(); + listView.setListAdapter(createListAdapter()); + fm.beginTransaction().add(R.id.list_fragment_container, listView).commit(); + } + } + + protected abstract ListAdapter createListAdapter(); + protected abstract String getName(); +} + diff --git a/tests/JankBench/app/src/main/java/com/android/benchmark/ui/ListViewScrollActivity.java b/tests/JankBench/app/src/main/java/com/android/benchmark/ui/ListViewScrollActivity.java new file mode 100644 index 0000000000000..3ffb7706675f6 --- /dev/null +++ b/tests/JankBench/app/src/main/java/com/android/benchmark/ui/ListViewScrollActivity.java @@ -0,0 +1,113 @@ +/* + * Copyright (C) 2015 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. + * + */ + +package com.android.benchmark.ui; + +import android.content.Intent; +import android.os.Bundle; +import android.support.v7.app.ActionBar; +import android.view.FrameMetrics; +import android.view.MotionEvent; +import android.widget.ArrayAdapter; +import android.widget.FrameLayout; +import android.widget.ListAdapter; + +import com.android.benchmark.R; +import com.android.benchmark.ui.automation.Automator; +import com.android.benchmark.ui.automation.Interaction; + +import java.io.File; +import java.util.List; + +public class ListViewScrollActivity extends ListActivityBase { + + private static final int LIST_SIZE = 400; + private static final int INTERACTION_COUNT = 4; + + private Automator mAutomator; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + final int runId = getIntent().getIntExtra("com.android.benchmark.RUN_ID", 0); + final int iteration = getIntent().getIntExtra("com.android.benchmark.ITERATION", -1); + + ActionBar actionBar = getSupportActionBar(); + if (actionBar != null) { + actionBar.setTitle(getTitle()); + } + + mAutomator = new Automator(getName(), runId, iteration, getWindow(), + new Automator.AutomateCallback() { + @Override + public void onPostAutomate() { + Intent result = new Intent(); + setResult(RESULT_OK, result); + finish(); + } + + @Override + public void onPostInteraction(List metrics) {} + + @Override + public void onAutomate() { + FrameLayout v = (FrameLayout) findViewById(R.id.list_fragment_container); + + int[] coordinates = new int[2]; + v.getLocationOnScreen(coordinates); + + int x = coordinates[0]; + int y = coordinates[1]; + + float width = v.getWidth(); + float height = v.getHeight(); + + float middleX = (x + width) / 5; + float middleY = (y + height) / 5; + + Interaction flingUp = Interaction.newFlingUp(middleX, middleY); + Interaction flingDown = Interaction.newFlingDown(middleX, middleY); + + for (int i = 0; i < INTERACTION_COUNT; i++) { + addInteraction(flingUp); + addInteraction(flingDown); + } + } + }); + + mAutomator.start(); + } + + @Override + protected void onPause() { + super.onPause(); + if (mAutomator != null) { + mAutomator.cancel(); + mAutomator = null; + } + } + + @Override + protected ListAdapter createListAdapter() { + return new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, + Utils.buildStringList(LIST_SIZE)); + } + + @Override + protected String getName() { + return getString(R.string.list_view_scroll_name); + } +} diff --git a/tests/JankBench/app/src/main/java/com/android/benchmark/ui/ShadowGridActivity.java b/tests/JankBench/app/src/main/java/com/android/benchmark/ui/ShadowGridActivity.java new file mode 100644 index 0000000000000..68f75a3f277b8 --- /dev/null +++ b/tests/JankBench/app/src/main/java/com/android/benchmark/ui/ShadowGridActivity.java @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2015 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. + */ +package com.android.benchmark.ui; + +import android.content.Intent; +import android.os.Bundle; +import android.support.v4.app.FragmentManager; +import android.support.v4.app.ListFragment; +import android.support.v7.app.AppCompatActivity; +import android.view.View; +import android.widget.ArrayAdapter; +import android.widget.ListView; + +import com.android.benchmark.R; +import com.android.benchmark.ui.automation.Automator; +import com.android.benchmark.ui.automation.Interaction; + +public class ShadowGridActivity extends AppCompatActivity { + private Automator mAutomator; + public static class MyListFragment extends ListFragment { + @Override + public void onViewCreated(View view, Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + getListView().setDivider(null); + } + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + final int runId = getIntent().getIntExtra("com.android.benchmark.RUN_ID", 0); + final int iteration = getIntent().getIntExtra("com.android.benchmark.ITERATION", -1); + + FragmentManager fm = getSupportFragmentManager(); + if (fm.findFragmentById(android.R.id.content) == null) { + ListFragment listFragment = new MyListFragment(); + + listFragment.setListAdapter(new ArrayAdapter<>(this, + R.layout.card_row, R.id.card_text, Utils.buildStringList(200))); + fm.beginTransaction().add(android.R.id.content, listFragment).commit(); + + String testName = getString(R.string.shadow_grid_name); + + mAutomator = new Automator(testName, runId, iteration, getWindow(), + new Automator.AutomateCallback() { + @Override + public void onPostAutomate() { + Intent result = new Intent(); + setResult(RESULT_OK, result); + finish(); + } + + @Override + public void onAutomate() { + ListView v = (ListView) findViewById(android.R.id.list); + + int[] coordinates = new int[2]; + v.getLocationOnScreen(coordinates); + + int x = coordinates[0]; + int y = coordinates[1]; + + float width = v.getWidth(); + float height = v.getHeight(); + + float middleX = (x + width) / 2; + float middleY = (y + height) / 2; + + Interaction flingUp = Interaction.newFlingUp(middleX, middleY); + Interaction flingDown = Interaction.newFlingDown(middleX, middleY); + + addInteraction(flingUp); + addInteraction(flingDown); + addInteraction(flingUp); + addInteraction(flingDown); + addInteraction(flingUp); + addInteraction(flingDown); + addInteraction(flingUp); + addInteraction(flingDown); + } + }); + mAutomator.start(); + } + } + + @Override + protected void onPause() { + super.onPause(); + if (mAutomator != null) { + mAutomator.cancel(); + mAutomator = null; + } + } +} diff --git a/tests/JankBench/app/src/main/java/com/android/benchmark/ui/TextScrollActivity.java b/tests/JankBench/app/src/main/java/com/android/benchmark/ui/TextScrollActivity.java new file mode 100644 index 0000000000000..fcd168e988e09 --- /dev/null +++ b/tests/JankBench/app/src/main/java/com/android/benchmark/ui/TextScrollActivity.java @@ -0,0 +1,118 @@ +/* + * Copyright (C) 2015 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. + */ + +package com.android.benchmark.ui; + +import android.content.Intent; +import android.os.Bundle; +import android.widget.ArrayAdapter; +import android.widget.ListAdapter; +import android.widget.ListView; + +import com.android.benchmark.registry.BenchmarkRegistry; +import com.android.benchmark.ui.automation.Automator; +import com.android.benchmark.ui.automation.Interaction; + +import java.io.File; + +public class TextScrollActivity extends ListActivityBase { + + public static final String EXTRA_HIT_RATE = ".TextScrollActivity.EXTRA_HIT_RATE"; + + private static final int PARAGRAPH_COUNT = 200; + + private int mHitPercentage = 100; + private Automator mAutomator; + private String mName; + + @Override + public void onCreate(Bundle savedInstanceState) { + mHitPercentage = getIntent().getIntExtra(EXTRA_HIT_RATE, + mHitPercentage); + super.onCreate(savedInstanceState); + final int runId = getIntent().getIntExtra("com.android.benchmark.RUN_ID", 0); + final int iteration = getIntent().getIntExtra("com.android.benchmark.ITERATION", -1); + final int id = getIntent().getIntExtra(BenchmarkRegistry.EXTRA_ID, -1); + + if (id == -1) { + finish(); + return; + } + + mName = BenchmarkRegistry.getBenchmarkName(this, id); + + mAutomator = new Automator(getName(), runId, iteration, getWindow(), + new Automator.AutomateCallback() { + @Override + public void onPostAutomate() { + Intent result = new Intent(); + setResult(RESULT_OK, result); + finish(); + } + + @Override + public void onAutomate() { + ListView v = (ListView) findViewById(android.R.id.list); + + int[] coordinates = new int[2]; + v.getLocationOnScreen(coordinates); + + int x = coordinates[0]; + int y = coordinates[1]; + + float width = v.getWidth(); + float height = v.getHeight(); + + float middleX = (x + width) / 2; + float middleY = (y + height) / 2; + + Interaction flingUp = Interaction.newFlingUp(middleX, middleY); + Interaction flingDown = Interaction.newFlingDown(middleX, middleY); + + addInteraction(flingUp); + addInteraction(flingDown); + addInteraction(flingUp); + addInteraction(flingDown); + addInteraction(flingUp); + addInteraction(flingDown); + addInteraction(flingUp); + addInteraction(flingDown); + } + }); + + mAutomator.start(); + } + + @Override + protected void onPause() { + super.onPause(); + if (mAutomator != null) { + mAutomator.cancel(); + mAutomator = null; + } + } + + @Override + protected ListAdapter createListAdapter() { + return new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, + Utils.buildParagraphListWithHitPercentage(PARAGRAPH_COUNT, 80)); + } + + @Override + protected String getName() { + return mName; + } +} diff --git a/tests/JankBench/app/src/main/java/com/android/benchmark/ui/Utils.java b/tests/JankBench/app/src/main/java/com/android/benchmark/ui/Utils.java new file mode 100644 index 0000000000000..39f9206d4e076 --- /dev/null +++ b/tests/JankBench/app/src/main/java/com/android/benchmark/ui/Utils.java @@ -0,0 +1,135 @@ +/* + * Copyright (C) 2015 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. + */ + +package com.android.benchmark.ui; + +import android.content.res.Resources; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; + +import java.util.Random; + +public class Utils { + + private static final int RANDOM_WORD_LENGTH = 10; + + public static String getRandomWord(Random random, int length) { + StringBuilder builder = new StringBuilder(); + for (int i = 0; i < length; i++) { + char base = random.nextBoolean() ? 'A' : 'a'; + char nextChar = (char)(random.nextInt(26) + base); + builder.append(nextChar); + } + return builder.toString(); + } + + public static String[] buildStringList(int count) { + Random random = new Random(0); + String[] result = new String[count]; + for (int i = 0; i < count; i++) { + result[i] = getRandomWord(random, RANDOM_WORD_LENGTH); + } + + return result; + } + + // a small number of strings reused frequently, expected to hit + // in the word-granularity text layout cache + static final String[] CACHE_HIT_STRINGS = new String[] { + "a", + "small", + "number", + "of", + "strings", + "reused", + "frequently" + }; + + private static final int WORDS_IN_PARAGRAPH = 150; + + // misses are fairly long 'words' to ensure they miss + private static final int PARAGRAPH_MISS_MIN_LENGTH = 4; + private static final int PARAGRAPH_MISS_MAX_LENGTH = 9; + + static String[] buildParagraphListWithHitPercentage(int paragraphCount, int hitPercentage) { + if (hitPercentage < 0 || hitPercentage > 100) throw new IllegalArgumentException(); + + String[] strings = new String[paragraphCount]; + Random random = new Random(0); + for (int i = 0; i < strings.length; i++) { + StringBuilder result = new StringBuilder(); + for (int word = 0; word < WORDS_IN_PARAGRAPH; word++) { + if (word != 0) { + result.append(" "); + } + if (random.nextInt(100) < hitPercentage) { + // add a common word, which is very likely to hit in the cache + result.append(CACHE_HIT_STRINGS[random.nextInt(CACHE_HIT_STRINGS.length)]); + } else { + // construct a random word, which will *most likely* miss + int length = PARAGRAPH_MISS_MIN_LENGTH; + length += random.nextInt(PARAGRAPH_MISS_MAX_LENGTH - PARAGRAPH_MISS_MIN_LENGTH); + + result.append(getRandomWord(random, length)); + } + } + strings[i] = result.toString(); + } + + return strings; + } + + + public static int calculateInSampleSize( + BitmapFactory.Options options, int reqWidth, int reqHeight) { + // Raw height and width of image + final int height = options.outHeight; + final int width = options.outWidth; + int inSampleSize = 1; + + if (height > reqHeight || width > reqWidth) { + + final int halfHeight = height / 2; + final int halfWidth = width / 2; + + // Calculate the largest inSampleSize value that is a power of 2 and keeps both + // height and width larger than the requested height and width. + while ((halfHeight / inSampleSize) > reqHeight + && (halfWidth / inSampleSize) > reqWidth) { + inSampleSize *= 2; + } + } + + return inSampleSize; + } + + public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId, + int reqWidth, int reqHeight) { + + // First decode with inJustDecodeBounds=true to check dimensions + final BitmapFactory.Options options = new BitmapFactory.Options(); + options.inJustDecodeBounds = true; + BitmapFactory.decodeResource(res, resId, options); + + // Calculate inSampleSize + options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight); + + // Decode bitmap with inSampleSize set + options.inJustDecodeBounds = false; + return BitmapFactory.decodeResource(res, resId, options); + } + +} diff --git a/tests/JankBench/app/src/main/java/com/android/benchmark/ui/automation/Automator.java b/tests/JankBench/app/src/main/java/com/android/benchmark/ui/automation/Automator.java new file mode 100644 index 0000000000000..1efd6bc2aba60 --- /dev/null +++ b/tests/JankBench/app/src/main/java/com/android/benchmark/ui/automation/Automator.java @@ -0,0 +1,269 @@ +/* + * Copyright (C) 2016 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. + * + */ + +package com.android.benchmark.ui.automation; + +import android.annotation.TargetApi; +import android.app.Instrumentation; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.Looper; +import android.os.Message; +import android.view.FrameMetrics; +import android.view.MotionEvent; +import android.view.ViewTreeObserver; +import android.view.Window; + +import com.android.benchmark.results.GlobalResultsStore; +import com.android.benchmark.results.UiBenchmarkResult; + +import java.util.LinkedList; +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; + +@TargetApi(24) +public class Automator extends HandlerThread + implements ViewTreeObserver.OnGlobalLayoutListener, CollectorThread.CollectorListener { + public static final long FRAME_PERIOD_MILLIS = 16; + + private static final int PRE_READY_STATE_COUNT = 3; + private static final String TAG = "Benchmark.Automator"; + private final AtomicInteger mReadyState; + + private AutomateCallback mCallback; + private Window mWindow; + private AutomatorHandler mHandler; + private CollectorThread mCollectorThread; + private int mRunId; + private int mIteration; + private String mTestName; + + public static class AutomateCallback { + public void onAutomate() {} + public void onPostInteraction(List metrics) {} + public void onPostAutomate() {} + + protected final void addInteraction(Interaction interaction) { + if (mInteractions == null) { + return; + } + + mInteractions.add(interaction); + } + + protected final void setInteractions(List interactions) { + mInteractions = interactions; + } + + private List mInteractions; + } + + private static final class AutomatorHandler extends Handler { + public static final int MSG_NEXT_INTERACTION = 0; + public static final int MSG_ON_AUTOMATE = 1; + public static final int MSG_ON_POST_INTERACTION = 2; + private final String mTestName; + private final int mRunId; + private final int mIteration; + + private Instrumentation mInstrumentation; + private volatile boolean mCancelled; + private CollectorThread mCollectorThread; + private AutomateCallback mCallback; + private Window mWindow; + + LinkedList mInteractions; + private UiBenchmarkResult mResults; + + AutomatorHandler(Looper looper, Window window, CollectorThread collectorThread, + AutomateCallback callback, String testName, int runId, int iteration) { + super(looper); + + mInstrumentation = new Instrumentation(); + + mCallback = callback; + mWindow = window; + mCollectorThread = collectorThread; + mInteractions = new LinkedList<>(); + mTestName = testName; + mRunId = runId; + mIteration = iteration; + } + + @Override + public void handleMessage(Message msg) { + if (mCancelled) { + return; + } + + switch (msg.what) { + case MSG_NEXT_INTERACTION: + if (!nextInteraction()) { + stopCollector(); + writeResults(); + mCallback.onPostAutomate(); + } + break; + case MSG_ON_AUTOMATE: + mCollectorThread.attachToWindow(mWindow); + mCallback.setInteractions(mInteractions); + mCallback.onAutomate(); + postNextInteraction(); + break; + case MSG_ON_POST_INTERACTION: + List collectedStats = (List)msg.obj; + persistResults(collectedStats); + mCallback.onPostInteraction(collectedStats); + postNextInteraction(); + break; + } + } + + public void cancel() { + mCancelled = true; + stopCollector(); + } + + private void stopCollector() { + mCollectorThread.quitCollector(); + } + + private boolean nextInteraction() { + + Interaction interaction = mInteractions.poll(); + if (interaction != null) { + doInteraction(interaction); + return true; + } + return false; + } + + private void doInteraction(Interaction interaction) { + if (mCancelled) { + return; + } + + mCollectorThread.markInteractionStart(); + + if (interaction.getType() == Interaction.Type.KEY_EVENT) { + for (int code : interaction.getKeyCodes()) { + if (!mCancelled) { + mInstrumentation.sendKeyDownUpSync(code); + } else { + break; + } + } + } else { + for (MotionEvent event : interaction.getEvents()) { + if (!mCancelled) { + mInstrumentation.sendPointerSync(event); + } else { + break; + } + } + } + } + + protected void postNextInteraction() { + final Message msg = obtainMessage(AutomatorHandler.MSG_NEXT_INTERACTION); + sendMessage(msg); + } + + private void persistResults(List stats) { + if (stats.isEmpty()) { + return; + } + + if (mResults == null) { + mResults = new UiBenchmarkResult(stats); + } else { + mResults.update(stats); + } + } + + private void writeResults() { + GlobalResultsStore.getInstance(mWindow.getContext()) + .storeRunResults(mTestName, mRunId, mIteration, mResults); + } + } + + private void initHandler() { + mHandler = new AutomatorHandler(getLooper(), mWindow, mCollectorThread, mCallback, + mTestName, mRunId, mIteration); + mWindow = null; + mCallback = null; + mCollectorThread = null; + mTestName = null; + mRunId = 0; + mIteration = 0; + } + + @Override + public final void onGlobalLayout() { + if (!mCollectorThread.isAlive()) { + mCollectorThread.start(); + mWindow.getDecorView().getViewTreeObserver().removeOnGlobalLayoutListener(this); + mReadyState.decrementAndGet(); + } + } + + @Override + public void onCollectorThreadReady() { + if (mReadyState.decrementAndGet() == 0) { + initHandler(); + postOnAutomate(); + } + } + + @Override + protected void onLooperPrepared() { + if (mReadyState.decrementAndGet() == 0) { + initHandler(); + postOnAutomate(); + } + } + + @Override + public void onPostInteraction(List stats) { + Message m = mHandler.obtainMessage(AutomatorHandler.MSG_ON_POST_INTERACTION, stats); + mHandler.sendMessage(m); + } + + protected void postOnAutomate() { + final Message msg = mHandler.obtainMessage(AutomatorHandler.MSG_ON_AUTOMATE); + mHandler.sendMessage(msg); + } + + public void cancel() { + mHandler.removeMessages(AutomatorHandler.MSG_NEXT_INTERACTION); + mHandler.cancel(); + mHandler = null; + } + + public Automator(String testName, int runId, int iteration, + Window window, AutomateCallback callback) { + super("AutomatorThread"); + + mTestName = testName; + mRunId = runId; + mIteration = iteration; + mCallback = callback; + mWindow = window; + mWindow.getDecorView().getViewTreeObserver().addOnGlobalLayoutListener(this); + mCollectorThread = new CollectorThread(this); + mReadyState = new AtomicInteger(PRE_READY_STATE_COUNT); + } +} diff --git a/tests/JankBench/app/src/main/java/com/android/benchmark/ui/automation/CollectorThread.java b/tests/JankBench/app/src/main/java/com/android/benchmark/ui/automation/CollectorThread.java new file mode 100644 index 0000000000000..806c704d8a099 --- /dev/null +++ b/tests/JankBench/app/src/main/java/com/android/benchmark/ui/automation/CollectorThread.java @@ -0,0 +1,164 @@ +/* + * Copyright (C) 2015 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. + */ + +package com.android.benchmark.ui.automation; + +import android.annotation.TargetApi; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.Message; +import android.os.SystemClock; +import android.view.FrameMetrics; +import android.view.Window; + +import java.lang.ref.WeakReference; +import java.util.LinkedList; +import java.util.List; + +/** + * + */ +final class CollectorThread extends HandlerThread { + private FrameStatsCollector mCollector; + private Window mAttachedWindow; + private List mFrameTimingStats; + private long mLastFrameTime; + private WatchdogHandler mWatchdog; + private WeakReference mListener; + + private volatile boolean mCollecting; + + + interface CollectorListener { + void onCollectorThreadReady(); + void onPostInteraction(List stats); + } + + private final class WatchdogHandler extends Handler { + private static final long SCHEDULE_INTERVAL_MILLIS = 20 * Automator.FRAME_PERIOD_MILLIS; + + private static final int MSG_SCHEDULE = 0; + + @Override + public void handleMessage(Message msg) { + if (!mCollecting) { + return; + } + + long currentTime = SystemClock.uptimeMillis(); + if (mLastFrameTime + SCHEDULE_INTERVAL_MILLIS <= currentTime) { + // haven't seen a frame in a while, interaction is probably done + mCollecting = false; + CollectorListener listener = mListener.get(); + if (listener != null) { + listener.onPostInteraction(mFrameTimingStats); + } + } else { + schedule(); + } + } + + public void schedule() { + sendMessageDelayed(obtainMessage(MSG_SCHEDULE), SCHEDULE_INTERVAL_MILLIS); + } + + public void deschedule() { + removeMessages(MSG_SCHEDULE); + } + } + + static boolean tripleBuffered = false; + static int janks = 0; + static int total = 0; + @TargetApi(24) + private class FrameStatsCollector implements Window.OnFrameMetricsAvailableListener { + @Override + public void onFrameMetricsAvailable(Window window, FrameMetrics frameMetrics, int dropCount) { + if (!mCollecting) { + return; + } + mFrameTimingStats.add(new FrameMetrics(frameMetrics)); + mLastFrameTime = SystemClock.uptimeMillis(); + } + } + + public CollectorThread(CollectorListener listener) { + super("FrameStatsCollectorThread"); + mFrameTimingStats = new LinkedList<>(); + mListener = new WeakReference<>(listener); + } + + @TargetApi(24) + public void attachToWindow(Window window) { + if (mAttachedWindow != null) { + mAttachedWindow.removeOnFrameMetricsAvailableListener(mCollector); + } + + mAttachedWindow = window; + window.addOnFrameMetricsAvailableListener(mCollector, new Handler(getLooper())); + } + + @TargetApi(24) + public synchronized void detachFromWindow() { + if (mAttachedWindow != null) { + mAttachedWindow.removeOnFrameMetricsAvailableListener(mCollector); + } + + mAttachedWindow = null; + } + + @TargetApi(24) + @Override + protected void onLooperPrepared() { + super.onLooperPrepared(); + mCollector = new FrameStatsCollector(); + mWatchdog = new WatchdogHandler(); + + CollectorListener listener = mListener.get(); + if (listener != null) { + listener.onCollectorThreadReady(); + } + } + + public boolean quitCollector() { + stopCollecting(); + detachFromWindow(); + System.out.println("Jank Percentage: " + (100 * janks/ (double) total) + "%"); + tripleBuffered = false; + total = 0; + janks = 0; + return quit(); + } + + void stopCollecting() { + if (!mCollecting) { + return; + } + + mCollecting = false; + mWatchdog.deschedule(); + + + } + + public void markInteractionStart() { + mLastFrameTime = 0; + mFrameTimingStats.clear(); + mCollecting = true; + + mWatchdog.schedule(); + } +} diff --git a/tests/JankBench/app/src/main/java/com/android/benchmark/ui/automation/FrameTimingStats.java b/tests/JankBench/app/src/main/java/com/android/benchmark/ui/automation/FrameTimingStats.java new file mode 100644 index 0000000000000..1fd0998f8dd6a --- /dev/null +++ b/tests/JankBench/app/src/main/java/com/android/benchmark/ui/automation/FrameTimingStats.java @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2015 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. + * + */ + +package com.android.benchmark.ui.automation; + +import android.support.annotation.IntDef; + +import java.io.DataInput; +import java.io.DataInputStream; +import java.io.IOException; +import java.util.Arrays; + +public class FrameTimingStats { + @IntDef ({ + Index.FLAGS, + Index.INTENDED_VSYNC, + Index.VSYNC, + Index.OLDEST_INPUT_EVENT, + Index.NEWEST_INPUT_EVENT, + Index.HANDLE_INPUT_START, + Index.ANIMATION_START, + Index.PERFORM_TRAVERSALS_START, + Index.DRAW_START, + Index.SYNC_QUEUED, + Index.SYNC_START, + Index.ISSUE_DRAW_COMMANDS_START, + Index.SWAP_BUFFERS, + Index.FRAME_COMPLETED, + }) + public @interface Index { + int FLAGS = 0; + int INTENDED_VSYNC = 1; + int VSYNC = 2; + int OLDEST_INPUT_EVENT = 3; + int NEWEST_INPUT_EVENT = 4; + int HANDLE_INPUT_START = 5; + int ANIMATION_START = 6; + int PERFORM_TRAVERSALS_START = 7; + int DRAW_START = 8; + int SYNC_QUEUED = 9; + int SYNC_START = 10; + int ISSUE_DRAW_COMMANDS_START = 11; + int SWAP_BUFFERS = 12; + int FRAME_COMPLETED = 13; + + int FRAME_STATS_COUNT = 14; // must always be last + } + + private final long[] mStats; + + FrameTimingStats(long[] stats) { + mStats = Arrays.copyOf(stats, Index.FRAME_STATS_COUNT); + } + + public FrameTimingStats(DataInputStream inputStream) throws IOException { + mStats = new long[Index.FRAME_STATS_COUNT]; + update(inputStream); + } + + public void update(DataInputStream inputStream) throws IOException { + for (int i = 0; i < mStats.length; i++) { + mStats[i] = inputStream.readLong(); + } + } + + public long get(@Index int index) { + return mStats[index]; + } + + public long[] data() { + return mStats; + } +} diff --git a/tests/JankBench/app/src/main/java/com/android/benchmark/ui/automation/Interaction.java b/tests/JankBench/app/src/main/java/com/android/benchmark/ui/automation/Interaction.java new file mode 100644 index 0000000000000..370fed28e1b25 --- /dev/null +++ b/tests/JankBench/app/src/main/java/com/android/benchmark/ui/automation/Interaction.java @@ -0,0 +1,187 @@ +/* + * Copyright (C) 2015 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. + * + */ + +package com.android.benchmark.ui.automation; + +import android.os.SystemClock; +import android.support.annotation.IntDef; +import android.view.MotionEvent; + +import java.util.ArrayList; +import java.util.List; + +/** + * Encodes a UI interaction as a series of MotionEvents + */ +public class Interaction { + private static final int STEP_COUNT = 20; + // TODO: scale to device display density + private static final int DEFAULT_FLING_SIZE_PX = 500; + private static final int DEFAULT_FLING_DURATION_MS = 20; + private static final int DEFAULT_TAP_DURATION_MS = 20; + private List mEvents; + + // Interaction parameters + private final float[] mXPositions; + private final float[] mYPositions; + private final long mDuration; + private final int[] mKeyCodes; + private final @Interaction.Type int mType; + + @IntDef({ + Interaction.Type.TAP, + Interaction.Type.FLING, + Interaction.Type.PINCH, + Interaction.Type.KEY_EVENT}) + public @interface Type { + int TAP = 0; + int FLING = 1; + int PINCH = 2; + int KEY_EVENT = 3; + } + + public static Interaction newFling(float startX, float startY, + float endX, float endY, long duration) { + return new Interaction(Interaction.Type.FLING, new float[]{startX, endX}, + new float[]{startY, endY}, duration); + } + + public static Interaction newFlingDown(float startX, float startY) { + return new Interaction(Interaction.Type.FLING, + new float[]{startX, startX}, + new float[]{startY, startY + DEFAULT_FLING_SIZE_PX}, DEFAULT_FLING_DURATION_MS); + } + + public static Interaction newFlingUp(float startX, float startY) { + return new Interaction(Interaction.Type.FLING, + new float[]{startX, startX}, new float[]{startY, startY - DEFAULT_FLING_SIZE_PX}, + DEFAULT_FLING_DURATION_MS); + } + + public static Interaction newTap(float startX, float startY) { + return new Interaction(Interaction.Type.TAP, + new float[]{startX, startX}, new float[]{startY, startY}, + DEFAULT_FLING_DURATION_MS); + } + + public static Interaction newKeyInput(int[] keyCodes) { + return new Interaction(keyCodes); + } + + public List getEvents() { + switch (mType) { + case Type.FLING: + mEvents = createInterpolatedEventList(mXPositions, mYPositions, mDuration); + break; + case Type.PINCH: + break; + case Type.TAP: + mEvents = createInterpolatedEventList(mXPositions, mYPositions, mDuration); + break; + } + + return mEvents; + } + + public int getType() { + return mType; + } + + public int[] getKeyCodes() { + return mKeyCodes; + } + + private static List createInterpolatedEventList( + float[] xPos, float[] yPos, long duration) { + long startTime = SystemClock.uptimeMillis() + 100; + List result = new ArrayList<>(); + + float startX = xPos[0]; + float startY = yPos[0]; + + MotionEvent downEvent = MotionEvent.obtain( + startTime, startTime, MotionEvent.ACTION_DOWN, startX, startY, 0); + result.add(downEvent); + + for (int i = 1; i < xPos.length; i++) { + float endX = xPos[i]; + float endY = yPos[i]; + float stepX = (endX - startX) / STEP_COUNT; + float stepY = (endY - startY) / STEP_COUNT; + float stepT = duration / STEP_COUNT; + + for (int j = 0; j < STEP_COUNT; j++) { + long deltaT = Math.round(j * stepT); + long deltaX = Math.round(j * stepX); + long deltaY = Math.round(j * stepY); + MotionEvent moveEvent = MotionEvent.obtain(startTime, startTime + deltaT, + MotionEvent.ACTION_MOVE, startX + deltaX, startY + deltaY, 0); + result.add(moveEvent); + } + + startX = endX; + startY = endY; + } + + float lastX = xPos[xPos.length - 1]; + float lastY = yPos[yPos.length - 1]; + MotionEvent lastEvent = MotionEvent.obtain(startTime, startTime + duration, + MotionEvent.ACTION_UP, lastX, lastY, 0); + result.add(lastEvent); + + return result; + } + + private Interaction(@Interaction.Type int type, + float[] xPos, float[] yPos, long duration) { + mType = type; + mXPositions = xPos; + mYPositions = yPos; + mDuration = duration; + mKeyCodes = null; + } + + private Interaction(int[] codes) { + mKeyCodes = codes; + mType = Type.KEY_EVENT; + mYPositions = null; + mXPositions = null; + mDuration = 0; + } + + private Interaction(@Interaction.Type int type, + List xPositions, List yPositions, long duration) { + if (xPositions.size() != yPositions.size()) { + throw new IllegalArgumentException("must have equal number of x and y positions"); + } + + int current = 0; + mXPositions = new float[xPositions.size()]; + for (float p : xPositions) { + mXPositions[current++] = p; + } + + current = 0; + mYPositions = new float[yPositions.size()]; + for (float p : xPositions) { + mXPositions[current++] = p; + } + + mType = type; + mDuration = duration; + mKeyCodes = null; + } +} diff --git a/tests/JankBench/app/src/main/jni/Android.mk b/tests/JankBench/app/src/main/jni/Android.mk new file mode 100644 index 0000000000000..8ba874de0e8a0 --- /dev/null +++ b/tests/JankBench/app/src/main/jni/Android.mk @@ -0,0 +1,31 @@ +# Copyright (C) 2015 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. + +LOCAL_PATH := $(call my-dir) +LOCAL_SDK_VERSION := 26 + +include $(CLEAR_VARS) + +LOCAL_CFLAGS = -Wno-unused-parameter + +LOCAL_MODULE:= libnativebench + +LOCAL_SRC_FILES := \ + Bench.cpp \ + WorkerPool.cpp \ + test.cpp + +LOCAL_LDLIBS := -llog + +include $(BUILD_SHARED_LIBRARY) diff --git a/tests/JankBench/app/src/main/jni/Application.mk b/tests/JankBench/app/src/main/jni/Application.mk new file mode 100644 index 0000000000000..09bc0aca14f1a --- /dev/null +++ b/tests/JankBench/app/src/main/jni/Application.mk @@ -0,0 +1,17 @@ +# Copyright (C) 2015 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. + +APP_ABI := armeabi + +APP_MODULES := nativebench diff --git a/tests/JankBench/app/src/main/jni/Bench.cpp b/tests/JankBench/app/src/main/jni/Bench.cpp new file mode 100644 index 0000000000000..fbb4f11fa9887 --- /dev/null +++ b/tests/JankBench/app/src/main/jni/Bench.cpp @@ -0,0 +1,373 @@ +/* + * Copyright (C) 2015 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. + */ + +#include +#include +#include +#include + +#include "Bench.h" + + +Bench::Bench() +{ + mTimeBucket = NULL; + mTimeBuckets = 0; + mTimeBucketDivisor = 1; + + mMemLatencyLastSize = 0; + mMemDst = NULL; + mMemSrc = NULL; + mMemLoopCount = 0; +} + + +Bench::~Bench() +{ +} + +uint64_t Bench::getTimeNanos() const +{ + struct timespec t; + clock_gettime(CLOCK_MONOTONIC, &t); + return t.tv_nsec + ((uint64_t)t.tv_sec * 1000 * 1000 * 1000); +} + +uint64_t Bench::getTimeMillis() const +{ + return getTimeNanos() / 1000000; +} + + +void Bench::testWork(void *usr, uint32_t idx) +{ + Bench *b = (Bench *)usr; + //__android_log_print(ANDROID_LOG_INFO, "bench", "test %i %p", idx, b); + + float f1 = 0.f; + float f2 = 0.f; + float f3 = 0.f; + float f4 = 0.f; + + float *ipk = b->mIpKernel[idx]; + volatile float *src = b->mSrcBuf[idx]; + volatile float *out = b->mOutBuf[idx]; + + //__android_log_print(ANDROID_LOG_INFO, "bench", "test %p %p %p", ipk, src, out); + + do { + + for (int i = 0; i < 1024; i++) { + f1 += src[i * 4] * ipk[i]; + f2 += src[i * 4 + 1] * ipk[i]; + f3 += src[i * 4 + 2] * ipk[i]; + f4 += sqrtf(f1 + f2 + f3); + } + out[0] = f1; + out[1] = f2; + out[2] = f3; + out[3] = f4; + + } while (b->incTimeBucket()); +} + +bool Bench::initIP() { + int workers = mWorkers.getWorkerCount(); + + mIpKernel = new float *[workers]; + mSrcBuf = new float *[workers]; + mOutBuf = new float *[workers]; + + for (int i = 0; i < workers; i++) { + mIpKernel[i] = new float[1024]; + mSrcBuf[i] = new float[4096]; + mOutBuf[i] = new float[4]; + } + + return true; +} + +bool Bench::runPowerManagementTest(uint64_t options) { + //__android_log_print(ANDROID_LOG_INFO, "bench", "rpmt x %i", options); + + mTimeBucketDivisor = 1000 * 1000; // use ms + allocateBuckets(2 * 1000); + + usleep(2 * 1000 * 1000); + + //__android_log_print(ANDROID_LOG_INFO, "bench", "rpmt 2 b %i", mTimeBuckets); + + mTimeStartNanos = getTimeNanos(); + mTimeEndNanos = mTimeStartNanos + mTimeBuckets * mTimeBucketDivisor; + memset(mTimeBucket, 0, sizeof(uint32_t) * mTimeBuckets); + + bool useMT = false; + + //__android_log_print(ANDROID_LOG_INFO, "bench", "rpmt 2.1 b %i", mTimeBuckets); + mTimeEndGroupNanos = mTimeStartNanos; + do { + // Advance 8ms + mTimeEndGroupNanos += 8 * 1000 * 1000; + + int threads = useMT ? 1 : 0; + useMT = !useMT; + if ((options & 0x1f) != 0) { + threads = options & 0x1f; + } + + //__android_log_print(ANDROID_LOG_INFO, "bench", "threads %i", threads); + + mWorkers.launchWork(testWork, this, threads); + } while (mTimeEndGroupNanos <= mTimeEndNanos); + + return true; +} + +bool Bench::allocateBuckets(size_t bucketCount) { + if (bucketCount == mTimeBuckets) { + return true; + } + + if (mTimeBucket != NULL) { + delete[] mTimeBucket; + mTimeBucket = NULL; + } + + mTimeBuckets = bucketCount; + if (mTimeBuckets > 0) { + mTimeBucket = new uint32_t[mTimeBuckets]; + } + + return true; +} + +bool Bench::init() { + mWorkers.init(); + + initIP(); + //ALOGV("%p Launching thread(s), CPUs %i", mRSC, mWorkers.mCount + 1); + + return true; +} + +bool Bench::incTimeBucket() const { + uint64_t time = getTimeNanos(); + uint64_t bucket = (time - mTimeStartNanos) / mTimeBucketDivisor; + + if (bucket >= mTimeBuckets) { + return false; + } + + __sync_fetch_and_add(&mTimeBucket[bucket], 1); + + return time < mTimeEndGroupNanos; +} + +void Bench::getData(float *data, size_t count) const { + if (count > mTimeBuckets) { + count = mTimeBuckets; + } + for (size_t ct = 0; ct < count; ct++) { + data[ct] = (float)mTimeBucket[ct]; + } +} + +bool Bench::runCPUHeatSoak(uint64_t /* options */) +{ + mTimeBucketDivisor = 1000 * 1000; // use ms + allocateBuckets(1000); + + mTimeStartNanos = getTimeNanos(); + mTimeEndNanos = mTimeStartNanos + mTimeBuckets * mTimeBucketDivisor; + memset(mTimeBucket, 0, sizeof(uint32_t) * mTimeBuckets); + + mTimeEndGroupNanos = mTimeEndNanos; + mWorkers.launchWork(testWork, this, 0); + return true; +} + +float Bench::runMemoryBandwidthTest(uint64_t size) +{ + uint64_t t1 = getTimeMillis(); + for (size_t ct = mMemLoopCount; ct > 0; ct--) { + memcpy(mMemDst, mMemSrc, size); + } + double dt = getTimeMillis() - t1; + dt /= 1000; + + double bw = ((double)size) * mMemLoopCount / dt; + bw /= 1024 * 1024 * 1024; + + float targetTime = 0.2f; + if (dt > targetTime) { + mMemLoopCount = (size_t)((double)mMemLoopCount / (dt / targetTime)); + } + + return (float)bw; +} + +float Bench::runMemoryLatencyTest(uint64_t size) +{ + //__android_log_print(ANDROID_LOG_INFO, "bench", "latency %i", (int)size); + void ** sp = (void **)mMemSrc; + size_t maxIndex = size / sizeof(void *); + size_t loops = ((maxIndex / 2) & (~3)); + //loops = 10; + + if (size != mMemLatencyLastSize) { + __android_log_print(ANDROID_LOG_INFO, "bench", "latency build %i %i", (int)maxIndex, loops); + mMemLatencyLastSize = size; + memset((void *)mMemSrc, 0, mMemLatencyLastSize); + + size_t lastIdx = 0; + for (size_t ct = 0; ct < loops; ct++) { + size_t ni = rand() * rand(); + ni = ni % maxIndex; + while ((sp[ni] != NULL) || (ni == lastIdx)) { + ni++; + if (ni >= maxIndex) { + ni = 1; + } + // __android_log_print(ANDROID_LOG_INFO, "bench", "gen ni loop %i %i", lastIdx, ni); + } + // __android_log_print(ANDROID_LOG_INFO, "bench", "gen ct = %i %i %i %p %p", (int)ct, lastIdx, ni, &sp[lastIdx], &sp[ni]); + sp[lastIdx] = &sp[ni]; + lastIdx = ni; + } + sp[lastIdx] = 0; + } + //__android_log_print(ANDROID_LOG_INFO, "bench", "latency testing"); + + uint64_t t1 = getTimeNanos(); + for (size_t ct = mMemLoopCount; ct > 0; ct--) { + size_t lc = 1; + volatile void *p = sp[0]; + while (p != NULL) { + // Unroll once to minimize branching overhead. + void **pn = (void **)p; + p = pn[0]; + pn = (void **)p; + p = pn[0]; + } + } + //__android_log_print(ANDROID_LOG_INFO, "bench", "v %i %i", loops * mMemLoopCount, v); + + double dt = getTimeNanos() - t1; + double dts = dt / 1000000000; + double lat = dt / (loops * mMemLoopCount); + __android_log_print(ANDROID_LOG_INFO, "bench", "latency ret %f", lat); + + float targetTime = 0.2f; + if (dts > targetTime) { + mMemLoopCount = (size_t)((double)mMemLoopCount / (dts / targetTime)); + if (mMemLoopCount < 1) { + mMemLoopCount = 1; + } + } + + return (float)lat; +} + +bool Bench::startMemTests() +{ + mMemSrc = (uint8_t *)malloc(1024*1024*64); + mMemDst = (uint8_t *)malloc(1024*1024*64); + + memset(mMemSrc, 0, 1024*1024*16); + memset(mMemDst, 0, 1024*1024*16); + + mMemLoopCount = 1; + uint64_t start = getTimeMillis(); + while((getTimeMillis() - start) < 500) { + memcpy(mMemDst, mMemSrc, 1024); + mMemLoopCount++; + } + mMemLatencyLastSize = 0; + return true; +} + +void Bench::endMemTests() +{ + free(mMemSrc); + free(mMemDst); + mMemSrc = NULL; + mMemDst = NULL; + mMemLatencyLastSize = 0; +} + +void Bench::GflopKernelC() { + int halfKX = (mGFlop.kernelXSize / 2); + for (int x = halfKX; x < (mGFlop.imageXSize - halfKX - 1); x++) { + const float * krnPtr = mGFlop.kernelBuffer; + float sum = 0.f; + + int srcInc = mGFlop.imageXSize - mGFlop.kernelXSize; + const float * srcPtr = &mGFlop.srcBuffer[x - halfKX]; + + for (int ix = 0; ix < mGFlop.kernelXSize; ix++) { + sum += srcPtr[0] * krnPtr[0]; + krnPtr++; + srcPtr++; + } + + float * dstPtr = &mGFlop.dstBuffer[x]; + dstPtr[0] = sum; + + } + +} + +void Bench::GflopKernelC_y3() { +} + +float Bench::runGFlopsTest(uint64_t /* options */) +{ + mTimeBucketDivisor = 1000 * 1000; // use ms + allocateBuckets(1000); + + mTimeStartNanos = getTimeNanos(); + mTimeEndNanos = mTimeStartNanos + mTimeBuckets * mTimeBucketDivisor; + memset(mTimeBucket, 0, sizeof(uint32_t) * mTimeBuckets); + + mTimeEndGroupNanos = mTimeEndNanos; + mWorkers.launchWork(testWork, this, 0); + + // Simulate image convolve + mGFlop.kernelXSize = 27; + mGFlop.imageXSize = 1024 * 1024; + + mGFlop.srcBuffer = (float *)malloc(mGFlop.imageXSize * sizeof(float)); + mGFlop.dstBuffer = (float *)malloc(mGFlop.imageXSize * sizeof(float)); + mGFlop.kernelBuffer = (float *)malloc(mGFlop.kernelXSize * sizeof(float)); + + double ops = mGFlop.kernelXSize; + ops = ops * 2.f - 1.f; + ops *= mGFlop.imageXSize; + + uint64_t t1 = getTimeNanos(); + GflopKernelC(); + double dt = getTimeNanos() - t1; + + dt /= 1000.f * 1000.f * 1000.f; + + double gflops = ops / dt / 1000000000.f; + + __android_log_print(ANDROID_LOG_INFO, "bench", "v %f %f %f", dt, ops, gflops); + + return (float)gflops; +} + + diff --git a/tests/JankBench/app/src/main/jni/Bench.h b/tests/JankBench/app/src/main/jni/Bench.h new file mode 100644 index 0000000000000..43a9066d6c2cf --- /dev/null +++ b/tests/JankBench/app/src/main/jni/Bench.h @@ -0,0 +1,112 @@ +/* + * Copyright (C) 2015 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. + */ + +#ifndef ANDROID_BENCH_H +#define ANDROID_BENCH_H + +#include + +#include "WorkerPool.h" + +#include + + + +class Bench { +public: + Bench(); + ~Bench(); + + struct GFlop { + int kernelXSize; + //int kernelYSize; + int imageXSize; + //int imageYSize; + + float *srcBuffer; + float *kernelBuffer; + float *dstBuffer; + + + }; + GFlop mGFlop; + + bool init(); + + bool runPowerManagementTest(uint64_t options); + bool runCPUHeatSoak(uint64_t options); + + bool startMemTests(); + void endMemTests(); + float runMemoryBandwidthTest(uint64_t options); + float runMemoryLatencyTest(uint64_t options); + + float runGFlopsTest(uint64_t options); + + void getData(float *data, size_t count) const; + + + void finish(); + + void setPriority(int32_t p); + void destroyWorkerThreadResources(); + + uint64_t getTimeNanos() const; + uint64_t getTimeMillis() const; + + // Adds a work unit completed to the timeline and returns + // true if the test is ongoing, false if time is up + bool incTimeBucket() const; + + +protected: + WorkerPool mWorkers; + + bool mExit; + bool mPaused; + + static void testWork(void *usr, uint32_t idx); + +private: + uint8_t * volatile mMemSrc; + uint8_t * volatile mMemDst; + size_t mMemLoopCount; + size_t mMemLatencyLastSize; + + + float ** mIpKernel; + float * volatile * mSrcBuf; + float * volatile * mOutBuf; + uint32_t * mTimeBucket; + + uint64_t mTimeStartNanos; + uint64_t mTimeEndNanos; + uint64_t mTimeBucketDivisor; + uint32_t mTimeBuckets; + + uint64_t mTimeEndGroupNanos; + + bool initIP(); + void GflopKernelC(); + void GflopKernelC_y3(); + + bool allocateBuckets(size_t); + + +}; + + +#endif diff --git a/tests/JankBench/app/src/main/jni/WorkerPool.cpp b/tests/JankBench/app/src/main/jni/WorkerPool.cpp new file mode 100644 index 0000000000000..a92ac91529875 --- /dev/null +++ b/tests/JankBench/app/src/main/jni/WorkerPool.cpp @@ -0,0 +1,266 @@ +/* + * Copyright (C) 2015 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. + */ + +#include "WorkerPool.h" +//#include +#include +#include +#include +#include + + +//static pthread_key_t gThreadTLSKey = 0; +//static uint32_t gThreadTLSKeyCount = 0; +//static pthread_mutex_t gInitMutex = PTHREAD_MUTEX_INITIALIZER; + + +WorkerPool::Signal::Signal() { + mSet = true; +} + +WorkerPool::Signal::~Signal() { + pthread_mutex_destroy(&mMutex); + pthread_cond_destroy(&mCondition); +} + +bool WorkerPool::Signal::init() { + int status = pthread_mutex_init(&mMutex, NULL); + if (status) { + __android_log_print(ANDROID_LOG_INFO, "bench", "WorkerPool mutex init failure"); + return false; + } + + status = pthread_cond_init(&mCondition, NULL); + if (status) { + __android_log_print(ANDROID_LOG_INFO, "bench", "WorkerPool condition init failure"); + pthread_mutex_destroy(&mMutex); + return false; + } + + return true; +} + +void WorkerPool::Signal::set() { + int status; + + status = pthread_mutex_lock(&mMutex); + if (status) { + __android_log_print(ANDROID_LOG_INFO, "bench", "WorkerPool: error %i locking for set condition.", status); + return; + } + + mSet = true; + + status = pthread_cond_signal(&mCondition); + if (status) { + __android_log_print(ANDROID_LOG_INFO, "bench", "WorkerPool: error %i on set condition.", status); + } + + status = pthread_mutex_unlock(&mMutex); + if (status) { + __android_log_print(ANDROID_LOG_INFO, "bench", "WorkerPool: error %i unlocking for set condition.", status); + } +} + +bool WorkerPool::Signal::wait(uint64_t timeout) { + int status; + bool ret = false; + + status = pthread_mutex_lock(&mMutex); + if (status) { + __android_log_print(ANDROID_LOG_INFO, "bench", "WorkerPool: error %i locking for condition.", status); + return false; + } + + if (!mSet) { + if (!timeout) { + status = pthread_cond_wait(&mCondition, &mMutex); + } else { +#if defined(HAVE_PTHREAD_COND_TIMEDWAIT_RELATIVE) + status = pthread_cond_timeout_np(&mCondition, &mMutex, timeout / 1000000); +#else + // This is safe it will just make things less reponsive + status = pthread_cond_wait(&mCondition, &mMutex); +#endif + } + } + + if (!status) { + mSet = false; + ret = true; + } else { +#ifndef RS_SERVER + if (status != ETIMEDOUT) { + __android_log_print(ANDROID_LOG_INFO, "bench", "WorkerPool: error %i waiting for condition.", status); + } +#endif + } + + status = pthread_mutex_unlock(&mMutex); + if (status) { + __android_log_print(ANDROID_LOG_INFO, "bench", "WorkerPool: error %i unlocking for condition.", status); + } + + return ret; +} + + + +WorkerPool::WorkerPool() { + mExit = false; + mRunningCount = 0; + mLaunchCount = 0; + mCount = 0; + mThreadId = NULL; + mNativeThreadId = NULL; + mLaunchSignals = NULL; + mLaunchCallback = NULL; + + +} + + +WorkerPool::~WorkerPool() { +__android_log_print(ANDROID_LOG_INFO, "bench", "~wp"); + mExit = true; + mLaunchData = NULL; + mLaunchCallback = NULL; + mRunningCount = mCount; + + __sync_synchronize(); + for (uint32_t ct = 0; ct < mCount; ct++) { + mLaunchSignals[ct].set(); + } + void *res; + for (uint32_t ct = 0; ct < mCount; ct++) { + pthread_join(mThreadId[ct], &res); + } + //rsAssert(__sync_fetch_and_or(&mRunningCount, 0) == 0); + free(mThreadId); + free(mNativeThreadId); + delete[] mLaunchSignals; +} + +bool WorkerPool::init(int threadCount) { + int cpu = sysconf(_SC_NPROCESSORS_CONF); + if (threadCount > 0) { + cpu = threadCount; + } + if (cpu < 1) { + return false; + } + mCount = (uint32_t)cpu; + + __android_log_print(ANDROID_LOG_INFO, "Bench", "ThreadLaunch %i", mCount); + + mThreadId = (pthread_t *) calloc(mCount, sizeof(pthread_t)); + mNativeThreadId = (pid_t *) calloc(mCount, sizeof(pid_t)); + mLaunchSignals = new Signal[mCount]; + mLaunchCallback = NULL; + + mCompleteSignal.init(); + mRunningCount = mCount; + mLaunchCount = 0; + __sync_synchronize(); + + pthread_attr_t threadAttr; + int status = pthread_attr_init(&threadAttr); + if (status) { + __android_log_print(ANDROID_LOG_INFO, "bench", "Failed to init thread attribute."); + return false; + } + + for (uint32_t ct=0; ct < mCount; ct++) { + status = pthread_create(&mThreadId[ct], &threadAttr, helperThreadProc, this); + if (status) { + mCount = ct; + __android_log_print(ANDROID_LOG_INFO, "bench", "Created fewer than expected number of threads."); + return false; + } + } + while (__sync_fetch_and_or(&mRunningCount, 0) != 0) { + usleep(100); + } + + pthread_attr_destroy(&threadAttr); + return true; +} + +void * WorkerPool::helperThreadProc(void *vwp) { + WorkerPool *wp = (WorkerPool *)vwp; + + uint32_t idx = __sync_fetch_and_add(&wp->mLaunchCount, 1); + + wp->mLaunchSignals[idx].init(); + wp->mNativeThreadId[idx] = gettid(); + + while (!wp->mExit) { + wp->mLaunchSignals[idx].wait(); + if (wp->mLaunchCallback) { + // idx +1 is used because the calling thread is always worker 0. + wp->mLaunchCallback(wp->mLaunchData, idx); + } + __sync_fetch_and_sub(&wp->mRunningCount, 1); + wp->mCompleteSignal.set(); + } + + //ALOGV("RS helperThread exited %p idx=%i", dc, idx); + return NULL; +} + + +void WorkerPool::waitForAll() const { +} + +void WorkerPool::waitFor(uint64_t) const { +} + + + +uint64_t WorkerPool::launchWork(WorkerCallback_t cb, void *usr, int maxThreads) { + //__android_log_print(ANDROID_LOG_INFO, "bench", "lw 1"); + mLaunchData = usr; + mLaunchCallback = cb; + + if (maxThreads < 1) { + maxThreads = mCount; + } + if ((uint32_t)maxThreads > mCount) { + //__android_log_print(ANDROID_LOG_INFO, "bench", "launchWork max > count", maxThreads, mCount); + maxThreads = mCount; + } + + //__android_log_print(ANDROID_LOG_INFO, "bench", "lw 2 %i %i %i", maxThreads, mRunningCount, mCount); + mRunningCount = maxThreads; + __sync_synchronize(); + + for (int ct = 0; ct < maxThreads; ct++) { + mLaunchSignals[ct].set(); + } + + //__android_log_print(ANDROID_LOG_INFO, "bench", "lw 3 %i", mRunningCount); + while (__sync_fetch_and_or(&mRunningCount, 0) != 0) { + //__android_log_print(ANDROID_LOG_INFO, "bench", "lw 3.1 %i", mRunningCount); + mCompleteSignal.wait(); + } + + //__android_log_print(ANDROID_LOG_INFO, "bench", "lw 4 %i", mRunningCount); + return 0; + +} + + + diff --git a/tests/JankBench/app/src/main/jni/WorkerPool.h b/tests/JankBench/app/src/main/jni/WorkerPool.h new file mode 100644 index 0000000000000..f8985d21be879 --- /dev/null +++ b/tests/JankBench/app/src/main/jni/WorkerPool.h @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2015 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. + */ + +#ifndef ANDROID_WORKER_POOL_H +#define ANDROID_WORKER_POOL_H + +#include +#include + + + +class WorkerPool { +public: + WorkerPool(); + ~WorkerPool(); + + typedef void (*WorkerCallback_t)(void *usr, uint32_t idx); + + bool init(int threadCount = -1); + int getWorkerCount() const {return mCount;} + + void waitForAll() const; + void waitFor(uint64_t) const; + uint64_t launchWork(WorkerCallback_t cb, void *usr, int maxThreads = -1); + + + + +protected: + class Signal { + public: + Signal(); + ~Signal(); + + bool init(); + void set(); + + // returns true if the signal occured + // false for timeout + bool wait(uint64_t timeout = 0); + + protected: + bool mSet; + pthread_mutex_t mMutex; + pthread_cond_t mCondition; + }; + + bool mExit; + volatile int mRunningCount; + volatile int mLaunchCount; + uint32_t mCount; + pthread_t *mThreadId; + pid_t *mNativeThreadId; + Signal mCompleteSignal; + Signal *mLaunchSignals; + WorkerCallback_t mLaunchCallback; + void *mLaunchData; + + + + +private: + //static void * threadProc(void *); + static void * helperThreadProc(void *); + + +}; + + +#endif diff --git a/tests/JankBench/app/src/main/jni/test.cpp b/tests/JankBench/app/src/main/jni/test.cpp new file mode 100644 index 0000000000000..e163daa531c87 --- /dev/null +++ b/tests/JankBench/app/src/main/jni/test.cpp @@ -0,0 +1,178 @@ +/* + * Copyright (C) 2015 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. + */ + +#include +#include +//#include +//#include +#include +#include +#include +#include + +#include "jni.h" +#include "Bench.h" + +#define FUNC(name) Java_com_android_benchmark_synthetic_TestInterface_##name + +static uint64_t GetTime() { + struct timespec t; + clock_gettime(CLOCK_MONOTONIC, &t); + return t.tv_nsec + ((uint64_t)t.tv_sec * 1000 * 1000 * 1000); +} + +extern "C" { + +jlong Java_com_android_benchmark_synthetic_TestInterface_nInit(JNIEnv *_env, jobject _this, jlong options) { + Bench *b = new Bench(); + bool ret = b->init(); + + if (ret) { + return (jlong)b; + } + + delete b; + return 0; +} + +void Java_com_android_benchmark_synthetic_TestInterface_nDestroy(JNIEnv *_env, jobject _this, jlong _b) { + Bench *b = (Bench *)_b; + + delete b; +} + +jboolean Java_com_android_benchmark_synthetic_TestInterface_nRunPowerManagementTest( + JNIEnv *_env, jobject _this, jlong _b, jlong options) { + Bench *b = (Bench *)_b; + return b->runPowerManagementTest(options); +} + +jboolean Java_com_android_benchmark_synthetic_TestInterface_nRunCPUHeatSoakTest( + JNIEnv *_env, jobject _this, jlong _b, jlong options) { + Bench *b = (Bench *)_b; + return b->runCPUHeatSoak(options); +} + +float Java_com_android_benchmark_synthetic_TestInterface_nGetData( + JNIEnv *_env, jobject _this, jlong _b, jfloatArray data) { + Bench *b = (Bench *)_b; + + jsize len = _env->GetArrayLength(data); + float * ptr = _env->GetFloatArrayElements(data, 0); + + b->getData(ptr, len); + + _env->ReleaseFloatArrayElements(data, (jfloat *)ptr, 0); + + return 0; +} + +jboolean Java_com_android_benchmark_synthetic_TestInterface_nMemTestStart( + JNIEnv *_env, jobject _this, jlong _b) { + Bench *b = (Bench *)_b; + return b->startMemTests(); +} + +float Java_com_android_benchmark_synthetic_TestInterface_nMemTestBandwidth( + JNIEnv *_env, jobject _this, jlong _b, jlong opt) { + Bench *b = (Bench *)_b; + return b->runMemoryBandwidthTest(opt); +} + +float Java_com_android_benchmark_synthetic_TestInterface_nGFlopsTest( + JNIEnv *_env, jobject _this, jlong _b, jlong opt) { + Bench *b = (Bench *)_b; + return b->runGFlopsTest(opt); +} + +float Java_com_android_benchmark_synthetic_TestInterface_nMemTestLatency( + JNIEnv *_env, jobject _this, jlong _b, jlong opt) { + Bench *b = (Bench *)_b; + return b->runMemoryLatencyTest(opt); +} + +void Java_com_android_benchmark_synthetic_TestInterface_nMemTestEnd( + JNIEnv *_env, jobject _this, jlong _b) { + Bench *b = (Bench *)_b; + b->endMemTests(); +} + +float Java_com_android_benchmark_synthetic_TestInterface_nMemoryTest( + JNIEnv *_env, jobject _this, jint subtest) { + + uint8_t * volatile m1 = (uint8_t *)malloc(1024*1024*64); + uint8_t * m2 = (uint8_t *)malloc(1024*1024*64); + + memset(m1, 0, 1024*1024*16); + memset(m2, 0, 1024*1024*16); + + //__android_log_print(ANDROID_LOG_INFO, "bench", "test %i %p %p", subtest, m1, m2); + + + size_t loopCount = 0; + uint64_t start = GetTime(); + while((GetTime() - start) < 1000000000) { + memcpy(m1, m2, subtest); + loopCount++; + } + if (loopCount == 0) { + loopCount = 1; + } + + size_t count = loopCount; + uint64_t t1 = GetTime(); + while (loopCount > 0) { + memcpy(m1, m2, subtest); + loopCount--; + } + uint64_t t2 = GetTime(); + + double dt = t2 - t1; + dt /= 1000 * 1000 * 1000; + double bw = ((double)subtest) * count / dt; + + bw /= 1024 * 1024 * 1024; + + __android_log_print(ANDROID_LOG_INFO, "bench", "size %i, bw %f", subtest, bw); + + free (m1); + free (m2); + return (float)bw; +} + +jlong Java_com_android_benchmark_synthetic_MemoryAvailableLoad1_nMemTestMalloc( + JNIEnv *_env, jobject _this, jint bytes) { + uint8_t *p = (uint8_t *)malloc(bytes); + memset(p, 0, bytes); + return (jlong)p; +} + +void Java_com_android_benchmark_synthetic_MemoryAvailableLoad1_nMemTestFree( + JNIEnv *_env, jobject _this, jlong ptr) { + free((void *)ptr); +} + +jlong Java_com_android_benchmark_synthetic_MemoryAvailableLoad2_nMemTestMalloc( + JNIEnv *_env, jobject _this, jint bytes) { + return Java_com_android_benchmark_synthetic_MemoryAvailableLoad1_nMemTestMalloc(_env, _this, bytes); +} + +void Java_com_android_benchmark_synthetic_MemoryAvailableLoad2_nMemTestFree( + JNIEnv *_env, jobject _this, jlong ptr) { + Java_com_android_benchmark_synthetic_MemoryAvailableLoad1_nMemTestFree(_env, _this, ptr); +} + +}; // extern "C" diff --git a/tests/JankBench/app/src/main/res/drawable/ic_play.png b/tests/JankBench/app/src/main/res/drawable/ic_play.png new file mode 100644 index 0000000000000..13ed283cb5bd1 Binary files /dev/null and b/tests/JankBench/app/src/main/res/drawable/ic_play.png differ diff --git a/tests/JankBench/app/src/main/res/drawable/img1.jpg b/tests/JankBench/app/src/main/res/drawable/img1.jpg new file mode 100644 index 0000000000000..33c1fedc0c6fe Binary files /dev/null and b/tests/JankBench/app/src/main/res/drawable/img1.jpg differ diff --git a/tests/JankBench/app/src/main/res/drawable/img2.jpg b/tests/JankBench/app/src/main/res/drawable/img2.jpg new file mode 100644 index 0000000000000..1ea97f2cc59cd Binary files /dev/null and b/tests/JankBench/app/src/main/res/drawable/img2.jpg differ diff --git a/tests/JankBench/app/src/main/res/drawable/img3.jpg b/tests/JankBench/app/src/main/res/drawable/img3.jpg new file mode 100644 index 0000000000000..ff992695621db Binary files /dev/null and b/tests/JankBench/app/src/main/res/drawable/img3.jpg differ diff --git a/tests/JankBench/app/src/main/res/drawable/img4.jpg b/tests/JankBench/app/src/main/res/drawable/img4.jpg new file mode 100644 index 0000000000000..d9cbd2f42190f Binary files /dev/null and b/tests/JankBench/app/src/main/res/drawable/img4.jpg differ diff --git a/tests/JankBench/app/src/main/res/layout/activity_bitmap_upload.xml b/tests/JankBench/app/src/main/res/layout/activity_bitmap_upload.xml new file mode 100644 index 0000000000000..6b3c8992d4144 --- /dev/null +++ b/tests/JankBench/app/src/main/res/layout/activity_bitmap_upload.xml @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/tests/JankBench/app/src/main/res/layout/activity_home.xml b/tests/JankBench/app/src/main/res/layout/activity_home.xml new file mode 100644 index 0000000000000..c4f429922aabf --- /dev/null +++ b/tests/JankBench/app/src/main/res/layout/activity_home.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + diff --git a/tests/JankBench/app/src/main/res/layout/activity_list_fragment.xml b/tests/JankBench/app/src/main/res/layout/activity_list_fragment.xml new file mode 100644 index 0000000000000..0aaaddebc0404 --- /dev/null +++ b/tests/JankBench/app/src/main/res/layout/activity_list_fragment.xml @@ -0,0 +1,34 @@ + + + + + + + + diff --git a/tests/JankBench/app/src/main/res/layout/activity_memory.xml b/tests/JankBench/app/src/main/res/layout/activity_memory.xml new file mode 100644 index 0000000000000..fd5cadc249849 --- /dev/null +++ b/tests/JankBench/app/src/main/res/layout/activity_memory.xml @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + + diff --git a/tests/JankBench/app/src/main/res/layout/activity_running_list.xml b/tests/JankBench/app/src/main/res/layout/activity_running_list.xml new file mode 100644 index 0000000000000..7b7b93066f937 --- /dev/null +++ b/tests/JankBench/app/src/main/res/layout/activity_running_list.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/tests/JankBench/app/src/main/res/layout/benchmark_list_group_row.xml b/tests/JankBench/app/src/main/res/layout/benchmark_list_group_row.xml new file mode 100644 index 0000000000000..5375dbca194bc --- /dev/null +++ b/tests/JankBench/app/src/main/res/layout/benchmark_list_group_row.xml @@ -0,0 +1,31 @@ + + + + + + + + \ No newline at end of file diff --git a/tests/JankBench/app/src/main/res/layout/benchmark_list_item.xml b/tests/JankBench/app/src/main/res/layout/benchmark_list_item.xml new file mode 100644 index 0000000000000..5282e14fddfc5 --- /dev/null +++ b/tests/JankBench/app/src/main/res/layout/benchmark_list_item.xml @@ -0,0 +1,40 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/tests/JankBench/app/src/main/res/layout/card_row.xml b/tests/JankBench/app/src/main/res/layout/card_row.xml new file mode 100644 index 0000000000000..215f9df9b7fdd --- /dev/null +++ b/tests/JankBench/app/src/main/res/layout/card_row.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/tests/JankBench/app/src/main/res/layout/content_main.xml b/tests/JankBench/app/src/main/res/layout/content_main.xml new file mode 100644 index 0000000000000..201bd66a65897 --- /dev/null +++ b/tests/JankBench/app/src/main/res/layout/content_main.xml @@ -0,0 +1,26 @@ + + + + diff --git a/tests/JankBench/app/src/main/res/layout/fragment_dashboard.xml b/tests/JankBench/app/src/main/res/layout/fragment_dashboard.xml new file mode 100644 index 0000000000000..f3100c7ea75df --- /dev/null +++ b/tests/JankBench/app/src/main/res/layout/fragment_dashboard.xml @@ -0,0 +1,91 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/JankBench/app/src/main/res/layout/fragment_ui_results_detail.xml b/tests/JankBench/app/src/main/res/layout/fragment_ui_results_detail.xml new file mode 100644 index 0000000000000..74d98910294c0 --- /dev/null +++ b/tests/JankBench/app/src/main/res/layout/fragment_ui_results_detail.xml @@ -0,0 +1,10 @@ + + + + + + \ No newline at end of file diff --git a/tests/JankBench/app/src/main/res/layout/image_scroll_list_item.xml b/tests/JankBench/app/src/main/res/layout/image_scroll_list_item.xml new file mode 100644 index 0000000000000..c1662ea76fd39 --- /dev/null +++ b/tests/JankBench/app/src/main/res/layout/image_scroll_list_item.xml @@ -0,0 +1,18 @@ + + + + + + diff --git a/tests/JankBench/app/src/main/res/layout/results_list_item.xml b/tests/JankBench/app/src/main/res/layout/results_list_item.xml new file mode 100644 index 0000000000000..f38b147ac17e8 --- /dev/null +++ b/tests/JankBench/app/src/main/res/layout/results_list_item.xml @@ -0,0 +1,21 @@ + + + + + + + + \ No newline at end of file diff --git a/tests/JankBench/app/src/main/res/layout/running_benchmark_list_item.xml b/tests/JankBench/app/src/main/res/layout/running_benchmark_list_item.xml new file mode 100644 index 0000000000000..8a9d015e6ad36 --- /dev/null +++ b/tests/JankBench/app/src/main/res/layout/running_benchmark_list_item.xml @@ -0,0 +1,31 @@ + + + + + + + + \ No newline at end of file diff --git a/tests/JankBench/app/src/main/res/menu/menu_main.xml b/tests/JankBench/app/src/main/res/menu/menu_main.xml new file mode 100644 index 0000000000000..1633acdaaf310 --- /dev/null +++ b/tests/JankBench/app/src/main/res/menu/menu_main.xml @@ -0,0 +1,26 @@ + + + + + diff --git a/tests/JankBench/app/src/main/res/menu/menu_memory.xml b/tests/JankBench/app/src/main/res/menu/menu_memory.xml new file mode 100644 index 0000000000000..f2df7c9d76621 --- /dev/null +++ b/tests/JankBench/app/src/main/res/menu/menu_memory.xml @@ -0,0 +1,5 @@ + + + diff --git a/tests/JankBench/app/src/main/res/mipmap-hdpi/ic_launcher.png b/tests/JankBench/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000000..cde69bcccec65 Binary files /dev/null and b/tests/JankBench/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/tests/JankBench/app/src/main/res/mipmap-mdpi/ic_launcher.png b/tests/JankBench/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000000..c133a0cbd379f Binary files /dev/null and b/tests/JankBench/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/tests/JankBench/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/tests/JankBench/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000000000..bfa42f0e7b91d Binary files /dev/null and b/tests/JankBench/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/tests/JankBench/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/tests/JankBench/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000000..324e72cdd7480 Binary files /dev/null and b/tests/JankBench/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/tests/JankBench/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/tests/JankBench/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000000..aee44e1384346 Binary files /dev/null and b/tests/JankBench/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/tests/JankBench/app/src/main/res/values-v21/styles.xml b/tests/JankBench/app/src/main/res/values-v21/styles.xml new file mode 100644 index 0000000000000..99ed094a18cfd --- /dev/null +++ b/tests/JankBench/app/src/main/res/values-v21/styles.xml @@ -0,0 +1,25 @@ + + + + + + diff --git a/tests/JankBench/app/src/main/res/values-w820dp/dimens.xml b/tests/JankBench/app/src/main/res/values-w820dp/dimens.xml new file mode 100644 index 0000000000000..e783e5d107733 --- /dev/null +++ b/tests/JankBench/app/src/main/res/values-w820dp/dimens.xml @@ -0,0 +1,22 @@ + + + + + 64dp + diff --git a/tests/JankBench/app/src/main/res/values/attrs.xml b/tests/JankBench/app/src/main/res/values/attrs.xml new file mode 100644 index 0000000000000..a4286f173b150 --- /dev/null +++ b/tests/JankBench/app/src/main/res/values/attrs.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/JankBench/app/src/main/res/values/colors.xml b/tests/JankBench/app/src/main/res/values/colors.xml new file mode 100644 index 0000000000000..59156eef00675 --- /dev/null +++ b/tests/JankBench/app/src/main/res/values/colors.xml @@ -0,0 +1,22 @@ + + + + + #3F51B5 + #303F9F + #FF4081 + diff --git a/tests/JankBench/app/src/main/res/values/dimens.xml b/tests/JankBench/app/src/main/res/values/dimens.xml new file mode 100644 index 0000000000000..9da649a15f374 --- /dev/null +++ b/tests/JankBench/app/src/main/res/values/dimens.xml @@ -0,0 +1,27 @@ + + + + + 256dp + 16dp + 16dp + 16dp + 16dp + 200dp + 200dp + 16dp + diff --git a/tests/JankBench/app/src/main/res/values/ids.xml b/tests/JankBench/app/src/main/res/values/ids.xml new file mode 100644 index 0000000000000..6801fd9f61ec6 --- /dev/null +++ b/tests/JankBench/app/src/main/res/values/ids.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/tests/JankBench/app/src/main/res/values/strings.xml b/tests/JankBench/app/src/main/res/values/strings.xml new file mode 100644 index 0000000000000..270adf89e4ed0 --- /dev/null +++ b/tests/JankBench/app/src/main/res/values/strings.xml @@ -0,0 +1,51 @@ + + + + Benchmark + + Export to CSV + + List View Fling + Tests list view fling performance + Image List View Fling + Tests list view fling performance with images + Shadow Grid Fling + Tests shadow grid fling performance with images + High-hitrate text render + Tests high hitrate text rendering + Low-hitrate text render + Tests low-hitrate text rendering + Edit Text Input + Tests edit text input + Overdraw Test + Tests how the device handles overdraw + Memory Bandwidth + Test device\'s memory bandwidth + Memory Latency + Test device\'s memory latency + Power Management + Test device\'s power management + CPU Heat Soak + How hot can we make it? + CPU GFlops + How many gigaflops can the device attain? + + UI + Compute + Generic + ImageListViewScroll + diff --git a/tests/JankBench/app/src/main/res/values/styles.xml b/tests/JankBench/app/src/main/res/values/styles.xml new file mode 100644 index 0000000000000..25ce730ba2d10 --- /dev/null +++ b/tests/JankBench/app/src/main/res/values/styles.xml @@ -0,0 +1,43 @@ + + + + + + + + + + + diff --git a/tests/JankBench/app/src/main/res/xml/benchmark.xml b/tests/JankBench/app/src/main/res/xml/benchmark.xml new file mode 100644 index 0000000000000..07c453c25359f --- /dev/null +++ b/tests/JankBench/app/src/main/res/xml/benchmark.xml @@ -0,0 +1,97 @@ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/tests/JankBench/app/src/test/java/com/android/benchmark/ExampleUnitTest.java b/tests/JankBench/app/src/test/java/com/android/benchmark/ExampleUnitTest.java new file mode 100644 index 0000000000000..4464e87ef449c --- /dev/null +++ b/tests/JankBench/app/src/test/java/com/android/benchmark/ExampleUnitTest.java @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2015 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. + */ + +package com.android.benchmark; + +import org.junit.Test; + +import static org.junit.Assert.*; + +/** + * To work on unit tests, switch the Test Artifact in the Build Variants view. + */ +public class ExampleUnitTest { + @Test + public void addition_isCorrect() throws Exception { + assertEquals(4, 2 + 2); + } +} diff --git a/tests/JankBench/scripts/adbutil.py b/tests/JankBench/scripts/adbutil.py new file mode 100644 index 0000000000000..ad9f7d9fa3209 --- /dev/null +++ b/tests/JankBench/scripts/adbutil.py @@ -0,0 +1,62 @@ +import subprocess +import re +import threading + +ATRACE_PATH="/android/catapult/systrace/systrace/systrace.py" + +class AdbError(RuntimeError): + def __init__(self, arg): + self.args = arg + +def am(serial, cmd, args): + if not isinstance(args, list): + args = [args] + full_args = ["am"] + [cmd] + args + __call_adb(serial, full_args, False) + +def pm(serial, cmd, args): + if not isinstance(args, list): + args = [args] + full_args = ["pm"] + [cmd] + args + __call_adb(serial, full_args, False) + +def dumpsys(serial, topic): + return __call_adb(serial, ["dumpsys"] + [topic], True) + +def trace(serial, + tags = ["gfx", "sched", "view", "freq", "am", "wm", "power", "load", "memreclaim"], + time = "10"): + args = [ATRACE_PATH, "-e", serial, "-t" + time, "-b32768"] + tags + subprocess.call(args) + +def wake(serial): + output = dumpsys(serial, "power") + wakefulness = re.search('mWakefulness=([a-zA-Z]+)', output) + if wakefulness.group(1) != "Awake": + __call_adb(serial, ["input", "keyevent", "KEYCODE_POWER"], False) + +def root(serial): + subprocess.call(["adb", "-s", serial, "root"]) + +def pull(serial, path, dest): + subprocess.call(["adb", "-s", serial, "wait-for-device", "pull"] + [path] + [dest]) + +def shell(serial, cmd): + __call_adb(serial, cmd, False) + +def track_logcat(serial, awaited_string, callback): + threading.Thread(target=__track_logcat, name=serial + "-waiter", args=(serial, awaited_string, callback)).start() + +def __call_adb(serial, args, block): + full_args = ["adb", "-s", serial, "wait-for-device", "shell"] + args + print full_args + output = None + try: + if block: + output = subprocess.check_output(full_args) + else: + subprocess.call(full_args) + except subprocess.CalledProcessError: + raise AdbError("Error calling " + " ".join(args)) + + return output diff --git a/tests/JankBench/scripts/collect.py b/tests/JankBench/scripts/collect.py new file mode 100755 index 0000000000000..87a059453b433 --- /dev/null +++ b/tests/JankBench/scripts/collect.py @@ -0,0 +1,240 @@ +#!/usr/bin/python + +import optparse +import sys +import sqlite3 +import scipy.stats +import numpy +from math import log10, floor +import matplotlib + +matplotlib.use("Agg") + +import matplotlib.pyplot as plt +import pylab + +import adbutil +from devices import DEVICES + +DB_PATH="/data/data/com.android.benchmark/databases/BenchmarkResults" +OUT_PATH = "db/" + +QUERY_BAD_FRAME = ("select run_id, name, iteration, total_duration from ui_results " + "where total_duration >= 16 order by run_id, name, iteration") +QUERY_PERCENT_JANK = ("select run_id, name, iteration, sum(jank_frame) as jank_count, count (*) as total " + "from ui_results group by run_id, name, iteration") + +SKIP_TESTS = [ + # "BMUpload", + # "Low-hitrate text render", + # "High-hitrate text render", + # "Edit Text Input", + # "List View Fling" +] + +INCLUDE_TESTS = [ + #"BMUpload" + #"Shadow Grid Fling" + #"Image List View Fling" + #"Edit Text Input" +] + +class IterationResult: + def __init__(self): + self.durations = [] + self.jank_count = 0 + self.total_count = 0 + + +def get_scoremap(dbpath): + db = sqlite3.connect(dbpath) + rows = db.execute(QUERY_BAD_FRAME) + + scoremap = {} + for row in rows: + run_id = row[0] + name = row[1] + iteration = row[2] + total_duration = row[3] + + if not run_id in scoremap: + scoremap[run_id] = {} + + if not name in scoremap[run_id]: + scoremap[run_id][name] = {} + + if not iteration in scoremap[run_id][name]: + scoremap[run_id][name][iteration] = IterationResult() + + scoremap[run_id][name][iteration].durations.append(float(total_duration)) + + for row in db.execute(QUERY_PERCENT_JANK): + run_id = row[0] + name = row[1] + iteration = row[2] + jank_count = row[3] + total_count = row[4] + + if run_id in scoremap.keys() and name in scoremap[run_id].keys() and iteration in scoremap[run_id][name].keys(): + scoremap[run_id][name][iteration].jank_count = long(jank_count) + scoremap[run_id][name][iteration].total_count = long(total_count) + + db.close() + return scoremap + +def round_to_2(val): + return val + if val == 0: + return val + return round(val , -int(floor(log10(abs(val)))) + 1) + +def score_device(name, serial, pull = False, verbose = False): + dbpath = OUT_PATH + name + ".db" + + if pull: + adbutil.root(serial) + adbutil.pull(serial, DB_PATH, dbpath) + + scoremap = None + try: + scoremap = get_scoremap(dbpath) + except sqlite3.DatabaseError: + print "Database corrupt, fetching..." + adbutil.root(serial) + adbutil.pull(serial, DB_PATH, dbpath) + scoremap = get_scoremap(dbpath) + + per_test_score = {} + per_test_sample_count = {} + global_overall = {} + + for run_id in iter(scoremap): + overall = [] + if len(scoremap[run_id]) < 1: + if verbose: + print "Skipping short run %s" % run_id + continue + print "Run: %s" % run_id + for test in iter(scoremap[run_id]): + if test in SKIP_TESTS: + continue + if INCLUDE_TESTS and test not in INCLUDE_TESTS: + continue + if verbose: + print "\t%s" % test + scores = [] + means = [] + stddevs = [] + pjs = [] + sample_count = 0 + hit_min_count = 0 + # try pooling together all iterations + for iteration in iter(scoremap[run_id][test]): + res = scoremap[run_id][test][iteration] + stddev = round_to_2(numpy.std(res.durations)) + mean = round_to_2(numpy.mean(res.durations)) + sample_count += len(res.durations) + pj = round_to_2(100 * res.jank_count / float(res.total_count)) + score = stddev * mean * pj + score = 100 * len(res.durations) / float(res.total_count) + if score == 0: + score = 1 + scores.append(score) + means.append(mean) + stddevs.append(stddev) + pjs.append(pj) + if verbose: + print "\t%s: Score = %f x %f x %f = %f (%d samples)" % (iteration, stddev, mean, pj, score, len(res.durations)) + + if verbose: + print "\tHit min: %d" % hit_min_count + print "\tMean Variation: %0.2f%%" % (100 * scipy.stats.variation(means)) + print "\tStdDev Variation: %0.2f%%" % (100 * scipy.stats.variation(stddevs)) + print "\tPJ Variation: %0.2f%%" % (100 * scipy.stats.variation(pjs)) + + geo_run = numpy.mean(scores) + if test not in per_test_score: + per_test_score[test] = [] + + if test not in per_test_sample_count: + per_test_sample_count[test] = [] + + sample_count /= len(scoremap[run_id][test]) + + per_test_score[test].append(geo_run) + per_test_sample_count[test].append(int(sample_count)) + overall.append(geo_run) + + if not verbose: + print "\t%s:\t%0.2f (%0.2f avg. sample count)" % (test, geo_run, sample_count) + else: + print "\tOverall:\t%0.2f (%0.2f avg. sample count)" % (geo_run, sample_count) + print "" + + global_overall[run_id] = scipy.stats.gmean(overall) + print "Run Overall: %f" % global_overall[run_id] + print "" + + print "" + print "Variability (CV) - %s:" % name + + worst_offender_test = None + worst_offender_variation = 0 + for test in per_test_score: + variation = 100 * scipy.stats.variation(per_test_score[test]) + if worst_offender_variation < variation: + worst_offender_test = test + worst_offender_variation = variation + print "\t%s:\t%0.2f%% (%0.2f avg sample count)" % (test, variation, numpy.mean(per_test_sample_count[test])) + + print "\tOverall: %0.2f%%" % (100 * scipy.stats.variation([x for x in global_overall.values()])) + print "" + + return { + "overall": global_overall.values(), + "worst_offender_test": (name, worst_offender_test, worst_offender_variation) + } + +def parse_options(argv): + usage = 'Usage: %prog [options]' + desc = 'Example: %prog' + parser = optparse.OptionParser(usage=usage, description=desc) + parser.add_option("-p", dest='pull', action="store_true") + parser.add_option("-d", dest='device', action="store") + parser.add_option("-v", dest='verbose', action="store_true") + options, categories = parser.parse_args(argv[1:]) + return options + +def main(): + options = parse_options(sys.argv) + if options.device != None: + score_device(options.device, DEVICES[options.device], options.pull, options.verbose) + else: + device_scores = [] + worst_offenders = [] + for name, serial in DEVICES.iteritems(): + print "======== %s =========" % name + result = score_device(name, serial, options.pull, options.verbose) + device_scores.append((name, result["overall"])) + worst_offenders.append(result["worst_offender_test"]) + + + device_scores.sort(cmp=(lambda x, y: cmp(x[1], y[1]))) + print "Ranking by max overall score:" + for name, score in device_scores: + plt.plot([0, 1, 2, 3, 4, 5], score, label=name) + print "\t%s: %s" % (name, score) + + plt.ylabel("Jank %") + plt.xlabel("Iteration") + plt.title("Jank Percentage") + plt.legend() + pylab.savefig("holy.png", bbox_inches="tight") + + print "Worst offender tests:" + for device, test, variation in worst_offenders: + print "\t%s: %s %.2f%%" % (device, test, variation) + +if __name__ == "__main__": + main() + diff --git a/tests/JankBench/scripts/devices.py b/tests/JankBench/scripts/devices.py new file mode 100644 index 0000000000000..c8266c0ecef58 --- /dev/null +++ b/tests/JankBench/scripts/devices.py @@ -0,0 +1,11 @@ +#!/usr/bin/python + +DEVICES = { + 'bullhead': '00606a370e3ca155', + 'volantis': 'HT4BTWV00612', + 'angler': '84B5T15A29021748', + 'seed': '1285c85e', + 'ryu': '5A27000599', + 'shamu': 'ZX1G22W24R', +} + diff --git a/tests/JankBench/scripts/external/__init__.py b/tests/JankBench/scripts/external/__init__.py new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/tests/JankBench/scripts/external/statistics.py b/tests/JankBench/scripts/external/statistics.py new file mode 100644 index 0000000000000..518f54654469e --- /dev/null +++ b/tests/JankBench/scripts/external/statistics.py @@ -0,0 +1,638 @@ +## Module statistics.py +## +## Copyright (c) 2013 Steven D'Aprano . +## +## 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. + + +""" +Basic statistics module. + +This module provides functions for calculating statistics of data, including +averages, variance, and standard deviation. + +Calculating averages +-------------------- + +================== ============================================= +Function Description +================== ============================================= +mean Arithmetic mean (average) of data. +median Median (middle value) of data. +median_low Low median of data. +median_high High median of data. +median_grouped Median, or 50th percentile, of grouped data. +mode Mode (most common value) of data. +================== ============================================= + +Calculate the arithmetic mean ("the average") of data: + +>>> mean([-1.0, 2.5, 3.25, 5.75]) +2.625 + + +Calculate the standard median of discrete data: + +>>> median([2, 3, 4, 5]) +3.5 + + +Calculate the median, or 50th percentile, of data grouped into class intervals +centred on the data values provided. E.g. if your data points are rounded to +the nearest whole number: + +>>> median_grouped([2, 2, 3, 3, 3, 4]) #doctest: +ELLIPSIS +2.8333333333... + +This should be interpreted in this way: you have two data points in the class +interval 1.5-2.5, three data points in the class interval 2.5-3.5, and one in +the class interval 3.5-4.5. The median of these data points is 2.8333... + + +Calculating variability or spread +--------------------------------- + +================== ============================================= +Function Description +================== ============================================= +pvariance Population variance of data. +variance Sample variance of data. +pstdev Population standard deviation of data. +stdev Sample standard deviation of data. +================== ============================================= + +Calculate the standard deviation of sample data: + +>>> stdev([2.5, 3.25, 5.5, 11.25, 11.75]) #doctest: +ELLIPSIS +4.38961843444... + +If you have previously calculated the mean, you can pass it as the optional +second argument to the four "spread" functions to avoid recalculating it: + +>>> data = [1, 2, 2, 4, 4, 4, 5, 6] +>>> mu = mean(data) +>>> pvariance(data, mu) +2.5 + + +Exceptions +---------- + +A single exception is defined: StatisticsError is a subclass of ValueError. + +""" + +__all__ = [ 'StatisticsError', + 'pstdev', 'pvariance', 'stdev', 'variance', + 'median', 'median_low', 'median_high', 'median_grouped', + 'mean', 'mode', + ] + + +import collections +import math + +from fractions import Fraction +from decimal import Decimal +from itertools import groupby + + + +# === Exceptions === + +class StatisticsError(ValueError): + pass + + +# === Private utilities === + +def _sum(data, start=0): + """_sum(data [, start]) -> (type, sum, count) + + Return a high-precision sum of the given numeric data as a fraction, + together with the type to be converted to and the count of items. + + If optional argument ``start`` is given, it is added to the total. + If ``data`` is empty, ``start`` (defaulting to 0) is returned. + + + Examples + -------- + + >>> _sum([3, 2.25, 4.5, -0.5, 1.0], 0.75) + (, Fraction(11, 1), 5) + + Some sources of round-off error will be avoided: + + >>> _sum([1e50, 1, -1e50] * 1000) # Built-in sum returns zero. + (, Fraction(1000, 1), 3000) + + Fractions and Decimals are also supported: + + >>> from fractions import Fraction as F + >>> _sum([F(2, 3), F(7, 5), F(1, 4), F(5, 6)]) + (, Fraction(63, 20), 4) + + >>> from decimal import Decimal as D + >>> data = [D("0.1375"), D("0.2108"), D("0.3061"), D("0.0419")] + >>> _sum(data) + (, Fraction(6963, 10000), 4) + + Mixed types are currently treated as an error, except that int is + allowed. + """ + count = 0 + n, d = _exact_ratio(start) + partials = {d: n} + partials_get = partials.get + T = _coerce(int, type(start)) + for typ, values in groupby(data, type): + T = _coerce(T, typ) # or raise TypeError + for n,d in map(_exact_ratio, values): + count += 1 + partials[d] = partials_get(d, 0) + n + if None in partials: + # The sum will be a NAN or INF. We can ignore all the finite + # partials, and just look at this special one. + total = partials[None] + assert not _isfinite(total) + else: + # Sum all the partial sums using builtin sum. + # FIXME is this faster if we sum them in order of the denominator? + total = sum(Fraction(n, d) for d, n in sorted(partials.items())) + return (T, total, count) + + +def _isfinite(x): + try: + return x.is_finite() # Likely a Decimal. + except AttributeError: + return math.isfinite(x) # Coerces to float first. + + +def _coerce(T, S): + """Coerce types T and S to a common type, or raise TypeError. + + Coercion rules are currently an implementation detail. See the CoerceTest + test class in test_statistics for details. + """ + # See http://bugs.python.org/issue24068. + assert T is not bool, "initial type T is bool" + # If the types are the same, no need to coerce anything. Put this + # first, so that the usual case (no coercion needed) happens as soon + # as possible. + if T is S: return T + # Mixed int & other coerce to the other type. + if S is int or S is bool: return T + if T is int: return S + # If one is a (strict) subclass of the other, coerce to the subclass. + if issubclass(S, T): return S + if issubclass(T, S): return T + # Ints coerce to the other type. + if issubclass(T, int): return S + if issubclass(S, int): return T + # Mixed fraction & float coerces to float (or float subclass). + if issubclass(T, Fraction) and issubclass(S, float): + return S + if issubclass(T, float) and issubclass(S, Fraction): + return T + # Any other combination is disallowed. + msg = "don't know how to coerce %s and %s" + raise TypeError(msg % (T.__name__, S.__name__)) + + +def _exact_ratio(x): + """Return Real number x to exact (numerator, denominator) pair. + + >>> _exact_ratio(0.25) + (1, 4) + + x is expected to be an int, Fraction, Decimal or float. + """ + try: + # Optimise the common case of floats. We expect that the most often + # used numeric type will be builtin floats, so try to make this as + # fast as possible. + if type(x) is float: + return x.as_integer_ratio() + try: + # x may be an int, Fraction, or Integral ABC. + return (x.numerator, x.denominator) + except AttributeError: + try: + # x may be a float subclass. + return x.as_integer_ratio() + except AttributeError: + try: + # x may be a Decimal. + return _decimal_to_ratio(x) + except AttributeError: + # Just give up? + pass + except (OverflowError, ValueError): + # float NAN or INF. + assert not math.isfinite(x) + return (x, None) + msg = "can't convert type '{}' to numerator/denominator" + raise TypeError(msg.format(type(x).__name__)) + + +# FIXME This is faster than Fraction.from_decimal, but still too slow. +def _decimal_to_ratio(d): + """Convert Decimal d to exact integer ratio (numerator, denominator). + + >>> from decimal import Decimal + >>> _decimal_to_ratio(Decimal("2.6")) + (26, 10) + + """ + sign, digits, exp = d.as_tuple() + if exp in ('F', 'n', 'N'): # INF, NAN, sNAN + assert not d.is_finite() + return (d, None) + num = 0 + for digit in digits: + num = num*10 + digit + if exp < 0: + den = 10**-exp + else: + num *= 10**exp + den = 1 + if sign: + num = -num + return (num, den) + + +def _convert(value, T): + """Convert value to given numeric type T.""" + if type(value) is T: + # This covers the cases where T is Fraction, or where value is + # a NAN or INF (Decimal or float). + return value + if issubclass(T, int) and value.denominator != 1: + T = float + try: + # FIXME: what do we do if this overflows? + return T(value) + except TypeError: + if issubclass(T, Decimal): + return T(value.numerator)/T(value.denominator) + else: + raise + + +def _counts(data): + # Generate a table of sorted (value, frequency) pairs. + table = collections.Counter(iter(data)).most_common() + if not table: + return table + # Extract the values with the highest frequency. + maxfreq = table[0][1] + for i in range(1, len(table)): + if table[i][1] != maxfreq: + table = table[:i] + break + return table + + +# === Measures of central tendency (averages) === + +def mean(data): + """Return the sample arithmetic mean of data. + + >>> mean([1, 2, 3, 4, 4]) + 2.8 + + >>> from fractions import Fraction as F + >>> mean([F(3, 7), F(1, 21), F(5, 3), F(1, 3)]) + Fraction(13, 21) + + >>> from decimal import Decimal as D + >>> mean([D("0.5"), D("0.75"), D("0.625"), D("0.375")]) + Decimal('0.5625') + + If ``data`` is empty, StatisticsError will be raised. + """ + if iter(data) is data: + data = list(data) + n = len(data) + if n < 1: + raise StatisticsError('mean requires at least one data point') + T, total, count = _sum(data) + assert count == n + return _convert(total/n, T) + + +# FIXME: investigate ways to calculate medians without sorting? Quickselect? +def median(data): + """Return the median (middle value) of numeric data. + + When the number of data points is odd, return the middle data point. + When the number of data points is even, the median is interpolated by + taking the average of the two middle values: + + >>> median([1, 3, 5]) + 3 + >>> median([1, 3, 5, 7]) + 4.0 + + """ + data = sorted(data) + n = len(data) + if n == 0: + raise StatisticsError("no median for empty data") + if n%2 == 1: + return data[n//2] + else: + i = n//2 + return (data[i - 1] + data[i])/2 + + +def median_low(data): + """Return the low median of numeric data. + + When the number of data points is odd, the middle value is returned. + When it is even, the smaller of the two middle values is returned. + + >>> median_low([1, 3, 5]) + 3 + >>> median_low([1, 3, 5, 7]) + 3 + + """ + data = sorted(data) + n = len(data) + if n == 0: + raise StatisticsError("no median for empty data") + if n%2 == 1: + return data[n//2] + else: + return data[n//2 - 1] + + +def median_high(data): + """Return the high median of data. + + When the number of data points is odd, the middle value is returned. + When it is even, the larger of the two middle values is returned. + + >>> median_high([1, 3, 5]) + 3 + >>> median_high([1, 3, 5, 7]) + 5 + + """ + data = sorted(data) + n = len(data) + if n == 0: + raise StatisticsError("no median for empty data") + return data[n//2] + + +def median_grouped(data, interval=1): + """Return the 50th percentile (median) of grouped continuous data. + + >>> median_grouped([1, 2, 2, 3, 4, 4, 4, 4, 4, 5]) + 3.7 + >>> median_grouped([52, 52, 53, 54]) + 52.5 + + This calculates the median as the 50th percentile, and should be + used when your data is continuous and grouped. In the above example, + the values 1, 2, 3, etc. actually represent the midpoint of classes + 0.5-1.5, 1.5-2.5, 2.5-3.5, etc. The middle value falls somewhere in + class 3.5-4.5, and interpolation is used to estimate it. + + Optional argument ``interval`` represents the class interval, and + defaults to 1. Changing the class interval naturally will change the + interpolated 50th percentile value: + + >>> median_grouped([1, 3, 3, 5, 7], interval=1) + 3.25 + >>> median_grouped([1, 3, 3, 5, 7], interval=2) + 3.5 + + This function does not check whether the data points are at least + ``interval`` apart. + """ + data = sorted(data) + n = len(data) + if n == 0: + raise StatisticsError("no median for empty data") + elif n == 1: + return data[0] + # Find the value at the midpoint. Remember this corresponds to the + # centre of the class interval. + x = data[n//2] + for obj in (x, interval): + if isinstance(obj, (str, bytes)): + raise TypeError('expected number but got %r' % obj) + try: + L = x - interval/2 # The lower limit of the median interval. + except TypeError: + # Mixed type. For now we just coerce to float. + L = float(x) - float(interval)/2 + cf = data.index(x) # Number of values below the median interval. + # FIXME The following line could be more efficient for big lists. + f = data.count(x) # Number of data points in the median interval. + return L + interval*(n/2 - cf)/f + + +def mode(data): + """Return the most common data point from discrete or nominal data. + + ``mode`` assumes discrete data, and returns a single value. This is the + standard treatment of the mode as commonly taught in schools: + + >>> mode([1, 1, 2, 3, 3, 3, 3, 4]) + 3 + + This also works with nominal (non-numeric) data: + + >>> mode(["red", "blue", "blue", "red", "green", "red", "red"]) + 'red' + + If there is not exactly one most common value, ``mode`` will raise + StatisticsError. + """ + # Generate a table of sorted (value, frequency) pairs. + table = _counts(data) + if len(table) == 1: + return table[0][0] + elif table: + raise StatisticsError( + 'no unique mode; found %d equally common values' % len(table) + ) + else: + raise StatisticsError('no mode for empty data') + + +# === Measures of spread === + +# See http://mathworld.wolfram.com/Variance.html +# http://mathworld.wolfram.com/SampleVariance.html +# http://en.wikipedia.org/wiki/Algorithms_for_calculating_variance +# +# Under no circumstances use the so-called "computational formula for +# variance", as that is only suitable for hand calculations with a small +# amount of low-precision data. It has terrible numeric properties. +# +# See a comparison of three computational methods here: +# http://www.johndcook.com/blog/2008/09/26/comparing-three-methods-of-computing-standard-deviation/ + +def _ss(data, c=None): + """Return sum of square deviations of sequence data. + + If ``c`` is None, the mean is calculated in one pass, and the deviations + from the mean are calculated in a second pass. Otherwise, deviations are + calculated from ``c`` as given. Use the second case with care, as it can + lead to garbage results. + """ + if c is None: + c = mean(data) + T, total, count = _sum((x-c)**2 for x in data) + # The following sum should mathematically equal zero, but due to rounding + # error may not. + U, total2, count2 = _sum((x-c) for x in data) + assert T == U and count == count2 + total -= total2**2/len(data) + assert not total < 0, 'negative sum of square deviations: %f' % total + return (T, total) + + +def variance(data, xbar=None): + """Return the sample variance of data. + + data should be an iterable of Real-valued numbers, with at least two + values. The optional argument xbar, if given, should be the mean of + the data. If it is missing or None, the mean is automatically calculated. + + Use this function when your data is a sample from a population. To + calculate the variance from the entire population, see ``pvariance``. + + Examples: + + >>> data = [2.75, 1.75, 1.25, 0.25, 0.5, 1.25, 3.5] + >>> variance(data) + 1.3720238095238095 + + If you have already calculated the mean of your data, you can pass it as + the optional second argument ``xbar`` to avoid recalculating it: + + >>> m = mean(data) + >>> variance(data, m) + 1.3720238095238095 + + This function does not check that ``xbar`` is actually the mean of + ``data``. Giving arbitrary values for ``xbar`` may lead to invalid or + impossible results. + + Decimals and Fractions are supported: + + >>> from decimal import Decimal as D + >>> variance([D("27.5"), D("30.25"), D("30.25"), D("34.5"), D("41.75")]) + Decimal('31.01875') + + >>> from fractions import Fraction as F + >>> variance([F(1, 6), F(1, 2), F(5, 3)]) + Fraction(67, 108) + + """ + if iter(data) is data: + data = list(data) + n = len(data) + if n < 2: + raise StatisticsError('variance requires at least two data points') + T, ss = _ss(data, xbar) + return _convert(ss/(n-1), T) + + +def pvariance(data, mu=None): + """Return the population variance of ``data``. + + data should be an iterable of Real-valued numbers, with at least one + value. The optional argument mu, if given, should be the mean of + the data. If it is missing or None, the mean is automatically calculated. + + Use this function to calculate the variance from the entire population. + To estimate the variance from a sample, the ``variance`` function is + usually a better choice. + + Examples: + + >>> data = [0.0, 0.25, 0.25, 1.25, 1.5, 1.75, 2.75, 3.25] + >>> pvariance(data) + 1.25 + + If you have already calculated the mean of the data, you can pass it as + the optional second argument to avoid recalculating it: + + >>> mu = mean(data) + >>> pvariance(data, mu) + 1.25 + + This function does not check that ``mu`` is actually the mean of ``data``. + Giving arbitrary values for ``mu`` may lead to invalid or impossible + results. + + Decimals and Fractions are supported: + + >>> from decimal import Decimal as D + >>> pvariance([D("27.5"), D("30.25"), D("30.25"), D("34.5"), D("41.75")]) + Decimal('24.815') + + >>> from fractions import Fraction as F + >>> pvariance([F(1, 4), F(5, 4), F(1, 2)]) + Fraction(13, 72) + + """ + if iter(data) is data: + data = list(data) + n = len(data) + if n < 1: + raise StatisticsError('pvariance requires at least one data point') + ss = _ss(data, mu) + T, ss = _ss(data, mu) + return _convert(ss/n, T) + + +def stdev(data, xbar=None): + """Return the square root of the sample variance. + + See ``variance`` for arguments and other details. + + >>> stdev([1.5, 2.5, 2.5, 2.75, 3.25, 4.75]) + 1.0810874155219827 + + """ + var = variance(data, xbar) + try: + return var.sqrt() + except AttributeError: + return math.sqrt(var) + + +def pstdev(data, mu=None): + """Return the square root of the population variance. + + See ``pvariance`` for arguments and other details. + + >>> pstdev([1.5, 2.5, 2.5, 2.75, 3.25, 4.75]) + 0.986893273527251 + + """ + var = pvariance(data, mu) + try: + return var.sqrt() + except AttributeError: + return math.sqrt(var) diff --git a/tests/JankBench/scripts/itr_collect.py b/tests/JankBench/scripts/itr_collect.py new file mode 100755 index 0000000000000..76499a4c93626 --- /dev/null +++ b/tests/JankBench/scripts/itr_collect.py @@ -0,0 +1,154 @@ +#!/usr/bin/python + +import optparse +import sys +import sqlite3 +import scipy.stats +import numpy + +import adbutil +from devices import DEVICES + +DB_PATH="/data/data/com.android.benchmark/databases/BenchmarkResults" +OUT_PATH = "db/" + +QUERY_BAD_FRAME = ("select run_id, name, total_duration from ui_results " + "where total_duration >=12 order by run_id, name") +QUERY_PERCENT_JANK = ("select run_id, name, sum(jank_frame) as jank_count, count (*) as total " + "from ui_results group by run_id, name") + +class IterationResult: + def __init__(self): + self.durations = [] + self.jank_count = 0 + self.total_count = 0 + + +def get_scoremap(dbpath): + db = sqlite3.connect(dbpath) + rows = db.execute(QUERY_BAD_FRAME) + + scoremap = {} + for row in rows: + run_id = row[0] + name = row[1] + total_duration = row[2] + + if not run_id in scoremap: + scoremap[run_id] = {} + + if not name in scoremap[run_id]: + scoremap[run_id][name] = IterationResult() + + + scoremap[run_id][name].durations.append(float(total_duration)) + + for row in db.execute(QUERY_PERCENT_JANK): + run_id = row[0] + name = row[1] + jank_count = row[2] + total_count = row[3] + + if run_id in scoremap.keys() and name in scoremap[run_id].keys(): + scoremap[run_id][name].jank_count = long(jank_count) + scoremap[run_id][name].total_count = long(total_count) + + + db.close() + return scoremap + +def score_device(name, serial, pull = False, verbose = False): + dbpath = OUT_PATH + name + ".db" + + if pull: + adbutil.root(serial) + adbutil.pull(serial, DB_PATH, dbpath) + + scoremap = None + try: + scoremap = get_scoremap(dbpath) + except sqlite3.DatabaseError: + print "Database corrupt, fetching..." + adbutil.root(serial) + adbutil.pull(serial, DB_PATH, dbpath) + scoremap = get_scoremap(dbpath) + + per_test_score = {} + per_test_sample_count = {} + global_overall = {} + + for run_id in iter(scoremap): + overall = [] + if len(scoremap[run_id]) < 1: + if verbose: + print "Skipping short run %s" % run_id + continue + print "Run: %s" % run_id + for test in iter(scoremap[run_id]): + if verbose: + print "\t%s" % test + scores = [] + sample_count = 0 + res = scoremap[run_id][test] + stddev = numpy.std(res.durations) + mean = numpy.mean(res.durations) + sample_count = len(res.durations) + pj = 100 * res.jank_count / float(res.total_count) + score = stddev * mean *pj + if score == 0: + score = 1 + scores.append(score) + if verbose: + print "\tScore = %f x %f x %f = %f (%d samples)" % (stddev, mean, pj, score, len(res.durations)) + + geo_run = scipy.stats.gmean(scores) + if test not in per_test_score: + per_test_score[test] = [] + + if test not in per_test_sample_count: + per_test_sample_count[test] = [] + + per_test_score[test].append(geo_run) + per_test_sample_count[test].append(int(sample_count)) + overall.append(geo_run) + + if not verbose: + print "\t%s:\t%0.2f (%0.2f avg. sample count)" % (test, geo_run, sample_count) + else: + print "\tOverall:\t%0.2f (%0.2f avg. sample count)" % (geo_run, sample_count) + print "" + + global_overall[run_id] = scipy.stats.gmean(overall) + print "Run Overall: %f" % global_overall[run_id] + print "" + + print "" + print "Variability (CV) - %s:" % name + + for test in per_test_score: + print "\t%s:\t%0.2f%% (%0.2f avg sample count)" % (test, 100 * scipy.stats.variation(per_test_score[test]), numpy.mean(per_test_sample_count[test])) + + print "\tOverall: %0.2f%%" % (100 * scipy.stats.variation([x for x in global_overall.values()])) + print "" + +def parse_options(argv): + usage = 'Usage: %prog [options]' + desc = 'Example: %prog' + parser = optparse.OptionParser(usage=usage, description=desc) + parser.add_option("-p", dest='pull', action="store_true") + parser.add_option("-d", dest='device', action="store") + parser.add_option("-v", dest='verbose', action="store_true") + options, categories = parser.parse_args(argv[1:]) + return options + +def main(): + options = parse_options(sys.argv) + if options.device != None: + score_device(options.device, DEVICES[options.device], options.pull, options.verbose) + else: + for name, serial in DEVICES.iteritems(): + print "======== %s =========" % name + score_device(name, serial, options.pull, options.verbose) + +if __name__ == "__main__": + main() diff --git a/tests/JankBench/scripts/runall.py b/tests/JankBench/scripts/runall.py new file mode 100755 index 0000000000000..d9a06628f2b07 --- /dev/null +++ b/tests/JankBench/scripts/runall.py @@ -0,0 +1,65 @@ +#!/usr/bin/python + +import optparse +import sys +import time + +import adbutil +from devices import DEVICES + +def parse_options(argv): + usage = 'Usage: %prog [options]' + desc = 'Example: %prog' + parser = optparse.OptionParser(usage=usage, description=desc) + parser.add_option("-c", dest='clear', action="store_true") + parser.add_option("-d", dest='device', action="store",) + parser.add_option("-t", dest='trace', action="store_true") + options, categories = parser.parse_args(argv[1:]) + return (options, categories) + +def clear_data(device = None): + if device != None: + dev = DEVICES[device] + adbutil.root(dev) + adbutil.pm(dev, "clear", "com.android.benchmark") + else: + for name, dev in DEVICES.iteritems(): + print("Clearing " + name) + adbutil.root(dev) + adbutil.pm(dev, "clear", "com.android.benchmark") + +def start_device(name, dev): + print("Go " + name + "!") + try: + adbutil.am(dev, "force-stop", "com.android.benchmark") + adbutil.wake(dev) + adbutil.am(dev, "start", + ["-n", "\"com.android.benchmark/.app.RunLocalBenchmarksActivity\"", + "--eia", "\"com.android.benchmark.EXTRA_ENABLED_BENCHMARK_IDS\"", "\"0,1,2,3,4,5,6\"", + "--ei", "\"com.android.benchmark.EXTRA_RUN_COUNT\"", "\"5\""]) + except adbutil.AdbError: + print "Couldn't launch " + name + "." + +def start_benchmark(device, trace): + if device != None: + start_device(device, DEVICES[device]) + if trace: + time.sleep(3) + adbutil.trace(DEVICES[device]) + else: + if trace: + print("Note: -t only valid with -d, can't trace") + for name, dev in DEVICES.iteritems(): + start_device(name, dev) + +def main(): + options, categories = parse_options(sys.argv) + if options.clear: + print options.device + clear_data(options.device) + else: + start_benchmark(options.device, options.trace) + + +if __name__ == "__main__": + main()