1# Copyright 2015 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 unittest
6
7from telemetry.testing import test_page_test_results
8from telemetry.timeline import async_slice as async_slice_module
9from telemetry.timeline import model as model_module
10from telemetry.timeline import slice as slice_module
11from telemetry.web_perf.metrics import gpu_timeline
12from telemetry.web_perf import timeline_interaction_record as tir_module
13
14SERVICE_FRAME_END_CATEGORY, SERVICE_FRAME_END_NAME = \
15    gpu_timeline.SERVICE_FRAME_END_MARKER
16
17DEVICE_FRAME_END_CATEGORY, DEVICE_FRAME_END_NAME = \
18    gpu_timeline.DEVICE_FRAME_END_MARKER
19
20INTERACTION_RECORDS = [tir_module.TimelineInteractionRecord("test-record",
21                                                            0,
22                                                            float('inf'))]
23
24
25def _CreateGPUSlices(parent_thread, name, start_time, duration, offset=0):
26  args = {'gl_category': gpu_timeline.TOPLEVEL_GL_CATEGORY}
27  return (slice_module.Slice(parent_thread,
28                             gpu_timeline.TOPLEVEL_SERVICE_CATEGORY,
29                             name, start_time,
30                             args=args,
31                             duration=duration,
32                             thread_duration=duration),
33          async_slice_module.AsyncSlice(gpu_timeline.TOPLEVEL_DEVICE_CATEGORY,
34                             name, start_time + offset,
35                             args=args,
36                             duration=duration))
37
38def _CreateFrameEndSlices(parent_thread, start_time, duration, offset=0):
39  args = {'gl_category': gpu_timeline.TOPLEVEL_GL_CATEGORY}
40  return (slice_module.Slice(parent_thread,
41                             SERVICE_FRAME_END_CATEGORY,
42                             SERVICE_FRAME_END_NAME,
43                             start_time,
44                             args=args,
45                             duration=duration,
46                             thread_duration=duration),
47          async_slice_module.AsyncSlice(DEVICE_FRAME_END_CATEGORY,
48                             DEVICE_FRAME_END_NAME,
49                             start_time + offset,
50                             args=args,
51                             duration=duration))
52
53
54def _AddSliceToThread(parent_thread, slice_item):
55  if isinstance(slice_item, slice_module.Slice):
56    parent_thread.PushSlice(slice_item)
57  elif isinstance(slice_item, async_slice_module.AsyncSlice):
58    parent_thread.AddAsyncSlice(slice_item)
59  else:
60    assert False, "Invalid Slice Item Type: %s" % type(slice_item)
61
62
63class GPUTimelineTest(unittest.TestCase):
64  def GetResults(self, metric, model, renderer_thread, interaction_records):
65    results = test_page_test_results.TestPageTestResults(self)
66    metric.AddResults(model, renderer_thread, interaction_records, results)
67    return results
68
69  def testExpectedResults(self):
70    """Test a simply trace will output all expected results."""
71    model = model_module.TimelineModel()
72    test_thread = model.GetOrCreateProcess(1).GetOrCreateThread(2)
73    for slice_item in _CreateGPUSlices(test_thread, 'test_item', 100, 10):
74      _AddSliceToThread(test_thread, slice_item)
75    model.FinalizeImport()
76
77    metric = gpu_timeline.GPUTimelineMetric()
78    results = self.GetResults(metric, model=model, renderer_thread=test_thread,
79                              interaction_records=INTERACTION_RECORDS)
80
81    for name, src_type in (('swap', None), ('total', 'cpu'), ('total', 'gpu')):
82      results.AssertHasPageSpecificScalarValue(
83          gpu_timeline.TimelineName(name, src_type, 'max'), 'ms', 10)
84      results.AssertHasPageSpecificScalarValue(
85          gpu_timeline.TimelineName(name, src_type, 'mean'), 'ms', 10)
86      results.AssertHasPageSpecificScalarValue(
87          gpu_timeline.TimelineName(name, src_type, 'stddev'), 'ms', 0)
88
89    for tracked_name in gpu_timeline.TRACKED_GL_CONTEXT_NAME.values():
90      for source_type in ('cpu', 'gpu'):
91        results.AssertHasPageSpecificScalarValue(
92            gpu_timeline.TimelineName(tracked_name, source_type, 'max'),
93                                      'ms', 0)
94        results.AssertHasPageSpecificScalarValue(
95            gpu_timeline.TimelineName(tracked_name, source_type, 'mean'),
96                                      'ms', 0)
97        results.AssertHasPageSpecificScalarValue(
98            gpu_timeline.TimelineName(tracked_name, source_type, 'stddev'),
99                                      'ms', 0)
100
101  def testNoDeviceTraceResults(self):
102    """Test expected results when missing device traces."""
103    model = model_module.TimelineModel()
104    test_thread = model.GetOrCreateProcess(1).GetOrCreateThread(2)
105    service_slice, _ = _CreateGPUSlices(test_thread, 'test_item', 100, 10)
106    _AddSliceToThread(test_thread, service_slice)
107    model.FinalizeImport()
108
109    metric = gpu_timeline.GPUTimelineMetric()
110    results = self.GetResults(metric, model=model, renderer_thread=test_thread,
111                              interaction_records=INTERACTION_RECORDS)
112
113    for name, source_type in (('swap', None), ('total', 'cpu')):
114      results.AssertHasPageSpecificScalarValue(
115          gpu_timeline.TimelineName(name, source_type, 'max'), 'ms', 10)
116      results.AssertHasPageSpecificScalarValue(
117          gpu_timeline.TimelineName(name, source_type, 'mean'), 'ms', 10)
118      results.AssertHasPageSpecificScalarValue(
119          gpu_timeline.TimelineName(name, source_type, 'stddev'), 'ms', 0)
120
121    self.assertRaises(AssertionError, results.GetPageSpecificValueNamed,
122                      gpu_timeline.TimelineName('total', 'gpu', 'max'))
123    self.assertRaises(AssertionError, results.GetPageSpecificValueNamed,
124                      gpu_timeline.TimelineName('total', 'gpu', 'mean'))
125    self.assertRaises(AssertionError, results.GetPageSpecificValueNamed,
126                      gpu_timeline.TimelineName('total', 'gpu', 'stddev'))
127
128    for name in gpu_timeline.TRACKED_GL_CONTEXT_NAME.values():
129      results.AssertHasPageSpecificScalarValue(
130          gpu_timeline.TimelineName(name, 'cpu', 'max'), 'ms', 0)
131      results.AssertHasPageSpecificScalarValue(
132          gpu_timeline.TimelineName(name, 'cpu', 'mean'), 'ms', 0)
133      results.AssertHasPageSpecificScalarValue(
134          gpu_timeline.TimelineName(name, 'cpu', 'stddev'), 'ms', 0)
135
136      self.assertRaises(AssertionError, results.GetPageSpecificValueNamed,
137                        gpu_timeline.TimelineName(name, 'gpu', 'max'))
138      self.assertRaises(AssertionError, results.GetPageSpecificValueNamed,
139                        gpu_timeline.TimelineName(name, 'gpu', 'mean'))
140      self.assertRaises(AssertionError, results.GetPageSpecificValueNamed,
141                        gpu_timeline.TimelineName(name, 'gpu', 'stddev'))
142
143  def testFrameSeparation(self):
144    """Test frames are correctly calculated using the frame end marker."""
145    model = model_module.TimelineModel()
146    test_thread = model.GetOrCreateProcess(1).GetOrCreateThread(2)
147
148    # First frame is 10 seconds.
149    for slice_item in _CreateGPUSlices(test_thread, 'test_item', 100, 10):
150      _AddSliceToThread(test_thread, slice_item)
151
152    # Mark frame end.
153    for slice_item in _CreateFrameEndSlices(test_thread, 105, 5):
154      _AddSliceToThread(test_thread, slice_item)
155
156    # Second frame is 20 seconds.
157    for slice_item in _CreateGPUSlices(test_thread, 'test_item', 110, 20):
158      _AddSliceToThread(test_thread, slice_item)
159
160    model.FinalizeImport()
161
162    metric = gpu_timeline.GPUTimelineMetric()
163    results = self.GetResults(metric, model=model, renderer_thread=test_thread,
164                              interaction_records=INTERACTION_RECORDS)
165
166    for name, source_type in (('swap', None),
167                              ('total', 'cpu'),
168                              ('total', 'gpu')):
169      results.AssertHasPageSpecificScalarValue(
170          gpu_timeline.TimelineName(name, source_type, 'max'), 'ms', 20)
171      results.AssertHasPageSpecificScalarValue(
172          gpu_timeline.TimelineName(name, source_type, 'mean'), 'ms', 15)
173      results.AssertHasPageSpecificScalarValue(
174          gpu_timeline.TimelineName(name, source_type, 'stddev'), 'ms', 5)
175
176  def testFrameSeparationBeforeMarker(self):
177    """Test frames are correctly calculated using the frame end marker."""
178    model = model_module.TimelineModel()
179    test_thread = model.GetOrCreateProcess(1).GetOrCreateThread(2)
180
181    # Mark frame end.
182    for slice_item in _CreateFrameEndSlices(test_thread, 105, 5):
183      _AddSliceToThread(test_thread, slice_item)
184
185    # First frame is 10 seconds.
186    for slice_item in _CreateGPUSlices(test_thread, 'test_item', 100, 10):
187      _AddSliceToThread(test_thread, slice_item)
188
189    # Second frame is 20 seconds.
190    for slice_item in _CreateGPUSlices(test_thread, 'test_item', 110, 20):
191      _AddSliceToThread(test_thread, slice_item)
192
193    model.FinalizeImport()
194
195    metric = gpu_timeline.GPUTimelineMetric()
196    results = self.GetResults(metric, model=model, renderer_thread=test_thread,
197                              interaction_records=INTERACTION_RECORDS)
198
199    for name, src_type in (('swap', None), ('total', 'cpu'), ('total', 'gpu')):
200      results.AssertHasPageSpecificScalarValue(
201          gpu_timeline.TimelineName(name, src_type, 'max'), 'ms', 20)
202      results.AssertHasPageSpecificScalarValue(
203          gpu_timeline.TimelineName(name, src_type, 'mean'), 'ms', 15)
204      results.AssertHasPageSpecificScalarValue(
205          gpu_timeline.TimelineName(name, src_type, 'stddev'), 'ms', 5)
206
207  def testTrackedNameTraces(self):
208    """Be sure tracked names are being recorded correctly."""
209    self.assertGreater(len(gpu_timeline.TRACKED_GL_CONTEXT_NAME), 0)
210
211    marker, result = gpu_timeline.TRACKED_GL_CONTEXT_NAME.iteritems().next()
212
213    model = model_module.TimelineModel()
214    test_thread = model.GetOrCreateProcess(1).GetOrCreateThread(2)
215    for slice_item in _CreateGPUSlices(test_thread, marker, 100, 10):
216      _AddSliceToThread(test_thread, slice_item)
217    model.FinalizeImport()
218
219    metric = gpu_timeline.GPUTimelineMetric()
220    results = self.GetResults(metric, model=model, renderer_thread=test_thread,
221                              interaction_records=INTERACTION_RECORDS)
222
223    for source_type in ('cpu', 'gpu'):
224      results.AssertHasPageSpecificScalarValue(
225          gpu_timeline.TimelineName(result, source_type, 'max'),
226          'ms', 10)
227      results.AssertHasPageSpecificScalarValue(
228          gpu_timeline.TimelineName(result, source_type, 'mean'),
229          'ms', 10)
230      results.AssertHasPageSpecificScalarValue(
231          gpu_timeline.TimelineName(result, source_type, 'stddev'),
232          'ms', 0)
233
234  def testTrackedNameWithContextIDTraces(self):
235    """Be sure tracked names with context IDs are recorded correctly."""
236    self.assertGreater(len(gpu_timeline.TRACKED_GL_CONTEXT_NAME), 0)
237
238    marker, result = gpu_timeline.TRACKED_GL_CONTEXT_NAME.iteritems().next()
239    context_id = '-0x1234'
240
241    model = model_module.TimelineModel()
242    test_thread = model.GetOrCreateProcess(1).GetOrCreateThread(2)
243    for slice_item in _CreateGPUSlices(test_thread, marker + context_id,
244                                       100, 10):
245      _AddSliceToThread(test_thread, slice_item)
246    model.FinalizeImport()
247
248    metric = gpu_timeline.GPUTimelineMetric()
249    results = self.GetResults(metric, model=model, renderer_thread=test_thread,
250                              interaction_records=INTERACTION_RECORDS)
251
252    for source_type in ('cpu', 'gpu'):
253      results.AssertHasPageSpecificScalarValue(
254          gpu_timeline.TimelineName(result, source_type, 'max'),
255          'ms', 10)
256      results.AssertHasPageSpecificScalarValue(
257          gpu_timeline.TimelineName(result, source_type, 'mean'),
258          'ms', 10)
259      results.AssertHasPageSpecificScalarValue(
260          gpu_timeline.TimelineName(result, source_type, 'stddev'),
261          'ms', 0)
262
263  def testOutOfOrderDeviceTraces(self):
264    """Out of order device traces are still matched up to correct services."""
265    self.assertGreaterEqual(len(gpu_timeline.TRACKED_GL_CONTEXT_NAME), 2)
266
267    tracked_names_iter = gpu_timeline.TRACKED_GL_CONTEXT_NAME.iteritems()
268    marker1_name, result1_name = tracked_names_iter.next()
269    result2_name = result1_name
270    while result2_name == result1_name:
271      marker2_name, result2_name = tracked_names_iter.next()
272
273    model = model_module.TimelineModel()
274    test_thread = model.GetOrCreateProcess(1).GetOrCreateThread(2)
275
276    # marker1 lasts for 10 seconds.
277    service_item1, device_item1 = _CreateGPUSlices(test_thread, marker1_name,
278                                                   100, 10)
279    # marker2 lasts for 20 seconds.
280    service_item2, device_item2 = _CreateGPUSlices(test_thread, marker2_name,
281                                                   200, 20)
282
283    # Append out of order
284    _AddSliceToThread(test_thread, service_item1)
285    _AddSliceToThread(test_thread, service_item2)
286    _AddSliceToThread(test_thread, device_item2)
287    _AddSliceToThread(test_thread, device_item1)
288
289    model.FinalizeImport()
290
291    metric = gpu_timeline.GPUTimelineMetric()
292    results = self.GetResults(metric, model=model, renderer_thread=test_thread,
293                              interaction_records=INTERACTION_RECORDS)
294
295    for source_type in ('cpu', 'gpu'):
296      results.AssertHasPageSpecificScalarValue(
297          gpu_timeline.TimelineName(result1_name, source_type, 'max'),
298          'ms', 10)
299      results.AssertHasPageSpecificScalarValue(
300          gpu_timeline.TimelineName(result1_name, source_type, 'mean'),
301          'ms', 10)
302      results.AssertHasPageSpecificScalarValue(
303          gpu_timeline.TimelineName(result1_name, source_type, 'stddev'),
304          'ms', 0)
305      results.AssertHasPageSpecificScalarValue(
306          gpu_timeline.TimelineName(result2_name, source_type, 'max'),
307          'ms', 20)
308      results.AssertHasPageSpecificScalarValue(
309          gpu_timeline.TimelineName(result2_name, source_type, 'mean'),
310          'ms', 20)
311      results.AssertHasPageSpecificScalarValue(
312          gpu_timeline.TimelineName(result2_name, source_type, 'stddev'),
313          'ms', 0)
314