PerformanceCollector: Collect & report perf measurements in key/value form

- Added new functions to PerformanceCollector and PerformanceResultsWriter
- Modified unit tests to test new functionality and fix flakiness reported in
  2218327 and 2118268
- Added PerformanceCollectorTest to small suite
diff --git a/core/java/android/os/PerformanceCollector.java b/core/java/android/os/PerformanceCollector.java
index 4ca1f32..be1cf6d 100644
--- a/core/java/android/os/PerformanceCollector.java
+++ b/core/java/android/os/PerformanceCollector.java
@@ -107,6 +107,36 @@
          * @see PerformanceCollector#stopTiming(String)
          */
         public void writeStopTiming(Bundle results);
+
+        /**
+         * Callback invoked as last action in
+         * {@link PerformanceCollector#addMeasurement(String, long)} for
+         * reporting an integer type measurement.
+         *
+         * @param label short description of the metric that was measured
+         * @param value long value of the measurement
+         */
+        public void writeMeasurement(String label, long value);
+
+        /**
+         * Callback invoked as last action in
+         * {@link PerformanceCollector#addMeasurement(String, float)} for
+         * reporting a float type measurement.
+         *
+         * @param label short description of the metric that was measured
+         * @param value float value of the measurement
+         */
+        public void writeMeasurement(String label, float value);
+
+        /**
+         * Callback invoked as last action in
+         * {@link PerformanceCollector#addMeasurement(String, String)} for
+         * reporting a string field.
+         *
+         * @param label short description of the metric that was measured
+         * @param value string summary of the measurement
+         */
+        public void writeMeasurement(String label, String value);
     }
 
     /**
@@ -385,6 +415,39 @@
         return mPerfMeasurement;
     }
 
+    /**
+     * Add an integer type measurement to the collector.
+     *
+     * @param label short description of the metric that was measured
+     * @param value long value of the measurement
+     */
+    public void addMeasurement(String label, long value) {
+        if (mPerfWriter != null)
+            mPerfWriter.writeMeasurement(label, value);
+    }
+
+    /**
+     * Add a float type measurement to the collector.
+     *
+     * @param label short description of the metric that was measured
+     * @param value float value of the measurement
+     */
+    public void addMeasurement(String label, float value) {
+        if (mPerfWriter != null)
+            mPerfWriter.writeMeasurement(label, value);
+    }
+
+    /**
+     * Add a string field to the collector.
+     *
+     * @param label short description of the metric that was measured
+     * @param value string summary of the measurement
+     */
+    public void addMeasurement(String label, String value) {
+        if (mPerfWriter != null)
+            mPerfWriter.writeMeasurement(label, value);
+    }
+
     /*
      * Starts tracking memory usage, binder transactions, and real & cpu timing.
      */
diff --git a/test-runner/android/test/InstrumentationTestRunner.java b/test-runner/android/test/InstrumentationTestRunner.java
index b9978d6..773d7a9 100644
--- a/test-runner/android/test/InstrumentationTestRunner.java
+++ b/test-runner/android/test/InstrumentationTestRunner.java
@@ -227,17 +227,22 @@
      */
     private static final String REPORT_KEY_COVERAGE_PATH = "coverageFilePath";
     /**
+     * If included at the start of reporting keys, this prefix marks the key as a performance
+     * metric.
+     */
+    private static final String REPORT_KEY_PREFIX = "performance.";
+    /**
      * If included in the status or final bundle sent to an IInstrumentationWatcher, this key
      * reports the cpu time in milliseconds of the current test.
      */
     private static final String REPORT_KEY_PERF_CPU_TIME =
-        "performance." + PerformanceCollector.METRIC_KEY_CPU_TIME;
+        REPORT_KEY_PREFIX + PerformanceCollector.METRIC_KEY_CPU_TIME;
     /**
      * If included in the status or final bundle sent to an IInstrumentationWatcher, this key
      * reports the run time in milliseconds of the current test.
      */
     private static final String REPORT_KEY_PERF_EXECUTION_TIME =
