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.
8 from telemetry import benchmark
9 from telemetry.core import platform
10 from telemetry.core import wpr_modes
11 from telemetry.page import page as page_module
12 from telemetry.page import page_set
13 from telemetry.results import page_test_results
14 from telemetry.timeline import model as model_module
15 from telemetry.timeline import async_slice
16 from telemetry.unittest import options_for_unittests
17 from telemetry.unittest import page_test_test_case
18 from telemetry.value import scalar
19 from telemetry.web_perf import timeline_based_measurement as tbm_module
20 from telemetry.web_perf import timeline_interaction_record as tir_module
21 from telemetry.web_perf.metrics import timeline_based_metric
24 class FakeFastMetric(timeline_based_metric.TimelineBasedMetric):
26 def AddResults(self, model, renderer_thread, interaction_records, results):
27 results.AddValue(scalar.ScalarValue(
28 results.current_page, 'FakeFastMetric', 'ms', 1))
29 results.AddValue(scalar.ScalarValue(
30 results.current_page, 'FastMetricRecords', 'count',
31 len(interaction_records)))
34 class FakeSmoothMetric(timeline_based_metric.TimelineBasedMetric):
36 def AddResults(self, model, renderer_thread, interaction_records, results):
37 results.AddValue(scalar.ScalarValue(
38 results.current_page, 'FakeSmoothMetric', 'ms', 1))
39 results.AddValue(scalar.ScalarValue(
40 results.current_page, 'SmoothMetricRecords', 'count',
41 len(interaction_records)))
44 class FakeLoadingMetric(timeline_based_metric.TimelineBasedMetric):
46 def AddResults(self, model, renderer_thread, interaction_records, results):
47 results.AddValue(scalar.ScalarValue(
48 results.current_page, 'FakeLoadingMetric', 'ms', 2))
49 results.AddValue(scalar.ScalarValue(
50 results.current_page, 'LoadingMetricRecords', 'count',
51 len(interaction_records)))
54 def GetMetricFromMetricType(metric_type):
55 if metric_type == tir_module.IS_FAST:
56 return FakeFastMetric()
57 if metric_type == tir_module.IS_SMOOTH:
58 return FakeSmoothMetric()
59 if metric_type == tir_module.IS_RESPONSIVE:
60 return FakeLoadingMetric()
61 raise Exception('Unrecognized metric type: %s' % metric_type)
64 class TimelineBasedMetricTestData(object):
67 self._model = model_module.TimelineModel()
68 renderer_process = self._model.GetOrCreateProcess(1)
69 self._renderer_thread = renderer_process.GetOrCreateThread(2)
70 self._renderer_thread.name = 'CrRendererMain'
71 self._results = page_test_results.PageTestResults()
83 def AddInteraction(self, marker='', ts=0, duration=5):
84 self._renderer_thread.async_slices.append(async_slice.AsyncSlice(
85 'category', marker, timestamp=ts, duration=duration,
86 start_thread=self._renderer_thread, end_thread=self._renderer_thread,
87 thread_start=ts, thread_duration=duration))
89 def FinalizeImport(self):
90 self._model.FinalizeImport()
91 self._metric = tbm_module._TimelineBasedMetrics( # pylint: disable=W0212
92 self._model, self._renderer_thread, GetMetricFromMetricType)
93 self._ps = page_set.PageSet(file_path=os.path.dirname(__file__))
94 self._ps.AddPageWithDefaultRunNavigate('http://www.bar.com/')
95 self._results.WillRunPage(self._ps.pages[0])
98 self._metric.AddResults(self._results)
99 self._results.DidRunPage(self._ps.pages[0])
102 class TimelineBasedMetricsTests(unittest.TestCase):
104 def testFindTimelineInteractionRecords(self):
105 d = TimelineBasedMetricTestData()
106 d.AddInteraction(ts=0, duration=20,
107 marker='Interaction.LogicalName1/is_smooth')
108 d.AddInteraction(ts=25, duration=5,
109 marker='Interaction.LogicalName2/is_responsive')
110 d.AddInteraction(ts=50, duration=15,
111 marker='Interaction.LogicalName3/is_fast')
113 interactions = d.metric.FindTimelineInteractionRecords()
114 self.assertEquals(3, len(interactions))
115 self.assertTrue(interactions[0].is_smooth)
116 self.assertEquals(0, interactions[0].start)
117 self.assertEquals(20, interactions[0].end)
119 self.assertTrue(interactions[1].is_responsive)
120 self.assertEquals(25, interactions[1].start)
121 self.assertEquals(30, interactions[1].end)
123 self.assertTrue(interactions[2].is_fast)
124 self.assertEquals(50, interactions[2].start)
125 self.assertEquals(65, interactions[2].end)
127 def testAddResults(self):
128 d = TimelineBasedMetricTestData()
129 d.AddInteraction(ts=0, duration=20,
130 marker='Interaction.LogicalName1/is_smooth')
131 d.AddInteraction(ts=25, duration=5,
132 marker='Interaction.LogicalName2/is_responsive')
133 d.AddInteraction(ts=50, duration=15,
134 marker='Interaction.LogicalName3/is_fast')
137 self.assertEquals(1, len(d.results.FindAllPageSpecificValuesNamed(
138 'LogicalName1-FakeSmoothMetric')))
139 self.assertEquals(1, len(d.results.FindAllPageSpecificValuesNamed(
140 'LogicalName2-FakeLoadingMetric')))
141 self.assertEquals(1, len(d.results.FindAllPageSpecificValuesNamed(
142 'LogicalName3-FakeFastMetric')))
144 def testNoInteractions(self):
145 d = TimelineBasedMetricTestData()
147 self.assertRaises(tbm_module.InvalidInteractions, d.AddResults)
149 def testDuplicateUnrepeatableInteractions(self):
150 d = TimelineBasedMetricTestData()
151 d.AddInteraction(ts=10, duration=5,
152 marker='Interaction.LogicalName/is_smooth')
153 d.AddInteraction(ts=20, duration=5,
154 marker='Interaction.LogicalName/is_smooth')
156 self.assertRaises(tbm_module.InvalidInteractions, d.AddResults)
158 def testDuplicateRepeatableInteractions(self):
159 d = TimelineBasedMetricTestData()
160 d.AddInteraction(ts=10, duration=5,
161 marker='Interaction.LogicalName/is_smooth,repeatable')
162 d.AddInteraction(ts=20, duration=5,
163 marker='Interaction.LogicalName/is_smooth,repeatable')
166 self.assertEquals(1, len(d.results.pages_that_succeeded))
168 def testDuplicateRepeatableInteractionsWithDifferentMetrics(self):
169 d = TimelineBasedMetricTestData()
171 responsive_marker = 'Interaction.LogicalName/is_responsive,repeatable'
172 d.AddInteraction(ts=10, duration=5, marker=responsive_marker)
173 smooth_marker = 'Interaction.LogicalName/is_smooth,repeatable'
174 d.AddInteraction(ts=20, duration=5, marker=smooth_marker)
176 self.assertRaises(tbm_module.InvalidInteractions, d.AddResults)
179 class TestTimelinebasedMeasurementPage(page_module.Page):
181 def __init__(self, ps, base_dir, trigger_animation=False,
182 trigger_jank=False, trigger_slow=False):
183 super(TestTimelinebasedMeasurementPage, self).__init__(
184 'file://interaction_enabled_page.html', ps, base_dir)
185 self._trigger_animation = trigger_animation
186 self._trigger_jank = trigger_jank
187 self._trigger_slow = trigger_slow
189 def RunPageInteractions(self, action_runner):
190 if self._trigger_animation:
191 action_runner.TapElement('#animating-button')
192 action_runner.WaitForJavaScriptCondition('window.animationDone')
193 if self._trigger_jank:
194 action_runner.TapElement('#jank-button')
195 action_runner.WaitForJavaScriptCondition('window.jankScriptDone')
196 if self._trigger_slow:
197 action_runner.TapElement('#slow-button')
198 action_runner.WaitForJavaScriptCondition('window.slowScriptDone')
201 class TimelineBasedMeasurementTest(page_test_test_case.PageTestTestCase):
204 self._options = options_for_unittests.GetCopy()
205 self._options.browser_options.wpr_mode = wpr_modes.WPR_OFF
207 def testSmoothnessTimelineBasedMeasurementForSmoke(self):
208 ps = self.CreateEmptyPageSet()
209 ps.AddPage(TestTimelinebasedMeasurementPage(
210 ps, ps.base_dir, trigger_animation=True))
212 measurement = tbm_module.TimelineBasedMeasurement()
213 results = self.RunMeasurement(measurement, ps,
214 options=self._options)
216 self.assertEquals(0, len(results.failures))
217 v = results.FindAllPageSpecificValuesNamed(
218 'CenterAnimation-frame_time_discrepancy')
219 self.assertEquals(len(v), 1)
220 v = results.FindAllPageSpecificValuesNamed(
221 'DrawerAnimation-frame_time_discrepancy')
222 self.assertEquals(len(v), 1)
224 def testFastTimelineBasedMeasurementForSmoke(self):
225 ps = self.CreateEmptyPageSet()
226 ps.AddPage(TestTimelinebasedMeasurementPage(
227 ps, ps.base_dir, trigger_slow=True))
229 measurement = tbm_module.TimelineBasedMeasurement()
230 results = self.RunMeasurement(measurement, ps, options=self._options)
232 self.assertEquals([], results.failures)
233 expected_names = set([
234 'SlowThreadJsRun-fast-duration',
235 'SlowThreadJsRun-fast-idle_time',
236 'SlowThreadJsRun-fast-incremental_marking',
237 'SlowThreadJsRun-fast-incremental_marking_outside_idle',
238 'SlowThreadJsRun-fast-mark_compactor',
239 'SlowThreadJsRun-fast-mark_compactor_outside_idle',
240 'SlowThreadJsRun-fast-scavenger',
241 'SlowThreadJsRun-fast-scavenger_outside_idle',
242 'SlowThreadJsRun-fast-total_garbage_collection',
243 'SlowThreadJsRun-fast-total_garbage_collection_outside_idle',
246 if platform.GetHostPlatform().GetOSName() != 'win':
247 # CPU metric is only supported non-Windows platforms.
248 expected_names.add('SlowThreadJsRun-fast-cpu_time')
250 expected_names, set(v.name for v in results.all_page_specific_values))
252 # In interaction_enabled_page.html, the "slow" interaction executes
253 # a loop with window.performance.now() to wait 200ms.
254 # fast-duration measures wall time so its value should be at least 200ms.
255 v = results.FindAllPageSpecificValuesNamed('SlowThreadJsRun-fast-duration')
256 self.assertGreaterEqual(v[0].value, 200.0)
258 # Disabled since mainthread_jank metric is not supported on windows platform.
259 @benchmark.Disabled('win')
260 def testMainthreadJankTimelineBasedMeasurement(self):
261 ps = self.CreateEmptyPageSet()
262 ps.AddPage(TestTimelinebasedMeasurementPage(
263 ps, ps.base_dir, trigger_jank=True))
265 measurement = tbm_module.TimelineBasedMeasurement()
266 results = self.RunMeasurement(measurement, ps,
267 options=self._options)
268 self.assertEquals(0, len(results.failures))
270 # In interaction_enabled_page.html, we create a jank loop based on
271 # window.performance.now() (basically loop for x milliseconds).
272 # Since window.performance.now() uses wall-time instead of thread time,
273 # we only assert the biggest jank > 50ms here to account for the fact
274 # that the browser may deschedule during the jank loop.
275 v = results.FindAllPageSpecificValuesNamed(
276 'JankThreadJSRun-responsive-biggest_jank_thread_time')
277 self.assertGreaterEqual(v[0].value, 50)
279 v = results.FindAllPageSpecificValuesNamed(
280 'JankThreadJSRun-responsive-total_big_jank_thread_time')
281 self.assertGreaterEqual(v[0].value, 50)