ozone: evdev: Sync caps lock LED state to evdev
[chromium-blink-merge.git] / tools / chrome_proxy / integration_tests / chrome_proxy_metrics.py
blob65133767a9137b0efce9752da3bbfdf813ff8e7c
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 IsValidByViaHeader(self):
51 return (not self.ShouldHaveChromeProxyViaHeader() or
52 self.HasChromeProxyViaHeader())
54 def GetChromeProxyClientType(self):
55 """Get the client type directive from the Chrome-Proxy request header.
57 Returns:
58 The client type directive from the Chrome-Proxy request header for the
59 request that lead to this response. For example, if the request header
60 "Chrome-Proxy: c=android" is present, then this method would return
61 "android". Returns None if no client type directive is present.
62 """
63 if 'Chrome-Proxy' not in self.response.request_headers:
64 return None
66 chrome_proxy_request_header = self.response.request_headers['Chrome-Proxy']
67 values = [v.strip() for v in chrome_proxy_request_header.split(',')]
68 for value in values:
69 kvp = value.split('=', 1)
70 if len(kvp) == 2 and kvp[0].strip() == 'c':
71 return kvp[1].strip()
72 return None
74 def HasChromeProxyLoFi(self):
75 if 'Chrome-Proxy' not in self.response.request_headers:
76 return False
77 chrome_proxy_request_header = self.response.request_headers['Chrome-Proxy']
78 values = [v.strip() for v in chrome_proxy_request_header.split(',')]
79 for value in values:
80 if len(value) == 5 and value == 'q=low':
81 return True
82 return False
84 class ChromeProxyMetric(network_metrics.NetworkMetric):
85 """A Chrome proxy timeline metric."""
87 def __init__(self):
88 super(ChromeProxyMetric, self).__init__()
89 self.compute_data_saving = True
91 def SetEvents(self, events):
92 """Used for unittest."""
93 self._events = events
95 def ResponseFromEvent(self, event):
96 return ChromeProxyResponse(event)
98 def AddResults(self, tab, results):
99 raise NotImplementedError
101 def AddResultsForDataSaving(self, tab, results):
102 resources_via_proxy = 0
103 resources_from_cache = 0
104 resources_direct = 0
106 super(ChromeProxyMetric, self).AddResults(tab, results)
107 for resp in self.IterResponses(tab):
108 if resp.response.served_from_cache:
109 resources_from_cache += 1
110 if resp.HasChromeProxyViaHeader():
111 resources_via_proxy += 1
112 else:
113 resources_direct += 1
115 results.AddValue(scalar.ScalarValue(
116 results.current_page, 'resources_via_proxy', 'count',
117 resources_via_proxy))
118 results.AddValue(scalar.ScalarValue(
119 results.current_page, 'resources_from_cache', 'count',
120 resources_from_cache))
121 results.AddValue(scalar.ScalarValue(
122 results.current_page, 'resources_direct', 'count', resources_direct))
124 def AddResultsForHeaderValidation(self, tab, results):
125 via_count = 0
127 for resp in self.IterResponses(tab):
128 if resp.IsValidByViaHeader():
129 via_count += 1
130 else:
131 r = resp.response
132 raise ChromeProxyMetricException, (
133 '%s: Via header (%s) is not valid (refer=%s, status=%d)' % (
134 r.url, r.GetHeader('Via'), r.GetHeader('Referer'), r.status))
135 results.AddValue(scalar.ScalarValue(
136 results.current_page, 'checked_via_header', 'count', via_count))
138 def AddResultsForClientVersion(self, tab, results):
139 for resp in self.IterResponses(tab):
140 r = resp.response
141 if resp.response.status != 200:
142 raise ChromeProxyMetricException, ('%s: Response is not 200: %d' %
143 (r.url, r.status))
144 if not resp.IsValidByViaHeader():
145 raise ChromeProxyMetricException, ('%s: Response missing via header' %
146 (r.url))
147 results.AddValue(scalar.ScalarValue(
148 results.current_page, 'version_test', 'count', 1))
150 def GetClientTypeFromRequests(self, tab):
151 """Get the Chrome-Proxy client type value from requests made in this tab.
153 Returns:
154 The client type value from the first request made in this tab that
155 specifies a client type in the Chrome-Proxy request header. See
156 ChromeProxyResponse.GetChromeProxyClientType for more details about the
157 Chrome-Proxy client type. Returns None if none of the requests made in
158 this tab specify a client type.
160 for resp in self.IterResponses(tab):
161 client_type = resp.GetChromeProxyClientType()
162 if client_type:
163 return client_type
164 return None
166 def AddResultsForClientType(self, tab, results, client_type,
167 bypass_for_client_type):
168 via_count = 0
169 bypass_count = 0
171 for resp in self.IterResponses(tab):
172 if resp.HasChromeProxyViaHeader():
173 via_count += 1
174 if client_type.lower() == bypass_for_client_type.lower():
175 raise ChromeProxyMetricException, (
176 '%s: Response for client of type "%s" has via header, but should '
177 'be bypassed.' % (
178 resp.response.url, bypass_for_client_type, client_type))
179 elif resp.ShouldHaveChromeProxyViaHeader():
180 bypass_count += 1
181 if client_type.lower() != bypass_for_client_type.lower():
182 raise ChromeProxyMetricException, (
183 '%s: Response missing via header. Only "%s" clients should '
184 'bypass for this page, but this client is "%s".' % (
185 resp.response.url, bypass_for_client_type, client_type))
187 results.AddValue(scalar.ScalarValue(
188 results.current_page, 'via', 'count', via_count))
189 results.AddValue(scalar.ScalarValue(
190 results.current_page, 'bypass', 'count', bypass_count))
192 def AddResultsForLoFi(self, tab, results):
193 lo_fi_count = 0
195 for resp in self.IterResponses(tab):
196 if resp.HasChromeProxyViaHeader():
197 lo_fi_count += 1
198 else:
199 r = resp.response
200 raise ChromeProxyMetricException, (
201 '%s: LoFi not in request header.' % (r.url))
203 cl = resp.content_length
204 resource = resp.response.url
205 results.AddValue(scalar.ScalarValue(
206 results.current_page, 'lo_fi', 'count', lo_fi_count))
208 for resp in self.IterResponses(tab):
209 r = resp.response
210 cl = resp.content_length
211 ocl = resp.original_content_length
212 saving = resp.data_saving_rate * 100
213 if cl > 100:
214 raise ChromeProxyMetricException, (
215 'Image %s is %d bytes. Expecting less than 100 bytes.' %
216 (resource, cl))
218 results.AddValue(scalar.ScalarValue(
219 results.current_page, 'content_length', 'bytes', cl))
220 results.AddValue(scalar.ScalarValue(
221 results.current_page, 'original_content_length', 'bytes', ocl))
222 results.AddValue(scalar.ScalarValue(
223 results.current_page, 'data_saving', 'percent', saving))
225 def AddResultsForBypass(self, tab, results):
226 bypass_count = 0
228 for resp in self.IterResponses(tab):
229 if resp.HasChromeProxyViaHeader():
230 r = resp.response
231 raise ChromeProxyMetricException, (
232 '%s: Should not have Via header (%s) (refer=%s, status=%d)' % (
233 r.url, r.GetHeader('Via'), r.GetHeader('Referer'), r.status))
234 bypass_count += 1
236 results.AddValue(scalar.ScalarValue(
237 results.current_page, 'bypass', 'count', bypass_count))
239 def AddResultsForCorsBypass(self, tab, results):
240 eligible_response_count = 0
241 bypass_count = 0
242 bypasses = {}
243 for resp in self.IterResponses(tab):
244 logging.warn('got a resource %s' % (resp.response.url))
246 for resp in self.IterResponses(tab):
247 if resp.ShouldHaveChromeProxyViaHeader():
248 eligible_response_count += 1
249 if not resp.HasChromeProxyViaHeader():
250 bypass_count += 1
251 elif resp.response.status == 502:
252 bypasses[resp.response.url] = 0
254 for resp in self.IterResponses(tab):
255 if resp.ShouldHaveChromeProxyViaHeader():
256 if not resp.HasChromeProxyViaHeader():
257 if resp.response.status == 200:
258 if (bypasses.has_key(resp.response.url)):
259 bypasses[resp.response.url] = bypasses[resp.response.url] + 1
261 for url in bypasses:
262 if bypasses[url] == 0:
263 raise ChromeProxyMetricException, (
264 '%s: Got a 502 without a subsequent 200' % (url))
265 elif bypasses[url] > 1:
266 raise ChromeProxyMetricException, (
267 '%s: Got a 502 and multiple 200s: %d' % (url, bypasses[url]))
268 if bypass_count == 0:
269 raise ChromeProxyMetricException, (
270 'At least one response should be bypassed. '
271 '(eligible_response_count=%d, bypass_count=%d)\n' % (
272 eligible_response_count, bypass_count))
274 results.AddValue(scalar.ScalarValue(
275 results.current_page, 'cors_bypass', 'count', bypass_count))
277 def AddResultsForBlockOnce(self, tab, results):
278 eligible_response_count = 0
279 bypass_count = 0
281 for resp in self.IterResponses(tab):
282 if resp.ShouldHaveChromeProxyViaHeader():
283 eligible_response_count += 1
284 if not resp.HasChromeProxyViaHeader():
285 bypass_count += 1
287 if eligible_response_count <= 1:
288 raise ChromeProxyMetricException, (
289 'There should be more than one DRP eligible response '
290 '(eligible_response_count=%d, bypass_count=%d)\n' % (
291 eligible_response_count, bypass_count))
292 elif bypass_count != 1:
293 raise ChromeProxyMetricException, (
294 'Exactly one response should be bypassed. '
295 '(eligible_response_count=%d, bypass_count=%d)\n' % (
296 eligible_response_count, bypass_count))
297 else:
298 results.AddValue(scalar.ScalarValue(
299 results.current_page, 'eligible_responses', 'count',
300 eligible_response_count))
301 results.AddValue(scalar.ScalarValue(
302 results.current_page, 'bypass', 'count', bypass_count))
304 def AddResultsForSafebrowsingOn(self, tab, results):
305 results.AddValue(scalar.ScalarValue(
306 results.current_page, 'safebrowsing', 'timeout responses', 1))
308 def AddResultsForSafebrowsingOff(self, tab, results):
309 response_count = 0
310 for resp in self.IterResponses(tab):
311 # Data reduction proxy should return the real response for sites with
312 # malware.
313 response_count += 1
314 if not resp.HasChromeProxyViaHeader():
315 r = resp.response
316 raise ChromeProxyMetricException, (
317 '%s: Safebrowsing feature should be off for desktop and webview.\n'
318 'Reponse: status=(%d, %s)\nHeaders:\n %s' % (
319 r.url, r.status, r.status_text, r.headers))
321 if response_count == 0:
322 raise ChromeProxyMetricException, (
323 'Safebrowsing test failed: No valid responses received')
325 results.AddValue(scalar.ScalarValue(
326 results.current_page, 'safebrowsing', 'responses', response_count))
328 def AddResultsForHTTPFallback(self, tab, results):
329 via_fallback_count = 0
331 for resp in self.IterResponses(tab):
332 if resp.ShouldHaveChromeProxyViaHeader():
333 # All responses should have come through the HTTP fallback proxy, which
334 # means that they should have the via header, and if a remote port is
335 # defined, it should be port 80.
336 if (not resp.HasChromeProxyViaHeader() or
337 (resp.remote_port and resp.remote_port != 80)):
338 r = resp.response
339 raise ChromeProxyMetricException, (
340 '%s: Should have come through the fallback proxy.\n'
341 'Reponse: remote_port=%s status=(%d, %s)\nHeaders:\n %s' % (
342 r.url, str(resp.remote_port), r.status, r.status_text,
343 r.headers))
344 via_fallback_count += 1
346 results.AddValue(scalar.ScalarValue(
347 results.current_page, 'via_fallback', 'count', via_fallback_count))
349 def AddResultsForHTTPToDirectFallback(self, tab, results,
350 fallback_response_host):
351 via_fallback_count = 0
352 bypass_count = 0
353 responses = self.IterResponses(tab)
355 # The first response(s) coming from fallback_response_host should be
356 # through the HTTP fallback proxy.
357 resp = next(responses, None)
358 while resp and fallback_response_host in resp.response.url:
359 if fallback_response_host in resp.response.url:
360 if (not resp.HasChromeProxyViaHeader() or resp.remote_port != 80):
361 r = resp.response
362 raise ChromeProxyMetricException, (
363 'Response for %s should have come through the fallback proxy.\n'
364 'Response: remote_port=%s status=(%d, %s)\nHeaders:\n %s' % (
365 r.url, str(resp.remote_port), r.status, r.status_text,
366 r.headers))
367 else:
368 via_fallback_count += 1
369 resp = next(responses, None)
371 # All other responses should be bypassed.
372 while resp:
373 if resp.HasChromeProxyViaHeader():
374 r = resp.response
375 raise ChromeProxyMetricException, (
376 'Response for %s should not have via header.\n'
377 'Response: status=(%d, %s)\nHeaders:\n %s' % (
378 r.url, r.status, r.status_text, r.headers))
379 else:
380 bypass_count += 1
381 resp = next(responses, None)
383 # At least one response should go through the http proxy and be bypassed.
384 if via_fallback_count == 0 or bypass_count == 0:
385 raise ChromeProxyMetricException(
386 'There should be at least one response through the fallback proxy '
387 '(actual %s) and at least one bypassed response (actual %s)' %
388 (via_fallback_count, bypass_count))
390 results.AddValue(scalar.ScalarValue(
391 results.current_page, 'via_fallback', 'count', via_fallback_count))
392 results.AddValue(scalar.ScalarValue(
393 results.current_page, 'bypass', 'count', bypass_count))
395 def AddResultsForReenableAfterBypass(
396 self, tab, results, bypass_seconds_min, bypass_seconds_max):
397 """Verify results for a re-enable after bypass test.
399 Args:
400 tab: the tab for the test.
401 results: the results object to add the results values to.
402 bypass_seconds_min: the minimum duration of the bypass.
403 bypass_seconds_max: the maximum duration of the bypass.
405 bypass_count = 0
406 via_count = 0
408 for resp in self.IterResponses(tab):
409 if resp.HasChromeProxyViaHeader():
410 r = resp.response
411 raise ChromeProxyMetricException, (
412 'Response for %s should not have via header.\n'
413 'Reponse: status=(%d, %s)\nHeaders:\n %s' % (
414 r.url, r.status, r.status_text, r.headers))
415 else:
416 bypass_count += 1
418 # Wait until 30 seconds before the bypass should expire, and fetch a page.
419 # It should not have the via header because the proxy should still be
420 # bypassed.
421 time.sleep(bypass_seconds_min - 30)
423 tab.ClearCache(force=True)
424 before_metrics = ChromeProxyMetric()
425 before_metrics.Start(results.current_page, tab)
426 tab.Navigate('http://chromeproxy-test.appspot.com/default')
427 tab.WaitForJavaScriptExpression('performance.timing.loadEventStart', 10)
428 before_metrics.Stop(results.current_page, tab)
430 for resp in before_metrics.IterResponses(tab):
431 if resp.HasChromeProxyViaHeader():
432 r = resp.response
433 raise ChromeProxyMetricException, (
434 'Response for %s should not have via header; proxy should still '
435 'be bypassed.\nReponse: status=(%d, %s)\nHeaders:\n %s' % (
436 r.url, r.status, r.status_text, r.headers))
437 else:
438 bypass_count += 1
440 # Wait until 30 seconds after the bypass should expire, and fetch a page. It
441 # should have the via header since the proxy should no longer be bypassed.
442 time.sleep((bypass_seconds_max + 30) - (bypass_seconds_min - 30))
444 tab.ClearCache(force=True)
445 after_metrics = ChromeProxyMetric()
446 after_metrics.Start(results.current_page, tab)
447 tab.Navigate('http://chromeproxy-test.appspot.com/default')
448 tab.WaitForJavaScriptExpression('performance.timing.loadEventStart', 10)
449 after_metrics.Stop(results.current_page, tab)
451 for resp in after_metrics.IterResponses(tab):
452 if not resp.HasChromeProxyViaHeader():
453 r = resp.response
454 raise ChromeProxyMetricException, (
455 'Response for %s should have via header; proxy should no longer '
456 'be bypassed.\nReponse: status=(%d, %s)\nHeaders:\n %s' % (
457 r.url, r.status, r.status_text, r.headers))
458 else:
459 via_count += 1
461 results.AddValue(scalar.ScalarValue(
462 results.current_page, 'bypass', 'count', bypass_count))
463 results.AddValue(scalar.ScalarValue(
464 results.current_page, 'via', 'count', via_count))