blob: 59d830ac07dca8882a2dd54a621a7551db96de82 [file] [log] [blame]
# Copyright 2013 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import unittest
import random
from metrics import smoothness
from metrics.gpu_rendering_stats import GpuRenderingStats
from telemetry.page import page
from telemetry.page.page_measurement_results import PageMeasurementResults
from telemetry.core.backends.chrome.tracing_backend import RawTraceResultImpl
from telemetry.core.backends.chrome.trace_result import TraceResult
class MockTimer(object):
""" An instance of this class is used as a global timer to generate
random durations for stats and consistent timestamps for all mock trace
events.
"""
def __init__(self):
self.microseconds = 0
def Advance(self, low = 0, high = 100000):
duration = random.randint(low, high)
self.microseconds += duration
return duration
class MockFrame(object):
""" This class mocks rendering stats, texture stats and latency stats for
a single frame
"""
def __init__(self, mock_timer):
""" Initialize the stats to random values """
self.start = mock_timer.microseconds
self.main_stats = {}
self.impl_stats = {}
self.texture_stats = {}
self.latency_stats = {}
self.main_stats["animation_frame_count"] = 0
self.main_stats['screen_frame_count'] = 0
self.main_stats['paint_time'] = mock_timer.Advance()
self.main_stats['record_time'] = mock_timer.Advance()
self.main_stats['commit_time'] = mock_timer.Advance()
self.main_stats['commit_count'] = 1
self.main_stats['painted_pixel_count'] = random.randint(0, 2000000)
self.main_stats['recorded_pixel_count'] = random.randint(0, 2000000)
self.main_stats['image_gathering_count'] = random.randint(0, 100)
self.main_stats['image_gathering_time'] = mock_timer.Advance()
self.impl_stats['screen_frame_count'] = 1
self.impl_stats['dropped_frame_count'] = random.randint(0, 4)
self.impl_stats['rasterize_time'] = mock_timer.Advance()
self.impl_stats['rasterize_time_for_now_bins_on_pending_tree'] = \
mock_timer.Advance()
self.impl_stats['best_rasterize_time'] = mock_timer.Advance()
self.impl_stats['rasterized_pixel_count'] = random.randint(0, 2000000)
self.impl_stats['impl_thread_scroll_count'] = random.randint(0, 4)
self.impl_stats['main_thread_scroll_count'] = random.randint(0, 4)
self.impl_stats['drawn_layer_count'] = random.randint(0, 10)
self.impl_stats['missing_tile_count'] = random.randint(0, 10000)
self.impl_stats['deferred_image_decode_count'] = random.randint(0, 100)
self.impl_stats['deferred_image_cache_hit_count'] = random.randint(0, 50)
self.impl_stats['tile_analysis_count'] = random.randint(0, 10000)
self.impl_stats['solid_color_tile_analysis_count'] = random.randint(0, 1000)
self.impl_stats['deferred_image_decode_time'] = mock_timer.Advance()
self.impl_stats['tile_analysis_time'] = mock_timer.Advance()
self.texture_stats['texture_upload_count'] = random.randint(0, 1000)
self.texture_stats['texture_upload_time'] = mock_timer.Advance()
self.latency_stats['input_event_count'] = random.randint(0, 20)
self.latency_stats['input_event_latency'] = mock_timer.Advance()
self.latency_stats['touch_ui_count'] = random.randint(0, 20)
self.latency_stats['touch_ui_latency'] = mock_timer.Advance()
self.latency_stats['touch_acked_count'] = random.randint(0, 20)
self.latency_stats['touch_acked_latency'] = mock_timer.Advance()
self.latency_stats['scroll_update_count'] = random.randint(0, 20)
self.latency_stats['scroll_update_latency'] = mock_timer.Advance()
self.end = mock_timer.microseconds
self.duration = self.end - self.start
def AddToRenderingStats(self, rendering_stats):
""" Add the stats for this frame to a mock RenderingStats object (i.e. a
dictionary)
"""
rs = rendering_stats
rs['totalTimeInSeconds'] += self.duration / 1e6
rs['numFramesSentToScreen'] += (self.main_stats['screen_frame_count'] +
self.impl_stats['screen_frame_count'])
rs['droppedFrameCount'] += self.impl_stats['dropped_frame_count']
rs['numImplThreadScrolls'] += self.impl_stats['impl_thread_scroll_count']
rs['numMainThreadScrolls'] += self.impl_stats['main_thread_scroll_count']
rs['numLayersDrawn'] += self.impl_stats['drawn_layer_count']
rs['numMissingTiles'] += self.impl_stats['missing_tile_count']
rs['textureUploadCount'] += self.texture_stats['texture_upload_count']
rs['totalTextureUploadTimeInSeconds'] += \
self.texture_stats['texture_upload_time']
rs['totalCommitCount'] += self.main_stats['commit_count']
rs['totalCommitTimeInSeconds'] += self.main_stats['commit_time']
rs['totalDeferredImageDecodeCount'] += self.impl_stats[
'deferred_image_decode_count']
rs['totalDeferredImageDecodeTimeInSeconds'] += self.impl_stats[
'deferred_image_decode_time']
rs['totalDeferredImageCacheHitCount'] += self.impl_stats[
'deferred_image_cache_hit_count']
rs['totalImageGatheringCount'] += self.main_stats['image_gathering_count']
rs['totalImageGatheringTimeInSeconds'] += self.main_stats[
'image_gathering_time']
rs['totalTilesAnalyzed'] += self.impl_stats['tile_analysis_count']
rs['totalTileAnalysisTimeInSeconds'] += self.impl_stats[
'tile_analysis_time']
rs['solidColorTilesAnalyzed'] += self.impl_stats[
'solid_color_tile_analysis_count']
rs['inputEventCount'] += self.latency_stats['input_event_count']
rs['totalInputLatency'] += self.latency_stats['input_event_latency']
rs['touchUICount'] += self.latency_stats['touch_ui_count']
rs['totalTouchUILatency'] += self.latency_stats['touch_ui_latency']
rs['touchAckedCount'] += self.latency_stats['touch_acked_count']
rs['totalTouchAckedLatency'] += self.latency_stats['touch_acked_latency']
rs['scrollUpdateCount'] += self.latency_stats['scroll_update_count']
rs['totalScrollUpdateLatency'] += self.latency_stats[
'scroll_update_latency']
def AppendTraceEventForMainThreadStats(self, trace):
""" Append a trace event with the main thread stats to trace """
event = {'name': 'MainThreadRenderingStats::IssueTraceEvent',
'tts': self.end,
'pid': 20978,
'ts': self.end,
'cat': 'benchmark',
's': 't',
'tid': 11,
'ph': 'i',
'args': {'data': self.main_stats}}
trace.append(event)
def AppendTraceEventForImplThreadStats(self, trace):
""" Append a trace event with the impl thread stat to trace """
event = {'name': 'ImplThreadRenderingStats::IssueTraceEvent',
'tts': self.end,
'pid': 20978,
'ts': self.end,
'cat': 'benchmark',
's': 't',
'tid': 11,
'ph': 'i',
'args': {'data': self.impl_stats}}
trace.append(event)
class SmoothnessMetricsUnitTest(unittest.TestCase):
def FindTimelineMarker(self, timeline):
events = [s for
s in timeline.GetAllEventsOfName(
smoothness.TIMELINE_MARKER)
if s.parent_slice == None]
if len(events) != 1:
raise LookupError, 'timeline marker not found'
return events[0]
def testCalcResultsTraceEvents(self):
# Make the test repeatable by seeding the random number generator
random.seed(1234567)
mock_timer = MockTimer()
trace = []
rendering_stats = {
'totalTimeInSeconds': 0.0,
'numFramesSentToScreen': 0.0,
'droppedFrameCount': 0.0,
'numImplThreadScrolls': 0.0,
'numMainThreadScrolls': 0.0,
'numLayersDrawn': 0.0,
'numMissingTiles': 0.0,
'textureUploadCount': 0.0,
'totalTextureUploadTimeInSeconds': 0.0,
'totalCommitCount': 0.0,
'totalCommitTimeInSeconds': 0.0,
'totalDeferredImageDecodeCount': 0.0,
'totalDeferredImageDecodeTimeInSeconds': 0.0,
'totalDeferredImageCacheHitCount': 0.0,
'totalImageGatheringCount': 0.0,
'totalImageGatheringTimeInSeconds': 0.0,
'totalTilesAnalyzed': 0.0,
'totalTileAnalysisTimeInSeconds': 0.0,
'solidColorTilesAnalyzed': 0.0,
'inputEventCount': 0.0,
'totalInputLatency': 0.0,
'touchUICount': 0.0,
'totalTouchUILatency': 0.0,
'touchAckedCount': 0.0,
'totalTouchAckedLatency': 0.0,
'scrollUpdateCount': 0.0,
'totalScrollUpdateLatency': 0.0}
# Append a start trace event for the timeline marker
trace.append({'name': smoothness.TIMELINE_MARKER,
'tts': mock_timer.microseconds,
'args': {},
'pid': 20978,
'ts': mock_timer.microseconds,
'cat': 'webkit',
'tid': 11,
'ph': 'S',
'id': '0xafb37737e249e055'})
# Generate 100 random mock frames, append their trace events, and accumulate
# stats in rendering_stats.
for _ in xrange(0, 100):
mock_frame = MockFrame(mock_timer)
mock_frame.AppendTraceEventForMainThreadStats(trace)
mock_frame.AppendTraceEventForImplThreadStats(trace)
mock_frame.AddToRenderingStats(rendering_stats)
# Append finish trace event for timeline marker
trace.append({'name': smoothness.TIMELINE_MARKER,
'tts': mock_timer.microseconds,
'args': {},
'pid': 20978,
'ts': mock_timer.microseconds,
'cat': 'webkit',
'tid': 11,
'ph': 'F',
'id': '0xafb37737e249e055'})
# Create timeline object from the trace
trace_impl = RawTraceResultImpl(trace)
trace_result = TraceResult(trace_impl)
timeline = trace_result.AsTimelineModel()
timeline_marker = self.FindTimelineMarker(timeline)
stats = GpuRenderingStats(timeline_marker,
rendering_stats,
True)
res = PageMeasurementResults()
res.WillMeasurePage(page.Page('http://foo.com/', None))
smoothness.CalcResults(stats, res)
res.DidMeasurePage()
rs = rendering_stats
# Scroll Results
self.assertAlmostEquals(
round(rs['totalTimeInSeconds'] / rs['numFramesSentToScreen'] * 1000.0,
3),
res.page_results[0]['mean_frame_time'].value, 2)
self.assertAlmostEquals(
round(rs['droppedFrameCount'] / rs['numFramesSentToScreen'] * 100.0, 1),
res.page_results[0]['dropped_percent'].value)
self.assertAlmostEquals(
round(rs['numImplThreadScrolls'] / (rs['numImplThreadScrolls'] +
rs['numMainThreadScrolls']) * 100.0,
1),
res.page_results[0]['percent_impl_scrolled'].value)
self.assertAlmostEquals(
round(rs['numLayersDrawn'] / rs['numFramesSentToScreen'], 1),
res.page_results[0]['average_num_layers_drawn'].value)
self.assertAlmostEquals(
round(rs['numMissingTiles'] / rs['numFramesSentToScreen'], 1),
res.page_results[0]['average_num_missing_tiles'].value)
# Texture Upload Results
self.assertAlmostEquals(
round(rs['totalCommitTimeInSeconds'] / rs['totalCommitCount'] * 1000.0,
3),
res.page_results[0]['average_commit_time'].value)
self.assertEquals(
rs['textureUploadCount'],
res.page_results[0]['texture_upload_count'].value)
self.assertEquals(
rs['totalTextureUploadTimeInSeconds'],
res.page_results[0]['total_texture_upload_time'].value)
# Image Decoding Results
self.assertEquals(
rs['totalDeferredImageDecodeCount'],
res.page_results[0]['total_deferred_image_decode_count'].value)
self.assertEquals(
rs['totalDeferredImageCacheHitCount'],
res.page_results[0]['total_image_cache_hit_count'].value)
self.assertAlmostEquals(
round(rs['totalImageGatheringTimeInSeconds'] /
rs['totalImageGatheringCount'] * 1000.0, 3),
res.page_results[0]['average_image_gathering_time'].value)
self.assertEquals(
rs['totalDeferredImageDecodeTimeInSeconds'],
res.page_results[0]['total_deferred_image_decoding_time'].value)
# Tile Analysis Results
self.assertEquals(
rs['totalTilesAnalyzed'],
res.page_results[0]['total_tiles_analyzed'].value)
self.assertEquals(
rs['solidColorTilesAnalyzed'],
res.page_results[0]['solid_color_tiles_analyzed'].value)
self.assertAlmostEquals(
round(rs['totalTileAnalysisTimeInSeconds'] /
rs['totalTilesAnalyzed'] * 1000.0, 3),
res.page_results[0]['average_tile_analysis_time'].value)
# Latency Results
self.assertAlmostEquals(
round(rs['totalInputLatency'] / rs['inputEventCount'] * 1000.0, 3),
res.page_results[0]['average_latency'].value)
self.assertAlmostEquals(
round(rs['totalTouchUILatency'] / rs['touchUICount'] * 1000.0, 3),
res.page_results[0]['average_touch_ui_latency'].value)
self.assertAlmostEquals(
round(rs['totalTouchAckedLatency'] / rs['touchAckedCount'] * 1000.0, 3),
res.page_results[0]['average_touch_acked_latency'].value)
self.assertAlmostEquals(
round(rs['totalScrollUpdateLatency'] / rs['scrollUpdateCount'] * 1000.0,
3),
res.page_results[0]['average_scroll_update_latency'].value)