LayoutTestPermissionManager: pass top level origin, not URL.
[chromium-blink-merge.git] / tools / chrome_proxy / integration_tests / chrome_proxy_metrics.py
blob68ba33fa311af9bf05d99f082f3cadc31ed68379
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.
5 import logging
6 import time
8 from integration_tests import network_metrics
9 from telemetry.page import page_test
10 from telemetry.value import scalar
13 class ChromeProxyMetricException(page_test.MeasurementFailure):
14 pass
17 CHROME_PROXY_VIA_HEADER = 'Chrome-Compression-Proxy'
20 class ChromeProxyResponse(network_metrics.HTTPResponse):
21 """ Represents an HTTP response from a timeleine event."""
22 def __init__(self, event):
23 super(ChromeProxyResponse, self).__init__(event)
25 def ShouldHaveChromeProxyViaHeader(self):
26 resp = self.response
27 # Ignore https and data url
28 if resp.url.startswith('https') or resp.url.startswith('data:'):
29 return False
30 # Ignore 304 Not Modified and cache hit.
31 if resp.status == 304 or resp.served_from_cache:
32 return False
33 # Ignore invalid responses that don't have any header. Log a warning.
34 if not resp.headers:
35 logging.warning('response for %s does not any have header '
36 '(refer=%s, status=%s)',
37 resp.url, resp.GetHeader('Referer'), resp.status)
38 return False
39 return True
41 def HasChromeProxyViaHeader(self):
42 via_header = self.response.GetHeader('Via')
43 if not via_header:
44 return False
45 vias = [v.strip(' ') for v in via_header.split(',')]
46 # The Via header is valid if it has a 4-character version prefix followed by
47 # the proxy name, for example, "1.1 Chrome-Compression-Proxy".
48 return any(v[4:] == CHROME_PROXY_VIA_HEADER for v in vias)
50 def HasExtraViaHeader(self, extra_header):
51 via_header = self.response.GetHeader('Via')
52 if not via_header:
53 return False
54 vias = [v.strip(' ') for v in via_header.split(',')]
55 return any(v == extra_header for v in vias)
57 def IsValidByViaHeader(self):
58 return (not self.ShouldHaveChromeProxyViaHeader() or
59 self.HasChromeProxyViaHeader())
61 def GetChromeProxyClientType(self):
62 """Get the client type directive from the Chrome-Proxy request header.
64 Returns:
65 The client type directive from the Chrome-Proxy request header for the
66 request that lead to this response. For example, if the request header
67 "Chrome-Proxy: c=android" is present, then this method would return
68 "android". Returns None if no client type directive is present.
69 """
70 if 'Chrome-Proxy' not in self.response.request_headers:
71 return None
73 chrome_proxy_request_header = self.response.request_headers['Chrome-Proxy']
74 values = [v.strip() for v in chrome_proxy_request_header.split(',')]
75 for value in values:
76 kvp = value.split('=', 1)
77 if len(kvp) == 2 and kvp[0].strip() == 'c':
78 return kvp[1].strip()
79 return None
81 def HasChromeProxyLoFi(self):
82 if 'Chrome-Proxy' not in self.response.request_headers:
83 return False
84 chrome_proxy_request_header = self.response.request_headers['Chrome-Proxy']
85 values = [v.strip() for v in chrome_proxy_request_header.split(',')]
86 for value in values:
87 if len(value) == 5 and value == 'q=low':
88 return True
89 return False
91 class ChromeProxyMetric(network_metrics.NetworkMetric):
92 """A Chrome proxy timeline metric."""
94 def __init__(self):
95 super(ChromeProxyMetric, self).__init__()
96 self.compute_data_saving = True
98 def SetEvents(self, events):
99 """Used for unittest."""
100 self._events = events
102 def ResponseFromEvent(self, event):
103 return ChromeProxyResponse(event)
105 def AddResults(self, tab, results):
106 raise NotImplementedError
108 def AddResultsForDataSaving(self, tab, results):
109 resources_via_proxy = 0
110 resources_from_cache = 0
111 resources_direct = 0
113 super(ChromeProxyMetric, self).AddResults(tab, results)
114 for resp in self.IterResponses(tab):
115 if resp.response.served_from_cache:
116 resources_from_cache += 1
117 if resp.HasChromeProxyViaHeader():
118 resources_via_proxy += 1
119 else:
120 resources_direct += 1
122 if resources_from_cache + resources_via_proxy + resources_direct == 0:
123 raise ChromeProxyMetricException, (
124 'Expected at least one response, but zero responses were received.')
126 results.AddValue(scalar.ScalarValue(
127 results.current_page, 'resources_via_proxy', 'count',
128 resources_via_proxy))
129 results.AddValue(scalar.ScalarValue(
130 results.current_page, 'resources_from_cache', 'count',
131 resources_from_cache))
132 results.AddValue(scalar.ScalarValue(
133 results.current_page, 'resources_direct', 'count', resources_direct))
135 def AddResultsForHeaderValidation(self, tab, results):
136 via_count = 0
138 for resp in self.IterResponses(tab):
139 if resp.IsValidByViaHeader():
140 via_count += 1
141 else:
142 r = resp.response
143 raise ChromeProxyMetricException, (
144 '%s: Via header (%s) is not valid (refer=%s, status=%d)' % (
145 r.url, r.GetHeader('Via'), r.GetHeader('Referer'), r.status))
147 if via_count == 0:
148 raise ChromeProxyMetricException, (
149 'Expected at least one response through the proxy, but zero such '
150 'responses were received.')
151 results.AddValue(scalar.ScalarValue(
152 results.current_page, 'checked_via_header', 'count', via_count))
154 def AddResultsForLatency(self, tab, results):
155 # TODO(bustamante): This is a hack to workaround crbug.com/467174,
156 # once fixed just pull down window.performance.timing object and
157 # reference that everywhere.
158 load_event_start = tab.EvaluateJavaScript(
159 'window.performance.timing.loadEventStart')
160 navigation_start = tab.EvaluateJavaScript(
161 'window.performance.timing.navigationStart')
162 dom_content_loaded_event_start = tab.EvaluateJavaScript(
163 'window.performance.timing.domContentLoadedEventStart')
164 fetch_start = tab.EvaluateJavaScript(
165 'window.performance.timing.fetchStart')
166 request_start = tab.EvaluateJavaScript(
167 'window.performance.timing.requestStart')
168 domain_lookup_end = tab.EvaluateJavaScript(
169 'window.performance.timing.domainLookupEnd')
170 domain_lookup_start = tab.EvaluateJavaScript(
171 'window.performance.timing.domainLookupStart')
172 connect_end = tab.EvaluateJavaScript(
173 'window.performance.timing.connectEnd')
174 connect_start = tab.EvaluateJavaScript(
175 'window.performance.timing.connectStart')
176 response_end = tab.EvaluateJavaScript(
177 'window.performance.timing.responseEnd')
178 response_start = tab.EvaluateJavaScript(
179 'window.performance.timing.responseStart')
181 # NavigationStart relative markers in milliseconds.
182 load_start = (float(load_event_start) - navigation_start)
183 results.AddValue(scalar.ScalarValue(
184 results.current_page, 'load_start', 'ms', load_start))
186 dom_content_loaded_start = (
187 float(dom_content_loaded_event_start) - navigation_start)
188 results.AddValue(scalar.ScalarValue(
189 results.current_page, 'dom_content_loaded_start', 'ms',
190 dom_content_loaded_start))
192 fetch_start = (float(fetch_start) - navigation_start)
193 results.AddValue(scalar.ScalarValue(
194 results.current_page, 'fetch_start', 'ms', fetch_start,
195 important=False))
197 request_start = (float(request_start) - navigation_start)
198 results.AddValue(scalar.ScalarValue(
199 results.current_page, 'request_start', 'ms', request_start,
200 important=False))
202 # Phase measurements in milliseconds.
203 domain_lookup_duration = (float(domain_lookup_end) - domain_lookup_start)
204 results.AddValue(scalar.ScalarValue(
205 results.current_page, 'domain_lookup_duration', 'ms',
206 domain_lookup_duration, important=False))
208 connect_duration = (float(connect_end) - connect_start)
209 results.AddValue(scalar.ScalarValue(
210 results.current_page, 'connect_duration', 'ms', connect_duration,
211 important=False))
213 request_duration = (float(response_start) - request_start)
214 results.AddValue(scalar.ScalarValue(
215 results.current_page, 'request_duration', 'ms', request_duration,
216 important=False))
218 response_duration = (float(response_end) - response_start)
219 results.AddValue(scalar.ScalarValue(
220 results.current_page, 'response_duration', 'ms', response_duration,
221 important=False))
223 def AddResultsForExtraViaHeader(self, tab, results, extra_via_header):
224 extra_via_count = 0
226 for resp in self.IterResponses(tab):
227 if resp.HasChromeProxyViaHeader():
228 if resp.HasExtraViaHeader(extra_via_header):
229 extra_via_count += 1
230 else:
231 raise ChromeProxyMetricException, (
232 '%s: Should have via header %s.' % (resp.response.url,
233 extra_via_header))
235 results.AddValue(scalar.ScalarValue(
236 results.current_page, 'extra_via_header', 'count', extra_via_count))
238 def AddResultsForClientVersion(self, tab, results):
239 via_count = 0
240 for resp in self.IterResponses(tab):
241 r = resp.response
242 if resp.response.status != 200:
243 raise ChromeProxyMetricException, ('%s: Response is not 200: %d' %
244 (r.url, r.status))
245 if not resp.IsValidByViaHeader():
246 raise ChromeProxyMetricException, ('%s: Response missing via header' %
247 (r.url))
248 via_count += 1
250 if via_count == 0:
251 raise ChromeProxyMetricException, (
252 'Expected at least one response through the proxy, but zero such '
253 'responses were received.')
254 results.AddValue(scalar.ScalarValue(
255 results.current_page, 'responses_via_proxy', 'count', via_count))
257 def GetClientTypeFromRequests(self, tab):
258 """Get the Chrome-Proxy client type value from requests made in this tab.
260 Returns:
261 The client type value from the first request made in this tab that
262 specifies a client type in the Chrome-Proxy request header. See
263 ChromeProxyResponse.GetChromeProxyClientType for more details about the
264 Chrome-Proxy client type. Returns None if none of the requests made in
265 this tab specify a client type.
267 for resp in self.IterResponses(tab):
268 client_type = resp.GetChromeProxyClientType()
269 if client_type:
270 return client_type
271 return None
273 def AddResultsForClientType(self, tab, results, client_type,
274 bypass_for_client_type):
275 via_count = 0
276 bypass_count = 0
278 for resp in self.IterResponses(tab):
279 if resp.HasChromeProxyViaHeader():
280 via_count += 1
281 if client_type.lower() == bypass_for_client_type.lower():
282 raise ChromeProxyMetricException, (
283 '%s: Response for client of type "%s" has via header, but should '
284 'be bypassed.' % (resp.response.url, bypass_for_client_type))
285 elif resp.ShouldHaveChromeProxyViaHeader():
286 bypass_count += 1
287 if client_type.lower() != bypass_for_client_type.lower():
288 raise ChromeProxyMetricException, (
289 '%s: Response missing via header. Only "%s" clients should '
290 'bypass for this page, but this client is "%s".' % (
291 resp.response.url, bypass_for_client_type, client_type))
293 if via_count + bypass_count == 0:
294 raise ChromeProxyMetricException, (
295 'Expected at least one response that was eligible to be proxied, but '
296 'zero such responses were received.')
298 results.AddValue(scalar.ScalarValue(
299 results.current_page, 'via', 'count', via_count))
300 results.AddValue(scalar.ScalarValue(
301 results.current_page, 'bypass', 'count', bypass_count))
303 def AddResultsForLoFi(self, tab, results):
304 lo_fi_count = 0
306 for resp in self.IterResponses(tab):
307 if resp.HasChromeProxyLoFi():
308 lo_fi_count += 1
309 else:
310 raise ChromeProxyMetricException, (
311 '%s: LoFi not in request header.' % (resp.response.url))
313 if resp.content_length > 100:
314 raise ChromeProxyMetricException, (
315 'Image %s is %d bytes. Expecting less than 100 bytes.' %
316 (resp.response.url, resp.content_length))
318 if lo_fi_count == 0:
319 raise ChromeProxyMetricException, (
320 'Expected at least one LoFi response, but zero such responses were '
321 'received.')
323 results.AddValue(scalar.ScalarValue(
324 results.current_page, 'lo_fi', 'count', lo_fi_count))
325 super(ChromeProxyMetric, self).AddResults(tab, results)
327 def AddResultsForBypass(self, tab, results):
328 bypass_count = 0
330 for resp in self.IterResponses(tab):
331 if resp.HasChromeProxyViaHeader():
332 r = resp.response
333 raise ChromeProxyMetricException, (
334 '%s: Should not have Via header (%s) (refer=%s, status=%d)' % (
335 r.url, r.GetHeader('Via'), r.GetHeader('Referer'), r.status))
336 bypass_count += 1
338 if bypass_count == 0:
339 raise ChromeProxyMetricException, (
340 'Expected at least one response to be bypassed, but zero such '
341 'responses were received.')
342 results.AddValue(scalar.ScalarValue(
343 results.current_page, 'bypass', 'count', bypass_count))
345 def AddResultsForCorsBypass(self, tab, results):
346 eligible_response_count = 0
347 bypass_count = 0
348 bypasses = {}
349 for resp in self.IterResponses(tab):
350 logging.warn('got a resource %s' % (resp.response.url))
352 for resp in self.IterResponses(tab):
353 if resp.ShouldHaveChromeProxyViaHeader():
354 eligible_response_count += 1
355 if not resp.HasChromeProxyViaHeader():
356 bypass_count += 1
357 elif resp.response.status == 502:
358 bypasses[resp.response.url] = 0
360 for resp in self.IterResponses(tab):
361 if resp.ShouldHaveChromeProxyViaHeader():
362 if not resp.HasChromeProxyViaHeader():
363 if resp.response.status == 200:
364 if (bypasses.has_key(resp.response.url)):
365 bypasses[resp.response.url] = bypasses[resp.response.url] + 1
367 for url in bypasses:
368 if bypasses[url] == 0:
369 raise ChromeProxyMetricException, (
370 '%s: Got a 502 without a subsequent 200' % (url))
371 elif bypasses[url] > 1:
372 raise ChromeProxyMetricException, (
373 '%s: Got a 502 and multiple 200s: %d' % (url, bypasses[url]))
374 if bypass_count == 0:
375 raise ChromeProxyMetricException, (
376 'At least one response should be bypassed. '
377 '(eligible_response_count=%d, bypass_count=%d)\n' % (
378 eligible_response_count, bypass_count))
380 results.AddValue(scalar.ScalarValue(
381 results.current_page, 'cors_bypass', 'count', bypass_count))
383 def AddResultsForBlockOnce(self, tab, results):
384 eligible_response_count = 0
385 bypass_count = 0
387 for resp in self.IterResponses(tab):
388 if resp.ShouldHaveChromeProxyViaHeader():
389 eligible_response_count += 1
390 if not resp.HasChromeProxyViaHeader():
391 bypass_count += 1
393 if eligible_response_count <= 1:
394 raise ChromeProxyMetricException, (
395 'There should be more than one DRP eligible response '
396 '(eligible_response_count=%d, bypass_count=%d)\n' % (
397 eligible_response_count, bypass_count))
398 elif bypass_count != 1:
399 raise ChromeProxyMetricException, (
400 'Exactly one response should be bypassed. '
401 '(eligible_response_count=%d, bypass_count=%d)\n' % (
402 eligible_response_count, bypass_count))
403 else:
404 results.AddValue(scalar.ScalarValue(
405 results.current_page, 'eligible_responses', 'count',
406 eligible_response_count))
407 results.AddValue(scalar.ScalarValue(
408 results.current_page, 'bypass', 'count', bypass_count))
410 def AddResultsForSafebrowsingOn(self, tab, results):
411 results.AddValue(scalar.ScalarValue(
412 results.current_page, 'safebrowsing', 'timeout responses', 1))
414 def AddResultsForSafebrowsingOff(self, tab, results):
415 response_count = 0
416 for resp in self.IterResponses(tab):
417 # Data reduction proxy should return the real response for sites with
418 # malware.
419 response_count += 1
420 if not resp.HasChromeProxyViaHeader():
421 r = resp.response
422 raise ChromeProxyMetricException, (
423 '%s: Safebrowsing feature should be off for desktop and webview.\n'
424 'Reponse: status=(%d, %s)\nHeaders:\n %s' % (
425 r.url, r.status, r.status_text, r.headers))
427 if response_count == 0:
428 raise ChromeProxyMetricException, (
429 'Safebrowsing test failed: No valid responses received')
431 results.AddValue(scalar.ScalarValue(
432 results.current_page, 'safebrowsing', 'responses', response_count))
434 def AddResultsForHTTPFallback(self, tab, results):
435 via_fallback_count = 0
437 for resp in self.IterResponses(tab):
438 if resp.ShouldHaveChromeProxyViaHeader():
439 # All responses should have come through the HTTP fallback proxy, which
440 # means that they should have the via header, and if a remote port is
441 # defined, it should be port 80.
442 if (not resp.HasChromeProxyViaHeader() or
443 (resp.remote_port and resp.remote_port != 80)):
444 r = resp.response
445 raise ChromeProxyMetricException, (
446 '%s: Should have come through the fallback proxy.\n'
447 'Reponse: remote_port=%s status=(%d, %s)\nHeaders:\n %s' % (
448 r.url, str(resp.remote_port), r.status, r.status_text,
449 r.headers))
450 via_fallback_count += 1
452 if via_fallback_count == 0:
453 raise ChromeProxyMetricException, (
454 'Expected at least one response through the fallback proxy, but zero '
455 'such responses were received.')
456 results.AddValue(scalar.ScalarValue(
457 results.current_page, 'via_fallback', 'count', via_fallback_count))
459 def AddResultsForHTTPToDirectFallback(self, tab, results,
460 fallback_response_host):
461 via_fallback_count = 0
462 bypass_count = 0
463 responses = self.IterResponses(tab)
465 # The first response(s) coming from fallback_response_host should be
466 # through the HTTP fallback proxy.
467 resp = next(responses, None)
468 while resp and fallback_response_host in resp.response.url:
469 if fallback_response_host in resp.response.url:
470 if (not resp.HasChromeProxyViaHeader() or resp.remote_port != 80):
471 r = resp.response
472 raise ChromeProxyMetricException, (
473 'Response for %s should have come through the fallback proxy.\n'
474 'Response: remote_port=%s status=(%d, %s)\nHeaders:\n %s' % (
475 r.url, str(resp.remote_port), r.status, r.status_text,
476 r.headers))
477 else:
478 via_fallback_count += 1
479 resp = next(responses, None)
481 # All other responses should be bypassed.
482 while resp:
483 if resp.HasChromeProxyViaHeader():
484 r = resp.response
485 raise ChromeProxyMetricException, (
486 'Response for %s should not have via header.\n'
487 'Response: status=(%d, %s)\nHeaders:\n %s' % (
488 r.url, r.status, r.status_text, r.headers))
489 else:
490 bypass_count += 1
491 resp = next(responses, None)
493 # At least one response should go through the http proxy and be bypassed.
494 if via_fallback_count == 0 or bypass_count == 0:
495 raise ChromeProxyMetricException(
496 'There should be at least one response through the fallback proxy '
497 '(actual %s) and at least one bypassed response (actual %s)' %
498 (via_fallback_count, bypass_count))
500 results.AddValue(scalar.ScalarValue(
501 results.current_page, 'via_fallback', 'count', via_fallback_count))
502 results.AddValue(scalar.ScalarValue(
503 results.current_page, 'bypass', 'count', bypass_count))
505 def AddResultsForReenableAfterBypass(
506 self, tab, results, bypass_seconds_min, bypass_seconds_max):
507 """Verify results for a re-enable after bypass test.
509 Args:
510 tab: the tab for the test.
511 results: the results object to add the results values to.
512 bypass_seconds_min: the minimum duration of the bypass.
513 bypass_seconds_max: the maximum duration of the bypass.
515 bypass_count = 0
516 via_count = 0
518 for resp in self.IterResponses(tab):
519 if resp.HasChromeProxyViaHeader():
520 r = resp.response
521 raise ChromeProxyMetricException, (
522 'Response for %s should not have via header.\n'
523 'Reponse: status=(%d, %s)\nHeaders:\n %s' % (
524 r.url, r.status, r.status_text, r.headers))
525 else:
526 bypass_count += 1
528 # Wait until 30 seconds before the bypass should expire, and fetch a page.
529 # It should not have the via header because the proxy should still be
530 # bypassed.
531 time.sleep(bypass_seconds_min - 30)
533 tab.ClearCache(force=True)
534 before_metrics = ChromeProxyMetric()
535 before_metrics.Start(results.current_page, tab)
536 tab.Navigate('http://chromeproxy-test.appspot.com/default')
537 tab.WaitForJavaScriptExpression('performance.timing.loadEventStart', 10)
538 before_metrics.Stop(results.current_page, tab)
540 for resp in before_metrics.IterResponses(tab):
541 if resp.HasChromeProxyViaHeader():
542 r = resp.response
543 raise ChromeProxyMetricException, (
544 'Response for %s should not have via header; proxy should still '
545 'be bypassed.\nReponse: status=(%d, %s)\nHeaders:\n %s' % (
546 r.url, r.status, r.status_text, r.headers))
547 else:
548 bypass_count += 1
549 if bypass_count == 0:
550 raise ChromeProxyMetricException, (
551 'Expected at least one response to be bypassed before the bypass '
552 'expired, but zero such responses were received.')
554 # Wait until 30 seconds after the bypass should expire, and fetch a page. It
555 # should have the via header since the proxy should no longer be bypassed.
556 time.sleep((bypass_seconds_max + 30) - (bypass_seconds_min - 30))
558 tab.ClearCache(force=True)
559 after_metrics = ChromeProxyMetric()
560 after_metrics.Start(results.current_page, tab)
561 tab.Navigate('http://chromeproxy-test.appspot.com/default')
562 tab.WaitForJavaScriptExpression('performance.timing.loadEventStart', 10)
563 after_metrics.Stop(results.current_page, tab)
565 for resp in after_metrics.IterResponses(tab):
566 if not resp.HasChromeProxyViaHeader():
567 r = resp.response
568 raise ChromeProxyMetricException, (
569 'Response for %s should have via header; proxy should no longer '
570 'be bypassed.\nReponse: status=(%d, %s)\nHeaders:\n %s' % (
571 r.url, r.status, r.status_text, r.headers))
572 else:
573 via_count += 1
574 if via_count == 0:
575 raise ChromeProxyMetricException, (
576 'Expected at least one response through the proxy after the bypass '
577 'expired, but zero such responses were received.')
579 results.AddValue(scalar.ScalarValue(
580 results.current_page, 'bypass', 'count', bypass_count))
581 results.AddValue(scalar.ScalarValue(
582 results.current_page, 'via', 'count', via_count))