-        "performance." + PerformanceCollector.METRIC_KEY_EXECUTION_TIME;
+        REPORT_KEY_PREFIX + PerformanceCollector.METRIC_KEY_EXECUTION_TIME;
 
     /**
      * The test is starting.
@@ -739,11 +744,9 @@
         }
 
         public void writeEndSnapshot(Bundle results) {
-            // Copy all snapshot data fields as type long into mResults, which
-            // is outputted via Instrumentation.finish
-            for (String key : results.keySet()) {
-                mResults.putLong(key, results.getLong(key));
-            }
+            // Copy all snapshot data fields into mResults, which is outputted
+            // via Instrumentation.finish
+            mResults.putAll(results);
         }
 
         public void writeStartTiming(String label) {
@@ -768,6 +771,18 @@
             }
         }
 
+        public void writeMeasurement(String label, long value) {
+            mTestResult.putLong(REPORT_KEY_PREFIX + label, value);
+        }
+
+        public void writeMeasurement(String label, float value) {
+            mTestResult.putFloat(REPORT_KEY_PREFIX + label, value);
+        }
+
+        public void writeMeasurement(String label, String value) {
+            mTestResult.putString(REPORT_KEY_PREFIX + label, value);
+        }
+
         // TODO report the end of the cycle
     }
 }
diff --git a/tests/AndroidTests/src/com/android/unit_tests/os/PerformanceCollectorTest.java b/tests/AndroidTests/src/com/android/unit_tests/os/PerformanceCollectorTest.java
index d0fdff4..25b6e0e 100644
--- a/tests/AndroidTests/src/com/android/unit_tests/os/PerformanceCollectorTest.java
+++ b/tests/AndroidTests/src/com/android/unit_tests/os/PerformanceCollectorTest.java
@@ -19,8 +19,9 @@
 import android.os.Bundle;
 import android.os.Parcelable;
 import android.os.PerformanceCollector;
+import android.os.Process;
 import android.os.PerformanceCollector.PerformanceResultsWriter;
-import android.test.suitebuilder.annotation.LargeTest;
+import android.test.suitebuilder.annotation.SmallTest;
 
 import java.lang.reflect.Field;
 import java.util.ArrayList;
@@ -44,6 +45,7 @@
         mPerfCollector = null;
     }
 
+    @SmallTest
     public void testBeginSnapshotNoWriter() throws Exception {
         mPerfCollector.beginSnapshot("testBeginSnapshotNoWriter");
 
@@ -54,15 +56,16 @@
         assertEquals(2, snapshot.size());
     }
 
-    @LargeTest
+    @SmallTest
     public void testEndSnapshotNoWriter() throws Exception {
         mPerfCollector.beginSnapshot("testEndSnapshotNoWriter");
-        sleepForRandomLongPeriod();
+        workForRandomLongPeriod();
         Bundle snapshot = mPerfCollector.endSnapshot();
 
         verifySnapshotBundle(snapshot);
     }
 
+    @SmallTest
     public void testStartTimingNoWriter() throws Exception {
         mPerfCollector.startTiming("testStartTimingNoWriter");
 
@@ -73,21 +76,23 @@
         verifyTimingBundle(measurement, new ArrayList<String>());
     }
 
+    @SmallTest
     public void testAddIterationNoWriter() throws Exception {
         mPerfCollector.startTiming("testAddIterationNoWriter");
-        sleepForRandomTinyPeriod();
+        workForRandomTinyPeriod();
         Bundle iteration = mPerfCollector.addIteration("timing1");
 
         verifyIterationBundle(iteration, "timing1");
     }
 
+    @SmallTest
     public void testStopTimingNoWriter() throws Exception {
         mPerfCollector.startTiming("testStopTimingNoWriter");
-        sleepForRandomTinyPeriod();
+        workForRandomTinyPeriod();
         mPerfCollector.addIteration("timing2");
-        sleepForRandomTinyPeriod();
+        workForRandomTinyPeriod();
         mPerfCollector.addIteration("timing3");
-        sleepForRandomShortPeriod();
+        workForRandomShortPeriod();
         Bundle timing = mPerfCollector.stopTiming("timing4");
 
         ArrayList<String> labels = new ArrayList<String>();
@@ -97,6 +102,7 @@
         verifyTimingBundle(timing, labels);
     }
 
+    @SmallTest
     public void testBeginSnapshot() throws Exception {
         MockPerformanceResultsWriter writer = new MockPerformanceResultsWriter();
         mPerfCollector.setPerformanceResultsWriter(writer);
@@ -110,19 +116,20 @@
         assertEquals(2, snapshot.size());
     }
 
-    @LargeTest
+    @SmallTest
     public void testEndSnapshot() throws Exception {
         MockPerformanceResultsWriter writer = new MockPerformanceResultsWriter();
         mPerfCollector.setPerformanceResultsWriter(writer);
         mPerfCollector.beginSnapshot("testEndSnapshot");
-        sleepForRandomLongPeriod();
+        workForRandomLongPeriod();
         Bundle snapshot1 = mPerfCollector.endSnapshot();
         Bundle snapshot2 = writer.snapshotResults;
 
-        assertTrue(snapshot1.equals(snapshot2));
+        assertEqualsBundle(snapshot1, snapshot2);
         verifySnapshotBundle(snapshot1);
     }
 
+    @SmallTest
     public void testStartTiming() throws Exception {
         MockPerformanceResultsWriter writer = new MockPerformanceResultsWriter();
         mPerfCollector.setPerformanceResultsWriter(writer);
@@ -136,21 +143,23 @@
         verifyTimingBundle(measurement, new ArrayList<String>());
     }
 
+    @SmallTest
     public void testAddIteration() throws Exception {
         mPerfCollector.startTiming("testAddIteration");
-        sleepForRandomTinyPeriod();
+        workForRandomTinyPeriod();
         Bundle iteration = mPerfCollector.addIteration("timing5");
 
         verifyIterationBundle(iteration, "timing5");
     }
 
+    @SmallTest
     public void testStopTiming() throws Exception {
         mPerfCollector.startTiming("testStopTiming");
-        sleepForRandomTinyPeriod();
+        workForRandomTinyPeriod();
         mPerfCollector.addIteration("timing6");
-        sleepForRandomTinyPeriod();
+        workForRandomTinyPeriod();
         mPerfCollector.addIteration("timing7");
-        sleepForRandomShortPeriod();
+        workForRandomShortPeriod();
         Bundle timing = mPerfCollector.stopTiming("timing8");
 
         ArrayList<String> labels = new ArrayList<String>();
@@ -160,28 +169,90 @@
         verifyTimingBundle(timing, labels);
     }
 
-    // TODO: flaky test
-    // @LargeTest
+    @SmallTest
+    public void testAddMeasurementLong() throws Exception {
+        MockPerformanceResultsWriter writer = new MockPerformanceResultsWriter();
+        mPerfCollector.setPerformanceResultsWriter(writer);
+        mPerfCollector.startTiming("testAddMeasurementLong");
+        mPerfCollector.addMeasurement("testAddMeasurementLongZero", 0);
+        mPerfCollector.addMeasurement("testAddMeasurementLongPos", 348573);
+        mPerfCollector.addMeasurement("testAddMeasurementLongNeg", -19354);
+        mPerfCollector.stopTiming("");
+
+        assertEquals("testAddMeasurementLong", writer.timingLabel);
+        Bundle results = writer.timingResults;
+        assertEquals(4, results.size());
+        assertTrue(results.containsKey("testAddMeasurementLongZero"));
+        assertEquals(0, results.getLong("testAddMeasurementLongZero"));
+        assertTrue(results.containsKey("testAddMeasurementLongPos"));
+        assertEquals(348573, results.getLong("testAddMeasurementLongPos"));
+        assertTrue(results.containsKey("testAddMeasurementLongNeg"));
+        assertEquals(-19354, results.getLong("testAddMeasurementLongNeg"));
+    }
+
+    @SmallTest
+    public void testAddMeasurementFloat() throws Exception {
+        MockPerformanceResultsWriter writer = new MockPerformanceResultsWriter();
+        mPerfCollector.setPerformanceResultsWriter(writer);
+        mPerfCollector.startTiming("testAddMeasurementFloat");
+        mPerfCollector.addMeasurement("testAddMeasurementFloatZero", 0.0f);
+        mPerfCollector.addMeasurement("testAddMeasurementFloatPos", 348573.345f);
+        mPerfCollector.addMeasurement("testAddMeasurementFloatNeg", -19354.093f);
+        mPerfCollector.stopTiming("");
+
+        assertEquals("testAddMeasurementFloat", writer.timingLabel);
+        Bundle results = writer.timingResults;
+        assertEquals(4, results.size());
+        assertTrue(results.containsKey("testAddMeasurementFloatZero"));
+        assertEquals(0.0f, results.getFloat("testAddMeasurementFloatZero"));
+        assertTrue(results.containsKey("testAddMeasurementFloatPos"));
+        assertEquals(348573.345f, results.getFloat("testAddMeasurementFloatPos"));
+        assertTrue(results.containsKey("testAddMeasurementFloatNeg"));
+        assertEquals(-19354.093f, results.getFloat("testAddMeasurementFloatNeg"));
+    }
+
+    @SmallTest
+    public void testAddMeasurementString() throws Exception {
+        MockPerformanceResultsWriter writer = new MockPerformanceResultsWriter();
+        mPerfCollector.setPerformanceResultsWriter(writer);
+        mPerfCollector.startTiming("testAddMeasurementString");
+        mPerfCollector.addMeasurement("testAddMeasurementStringNull", null);
+        mPerfCollector.addMeasurement("testAddMeasurementStringEmpty", "");
+        mPerfCollector.addMeasurement("testAddMeasurementStringNonEmpty", "Hello World");
+        mPerfCollector.stopTiming("");
+
+        assertEquals("testAddMeasurementString", writer.timingLabel);
+        Bundle results = writer.timingResults;
+        assertEquals(4, results.size());
+        assertTrue(results.containsKey("testAddMeasurementStringNull"));
+        assertNull(results.getString("testAddMeasurementStringNull"));
+        assertTrue(results.containsKey("testAddMeasurementStringEmpty"));
+        assertEquals("", results.getString("testAddMeasurementStringEmpty"));
+        assertTrue(results.containsKey("testAddMeasurementStringNonEmpty"));
+        assertEquals("Hello World", results.getString("testAddMeasurementStringNonEmpty"));
+    }
+
+    @SmallTest
     public void testSimpleSequence() throws Exception {
         MockPerformanceResultsWriter writer = new MockPerformanceResultsWriter();
         mPerfCollector.setPerformanceResultsWriter(writer);
         mPerfCollector.beginSnapshot("testSimpleSequence");
         mPerfCollector.startTiming("testSimpleSequenceTiming");
-        sleepForRandomTinyPeriod();
+        workForRandomTinyPeriod();
         mPerfCollector.addIteration("iteration1");
-        sleepForRandomTinyPeriod();
+        workForRandomTinyPeriod();
         mPerfCollector.addIteration("iteration2");
-        sleepForRandomTinyPeriod();
+        workForRandomTinyPeriod();
         mPerfCollector.addIteration("iteration3");
-        sleepForRandomTinyPeriod();
+        workForRandomTinyPeriod();
         mPerfCollector.addIteration("iteration4");
-        sleepForRandomShortPeriod();
+        workForRandomShortPeriod();
         Bundle timing = mPerfCollector.stopTiming("iteration5");
-        sleepForRandomLongPeriod();
+        workForRandomLongPeriod();
         Bundle snapshot1 = mPerfCollector.endSnapshot();
         Bundle snapshot2 = writer.snapshotResults;
 
-        assertTrue(snapshot1.equals(snapshot2));
+        assertEqualsBundle(snapshot1, snapshot2);
         verifySnapshotBundle(snapshot1);
 
         ArrayList<String> labels = new ArrayList<String>();
@@ -193,60 +264,59 @@
         verifyTimingBundle(timing, labels);
     }
 
-    // TODO: flaky test
-    // @LargeTest
+    @SmallTest
     public void testLongSequence() throws Exception {
         MockPerformanceResultsWriter writer = new MockPerformanceResultsWriter();
         mPerfCollector.setPerformanceResultsWriter(writer);
         mPerfCollector.beginSnapshot("testLongSequence");
         mPerfCollector.startTiming("testLongSequenceTiming1");
-        sleepForRandomTinyPeriod();
+        workForRandomTinyPeriod();
         mPerfCollector.addIteration("iteration1");
-        sleepForRandomTinyPeriod();
+        workForRandomTinyPeriod();
         mPerfCollector.addIteration("iteration2");
-        sleepForRandomShortPeriod();
+        workForRandomShortPeriod();
         Bundle timing1 = mPerfCollector.stopTiming("iteration3");
-        sleepForRandomLongPeriod();
+        workForRandomLongPeriod();
 
         mPerfCollector.startTiming("testLongSequenceTiming2");
-        sleepForRandomTinyPeriod();
+        workForRandomTinyPeriod();
         mPerfCollector.addIteration("iteration4");
-        sleepForRandomTinyPeriod();
+        workForRandomTinyPeriod();
         mPerfCollector.addIteration("iteration5");
-        sleepForRandomShortPeriod();
+        workForRandomShortPeriod();
         Bundle timing2 = mPerfCollector.stopTiming("iteration6");
-        sleepForRandomLongPeriod();
+        workForRandomLongPeriod();
 
         mPerfCollector.startTiming("testLongSequenceTiming3");
-        sleepForRandomTinyPeriod();
+        workForRandomTinyPeriod();
         mPerfCollector.addIteration("iteration7");
-        sleepForRandomTinyPeriod();
+        workForRandomTinyPeriod();
         mPerfCollector.addIteration("iteration8");
-        sleepForRandomShortPeriod();
+        workForRandomShortPeriod();
         Bundle timing3 = mPerfCollector.stopTiming("iteration9");
-        sleepForRandomLongPeriod();
+        workForRandomLongPeriod();
 
         mPerfCollector.startTiming("testLongSequenceTiming4");
-        sleepForRandomTinyPeriod();
+        workForRandomTinyPeriod();
         mPerfCollector.addIteration("iteration10");
-        sleepForRandomTinyPeriod();
+        workForRandomTinyPeriod();
         mPerfCollector.addIteration("iteration11");
-        sleepForRandomShortPeriod();
+        workForRandomShortPeriod();
         Bundle timing4 = mPerfCollector.stopTiming("iteration12");
-        sleepForRandomLongPeriod();
+        workForRandomLongPeriod();
 
         mPerfCollector.startTiming("testLongSequenceTiming5");
-        sleepForRandomTinyPeriod();
+        workForRandomTinyPeriod();
         mPerfCollector.addIteration("iteration13");
-        sleepForRandomTinyPeriod();
+        workForRandomTinyPeriod();
         mPerfCollector.addIteration("iteration14");
-        sleepForRandomShortPeriod();
+        workForRandomShortPeriod();
         Bundle timing5 = mPerfCollector.stopTiming("iteration15");
-        sleepForRandomLongPeriod();
+        workForRandomLongPeriod();
         Bundle snapshot1 = mPerfCollector.endSnapshot();
         Bundle snapshot2 = writer.snapshotResults;
 
-        assertTrue(snapshot1.equals(snapshot2));
+        assertEqualsBundle(snapshot1, snapshot2);
         verifySnapshotBundle(snapshot1);
 
         ArrayList<String> labels1 = new ArrayList<String>();
@@ -280,57 +350,53 @@
      * Verify that snapshotting and timing do not interfere w/ each other,
      * by staggering calls to snapshot and timing functions.
      */
