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.
8 from common
import chrome_proxy_metrics
9 from common
import network_metrics
10 from common
.chrome_proxy_metrics
import ChromeProxyMetricException
11 from telemetry
.page
import page_test
12 from telemetry
.value
import scalar
15 class ChromeProxyMetric(network_metrics
.NetworkMetric
):
16 """A Chrome proxy timeline metric."""
19 super(ChromeProxyMetric
, self
).__init
__()
20 self
.compute_data_saving
= True
22 def SetEvents(self
, events
):
23 """Used for unittest."""
26 def ResponseFromEvent(self
, event
):
27 return chrome_proxy_metrics
.ChromeProxyResponse(event
)
29 def AddResults(self
, tab
, results
):
30 raise NotImplementedError
32 def AddResultsForDataSaving(self
, tab
, results
):
33 resources_via_proxy
= 0
34 resources_from_cache
= 0
37 super(ChromeProxyMetric
, self
).AddResults(tab
, results
)
38 for resp
in self
.IterResponses(tab
):
39 if resp
.response
.served_from_cache
:
40 resources_from_cache
+= 1
41 if resp
.HasChromeProxyViaHeader():
42 resources_via_proxy
+= 1
46 if resources_from_cache
+ resources_via_proxy
+ resources_direct
== 0:
47 raise ChromeProxyMetricException
, (
48 'Expected at least one response, but zero responses were received.')
50 results
.AddValue(scalar
.ScalarValue(
51 results
.current_page
, 'resources_via_proxy', 'count',
53 results
.AddValue(scalar
.ScalarValue(
54 results
.current_page
, 'resources_from_cache', 'count',
55 resources_from_cache
))
56 results
.AddValue(scalar
.ScalarValue(
57 results
.current_page
, 'resources_direct', 'count', resources_direct
))
59 def AddResultsForHeaderValidation(self
, tab
, results
):
62 for resp
in self
.IterResponses(tab
):
63 if resp
.IsValidByViaHeader():
67 raise ChromeProxyMetricException
, (
68 '%s: Via header (%s) is not valid (refer=%s, status=%d)' % (
69 r
.url
, r
.GetHeader('Via'), r
.GetHeader('Referer'), r
.status
))
72 raise ChromeProxyMetricException
, (
73 'Expected at least one response through the proxy, but zero such '
74 'responses were received.')
75 results
.AddValue(scalar
.ScalarValue(
76 results
.current_page
, 'checked_via_header', 'count', via_count
))
78 def AddResultsForLatency(self
, tab
, results
):
79 # TODO(bustamante): This is a hack to workaround crbug.com/467174,
80 # once fixed just pull down window.performance.timing object and
81 # reference that everywhere.
82 load_event_start
= tab
.EvaluateJavaScript(
83 'window.performance.timing.loadEventStart')
84 navigation_start
= tab
.EvaluateJavaScript(
85 'window.performance.timing.navigationStart')
86 dom_content_loaded_event_start
= tab
.EvaluateJavaScript(
87 'window.performance.timing.domContentLoadedEventStart')
88 fetch_start
= tab
.EvaluateJavaScript(
89 'window.performance.timing.fetchStart')
90 request_start
= tab
.EvaluateJavaScript(
91 'window.performance.timing.requestStart')
92 domain_lookup_end
= tab
.EvaluateJavaScript(
93 'window.performance.timing.domainLookupEnd')
94 domain_lookup_start
= tab
.EvaluateJavaScript(
95 'window.performance.timing.domainLookupStart')
96 connect_end
= tab
.EvaluateJavaScript(
97 'window.performance.timing.connectEnd')
98 connect_start
= tab
.EvaluateJavaScript(
99 'window.performance.timing.connectStart')
100 response_end
= tab
.EvaluateJavaScript(
101 'window.performance.timing.responseEnd')
102 response_start
= tab
.EvaluateJavaScript(
103 'window.performance.timing.responseStart')
105 # NavigationStart relative markers in milliseconds.
106 load_start
= (float(load_event_start
) - navigation_start
)
107 results
.AddValue(scalar
.ScalarValue(
108 results
.current_page
, 'load_start', 'ms', load_start
))
110 dom_content_loaded_start
= (
111 float(dom_content_loaded_event_start
) - navigation_start
)
112 results
.AddValue(scalar
.ScalarValue(
113 results
.current_page
, 'dom_content_loaded_start', 'ms',
114 dom_content_loaded_start
))
116 fetch_start
= (float(fetch_start
) - navigation_start
)
117 results
.AddValue(scalar
.ScalarValue(
118 results
.current_page
, 'fetch_start', 'ms', fetch_start
,
121 request_start
= (float(request_start
) - navigation_start
)
122 results
.AddValue(scalar
.ScalarValue(
123 results
.current_page
, 'request_start', 'ms', request_start
,
126 # Phase measurements in milliseconds.
127 domain_lookup_duration
= (float(domain_lookup_end
) - domain_lookup_start
)
128 results
.AddValue(scalar
.ScalarValue(
129 results
.current_page
, 'domain_lookup_duration', 'ms',
130 domain_lookup_duration
, important
=False))
132 connect_duration
= (float(connect_end
) - connect_start
)
133 results
.AddValue(scalar
.ScalarValue(
134 results
.current_page
, 'connect_duration', 'ms', connect_duration
,
137 request_duration
= (float(response_start
) - request_start
)
138 results
.AddValue(scalar
.ScalarValue(
139 results
.current_page
, 'request_duration', 'ms', request_duration
,
142 response_duration
= (float(response_end
) - response_start
)
143 results
.AddValue(scalar
.ScalarValue(
144 results
.current_page
, 'response_duration', 'ms', response_duration
,
147 def AddResultsForExtraViaHeader(self
, tab
, results
, extra_via_header
):
150 for resp
in self
.IterResponses(tab
):
151 if resp
.HasChromeProxyViaHeader():
152 if resp
.HasExtraViaHeader(extra_via_header
):
155 raise ChromeProxyMetricException
, (
156 '%s: Should have via header %s.' % (resp
.response
.url
,
159 results
.AddValue(scalar
.ScalarValue(
160 results
.current_page
, 'extra_via_header', 'count', extra_via_count
))
162 def AddResultsForClientVersion(self
, tab
, results
):
164 for resp
in self
.IterResponses(tab
):
166 if resp
.response
.status
!= 200:
167 raise ChromeProxyMetricException
, ('%s: Response is not 200: %d' %
169 if not resp
.IsValidByViaHeader():
170 raise ChromeProxyMetricException
, ('%s: Response missing via header' %
175 raise ChromeProxyMetricException
, (
176 'Expected at least one response through the proxy, but zero such '
177 'responses were received.')
178 results
.AddValue(scalar
.ScalarValue(
179 results
.current_page
, 'responses_via_proxy', 'count', via_count
))
181 def GetClientTypeFromRequests(self
, tab
):
182 """Get the Chrome-Proxy client type value from requests made in this tab.
185 The client type value from the first request made in this tab that
186 specifies a client type in the Chrome-Proxy request header. See
187 ChromeProxyResponse.GetChromeProxyClientType for more details about the
188 Chrome-Proxy client type. Returns None if none of the requests made in
189 this tab specify a client type.
191 for resp
in self
.IterResponses(tab
):
192 client_type
= resp
.GetChromeProxyClientType()
197 def AddResultsForClientType(self
, tab
, results
, client_type
,
198 bypass_for_client_type
):
202 for resp
in self
.IterResponses(tab
):
203 if resp
.HasChromeProxyViaHeader():
205 if client_type
.lower() == bypass_for_client_type
.lower():
206 raise ChromeProxyMetricException
, (
207 '%s: Response for client of type "%s" has via header, but should '
208 'be bypassed.' % (resp
.response
.url
, bypass_for_client_type
))
209 elif resp
.ShouldHaveChromeProxyViaHeader():
211 if client_type
.lower() != bypass_for_client_type
.lower():
212 raise ChromeProxyMetricException
, (
213 '%s: Response missing via header. Only "%s" clients should '
214 'bypass for this page, but this client is "%s".' % (
215 resp
.response
.url
, bypass_for_client_type
, client_type
))
217 if via_count
+ bypass_count
== 0:
218 raise ChromeProxyMetricException
, (
219 'Expected at least one response that was eligible to be proxied, but '
220 'zero such responses were received.')
222 results
.AddValue(scalar
.ScalarValue(
223 results
.current_page
, 'via', 'count', via_count
))
224 results
.AddValue(scalar
.ScalarValue(
225 results
.current_page
, 'bypass', 'count', bypass_count
))
227 def AddResultsForLoFi(self
, tab
, results
):
228 lo_fi_request_count
= 0
229 lo_fi_response_count
= 0
231 for resp
in self
.IterResponses(tab
):
232 if resp
.HasChromeProxyLoFiRequest():
233 lo_fi_request_count
+= 1
235 raise ChromeProxyMetricException
, (
236 '%s: LoFi not in request header.' % (resp
.response
.url
))
238 if resp
.HasChromeProxyLoFiResponse():
239 lo_fi_response_count
+= 1
241 raise ChromeProxyMetricException
, (
242 '%s: LoFi not in response header.' % (resp
.response
.url
))
244 if resp
.content_length
> 100:
245 raise ChromeProxyMetricException
, (
246 'Image %s is %d bytes. Expecting less than 100 bytes.' %
247 (resp
.response
.url
, resp
.content_length
))
249 if lo_fi_request_count
== 0:
250 raise ChromeProxyMetricException
, (
251 'Expected at least one LoFi request, but zero such requests were '
253 if lo_fi_response_count
== 0:
254 raise ChromeProxyMetricException
, (
255 'Expected at least one LoFi response, but zero such responses were '
258 results
.AddValue(scalar
.ScalarValue(
259 results
.current_page
, 'lo_fi_request', 'count', lo_fi_request_count
))
260 results
.AddValue(scalar
.ScalarValue(
261 results
.current_page
, 'lo_fi_response', 'count', lo_fi_response_count
))
262 super(ChromeProxyMetric
, self
).AddResults(tab
, results
)
264 def AddResultsForBypass(self
, tab
, results
):
267 for resp
in self
.IterResponses(tab
):
268 if resp
.HasChromeProxyViaHeader():
270 raise ChromeProxyMetricException
, (
271 '%s: Should not have Via header (%s) (refer=%s, status=%d)' % (
272 r
.url
, r
.GetHeader('Via'), r
.GetHeader('Referer'), r
.status
))
275 if bypass_count
== 0:
276 raise ChromeProxyMetricException
, (
277 'Expected at least one response to be bypassed, but zero such '
278 'responses were received.')
279 results
.AddValue(scalar
.ScalarValue(
280 results
.current_page
, 'bypass', 'count', bypass_count
))
282 def AddResultsForCorsBypass(self
, tab
, results
):
283 eligible_response_count
= 0
286 for resp
in self
.IterResponses(tab
):
287 logging
.warn('got a resource %s' % (resp
.response
.url
))
289 for resp
in self
.IterResponses(tab
):
290 if resp
.ShouldHaveChromeProxyViaHeader():
291 eligible_response_count
+= 1
292 if not resp
.HasChromeProxyViaHeader():
294 elif resp
.response
.status
== 502:
295 bypasses
[resp
.response
.url
] = 0
297 for resp
in self
.IterResponses(tab
):
298 if resp
.ShouldHaveChromeProxyViaHeader():
299 if not resp
.HasChromeProxyViaHeader():
300 if resp
.response
.status
== 200:
301 if (bypasses
.has_key(resp
.response
.url
)):
302 bypasses
[resp
.response
.url
] = bypasses
[resp
.response
.url
] + 1
305 if bypasses
[url
] == 0:
306 raise ChromeProxyMetricException
, (
307 '%s: Got a 502 without a subsequent 200' % (url
))
308 elif bypasses
[url
] > 1:
309 raise ChromeProxyMetricException
, (
310 '%s: Got a 502 and multiple 200s: %d' % (url
, bypasses
[url
]))
311 if bypass_count
== 0:
312 raise ChromeProxyMetricException
, (
313 'At least one response should be bypassed. '
314 '(eligible_response_count=%d, bypass_count=%d)\n' % (
315 eligible_response_count
, bypass_count
))
317 results
.AddValue(scalar
.ScalarValue(
318 results
.current_page
, 'cors_bypass', 'count', bypass_count
))
320 def AddResultsForBlockOnce(self
, tab
, results
):
321 eligible_response_count
= 0
324 for resp
in self
.IterResponses(tab
):
325 if resp
.ShouldHaveChromeProxyViaHeader():
326 eligible_response_count
+= 1
327 if not resp
.HasChromeProxyViaHeader():
330 if eligible_response_count
<= 1:
331 raise ChromeProxyMetricException
, (
332 'There should be more than one DRP eligible response '
333 '(eligible_response_count=%d, bypass_count=%d)\n' % (
334 eligible_response_count
, bypass_count
))
335 elif bypass_count
!= 1:
336 raise ChromeProxyMetricException
, (
337 'Exactly one response should be bypassed. '
338 '(eligible_response_count=%d, bypass_count=%d)\n' % (
339 eligible_response_count
, bypass_count
))
341 results
.AddValue(scalar
.ScalarValue(
342 results
.current_page
, 'eligible_responses', 'count',
343 eligible_response_count
))
344 results
.AddValue(scalar
.ScalarValue(
345 results
.current_page
, 'bypass', 'count', bypass_count
))
347 def AddResultsForSafebrowsingOn(self
, tab
, results
):
348 results
.AddValue(scalar
.ScalarValue(
349 results
.current_page
, 'safebrowsing', 'timeout responses', 1))
351 def AddResultsForSafebrowsingOff(self
, tab
, results
):
353 for resp
in self
.IterResponses(tab
):
354 # Data reduction proxy should return the real response for sites with
357 if not resp
.HasChromeProxyViaHeader():
359 raise ChromeProxyMetricException
, (
360 '%s: Safebrowsing feature should be off for desktop and webview.\n'
361 'Reponse: status=(%d, %s)\nHeaders:\n %s' % (
362 r
.url
, r
.status
, r
.status_text
, r
.headers
))
364 if response_count
== 0:
365 raise ChromeProxyMetricException
, (
366 'Safebrowsing test failed: No valid responses received')
368 results
.AddValue(scalar
.ScalarValue(
369 results
.current_page
, 'safebrowsing', 'responses', response_count
))
371 def AddResultsForHTTPFallback(self
, tab
, results
):
372 via_fallback_count
= 0
374 for resp
in self
.IterResponses(tab
):
375 if resp
.ShouldHaveChromeProxyViaHeader():
376 # All responses should have come through the HTTP fallback proxy, which
377 # means that they should have the via header, and if a remote port is
378 # defined, it should be port 80.
379 if (not resp
.HasChromeProxyViaHeader() or
380 (resp
.remote_port
and resp
.remote_port
!= 80)):
382 raise ChromeProxyMetricException
, (
383 '%s: Should have come through the fallback proxy.\n'
384 'Reponse: remote_port=%s status=(%d, %s)\nHeaders:\n %s' % (
385 r
.url
, str(resp
.remote_port
), r
.status
, r
.status_text
,
387 via_fallback_count
+= 1
389 if via_fallback_count
== 0:
390 raise ChromeProxyMetricException
, (
391 'Expected at least one response through the fallback proxy, but zero '
392 'such responses were received.')
393 results
.AddValue(scalar
.ScalarValue(
394 results
.current_page
, 'via_fallback', 'count', via_fallback_count
))
396 def AddResultsForHTTPToDirectFallback(self
, tab
, results
,
397 fallback_response_host
):
398 via_fallback_count
= 0
400 responses
= self
.IterResponses(tab
)
402 # The first response(s) coming from fallback_response_host should be
403 # through the HTTP fallback proxy.
404 resp
= next(responses
, None)
405 while resp
and fallback_response_host
in resp
.response
.url
:
406 if fallback_response_host
in resp
.response
.url
:
407 if (not resp
.HasChromeProxyViaHeader() or resp
.remote_port
!= 80):
409 raise ChromeProxyMetricException
, (
410 'Response for %s should have come through the fallback proxy.\n'
411 'Response: remote_port=%s status=(%d, %s)\nHeaders:\n %s' % (
412 r
.url
, str(resp
.remote_port
), r
.status
, r
.status_text
,
415 via_fallback_count
+= 1
416 resp
= next(responses
, None)
418 # All other responses should be bypassed.
420 if resp
.HasChromeProxyViaHeader():
422 raise ChromeProxyMetricException
, (
423 'Response for %s should not have via header.\n'
424 'Response: status=(%d, %s)\nHeaders:\n %s' % (
425 r
.url
, r
.status
, r
.status_text
, r
.headers
))
428 resp
= next(responses
, None)
430 # At least one response should go through the http proxy and be bypassed.
431 if via_fallback_count
== 0 or bypass_count
== 0:
432 raise ChromeProxyMetricException(
433 'There should be at least one response through the fallback proxy '
434 '(actual %s) and at least one bypassed response (actual %s)' %
435 (via_fallback_count
, bypass_count
))
437 results
.AddValue(scalar
.ScalarValue(
438 results
.current_page
, 'via_fallback', 'count', via_fallback_count
))
439 results
.AddValue(scalar
.ScalarValue(
440 results
.current_page
, 'bypass', 'count', bypass_count
))
442 def AddResultsForReenableAfterBypass(
443 self
, tab
, results
, bypass_seconds_min
, bypass_seconds_max
):
444 """Verify results for a re-enable after bypass test.
447 tab: the tab for the test.
448 results: the results object to add the results values to.
449 bypass_seconds_min: the minimum duration of the bypass.
450 bypass_seconds_max: the maximum duration of the bypass.
455 for resp
in self
.IterResponses(tab
):
456 if resp
.HasChromeProxyViaHeader():
458 raise ChromeProxyMetricException
, (
459 'Response for %s should not have via header.\n'
460 'Reponse: status=(%d, %s)\nHeaders:\n %s' % (
461 r
.url
, r
.status
, r
.status_text
, r
.headers
))
465 # Wait until 30 seconds before the bypass should expire, and fetch a page.
466 # It should not have the via header because the proxy should still be
468 time
.sleep(bypass_seconds_min
- 30)
470 tab
.ClearCache(force
=True)
471 before_metrics
= ChromeProxyMetric()
472 before_metrics
.Start(results
.current_page
, tab
)
473 tab
.Navigate('http://chromeproxy-test.appspot.com/default')
474 tab
.WaitForJavaScriptExpression('performance.timing.loadEventStart', 10)
475 before_metrics
.Stop(results
.current_page
, tab
)
477 for resp
in before_metrics
.IterResponses(tab
):
478 if resp
.HasChromeProxyViaHeader():
480 raise ChromeProxyMetricException
, (
481 'Response for %s should not have via header; proxy should still '
482 'be bypassed.\nReponse: status=(%d, %s)\nHeaders:\n %s' % (
483 r
.url
, r
.status
, r
.status_text
, r
.headers
))
486 if bypass_count
== 0:
487 raise ChromeProxyMetricException
, (
488 'Expected at least one response to be bypassed before the bypass '
489 'expired, but zero such responses were received.')
491 # Wait until 30 seconds after the bypass should expire, and fetch a page. It
492 # should have the via header since the proxy should no longer be bypassed.
493 time
.sleep((bypass_seconds_max
+ 30) - (bypass_seconds_min
- 30))
495 tab
.ClearCache(force
=True)
496 after_metrics
= ChromeProxyMetric()
497 after_metrics
.Start(results
.current_page
, tab
)
498 tab
.Navigate('http://chromeproxy-test.appspot.com/default')
499 tab
.WaitForJavaScriptExpression('performance.timing.loadEventStart', 10)
500 after_metrics
.Stop(results
.current_page
, tab
)
502 for resp
in after_metrics
.IterResponses(tab
):
503 if not resp
.HasChromeProxyViaHeader():
505 raise ChromeProxyMetricException
, (
506 'Response for %s should have via header; proxy should no longer '
507 'be bypassed.\nReponse: status=(%d, %s)\nHeaders:\n %s' % (
508 r
.url
, r
.status
, r
.status_text
, r
.headers
))
512 raise ChromeProxyMetricException
, (
513 'Expected at least one response through the proxy after the bypass '
514 'expired, but zero such responses were received.')
516 results
.AddValue(scalar
.ScalarValue(
517 results
.current_page
, 'bypass', 'count', bypass_count
))
518 results
.AddValue(scalar
.ScalarValue(
519 results
.current_page
, 'via', 'count', via_count
))