def _show_graph(objs, edge_func, swap_source_target, max_depth=3, extra_ignore=(), filter=None, too_many=10, highlight=None, filename=None, extra_info=None, refcounts=False, shortnames=True, output=None, cull_func=None): if not _isinstance(objs, (list, tuple)): objs = [objs] is_interactive = False if filename and output: raise ValueError('Cannot specify both output and filename.') elif output: f = output elif filename and filename.endswith('.dot'): f = codecs.open(filename, 'w', encoding='utf-8') dot_filename = filename elif IS_INTERACTIVE: is_interactive = True f = StringIO() else: fd, dot_filename = tempfile.mkstemp(prefix='objgraph-', suffix='.dot', text=True) f = os.fdopen(fd, "w") if getattr(f, 'encoding', None): # Python 3 will wrap the file in the user's preferred encoding # Re-wrap it for utf-8 import io f = io.TextIOWrapper(f.detach(), 'utf-8') f.write('digraph ObjectGraph {\n' ' node[shape=box, style=filled, fillcolor=white];\n') queue = [] depth = {} ignore = set(extra_ignore) ignore.add(id(objs)) ignore.add(id(extra_ignore)) ignore.add(id(queue)) ignore.add(id(depth)) ignore.add(id(ignore)) ignore.add(id(sys._getframe())) # this function ignore.add(id(sys._getframe().f_locals)) ignore.add(id(sys._getframe(1))) # show_refs/show_backrefs ignore.add(id(sys._getframe(1).f_locals)) for obj in objs: f.write(' %s[fontcolor=red];\n' % (_obj_node_id(obj))) depth[id(obj)] = 0 queue.append(obj) del obj gc.collect() nodes = 0 while queue: nodes += 1 # The names "source" and "target" are reversed here because # originally there was just show_backrefs() and we were # traversing the reference graph backwards. target = queue.pop(0) tdepth = depth[id(target)] f.write(' %s[label="%s"];\n' % (_obj_node_id(target), _obj_label(target, extra_info, refcounts, shortnames))) h, s, v = _gradient((0, 0, 1), (0, 0, .3), tdepth, max_depth) if inspect.ismodule(target): h = .3 s = 1 if highlight and highlight(target): h = .6 s = .6 v = 0.5 + v * 0.5 f.write(' %s[fillcolor="%g,%g,%g"];\n' % (_obj_node_id(target), h, s, v)) if v < 0.5: f.write(' %s[fontcolor=white];\n' % (_obj_node_id(target))) if hasattr(getattr(target, '__class__', None), '__del__'): f.write(' %s->%s_has_a_del[color=red,style=dotted,' 'len=0.25,weight=10];\n' % (_obj_node_id(target), _obj_node_id(target))) f.write(' %s_has_a_del[label="__del__",shape=doublecircle,' 'height=0.25,color=red,fillcolor="0,.5,1",fontsize=6];\n' % (_obj_node_id(target))) if tdepth >= max_depth: continue if cull_func is not None and cull_func(target): continue neighbours = edge_func(target) ignore.add(id(neighbours)) n = 0 skipped = 0 for source in neighbours: if id(source) in ignore: continue if filter and not filter(source): continue if n >= too_many: skipped += 1 continue if swap_source_target: srcnode, tgtnode = target, source else: srcnode, tgtnode = source, target elabel = _edge_label(srcnode, tgtnode, shortnames) f.write(' %s -> %s%s;\n' % (_obj_node_id(srcnode), _obj_node_id(tgtnode), elabel)) if id(source) not in depth: depth[id(source)] = tdepth + 1 queue.append(source) n += 1 del source del neighbours if skipped > 0: h, s, v = _gradient((0, 1, 1), (0, 1, .3), tdepth + 1, max_depth) if swap_source_target: label = "%d more references" % skipped edge = "%s->too_many_%s" % (_obj_node_id(target), _obj_node_id(target)) else: label = "%d more backreferences" % skipped edge = "too_many_%s->%s" % (_obj_node_id(target), _obj_node_id(target)) f.write(' %s[color=red,style=dotted,len=0.25,weight=10];\n' % edge) f.write(' too_many_%s[label="%s",shape=box,height=0.25,' 'color=red,fillcolor="%g,%g,%g",fontsize=6];\n' % (_obj_node_id(target), label, h, s, v)) f.write(' too_many_%s[fontcolor=white];\n' % (_obj_node_id(target))) f.write("}\n") if output: return if is_interactive: return graphviz.Source(f.getvalue()) else: # The file should only be closed if this function was in charge of # opening the file. f.close() print("Graph written to %s (%d nodes)" % (dot_filename, nodes)) _present_graph(dot_filename, filename)