OSDN Git Service

JankBench: make it build and run in Android build
authorJoel Fernandes <joelaf@google.com>
Thu, 26 Oct 2017 19:24:30 +0000 (12:24 -0700)
committerJoel Fernandes <joelaf@google.com>
Fri, 22 Dec 2017 20:23:30 +0000 (20:23 +0000)
JankBench is a tool heavily used for scheduler and graphics testing.
JankBench has been an android studio project and traditionally its APK
has been built outside of the Android tree using studio. This patch
makes it possible to build it using Android source tree without needing
studio.

Some library imports needed renaming and an xml file had a typo, also
resource IDs need to be 16-bits so I fixed that up. List fragments can't
be anonymous instantiations anymore so changed it to be non-anonymous.

Bug: 31544438
Test: Run all Jankbench benchmarks manually in the app.
Change-Id: Ib5e4351fcc72acdec20424ae30598c205e7803f7
Signed-off-by: Joel Fernandes <joelaf@google.com>
79 files changed:
tests/JankBench/Android.mk [new file with mode: 0644]
tests/JankBench/app/src/androidTest/java/com/android/benchmark/ApplicationTest.java [new file with mode: 0644]
tests/JankBench/app/src/main/AndroidManifest.xml [new file with mode: 0644]
tests/JankBench/app/src/main/java/com/android/benchmark/app/BenchmarkDashboardFragment.java [new file with mode: 0644]
tests/JankBench/app/src/main/java/com/android/benchmark/app/BenchmarkListAdapter.java [new file with mode: 0644]
tests/JankBench/app/src/main/java/com/android/benchmark/app/HomeActivity.java [new file with mode: 0644]
tests/JankBench/app/src/main/java/com/android/benchmark/app/PerfTimeline.java [new file with mode: 0644]
tests/JankBench/app/src/main/java/com/android/benchmark/app/RunLocalBenchmarksActivity.java [new file with mode: 0644]
tests/JankBench/app/src/main/java/com/android/benchmark/app/UiResultsFragment.java [new file with mode: 0644]
tests/JankBench/app/src/main/java/com/android/benchmark/registry/BenchmarkCategory.java [new file with mode: 0644]
tests/JankBench/app/src/main/java/com/android/benchmark/registry/BenchmarkGroup.java [new file with mode: 0644]
tests/JankBench/app/src/main/java/com/android/benchmark/registry/BenchmarkRegistry.java [new file with mode: 0644]
tests/JankBench/app/src/main/java/com/android/benchmark/results/GlobalResultsStore.java [new file with mode: 0644]
tests/JankBench/app/src/main/java/com/android/benchmark/results/UiBenchmarkResult.java [new file with mode: 0644]
tests/JankBench/app/src/main/java/com/android/benchmark/synthetic/MemoryActivity.java [new file with mode: 0644]
tests/JankBench/app/src/main/java/com/android/benchmark/synthetic/TestInterface.java [new file with mode: 0644]
tests/JankBench/app/src/main/java/com/android/benchmark/ui/BitmapUploadActivity.java [new file with mode: 0644]
tests/JankBench/app/src/main/java/com/android/benchmark/ui/EditTextInputActivity.java [new file with mode: 0644]
tests/JankBench/app/src/main/java/com/android/benchmark/ui/FullScreenOverdrawActivity.java [new file with mode: 0644]
tests/JankBench/app/src/main/java/com/android/benchmark/ui/ImageListViewScrollActivity.java [new file with mode: 0644]
tests/JankBench/app/src/main/java/com/android/benchmark/ui/ListActivityBase.java [new file with mode: 0644]
tests/JankBench/app/src/main/java/com/android/benchmark/ui/ListViewScrollActivity.java [new file with mode: 0644]
tests/JankBench/app/src/main/java/com/android/benchmark/ui/ShadowGridActivity.java [new file with mode: 0644]
tests/JankBench/app/src/main/java/com/android/benchmark/ui/TextScrollActivity.java [new file with mode: 0644]
tests/JankBench/app/src/main/java/com/android/benchmark/ui/Utils.java [new file with mode: 0644]
tests/JankBench/app/src/main/java/com/android/benchmark/ui/automation/Automator.java [new file with mode: 0644]
tests/JankBench/app/src/main/java/com/android/benchmark/ui/automation/CollectorThread.java [new file with mode: 0644]
tests/JankBench/app/src/main/java/com/android/benchmark/ui/automation/FrameTimingStats.java [new file with mode: 0644]
tests/JankBench/app/src/main/java/com/android/benchmark/ui/automation/Interaction.java [new file with mode: 0644]
tests/JankBench/app/src/main/jni/Android.mk [new file with mode: 0644]
tests/JankBench/app/src/main/jni/Application.mk [new file with mode: 0644]
tests/JankBench/app/src/main/jni/Bench.cpp [new file with mode: 0644]
tests/JankBench/app/src/main/jni/Bench.h [new file with mode: 0644]
tests/JankBench/app/src/main/jni/WorkerPool.cpp [new file with mode: 0644]
tests/JankBench/app/src/main/jni/WorkerPool.h [new file with mode: 0644]
tests/JankBench/app/src/main/jni/test.cpp [new file with mode: 0644]
tests/JankBench/app/src/main/res/drawable/ic_play.png [new file with mode: 0644]
tests/JankBench/app/src/main/res/drawable/img1.jpg [new file with mode: 0644]
tests/JankBench/app/src/main/res/drawable/img2.jpg [new file with mode: 0644]
tests/JankBench/app/src/main/res/drawable/img3.jpg [new file with mode: 0644]
tests/JankBench/app/src/main/res/drawable/img4.jpg [new file with mode: 0644]
tests/JankBench/app/src/main/res/layout/activity_bitmap_upload.xml [new file with mode: 0644]
tests/JankBench/app/src/main/res/layout/activity_home.xml [new file with mode: 0644]
tests/JankBench/app/src/main/res/layout/activity_list_fragment.xml [new file with mode: 0644]
tests/JankBench/app/src/main/res/layout/activity_memory.xml [new file with mode: 0644]
tests/JankBench/app/src/main/res/layout/activity_running_list.xml [new file with mode: 0644]
tests/JankBench/app/src/main/res/layout/benchmark_list_group_row.xml [new file with mode: 0644]
tests/JankBench/app/src/main/res/layout/benchmark_list_item.xml [new file with mode: 0644]
tests/JankBench/app/src/main/res/layout/card_row.xml [new file with mode: 0644]
tests/JankBench/app/src/main/res/layout/content_main.xml [new file with mode: 0644]
tests/JankBench/app/src/main/res/layout/fragment_dashboard.xml [new file with mode: 0644]
tests/JankBench/app/src/main/res/layout/fragment_ui_results_detail.xml [new file with mode: 0644]
tests/JankBench/app/src/main/res/layout/image_scroll_list_item.xml [new file with mode: 0644]
tests/JankBench/app/src/main/res/layout/results_list_item.xml [new file with mode: 0644]
tests/JankBench/app/src/main/res/layout/running_benchmark_list_item.xml [new file with mode: 0644]
tests/JankBench/app/src/main/res/menu/menu_main.xml [new file with mode: 0644]
tests/JankBench/app/src/main/res/menu/menu_memory.xml [new file with mode: 0644]
tests/JankBench/app/src/main/res/mipmap-hdpi/ic_launcher.png [new file with mode: 0644]
tests/JankBench/app/src/main/res/mipmap-mdpi/ic_launcher.png [new file with mode: 0644]
tests/JankBench/app/src/main/res/mipmap-xhdpi/ic_launcher.png [new file with mode: 0644]
tests/JankBench/app/src/main/res/mipmap-xxhdpi/ic_launcher.png [new file with mode: 0644]
tests/JankBench/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png [new file with mode: 0644]
tests/JankBench/app/src/main/res/values-v21/styles.xml [new file with mode: 0644]
tests/JankBench/app/src/main/res/values-w820dp/dimens.xml [new file with mode: 0644]
tests/JankBench/app/src/main/res/values/attrs.xml [new file with mode: 0644]
tests/JankBench/app/src/main/res/values/colors.xml [new file with mode: 0644]
tests/JankBench/app/src/main/res/values/dimens.xml [new file with mode: 0644]
tests/JankBench/app/src/main/res/values/ids.xml [new file with mode: 0644]
tests/JankBench/app/src/main/res/values/strings.xml [new file with mode: 0644]
tests/JankBench/app/src/main/res/values/styles.xml [new file with mode: 0644]
tests/JankBench/app/src/main/res/xml/benchmark.xml [new file with mode: 0644]
tests/JankBench/app/src/test/java/com/android/benchmark/ExampleUnitTest.java [new file with mode: 0644]
tests/JankBench/scripts/adbutil.py [new file with mode: 0644]
tests/JankBench/scripts/collect.py [new file with mode: 0755]
tests/JankBench/scripts/devices.py [new file with mode: 0644]
tests/JankBench/scripts/external/__init__.py [new file with mode: 0644]
tests/JankBench/scripts/external/statistics.py [new file with mode: 0644]
tests/JankBench/scripts/itr_collect.py [new file with mode: 0755]
tests/JankBench/scripts/runall.py [new file with mode: 0755]

