Enable swapping a frame back in to its parent process
[chromium-blink-merge.git] / tools / chrome_proxy / integration_tests / network_metrics.py
blobb83abb0220d5e1c694c298c53acd64815ad3b8b9
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 base64
6 import gzip
7 import hashlib
8 import io
9 import logging
10 import zlib
12 from metrics import Metric
13 from telemetry.page import page_test
14 # All network metrics are Chrome only for now.
15 from telemetry.core.backends.chrome import inspector_network
16 from telemetry.timeline import recording_options
17 from telemetry.value import scalar
20 class NetworkMetricException(page_test.MeasurementFailure):
21 pass
24 class HTTPResponse(object):
25 """ Represents an HTTP response from a timeline event."""
26 def __init__(self, event):
27 self._response = (
28 inspector_network.InspectorNetworkResponseData.FromTimelineEvent(event))
29 self._content_length = None
31 @property
32 def response(self):
33 return self._response
35 @property
36 def url_signature(self):
37 return hashlib.md5(self.response.url).hexdigest()
39 @property
40 def content_length(self):
41 if self._content_length is None:
42 self._content_length = self.GetContentLength()
43 return self._content_length
45 @property
46 def has_original_content_length(self):
47 return 'X-Original-Content-Length' in self.response.headers
49 @property
50 def original_content_length(self):
51 if self.has_original_content_length:
52 return int(self.response.GetHeader('X-Original-Content-Length'))
53 return 0
55 @property
56 def data_saving_rate(self):
57 if (self.response.served_from_cache or
58 not self.has_original_content_length or
59 self.original_content_length <= 0):
60 return 0.0
61 return (float(self.original_content_length - self.content_length) /
62 self.original_content_length)
64 def GetContentLengthFromBody(self):
65 resp = self.response
66 body, base64_encoded = resp.GetBody()
67 if not body:
68 return 0
69 # The binary data like images, etc is base64_encoded. Decode it to get
70 # the actualy content length.
71 if base64_encoded:
72 decoded = base64.b64decode(body)
73 return len(decoded)
75 encoding = resp.GetHeader('Content-Encoding')
76 if not encoding:
77 return len(body)
78 # The response body returned from a timeline event is always decompressed.
79 # So, we need to compress it to get the actual content length if headers
80 # say so.
81 encoding = encoding.lower()
82 if encoding == 'gzip':
83 return self.GetGizppedBodyLength(body)
84 elif encoding == 'deflate':
85 return len(zlib.compress(body, 9))
86 else:
87 raise NetworkMetricException, (
88 'Unknown Content-Encoding %s for %s' % (encoding, resp.url))
90 def GetContentLength(self):
91 cl = 0
92 try:
93 cl = self.GetContentLengthFromBody()
94 except Exception, e:
95 resp = self.response
96 logging.warning('Fail to get content length for %s from body: %s',
97 resp.url[:100], e)
98 cl_header = resp.GetHeader('Content-Length')
99 if cl_header:
100 cl = int(cl_header)
101 else:
102 body, _ = resp.GetBody()
103 if body:
104 cl = len(body)
105 return cl
107 @staticmethod
108 def GetGizppedBodyLength(body):
109 if not body:
110 return 0
111 bio = io.BytesIO()
112 try:
113 with gzip.GzipFile(fileobj=bio, mode="wb", compresslevel=9) as f:
114 f.write(body.encode('utf-8'))
115 except Exception, e:
116 logging.warning('Fail to gzip response body: %s', e)
117 raise e
118 return len(bio.getvalue())
121 class NetworkMetric(Metric):
122 """A network metric based on timeline events."""
124 def __init__(self):
125 super(NetworkMetric, self).__init__()
127 # Whether to add detailed result for each sub-resource in a page.
128 self.add_result_for_resource = False
129 self.compute_data_saving = False
130 self._events = None
132 def Start(self, page, tab):
133 self._events = None
134 opts = recording_options.TimelineRecordingOptions()
135 opts.record_network = True
136 tab.StartTimelineRecording(opts)
138 def Stop(self, page, tab):
139 assert self._events is None
140 tab.StopTimelineRecording()
142 def IterResponses(self, tab):
143 if self._events is None:
144 self._events = tab.timeline_model.GetAllEventsOfName('HTTPResponse')
145 if len(self._events) == 0:
146 return
147 for e in self._events:
148 yield self.ResponseFromEvent(e)
150 def ResponseFromEvent(self, event):
151 return HTTPResponse(event)
153 def AddResults(self, tab, results):
154 content_length = 0
155 original_content_length = 0
157 for resp in self.IterResponses(tab):
158 # Ignore content length calculation for cache hit.
159 if resp.response.served_from_cache:
160 continue
162 resource = resp.response.url
163 resource_signature = resp.url_signature
164 cl = resp.content_length
165 if resp.has_original_content_length:
166 ocl = resp.original_content_length
167 if ocl < cl:
168 logging.warning('original content length (%d) is less than content '
169 'lenght(%d) for resource %s', ocl, cl, resource)
170 if self.add_result_for_resource:
171 results.AddValue(scalar.ScalarValue(
172 results.current_page,
173 'resource_data_saving_' + resource_signature, 'percent',
174 resp.data_saving_rate * 100))
175 results.AddValue(scalar.ScalarValue(
176 results.current_page,
177 'resource_original_content_length_' + resource_signature, 'bytes',
178 ocl))
179 original_content_length += ocl
180 else:
181 original_content_length += cl
182 if self.add_result_for_resource:
183 results.AddValue(scalar.ScalarValue(
184 results.current_page,
185 'resource_content_length_' + resource_signature, 'bytes', cl))
186 content_length += cl
188 results.AddValue(scalar.ScalarValue(
189 results.current_page, 'content_length', 'bytes', content_length))
190 results.AddValue(scalar.ScalarValue(
191 results.current_page, 'original_content_length', 'bytes',
192 original_content_length))
193 if self.compute_data_saving:
194 if (original_content_length > 0 and
195 original_content_length >= content_length):
196 saving = (float(original_content_length-content_length) * 100 /
197 original_content_length)
198 results.AddValue(scalar.ScalarValue(
199 results.current_page, 'data_saving', 'percent', saving))
200 else:
201 results.AddValue(scalar.ScalarValue(
202 results.current_page, 'data_saving', 'percent', 0.0))