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
5import os
6import unittest
7
8from telemetry import story
9from telemetry.internal.results import page_test_results
10from telemetry.page import page as page_module
11from telemetry.timeline import async_slice
12from telemetry.timeline import model as model_module
13from telemetry.value import improvement_direction
14from telemetry.value import scalar
15from telemetry.web_perf.metrics import timeline_based_metric
16from telemetry.web_perf import timeline_based_measurement as tbm_module
17
18
19class FakeSmoothMetric(timeline_based_metric.TimelineBasedMetric):
20
21  def AddResults(self, model, renderer_thread, interaction_records, results):
22    results.AddValue(scalar.ScalarValue(
23        results.current_page, 'FakeSmoothMetric', 'ms', 1,
24        improvement_direction=improvement_direction.DOWN))
25    results.AddValue(scalar.ScalarValue(
26        results.current_page, 'SmoothMetricRecords', 'count',
27        len(interaction_records),
28        improvement_direction=improvement_direction.DOWN))
29
30
31class FakeLoadingMetric(timeline_based_metric.TimelineBasedMetric):
32
33  def AddResults(self, model, renderer_thread, interaction_records, results):
34    results.AddValue(scalar.ScalarValue(
35        results.current_page, 'FakeLoadingMetric', 'ms', 2,
36        improvement_direction=improvement_direction.DOWN))
37    results.AddValue(scalar.ScalarValue(
38        results.current_page, 'LoadingMetricRecords', 'count',
39        len(interaction_records),
40        improvement_direction=improvement_direction.DOWN))
41
42
43class FakeStartupMetric(timeline_based_metric.TimelineBasedMetric):
44
45  def AddResults(self, model, renderer_thread, interaction_records, results):
46    pass
47
48  def AddWholeTraceResults(self, model, results):
49    results.AddValue(scalar.ScalarValue(
50        results.current_page, 'FakeStartupMetric', 'ms', 3,
51        improvement_direction=improvement_direction.DOWN))
52
53
54class TimelineBasedMetricTestData(object):
55
56  def __init__(self, options):
57    self._model = model_module.TimelineModel()
58    renderer_process = self._model.GetOrCreateProcess(1)
59    self._renderer_thread = renderer_process.GetOrCreateThread(2)
60    self._renderer_thread.name = 'CrRendererMain'
61    self._foo_thread = renderer_process.GetOrCreateThread(3)
62    self._foo_thread.name = 'CrFoo'
63
64    self._results_wrapper = tbm_module._TBMResultWrapper()
65    self._results = page_test_results.PageTestResults()
66    self._story_set = None
67    self._threads_to_records_map = None
68    self._tbm_options = options
69
70  @property
71  def model(self):
72    return self._model
73
74  @property
75  def renderer_thread(self):
76    return self._renderer_thread
77
78  @property
79  def foo_thread(self):
80    return self._foo_thread
81
82  @property
83  def threads_to_records_map(self):
84    return self._threads_to_records_map
85
86  @property
87  def results(self):
88    return self._results
89
90  def AddInteraction(self, thread, marker='', ts=0, duration=5):
91    assert thread in (self._renderer_thread, self._foo_thread)
92    thread.async_slices.append(async_slice.AsyncSlice(
93        'category', marker, timestamp=ts, duration=duration,
94        start_thread=self._renderer_thread, end_thread=self._renderer_thread,
95        thread_start=ts, thread_duration=duration))
96
97  def FinalizeImport(self):
98    self._model.FinalizeImport()
99    self._threads_to_records_map = (
100      tbm_module._GetRendererThreadsToInteractionRecordsMap(self._model))
101    self._story_set = story.StorySet(base_dir=os.path.dirname(__file__))
102    self._story_set.AddStory(page_module.Page(
103        'http://www.bar.com/', self._story_set, self._story_set.base_dir))
104    self._results.WillRunPage(self._story_set.stories[0])
105
106  def AddResults(self):
107    all_metrics = self._tbm_options.GetLegacyTimelineBasedMetrics()
108
109    for thread, records in self._threads_to_records_map.iteritems():
110      # pylint: disable=protected-access
111      metric = tbm_module._TimelineBasedMetrics(
112          self._model, thread, records, self._results_wrapper, all_metrics)
113      metric.AddResults(self._results)
114
115    for metric in all_metrics:
116      metric.AddWholeTraceResults(self._model, self._results)
117
118    self._results.DidRunPage(self._story_set.stories[0])
119
120
121class TimelineBasedMetricsTests(unittest.TestCase):
122
123  def setUp(self):
124    self.actual_get_all_tbm_metrics = (
125        tbm_module._GetAllLegacyTimelineBasedMetrics)
126    self._options = tbm_module.Options()
127    self._options.SetLegacyTimelineBasedMetrics(
128        (FakeSmoothMetric(), FakeLoadingMetric(), FakeStartupMetric()))
129
130  def tearDown(self):
131    tbm_module._GetAllLegacyTimelineBasedMetrics = (
132        self.actual_get_all_tbm_metrics)
133
134  def testGetRendererThreadsToInteractionRecordsMap(self):
135    d = TimelineBasedMetricTestData(self._options)
136    # Insert 2 interaction records to renderer_thread and 1 to foo_thread
137    d.AddInteraction(d.renderer_thread, ts=0, duration=20,
138                     marker='Interaction.LogicalName1')
139    d.AddInteraction(d.renderer_thread, ts=25, duration=5,
140                     marker='Interaction.LogicalName2')
141    d.AddInteraction(d.foo_thread, ts=50, duration=15,
142                     marker='Interaction.LogicalName3')
143    d.FinalizeImport()
144
145    self.assertEquals(2, len(d.threads_to_records_map))
146
147    # Assert the 2 interaction records of renderer_thread are in the map.
148    self.assertIn(d.renderer_thread, d.threads_to_records_map)
149    interactions = d.threads_to_records_map[d.renderer_thread]
150    self.assertEquals(2, len(interactions))
151    self.assertEquals(0, interactions[0].start)
152    self.assertEquals(20, interactions[0].end)
153
154    self.assertEquals(25, interactions[1].start)
155    self.assertEquals(30, interactions[1].end)
156
157    # Assert the 1 interaction records of foo_thread is in the map.
158    self.assertIn(d.foo_thread, d.threads_to_records_map)
159    interactions = d.threads_to_records_map[d.foo_thread]
160    self.assertEquals(1, len(interactions))
161    self.assertEquals(50, interactions[0].start)
162    self.assertEquals(65, interactions[0].end)
163
164  def testAddResults(self):
165    d = TimelineBasedMetricTestData(self._options)
166    d.AddInteraction(d.renderer_thread, ts=0, duration=20,
167                     marker='Interaction.LogicalName1')
168    d.AddInteraction(d.foo_thread, ts=25, duration=5,
169                     marker='Interaction.LogicalName2')
170    d.FinalizeImport()
171    d.AddResults()
172    self.assertEquals(1, len(d.results.FindAllPageSpecificValuesFromIRNamed(
173        'LogicalName1', 'FakeSmoothMetric')))
174    self.assertEquals(1, len(d.results.FindAllPageSpecificValuesFromIRNamed(
175        'LogicalName2', 'FakeLoadingMetric')))
176    self.assertEquals(1, len(d.results.FindAllPageSpecificValuesNamed(
177        'FakeStartupMetric')))
178
179  def testDuplicateInteractionsInDifferentThreads(self):
180    d = TimelineBasedMetricTestData(self._options)
181    d.AddInteraction(d.renderer_thread, ts=10, duration=5,
182                     marker='Interaction.LogicalName/repeatable')
183    d.AddInteraction(d.foo_thread, ts=20, duration=5,
184                     marker='Interaction.LogicalName')
185    self.assertRaises(tbm_module.InvalidInteractions, d.FinalizeImport)
186
187  def testDuplicateRepeatableInteractionsInDifferentThreads(self):
188    d = TimelineBasedMetricTestData(self._options)
189    d.AddInteraction(d.renderer_thread, ts=10, duration=5,
190                     marker='Interaction.LogicalName/repeatable')
191    d.AddInteraction(d.foo_thread, ts=20, duration=5,
192                     marker='Interaction.LogicalName/repeatable')
193    self.assertRaises(tbm_module.InvalidInteractions, d.FinalizeImport)
194
195  def testDuplicateUnrepeatableInteractionsInSameThread(self):
196    d = TimelineBasedMetricTestData(self._options)
197    d.AddInteraction(d.renderer_thread, ts=10, duration=5,
198                     marker='Interaction.LogicalName')
199    d.AddInteraction(d.renderer_thread, ts=20, duration=5,
200                     marker='Interaction.LogicalName')
201    d.FinalizeImport()
202    self.assertRaises(tbm_module.InvalidInteractions, d.AddResults)
203
204  def testDuplicateRepeatableInteractions(self):
205    d = TimelineBasedMetricTestData(self._options)
206    d.AddInteraction(d.renderer_thread, ts=10, duration=5,
207                     marker='Interaction.LogicalName/repeatable')
208    d.AddInteraction(d.renderer_thread, ts=20, duration=5,
209                     marker='Interaction.LogicalName/repeatable')
210    d.FinalizeImport()
211    d.AddResults()
212    self.assertEquals(1, len(d.results.pages_that_succeeded))
213