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 random
6import unittest
7
8from telemetry.timeline import async_slice
9from telemetry.timeline import bounds
10from telemetry.timeline import model
11from telemetry.util import perf_tests_helper
12from telemetry.util import statistics
13from telemetry.web_perf.metrics import rendering_stats
14
15
16class MockTimer(object):
17  """A mock timer class which can generate random durations.
18
19  An instance of this class is used as a global timer to generate random
20  durations for stats and consistent timestamps for all mock trace events.
21  The unit of time is milliseconds.
22  """
23
24  def __init__(self):
25    self.milliseconds = 0
26
27  def Advance(self, low=0.1, high=1):
28    delta = random.uniform(low, high)
29    self.milliseconds += delta
30    return delta
31
32  def AdvanceAndGet(self, low=0.1, high=1):
33    self.Advance(low, high)
34    return self.milliseconds
35
36
37class MockVblankTimer(object):
38  """A mock vblank timer class which can generate random durations.
39
40  An instance of this class is used as a vblank timer to generate random
41  durations for drm stats and consistent timeval for mock trace drm events.
42  The unit of time is microseconds.
43  """
44
45  def __init__(self):
46    self.microseconds = 200000000
47
48  def TvAdvance(self, low=100, high=1000):
49    delta = random.randint(low, high)
50    self.microseconds += delta
51    return delta
52
53  def TvAdvanceAndGet(self, low=100, high=1000):
54    self.TvAdvance(low, high)
55    return self.microseconds
56
57
58class ReferenceRenderingStats(object):
59  """ Stores expected data for comparison with actual RenderingStats """
60
61  def __init__(self):
62    self.frame_timestamps = []
63    self.frame_times = []
64    self.approximated_pixel_percentages = []
65    self.checkerboarded_pixel_percentages = []
66
67  def AppendNewRange(self):
68    self.frame_timestamps.append([])
69    self.frame_times.append([])
70    self.approximated_pixel_percentages.append([])
71    self.checkerboarded_pixel_percentages.append([])
72
73
74class ReferenceInputLatencyStats(object):
75  """ Stores expected data for comparison with actual input latency stats """
76
77  def __init__(self):
78    self.input_event_latency = []
79    self.input_event = []
80
81
82def AddSurfaceFlingerStats(mock_timer, thread, first_frame,
83                           ref_stats=None):
84  """ Adds a random surface flinger stats event.
85
86  thread: The timeline model thread to which the event will be added.
87  first_frame: Is this the first frame within the bounds of an action?
88  ref_stats: A ReferenceRenderingStats object to record expected values.
89  """
90  # Create random data and timestamp for impl thread rendering stats.
91  data = {'frame_count': 1,
92          'refresh_period': 16.6666}
93  timestamp = mock_timer.AdvanceAndGet()
94
95  # Add a slice with the event data to the given thread.
96  thread.PushCompleteSlice(
97      'SurfaceFlinger', 'vsync_before',
98      timestamp, duration=0.0, thread_timestamp=None, thread_duration=None,
99      args={'data': data})
100
101  if not ref_stats:
102    return
103
104  # Add timestamp only if a frame was output
105  if data['frame_count'] == 1:
106    if not first_frame:
107      # Add frame_time if this is not the first frame in within the bounds of an
108      # action.
109      prev_timestamp = ref_stats.frame_timestamps[-1][-1]
110      ref_stats.frame_times[-1].append(timestamp - prev_timestamp)
111    ref_stats.frame_timestamps[-1].append(timestamp)
112
113
114def AddDrmEventFlipStats(mock_timer, vblank_timer, thread,
115                         first_frame, ref_stats=None):
116  """ Adds a random drm flip complete event.
117
118  thread: The timeline model thread to which the event will be added.
119  first_frame: Is this the first frame within the bounds of an action?
120  ref_stats: A ReferenceRenderingStats object to record expected values.
121  """
122  # Create random data and timestamp for drm thread flip complete stats.
123  vblank_timeval = vblank_timer.TvAdvanceAndGet()
124  vblank_tv_sec = vblank_timeval / 1000000
125  vblank_tv_usec = vblank_timeval % 1000000
126  data = {'frame_count': 1,
127          'vblank.tv_usec': vblank_tv_usec,
128          'vblank.tv_sec': vblank_tv_sec}
129  timestamp = mock_timer.AdvanceAndGet()
130
131  # Add a slice with the event data to the given thread.
132  thread.PushCompleteSlice(
133      'benchmark,drm', 'DrmEventFlipComplete',
134      timestamp, duration=0.0, thread_timestamp=None, thread_duration=None,
135      args={'data': data})
136
137  if not ref_stats:
138    return
139
140  # Add vblank timeval only if a frame was output.
141  cur_timestamp = vblank_tv_sec * 1000.0 + vblank_tv_usec / 1000.0
142  if not first_frame:
143    # Add frame_time if this is not the first frame in within the bounds of an
144    # action.
145    prev_timestamp = ref_stats.frame_timestamps[-1][-1]
146    ref_stats.frame_times[-1].append(cur_timestamp - prev_timestamp)
147  ref_stats.frame_timestamps[-1].append(cur_timestamp)
148
149
150def AddDisplayRenderingStats(mock_timer, thread, first_frame,
151                             ref_stats=None):
152  """ Adds a random display rendering stats event.
153
154  thread: The timeline model thread to which the event will be added.
155  first_frame: Is this the first frame within the bounds of an action?
156  ref_stats: A ReferenceRenderingStats object to record expected values.
157  """
158  # Create random data and timestamp for main thread rendering stats.
159  data = {'frame_count': 1}
160  timestamp = mock_timer.AdvanceAndGet()
161
162  # Add a slice with the event data to the given thread.
163  thread.PushCompleteSlice(
164      'benchmark', 'BenchmarkInstrumentation::DisplayRenderingStats',
165      timestamp, duration=0.0, thread_timestamp=None, thread_duration=None,
166      args={'data': data})
167
168  if not ref_stats:
169    return
170
171  # Add timestamp only if a frame was output
172  if not first_frame:
173    # Add frame_time if this is not the first frame in within the bounds of an
174    # action.
175    prev_timestamp = ref_stats.frame_timestamps[-1][-1]
176    ref_stats.frame_times[-1].append(timestamp - prev_timestamp)
177  ref_stats.frame_timestamps[-1].append(timestamp)
178
179
180def AddImplThreadRenderingStats(mock_timer, thread, first_frame,
181                                ref_stats=None):
182  """ Adds a random impl thread rendering stats event.
183
184  thread: The timeline model thread to which the event will be added.
185  first_frame: Is this the first frame within the bounds of an action?
186  ref_stats: A ReferenceRenderingStats object to record expected values.
187  """
188  # Create random data and timestamp for impl thread rendering stats.
189  data = {'frame_count': 1,
190          'visible_content_area': random.uniform(0, 100),
191          'approximated_visible_content_area': random.uniform(0, 5),
192          'checkerboarded_visible_content_area': random.uniform(0, 5)}
193  timestamp = mock_timer.AdvanceAndGet()
194
195  # Add a slice with the event data to the given thread.
196  thread.PushCompleteSlice(
197      'benchmark', 'BenchmarkInstrumentation::ImplThreadRenderingStats',
198      timestamp, duration=0.0, thread_timestamp=None, thread_duration=None,
199      args={'data': data})
200
201  if not ref_stats:
202    return
203
204  # Add timestamp only if a frame was output
205  if data['frame_count'] == 1:
206    if not first_frame:
207      # Add frame_time if this is not the first frame in within the bounds of an
208      # action.
209      prev_timestamp = ref_stats.frame_timestamps[-1][-1]
210      ref_stats.frame_times[-1].append(timestamp - prev_timestamp)
211    ref_stats.frame_timestamps[-1].append(timestamp)
212
213  ref_stats.approximated_pixel_percentages[-1].append(
214      round(statistics.DivideIfPossibleOrZero(
215          data['approximated_visible_content_area'],
216          data['visible_content_area']) * 100.0, 3))
217
218  ref_stats.checkerboarded_pixel_percentages[-1].append(
219      round(statistics.DivideIfPossibleOrZero(
220          data['checkerboarded_visible_content_area'],
221          data['visible_content_area']) * 100.0, 3))
222
223def AddInputLatencyStats(mock_timer, start_thread, end_thread,
224                         ref_latency_stats=None):
225  """ Adds a random input latency stats event.
226
227  start_thread: The start thread on which the async slice is added.
228  end_thread: The end thread on which the async slice is ended.
229  ref_latency_stats: A ReferenceInputLatencyStats object for expected values.
230  """
231
232  original_comp_time = mock_timer.AdvanceAndGet(2, 4) * 1000.0
233  ui_comp_time = mock_timer.AdvanceAndGet(2, 4) * 1000.0
234  begin_comp_time = mock_timer.AdvanceAndGet(2, 4) * 1000.0
235  forward_comp_time = mock_timer.AdvanceAndGet(2, 4) * 1000.0
236  end_comp_time = mock_timer.AdvanceAndGet(10, 20) * 1000.0
237
238  data = {rendering_stats.ORIGINAL_COMP_NAME: {'time': original_comp_time},
239          rendering_stats.UI_COMP_NAME: {'time': ui_comp_time},
240          rendering_stats.BEGIN_COMP_NAME: {'time': begin_comp_time},
241          rendering_stats.END_COMP_NAME: {'time': end_comp_time}}
242
243  timestamp = mock_timer.AdvanceAndGet(2, 4)
244
245  tracing_async_slice = async_slice.AsyncSlice(
246      'benchmark', 'InputLatency', timestamp)
247
248  async_sub_slice = async_slice.AsyncSlice(
249      'benchmark', rendering_stats.GESTURE_SCROLL_UPDATE_EVENT_NAME, timestamp)
250  async_sub_slice.args = {'data': data}
251  async_sub_slice.parent_slice = tracing_async_slice
252  async_sub_slice.start_thread = start_thread
253  async_sub_slice.end_thread = end_thread
254
255  tracing_async_slice.sub_slices.append(async_sub_slice)
256  tracing_async_slice.start_thread = start_thread
257  tracing_async_slice.end_thread = end_thread
258  start_thread.AddAsyncSlice(tracing_async_slice)
259
260  # Add scroll update latency info.
261  scroll_update_data = {
262      rendering_stats.BEGIN_SCROLL_UPDATE_COMP_NAME: {'time': begin_comp_time},
263      rendering_stats.FORWARD_SCROLL_UPDATE_COMP_NAME:
264          {'time': forward_comp_time},
265      rendering_stats.END_COMP_NAME: {'time': end_comp_time}
266  }
267
268  scroll_async_slice = async_slice.AsyncSlice(
269      'benchmark', 'InputLatency', timestamp)
270
271  scroll_async_sub_slice = async_slice.AsyncSlice(
272      'benchmark', rendering_stats.MAIN_THREAD_SCROLL_UPDATE_EVENT_NAME,
273      timestamp)
274  scroll_async_sub_slice.args = {'data': scroll_update_data}
275  scroll_async_sub_slice.parent_slice = scroll_async_slice
276  scroll_async_sub_slice.start_thread = start_thread
277  scroll_async_sub_slice.end_thread = end_thread
278
279  scroll_async_slice.sub_slices.append(scroll_async_sub_slice)
280  scroll_async_slice.start_thread = start_thread
281  scroll_async_slice.end_thread = end_thread
282  start_thread.AddAsyncSlice(scroll_async_slice)
283
284  # Also add some dummy frame statistics so we can feed the resulting timeline
285  # to RenderingStats.
286  AddImplThreadRenderingStats(mock_timer, end_thread, False)
287
288  if not ref_latency_stats:
289    return
290
291  ref_latency_stats.input_event.append(async_sub_slice)
292  ref_latency_stats.input_event.append(scroll_async_sub_slice)
293  ref_latency_stats.input_event_latency.append((
294      rendering_stats.GESTURE_SCROLL_UPDATE_EVENT_NAME,
295      (data[rendering_stats.END_COMP_NAME]['time'] -
296       data[rendering_stats.ORIGINAL_COMP_NAME]['time']) / 1000.0))
297  scroll_update_time = (
298      scroll_update_data[rendering_stats.END_COMP_NAME]['time'] -
299      scroll_update_data[rendering_stats.BEGIN_SCROLL_UPDATE_COMP_NAME]['time'])
300  ref_latency_stats.input_event_latency.append((
301      rendering_stats.MAIN_THREAD_SCROLL_UPDATE_EVENT_NAME,
302      scroll_update_time / 1000.0))
303
304
305class RenderingStatsUnitTest(unittest.TestCase):
306
307  def testHasRenderingStats(self):
308    timeline = model.TimelineModel()
309    timer = MockTimer()
310
311    # A process without rendering stats
312    process_without_stats = timeline.GetOrCreateProcess(pid=1)
313    thread_without_stats = process_without_stats.GetOrCreateThread(tid=11)
314    process_without_stats.FinalizeImport()
315    self.assertFalse(rendering_stats.HasRenderingStats(thread_without_stats))
316
317    # A process with rendering stats, but no frames in them
318    process_without_frames = timeline.GetOrCreateProcess(pid=2)
319    thread_without_frames = process_without_frames.GetOrCreateThread(tid=21)
320    process_without_frames.FinalizeImport()
321    self.assertFalse(rendering_stats.HasRenderingStats(thread_without_frames))
322
323    # A process with rendering stats and frames in them
324    process_with_frames = timeline.GetOrCreateProcess(pid=3)
325    thread_with_frames = process_with_frames.GetOrCreateThread(tid=31)
326    AddImplThreadRenderingStats(timer, thread_with_frames, True, None)
327    process_with_frames.FinalizeImport()
328    self.assertTrue(rendering_stats.HasRenderingStats(thread_with_frames))
329
330  def testHasDrmStats(self):
331    timeline = model.TimelineModel()
332    timer = MockTimer()
333    vblank_timer = MockVblankTimer()
334
335    # A process without drm stats
336    process_without_stats = timeline.GetOrCreateProcess(pid=5)
337    thread_without_stats = process_without_stats.GetOrCreateThread(tid=51)
338    process_without_stats.FinalizeImport()
339    self.assertFalse(rendering_stats.HasDrmStats(thread_without_stats))
340
341    # A process with drm stats and frames in them
342    process_with_frames = timeline.GetOrCreateProcess(pid=6)
343    thread_with_frames = process_with_frames.GetOrCreateThread(tid=61)
344    AddDrmEventFlipStats(timer, vblank_timer, thread_with_frames, True, None)
345    process_with_frames.FinalizeImport()
346    self.assertTrue(rendering_stats.HasDrmStats(thread_with_frames))
347
348  def testBothSurfaceFlingerAndDisplayStats(self):
349    timeline = model.TimelineModel()
350    timer = MockTimer()
351
352    ref_stats = ReferenceRenderingStats()
353    ref_stats.AppendNewRange()
354    surface_flinger = timeline.GetOrCreateProcess(pid=4)
355    surface_flinger.name = 'SurfaceFlinger'
356    surface_flinger_thread = surface_flinger.GetOrCreateThread(tid=41)
357    renderer = timeline.GetOrCreateProcess(pid=2)
358    browser = timeline.GetOrCreateProcess(pid=3)
359    browser_main = browser.GetOrCreateThread(tid=31)
360    browser_main.BeginSlice('webkit.console', 'ActionA',
361                            timer.AdvanceAndGet(2, 4), '')
362
363    # Create SurfaceFlinger stats and display rendering stats.
364    for i in xrange(0, 10):
365      first = (i == 0)
366      AddSurfaceFlingerStats(timer, surface_flinger_thread, first, ref_stats)
367      timer.Advance(2, 4)
368
369    for i in xrange(0, 10):
370      first = (i == 0)
371      AddDisplayRenderingStats(timer, browser_main, first, None)
372      timer.Advance(5, 10)
373
374    browser_main.EndSlice(timer.AdvanceAndGet())
375    timer.Advance(2, 4)
376
377    browser.FinalizeImport()
378    renderer.FinalizeImport()
379    timeline_markers = timeline.FindTimelineMarkers(['ActionA'])
380    timeline_ranges = [bounds.Bounds.CreateFromEvent(marker)
381                       for marker in timeline_markers]
382    stats = rendering_stats.RenderingStats(
383        renderer, browser, surface_flinger, None, timeline_ranges)
384
385    # Compare rendering stats to reference - Only SurfaceFlinger stats should
386    # count
387    self.assertEquals(stats.frame_timestamps, ref_stats.frame_timestamps)
388    self.assertEquals(stats.frame_times, ref_stats.frame_times)
389
390  def testBothDrmAndDisplayStats(self):
391    timeline = model.TimelineModel()
392    timer = MockTimer()
393    vblank_timer = MockVblankTimer()
394
395    ref_stats = ReferenceRenderingStats()
396    ref_stats.AppendNewRange()
397    gpu = timeline.GetOrCreateProcess(pid=6)
398    gpu.name = 'GPU Process'
399    gpu_drm_thread = gpu.GetOrCreateThread(tid=61)
400    renderer = timeline.GetOrCreateProcess(pid=2)
401    browser = timeline.GetOrCreateProcess(pid=3)
402    browser_main = browser.GetOrCreateThread(tid=31)
403    browser_main.BeginSlice('webkit.console', 'ActionA',
404                            timer.AdvanceAndGet(2, 4), '')
405    vblank_timer.TvAdvance(2000, 4000)
406
407    # Create drm flip stats and display rendering stats.
408    for i in xrange(0, 10):
409      first = (i == 0)
410      AddDrmEventFlipStats(timer, vblank_timer, gpu_drm_thread,
411                           first, ref_stats)
412      timer.Advance(2, 4)
413      vblank_timer.TvAdvance(2000, 4000)
414
415    for i in xrange(0, 10):
416      first = (i == 0)
417      AddDisplayRenderingStats(timer, browser_main, first, None)
418      timer.Advance(5, 10)
419      vblank_timer.TvAdvance(5000, 10000)
420
421    browser_main.EndSlice(timer.AdvanceAndGet())
422    timer.Advance(2, 4)
423    vblank_timer.TvAdvance(2000, 4000)
424
425    browser.FinalizeImport()
426    renderer.FinalizeImport()
427    timeline_markers = timeline.FindTimelineMarkers(['ActionA'])
428    timeline_ranges = [bounds.Bounds.CreateFromEvent(marker)
429                       for marker in timeline_markers]
430    stats = rendering_stats.RenderingStats(
431        renderer, browser, None, gpu, timeline_ranges)
432
433    # Compare rendering stats to reference - Only drm flip stats should
434    # count
435    self.assertEquals(stats.frame_timestamps, ref_stats.frame_timestamps)
436    self.assertEquals(stats.frame_times, ref_stats.frame_times)
437
438  def testBothDisplayAndImplStats(self):
439    timeline = model.TimelineModel()
440    timer = MockTimer()
441
442    ref_stats = ReferenceRenderingStats()
443    ref_stats.AppendNewRange()
444    renderer = timeline.GetOrCreateProcess(pid=2)
445    browser = timeline.GetOrCreateProcess(pid=3)
446    browser_main = browser.GetOrCreateThread(tid=31)
447    browser_main.BeginSlice('webkit.console', 'ActionA',
448                            timer.AdvanceAndGet(2, 4), '')
449
450    # Create main, impl, and display rendering stats.
451    for i in xrange(0, 10):
452      first = (i == 0)
453      AddImplThreadRenderingStats(timer, browser_main, first, None)
454      timer.Advance(2, 4)
455
456    for i in xrange(0, 10):
457      first = (i == 0)
458      AddDisplayRenderingStats(timer, browser_main, first, ref_stats)
459      timer.Advance(5, 10)
460
461    browser_main.EndSlice(timer.AdvanceAndGet())
462    timer.Advance(2, 4)
463
464    browser.FinalizeImport()
465    renderer.FinalizeImport()
466    timeline_markers = timeline.FindTimelineMarkers(['ActionA'])
467    timeline_ranges = [bounds.Bounds.CreateFromEvent(marker)
468                       for marker in timeline_markers]
469    stats = rendering_stats.RenderingStats(
470        renderer, browser, None, None, timeline_ranges)
471
472    # Compare rendering stats to reference - Only display stats should count
473    self.assertEquals(stats.frame_timestamps, ref_stats.frame_timestamps)
474    self.assertEquals(stats.frame_times, ref_stats.frame_times)
475
476  def testRangeWithoutFrames(self):
477    timer = MockTimer()
478    timeline = model.TimelineModel()
479
480    # Create a renderer process, with a main thread and impl thread.
481    renderer = timeline.GetOrCreateProcess(pid=2)
482    renderer_main = renderer.GetOrCreateThread(tid=21)
483    renderer_compositor = renderer.GetOrCreateThread(tid=22)
484
485    # Create 10 main and impl rendering stats events for Action A.
486    renderer_main.BeginSlice('webkit.console', 'ActionA',
487                             timer.AdvanceAndGet(2, 4), '')
488    for i in xrange(0, 10):
489      first = (i == 0)
490      AddImplThreadRenderingStats(timer, renderer_compositor, first, None)
491    renderer_main.EndSlice(timer.AdvanceAndGet(2, 4))
492    timer.Advance(2, 4)
493
494    # Create 5 main and impl rendering stats events not within any action.
495    for i in xrange(0, 5):
496      first = (i == 0)
497      AddImplThreadRenderingStats(timer, renderer_compositor, first, None)
498
499    # Create Action B without any frames. This should trigger
500    # NotEnoughFramesError when the RenderingStats object is created.
501    renderer_main.BeginSlice('webkit.console', 'ActionB',
502                             timer.AdvanceAndGet(2, 4), '')
503    renderer_main.EndSlice(timer.AdvanceAndGet(2, 4))
504
505    renderer.FinalizeImport()
506
507    timeline_markers = timeline.FindTimelineMarkers(['ActionA', 'ActionB'])
508    timeline_ranges = [bounds.Bounds.CreateFromEvent(marker)
509                       for marker in timeline_markers]
510
511    stats = rendering_stats.RenderingStats(
512        renderer, None, None, None, timeline_ranges)
513    self.assertEquals(0, len(stats.frame_timestamps[1]))
514
515  def testFromTimeline(self):
516    timeline = model.TimelineModel()
517
518    # Create a browser process and a renderer process, and a main thread and
519    # impl thread for each.
520    browser = timeline.GetOrCreateProcess(pid=1)
521    browser_compositor = browser.GetOrCreateThread(tid=12)
522    renderer = timeline.GetOrCreateProcess(pid=2)
523    renderer_main = renderer.GetOrCreateThread(tid=21)
524    renderer_compositor = renderer.GetOrCreateThread(tid=22)
525
526    timer = MockTimer()
527    renderer_ref_stats = ReferenceRenderingStats()
528    browser_ref_stats = ReferenceRenderingStats()
529
530    # Create 10 main and impl rendering stats events for Action A.
531    renderer_main.BeginSlice('webkit.console', 'ActionA',
532                             timer.AdvanceAndGet(2, 4), '')
533    renderer_ref_stats.AppendNewRange()
534    browser_ref_stats.AppendNewRange()
535    for i in xrange(0, 10):
536      first = (i == 0)
537      AddImplThreadRenderingStats(
538          timer, renderer_compositor, first, renderer_ref_stats)
539      AddImplThreadRenderingStats(
540          timer, browser_compositor, first, browser_ref_stats)
541    renderer_main.EndSlice(timer.AdvanceAndGet(2, 4))
542
543    # Create 5 main and impl rendering stats events not within any action.
544    for i in xrange(0, 5):
545      first = (i == 0)
546      AddImplThreadRenderingStats(timer, renderer_compositor, first, None)
547      AddImplThreadRenderingStats(timer, browser_compositor, first, None)
548
549    # Create 10 main and impl rendering stats events for Action B.
550    renderer_main.BeginSlice('webkit.console', 'ActionB',
551                             timer.AdvanceAndGet(2, 4), '')
552    renderer_ref_stats.AppendNewRange()
553    browser_ref_stats.AppendNewRange()
554    for i in xrange(0, 10):
555      first = (i == 0)
556      AddImplThreadRenderingStats(
557          timer, renderer_compositor, first, renderer_ref_stats)
558      AddImplThreadRenderingStats(
559          timer, browser_compositor, first, browser_ref_stats)
560    renderer_main.EndSlice(timer.AdvanceAndGet(2, 4))
561
562    # Create 10 main and impl rendering stats events for Action A.
563    renderer_main.BeginSlice('webkit.console', 'ActionA',
564                             timer.AdvanceAndGet(2, 4), '')
565    renderer_ref_stats.AppendNewRange()
566    browser_ref_stats.AppendNewRange()
567    for i in xrange(0, 10):
568      first = (i == 0)
569      AddImplThreadRenderingStats(
570          timer, renderer_compositor, first, renderer_ref_stats)
571      AddImplThreadRenderingStats(
572          timer, browser_compositor, first, browser_ref_stats)
573    renderer_main.EndSlice(timer.AdvanceAndGet(2, 4))
574    timer.Advance(2, 4)
575
576    browser.FinalizeImport()
577    renderer.FinalizeImport()
578
579    timeline_markers = timeline.FindTimelineMarkers(
580        ['ActionA', 'ActionB', 'ActionA'])
581    timeline_ranges = [bounds.Bounds.CreateFromEvent(marker)
582                       for marker in timeline_markers]
583    stats = rendering_stats.RenderingStats(
584        renderer, browser, None, None, timeline_ranges)
585
586    # Compare rendering stats to reference.
587    self.assertEquals(stats.frame_timestamps,
588                      browser_ref_stats.frame_timestamps)
589    self.assertEquals(stats.frame_times, browser_ref_stats.frame_times)
590    self.assertEquals(stats.approximated_pixel_percentages,
591                      renderer_ref_stats.approximated_pixel_percentages)
592    self.assertEquals(stats.checkerboarded_pixel_percentages,
593                      renderer_ref_stats.checkerboarded_pixel_percentages)
594
595  def testInputLatencyFromTimeline(self):
596    timeline = model.TimelineModel()
597
598    # Create a browser process and a renderer process.
599    browser = timeline.GetOrCreateProcess(pid=1)
600    browser_main = browser.GetOrCreateThread(tid=11)
601    renderer = timeline.GetOrCreateProcess(pid=2)
602    renderer_main = renderer.GetOrCreateThread(tid=21)
603
604    timer = MockTimer()
605    ref_latency = ReferenceInputLatencyStats()
606
607    # Create 10 input latency stats events for Action A.
608    renderer_main.BeginSlice('webkit.console', 'ActionA',
609                             timer.AdvanceAndGet(2, 4), '')
610    for _ in xrange(0, 10):
611      AddInputLatencyStats(timer, browser_main, renderer_main, ref_latency)
612    renderer_main.EndSlice(timer.AdvanceAndGet(2, 4))
613
614    # Create 5 input latency stats events not within any action.
615    timer.Advance(2, 4)
616    for _ in xrange(0, 5):
617      AddInputLatencyStats(timer, browser_main, renderer_main, None)
618
619    # Create 10 input latency stats events for Action B.
620    renderer_main.BeginSlice('webkit.console', 'ActionB',
621                             timer.AdvanceAndGet(2, 4), '')
622    for _ in xrange(0, 10):
623      AddInputLatencyStats(timer, browser_main, renderer_main, ref_latency)
624    renderer_main.EndSlice(timer.AdvanceAndGet(2, 4))
625
626    # Create 10 input latency stats events for Action A.
627    renderer_main.BeginSlice('webkit.console', 'ActionA',
628                             timer.AdvanceAndGet(2, 4), '')
629    for _ in xrange(0, 10):
630      AddInputLatencyStats(timer, browser_main, renderer_main, ref_latency)
631    renderer_main.EndSlice(timer.AdvanceAndGet(2, 4))
632
633    browser.FinalizeImport()
634    renderer.FinalizeImport()
635
636    latency_events = []
637
638    timeline_markers = timeline.FindTimelineMarkers(
639        ['ActionA', 'ActionB', 'ActionA'])
640    timeline_ranges = [bounds.Bounds.CreateFromEvent(marker)
641                       for marker in timeline_markers]
642    for timeline_range in timeline_ranges:
643      if timeline_range.is_empty:
644        continue
645      latency_events.extend(rendering_stats.GetLatencyEvents(
646          browser, timeline_range))
647
648    self.assertEquals(latency_events, ref_latency.input_event)
649    event_latency_result = rendering_stats.ComputeEventLatencies(latency_events)
650    self.assertEquals(event_latency_result,
651                      ref_latency.input_event_latency)
652
653    stats = rendering_stats.RenderingStats(
654        renderer, browser, None, None, timeline_ranges)
655    self.assertEquals(
656        perf_tests_helper.FlattenList(stats.input_event_latency),
657        [latency for name, latency in ref_latency.input_event_latency
658         if name != rendering_stats.MAIN_THREAD_SCROLL_UPDATE_EVENT_NAME])
659    self.assertEquals(
660        perf_tests_helper.FlattenList(stats.main_thread_scroll_latency),
661        [latency for name, latency in ref_latency.input_event_latency
662         if name == rendering_stats.MAIN_THREAD_SCROLL_UPDATE_EVENT_NAME])
663    self.assertEquals(
664        perf_tests_helper.FlattenList(stats.gesture_scroll_update_latency),
665        [latency for name, latency in ref_latency.input_event_latency
666         if name == rendering_stats.GESTURE_SCROLL_UPDATE_EVENT_NAME])
667