OLD | NEW |
1 # Copyright 2015 The Chromium Authors. All rights reserved. | 1 # Copyright 2015 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 """Models for loading in chrome. | 5 """Models for loading in chrome. |
6 | 6 |
7 (Redirect the following to the general model module once we have one) | 7 (Redirect the following to the general model module once we have one) |
8 A model is an object with the following methods. | 8 A model is an object with the following methods. |
9 CostMs(): return the cost of the model in milliseconds. | 9 CostMs(): return the cost of the model in milliseconds. |
10 Set(): set model-specific parameters. | 10 Set(): set model-specific parameters. |
11 | 11 |
12 ResourceGraph | 12 ResourceGraph |
13 This creates a DAG of resource dependencies from loading.log_requests to model | 13 This creates a DAG of resource dependencies from loading.log_requests to model |
14 loading time. The model may be parameterized by changing the loading time of | 14 loading time. The model may be parameterized by changing the loading time of |
15 a particular or all resources. | 15 a particular or all resources. |
16 """ | 16 """ |
17 | 17 |
18 import logging | 18 import logging |
19 import os | 19 import os |
20 import urlparse | 20 import urlparse |
21 import sys | 21 import sys |
22 | 22 |
23 import dag | 23 import dag |
| 24 import loading_trace |
24 import request_dependencies_lens | 25 import request_dependencies_lens |
25 | 26 |
26 class ResourceGraph(object): | 27 class ResourceGraph(object): |
27 """A model of loading by a DAG (tree?) of resource dependancies. | 28 """A model of loading by a DAG (tree?) of resource dependancies. |
28 | 29 |
29 Set parameters: | 30 Set parameters: |
30 cache_all: if true, assume zero loading time for all resources. | 31 cache_all: if true, assume zero loading time for all resources. |
31 """ | 32 """ |
32 def __init__(self, trace): | 33 def __init__(self, trace): |
33 """Create from a LoadingTrace. | 34 """Create from a LoadingTrace (or json of a trace). |
34 | 35 |
35 Args: | 36 Args: |
36 trace: (LoadingTrace) Loading trace. | 37 trace: (LoadingTrace/JSON) Loading trace or JSON of a trace. |
37 """ | 38 """ |
| 39 if type(trace) == dict: |
| 40 trace = loading_trace.LoadingTrace.FromJsonDict(trace) |
38 self._BuildDag(trace) | 41 self._BuildDag(trace) |
39 self._global_start = min([n.StartTime() for n in self._node_info]) | 42 self._global_start = min([n.StartTime() for n in self._node_info]) |
40 # Sort before splitting children so that we can correctly dectect if a | 43 # Sort before splitting children so that we can correctly dectect if a |
41 # reparented child is actually a dependency for a child of its new parent. | 44 # reparented child is actually a dependency for a child of its new parent. |
42 try: | 45 try: |
43 for n in dag.TopologicalSort(self._nodes): | 46 for n in dag.TopologicalSort(self._nodes): |
44 self._SplitChildrenByTime(self._node_info[n.Index()]) | 47 self._SplitChildrenByTime(self._node_info[n.Index()]) |
45 except AssertionError as exc: | 48 except AssertionError as exc: |
46 sys.stderr.write('Bad topological sort: %s\n' | 49 sys.stderr.write('Bad topological sort: %s\n' |
47 'Skipping child split\n' % str(exc)) | 50 'Skipping child split\n' % str(exc)) |
(...skipping 235 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
283 for c in children: | 286 for c in children: |
284 assert n in c.Predecessors() # Integrity checking | 287 assert n in c.Predecessors() # Integrity checking |
285 queue.append(c) | 288 queue.append(c) |
286 assert len(visited) == len(self._nodes) | 289 assert len(visited) == len(self._nodes) |
287 return '\n'.join(output) | 290 return '\n'.join(output) |
288 | 291 |
289 ## | 292 ## |
290 ## Internal items | 293 ## Internal items |
291 ## | 294 ## |
292 | 295 |
293 _CONTENT_TYPE_TO_COLOR = {'html': 'red', 'css': 'green', 'script': 'blue', | 296 _CONTENT_KIND_TO_COLOR = { |
294 'json': 'purple', 'gif_image': 'grey', | 297 'application': 'blue', # Scripts. |
295 'image': 'orange', 'other': 'white'} | 298 'font': 'grey70', |
| 299 'image': 'orange', # This probably catches gifs? |
| 300 'video': 'hotpink1', |
| 301 } |
| 302 |
| 303 _CONTENT_TYPE_TO_COLOR = { |
| 304 'html': 'red', |
| 305 'css': 'green', |
| 306 'script': 'blue', |
| 307 'javascript': 'blue', |
| 308 'json': 'purple', |
| 309 'gif': 'grey', |
| 310 'image': 'orange', |
| 311 'jpeg': 'orange', |
| 312 'png': 'orange', |
| 313 'plain': 'brown3', |
| 314 'octet-stream': 'brown3', |
| 315 'other': 'white', |
| 316 } |
296 | 317 |
297 # This resource type may induce a timing dependency. See _SplitChildrenByTime | 318 # This resource type may induce a timing dependency. See _SplitChildrenByTime |
298 # for details. | 319 # for details. |
299 # TODO(mattcary): are these right? | 320 # TODO(mattcary): are these right? |
300 _CAN_BE_TIMING_PARENT = set(['script', 'magic-debug-content']) | 321 _CAN_BE_TIMING_PARENT = set(['script', 'magic-debug-content']) |
301 _CAN_MAKE_TIMING_DEPENDENCE = set(['json', 'other', 'magic-debug-content']) | 322 _CAN_MAKE_TIMING_DEPENDENCE = set(['json', 'other', 'magic-debug-content']) |
302 | 323 |
303 class _NodeInfo(object): | 324 class _NodeInfo(object): |
304 """Our internal class that adds cost and other information to nodes. | 325 """Our internal class that adds cost and other information to nodes. |
305 | 326 |
(...skipping 10 matching lines...) Expand all Loading... |
316 Args: | 337 Args: |
317 node: The node to augment. | 338 node: The node to augment. |
318 request: The request associated with this node. | 339 request: The request associated with this node. |
319 """ | 340 """ |
320 self._request = request | 341 self._request = request |
321 self._node = node | 342 self._node = node |
322 self._edge_costs = {} | 343 self._edge_costs = {} |
323 self._edge_annotations = {} | 344 self._edge_annotations = {} |
324 # All fields in timing are millis relative to request_time, which is epoch | 345 # All fields in timing are millis relative to request_time, which is epoch |
325 # seconds. | 346 # seconds. |
326 self._node_cost = max([t for f, t in request.timing._asdict().iteritems() | 347 self._node_cost = max( |
327 if f != 'request_time']) | 348 [0] + [t for f, t in request.timing._asdict().iteritems() |
| 349 if f != 'request_time']) |
328 | 350 |
329 def __str__(self): | 351 def __str__(self): |
330 return self.ShortName() | 352 return self.ShortName() |
331 | 353 |
332 def Node(self): | 354 def Node(self): |
333 return self._node | 355 return self._node |
334 | 356 |
335 def Index(self): | 357 def Index(self): |
336 return self._node.Index() | 358 return self._node.Index() |
337 | 359 |
(...skipping 18 matching lines...) Expand all Loading... |
356 | 378 |
357 def ContentType(self): | 379 def ContentType(self): |
358 return self._request.GetContentType() | 380 return self._request.GetContentType() |
359 | 381 |
360 def ShortName(self): | 382 def ShortName(self): |
361 """Returns either the hostname of the resource, or the filename, | 383 """Returns either the hostname of the resource, or the filename, |
362 or the end of the path. Tries to include the domain as much as possible. | 384 or the end of the path. Tries to include the domain as much as possible. |
363 """ | 385 """ |
364 parsed = urlparse.urlparse(self._request.url) | 386 parsed = urlparse.urlparse(self._request.url) |
365 path = parsed.path | 387 path = parsed.path |
| 388 hostname = parsed.hostname if parsed.hostname else '?.?.?' |
366 if path != '' and path != '/': | 389 if path != '' and path != '/': |
367 last_path = parsed.path.split('/')[-1] | 390 last_path = parsed.path.split('/')[-1] |
368 if len(last_path) < 10: | 391 if len(last_path) < 10: |
369 if len(path) < 10: | 392 if len(path) < 10: |
370 return parsed.hostname + '/' + path | 393 return hostname + '/' + path |
371 else: | 394 else: |
372 return parsed.hostname + '/..' + parsed.path[-10:] | 395 return hostname + '/..' + parsed.path[-10:] |
373 elif len(last_path) > 10: | 396 elif len(last_path) > 10: |
374 return parsed.hostname + '/..' + last_path[:5] | 397 return hostname + '/..' + last_path[:5] |
375 else: | 398 else: |
376 return parsed.hostname + '/..' + last_path | 399 return hostname + '/..' + last_path |
377 else: | 400 else: |
378 return parsed.hostname | 401 return hostname |
379 | 402 |
380 def Url(self): | 403 def Url(self): |
381 return self._request.url | 404 return self._request.url |
382 | 405 |
383 def SetEdgeCost(self, child, cost): | 406 def SetEdgeCost(self, child, cost): |
384 assert child.Node() in self._node.Successors() | 407 assert child.Node() in self._node.Successors() |
385 self._edge_costs[child] = cost | 408 self._edge_costs[child] = cost |
386 | 409 |
387 def AddEdgeAnnotation(self, s, annotation): | 410 def AddEdgeAnnotation(self, s, annotation): |
388 assert s.Node() in self._node.Successors() | 411 assert s.Node() in self._node.Successors() |
(...skipping 67 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
456 next_index = len(self._nodes) | 479 next_index = len(self._nodes) |
457 assert request not in index_by_request | 480 assert request not in index_by_request |
458 index_by_request[request] = next_index | 481 index_by_request[request] = next_index |
459 node = dag.Node(next_index) | 482 node = dag.Node(next_index) |
460 node_info = self._NodeInfo(node, request) | 483 node_info = self._NodeInfo(node, request) |
461 self._nodes.append(node) | 484 self._nodes.append(node) |
462 self._node_info.append(node_info) | 485 self._node_info.append(node_info) |
463 | 486 |
464 dependencies = request_dependencies_lens.RequestDependencyLens( | 487 dependencies = request_dependencies_lens.RequestDependencyLens( |
465 trace).GetRequestDependencies() | 488 trace).GetRequestDependencies() |
466 for child_rq, parent_rq, reason in dependencies: | 489 for parent_rq, child_rq, reason in dependencies: |
467 parent = self._node_info[index_by_request[parent_rq]] | 490 parent = self._node_info[index_by_request[parent_rq]] |
468 child = self._node_info[index_by_request[child_rq]] | 491 child = self._node_info[index_by_request[child_rq]] |
469 edge_cost = child.StartTime() - parent.EndTime() | 492 edge_cost = child.StartTime() - parent.EndTime() |
470 if edge_cost < 0: | 493 if edge_cost < 0: |
471 edge_cost = 0 | 494 edge_cost = 0 |
472 if child.StartTime() < parent.StartTime(): | 495 if child.StartTime() < parent.StartTime(): |
473 logging.error('Inverted dependency: %s->%s', | 496 logging.error('Inverted dependency: %s->%s', |
474 parent.ShortName(), child.ShortName()) | 497 parent.ShortName(), child.ShortName()) |
475 # Note that child.StartTime() < parent.EndTime() appears to happen a | 498 # Note that child.StartTime() < parent.EndTime() appears to happen a |
476 # fair amount in practice. | 499 # fair amount in practice. |
(...skipping 65 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
542 break | 565 break |
543 if end_mark >= len(children_by_end_time): | 566 if end_mark >= len(children_by_end_time): |
544 break # It's not possible to rearrange any more children. | 567 break # It's not possible to rearrange any more children. |
545 if go_to_next_child: | 568 if go_to_next_child: |
546 continue # We can't rearrange this child, but the next child may be | 569 continue # We can't rearrange this child, but the next child may be |
547 # eligible. | 570 # eligible. |
548 if children_by_end_time[end_mark].EndTime() <= current.StartTime(): | 571 if children_by_end_time[end_mark].EndTime() <= current.StartTime(): |
549 current.ReparentTo(parent, children_by_end_time[end_mark]) | 572 current.ReparentTo(parent, children_by_end_time[end_mark]) |
550 children_by_end_time[end_mark].AddEdgeAnnotation(current, 'timing') | 573 children_by_end_time[end_mark].AddEdgeAnnotation(current, 'timing') |
551 | 574 |
| 575 def _ContentTypeToColor(self, content_type): |
| 576 if not content_type: |
| 577 type_str = 'other' |
| 578 elif '/' in content_type: |
| 579 kind, type_str = content_type.split('/') |
| 580 if kind in self._CONTENT_KIND_TO_COLOR: |
| 581 return self._CONTENT_KIND_TO_COLOR[kind] |
| 582 else: |
| 583 type_str = content_type |
| 584 return self._CONTENT_TYPE_TO_COLOR[type_str] |
| 585 |
552 def _GraphvizNode(self, index, highlight): | 586 def _GraphvizNode(self, index, highlight): |
553 """Returns a graphviz node description for a given node. | 587 """Returns a graphviz node description for a given node. |
554 | 588 |
555 Args: | 589 Args: |
556 index: index of the node. | 590 index: index of the node. |
557 highlight: a list of node items to emphasize. Any resource url which | 591 highlight: a list of node items to emphasize. Any resource url which |
558 contains any highlight text will be distinguished in the output. | 592 contains any highlight text will be distinguished in the output. |
559 | 593 |
560 Returns: | 594 Returns: |
561 A string describing the resource in graphviz format. | 595 A string describing the resource in graphviz format. |
562 The resource is color-coded according to its content type, and its shape | 596 The resource is color-coded according to its content type, and its shape |
563 is oval if its max-age is less than 300s (or if it's not cacheable). | 597 is oval if its max-age is less than 300s (or if it's not cacheable). |
564 """ | 598 """ |
565 node_info = self._node_info[index] | 599 node_info = self._node_info[index] |
566 color = self._CONTENT_TYPE_TO_COLOR[node_info.ContentType()] | 600 color = self._ContentTypeToColor(node_info.ContentType()) |
567 max_age = node_info.Request().MaxAge() | 601 max_age = node_info.Request().MaxAge() |
568 shape = 'polygon' if max_age > 300 else 'oval' | 602 shape = 'polygon' if max_age > 300 else 'oval' |
569 styles = ['filled'] | 603 styles = ['filled'] |
570 if highlight: | 604 if highlight: |
571 for fragment in highlight: | 605 for fragment in highlight: |
572 if fragment in node_info.Url(): | 606 if fragment in node_info.Url(): |
573 styles.append('dotted') | 607 styles.append('dotted') |
574 break | 608 break |
575 return ('%d [label = "%s\\n%.2f->%.2f (%.2f)"; style = "%s"; ' | 609 return ('%d [label = "%s\\n%.2f->%.2f (%.2f)"; style = "%s"; ' |
576 'fillcolor = %s; shape = %s];\n' | 610 'fillcolor = %s; shape = %s];\n' |
(...skipping 89 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
666 Dict of image url + short name to NodeInfo. | 700 Dict of image url + short name to NodeInfo. |
667 """ | 701 """ |
668 image_to_info = {} | 702 image_to_info = {} |
669 for n in self._node_info: | 703 for n in self._node_info: |
670 if (n.ContentType().startswith('image') and | 704 if (n.ContentType().startswith('image') and |
671 not self._IsAdUrl(n.Url())): | 705 not self._IsAdUrl(n.Url())): |
672 key = str((n.Url(), n.ShortName(), n.StartTime())) | 706 key = str((n.Url(), n.ShortName(), n.StartTime())) |
673 assert key not in image_to_info, n.Url() | 707 assert key not in image_to_info, n.Url() |
674 image_to_info[key] = n | 708 image_to_info[key] = n |
675 return image_to_info | 709 return image_to_info |
OLD | NEW |