[Lldb-commits] [lldb] r298324 - [analyze-project-deps.py] Add the ability to list all cycles.

Zachary Turner via lldb-commits lldb-commits at lists.llvm.org
Mon Mar 20 16:54:26 PDT 2017


Author: zturner
Date: Mon Mar 20 18:54:26 2017
New Revision: 298324

URL: http://llvm.org/viewvc/llvm-project?rev=298324&view=rev
Log:
[analyze-project-deps.py] Add the ability to list all cycles.

This analyzes the dependency graph and computes all minimal
cycles.  Equivalent cycles that differ only by rotation are
excluded, as are cycles that are "super-cycles" of other
smaller cycles.  For example, if we discover the cycle
A -> C -> A, and then later A -> B -> C -> D -> A, this latter
cycle is not considered.  Thus, it is possible that after
eliminating some cycles, new ones will appear.  However,
this is the only way to make the algorithm terminate in
a reasonable amount of time.

Modified:
    lldb/trunk/scripts/analyze-project-deps.py

Modified: lldb/trunk/scripts/analyze-project-deps.py
URL: http://llvm.org/viewvc/llvm-project/lldb/trunk/scripts/analyze-project-deps.py?rev=298324&r1=298323&r2=298324&view=diff
==============================================================================
--- lldb/trunk/scripts/analyze-project-deps.py (original)
+++ lldb/trunk/scripts/analyze-project-deps.py Mon Mar 20 18:54:26 2017
@@ -8,6 +8,10 @@ parser = argparse.ArgumentParser(
     description='Analyze LLDB project #include dependencies.')
 parser.add_argument('--show-counts', default=False, action='store_true', 
     help='When true, show the number of dependencies from each subproject')
+parser.add_argument('--discover-cycles', default=False, action='store_true',
+    help='When true, find and display all project dependency cycles.  Note,'
+         'this option is very slow')
+
 args = parser.parse_args()
 
 src_dir = os.path.join(lldb_root, "source")
@@ -17,9 +21,17 @@ src_map = {}
 
 include_regex = re.compile('#include \"((lldb|Plugins|clang)(.*/)+).*\"')
 
+def is_sublist(small, big):
+    it = iter(big)
+    return all(c in it for c in small)
+
 def normalize_host(str):
     if str.startswith("lldb/Host"):
         return "lldb/Host"
+    if str.startswith("Plugins"):
+        return "lldb/" + str
+    if str.startswith("lldb/../../source"):
+        return str.replace("lldb/../../source", "lldb")
     return str
 
 def scan_deps(this_dir, file):
@@ -40,7 +52,7 @@ def scan_deps(this_dir, file):
             relative = normalize_host(relative)
             if relative in deps:
                 deps[relative] += 1
-            else:
+            elif relative != this_dir:
                 deps[relative] = 1
     if this_dir not in src_map and len(deps) > 0:
         src_map[this_dir] = deps
@@ -65,6 +77,57 @@ for (base, dirs, files) in os.walk(src_d
         scan_deps(norm_base_path, src_path)
     pass
 
+def is_existing_cycle(path, cycles):
+    # If we have a cycle like # A -> B -> C (with an implicit -> A at the end)
+    # then we don't just want to check for an occurrence of A -> B -> C in the
+    # list of known cycles, but every possible rotation of A -> B -> C.  For
+    # example, if we previously encountered B -> C -> A (with an implicit -> B
+    # at the end), then A -> B -> C is also a cycle.  This is an important
+    # optimization which reduces the search space by multiple orders of
+    # magnitude.
+    for i in xrange(0,len(path)):
+        if any(is_sublist(x, path) for x in cycles):
+            return True
+        path = [path[-1]] + path[0:-1]
+    return False
+
+def expand(path_queue, path_lengths, cycles, src_map):
+    # We do a breadth first search, to make sure we visit all paths in order
+    # of ascending length.  This is an important optimization to make sure that
+    # short cycles are discovered first, which will allow us to discard longer
+    # cycles which grow the search space exponentially the longer they get.
+    while len(path_queue) > 0:
+        cur_path = path_queue.pop(0)
+        if is_existing_cycle(cur_path, cycles):
+            continue
+
+        next_len = path_lengths.pop(0) + 1
+
+        last_component = cur_path[-1]
+
+        for item in src_map[last_component]:
+            if item.startswith("clang"):
+                continue
+
+            if item in cur_path:
+                # This is a cycle.  Minimize it and then check if the result is
+                # already in the list of cycles.  Insert it (or not) and then
+                # exit.
+                new_index = cur_path.index(item)
+                cycle = cur_path[new_index:]
+                if not is_existing_cycle(cycle, cycles):
+                    cycles.append(cycle)
+                continue
+
+            path_lengths.append(next_len)
+            path_queue.append(cur_path + [item])
+    pass
+
+cycles = []
+
+path_queue = [[x] for x in src_map.iterkeys()]
+path_lens = [1] * len(path_queue)
+
 items = list(src_map.iteritems())
 items.sort(lambda A, B : cmp(A[0], B[0]))
 
@@ -79,4 +142,17 @@ for (path, deps) in items:
         sorted_deps.sort(lambda A, B: cmp(A[0], B[0]))
         for dep in sorted_deps:
             print "\t{}".format(dep[0])
+
+if args.discover_cycles:
+    print "Analyzing cycles..."
+
+    expand(path_queue, path_lens, cycles, src_map)
+
+    average = sum([len(x)+1 for x in cycles]) / len(cycles)
+
+    print "Found {} cycles.  Average cycle length = {}.".format(len(cycles), average)
+    for cycle in cycles:
+        cycle.append(cycle[0])
+        print " -> ".join(cycle)
+
 pass
\ No newline at end of file




More information about the lldb-commits mailing list