-    @LargeTest
+    @SmallTest
     public void testOutOfOrderSequence() {
         MockPerformanceResultsWriter writer = new MockPerformanceResultsWriter();
         mPerfCollector.setPerformanceResultsWriter(writer);
         mPerfCollector.startTiming("testOutOfOrderSequenceTiming");
-        sleepForRandomShortPeriod();
+        workForRandomShortPeriod();
         mPerfCollector.beginSnapshot("testOutOfOrderSequenceSnapshot");
-        sleepForRandomShortPeriod();
+        workForRandomShortPeriod();
         Bundle timing1 = mPerfCollector.stopTiming("timing1");
-        sleepForRandomShortPeriod();
+        workForRandomShortPeriod();
         Bundle snapshot1 = mPerfCollector.endSnapshot();
 
         Bundle timing2 = writer.timingResults;
         Bundle snapshot2 = writer.snapshotResults;
 
-        assertTrue(snapshot1.equals(snapshot2));
+        assertEqualsBundle(snapshot1, snapshot2);
         verifySnapshotBundle(snapshot1);
 
-        assertTrue(timing1.equals(timing2));
+        assertEqualsBundle(timing1, timing2);
         ArrayList<String> labels = new ArrayList<String>();
         labels.add("timing1");
         verifyTimingBundle(timing1, labels);
     }
 
