Commit d239c1be authored by nednguyen's avatar nednguyen Committed by Commit bot

[Telemetry] Add file handle & trace value.

Modify json_output_formatter to include trace values in the result output.

This is a partial land of Ethan's patch in https://codereview.chromium.org/545523002/.

BUG=

Review URL: https://codereview.chromium.org/663023005

Cr-Commit-Position: refs/heads/master@{#301558}
parent c119b2a9
......@@ -11,6 +11,7 @@ from telemetry.page import page_test
from telemetry.page.actions import action_runner
from telemetry.value import list_of_scalar_values
from telemetry.value import scalar
from telemetry.value import trace
from telemetry.web_perf import timeline_interaction_record as tir_module
from telemetry.web_perf.metrics import smoothness
......@@ -56,7 +57,7 @@ class SmoothnessController(object):
RUN_SMOOTH_ACTIONS, is_smooth=True)
def Stop(self, tab):
# End the smooth marker for all smooth actions.
# End the smooth marker for all smooth actions.
self._interaction.End()
# Stop tracing for smoothness metric.
if tab.browser.platform.IsRawDisplayFrameRateSupported():
......@@ -69,7 +70,8 @@ class SmoothnessController(object):
# Add results of smoothness metric. This computes the smoothness metric for
# the time ranges of gestures, if there is at least one, else the the time
# ranges from the first action to the last action.
results.AddValue(trace.TraceValue(
results.current_page, self._tracing_timeline_data))
renderer_thread = self._timeline_model.GetRendererThreadFromTabId(
tab.id)
run_smooth_actions_record = None
......
......@@ -3,20 +3,24 @@
# found in the LICENSE file.
import json
import os
from telemetry.results import output_formatter
from telemetry.util import file_handle
def ResultsAsDict(page_test_results, benchmark_metadata):
def ResultsAsDict(page_test_results, benchmark_metadata, output_dir):
"""Takes PageTestResults to a dict serializable to JSON.
To serialize results as JSON we first convert them to a dict that can be
serialized by the json module. It also requires a benchmark_metadat object
for metadata to be integrated into the results (currently the benchmark
name).
name). This function will also output trace files if they exist.
Args:
page_test_results: a PageTestResults object
benchmark_metadata: a benchmark.BenchmarkMetadata object
output_dir: the directory that results are being output to.
"""
result_dict = {
'format_version': '0.2',
......@@ -28,23 +32,41 @@ def ResultsAsDict(page_test_results, benchmark_metadata):
'pages': {p.id: p.AsDict() for p in _GetAllPages(page_test_results)}
}
file_ids_to_paths = _OutputTraceFiles(page_test_results, output_dir)
if file_ids_to_paths:
result_dict['files'] = file_ids_to_paths
return result_dict
def _OutputTraceFiles(page_test_results, output_dir):
file_handles = page_test_results.all_file_handles
if not file_handles:
return {}
trace_dir = os.path.join(output_dir, 'trace_files')
if not os.path.isdir(trace_dir):
os.makedirs(trace_dir)
return file_handle.OutputFiles(file_handles, trace_dir)
def _GetAllPages(page_test_results):
pages = set(page_run.page for page_run in
page_test_results.all_page_runs)
return pages
class JsonOutputFormatter(output_formatter.OutputFormatter):
def __init__(self, output_stream, benchmark_metadata):
def __init__(self, output_stream, output_dir, benchmark_metadata):
super(JsonOutputFormatter, self).__init__(output_stream)
self._benchmark_metadata = benchmark_metadata
self._output_dir = output_dir
@property
def benchmark_metadata(self):
return self._benchmark_metadata
def Format(self, page_test_results):
json.dump(ResultsAsDict(page_test_results, self.benchmark_metadata),
json.dump(
ResultsAsDict(
page_test_results, self.benchmark_metadata, self._output_dir),
self.output_stream)
self.output_stream.write('\n')
......@@ -3,14 +3,18 @@
# found in the LICENSE file.
import json
import os
import shutil
import StringIO
import unittest
import tempfile
from telemetry import benchmark
from telemetry.page import page_set
from telemetry.results import json_output_formatter
from telemetry.results import page_test_results
from telemetry.value import scalar
from telemetry.value import trace
from telemetry.timeline import tracing_timeline_data
def _MakePageSet():
......@@ -28,10 +32,15 @@ def _HasValueNamed(values, name):
class JsonOutputFormatterTest(unittest.TestCase):
def setUp(self):
self._output = StringIO.StringIO()
self._ouput_dir = tempfile.mkdtemp()
self._page_set = _MakePageSet()
self._formatter = json_output_formatter.JsonOutputFormatter(self._output,
self._formatter = json_output_formatter.JsonOutputFormatter(
self._output, self._ouput_dir,
benchmark.BenchmarkMetadata('benchmark_name'))
def tearDown(self):
shutil.rmtree(self._ouput_dir)
def testOutputAndParse(self):
results = page_test_results.PageTestResults()
......@@ -48,7 +57,7 @@ class JsonOutputFormatterTest(unittest.TestCase):
def testAsDictBaseKeys(self):
results = page_test_results.PageTestResults()
d = json_output_formatter.ResultsAsDict(results,
self._formatter.benchmark_metadata)
self._formatter.benchmark_metadata, self._ouput_dir)
self.assertEquals(d['format_version'], '0.2')
self.assertEquals(d['benchmark_name'], 'benchmark_name')
......@@ -61,11 +70,31 @@ class JsonOutputFormatterTest(unittest.TestCase):
results.DidRunPage(self._page_set[0])
d = json_output_formatter.ResultsAsDict(results,
self._formatter.benchmark_metadata)
self._formatter.benchmark_metadata, self._ouput_dir)
self.assertTrue(_HasPage(d['pages'], self._page_set[0]))
self.assertTrue(_HasValueNamed(d['per_page_values'], 'foo'))
def testAsDictWithTraceValue(self):
results = page_test_results.PageTestResults()
results.WillRunPage(self._page_set[0])
v0 = trace.TraceValue(
results.current_page,
tracing_timeline_data.TracingTimelineData({'event': 'test'}))
results.AddValue(v0)
results.DidRunPage(self._page_set[0])
d = json_output_formatter.ResultsAsDict(results,
self._formatter.benchmark_metadata, self._ouput_dir)
self.assertTrue(_HasPage(d['pages'], self._page_set[0]))
self.assertTrue(_HasValueNamed(d['per_page_values'], 'trace'))
self.assertEquals(len(d['files']), 1)
output_trace_path = d['files'].values()[0]
self.assertTrue(output_trace_path.startswith(self._ouput_dir))
self.assertTrue(os.path.exists(output_trace_path))
def testAsDictWithTwoPages(self):
results = page_test_results.PageTestResults()
results.WillRunPage(self._page_set[0])
......@@ -79,7 +108,7 @@ class JsonOutputFormatterTest(unittest.TestCase):
results.DidRunPage(self._page_set[1])
d = json_output_formatter.ResultsAsDict(results,
self._formatter.benchmark_metadata)
self._formatter.benchmark_metadata, self._ouput_dir)
self.assertTrue(_HasPage(d['pages'], self._page_set[0]))
self.assertTrue(_HasPage(d['pages'], self._page_set[1]))
......@@ -92,7 +121,7 @@ class JsonOutputFormatterTest(unittest.TestCase):
results.AddSummaryValue(v)
d = json_output_formatter.ResultsAsDict(results,
self._formatter.benchmark_metadata)
self._formatter.benchmark_metadata, self._ouput_dir)
self.assertFalse(d['pages'])
self.assertTrue(_HasValueNamed(d['summary_values'], 'baz'))
......@@ -4,6 +4,7 @@
import collections
import copy
import itertools
import traceback
from telemetry import value as value_module
......@@ -61,6 +62,13 @@ class PageTestResults(object):
values += self._current_page_run.values
return values
@property
def all_file_handles(self):
all_values = itertools.chain(
self.all_summary_values, self.all_page_specific_values)
return [fh for fh in map(lambda v: v.GetAssociatedFileHandle(), all_values)
if fh is not None]
@property
def all_summary_values(self):
return self._all_summary_values
......
......@@ -142,7 +142,7 @@ def CreateResults(benchmark_metadata, options):
options.results_label, trace_tag=options.output_trace_tag))
elif output_format == 'json':
output_formatters.append(json_output_formatter.JsonOutputFormatter(
output_stream, benchmark_metadata))
output_stream, options.output_dir, benchmark_metadata))
elif output_format == 'chartjson':
output_formatters.append(
chart_json_output_formatter.ChartJsonOutputFormatter(
......
# Copyright 2014 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import os
import shutil
_next_file_id = 0
class FileHandle(object):
def __init__(self, temp_file=None, absolute_path=None):
"""Constructs a FileHandle object.
This constructor should not be used by the user; rather it is preferred to
use the module-level GetAbsPath and FromTempFile functions.
Args:
temp_file: An instance of a temporary file object.
absolute_path: A path; should not be passed if tempfile is and vice-versa.
extension: A string that specifies the file extension. It must starts with
".".
"""
# Exactly one of absolute_path or temp_file must be specified.
assert (absolute_path is None) != (temp_file is None)
self._temp_file = temp_file
self._absolute_path = absolute_path
global _next_file_id
self._id = _next_file_id
_next_file_id += 1
@property
def id(self):
return self._id
@property
def extension(self):
return os.path.splitext(self.GetAbsPath())[1]
def GetAbsPath(self):
"""Returns the path to the pointed-to file relative to the given start path.
Args:
start: A string representing a starting path.
Returns:
A string giving the relative path from path to this file.
"""
if self._temp_file:
self._temp_file.close()
return self._temp_file.name
else:
return self._absolute_path
def FromTempFile(temp_file):
"""Constructs a FileHandle pointing to a temporary file.
Returns:
A FileHandle referring to a named temporary file.
"""
return FileHandle(temp_file)
def FromFilePath(path):
"""Constructs a FileHandle from an absolute file path.
Args:
path: A string giving the absolute path to a file.
Returns:
A FileHandle referring to the file at the specified path.
"""
return FileHandle(None, os.path.abspath(path))
def OutputFiles(file_handles, dir_name):
"""Outputs a list of file_handles by ID with a given dir_name and extension.
For the normal use case where we generate a collection of FileHandles
corresponding to temporary files, it is often necessary to collocate the
represented files into a single place. This function copies each file
referenced by an element of file_handles to a standardized location indicated
by dir_name.
Args:
file_handles: A list of file handles
dir_name: A string that specifies the directory to output the files.
Returns:
A dict mapping IDs to output files' paths.
"""
file_ids_to_paths = dict()
for fh in file_handles:
file_name = str(fh.id) + fh.extension
file_path = os.path.abspath(os.path.join(dir_name, file_name))
shutil.copy(fh.GetAbsPath(), file_path)
file_ids_to_paths[fh.id] = file_path
return file_ids_to_paths
# Copyright 2014 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import os
import unittest
import shutil
import tempfile
from telemetry.util import file_handle
class FileHandleUnittest(unittest.TestCase):
def setUp(self):
self.temp_file_txt = tempfile.NamedTemporaryFile(
suffix='.txt', delete=False)
self.abs_path_html = tempfile.NamedTemporaryFile(
suffix='.html', delete=False).name
def tearDown(self):
os.remove(self.abs_path_html)
def testCreatingFileHandle(self):
fh1 = file_handle.FromTempFile(self.temp_file_txt)
self.assertEquals(fh1.extension, '.txt')
fh2 = file_handle.FromFilePath(self.abs_path_html)
self.assertEquals(fh2.extension, '.html')
self.assertNotEquals(fh1.id, fh2.id)
def testOutputFiles(self):
fh1 = file_handle.FromTempFile(self.temp_file_txt)
fh2 = file_handle.FromFilePath(self.abs_path_html)
tmpdir = tempfile.mkdtemp()
try:
file_ids_to_paths = file_handle.OutputFiles([fh1, fh2], tmpdir)
expected_output_file_1_path = os.path.join(tmpdir, str(fh1.id) + '.txt')
expected_output_file_2_path = os.path.join(tmpdir, str(fh2.id) + '.html')
self.assertEqual(file_ids_to_paths[fh1.id], expected_output_file_1_path)
self.assertEqual(file_ids_to_paths[fh2.id], expected_output_file_2_path)
# Test that the files are actually output.
self.assertTrue(os.path.exists(expected_output_file_1_path))
self.assertTrue(os.path.exists(expected_output_file_2_path))
finally:
shutil.rmtree(tmpdir)
......@@ -274,6 +274,10 @@ class Value(object):
return d
def GetAssociatedFileHandle(self):
"""Returns the file handle associated with this value (may be None)."""
return None
def ValueNameFromTraceAndChartName(trace_name, chart_name=None):
"""Mangles a trace name plus optional chart name into a standard string.
......
# Copyright 2014 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import tempfile
from telemetry import value as value_module
from telemetry.util import file_handle
import telemetry.web_components # pylint: disable=W0611
from trace_viewer.build import trace2html
class TraceValue(value_module.Value):
def __init__(self, page, tracing_timeline_data, important=False,
description=None):
"""A value that contains a TracingTimelineData object and knows how to
output it.
Adding TraceValues and outputting as JSON will produce a directory full of
HTML files called trace_files. Outputting as chart JSON will also produce
an index, files.html, linking to each of these files.
"""
super(TraceValue, self).__init__(
page, name='trace', units='', important=important,
description=description)
tf = tempfile.NamedTemporaryFile(delete=False, suffix='.html')
if page:
title = page.display_name
else:
title = ''
trace2html.WriteHTMLForTraceDataToFile(
[tracing_timeline_data.EventData()], title, tf)
tf.close()
self._file_handle = file_handle.FromTempFile(tf)
def GetAssociatedFileHandle(self):
return self._file_handle
def __repr__(self):
if self.page:
page_name = self.page.url
else:
page_name = None
return 'TraceValue(%s, %s)' % (page_name, self.name)
def GetBuildbotDataType(self, output_context):
return None
def GetBuildbotValue(self):
return None
def GetRepresentativeNumber(self):
return None
def GetRepresentativeString(self):
return None
@staticmethod
def GetJSONTypeName():
return 'trace'
@classmethod
def MergeLikeValuesFromSamePage(cls, values):
# TODO(eakuefner): Implement a MultiTraceValue: a Polymer-based,
# componentized, MultiTraceViwer-backed representation of more than one
# trace.
assert len(values) > 0
return values[0]
@classmethod
def MergeLikeValuesFromDifferentPages(cls, values,
group_by_name_suffix=False):
return None
def AsDict(self):
d = super(TraceValue, self).AsDict()
d['file_id'] = self._file_handle.id
return d
# Copyright 2014 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import os
import unittest
from telemetry.page import page_set
from telemetry.timeline import tracing_timeline_data
from telemetry.value import trace
class TestBase(unittest.TestCase):
def setUp(self):
self.page_set = page_set.PageSet(file_path=os.path.dirname(__file__))
self.page_set.AddPageWithDefaultRunNavigate("http://www.bar.com/")
self.page_set.AddPageWithDefaultRunNavigate("http://www.baz.com/")
self.page_set.AddPageWithDefaultRunNavigate("http://www.foo.com/")
@property
def pages(self):
return self.page_set.pages
class ValueTest(TestBase):
def testAsDict(self):
v = trace.TraceValue(
None, tracing_timeline_data.TracingTimelineData({'test' : 1}))
fh_id = v.GetAssociatedFileHandle().id
d = v.AsDict()
self.assertEqual(d['file_id'], fh_id)
......@@ -2,16 +2,13 @@
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import logging
import os
from collections import defaultdict
from telemetry.core import util
from telemetry.core.platform import tracing_category_filter
from telemetry.core.platform import tracing_options
from telemetry.page import page_test
from telemetry.timeline import model as model_module
from telemetry.value import string as string_value_module
from telemetry.value import trace
from telemetry.web_perf import timeline_interaction_record as tir_module
from telemetry.web_perf.metrics import fast_metric
from telemetry.web_perf.metrics import responsiveness_metric
......@@ -147,10 +144,6 @@ class TimelineBasedMeasurement(page_test.PageTest):
choices=ALL_OVERHEAD_LEVELS,
default=NO_OVERHEAD_LEVEL,
help='How much overhead to incur during the measurement.')
parser.add_option(
'--trace-dir', dest='trace_dir', type='string', default=None,
help=('Where to save the trace after the run. If this flag '
'is not set, the trace will not be saved.'))
def WillNavigateToPage(self, page, tab):
if not tab.browser.platform.tracing_controller.IsChromeTracingSupported(
......@@ -179,24 +172,14 @@ class TimelineBasedMeasurement(page_test.PageTest):
def ValidateAndMeasurePage(self, page, tab, results):
""" Collect all possible metrics and added them to results. """
trace_result = tab.browser.platform.tracing_controller.Stop()
trace_dir = self.options.trace_dir
if trace_dir:
trace_file_path = util.GetSequentialFileName(
os.path.join(trace_dir, 'trace')) + '.json'
try:
with open(trace_file_path, 'w') as f:
trace_result.Serialize(f)
results.AddValue(string_value_module.StringValue(
page, 'trace_path', 'string', trace_file_path))
except IOError, e:
logging.error('Cannot open %s. %s' % (trace_file_path, e))
results.AddValue(trace.TraceValue(results.current_page, trace_result))
model = model_module.TimelineModel(trace_result)
renderer_thread = model.GetRendererThreadFromTabId(tab.id)
meta_metrics = _TimelineBasedMetrics(
model, renderer_thread, _GetMetricFromMetricType)
meta_metrics.AddResults(results)
def CleanUpAfterPage(self, page, tab):
if tab.browser.platform.tracing_controller.is_tracing_running:
tab.browser.platform.tracing_controller.Stop()
......@@ -241,6 +241,7 @@ class TimelineBasedMeasurementTest(page_test_test_case.PageTestTestCase):
'SlowThreadJsRun-fast-scavenger_outside_idle',
'SlowThreadJsRun-fast-total_garbage_collection',
'SlowThreadJsRun-fast-total_garbage_collection_outside_idle',
'trace',
])
if platform.GetHostPlatform().GetOSName() != 'win':
# CPU metric is only supported non-Windows platforms.
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment