| # Copyright 2015 The Chromium Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| """Support for directed acyclic graphs. |
| |
| Used in the ResourceGraph model for chrome loading. |
| """ |
| |
| class Node(object): |
| """A node in a DAG. |
| |
| We do not enforce at a node level that a graph is a DAG. Methods like |
| TopologicalSort will assume a DAG and may fail if that's not the case. |
| |
| Nodes are identified with an index that must be unique for a particular graph |
| (it is used for hashing and equality). A graph is represented as a list of |
| nodes, for example in the TopologicalSort class method. By convention a node's |
| index is its position in this list, making it easy to store auxillary |
| information. |
| """ |
| def __init__(self, index): |
| """Create a new node. |
| |
| Args: |
| index: index of the node. We assume these indicies uniquely identify a |
| node (and so use it for hashing and equality). |
| """ |
| self._predecessors = set() |
| self._successors = set() |
| self._index = index |
| |
| def Predecessors(self): |
| return self._predecessors |
| |
| def Successors(self): |
| return self._successors |
| |
| def AddSuccessor(self, s): |
| """Add a successor. |
| |
| Updates appropriate links. Any existing parents of s are unchanged; to move |
| a node you must do a combination of RemoveSuccessor and AddSuccessor. |
| |
| Args: |
| s: the node to add as a successor. |
| """ |
| self._successors.add(s) |
| s._predecessors.add(self) |
| |
| def RemoveSuccessor(self, s): |
| """Removes a successor. |
| |
| Updates appropriate links. |
| |
| Args: |
| s: the node to remove as a successor. Will raise a set exception if s is |
| not an existing successor. |
| """ |
| self._successors.remove(s) |
| s._predecessors.remove(self) |
| |
| def SortedSuccessors(self): |
| children = [c for c in self.Successors()] |
| children.sort(key=lambda c: c.Index()) |
| return children |
| |
| def Index(self): |
| return self._index |
| |
| def __eq__(self, o): |
| return self.Index() == o.Index() |
| |
| def __hash__(self): |
| return hash(self.Index()) |
| |
| |
| def TopologicalSort(nodes, node_filter=None): |
| """Topological sort. |
| |
| We use a BFS-like walk which ensures that sibling are always grouped |
| together in the output. This is more convenient for some later analyses. |
| |
| Args: |
| nodes: [Node, ...] Nodes to sort. |
| node_filter: a filter Node->boolean to restrict the graph. A node passes the |
| filter on a return value of True. Only the subgraph reachable from a root |
| passing the filter is considered. |
| |
| Returns: |
| A list of Nodes in topological order. Note that node indicies are |
| unchanged; the original list nodes is not modified. |
| """ |
| if node_filter is None: |
| node_filter = lambda _: True |
| sorted_nodes = [] |
| sources = [] |
| remaining_in_edges = {} |
| for n in nodes: |
| if n.Predecessors(): |
| remaining_in_edges[n] = len(n.Predecessors()) |
| elif node_filter(n): |
| sources.append(n) |
| while sources: |
| n = sources.pop(0) |
| assert node_filter(n) |
| sorted_nodes.append(n) |
| # We sort by index to get consistent sorts across runs/machines. |
| for c in n.SortedSuccessors(): |
| assert remaining_in_edges[c] > 0 |
| if not node_filter(c): |
| continue |
| remaining_in_edges[c] -= 1 |
| if not remaining_in_edges[c]: |
| sources.append(c) |
| return sorted_nodes |