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