Skip to content

Commit

Permalink
Adding telemetry tests for touch text selection performance.
Browse files Browse the repository at this point in the history
This CL adds a new page set |text_selection_sites|, which contains pages from
top_10_mobile page set and peforms text selection and selection handle drag on
the pages.

There's two benchmarks: one for "direction" selection strategy, and another one
for "character" selection strategy.

BUG=497779
CQ_EXTRA_TRYBOTS=tryserver.chromium.perf:linux_perf_bisect;tryserver.chromium.perf:mac_perf_bisect;tryserver.chromium.perf:win_perf_bisect

Review URL: https://codereview.chromium.org/1190423003

Cr-Commit-Position: refs/heads/master@{#341124}
  • Loading branch information
mfomitchev authored and Commit bot committed Jul 30, 2015
1 parent 23b1291 commit 715cd9f
Show file tree
Hide file tree
Showing 9 changed files with 370 additions and 92 deletions.
2 changes: 2 additions & 0 deletions tools/perf/benchmarks/benchmark_smoke_unittest.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
from benchmarks import spaceport
from benchmarks import speedometer
from benchmarks import sunspider
from benchmarks import text_selection


def SmokeTestGenerator(benchmark):
Expand Down Expand Up @@ -84,6 +85,7 @@ def CreateStorySet(self, options):
spaceport, # Takes 451 seconds.
speedometer, # Takes 101 seconds.
jetstream, # Take 206 seconds.
text_selection, # Always fails on cq bot.
}

# Some smoke benchmark tests that run quickly on desktop platform can be very
Expand Down
62 changes: 62 additions & 0 deletions tools/perf/benchmarks/text_selection.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# Copyright 2014 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.

from core import perf_benchmark

from telemetry import benchmark
from telemetry.timeline import tracing_category_filter
from telemetry.web_perf import timeline_based_measurement

import page_sets

TEXT_SELECTION_CATEGORY = 'blink'
TIMELINE_REQUIRED_CATEGORY = 'blink.console'


class _TextSelection(perf_benchmark.PerfBenchmark):
page_set = page_sets.TextSelectionSitesPageSet

def CreateTimelineBasedMeasurementOptions(self):
cat_filter = tracing_category_filter.CreateMinimalOverheadFilter()
cat_filter.AddIncludedCategory(TEXT_SELECTION_CATEGORY)
cat_filter.AddIncludedCategory(TIMELINE_REQUIRED_CATEGORY)

return timeline_based_measurement.Options(
overhead_level=cat_filter)

@classmethod
def Name(cls):
return 'text_selection'

@classmethod
def ValueCanBeAddedPredicate(cls, value, is_first_result):
if 'text-selection' not in value.name:
return False
return value.values != None


@benchmark.Enabled('android')
class TextSelectionDirection(_TextSelection):
"""Measure text selection metrics while dragging a touch selection handle on a
subset of top ten mobile sites and using the 'direction' touch selection
strategy."""
def SetExtraBrowserOptions(self, options):
options.AppendExtraBrowserArgs(['--touch-selection-strategy=direction'])

@classmethod
def Name(cls):
return 'text_selection.direction'


@benchmark.Enabled('android')
class TextSelectionCharacter(_TextSelection):
"""Measure text selection metrics while dragging a touch selection handle on a
subset of top ten mobile sites and using the 'character' touch selection
strategy."""
def SetExtraBrowserOptions(self, options):
options.AppendExtraBrowserArgs(['--touch-selection-strategy=character'])

@classmethod
def Name(cls):
return 'text_selection.character'
163 changes: 163 additions & 0 deletions tools/perf/page_sets/text_selection_sites.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
# Copyright 2014 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.
from telemetry import story
from telemetry.page import page as page_module
from telemetry.page import shared_page_state


class SimplePage(page_module.Page):

def __init__(self, url, page_set):
super(SimplePage, self).__init__(
url=url,
page_set=page_set,
shared_page_state_class=shared_page_state.SharedPageState,
credentials_path='data/credentials.json')
self.archive_data_file = 'data/text_selection_sites.json'

def RunNavigateSteps(self, action_runner):
super(SimplePage, self).RunNavigateSteps(action_runner)
action_runner.WaitForJavaScriptCondition(
'document.readyState == "complete"')


class SimpleTextSelectionPage(SimplePage):