diff --git a/tests/JankBench/Android.mk b/tests/JankBench/Android.mk
new file mode 100644 (file)
index 0000000..12568a0
--- /dev/null
@@ -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 (file)
index 0000000..79aff90
--- /dev/null
@@ -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 (file)
index 0000000..58aa66f
--- /dev/null
@@ -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 (file)
index 0000000..b0a97ae
--- /dev/null
@@ -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 (file)
index 0000000..7419b30
--- /dev/null
@@ -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 (file)
index 0000000..79bafd6
--- /dev/null
@@ -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 (file)
index 0000000..1c82d6d
--- /dev/null
@@ -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 (file)
index 0000000..7641d00
--- /dev/null
@@ -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 (file)
index 0000000..56e94d5
--- /dev/null
@@ -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 (file)
index 0000000..d91e579
--- /dev/null
@@ -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 (file)
index 0000000..4cb7716
--- /dev/null
@@ -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 (file)
index 0000000..89c6aed
--- /dev/null
@@ -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 (file)
index 0000000..5d0cba2
--- /dev/null
@@ -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 (file)
index 0000000..da6e05a
--- /dev/null
@@ -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 (file)
index 0000000..aba16d5
--- /dev/null
@@ -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 (file)
index 0000000..8f083a2
--- /dev/null
@@ -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 (file)
index 0000000..f6a528a
--- /dev/null
@@ -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 (file)
index 0000000..ea6fb58
--- /dev/null
@@ -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 (file)
index 0000000..95fce38
--- /dev/null
@@ -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 (file)
index 0000000..4644ea1
--- /dev/null
@@ -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 (file)
index 0000000..b973bc7
--- /dev/null
@@ -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 (file)
index 0000000..3ffb770
--- /dev/null
@@ -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 (file)
index 0000000..68f75a3
--- /dev/null
@@ -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 (file)
index 0000000..fcd168e
--- /dev/null
@@ -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 (file)
index 0000000..39f9206
--- /dev/null
@@ -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 (file)
index 0000000..1efd6bc
--- /dev/null
@@ -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 (file)
index 0000000..806c704
--- /dev/null
@@ -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 (file)
index 0000000..1fd0998
--- /dev/null
@@ -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 (file)
index 0000000..370fed2
--- /dev/null
@@ -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 (file)
index 0000000..8ba874d
--- /dev/null
@@ -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 (file)
index 0000000..09bc0ac
--- /dev/null
@@ -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 (file)
index 0000000..fbb4f11
--- /dev/null
@@ -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 (file)
index 0000000..43a9066
--- /dev/null
@@ -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 (file)
index 0000000..a92ac91
--- /dev/null
@@ -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 (file)
index 0000000..f8985d2
--- /dev/null
@@ -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 (file)
index 0000000..e163daa
--- /dev/null
@@ -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 (file)
index 0000000..13ed283
Binary files /dev/null and b/tests/JankBench/app/src/main/res/drawable/ic_play.png differ
diff --git a/tests/JankBench/app/src/main/res/drawable/img1.jpg b/tests/JankBench/app/src/main/res/drawable/img1.jpg
new file mode 100644 (file)
index 0000000..33c1fed
Binary files /dev/null and b/tests/JankBench/app/src/main/res/drawable/img1.jpg differ
diff --git a/tests/JankBench/app/src/main/res/drawable/img2.jpg b/tests/JankBench/app/src/main/res/drawable/img2.jpg
new file mode 100644 (file)
index 0000000..1ea97f2
Binary files /dev/null and b/tests/JankBench/app/src/main/res/drawable/img2.jpg differ
diff --git a/tests/JankBench/app/src/main/res/drawable/img3.jpg b/tests/JankBench/app/src/main/res/drawable/img3.jpg
new file mode 100644 (file)
index 0000000..ff99269
Binary files /dev/null and b/tests/JankBench/app/src/main/res/drawable/img3.jpg differ
diff --git a/tests/JankBench/app/src/main/res/drawable/img4.jpg b/tests/JankBench/app/src/main/res/drawable/img4.jpg
new file mode 100644 (file)
index 0000000..d9cbd2f
Binary files /dev/null and b/tests/JankBench/app/src/main/res/drawable/img4.jpg differ
diff --git a/tests/JankBench/app/src/main/res/layout/activity_bitmap_upload.xml b/tests/JankBench/app/src/main/res/layout/activity_bitmap_upload.xml
new file mode 100644 (file)
index 0000000..6b3c899
--- /dev/null
@@ -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 (file)
index 0000000..c4f4299
--- /dev/null
@@ -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 (file)
index 0000000..0aaadde
--- /dev/null
@@ -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 (file)
index 0000000..fd5cadc
--- /dev/null
@@ -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 (file)
index 0000000..7b7b930
--- /dev/null
@@ -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 (file)
index 0000000..5375dbc
--- /dev/null
@@ -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 (file)
index 0000000..5282e14
--- /dev/null
@@ -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 (file)
index 0000000..215f9df
--- /dev/null
@@ -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 (file)
index 0000000..201bd66
--- /dev/null
@@ -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 (file)
index 0000000..f3100c7
--- /dev/null
@@ -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 (file)
index 0000000..74d9891
--- /dev/null
@@ -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 (file)
index 0000000..c1662ea
--- /dev/null
@@ -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 (file)
index 0000000..f38b147
--- /dev/null
@@ -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 (file)
index 0000000..8a9d015
--- /dev/null
@@ -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 (file)
index 0000000..1633acd
--- /dev/null
@@ -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 (file)
index 0000000..f2df7c9
--- /dev/null
@@ -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 (file)
index 0000000..cde69bc
Binary files /dev/null and b/tests/JankBench/app/src/main/res/mipmap-hdpi/ic_launcher.png differ
diff --git a/tests/JankBench/app/src/main/res/mipmap-mdpi/ic_launcher.png b/tests/JankBench/app/src/main/res/mipmap-mdpi/ic_launcher.png
new file mode 100644 (file)
index 0000000..c133a0c
Binary files /dev/null and b/tests/JankBench/app/src/main/res/mipmap-mdpi/ic_launcher.png differ
diff --git a/tests/JankBench/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/tests/JankBench/app/src/main/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644 (file)
index 0000000..bfa42f0
Binary files /dev/null and b/tests/JankBench/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ
diff --git a/tests/JankBench/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/tests/JankBench/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644 (file)
index 0000000..324e72c
Binary files /dev/null and b/tests/JankBench/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ
diff --git a/tests/JankBench/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/tests/JankBench/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644 (file)
index 0000000..aee44e1
Binary files /dev/null and b/tests/JankBench/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ
diff --git a/tests/JankBench/app/src/main/res/values-v21/styles.xml b/tests/JankBench/app/src/main/res/values-v21/styles.xml
new file mode 100644 (file)
index 0000000..99ed094
--- /dev/null
@@ -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 (file)
index 0000000..e783e5d
--- /dev/null
@@ -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 (file)
index 0000000..a4286f1
--- /dev/null
@@ -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 (file)
index 0000000..59156ee
--- /dev/null
@@ -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 (file)
index 0000000..9da649a
--- /dev/null
@@ -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 (file)
index 0000000..6801fd9
--- /dev/null
@@ -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 (file)
index 0000000..270adf8
--- /dev/null
@@ -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 (file)
index 0000000..25ce730
--- /dev/null
@@ -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 (file)
index 0000000..07c453c
--- /dev/null
@@ -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 (file)
index 0000000..4464e87
--- /dev/null
@@ -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 (file)
index 0000000..ad9f7d9
--- /dev/null
@@ -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 (executable)
index 0000000..87a0594
--- /dev/null
@@ -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 (file)
index 0000000..c8266c0
--- /dev/null
@@ -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 (file)
index 0000000..e69de29
diff --git a/tests/JankBench/scripts/external/statistics.py b/tests/JankBench/scripts/external/statistics.py
new file mode 100644 (file)
index 0000000..518f546
--- /dev/null
@@ -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 (executable)
index 0000000..76499a4
--- /dev/null
@@ -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 (executable)
index 0000000..d9a0662
--- /dev/null
@@ -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()