Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / tools / perf / measurements / blink_style.py
blob48fd449511f9df34bbacf215c1263c67b0fbb88d
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.
5 from collections import defaultdict
6 from itertools import starmap
7 from telemetry.core import exceptions
8 from telemetry.core import util
9 from telemetry.page import action_runner
10 from telemetry.page import page_test
11 from telemetry.value import scalar
13 from measurements import timeline_controller
16 class BlinkStyle(page_test.PageTest):
17 def __init__(self):
18 super(BlinkStyle, self).__init__()
19 self._controller = None
21 def WillNavigateToPage(self, page, tab):
22 self._controller = timeline_controller.TimelineController()
23 self._controller.trace_categories = 'blink_style,blink.console'
24 self._controller.SetUp(page, tab)
25 self._controller.Start(tab)
27 def DidRunPage(self, platform):
28 if self._controller:
29 self._controller.CleanUp(platform)
31 def ValidateAndMeasurePage(self, page, tab, results):
32 runner = action_runner.ActionRunner(tab)
33 with runner.CreateInteraction('wait-for-quiescence'):
34 tab.ExecuteJavaScript('console.time("");')
35 try:
36 util.WaitFor(tab.HasReachedQuiescence, 15)
37 except exceptions.TimeoutException:
38 # Some sites never reach quiesence. As this benchmark normalizes/
39 # categories results, it shouldn't be necessary to reach the same
40 # state on every run.
41 pass
43 tab.ExecuteJavaScript('''
44 for (var i = 0; i < 11; i++) {
45 var cold = i % 2 == 0;
46 var name = "update_style";
47 if (cold) name += "_cold";
48 console.time(name);
49 // Occasionally documents will break the APIs we need
50 try {
51 // On cold runs, force a new StyleResolver
52 if (cold) {
53 var style = document.createElement("style");
54 document.head.appendChild(style);
55 style.remove();
57 // Invalidate style for the whole document
58 document.documentElement.lang += "z";
59 // Force a style update (but not layout)
60 getComputedStyle(document.documentElement).color;
61 } catch (e) {}
62 console.timeEnd(name);
63 }''')
65 self._controller.Stop(tab, results)
66 renderer = self._controller.model.GetRendererThreadFromTabId(tab.id)
67 markers = [event for event in renderer.async_slices
68 if event.name.startswith('update_style')
69 and event.category == 'blink.console']
70 # Drop the first run.
71 markers = markers[1:]
72 assert len(markers) == 10
74 def duration(event):
75 if event.has_thread_timestamps:
76 return event.thread_duration
77 else:
78 return event.duration
80 for marker in markers:
81 for event in renderer.all_slices:
82 if (event.name == 'Document::updateStyle'
83 and event.start >= marker.start
84 and event.end <= marker.end):
85 access_count = event.args.get('resolverAccessCount')
86 if access_count is None:
87 # absent in earlier versions
88 continue
89 min_access_count = 50
91 if access_count >= min_access_count:
92 result = 1000 * (duration(event) / access_count)
93 results.AddValue(scalar.ScalarValue(
94 page, marker.name, 'ms/1000 elements', result))
96 class ParserEvent(object):
97 def __init__(self, summary_event, tokenize_event, parse_event):
98 min_sheet_length = 1000
99 ua_sheet_mode = 5
100 enormous_token_threshold = 100
101 large_token_threshold = 5
103 self.mode = summary_event.args.get('mode')
104 self.length = summary_event.args.get('length')
105 self.tokens = summary_event.args.get('tokenCount')
106 self.tokenize_duration = duration(tokenize_event)
107 self.parse_duration = duration(parse_event)
108 self.chars_per_token = 0
109 if self.tokens:
110 self.chars_per_token = self.length / float(self.tokens)
111 if self.mode == ua_sheet_mode or self.length < min_sheet_length:
112 self.category = 'ignored'
113 elif self.chars_per_token > enormous_token_threshold:
114 self.category = 'enormous_tokens'
115 elif self.chars_per_token > large_token_threshold:
116 self.category = 'large_tokens'
117 else:
118 self.category = 'regular'
120 parser_events = [event for event in renderer.all_slices
121 if event.name == 'CSSParserImpl::parseStyleSheet'
122 or event.name == 'CSSParserImpl::parseStyleSheet.tokenize'
123 or event.name == 'CSSParserImpl::parseStyleSheet.parse']
125 merged_events = starmap(ParserEvent, zip(*[iter(parser_events)]*3))
127 events_by_category = defaultdict(list)
128 for event in merged_events:
129 if event.category != 'ignored':
130 events_by_category[event.category].append(event)
132 for category, events in events_by_category.items():
133 parse_duration = sum(event.parse_duration for event in events)
134 tokenize_duration = sum(event.tokenize_duration for event in events)
135 tokens = sum(event.tokens for event in events)
136 length = sum(event.length for event in events)
138 results.AddValue(
139 scalar.ScalarValue(page, ('parse_css_%s' % category),
140 'tokens/s', 1000 / (parse_duration / tokens)))
142 results.AddValue(
143 scalar.ScalarValue(page, ('tokenize_css_%s' % category),
144 'char/s', 1000 / (tokenize_duration / length)))