def __init__(self, url, page_set):
super(SimpleTextSelectionPage, self).__init__(url=url, page_set=page_set)

def RunPageInteractions(self, action_runner):
# Create a fixed position div in the top left corner of the page, and
# another one in the bottom right corner of the page.
# Select the text within the first div.
action_runner.ExecuteJavaScript('''
(function() {
var text_div = document.createElement('div');
var text_div_2 = document.createElement('div');
text_div.style.fontSize = text_div_2.style.fontSize = "10vh";
text_div.style.lineHeight = text_div_2.style.lineHeight = "normal";
text_div.style.color = text_div_2.style.color = "red";
text_div.style.zIndex = text_div_2.style.zIndex = "1000";
text_div.style.position = text_div_2.style.position = "fixed";
text_div.style.left = "10%";
text_div.style.top = "10%";
text_div_2.style.right="0";
text_div_2.style.bottom="2%";
text_div.id="text-for-perf-test";
text_div_2.id="text-for-perf-test-2";
text_div.innerText="Hello";
text_div_2.innerText="World";
document.body.insertBefore(text_div, document.body.firstChild);
document.body.appendChild(text_div_2);
var selection = window.getSelection();
var textNode = text_div.childNodes[0];
selection.setBaseAndExtent(textNode, 0, textNode, 5);
window.requestAnimationFrame(function() {
text_div.style.color="green";
});
})();''')

# Wait two frames so that the selection information is sent to chromium
# and it is able to process input events interacting with selection.
action_runner.WaitForJavaScriptCondition(
'document.getElementById("text-for-perf-test").style.color == "green"')
action_runner.ExecuteJavaScript('''
window.requestAnimationFrame(function() {
document.getElementById("text-for-perf-test").style.color="red";
});
''')
action_runner.WaitForJavaScriptCondition(
'document.getElementById("text-for-perf-test").style.color == "red"')

# Confirm that the selection is set correctly.
text = action_runner.EvaluateJavaScript('window.getSelection().toString()')
assert text == "Hello"

# Tap on the selected text to make the handles show up.
with action_runner.CreateGestureInteraction('TapAction'):
action_runner.TapElement('#text-for-perf-test')

text_div_bottom = float(action_runner.EvaluateJavaScript('''
document.getElementById("text-for-perf-test").getClientRects()[0].bottom
'''))
text_div_2_bottom = float(action_runner.EvaluateJavaScript('''
document.getElementById(
"text-for-perf-test-2").getClientRects()[0].bottom
'''))
body_rect_str = action_runner.EvaluateJavaScript('''
var r = window.__GestureCommon_GetBoundingVisibleRect(document.body);
r.left + " " + r.top + " " + r.height + " " + r.width;
''')
body_rect_left, body_rect_top, body_rect_height, body_rect_width = map(
float, body_rect_str.split())

# Start the drag gesture 5 pixels below the bottom left corner of the
# first div in order to drag the left selection handle.
p1_left_ratio = .1
p1_top_ratio = float((text_div_bottom + 5 - body_rect_top) /
body_rect_height)

# End the drag gesture below the bottom right corner of the second div,
# so that the selection end is in the second div and we can easily
# determine the position of the corresponding handle.
p2_top_ratio = float((text_div_2_bottom - body_rect_top) /
body_rect_height)

with action_runner.CreateGestureInteraction('DragAction-1'):
action_runner.DragPage(left_start_ratio=p1_left_ratio,
top_start_ratio=p1_top_ratio, left_end_ratio=.99,
top_end_ratio=p2_top_ratio, speed_in_pixels_per_second=300,
use_touch=1)

# Confirm that the selection has changed.
text = action_runner.EvaluateJavaScript('window.getSelection().toString()')
assert text != "Hello"

# Determine the coordinates of the end of the selection
sel_end_str = action_runner.EvaluateJavaScript('''
var rects = window.getSelection().getRangeAt(0).getClientRects();
var last_rect = rects[rects.length - 1];
last_rect.right + " " + last_rect.bottom;
''')
sel_end_x, sel_end_y = map(float, sel_end_str.split())

# Start the second drag gesture 5 pixels below the end of the selection
# in order to drag the selection handle.
p2_left_ratio = float((sel_end_x - body_rect_left) / body_rect_width)
p2_top_ratio = float((sel_end_y + 5 - body_rect_top) / body_rect_height)

