1 # Copyright (c) 2012 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 """The page cycler measurement.
7 This measurement registers a window load handler in which is forces a layout and
8 then records the value of performance.now(). This call to now() measures the
9 time from navigationStart (immediately after the previous page's beforeunload
10 event) until after the layout in the page's load event. In addition, two garbage
11 collections are performed in between the page loads (in the beforeunload event).
12 This extra garbage collection time is not included in the measurement times.
14 Finally, various memory and IO statistics are gathered at the very end of
22 from metrics
import io
23 from metrics
import memory
24 from metrics
import v8_object_stats
25 from telemetry
.core
import util
26 from telemetry
.page
import page_measurement
28 class PageCycler(page_measurement
.PageMeasurement
):
29 def __init__(self
, *args
, **kwargs
):
30 super(PageCycler
, self
).__init
__(*args
, **kwargs
)
32 with
open(os
.path
.join(os
.path
.dirname(__file__
),
33 'page_cycler.js'), 'r') as f
:
34 self
._page
_cycler
_js
= f
.read()
36 self
._record
_v
8_object
_stats
= False
38 self
._memory
_metric
= None
39 self
._v
8_object
_stats
_metric
= None
40 self
._number
_warm
_runs
= None
41 self
._cold
_runs
_requested
= False
42 self
._has
_loaded
_page
= collections
.defaultdict(int)
44 def AddCommandLineOptions(self
, parser
):
45 # The page cyclers should default to 10 iterations. In order to change the
46 # default of an option, we must remove and re-add it.
47 # TODO: Remove this after transition to run_benchmark.
48 pageset_repeat_option
= parser
.get_option('--pageset-repeat')
49 pageset_repeat_option
.default
= 10
50 parser
.remove_option('--pageset-repeat')
51 parser
.add_option(pageset_repeat_option
)
53 parser
.add_option('--v8-object-stats',
55 help='Enable detailed V8 object statistics.')
57 parser
.add_option('--cold-load-percent', type='int', default
=0,
58 help='%d of page visits for which a cold load is forced')
61 def DidStartBrowser(self
, browser
):
62 """Initialize metrics once right after the browser has been launched."""
63 self
._memory
_metric
= memory
.MemoryMetric(browser
)
64 if self
._record
_v
8_object
_stats
:
65 self
._v
8_object
_stats
_metric
= v8_object_stats
.V8ObjectStatsMetric()
67 def DidStartHTTPServer(self
, tab
):
68 # Avoid paying for a cross-renderer navigation on the first page on legacy
69 # page cyclers which use the filesystem.
70 tab
.Navigate(tab
.browser
.http_server
.UrlOf('nonexistent.html'))
72 def WillNavigateToPage(self
, page
, tab
):
73 page
.script_to_evaluate_on_commit
= self
._page
_cycler
_js
74 if self
.ShouldRunCold(page
.url
):
77 def DidNavigateToPage(self
, page
, tab
):
78 self
._memory
_metric
.Start(page
, tab
)
79 if self
._record
_v
8_object
_stats
:
80 self
._v
8_object
_stats
_metric
.Start(page
, tab
)
82 def CustomizeBrowserOptions(self
, options
):
83 memory
.MemoryMetric
.CustomizeBrowserOptions(options
)
84 io
.IOMetric
.CustomizeBrowserOptions(options
)
85 options
.AppendExtraBrowserArgs('--js-flags=--expose_gc')
87 if options
.v8_object_stats
:
88 self
._record
_v
8_object
_stats
= True
89 v8_object_stats
.V8ObjectStatsMetric
.CustomizeBrowserOptions(options
)
91 # A disk cache bug causes some page cyclers to hang on mac.
92 # TODO(tonyg): Re-enable these tests when crbug.com/268646 is fixed.
93 if (sys
.platform
== 'darwin' and
94 (sys
.argv
[-1].endswith('/intl_hi_ru.json') or
95 sys
.argv
[-1].endswith('/tough_layout_cases.json') or
96 sys
.argv
[-1].endswith('/typical_25.json'))):
97 print '%s is currently disabled on mac. Skipping test.' % sys
.argv
[-1]
100 # Handle requests for cold cache runs
101 if (options
.cold_load_percent
and
102 (options
.repeat_options
.page_repeat_secs
or
103 options
.repeat_options
.pageset_repeat_secs
)):
104 raise Exception('--cold-load-percent is incompatible with timed repeat')
106 if (options
.cold_load_percent
and
107 (options
.cold_load_percent
< 0 or options
.cold_load_percent
> 100)):
108 raise Exception('--cold-load-percent must be in the range [0-100]')
110 # TODO(rdsmith): Properly handle interaction of page_repeat with
111 # dropping the first run.
112 number_warm_pageset_runs
= int(
113 (int(options
.repeat_options
.pageset_repeat_iters
) - 1) *
114 (100 - options
.cold_load_percent
) / 100)
116 # Make sure _number_cold_runs is an integer multiple of page_repeat.
117 # Without this, --pageset_shuffle + --page_repeat could lead to
118 # assertion failures on _started_warm in WillNavigateToPage.
119 self
._number
_warm
_runs
= (number_warm_pageset_runs
*
120 options
.repeat_options
.page_repeat_iters
)
121 self
._cold
_runs
_requested
= bool(options
.cold_load_percent
)
122 self
.discard_first_result
= (bool(options
.cold_load_percent
) or
123 self
.discard_first_result
)
125 def MeasurePage(self
, page
, tab
, results
):
127 return bool(tab
.EvaluateJavaScript('__pc_load_time'))
128 util
.WaitFor(_IsDone
, 60)
129 chart_name
= ('times' if not self
._cold
_runs
_requested
else
130 'cold_times' if self
.ShouldRunCold(page
.url
) else
133 results
.Add('page_load_time', 'ms',
134 int(float(tab
.EvaluateJavaScript('__pc_load_time'))),
135 chart_name
=chart_name
)
137 self
._has
_loaded
_page
[page
.url
] += 1
139 self
._memory
_metric
.Stop(page
, tab
)
140 self
._memory
_metric
.AddResults(tab
, results
)
141 if self
._record
_v
8_object
_stats
:
142 self
._v
8_object
_stats
_metric
.Stop(page
, tab
)
143 self
._v
8_object
_stats
_metric
.AddResults(tab
, results
)
145 def DidRunTest(self
, tab
, results
):
146 self
._memory
_metric
.AddSummaryResults(results
)
147 io
.IOMetric().AddSummaryResults(tab
, results
)
149 def ShouldRunCold(self
, url
):
150 # We do the warm runs first for two reasons. The first is so we can
151 # preserve any initial profile cache for as long as possible.
152 # The second is that, if we did cold runs first, we'd have a transition
153 # page set during which we wanted the run for each URL to both
154 # contribute to the cold data and warm the catch for the following
155 # warm run, and clearing the cache before the load of the following
156 # URL would eliminate the intended warmup for the previous URL.
157 return self
._has
_loaded
_page
[url
] >= self
._number
_warm
_runs
+ 1
159 def results_are_the_same_on_every_page(self
):
160 return not self
._cold
_runs
_requested