Upstream version 9.38.198.0
[platform/framework/web/crosswalk.git] / src / tools / telemetry / telemetry / web_perf / timeline_based_measurement.py
1 # Copyright 2014 The Chromium Authors. All rights reserved.
2 # Use of this source code is governed by a BSD-style license that can be
3 # found in the LICENSE file.
4
5 import logging
6 import os
7 from collections import defaultdict
8
9 from telemetry.core import util
10 from telemetry.core.platform import tracing_category_filter
11 from telemetry.page import page_test
12 from telemetry.timeline import model as model_module
13 from telemetry.value import string as string_value_module
14 from telemetry.web_perf import timeline_interaction_record as tir_module
15 from telemetry.web_perf.metrics import fast_metric
16 from telemetry.web_perf.metrics import responsiveness_metric
17 from telemetry.web_perf.metrics import smoothness
18
19 # TimelineBasedMeasurement considers all instrumentation as producing a single
20 # timeline. But, depending on the amount of instrumentation that is enabled,
21 # overhead increases. The user of the measurement must therefore chose between
22 # a few levels of instrumentation.
23 NO_OVERHEAD_LEVEL = 'no-overhead'
24 MINIMAL_OVERHEAD_LEVEL = 'minimal-overhead'
25 DEBUG_OVERHEAD_LEVEL = 'debug-overhead'
26
27 ALL_OVERHEAD_LEVELS = [
28   NO_OVERHEAD_LEVEL,
29   MINIMAL_OVERHEAD_LEVEL,
30   DEBUG_OVERHEAD_LEVEL
31 ]
32
33
34 class InvalidInteractions(Exception):
35   pass
36
37
38 def _GetMetricFromMetricType(metric_type):
39   if metric_type == tir_module.IS_FAST:
40     return fast_metric.FastMetric()
41   if metric_type == tir_module.IS_SMOOTH:
42     return smoothness.SmoothnessMetric()
43   if metric_type == tir_module.IS_RESPONSIVE:
44     return responsiveness_metric.ResponsivenessMetric()
45   raise Exception('Unrecognized metric type: %s' % metric_type)
46
47
48 # TODO(nednguyen): Get rid of this results wrapper hack after we add interaction
49 # record to telemetry value system.
50 class _ResultsWrapper(object):
51   def __init__(self, results, label):
52     self._results = results
53     self._result_prefix = label
54
55   @property
56   def current_page(self):
57     return self._results.current_page
58
59   def _GetResultName(self, trace_name):
60     return '%s-%s' % (self._result_prefix, trace_name)
61
62   def AddValue(self, value):
63     value.name = self._GetResultName(value.name)
64     self._results.AddValue(value)
65
66 class _TimelineBasedMetrics(object):
67   def __init__(self, model, renderer_thread,
68                get_metric_from_metric_type_callback):
69     self._model = model
70     self._renderer_thread = renderer_thread
71     self._get_metric_from_metric_type_callback = \
72         get_metric_from_metric_type_callback
73
74   def FindTimelineInteractionRecords(self):
75     # TODO(nduca): Add support for page-load interaction record.
76     return [tir_module.TimelineInteractionRecord.FromAsyncEvent(event) for
77             event in self._renderer_thread.async_slices
78             if tir_module.IsTimelineInteractionRecord(event.name)]
79
80   def AddResults(self, results):
81     all_interactions = self.FindTimelineInteractionRecords()
82     if len(all_interactions) == 0:
83       raise InvalidInteractions('Expected at least one interaction record on '
84                                 'the page')
85
86     interactions_by_label = defaultdict(list)
87     for i in all_interactions:
88       interactions_by_label[i.label].append(i)
89
90     for label, interactions in interactions_by_label.iteritems():
91       are_repeatable = [i.repeatable for i in interactions]
92       if not all(are_repeatable) and len(interactions) > 1:
93         raise InvalidInteractions('Duplicate unrepeatable interaction records '
94                                   'on the page')
95       wrapped_results = _ResultsWrapper(results, label)
96       self.UpdateResultsByMetric(interactions, wrapped_results)
97
98   def UpdateResultsByMetric(self, interactions, wrapped_results):
99     for metric_type in tir_module.METRICS:
100       # For each metric type, either all or none of the interactions should
101       # have that metric.
102       interactions_with_metric = [i for i in interactions if
103                                   i.HasMetric(metric_type)]
104       if not interactions_with_metric:
105         continue
106       if len(interactions_with_metric) != len(interactions):
107         raise InvalidInteractions('Interaction records with the same logical '
108                                   'name must have the same flags.')
109       metric = self._get_metric_from_metric_type_callback(metric_type)
110       metric.AddResults(self._model, self._renderer_thread,
111                         interactions, wrapped_results)
112
113
114 class TimelineBasedMeasurement(page_test.PageTest):
115   """Collects multiple metrics pages based on their interaction records.
116
117   A timeline measurement shifts the burden of what metrics to collect onto the
118   page under test, or the pageset running that page. Instead of the measurement
119   having a fixed set of values it collects about the page, the page being tested
120   issues (via javascript) an Interaction record into the user timing API that
121   describing what the page is doing at that time, as well as a standardized set
122   of flags describing the semantics of the work being done. The
123   TimelineBasedMeasurement object collects a trace that includes both these
124   interaction recorsd, and a user-chosen amount of performance data using
125   Telemetry's various timeline-producing APIs, tracing especially.
126
127   It then passes the recorded timeline to different TimelineBasedMetrics based
128   on those flags. This allows a single run through a page to produce load timing
129   data, smoothness data, critical jank information and overall cpu usage
130   information.
131
132   For information on how to mark up a page to work with
133   TimelineBasedMeasurement, refer to the
134   perf.metrics.timeline_interaction_record module.
135
136   """
137   def __init__(self):
138     super(TimelineBasedMeasurement, self).__init__('RunSmoothness')
139
140   @classmethod
141   def AddCommandLineArgs(cls, parser):
142     parser.add_option(
143         '--overhead-level', dest='overhead_level', type='choice',
144         choices=ALL_OVERHEAD_LEVELS,
145         default=NO_OVERHEAD_LEVEL,
146         help='How much overhead to incur during the measurement.')
147     parser.add_option(
148         '--trace-dir', dest='trace_dir', type='string', default=None,
149         help=('Where to save the trace after the run. If this flag '
150               'is not set, the trace will not be saved.'))
151
152   def WillNavigateToPage(self, page, tab):
153     if not tab.browser.supports_tracing:
154       raise Exception('Not supported')
155
156     assert self.options.overhead_level in ALL_OVERHEAD_LEVELS
157     if self.options.overhead_level == NO_OVERHEAD_LEVEL:
158       category_filter = tracing_category_filter.CreateNoOverheadFilter()
159     elif self.options.overhead_level == MINIMAL_OVERHEAD_LEVEL:
160       category_filter = tracing_category_filter.CreateMinimalOverheadFilter()
161     else:
162       category_filter = tracing_category_filter.CreateDebugOverheadFilter()
163
164     for delay in page.GetSyntheticDelayCategories():
165       category_filter.AddSyntheticDelay(delay)
166
167     tab.browser.StartTracing(category_filter)
168
169   def ValidateAndMeasurePage(self, page, tab, results):
170     """ Collect all possible metrics and added them to results. """
171     trace_result = tab.browser.StopTracing()
172     trace_dir = self.options.trace_dir
173     if trace_dir:
174       trace_file_path = util.GetSequentialFileName(
175           os.path.join(trace_dir, 'trace')) + '.json'
176       try:
177         with open(trace_file_path, 'w') as f:
178           trace_result.Serialize(f)
179         results.AddValue(string_value_module.StringValue(
180             page, 'trace_path', 'string', trace_file_path))
181       except IOError, e:
182         logging.error('Cannot open %s. %s' % (trace_file_path, e))
183
184     model = model_module.TimelineModel(trace_result)
185     renderer_thread = model.GetRendererThreadFromTabId(tab.id)
186     meta_metrics = _TimelineBasedMetrics(
187         model, renderer_thread, _GetMetricFromMetricType)
188     meta_metrics.AddResults(results)
189
190   def CleanUpAfterPage(self, page, tab):
191     if tab.browser.is_tracing_running:
192       tab.browser.StopTracing()