-    private void sleepForRandomPeriod(int minDuration, int maxDuration) {
+    private void workForRandomPeriod(int minDuration, int maxDuration) {
         Random random = new Random();
         int period = minDuration + random.nextInt(maxDuration - minDuration);
-        int slept = 0;
-        // Generate random positive amount of work, so cpu time is measurable in
+        long start = Process.getElapsedCpuTime();
+        // Generate positive amount of work, so cpu time is measurable in
         // milliseconds
-        while (slept < period) {
-            int step = random.nextInt(minDuration/5);
-            try {
-                Thread.sleep(step);
-            } catch (InterruptedException e ) {
-                // eat the exception
+        while (Process.getElapsedCpuTime() - start < period) {
+            for (int i = 0, temp = 0; i < 50; i++ ) {
+                temp += i;
             }
-            slept += step;
         }
     }
 
-    private void sleepForRandomTinyPeriod() {
-        sleepForRandomPeriod(25, 50);
+    private void workForRandomTinyPeriod() {
+        workForRandomPeriod(2, 5);
     }
 
-    private void sleepForRandomShortPeriod() {
-        sleepForRandomPeriod(100, 250);
+    private void workForRandomShortPeriod() {
+        workForRandomPeriod(10, 25);
     }
 
-    private void sleepForRandomLongPeriod() {
-        sleepForRandomPeriod(500, 1000);
+    private void workForRandomLongPeriod() {
+        workForRandomPeriod(50, 100);
     }
 
     private void verifySnapshotBundle(Bundle snapshot) {
@@ -411,6 +477,13 @@
         }
     }
 
+    private void assertEqualsBundle(Bundle b1, Bundle b2) {
+        assertEquals(b1.keySet(), b2.keySet());
+        for (String key : b1.keySet()) {
+            assertEquals(b1.get(key), b2.get(key));
+        }
+    }
+
     private Object readPrivateField(String fieldName, Object object) throws Exception {
         Field f = object.getClass().getDeclaredField(fieldName);
         f.setAccessible(true);
@@ -429,7 +502,7 @@
         }
 
         public void writeEndSnapshot(Bundle results) {
-            snapshotResults = results;
+            snapshotResults.putAll(results);
         }
 
         public void writeStartTiming(String label) {
@@ -437,7 +510,19 @@
         }
 
         public void writeStopTiming(Bundle results) {
-            timingResults = results;
+            timingResults.putAll(results);
+        }
+
+        public void writeMeasurement(String label, long value) {
+            timingResults.putLong(label, value);
+        }
+
+        public void writeMeasurement(String label, float value) {
+            timingResults.putFloat(label, value);
+        }
+
+        public void writeMeasurement(String label, String value) {
+            timingResults.putString(label, value);
         }
     }
 }