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
import inspector_network
16 from telemetry
.timeline
import recording_options
17 from telemetry
.value
import scalar
20 class NetworkMetricException(page_test
.MeasurementFailure
):
24 class HTTPResponse(object):
25 """ Represents an HTTP response from a timeline event."""
26 def __init__(self
, event
):
28 inspector_network
.InspectorNetworkResponseData
.FromTimelineEvent(event
))
29 self
._content
_length
= None
36 def url_signature(self
):
37 return hashlib
.md5(self
.response
.url
).hexdigest()
40 def content_length(self
):
41 if self
._content
_length
is None:
42 self
._content
_length
= self
.GetContentLength()
43 return self
._content
_length
46 def has_original_content_length(self
):
47 return 'X-Original-Content-Length' in self
.response
.headers
50 def original_content_length(self
):
51 if self
.has_original_content_length
:
52 return int(self
.response
.GetHeader('X-Original-Content-Length'))
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):
61 return (float(self
.original_content_length
- self
.content_length
) /
62 self
.original_content_length
)
64 def GetContentLengthFromBody(self
):
66 body
, base64_encoded
= resp
.GetBody()
69 # The binary data like images, etc is base64_encoded. Decode it to get
70 # the actualy content length.
72 decoded
= base64
.b64decode(body
)
75 encoding
= resp
.GetHeader('Content-Encoding')
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
81 encoding
= encoding
.lower()
82 if encoding
== 'gzip':
83 return self
.GetGizppedBodyLength(body
)
84 elif encoding
== 'deflate':
85 return len(zlib
.compress(body
, 9))
87 raise NetworkMetricException
, (
88 'Unknown Content-Encoding %s for %s' % (encoding
, resp
.url
))
90 def GetContentLength(self
):
93 cl
= self
.GetContentLengthFromBody()
96 logging
.warning('Fail to get content length for %s from body: %s',
98 cl_header
= resp
.GetHeader('Content-Length')
102 body
, _
= resp
.GetBody()
108 def GetGizppedBodyLength(body
):
113 with gzip
.GzipFile(fileobj
=bio
, mode
="wb", compresslevel
=9) as f
:
114 f
.write(body
.encode('utf-8'))
116 logging
.warning('Fail to gzip response body: %s', e
)
118 return len(bio
.getvalue())
121 class NetworkMetric(Metric
):
122 """A network metric based on timeline events."""
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
132 def Start(self
, page
, tab
):
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:
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
):
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
:
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
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',
179 original_content_length
+= ocl
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
))
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
))
201 results
.AddValue(scalar
.ScalarValue(
202 results
.current_page
, 'data_saving', 'percent', 0.0))