| OLD | NEW |
| 1 # Copyright (c) 2016 The Chromium Authors. All rights reserved. | 1 # Copyright (c) 2016 The Chromium Authors. All rights reserved. |
| 2 # Use of this source code is governed by a BSD-style license that can be | 2 # Use of this source code is governed by a BSD-style license that can be |
| 3 # found in the LICENSE file. | 3 # found in the LICENSE file. |
| 4 | 4 |
| 5 """The request data track. | 5 """The request data track. |
| 6 | 6 |
| 7 When executed, parses a JSON dump of DevTools messages. | 7 When executed, parses a JSON dump of DevTools messages. |
| 8 """ | 8 """ |
| 9 | 9 |
| 10 import collections | 10 import collections |
| (...skipping 28 matching lines...) Expand all Loading... |
| 39 """ | 39 """ |
| 40 return json.loads(json.dumps(timing)) | 40 return json.loads(json.dumps(timing)) |
| 41 | 41 |
| 42 class Request(object): | 42 class Request(object): |
| 43 """Represents a single request. | 43 """Represents a single request. |
| 44 | 44 |
| 45 Generally speaking, fields here closely mirror those documented in | 45 Generally speaking, fields here closely mirror those documented in |
| 46 third_party/WebKit/Source/devtools/protocol.json. | 46 third_party/WebKit/Source/devtools/protocol.json. |
| 47 | 47 |
| 48 Fields: | 48 Fields: |
| 49 request_id: (str) unique request ID. Postfixed with REDIRECT_SUFFIX for | 49 request_id: (str) unique request ID. Postfixed with _REDIRECT_SUFFIX for |
| 50 redirects. | 50 redirects. |
| 51 frame_id: (str) unique frame identifier. | 51 frame_id: (str) unique frame identifier. |
| 52 loader_id: (str) unique frame identifier. | 52 loader_id: (str) unique frame identifier. |
| 53 document_url: (str) URL of the document this request is loaded for. | 53 document_url: (str) URL of the document this request is loaded for. |
| 54 url: (str) Request URL. | 54 url: (str) Request URL. |
| 55 protocol: (str) protocol used for the request. | 55 protocol: (str) protocol used for the request. |
| 56 method: (str) HTTP method, such as POST or GET. | 56 method: (str) HTTP method, such as POST or GET. |
| 57 request_headers: (dict) {'header': 'value'} Request headers. | 57 request_headers: (dict) {'header': 'value'} Request headers. |
| 58 response_headers: (dict) {'header': 'value'} Response headers. | 58 response_headers: (dict) {'header': 'value'} Response headers. |
| 59 initial_priority: (str) Initial request priority, in REQUEST_PRIORITIES. | 59 initial_priority: (str) Initial request priority, in REQUEST_PRIORITIES. |
| (...skipping 10 matching lines...) Expand all Loading... |
| 70 encoded_data_length: (int) Total encoded data length. | 70 encoded_data_length: (int) Total encoded data length. |
| 71 data_chunks: (list) [(offset, encoded_data_length), ...] List of data | 71 data_chunks: (list) [(offset, encoded_data_length), ...] List of data |
| 72 chunks received, with their offset in ms relative to | 72 chunks received, with their offset in ms relative to |
| 73 Timing.requestTime. | 73 Timing.requestTime. |
| 74 failed: (bool) Whether the request failed. | 74 failed: (bool) Whether the request failed. |
| 75 """ | 75 """ |
| 76 REQUEST_PRIORITIES = ('VeryLow', 'Low', 'Medium', 'High', 'VeryHigh') | 76 REQUEST_PRIORITIES = ('VeryLow', 'Low', 'Medium', 'High', 'VeryHigh') |
| 77 RESOURCE_TYPES = ('Document', 'Stylesheet', 'Image', 'Media', 'Font', | 77 RESOURCE_TYPES = ('Document', 'Stylesheet', 'Image', 'Media', 'Font', |
| 78 'Script', 'TextTrack', 'XHR', 'Fetch', 'EventSource', | 78 'Script', 'TextTrack', 'XHR', 'Fetch', 'EventSource', |
| 79 'WebSocket', 'Manifest', 'Other') | 79 'WebSocket', 'Manifest', 'Other') |
| 80 INITIATORS = ('parser', 'script', 'other') | 80 INITIATORS = ('parser', 'script', 'other', 'redirect') |
| 81 INITIATING_REQUEST = 'initiating_request' |
| 82 ORIGINAL_INITIATOR = 'original_initiator' |
| 81 def __init__(self): | 83 def __init__(self): |
| 82 self.request_id = None | 84 self.request_id = None |
| 83 self.frame_id = None | 85 self.frame_id = None |
| 84 self.loader_id = None | 86 self.loader_id = None |
| 85 self.document_url = None | 87 self.document_url = None |
| 86 self.url = None | 88 self.url = None |
| 87 self.protocol = None | 89 self.protocol = None |
| 88 self.method = None | 90 self.method = None |
| 89 self.request_headers = None | 91 self.request_headers = None |
| 90 self.response_headers = None | 92 self.response_headers = None |
| (...skipping 74 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 165 | 167 |
| 166 def __hash__(self): | 168 def __hash__(self): |
| 167 return hash(self.request_id) | 169 return hash(self.request_id) |
| 168 | 170 |
| 169 def __str__(self): | 171 def __str__(self): |
| 170 return json.dumps(self.ToJsonDict(), sort_keys=True, indent=2) | 172 return json.dumps(self.ToJsonDict(), sort_keys=True, indent=2) |
| 171 | 173 |
| 172 | 174 |
| 173 class RequestTrack(devtools_monitor.Track): | 175 class RequestTrack(devtools_monitor.Track): |
| 174 """Aggregates request data.""" | 176 """Aggregates request data.""" |
| 175 REDIRECT_SUFFIX = '.redirect' | 177 _REDIRECT_SUFFIX = '.redirect' |
| 176 # Request status | 178 # Request status |
| 177 _STATUS_SENT = 0 | 179 _STATUS_SENT = 0 |
| 178 _STATUS_RESPONSE = 1 | 180 _STATUS_RESPONSE = 1 |
| 179 _STATUS_DATA = 2 | 181 _STATUS_DATA = 2 |
| 180 _STATUS_FINISHED = 3 | 182 _STATUS_FINISHED = 3 |
| 181 _STATUS_FAILED = 4 | 183 _STATUS_FAILED = 4 |
| 182 # Serialization KEYS | 184 # Serialization KEYS |
| 183 _EVENTS_KEY = 'events' | 185 _EVENTS_KEY = 'events' |
| 184 _METADATA_KEY = 'metadata' | 186 _METADATA_KEY = 'metadata' |
| 185 _DUPLICATES_KEY = 'duplicates_count' | 187 _DUPLICATES_KEY = 'duplicates_count' |
| 188 _INCONSISTENT_INITIATORS_KEY = 'inconsistent_initiators' |
| 186 def __init__(self, connection): | 189 def __init__(self, connection): |
| 187 super(RequestTrack, self).__init__(connection) | 190 super(RequestTrack, self).__init__(connection) |
| 188 self._connection = connection | 191 self._connection = connection |
| 189 self._requests = [] | 192 self._requests = [] |
| 190 self._requests_in_flight = {} # requestId -> (request, status) | 193 self._requests_in_flight = {} # requestId -> (request, status) |
| 191 self._completed_requests_by_id = {} | 194 self._completed_requests_by_id = {} |
| 195 self._redirects_count_by_id = collections.defaultdict(int) |
| 192 if connection: # Optional for testing. | 196 if connection: # Optional for testing. |
| 193 for method in RequestTrack._METHOD_TO_HANDLER: | 197 for method in RequestTrack._METHOD_TO_HANDLER: |
| 194 self._connection.RegisterListener(method, self) | 198 self._connection.RegisterListener(method, self) |
| 195 # responseReceived message are sometimes duplicated. Records the message to | 199 # responseReceived message are sometimes duplicated. Records the message to |
| 196 # detect this. | 200 # detect this. |
| 197 self._request_id_to_response_received = {} | 201 self._request_id_to_response_received = {} |
| 198 self.duplicates_count = 0 | 202 self.duplicates_count = 0 |
| 203 self.inconsistent_initiators_count = 0 |
| 199 | 204 |
| 200 def Handle(self, method, msg): | 205 def Handle(self, method, msg): |
| 201 assert method in RequestTrack._METHOD_TO_HANDLER | 206 assert method in RequestTrack._METHOD_TO_HANDLER |
| 202 params = msg['params'] | 207 params = msg['params'] |
| 203 request_id = params['requestId'] | 208 request_id = params['requestId'] |
| 204 RequestTrack._METHOD_TO_HANDLER[method](self, request_id, params) | 209 RequestTrack._METHOD_TO_HANDLER[method](self, request_id, params) |
| 205 | 210 |
| 206 def GetEvents(self): | 211 def GetEvents(self): |
| 207 if self._requests_in_flight: | 212 if self._requests_in_flight: |
| 208 logging.warning('Number of requests still in flight: %d.' | 213 logging.warning('Number of requests still in flight: %d.' |
| 209 % len(self._requests_in_flight)) | 214 % len(self._requests_in_flight)) |
| 210 return self._requests | 215 return self._requests |
| 211 | 216 |
| 212 def ToJsonDict(self): | 217 def ToJsonDict(self): |
| 213 if self._requests_in_flight: | 218 if self._requests_in_flight: |
| 214 logging.warning('Requests in flight, will be ignored in the dump') | 219 logging.warning('Requests in flight, will be ignored in the dump') |
| 215 return {self._EVENTS_KEY: [ | 220 return {self._EVENTS_KEY: [ |
| 216 request.ToJsonDict() for request in self._requests], | 221 request.ToJsonDict() for request in self._requests], |
| 217 self._METADATA_KEY: {self._DUPLICATES_KEY: self.duplicates_count}} | 222 self._METADATA_KEY: { |
| 223 self._DUPLICATES_KEY: self.duplicates_count, |
| 224 self._INCONSISTENT_INITIATORS_KEY: |
| 225 self.inconsistent_initiators_count}} |
| 218 | 226 |
| 219 @classmethod | 227 @classmethod |
| 220 def FromJsonDict(cls, json_dict): | 228 def FromJsonDict(cls, json_dict): |
| 221 assert cls._EVENTS_KEY in json_dict | 229 assert cls._EVENTS_KEY in json_dict |
| 222 assert cls._METADATA_KEY in json_dict | 230 assert cls._METADATA_KEY in json_dict |
| 223 result = RequestTrack(None) | 231 result = RequestTrack(None) |
| 224 requests = [Request.FromJsonDict(request) | 232 requests = [Request.FromJsonDict(request) |
| 225 for request in json_dict[cls._EVENTS_KEY]] | 233 for request in json_dict[cls._EVENTS_KEY]] |
| 226 result._requests = requests | 234 result._requests = requests |
| 227 result.duplicates_count = json_dict[cls._METADATA_KEY][cls._DUPLICATES_KEY] | 235 metadata = json_dict[cls._METADATA_KEY] |
| 236 result.duplicates_count = metadata.get(cls._DUPLICATES_KEY, 0) |
| 237 result.inconsistent_initiators_count = metadata.get( |
| 238 cls._INCONSISTENT_INITIATORS_KEY, 0) |
| 228 return result | 239 return result |
| 229 | 240 |
| 230 def _RequestWillBeSent(self, request_id, params): | 241 def _RequestWillBeSent(self, request_id, params): |
| 231 # Several "requestWillBeSent" events can be dispatched in a row in the case | 242 # Several "requestWillBeSent" events can be dispatched in a row in the case |
| 232 # of redirects. | 243 # of redirects. |
| 244 redirect_initiator = None |
| 233 if request_id in self._requests_in_flight: | 245 if request_id in self._requests_in_flight: |
| 234 self._HandleRedirect(request_id, params) | 246 redirect_initiator = self._HandleRedirect(request_id, params) |
| 235 assert (request_id not in self._requests_in_flight | 247 assert (request_id not in self._requests_in_flight |
| 236 and request_id not in self._completed_requests_by_id) | 248 and request_id not in self._completed_requests_by_id) |
| 237 r = Request() | 249 r = Request() |
| 238 r.request_id = request_id | 250 r.request_id = request_id |
| 239 _CopyFromDictToObject( | 251 _CopyFromDictToObject( |
| 240 params, r, (('frameId', 'frame_id'), ('loaderId', 'loader_id'), | 252 params, r, (('frameId', 'frame_id'), ('loaderId', 'loader_id'), |
| 241 ('documentURL', 'document_url'), | 253 ('documentURL', 'document_url'), |
| 242 ('timestamp', 'timestamp'), ('wallTime', 'wall_time'), | 254 ('timestamp', 'timestamp'), ('wallTime', 'wall_time'), |
| 243 ('initiator', 'initiator'))) | 255 ('initiator', 'initiator'))) |
| 244 request = params['request'] | 256 request = params['request'] |
| 245 _CopyFromDictToObject( | 257 _CopyFromDictToObject( |
| 246 request, r, (('url', 'url'), ('method', 'method'), | 258 request, r, (('url', 'url'), ('method', 'method'), |
| 247 ('headers', 'headers'), | 259 ('headers', 'headers'), |
| 248 ('initialPriority', 'initial_priority'))) | 260 ('initialPriority', 'initial_priority'))) |
| 249 r.resource_type = params.get('type', 'Other') | 261 r.resource_type = params.get('type', 'Other') |
| 262 if redirect_initiator: |
| 263 original_initiator = r.initiator |
| 264 r.initiator = redirect_initiator |
| 265 r.initiator[Request.ORIGINAL_INITIATOR] = original_initiator |
| 266 initiating_request = self._completed_requests_by_id[ |
| 267 redirect_initiator[Request.INITIATING_REQUEST]] |
| 268 initiating_initiator = initiating_request.initiator.get( |
| 269 Request.ORIGINAL_INITIATOR, initiating_request.initiator) |
| 270 if initiating_initiator != original_initiator: |
| 271 self.inconsistent_initiators_count += 1 |
| 250 self._requests_in_flight[request_id] = (r, RequestTrack._STATUS_SENT) | 272 self._requests_in_flight[request_id] = (r, RequestTrack._STATUS_SENT) |
| 251 | 273 |
| 252 def _HandleRedirect(self, request_id, params): | 274 def _HandleRedirect(self, request_id, params): |
| 253 (r, status) = self._requests_in_flight[request_id] | 275 (r, status) = self._requests_in_flight[request_id] |
| 254 assert status == RequestTrack._STATUS_SENT | 276 assert status == RequestTrack._STATUS_SENT |
| 255 # The second request contains timing information pertaining to the first | 277 # The second request contains timing information pertaining to the first |
| 256 # one. Finalize the first request. | 278 # one. Finalize the first request. |
| 257 assert 'redirectResponse' in params | 279 assert 'redirectResponse' in params |
| 258 redirect_response = params['redirectResponse'] | 280 redirect_response = params['redirectResponse'] |
| 281 |
| 259 _CopyFromDictToObject(redirect_response, r, | 282 _CopyFromDictToObject(redirect_response, r, |
| 260 (('headers', 'response_headers'), | 283 (('headers', 'response_headers'), |
| 261 ('encodedDataLength', 'encoded_data_length'), | 284 ('encodedDataLength', 'encoded_data_length'), |
| 262 ('fromDiskCache', 'from_disk_cache'))) | 285 ('fromDiskCache', 'from_disk_cache'))) |
| 263 r.timing = TimingFromDict(redirect_response['timing']) | 286 r.timing = TimingFromDict(redirect_response['timing']) |
| 264 r.request_id = request_id + self.REDIRECT_SUFFIX | 287 |
| 288 redirect_index = self._redirects_count_by_id[request_id] |
| 289 self._redirects_count_by_id[request_id] += 1 |
| 290 r.request_id = '%s%s.%d' % (request_id, self._REDIRECT_SUFFIX, |
| 291 redirect_index + 1) |
| 292 initiator = { |
| 293 'type': 'redirect', Request.INITIATING_REQUEST: r.request_id} |
| 265 self._requests_in_flight[r.request_id] = (r, RequestTrack._STATUS_FINISHED) | 294 self._requests_in_flight[r.request_id] = (r, RequestTrack._STATUS_FINISHED) |
| 266 del self._requests_in_flight[request_id] | 295 del self._requests_in_flight[request_id] |
| 267 self._FinalizeRequest(r.request_id) | 296 self._FinalizeRequest(r.request_id) |
| 297 return initiator |
| 268 | 298 |
| 269 def _RequestServedFromCache(self, request_id, _): | 299 def _RequestServedFromCache(self, request_id, _): |
| 270 assert request_id in self._requests_in_flight | 300 assert request_id in self._requests_in_flight |
| 271 (request, status) = self._requests_in_flight[request_id] | 301 (request, status) = self._requests_in_flight[request_id] |
| 272 assert status == RequestTrack._STATUS_SENT | 302 assert status == RequestTrack._STATUS_SENT |
| 273 request.served_from_cache = True | 303 request.served_from_cache = True |
| 274 | 304 |
| 275 def _ResponseReceived(self, request_id, params): | 305 def _ResponseReceived(self, request_id, params): |
| 276 assert request_id in self._requests_in_flight | 306 assert request_id in self._requests_in_flight |
| 277 (r, status) = self._requests_in_flight[request_id] | 307 (r, status) = self._requests_in_flight[request_id] |
| (...skipping 96 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 374 | 404 |
| 375 | 405 |
| 376 if __name__ == '__main__': | 406 if __name__ == '__main__': |
| 377 import json | 407 import json |
| 378 import sys | 408 import sys |
| 379 events = json.load(open(sys.argv[1], 'r')) | 409 events = json.load(open(sys.argv[1], 'r')) |
| 380 request_track = RequestTrack(None) | 410 request_track = RequestTrack(None) |
| 381 for event in events: | 411 for event in events: |
| 382 event_method = event['method'] | 412 event_method = event['method'] |
| 383 request_track.Handle(event_method, event) | 413 request_track.Handle(event_method, event) |
| OLD | NEW |