Merge "JankBench: make it build and run in Android build"
diff --git a/tests/JankBench/Android.mk b/tests/JankBench/Android.mk
new file mode 100644
index 0000000..12568a0
--- /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 0000000..79aff90
--- /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;
+
+/**
+ * <a href="http://d.android.com/tools/testing/testing_android.html">Testing Fundamentals</a>
+ */
+public class ApplicationTest extends ApplicationTestCase<Application> {
+ 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 0000000..58aa66f
--- /dev/null
+++ b/tests/JankBench/app/src/main/AndroidManifest.xml
@@ -0,0 +1,62 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ 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.
+ ~
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.benchmark">
+
+ <uses-sdk android:minSdkVersion="24" />
+
+ <android:uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+ <android:uses-permission android:name="android.permission.READ_PHONE_STATE" />
+ <android:uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
+
+ <application
+ android:allowBackup="true"
+ android:icon="@mipmap/ic_launcher"
+ android:label="@string/app_name"
+ android:supportsRtl="true"
+ android:theme="@style/AppTheme">
+ <activity
+ android:name=".app.HomeActivity"
+ android:label="@string/app_name"
+ android:theme="@style/AppTheme.NoActionBar">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ <activity
+ android:name=".app.RunLocalBenchmarksActivity"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="com.android.benchmark.ACTION_BENCHMARK" />
+ </intent-filter>
+
+ <meta-data
+ android:name="com.android.benchmark.benchmark_group"
+ android:resource="@xml/benchmark" />
+ </activity>
+ <activity android:name=".ui.ListViewScrollActivity" />
+ <activity android:name=".ui.ImageListViewScrollActivity" />
+ <activity android:name=".ui.ShadowGridActivity" />
+ <activity android:name=".ui.TextScrollActivity" />
+ <activity android:name=".ui.EditTextInputActivity" />
+ <activity android:name=".synthetic.MemoryActivity" />
+ <activity android:name=".ui.FullScreenOverdrawActivity"></activity>
+ <activity android:name=".ui.BitmapUploadActivity"></activity>
+ </application>
+
+</manifest>
\ 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 0000000..b0a97ae0
--- /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 0000000..7419b30
--- /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 0000000..79bafd6
--- /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<Intent> 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<Void, Void, Void>() {
+ @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 0000000..1c82d6d
--- /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 0000000..7641d00
--- /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<LocalBenchmark> 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<LocalBenchmark> mBenchmarks;
+ private int mRunId;
+
+ public void setBenchmarks(ArrayList<LocalBenchmark> 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<String> 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<Void, Void, Integer>() {
+ @Override
+ protected Integer doInBackground(Void... voids) {
+ GlobalResultsStore gsr =
+ GlobalResultsStore.getInstance(RunLocalBenchmarksActivity.this);
+ ArrayList<Double> testLevelScores = new ArrayList<>();
+ final SummaryStatistics stats = new SummaryStatistics();
+ for (LocalBenchmark b : mBenchmarksToRun) {
+ HashMap<String, ArrayList<UiBenchmarkResult>> detailedResults =
+ gsr.loadDetailedResults(mCurrentRunId);
+ for (ArrayList<UiBenchmarkResult> 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 0000000..56e94d5
--- /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<UiBenchmarkResult> mResults = new ArrayList<>();
+
+ private AsyncTask<Void, Void, ArrayList<Map<String, String>>> mLoadScoresTask =
+ new AsyncTask<Void, Void, ArrayList<Map<String, String>>>() {
+ @Override
+ protected ArrayList<Map<String, String>> 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<Map<String, String>> dataMap = new ArrayList<>();
+ for (int i = 0; i < data.length - 1; i += 2) {
+ HashMap<String, String> map = new HashMap<>();
+ map.put("name", data[i]);
+ map.put("value", data[i + 1]);
+ dataMap.add(map);
+ }
+
+ return dataMap;
+ }
+
+ @Override
+ protected void onPostExecute(ArrayList<Map<String, String>> 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 0000000..d91e579
--- /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 0000000..4cb7716
--- /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 0000000..89c6aed
--- /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<BenchmarkGroup> 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<ResolveInfo> resolveInfos = pm.queryIntentActivities(intent,
+ PackageManager.GET_ACTIVITIES | PackageManager.GET_META_DATA);
+
+ for (ResolveInfo inf : resolveInfos) {
+ List<BenchmarkGroup> 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<BenchmarkGroup> parseBenchmarkGroup(ActivityInfo activityInfo) {
+ PackageManager pm = mContext.getPackageManager();
+
+ ComponentName componentName = new ComponentName(
+ activityInfo.packageName, activityInfo.name);
+
+ SparseArray<List<BenchmarkGroup.Benchmark>> 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<BenchmarkGroup.Benchmark> 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<BenchmarkGroup> result = new ArrayList<>();
+ Intent testIntent = getIntentFromInfo(activityInfo);
+ for (int i = 0; i < benchmarks.size(); i++) {
+ int cat = benchmarks.keyAt(i);
+ List<BenchmarkGroup.Benchmark> 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 0000000..5d0cba2
--- /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<UiBenchmarkResult> loadTestResults(String testName, int runId) {
+ SQLiteDatabase db = getReadableDatabase();
+ ArrayList<UiBenchmarkResult> 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<String, ArrayList<UiBenchmarkResult>> loadDetailedResults(int runId) {
+ SQLiteDatabase db = getReadableDatabase();
+ HashMap<String, ArrayList<UiBenchmarkResult>> 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<UiBenchmarkResult> 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<String, DescriptiveStatistics> 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<String, ArrayList<UiBenchmarkResult>> 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<UiBenchmarkResult> 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<String, ArrayList<UiBenchmarkResult>> 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<UiBenchmarkResult> 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 0000000..da6e05a
--- /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<FrameMetrics> instances) {
+ mStoredStatistics = new DescriptiveStatistics[METRICS.length];
+ insertMetrics(instances);
+ }
+
+ public UiBenchmarkResult(double[] values) {
+ mStoredStatistics = new DescriptiveStatistics[METRICS.length];
+ insertValues(values);
+ }
+
+ public void update(List<FrameMetrics> 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<Integer> 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<FrameMetrics> 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 0000000..aba16d5
--- /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 0000000..8f083a2
--- /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<Integer> mCommandQueue = new LinkedList<Integer>();
+
+ 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 0000000..f6a528a
--- /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 0000000..ea6fb58
--- /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 0000000..95fce38
--- /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 0000000..4644ea1
--- /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<View, BitmapWorkerTask> 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<Integer, Void, Bitmap> {
+ private final WeakReference<ImageView> 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 0000000..b973bc7
--- /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 0000000..3ffb770
--- /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<FrameMetrics> 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 0000000..68f75a3
--- /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 0000000..fcd168e
--- /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 0000000..39f9206
--- /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 0000000..1efd6bc
--- /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<FrameMetrics> metrics) {}
+ public void onPostAutomate() {}
+
+ protected final void addInteraction(Interaction interaction) {
+ if (mInteractions == null) {
+ return;
+ }
+
+ mInteractions.add(interaction);
+ }
+
+ protected final void setInteractions(List<Interaction> interactions) {
+ mInteractions = interactions;
+ }
+
+ private List<Interaction> 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<Interaction> 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<FrameMetrics> collectedStats = (List<FrameMetrics>)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<FrameMetrics> 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<FrameMetrics> 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 0000000..806c704
--- /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<FrameMetrics> mFrameTimingStats;
+ private long mLastFrameTime;
+ private WatchdogHandler mWatchdog;
+ private WeakReference<CollectorListener> mListener;
+
+ private volatile boolean mCollecting;
+
+
+ interface CollectorListener {
+ void onCollectorThreadReady();
+ void onPostInteraction(List<FrameMetrics> 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 0000000..1fd0998
--- /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 0000000..370fed2
--- /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<MotionEvent> 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<MotionEvent> 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<MotionEvent> createInterpolatedEventList(
+ float[] xPos, float[] yPos, long duration) {
+ long startTime = SystemClock.uptimeMillis() + 100;
+ List<MotionEvent> 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<Float> xPositions, List<Float> 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 0000000..8ba874de
--- /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 0000000..09bc0ac
--- /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 0000000..fbb4f11
--- /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 <android/log.h>
+#include <math.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#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 0000000..43a9066
--- /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 <pthread.h>
+
+#include "WorkerPool.h"
+
+#include <string.h>
+
+
+
+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 0000000..a92ac91
--- /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 <atomic>
+#include <stdlib.h>
+#include <unistd.h>
+#include <errno.h>
+#include <android/log.h>
+
+
+//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 0000000..f8985d2
--- /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 <pthread.h>
+#include <string.h>
+
+
+
+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 0000000..e163daa
--- /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 <stdlib.h>
+#include <stdio.h>
+//#include <fcntl.h>
+//#include <unistd.h>
+#include <math.h>
+#include <inttypes.h>
+#include <time.h>
+#include <android/log.h>
+
+#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 0000000..13ed283
--- /dev/null
+++ b/tests/JankBench/app/src/main/res/drawable/ic_play.png
Binary files 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 0000000..33c1fed
--- /dev/null
+++ b/tests/JankBench/app/src/main/res/drawable/img1.jpg
Binary files 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 0000000..1ea97f2
--- /dev/null
+++ b/tests/JankBench/app/src/main/res/drawable/img2.jpg
Binary files 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 0000000..ff99269
--- /dev/null
+++ b/tests/JankBench/app/src/main/res/drawable/img3.jpg
Binary files 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 0000000..d9cbd2f
--- /dev/null
+++ b/tests/JankBench/app/src/main/res/drawable/img4.jpg
Binary files 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 0000000..6b3c899
--- /dev/null
+++ b/tests/JankBench/app/src/main/res/layout/activity_bitmap_upload.xml
@@ -0,0 +1,52 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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
+ -->
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/upload_root"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:padding="10dp"
+ android:clipToPadding="false">
+ <android.support.v7.widget.CardView
+ android:layout_width="match_parent"
+ android:layout_height="0dp"
+ android:layout_weight="1">
+ <view class="com.android.benchmark.ui.BitmapUploadActivity$UploadView"
+ android:id="@+id/upload_view"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"/>
+ </android.support.v7.widget.CardView>
+
+ <android.support.v4.widget.Space
+ android:layout_height="10dp"
+ android:layout_width="match_parent" />
+
+ <android.support.v7.widget.CardView
+ android:layout_width="match_parent"
+ android:layout_height="0dp"
+ android:layout_weight="1" />
+
+ <android.support.v4.widget.Space
+ android:layout_height="10dp"
+ android:layout_width="match_parent" />
+
+ <android.support.v7.widget.CardView
+ android:layout_width="match_parent"
+ android:layout_height="0dp"
+ android:layout_weight="1" />
+</LinearLayout>
\ 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 0000000..c4f4299
--- /dev/null
+++ b/tests/JankBench/app/src/main/res/layout/activity_home.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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.
+ ~
+ -->
+
+<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:fitsSystemWindows="true"
+ tools:context=".app.HomeActivity">
+
+ <android.support.design.widget.AppBarLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:theme="@style/AppTheme.AppBarOverlay">
+
+ <android.support.v7.widget.Toolbar
+ android:id="@+id/toolbar"
+ android:layout_width="match_parent"
+ android:layout_height="?attr/actionBarSize"
+ android:background="?attr/colorPrimary"
+ app:popupTheme="@style/AppTheme.PopupOverlay" />
+
+ </android.support.design.widget.AppBarLayout>
+
+ <include layout="@layout/content_main" />
+
+</android.support.design.widget.CoordinatorLayout>
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 0000000..0aaadde
--- /dev/null
+++ b/tests/JankBench/app/src/main/res/layout/activity_list_fragment.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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.
+ ~
+ -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:paddingBottom="@dimen/activity_vertical_margin"
+ android:paddingLeft="@dimen/activity_horizontal_margin"
+ android:paddingRight="@dimen/activity_horizontal_margin"
+ android:paddingTop="@dimen/activity_vertical_margin"
+ tools:context=".app.HomeActivity"
+ android:orientation="vertical">
+
+ <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/list_fragment_container"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" />
+
+</LinearLayout>
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 0000000..fd5cadc
--- /dev/null
+++ b/tests/JankBench/app/src/main/res/layout/activity_memory.xml
@@ -0,0 +1,49 @@
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
+ android:layout_height="match_parent" android:paddingLeft="@dimen/activity_horizontal_margin"
+ android:paddingRight="@dimen/activity_horizontal_margin"
+ android:paddingTop="@dimen/activity_vertical_margin"
+ android:paddingBottom="@dimen/activity_vertical_margin"
+ tools:context="com.android.benchmark.synthetic.MemoryActivity">
+
+ <LinearLayout
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <TextView
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:textAppearance="?android:attr/textAppearanceLarge"
+ android:text="Large Text"
+ android:id="@+id/textView_status" />
+
+ <TextView
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:text=""
+ android:id="@+id/textView_min" />
+
+ <TextView
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:text=""
+ android:id="@+id/textView_max" />
+
+ <TextView
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:text=""
+ android:id="@+id/textView_typical" />
+
+ <view
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ class="com.android.benchmark.app.PerfTimeline"
+ android:id="@+id/mem_timeline" />
+
+ </LinearLayout>
+</RelativeLayout>
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 0000000..7b7b930
--- /dev/null
+++ b/tests/JankBench/app/src/main/res/layout/activity_running_list.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical" android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:paddingBottom="@dimen/activity_vertical_margin"
+ android:paddingLeft="@dimen/activity_horizontal_margin"
+ android:paddingRight="@dimen/activity_horizontal_margin"
+ android:paddingTop="@dimen/activity_vertical_margin"
+ tools:context=".app.HomeActivity"
+ android:orientation="vertical">
+
+ <TextView
+ android:id="@+id/score_text_view"
+ android:textSize="20sp"
+ android:textStyle="bold"
+ android:layout_width="match_parent"
+ android:layout_height="30dp"
+ />
+
+ <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/list_fragment_container"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" />
+
+ </LinearLayout>
+</LinearLayout>
\ 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 0000000..5375dbc
--- /dev/null
+++ b/tests/JankBench/app/src/main/res/layout/benchmark_list_group_row.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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.
+ ~
+ -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical" android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <TextView
+ android:id="@+id/group_name"
+ android:paddingLeft="?android:attr/expandableListPreferredItemPaddingLeft"
+ android:textSize="17dp"
+ android:paddingTop="10dp"
+ android:paddingBottom="10dp"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" />
+
+</LinearLayout>
\ 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 0000000..5282e14
--- /dev/null
+++ b/tests/JankBench/app/src/main/res/layout/benchmark_list_item.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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.
+ ~
+ -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="horizontal"
+ android:paddingLeft="?android:expandableListPreferredChildPaddingLeft"
+ android:layout_width="match_parent"
+ android:layout_height="55dip">
+
+
+ <CheckBox
+ android:id="@+id/benchmark_enable_checkbox"
+ android:checked="true"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+
+ <TextView
+ android:id="@+id/benchmark_name"
+ android:textSize="17dp"
+ android:paddingLeft="10dp"
+ android:paddingTop="10dp"
+ android:paddingBottom="10dp"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+
+</LinearLayout>
\ 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 0000000..215f9df
--- /dev/null
+++ b/tests/JankBench/app/src/main/res/layout/card_row.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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
+ -->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="horizontal"
+ android:layout_width="match_parent"
+ android:layout_height="100dp"
+ android:paddingStart="10dp"
+ android:paddingEnd="10dp"
+ android:paddingTop="5dp"
+ android:paddingBottom="5dp"
+ android:clipToPadding="false"
+ android:background="@null">
+ <android.support.v7.widget.CardView
+ android:layout_width="0dp"
+ android:layout_height="match_parent"
+ android:layout_weight="1">
+ <TextView
+ android:id="@+id/card_text"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"/>
+ </android.support.v7.widget.CardView>
+
+ <android.support.v4.widget.Space
+ android:layout_height="match_parent"
+ android:layout_width="10dp" />
+
+ <android.support.v7.widget.CardView
+ android:layout_width="0dp"
+ android:layout_height="match_parent"
+ android:layout_weight="1" />
+</LinearLayout>
\ 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 0000000..201bd66
--- /dev/null
+++ b/tests/JankBench/app/src/main/res/layout/content_main.xml
@@ -0,0 +1,26 @@
+<!--
+ ~ 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.
+ ~
+ -->
+
+<fragment xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:id="@+id/fragment_start_button"
+ android:name="com.android.benchmark.app.BenchmarkDashboardFragment"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ app:layout_behavior="@string/appbar_scrolling_view_behavior"
+ tools:layout="@layout/fragment_dashboard" />
+
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 0000000..f3100c7
--- /dev/null
+++ b/tests/JankBench/app/src/main/res/layout/fragment_dashboard.xml
@@ -0,0 +1,91 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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.
+ ~
+ -->
+
+<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:id="@+id/main_content"
+ android:layout_width="match_parent"
+ android:layout_height="fill_parent"
+ android:fitsSystemWindows="true">
+
+ <android.support.design.widget.AppBarLayout
+ android:id="@+id/appbar"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/detail_backdrop_height"
+ android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
+ android:fitsSystemWindows="true">
+
+ <android.support.design.widget.CollapsingToolbarLayout
+ android:id="@+id/collapsing_toolbar"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ app:layout_scrollFlags="scroll"
+ android:fitsSystemWindows="true"
+ app:contentScrim="?attr/colorPrimary"
+ app:expandedTitleMarginStart="48dp"
+ app:expandedTitleMarginEnd="64dp">
+
+ <android.support.v7.widget.Toolbar
+ android:id="@+id/toolbar"
+ android:layout_width="match_parent"
+ android:layout_height="?attr/actionBarSize"
+ app:popupTheme="@style/ThemeOverlay.AppCompat.Light"
+ app:layout_collapseMode="parallax" />
+
+ <ImageView
+ android:id="@+id/backdrop"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:src="@mipmap/ic_launcher"
+ android:scaleType="centerCrop"
+ android:fitsSystemWindows="true"
+ app:layout_collapseMode="parallax" />
+
+ </android.support.design.widget.CollapsingToolbarLayout>
+
+ </android.support.design.widget.AppBarLayout>
+
+ <android.support.v4.widget.NestedScrollView
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ app:layout_behavior="@string/appbar_scrolling_view_behavior">
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical" >
+
+ <ExpandableListView
+ android:id="@+id/test_list"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" />
+
+ </LinearLayout>
+
+ </android.support.v4.widget.NestedScrollView>
+
+ <android.support.design.widget.FloatingActionButton
+ android:id="@+id/start_button"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:src="@drawable/ic_play"
+ app:layout_anchor="@id/appbar"
+ app:layout_anchorGravity="bottom|right|end"
+ android:layout_margin="@dimen/fab_margin"
+ android:clickable="true"/>
+
+</android.support.design.widget.CoordinatorLayout>
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 0000000..74d9891
--- /dev/null
+++ b/tests/JankBench/app/src/main/res/layout/fragment_ui_results_detail.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical" android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <ExpandableListView
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" />
+
+</LinearLayout>
\ 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 0000000..c1662ea
--- /dev/null
+++ b/tests/JankBench/app/src/main/res/layout/image_scroll_list_item.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+ <ImageView
+ android:id="@+id/image_scroll_image"
+ android:scaleType="centerCrop"
+ android:layout_width="100dp"
+ android:layout_height="100dp" />
+
+ <TextView
+ android:id="@+id/image_scroll_text"
+ android:layout_gravity="right"
+ android:textSize="12sp"
+ android:paddingLeft="20dp"
+ android:layout_width="100dp"
+ android:layout_height="wrap_content" />
+</LinearLayout>
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 0000000..f38b147
--- /dev/null
+++ b/tests/JankBench/app/src/main/res/layout/results_list_item.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="horizontal" android:layout_width="match_parent"
+ android:padding="8dp"
+ android:layout_height="match_parent">
+
+ <TextView
+ android:id="@+id/result_name"
+ android:textSize="16sp"
+ android:layout_gravity="left"
+ android:layout_width="200dp"
+ android:layout_height="wrap_content" />
+
+ <TextView
+ android:id="@+id/result_value"
+ android:textSize="16sp"
+ android:layout_gravity="right"
+ android:layout_width="200dp"
+ android:layout_height="wrap_content" />
+
+</LinearLayout>
\ 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 0000000..8a9d015
--- /dev/null
+++ b/tests/JankBench/app/src/main/res/layout/running_benchmark_list_item.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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.
+ ~
+ -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical" android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <TextView
+ android:id="@+id/benchmark_name"
+ android:textSize="17sp"
+ android:paddingLeft="?android:listPreferredItemPaddingLeft"
+ android:paddingTop="10dp"
+ android:paddingBottom="10dp"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+
+</LinearLayout>
\ 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 0000000..1633acd
--- /dev/null
+++ b/tests/JankBench/app/src/main/res/menu/menu_main.xml
@@ -0,0 +1,26 @@
+<!--
+ ~ 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.
+ ~
+ -->
+
+<menu xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ xmlns:tools="http://schemas.android.com/tools"
+ tools:context=".app.HomeActivity">
+ <item
+ android:id="@+id/action_settings"
+ android:orderInCategory="100"
+ android:title="@string/action_export"
+ app:showAsAction="never" />
+</menu>
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 0000000..f2df7c9
--- /dev/null
+++ b/tests/JankBench/app/src/main/res/menu/menu_memory.xml
@@ -0,0 +1,5 @@
+<menu xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools" tools:context="com.android.benchmark.Memory">
+ <item android:id="@+id/action_settings" android:title="@string/action_export"
+ android:orderInCategory="100" />
+</menu>
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 0000000..cde69bc
--- /dev/null
+++ b/tests/JankBench/app/src/main/res/mipmap-hdpi/ic_launcher.png
Binary files 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 0000000..c133a0c
--- /dev/null
+++ b/tests/JankBench/app/src/main/res/mipmap-mdpi/ic_launcher.png
Binary files 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 0000000..bfa42f0
--- /dev/null
+++ b/tests/JankBench/app/src/main/res/mipmap-xhdpi/ic_launcher.png
Binary files 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 0000000..324e72c
--- /dev/null
+++ b/tests/JankBench/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
Binary files 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 0000000..aee44e1
--- /dev/null
+++ b/tests/JankBench/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
Binary files 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 0000000..99ed094
--- /dev/null
+++ b/tests/JankBench/app/src/main/res/values-v21/styles.xml
@@ -0,0 +1,25 @@
+<!--
+ ~ 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.
+ ~
+ -->
+
+<resources>
+
+ <style name="AppTheme.NoActionBar">
+ <item name="windowActionBar">false</item>
+ <item name="windowNoTitle">true</item>
+ <item name="android:windowDrawsSystemBarBackgrounds">true</item>
+ <item name="android:statusBarColor">@android:color/transparent</item>
+ </style>
+</resources>
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 0000000..e783e5d
--- /dev/null
+++ b/tests/JankBench/app/src/main/res/values-w820dp/dimens.xml
@@ -0,0 +1,22 @@
+<!--
+ ~ 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.
+ ~
+ -->
+
+<resources>
+ <!-- Example customization of dimensions originally defined in res/values/dimens.xml
+ (such as screen margins) for screens with more than 820dp of available width. This
+ would include 7" and 10" devices in landscape (~960dp and ~1280dp respectively). -->
+ <dimen name="activity_horizontal_margin">64dp</dimen>
+</resources>
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 0000000..a4286f1
--- /dev/null
+++ b/tests/JankBench/app/src/main/res/values/attrs.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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.
+ ~
+ -->
+
+<resources>
+ <!-- Root tag for benchmarks -->
+ <declare-styleable name="AndroidBenchmarks" />
+
+ <declare-styleable name="BenchmarkGroup">
+ <attr name="name" format="reference|string" />
+ <attr name="description" format="reference|string" />
+ </declare-styleable>
+
+ <declare-styleable name="Benchmark">
+ <attr name="name" />
+ <attr name="description" />
+ <attr name="id" format="reference" />
+ <attr name="category" format="enum">
+ <enum name="generic" value="0" />
+ <enum name="ui" value="1" />
+ <enum name="compute" value="2" />
+ </attr>
+ </declare-styleable>
+
+ <declare-styleable name="PerfTimeline"><attr name="exampleString" format="string"/>
+ <attr name="exampleDimension" format="dimension"/>
+ <attr name="exampleColor" format="color"/>
+ <attr name="exampleDrawable" format="color|reference"/>
+ </declare-styleable>
+
+</resources>
+
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 0000000..59156ee
--- /dev/null
+++ b/tests/JankBench/app/src/main/res/values/colors.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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.
+ ~
+ -->
+
+<resources>
+ <color name="colorPrimary">#3F51B5</color>
+ <color name="colorPrimaryDark">#303F9F</color>
+ <color name="colorAccent">#FF4081</color>
+</resources>
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 0000000..9da649a
--- /dev/null
+++ b/tests/JankBench/app/src/main/res/values/dimens.xml
@@ -0,0 +1,27 @@
+<!--
+ ~ 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.
+ ~
+ -->
+
+<resources>
+ <!-- Default screen margins, per the Android Design guidelines. -->
+ <dimen name="detail_backdrop_height">256dp</dimen>
+ <dimen name="card_margin">16dp</dimen>
+ <dimen name="activity_horizontal_margin">16dp</dimen>
+ <dimen name="activity_vertical_margin">16dp</dimen>
+ <dimen name="fab_margin">16dp</dimen>
+ <dimen name="app_bar_height">200dp</dimen>
+ <dimen name="item_width">200dp</dimen>
+ <dimen name="text_margin">16dp</dimen>
+</resources>
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 0000000..6801fd9
--- /dev/null
+++ b/tests/JankBench/app/src/main/res/values/ids.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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.
+ ~
+ -->
+
+<resources>
+ <item name="benchmark_list_view_scroll" type="id" />
+ <item name="benchmark_image_list_view_scroll" type="id" />
+ <item name="benchmark_shadow_grid" type="id" />
+ <item name="benchmark_text_high_hitrate" type="id" />
+ <item name="benchmark_text_low_hitrate" type="id" />
+ <item name="benchmark_edit_text_input" type="id" />
+ <item name="benchmark_overdraw" type="id" />
+ <item name="benchmark_memory_bandwidth" type="id" />
+ <item name="benchmark_memory_latency" type="id" />
+ <item name="benchmark_power_management" type="id" />
+ <item name="benchmark_cpu_heat_soak" type="id" />
+ <item name="benchmark_cpu_gflops" type="id" />
+</resources>
\ 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 0000000..270adf8
--- /dev/null
+++ b/tests/JankBench/app/src/main/res/values/strings.xml
@@ -0,0 +1,51 @@
+<!--
+ ~ 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.
+ ~
+ -->
+
+<resources>
+ <string name="app_name">Benchmark</string>
+
+ <string name="action_export">Export to CSV</string>
+
+ <string name="list_view_scroll_name">List View Fling</string>
+ <string name="list_view_scroll_description">Tests list view fling performance</string>
+ <string name="image_list_view_scroll_name">Image List View Fling</string>
+ <string name="image_list_view_scroll_description">Tests list view fling performance with images</string>
+ <string name="shadow_grid_name">Shadow Grid Fling</string>
+ <string name="shadow_grid_description">Tests shadow grid fling performance with images</string>
+ <string name="text_high_hitrate_name">High-hitrate text render</string>
+ <string name="text_high_hitrate_description">Tests high hitrate text rendering</string>
+ <string name="text_low_hitrate_name">Low-hitrate text render</string>
+ <string name="text_low_hitrate_description">Tests low-hitrate text rendering</string>
+ <string name="edit_text_input_name">Edit Text Input</string>
+ <string name="edit_text_input_description">Tests edit text input</string>
+ <string name="overdraw_name">Overdraw Test</string>
+ <string name="overdraw_description">Tests how the device handles overdraw</string>
+ <string name="memory_bandwidth_name">Memory Bandwidth</string>
+ <string name="memory_bandwidth_description">Test device\'s memory bandwidth</string>
+ <string name="memory_latency_name">Memory Latency</string>
+ <string name="memory_latency_description">Test device\'s memory latency</string>
+ <string name="power_management_name">Power Management</string>
+ <string name="power_management_description">Test device\'s power management</string>
+ <string name="cpu_heat_soak_name">CPU Heat Soak</string>
+ <string name="cpu_heat_soak_description">How hot can we make it?</string>
+ <string name="cpu_gflops_name">CPU GFlops</string>
+ <string name="cpu_gflops_description">How many gigaflops can the device attain?</string>
+
+ <string name="benchmark_category_ui">UI</string>
+ <string name="benchmark_category_compute">Compute</string>
+ <string name="benchmark_category_generic">Generic</string>
+ <string name="title_activity_image_list_view_scroll">ImageListViewScroll</string>
+</resources>
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 0000000..25ce730
--- /dev/null
+++ b/tests/JankBench/app/src/main/res/values/styles.xml
@@ -0,0 +1,43 @@
+<!--
+ ~ 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.
+ ~
+ -->
+
+<resources>
+
+ <!-- Base application theme. -->
+ <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
+ <!-- Customize your theme here. -->
+ <item name="colorPrimary">@color/colorPrimary</item>
+ <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
+ <item name="colorAccent">@color/colorAccent</item>
+ </style>
+
+ <style name="AppTheme.NoActionBar">
+ <item name="windowActionBar">false</item>
+ <item name="windowNoTitle">true</item>
+ </style>
+
+ <style name="AppTheme.AppBarOverlay" parent="ThemeOverlay.AppCompat.Dark.ActionBar" />
+
+ <style name="AppTheme.PopupOverlay" parent="ThemeOverlay.AppCompat.Light" />
+
+ <style name="Widget.CardContent" parent="android:Widget">
+ <item name="android:paddingLeft">16dp</item>
+ <item name="android:paddingRight">16dp</item>
+ <item name="android:paddingTop">24dp</item>
+ <item name="android:paddingBottom">24dp</item>
+ <item name="android:orientation">vertical</item>
+ </style>
+</resources>
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 0000000..07c453c
--- /dev/null
+++ b/tests/JankBench/app/src/main/res/xml/benchmark.xml
@@ -0,0 +1,97 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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.
+ ~
+ -->
+
+<com.android.benchmark.BenchmarkGroup
+ xmlns:benchmark="http://schemas.android.com/apk/res-auto"
+ benchmark:description="Benchmarks of the Android system"
+ benchmark:name="Android Benchmarks">
+
+ <com.android.benchmark.Benchmark
+ benchmark:name="@string/list_view_scroll_name"
+ benchmark:id="@id/benchmark_list_view_scroll"
+ benchmark:category="ui"
+ benchmark:description="@string/list_view_scroll_description" />
+
+ <com.android.benchmark.Benchmark
+ benchmark:name="@string/image_list_view_scroll_name"
+ benchmark:id="@id/benchmark_image_list_view_scroll"
+ benchmark:category="ui"
+ benchmark:description="@string/image_list_view_scroll_description" />
+
+ <com.android.benchmark.Benchmark
+ benchmark:name="@string/shadow_grid_name"
+ benchmark:id="@id/benchmark_shadow_grid"
+ benchmark:category="ui"
+ benchmark:description="@string/shadow_grid_description" />
+
+ <com.android.benchmark.Benchmark
+ benchmark:name="@string/text_low_hitrate_name"
+ benchmark:id="@id/benchmark_text_low_hitrate"
+ benchmark:category="ui"
+ benchmark:description="@string/text_low_hitrate_description" />
+
+ <com.android.benchmark.Benchmark
+ benchmark:name="@string/text_high_hitrate_name"
+ benchmark:id="@id/benchmark_text_high_hitrate"
+ benchmark:category="ui"
+ benchmark:description="@string/text_high_hitrate_description" />
+
+ <com.android.benchmark.Benchmark
+ benchmark:name="@string/edit_text_input_name"
+ benchmark:id="@id/benchmark_edit_text_input"
+ benchmark:category="ui"
+ benchmark:description="@string/edit_text_input_description" />
+
+ <com.android.benchmark.Benchmark
+ benchmark:name="@string/overdraw_name"
+ benchmark:id="@id/benchmark_overdraw"
+ benchmark:category="ui"
+ benchmark:description="@string/overdraw_description" />
+
+ <!--
+ <com.android.benchmark.Benchmark
+ benchmark:name="@string/memory_bandwidth_name"
+ benchmark:id="@id/benchmark_memory_bandwidth"
+ benchmark:category="compute"
+ benchmark:description="@string/memory_bandwidth_description" />
+
+ <com.android.benchmark.Benchmark
+ benchmark:name="@string/memory_latency_name"
+ benchmark:id="@id/benchmark_memory_latency"
+ benchmark:category="compute"
+ benchmark:description="@string/memory_latency_description" />
+
+ <com.android.benchmark.Benchmark
+ benchmark:name="@string/power_management_name"
+ benchmark:id="@id/benchmark_power_management"
+ benchmark:category="compute"
+ benchmark:description="@string/power_management_description" />
+
+ <com.android.benchmark.Benchmark
+ benchmark:name="@string/cpu_heat_soak_name"
+ benchmark:id="@id/benchmark_cpu_heat_soak"
+ benchmark:category="compute"
+ benchmark:description="@string/cpu_heat_soak_description" />
+
+ <com.android.benchmark.Benchmark
+ benchmark:name="@string/cpu_gflops_name"
+ benchmark:id="@id/benchmark_cpu_gflops"
+ benchmark:category="compute"
+ benchmark:description="@string/cpu_gflops_description" />
+ -->
+
+</com.android.benchmark.BenchmarkGroup>
\ 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 0000000..4464e87
--- /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 0000000..ad9f7d9
--- /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 0000000..87a0594
--- /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 0000000..c8266c0
--- /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 0000000..e69de29
--- /dev/null
+++ b/tests/JankBench/scripts/external/__init__.py
diff --git a/tests/JankBench/scripts/external/statistics.py b/tests/JankBench/scripts/external/statistics.py
new file mode 100644
index 0000000..518f546
--- /dev/null
+++ b/tests/JankBench/scripts/external/statistics.py
@@ -0,0 +1,638 @@
+## Module statistics.py
+##
+## Copyright (c) 2013 Steven D'Aprano <steve+python@pearwood.info>.
+##
+## 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)
+ (<class 'float'>, Fraction(11, 1), 5)
+
+ Some sources of round-off error will be avoided:
+
+ >>> _sum([1e50, 1, -1e50] * 1000) # Built-in sum returns zero.
+ (<class 'float'>, 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)])
+ (<class 'fractions.Fraction'>, 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)
+ (<class 'decimal.Decimal'>, 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 0000000..76499a4
--- /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 0000000..d9a0662
--- /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()