[autotest] telemetry_Benchmarks upload perf data using output_perf_value

telemetry_Benchmarks now upload perf data using output_perf_value.
It will not write to the keyval file anymore.

After the change lands, we should stop the scripts extract_perf.py
and generate_perf_graphs that are currently kicked off
by cron to upload the perf data.

Also makes output_perf_value accept empty unit.

CQ-DEPEND=I0b746f4ab3162eb7ba4aefa12294b7a60e326513
TEST=1)Locally kicked off the tests from the AFE,
confirmed that the perf data was uploaded to the testing
perf dashboard.
2)test_unittest.py telemetry_runner_unittest.py
BUG=chromium:280634

Change-Id: Icf6dca08cab0268b41f8359eb73084dfee3e4b9d
Reviewed-on: https://chromium-review.googlesource.com/176746
Reviewed-by: Fang Deng <fdeng@chromium.org>
Tested-by: Fang Deng <fdeng@chromium.org>
Commit-Queue: Fang Deng <fdeng@chromium.org>
diff --git a/client/common_lib/test.py b/client/common_lib/test.py
index 9f6d20d..31e4cbc 100644
--- a/client/common_lib/test.py
+++ b/client/common_lib/test.py
@@ -86,7 +86,7 @@
         return new_dict
 
 
-    def output_perf_value(self, description, value, units,
+    def output_perf_value(self, description, value, units=None,
                           higher_is_better=True, graph=None):
         """
         Records a measured performance value in an output file.
@@ -124,10 +124,11 @@
             raise ValueError('The units must be at most 32 characters.')
         string_regex = re.compile(r'^[-\.\w]+$')
         if (not string_regex.search(description) or
-            not string_regex.search(units)):
+            (units and not string_regex.search(units))):
             raise ValueError('Invalid description or units string. May only '
                              'contain letters, numbers, periods, dashes, and '
-                             'underscores.')
+                             'underscores. description: %s, units: %s' %
+                             (description, units))
 
         entry = {
             'description': description,
diff --git a/server/cros/telemetry_runner.py b/server/cros/telemetry_runner.py
index ddffd80..82429af 100644
--- a/server/cros/telemetry_runner.py
+++ b/server/cros/telemetry_runner.py
@@ -30,10 +30,6 @@
                              '(?P<VALUE>[\{\[]?[-\d\., ]+[\}\]]?)('
                              ' ?(?P<UNITS>.+))?')
 
-# Constants pertaining to perf keys generated from Telemetry test results.
-PERF_KEY_TELEMETRY_PREFIX = 'TELEMETRY'
-PERF_KEY_DELIMITER = '--'
-
 
 class TelemetryResult(object):
     """Class to represent the results of a telemetry run.
@@ -55,7 +51,10 @@
         else:
             self.status = FAILED_STATUS
 
-        self.perf_keyvals = {}
+        # A list of perf values, e.g.
+        # [{'graph': 'graphA', 'trace': 'page_load_time',
+        #   'units': 'secs', 'value':0.5}, ...]
+        self.perf_data = []
         self._stdout = stdout
         self._stderr = stderr
         self.output = '\n'.join([stdout, stderr])
@@ -172,13 +171,16 @@
                 # In this example, we'd get 34.2.
                 value_list = [float(x) for x in value.strip('{},').split(',')]
                 value = value_list[0]  # Position 0 is the value.
+            elif re.search('^\d+$', value):
+                value = int(value)
+            else:
+                value = float(value)
 
-            perf_key = PERF_KEY_DELIMITER.join(
-                    [PERF_KEY_TELEMETRY_PREFIX, graph_name, trace_name, units])
-            self.perf_keyvals[perf_key] = str(value)
+            self.perf_data.append({'graph':graph_name, 'trace': trace_name,
+                                   'units': units, 'value': value})
 
         pp = pprint.PrettyPrinter(indent=2)
-        logging.debug('Perf Keyvals: %s', pp.pformat(self.perf_keyvals))
+        logging.debug('Perf values: %s', pp.pformat(self.perf_data))
 
         if self.status is SUCCESS_STATUS:
             return
@@ -312,14 +314,38 @@
         return self._run_test(TELEMETRY_RUN_CROS_TESTS_SCRIPT, test)
 
 
-    def run_telemetry_benchmark(self, benchmark, keyval_writer=None):
+    @staticmethod
+    def _output_perf_value(perf_value_writer, perf_data):
+        """Output perf values to result dir.
+
+        The perf values will be output to the result dir and
+        be subsequently uploaded to perf dashboard.
+
+        @param perf_value_writer: Should be an instance with the function
+                                  output_perf_value(), if None, no perf value
+                                  will be written. Typically this will be the
+                                  job object from an autotest test.
+        @param perf_data: A list of perf values, each value is
+                          a dictionary that looks like
+                          {'graph':'GraphA', 'trace':'metric1',
+                           'units':'secs', 'value':0.5}
+        """
+        for perf_value in perf_data:
+            perf_value_writer.output_perf_value(
+                    description=perf_value['trace'],
+                    value=perf_value['value'],
+                    units=perf_value['units'],
+                    graph=perf_value['graph'])
+
+
+    def run_telemetry_benchmark(self, benchmark, perf_value_writer=None):
         """Runs a telemetry benchmark on a dut.
 
         @param benchmark: Benchmark we want to run.
-        @param keyval_writer: Should be a instance with the function
-                              write_perf_keyval(), if None, no keyvals will be
-                              written. Typically this will be the job object
-                              from a autotest test.
+        @param perf_value_writer: Should be an instance with the function
+                                  output_perf_value(), if None, no perf value
+                                  will be written. Typically this will be the
+                                  job object from an autotest test.
 
         @returns A TelemetryResult Instance with the results of this telemetry
                  execution.
@@ -330,8 +356,8 @@
         result = self._run_telemetry(telemetry_script, benchmark)
         result.parse_benchmark_results()
 
-        if keyval_writer:
-            keyval_writer.write_perf_keyval(result.perf_keyvals)
+        if perf_value_writer:
+            self._output_perf_value(perf_value_writer, result.perf_data)
 
         if result.status is WARNING_STATUS:
             raise error.TestWarn('Telemetry Benchmark: %s'
diff --git a/server/cros/telemetry_runner_unittest.py b/server/cros/telemetry_runner_unittest.py
index 2d1c679..d5376dd 100644
--- a/server/cros/telemetry_runner_unittest.py
+++ b/server/cros/telemetry_runner_unittest.py
@@ -21,12 +21,16 @@
         '[614,527,523,471,530,523,577,625,614,538] ms\n'
         'RESULT graph_name: test_name= {3.14, 0.98} units')
 
-    EXPECTED_KEYVALS = {
-        'TELEMETRY--average_commit_time_by_url--http___www.ebay.com--ms':
-            '8.86528',
-        'TELEMETRY--CodeLoad--CodeLoad--score__bigger_is_better_': '6343',
-        'TELEMETRY--ai-astar--ai-astar--ms': '554.2',
-        'TELEMETRY--graph_name--test_name--units': '3.14'}
+    EXPECTED_PERF_DATA = [
+        {'graph': 'average_commit_time_by_url', 'trace': 'http___www.ebay.com',
+         'units': 'ms', 'value': 8.86528},
+        {'graph': 'CodeLoad', 'trace': 'CodeLoad',
+         'units': 'score__bigger_is_better_', 'value': 6343},
+        {'graph': 'ai-astar', 'trace': 'ai-astar',
+         'units': 'ms', 'value': 554.2},
+        {'graph': 'graph_name', 'trace': 'test_name',
+         'units': 'units', 'value': 3.14}]
+
 
     def testEmptyStdout(self):
         """Test when the test exits with 0 but there is no output."""
@@ -41,7 +45,7 @@
                 exit_code=0, stdout=self.SAMPLE_RESULT_LINES)
         result.parse_benchmark_results()
         self.assertEquals(result.status, telemetry_runner.SUCCESS_STATUS)
-        self.assertEquals(self.EXPECTED_KEYVALS, result.perf_keyvals)
+        self.assertEquals(self.EXPECTED_PERF_DATA, result.perf_data)
 
 
     def testOnlyResultLinesWithWarnings(self):
@@ -54,7 +58,7 @@
                                                   stderr=stderr)
         result.parse_benchmark_results()
         self.assertEquals(result.status, telemetry_runner.WARNING_STATUS)
-        self.assertEquals(self.EXPECTED_KEYVALS, result.perf_keyvals)
+        self.assertEquals(self.EXPECTED_PERF_DATA, result.perf_data)
 
 
     def testOnlyResultLinesWithWarningsAndTraceback(self):
@@ -71,7 +75,7 @@
                                                   stderr=stderr)
         result.parse_benchmark_results()
         self.assertEquals(result.status, telemetry_runner.FAILED_STATUS)
-        self.assertEquals(self.EXPECTED_KEYVALS, result.perf_keyvals)
+        self.assertEquals(self.EXPECTED_PERF_DATA, result.perf_data)
 
 
     def testInfoBeforeResultLines(self):
@@ -84,7 +88,7 @@
                                                   stderr=stderr)
         result.parse_benchmark_results()
         self.assertEquals(result.status, telemetry_runner.WARNING_STATUS)
-        self.assertEquals(self.EXPECTED_KEYVALS, result.perf_keyvals)
+        self.assertEquals(self.EXPECTED_PERF_DATA, result.perf_data)
 
 
     def testInfoAfterResultLines(self):
@@ -98,7 +102,7 @@
                                                   stderr='')
         result.parse_benchmark_results()
         self.assertEquals(result.status, telemetry_runner.SUCCESS_STATUS)
-        self.assertEquals(self.EXPECTED_KEYVALS, result.perf_keyvals)
+        self.assertEquals(self.EXPECTED_PERF_DATA, result.perf_data)
 
 
     def testInfoBeforeAndAfterResultLines(self):
@@ -113,7 +117,7 @@
                                                   stderr='')
         result.parse_benchmark_results()
         self.assertEquals(result.status, telemetry_runner.SUCCESS_STATUS)
-        self.assertEquals(self.EXPECTED_KEYVALS, result.perf_keyvals)
+        self.assertEquals(self.EXPECTED_PERF_DATA, result.perf_data)
 
 
     def testNoResultLines(self):
@@ -127,7 +131,7 @@
                                                   stderr='')
         result.parse_benchmark_results()
         self.assertEquals(result.status, telemetry_runner.SUCCESS_STATUS)
-        self.assertEquals({}, result.perf_keyvals)
+        self.assertEquals([], result.perf_data)
 
 
     def testBadCharactersInResultStringComponents(self):
@@ -139,18 +143,22 @@
             'RESULT ai-astar: ai-astar= '
             '[614,527,523,471,530,523,577,625,614,538] ~~ms\n'
             'RESULT !!graph_name: &&test_name= {3.14, 0.98} units!')
-        expected_keyvals = {
-            'TELEMETRY--average_commit_time_by_url_--http___www.__ebay.com--ms':
-                '8.86528',
-            'TELEMETRY--CodeLoad_--CodeLoad--score': '6343',
-            'TELEMETRY--ai-astar--ai-astar--__ms': '554.2',
-            'TELEMETRY--__graph_name--__test_name--units_': '3.14'}
+        expected_perf_data = [
+            {'graph': 'average_commit_time_by_url_',
+             'trace': 'http___www.__ebay.com',
+             'units': 'ms', 'value': 8.86528},
+            {'graph': 'CodeLoad_', 'trace': 'CodeLoad',
+             'units': 'score', 'value': 6343},
+            {'graph': 'ai-astar', 'trace': 'ai-astar',
+             'units': '__ms', 'value': 554.2},
+            {'graph': '__graph_name', 'trace': '__test_name',
+             'units': 'units_', 'value': 3.14}]
 
         result = telemetry_runner.TelemetryResult(exit_code=0, stdout=stdout,
                                                   stderr='')
         result.parse_benchmark_results()
         self.assertEquals(result.status, telemetry_runner.SUCCESS_STATUS)
-        self.assertEquals(expected_keyvals, result.perf_keyvals)
+        self.assertEquals(expected_perf_data, result.perf_data)
 
 
     def testCleanupUnitsString(self):
diff --git a/server/site_tests/telemetry_Benchmarks/telemetry_Benchmarks.py b/server/site_tests/telemetry_Benchmarks/telemetry_Benchmarks.py
index 896d1ce..3c31793 100644
--- a/server/site_tests/telemetry_Benchmarks/telemetry_Benchmarks.py
+++ b/server/site_tests/telemetry_Benchmarks/telemetry_Benchmarks.py
@@ -17,4 +17,4 @@
         @param benchmark: benchmark we want to run.
         """
         telemetry = telemetry_runner.TelemetryRunner(host)
-        telemetry.run_telemetry_benchmark(benchmark, keyval_writer=self)
+        telemetry.run_telemetry_benchmark(benchmark, perf_value_writer=self)
diff --git a/tko/perf_upload/perf_dashboard_config.json b/tko/perf_upload/perf_dashboard_config.json
index 337e3db..212e05b 100644
--- a/tko/perf_upload/perf_dashboard_config.json
+++ b/tko/perf_upload/perf_dashboard_config.json
@@ -18,5 +18,45 @@
   {
     "autotest_name": "video_VideoDecodeMemeoryUsage",
     "master_name": "ChromeOSVideo"
+  },
+  {
+    "autotest_name": "telemetry_Benchmarks.dromaeo.domcoreattr",
+    "master_name": "ChromiumPerf",
+    "dashboard_test_name": "dromaeo.domcoreattr"
+  },
+  {
+    "autotest_name": "telemetry_Benchmarks.dromaeo.domcoremodify",
+    "master_name": "ChromiumPerf",
+    "dashboard_test_name": "dromaeo.domcoremodify"
+  },
+  {
+    "autotest_name": "telemetry_Benchmarks.dromaeo.domcorequery",
+    "master_name": "ChromiumPerf",
+    "dashboard_test_name": "dromaeo.domcorequery"
+  },
+  {
+    "autotest_name": "telemetry_Benchmarks.dromaeo.domcoretraverse",
+    "master_name": "ChromiumPerf",
+    "dashboard_test_name": "dromaeo.domcoretraverse"
+  },
+  {
+    "autotest_name": "telemetry_Benchmarks.media.tough_media_cases",
+    "master_name": "ChromiumPerf",
+    "dashboard_test_name": "media.tough_media_cases"
+  },
+  {
+    "autotest_name": "telemetry_Benchmarks.memory.top_25",
+    "master_name": "ChromiumPerf",
+    "dashboard_test_name": "memory.top_25"
+  },
+  {
+    "autotest_name": "telemetry_Benchmarks.robohornet_pro",
+    "master_name": "ChromiumPerf",
+    "dashboard_test_name": "robohornet_pro"
+  },
+  {
+    "autotest_name": "telemetry_Benchmarks.smoothness.top_25",
+    "master_name": "ChromiumPerf",
+    "dashboard_test_name": "smoothness.top_25"
   }
 ]