$search
00001 import roslib.names 00002 import rosgraph.impl.graph 00003 import collections 00004 00005 ORIENTATIONS = ['LR', 'TB', 'RL', 'BT'] 00006 00007 INIT_DOTCODE = """ 00008 digraph G { initializing [label="initializing..."]; } 00009 """ 00010 00011 # node/node connectivity 00012 NODE_NODE_GRAPH = "node_node" 00013 # node/topic connections where an actual network connection exists 00014 NODE_TOPIC_GRAPH = "node_topic" 00015 # all node/topic connections, even if no actual network connection 00016 NODE_TOPIC_ALL_GRAPH = "node_topic_all" 00017 00018 import urllib 00019 def safe_dotcode_name(name): 00020 """ 00021 encode the name for dotcode symbol-safe syntax 00022 """ 00023 # not the best solution, but unsafe names shouldn't be coming through much 00024 ret = urllib.quote(name) 00025 ret = ret.replace('/', '_') 00026 ret = ret.replace('%', '_') 00027 ret = ret.replace('-', '_') 00028 return ret 00029 00030 #TODO: Make param 00031 QUIET_NAMES = ['/diag_agg', '/runtime_logger', '/pr2_dashboard', '/rviz', '/rosout', '/cpu_monitor', '/monitor','/hd_monitor', '/rxloggerlevel', '/clock'] 00032 00033 class Graph: 00034 def __init__(self, orientation, quiet): 00035 self.nodes = {} 00036 self.edges = [] 00037 self.orientation = orientation 00038 self.quiet = quiet 00039 self.parents = {} 00040 self.children = collections.defaultdict(list) 00041 self.group_topics = {} 00042 00043 def add_node(self, o): 00044 self.nodes[ o.name ] = o 00045 00046 def add_edge(self, e): 00047 self.edges.append(e) 00048 self.nodes[ e.start ].add_edge( e ) 00049 00050 def set_hierarchy(self, config, parent=None): 00051 for group, value in config.iteritems(): 00052 if parent is not None: 00053 self.parents[group] = parent 00054 self.children[parent].append(group) 00055 if type(value)==type([]): 00056 for v in value: 00057 if v in self.nodes: 00058 self.parents[v] = group 00059 self.children[group].append(v) 00060 else: 00061 self.group_topics[group] = v 00062 else: 00063 if 'topics' in value: 00064 for v in value['topics']: 00065 self.group_topics[group] = v 00066 del value['topics'] 00067 00068 self.set_hierarchy(value, group) 00069 00070 def dot_code(self): 00071 self.clean() 00072 00073 if len(self.nodes)==0: 00074 nodes_str = ' empty;' 00075 else: 00076 nodes_str = self.node_dot_code() 00077 00078 edges_str = '\n'.join([e.dot_code(self) for e in self.edges if self.is_valid_edge(e) ]) 00079 00080 s = "digraph G {\n rankdir=%s;\ncompound=true; \n"%self.orientation 00081 s += "%(nodes_str)s\n%(edges_str)s}\n"%vars() 00082 return s 00083 00084 def is_valid_node(self, node): 00085 if self.quiet and node.name in QUIET_NAMES: 00086 return False 00087 if node.is_msg and len(node.edges)==0: 00088 return False 00089 return True 00090 00091 def is_valid_edge(self, edge): 00092 if not (edge.start in self.nodes and edge.end in self.nodes): 00093 return False 00094 start = self.nodes[edge.start] 00095 end = self.nodes[edge.end] 00096 if not (self.is_valid_node(start) and self.is_valid_node(end)): 00097 return False 00098 if self.same_parents(edge): 00099 return False 00100 return True 00101 00102 def same_parents(self, edge): 00103 return edge.start in self.parents and edge.end in self.parents and self.parents[edge.start]==self.parents[edge.end] 00104 00105 def clean(self): 00106 changes = True 00107 while changes: 00108 changes = False 00109 v_edges = [] 00110 00111 for e in self.edges: 00112 if self.is_valid_edge(e): 00113 v_edges.append(e) 00114 else: 00115 self.nodes[ e.start ].remove_edge(e) 00116 changes = True 00117 self.edges = v_edges 00118 00119 v_nodes = {} 00120 nnum = len(self.nodes) 00121 for edge in self.edges: 00122 for name in [edge.start, edge.end]: 00123 node = self.nodes[name] 00124 if name not in v_nodes and self.is_valid_node(node): 00125 v_nodes[name] = node 00126 self.nodes = v_nodes 00127 if len(self.nodes) != nnum: 00128 changes = True 00129 00130 00131 def node_dot_code(self, parent=None, depth=1): 00132 s = '' 00133 std = ' '*depth 00134 tab = ' '*(depth+1) 00135 00136 if parent is not None: 00137 color = 'grey%d'%(100-(depth-1)*10) 00138 s += std + 'subgraph %s {\n' % safe_dotcode_name('cluster' + parent) 00139 s += tab + 'label = "%s";\n'%parent 00140 s += tab + 'style=filled;\n' 00141 s += tab + 'fillcolor=%s;\n'%color 00142 00143 for child in self.children[parent]: 00144 if child in self.nodes: 00145 if self.is_valid_node(self.nodes[child]): 00146 s += self.nodes[child].dot_code() 00147 else: 00148 s += self.node_dot_code(child, depth+1) 00149 00150 if parent is not None: 00151 s += std + '}\n' 00152 else: 00153 for name, node in self.nodes.iteritems(): 00154 if name not in self.parents: 00155 if self.is_valid_node(node): 00156 s += node.dot_code() 00157 return s 00158 00159 def get_proper_group(self, topic, node): 00160 if node in self.group_topics and self.group_topics[node]==topic: 00161 return node 00162 elif node not in self.parents or self.parents[node] is None: 00163 return None 00164 else: 00165 return self.get_proper_group(topic, self.parents[node]) 00166 00167 class Node: 00168 def __init__(self, name, is_msg): 00169 self.name = name 00170 self.badness = 0 00171 self.is_msg = is_msg 00172 self.group = None 00173 self.edges = {} 00174 00175 def get_id(self): 00176 if self.is_msg: 00177 return safe_dotcode_name(self.name) + '_msg' 00178 else: 00179 return safe_dotcode_name(self.name) 00180 00181 def add_edge(self, e): 00182 self.edges[e.end] = e 00183 00184 def remove_edge(self, e): 00185 if e.end in self.edges: 00186 del self.edges[e.end] 00187 00188 def dot_code(self): 00189 s = ' %s '%self.get_id() 00190 m = {} 00191 m['label'] = self.name 00192 00193 if self.is_msg: 00194 m['URL'] = 'topic:%s'%self.name 00195 else: 00196 m['URL'] = 'node:%s'%self.name 00197 00198 if self.is_msg: 00199 m['shape'] = 'box' 00200 m['style'] = 'filled' 00201 m['fillcolor'] = 'white' 00202 elif self.badness==2: 00203 m['color'] = 'red' 00204 m['shape'] = 'doublecircle' 00205 elif self.badness==1: 00206 m['color'] = 'orange' 00207 m['shape'] = 'doublecircle' 00208 else: 00209 m['style'] = 'filled' 00210 m['fillcolor'] = 'white' 00211 00212 atts = ', '.join(['%s="%s"'%p for p in m.items()]) 00213 return '%s[%s];'%(s,atts) 00214 00215 def __repr__(self): 00216 return self.name 00217 00218 class Edge: 00219 def __init__(self, e): 00220 self.start = e.start.strip() 00221 self.end = e.end.strip() 00222 self.label = e.label.strip() 00223 00224 def dot_code(self, graph): 00225 start = graph.nodes[self.start] 00226 end = graph.nodes[self.end] 00227 s = ' %s->%s'%(start.get_id(), end.get_id()) 00228 00229 sub = [] 00230 if self.label: 00231 sub.append('label="%s"'%self.label) 00232 00233 tail = graph.get_proper_group(self.label, self.start) 00234 head = graph.get_proper_group(self.label, self.end) 00235 if head: 00236 sub.append('lhead="cluster%s"'%head) 00237 if tail: 00238 sub.append('ltail="cluster%s"'%tail) 00239 if len(sub)>0: 00240 s += ' [%s]'% ','.join(sub) 00241 return s 00242 00243 def __repr__(self): 00244 return self.start + '->' + self.end 00245 00246 00247 00248 def generate_dotcode(g, ns_filter, graph_mode, orientation, config, quiet=False): 00249 """ 00250 @param g: Graph instance 00251 @param ns_filter: namespace filter (must be canonicalized with trailing '/') 00252 @type ns_filter: string 00253 @param graph_mode str: NODE_NODE_GRAPH | NODE_TOPIC_GRAPH | NODE_TOPIC_ALL_GRAPH 00254 @type graph_mode: str 00255 @param orientation: rankdir value (see ORIENTATIONS dict) 00256 @return: dotcode generated from graph singleton 00257 @rtype: str 00258 """ 00259 graph = Graph(orientation, quiet) 00260 for node_name in g.nn_nodes: 00261 n = Node(node_name, False) 00262 graph.add_node(n) 00263 00264 if graph_mode == NODE_TOPIC_GRAPH or \ 00265 graph_mode == NODE_TOPIC_ALL_GRAPH: 00266 for node_name in g.nt_nodes: 00267 n = Node( node_name.strip(), True ) 00268 graph.add_node(n) 00269 00270 if graph_mode == NODE_NODE_GRAPH: 00271 edges = g.nn_edges 00272 elif graph_mode == NODE_TOPIC_GRAPH: 00273 edges = g.nt_edges 00274 else: 00275 edges = g.nt_all_edges 00276 00277 for edge in edges: 00278 graph.add_edge( Edge(edge) ) 00279 00280 for node_name, node in g.bad_nodes.iteritems(): 00281 if node.type == rosgraph.impl.graph.BadNode.DEAD: 00282 graph.nodes[node_name].badness = 2 00283 else: 00284 graph.nodes[node_name].badness = 1 00285 00286 graph.set_hierarchy(config) 00287 00288 s = graph.dot_code() 00289 #print s 00290 return s 00291 00292 def generate_namespaces(g, graph_mode, quiet=False): 00293 """ 00294 Determine the namespaces of the nodes being displayed 00295 """ 00296 namespaces = [] 00297 if graph_mode == NODE_NODE_GRAPH: 00298 nodes = g.nn_nodes 00299 #if quiet: 00300 # nodes = [n for n in nodes if not n in QUIET_NAMES] 00301 namespaces = list(set([roslib.names.namespace(n) for n in nodes])) 00302 00303 elif graph_mode == NODE_TOPIC_GRAPH or \ 00304 graph_mode == NODE_TOPIC_ALL_GRAPH: 00305 nn_nodes = g.nn_nodes 00306 nt_nodes = g.nt_nodes 00307 #if quiet: 00308 # nn_nodes = [n for n in nn_nodes if not n in QUIET_NAMES] 00309 # nt_nodes = [n for n in nt_nodes if not n in QUIET_NAMES] 00310 if nn_nodes or nt_nodes: 00311 namespaces = [roslib.names.namespace(n) for n in nn_nodes] 00312 # an annoyance with the rosgraph library is that it 00313 # prepends a space to topic names as they have to have 00314 # different graph node namees from nodes. we have to strip here 00315 namespaces.extend([roslib.names.namespace(n[1:]) for n in nt_nodes]) 00316 00317 return list(set(namespaces)) 00318