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 |