with action_runner.CreateGestureInteraction('DragAction-2'):
action_runner.DragPage(left_start_ratio=p2_left_ratio,
top_start_ratio=p2_top_ratio, left_end_ratio=p1_left_ratio,
top_end_ratio=p1_top_ratio, speed_in_pixels_per_second=300,
use_touch=1)

# Confirm that the selection is back to the text in the first div.
text = action_runner.EvaluateJavaScript('window.getSelection().toString()')
assert text == "Hello"


class TextSelectionSitesPageSet(story.StorySet):
def __init__(self):
super(TextSelectionSitesPageSet, self).__init__(
archive_data_file='data/top_10_mobile.json',
cloud_storage_bucket=story.PARTNER_BUCKET)

# A subset of top_10_mobile page set
page_urls = [
'https://www.google.com/#hl=en&q=science',
'https://m.facebook.com/rihanna',
'http://search.yahoo.com/search;_ylt=?p=google',
'http://www.baidu.com/s?word=google',
'https://mobile.twitter.com/justinbieber?skip_interstitial=true',
'http://yandex.ru/touchsearch?text=science'
]

for url in page_urls:
self.AddStory(SimpleTextSelectionPage(url, self))
34 changes: 7 additions & 27 deletions tools/telemetry/telemetry/web_perf/metrics/layout.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,41 +2,21 @@
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

from telemetry.value import list_of_scalar_values
from telemetry.web_perf.metrics import timeline_based_metric
from telemetry.web_perf.metrics import single_event

EVENT_NAME = 'FrameView::performLayout'
METRIC_NAME = 'layout'

class LayoutMetric(timeline_based_metric.TimelineBasedMetric):
class LayoutMetric(single_event._SingleEventMetric):
"""Reports directly durations of FrameView::performLayout events.
layout: Durations of FrameView::performLayout events that were caused by and
start during user interaction.
Layout happens no more than once per frame, so per-frame-ness is implied.
"""
EVENT_NAME = 'FrameView::performLayout'

def __init__(self):
super(LayoutMetric, self).__init__()

def AddResults(self, _model, renderer_thread, interactions, results):
assert interactions
self._AddResultsInternal(renderer_thread.parent.IterAllSlices(),
interactions, results)

def _AddResultsInternal(self, events, interactions, results):
layouts = []
for event in events:
if (event.name == self.EVENT_NAME) and any(
interaction.start <= event.start <= interaction.end
for interaction in interactions):
layouts.append(event.end - event.start)
if not layouts:
return
results.AddValue(list_of_scalar_values.ListOfScalarValues(
page=results.current_page,
name='layout',
units='ms',
values=layouts,
description=('List of durations of layouts that were caused by and '
'start during interactions')))
super(LayoutMetric, self).__init__(EVENT_NAME, METRIC_NAME,
metric_description=('List of durations of layouts that were caused by '
'and start during interactions'))
64 changes: 0 additions & 64 deletions tools/telemetry/telemetry/web_perf/metrics/layout_unittest.py

This file was deleted.

42 changes: 42 additions & 0 deletions tools/telemetry/telemetry/web_perf/metrics/single_event.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# Copyright 2014 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.

from telemetry.value import list_of_scalar_values
from telemetry.web_perf.metrics import timeline_based_metric


class _SingleEventMetric(timeline_based_metric.TimelineBasedMetric):
"""Reports directly durations of specific trace events that start during the
user interaction.
"""

def __init__(self, trace_event_name, metric_name, metric_description=None):
super(_SingleEventMetric, self).__init__()
self._TRACE_EVENT_NAME = trace_event_name
self._metric_name = metric_name
self._metric_description = metric_description

def AddResults(self, _model, renderer_thread, interactions, results):
assert interactions
self._AddResultsInternal(renderer_thread.parent.IterAllSlices(),
interactions, results)

def _AddResultsInternal(self, events, interactions, results):
events_found = []
for event in events:
if (event.name == self._TRACE_EVENT_NAME) and any(
interaction.start <= event.start <= interaction.end
for interaction in interactions):
if event.has_thread_timestamps:
events_found.append(event.thread_duration)
else:
events_found.append(event.duration)
if not events_found:
return
results.AddValue(list_of_scalar_values.ListOfScalarValues(
page=results.current_page,
name=self._metric_name,
units='ms',
values=events_found,
description=self._metric_description))
Loading

0 comments on commit 715cd9f

Please sign in to comment.