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.
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
):
23 class HTTPResponse(object):
24 """ Represents an HTTP response from a timeline event."""
25 def __init__(self
, event
):
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
38 def remote_port(self
):
39 return self
._remote
_port
42 def url_signature(self
):
43 return hashlib
.md5(self
.response
.url
).hexdigest()
46 def content_length(self
):
47 if self
._content
_length
is None:
48 self
._content
_length
= self
.GetContentLength()
49 return self
._content
_length
52 def has_original_content_length(self
):
53 return 'X-Original-Content-Length' in self
.response
.headers
56 def original_content_length(self
):
57 if self
.has_original_content_length
:
58 return int(self
.response
.GetHeader('X-Original-Content-Length'))
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):
67 return (float(self
.original_content_length
- self
.content_length
) /
68 self
.original_content_length
)
70 def GetContentLengthFromBody(self
):
72 body
, base64_encoded
= resp
.GetBody()
75 # The binary data like images, etc is base64_encoded. Decode it to get
76 # the actualy content length.
78 decoded
= base64
.b64decode(body
)
81 encoding
= resp
.GetHeader('Content-Encoding')
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
87 encoding
= encoding
.lower()
88 if encoding
== 'gzip':
89 return self
.GetGizppedBodyLength(body
)
90 elif encoding
== 'deflate':
91 return len(zlib
.compress(body
, 9))
93 raise NetworkMetricException
, (
94 'Unknown Content-Encoding %s for %s' % (encoding
, resp
.url
))
96 def GetContentLength(self
):
99 cl
= self
.GetContentLengthFromBody()
102 logging
.warning('Fail to get content length for %s from body: %s',
104 cl_header
= resp
.GetHeader('Content-Length')
108 body
, _
= resp
.GetBody()
114 def GetGizppedBodyLength(body
):
119 with gzip
.GzipFile(fileobj
=bio
, mode
="wb", compresslevel
=9) as f
:
120 f
.write(body
.encode('utf-8'))
122 logging
.warning('Fail to gzip response body: %s', e
)
124 return len(bio
.getvalue())
127 class NetworkMetric(Metric
):
128 """A network metric based on timeline events."""
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
138 def Start(self
, page
, tab
):
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:
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
):
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
:
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
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',
183 original_content_length
+= ocl
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
))
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
))
205 results
.AddValue(scalar
.ScalarValue(
206 results
.current_page
, 'data_saving', 'percent', 0.0))