1 from __future__
import print_function
2 from __future__
import absolute_import
12 import FWCore.ParameterSet.Config
as cms
13 import FWCore.ParameterSet.Modules
as mod
14 import FWCore.ParameterSet.Types
as typ
18 from FWCore.GuiBrowsers.FileExportPlugin
import FileExportPlugin
36 if self.data.process():
37 for tlo
in self.data.children(self.data.topLevelObjects()[0]):
38 children = self.data.children(tlo)
40 all_toplevel[tlo._label]=children
44 for tlo
in self.data.topLevelObjects():
45 if self.data.type(tlo)==
'Sequence':
46 if 'sequences' in all_toplevel:
47 all_toplevel[
'sequences']+=[tlo]
49 all_toplevel[
'sequences']=[tlo]
50 if self.data.type(tlo)==
'Path':
51 if 'paths' in all_toplevel:
52 all_toplevel[
'paths']+=[tlo]
54 all_toplevel[
'paths']=[tlo]
55 if self.data.type(tlo)
in (
'EDAnalyzer',
'EDFilter',
'EDProducer',
'OutputModule'):
56 self.
nodes[self.data.label(tlo)]={
'obj':tlo,
'n_label':self.
nodeLabel(tlo),
'n_shape':self.shapes.get(self.data.type(tlo),
'plaintext'),
'inpath':
True}
57 if self.
options[
'services']
and self.data.type(tlo)==
'Service':
58 self.
nodes[self.data.label(tlo)]={
'obj':tlo,
'n_label':self.
nodeLabel(tlo),
'n_shape':self.shapes.get(self.data.type(tlo),
'plaintext'),
'inpath':
False}
59 if self.
options[
'es']
and self.data.type(tlo)
in (
'ESSource',
'ESProducer'):
60 self.
nodes[self.data.label(tlo)]={
'obj':tlo,
'n_label':self.
nodeLabel(tlo),
'n_shape':self.shapes.get(self.data.type(tlo),
'plaintext'),
'inpath':
False}
64 children = self.data.children(obj)
66 seqlabel = self.data.label(obj)
68 seqlabel +=
'\\n%s:%s' % (self.data.pypackage(obj),self.data.lineNumber(obj))
69 result=
'subgraph clusterSeq%s {\nlabel="Sequence %s"\ncolor="%s"\nfontcolor="%s"\nfontname="%s"\nfontsize=%s\n' % (self.data.label(obj),seqlabel,self.
options[
'color_sequence'],self.
options[
'color_sequence'],self.
options[
'font_name'],self.
options[
'font_size'])
75 self.
nodes[self.data.label(obj)]={
'obj':obj,
'n_label':self.
nodeLabel(obj),
'n_shape':self.shapes.get(self.data.type(obj),
'plaintext'),
'inpath':
True}
76 return '%s\n'%self.data.label(obj)
80 children=self.data.children(obj)
90 result = self.data.label(obj)
92 result +=
'\\n%s'%self.data.classname(obj)
94 result +=
'\\n%s:%s'%(self.data.pypackage(obj),self.data.lineNumber(obj))
99 classname = self.data.classname(obj)
100 pypath = self.data.pypath(obj)
101 pyline = self.data.lineNumber(obj)
107 pathlabel = self.data.label(path)
109 pathlabel +=
'\\n%s:%s'%(self.data.pypackage(path),self.data.lineNumber(path))
111 pathresult =
'subgraph cluster%s {\nlabel="%s"\ncolor="%s"\nfontcolor="%s"\nfontname="%s"\nfontsize=%s\n' % (self.data.label(path),pathlabel,self.
options[
'color_endpath'],self.
options[
'color_endpath'],self.
options[
'font_name'],self.
options[
'font_size'])
113 pathresult =
'subgraph cluster%s {\nlabel="%s"\ncolor="%s"\nfontcolor="%s"\nfontname="%s"\nfontsize=%s\n' % (self.data.label(path),pathlabel,self.
options[
'color_path'],self.
options[
'color_path'],self.
options[
'font_name'],self.
options[
'font_size'])
116 self.endstarts.append(
'endstart_%s'%self.data.label(path))
117 self.
nodes[
'endstart_%s'%self.data.label(path)]={
'obj':path,
'n_label':
'Start %s'%self.data.label(path),
'n_color':
'grey',
'n_shape':
'plaintext',
'inpath':
False}
119 self.pathstarts.append(
'start_%s'%self.data.label(path))
120 self.pathends.append(
'end_%s'%self.data.label(path))
121 self.
nodes[
'start_%s'%self.data.label(path)]={
'obj':path,
'n_label':
'Start %s'%self.data.label(path),
'n_color':
'grey',
'n_shape':
'plaintext',
'inpath':
False}
122 self.
nodes[
'end_%s'%self.data.label(path)]={
'obj':path,
'n_label':
'End %s'%self.data.label(path),
'n_color':
'grey',
'n_shape':
'plaintext',
'inpath':
False}
127 self.
nodes[self.data.label(c)]={
'obj':c,
'n_label':self.
nodeLabel(c),
'n_shape':self.shapes.get(self.data.type(c),
'plaintext'),
'inpath':
True}
128 labels.append(self.data.label(c))
130 pathresult +=
'->'.
join(labels)+
'\n' 133 pathresult +=
'\n'.
join(labels)+
'\n' 135 if self.data.children(path):
136 for path_child
in self.data.children(path):
139 if len(labels)>0
and self.
options[
'seqconnect']:
141 pathresult +=
'endstart_%s->%s\n' % (self.data.label(path),labels[0])
143 pathresult +=
'start_%s->%s\n%s->end_%s\n' % (self.data.label(path),labels[0],labels[-1],self.data.label(path))
154 for path
in self.
toplevel[
'endpaths']:
157 for seq
in self.
toplevel[
'sequences']:
165 result+=
"%s->%s\n" % (p,p2)
172 allobjects = [self.
nodes[n][
'obj']
for n
in self.
nodes if self.
nodes[n][
'inpath']]
173 self.data.readConnections(allobjects)
174 connections = self.data.connections()
175 for objects,names
in connections.items():
177 result +=
'%s->%s[label="%s",color="%s",fontcolor="%s",fontsize=%s,fontname="%s"]\n' % (objects[0],objects[1],names[1],self.
options[
'color_inputtag'],self.
options[
'color_inputtag'],self.
options[
'font_size'],self.
options[
'font_name'])
179 result +=
'%s->%s[color="%s"]\n' % (objects[0],objects[1],self.
options[
'color_inputtag'])
190 self.
nodes[
'source']={
'obj':s,
'n_label':self.data.classname(s),
'n_shape':self.
shapes[
'Source']}
193 result +=
'source->%s\n' % (p)
203 for e
in self.
toplevel[
'essources']:
204 servicenodes.append(self.data.label(e))
205 self.
nodes[self.data.label(e)]={
'obj':e,
'n_label':self.
nodeLabel(e),
'n_shape':self.
shapes[
'ESSource'],
'inpath':
False}
207 for e
in self.
toplevel[
'esproducers']:
208 servicenodes.append(self.data.label(e))
209 self.
nodes[self.data.label(e)]={
'obj':e,
'n_label':self.
nodeLabel(e),
'n_shape':self.
shapes[
'ESProducer'],
'inpath':
False}
213 self.servicenodes.append(self.data.label(s))
214 self.
nodes[self.data.label(s)]={
'obj':s,
'n_label':self.
nodeLabel(e),
'n_shape':self.
shapes[
'Service'],
'inpath':
False}
217 maxendpath=
max([len(
recurseChildren(path)
for path
in self.toplevel.get(
'endpaths',(0,)))])
221 for i,s
in enumerate(servicenodes[:-1]):
222 if not i%(maxpath+maxendpath)==(maxpath+maxendpath)-1:
223 result+=
'%s->%s[style=invis]\n' % (s,servicenodes[i+1])
233 result +=
"%s[%s]\n" % (n,
','.
join([
'%s="%s"' % (k[2:],v)
for k,v
in self.
nodes[n].
items()
if k[0:2]==
'n_']))
238 Return a legend subgraph using current shape and colour preferences. 240 return 'subgraph clusterLegend {\nlabel="legend"\ncolor=red\nSource->Producer->Filter->Analyzer\nService->ESSource[style=invis]\nESSource->ESProducer[style=invis]\nProducer->Filter[color="%s",label="InputTag",fontcolor="%s"]\nProducer[shape=%s]\nFilter[shape=%s]\nAnalyzer[shape=%s]\nESSource[shape=%s]\nESProducer[shape=%s]\nSource[shape=%s]\nService[shape=%s]\nsubgraph clusterLegendSequence {\nlabel="Sequence"\ncolor="%s"\nfontcolor="%s"\nProducer\nFilter\n}\n}\n' % (self.
options[
'color_inputtag'],self.
options[
'color_inputtag'],self.
shapes[
'EDProducer'],self.
shapes[
'EDFilter'],self.
shapes[
'EDAnalyzer'],self.
shapes[
'ESSource'],self.
shapes[
'ESProducer'],self.
shapes[
'Source'],self.
shapes[
'Service'],self.
options[
'color_sequence'],self.
options[
'color_sequence'])
256 if self.data.process():
257 return 'digraph configbrowse {\nsubgraph clusterProcess {\nlabel="%s\\n%s"\nfontsize=%s\nfontname="%s"\n%s\n}\n}\n' % (self.data.process().name_(),self.data._filename,self.
options[
'font_size'],self.
options[
'font_name'],
'\n'.
join(blocks))
259 return 'digraph configbrowse {\nsubgraph clusterCFF {\nlabel="%s"\nfontsize=%s\nfontname="%s"\n%s\n}\n}\n' % (self.data._filename,self.
options[
'font_size'],self.
options[
'font_name'],
'\n'.
join(blocks))
265 Export a CMSSW config file to DOT (http://www.graphviz.org) markup, either as raw markup or by invoking the dot program, as an image. 268 'legend':(
'Show Legend',
'boolean',
True),
269 'source':(
'Show Source',
'boolean',
True),
270 'es':(
'Show Event Setup',
'boolean',
False),
271 'tagconnect':(
'Connect InputTags',
'boolean',
True),
272 'seqconnect':(
'Connect Module Sequence',
'boolean',
False),
273 'services':(
'Show Services',
'boolean',
False),
274 'endpath':(
'Show EndPaths',
'boolean',
True),
275 'seq':(
'Group Sequences',
'boolean',
True),
276 'class':(
'Show Class',
'boolean',
True),
277 'file':(
'Show File',
'boolean',
True),
278 'schedule':(
'Show Schedule',
'boolean',
False),
279 'taglabel':(
'Show Tag Labels',
'boolean',
True),
280 'color_path':(
'Path Color',
'color',
'#ff00ff'),
281 'color_endpath':(
'EndPath Color',
'color',
'#ff0000'),
282 'color_sequence':(
'Sequence Color',
'color',
'#00ff00'),
283 'color_inputtag':(
'InputTag Color',
'color',
'#0000ff'),
284 'color_schedule':(
'Schedule Color',
'color',
'#00ffff'),
285 'url':(
'Include URLs',
'boolean',
False),
286 'urlprocess':(
'Postprocess URL (for client-side imagemaps)',
'boolean',
False),
287 'urlbase':(
'URL to generate',
'string',
"{'split_x':1,'split_y':2,'scale_x':1.,'scale_y':1.,'cells':[{'top':0,'left':0,'width':1,'height':1,'html_href':'http://cmslxr.fnal.gov/lxr/ident/?i=$classname','html_alt':'LXR','html_class':'LXR'},{'top':1,'left':0,'width':1,'height':1,'html_href':'http://cmssw.cvs.cern.ch/cgi-bin/cmssw.cgi/CMSSW/$pypath?view=markup#$pyline','html_alt':'CVS','html_class':'CVS'}]}"),
288 'node_graphs':(
'Produce individual graphs focussing on each node',
'boolean',
False),
289 'node_graphs_restrict':(
'Select which nodes to make node graphs for',
'string',
''),
290 'node_depth':(
'Search depth for individual node graphs',
'int',1),
291 'font_name':(
'Font name',
'string',
'Times-Roman'),
292 'font_size':(
'Font size',
'int',8),
293 'png_max_size':(
'Maximum edge for png image',
'int',16768)
295 plugin_name=
'DOT Export' 296 file_types=(
'bmp',
'dot',
'eps',
'gif',
'jpg',
'pdf',
'png',
'ps',
'svg',
'tif',
'png+map',
'stdout')
298 FileExportPlugin.__init__(self)
302 self.
shapes[
'EDProducer']=
'box' 303 self.
shapes[
'EDFilter']=
'invhouse' 304 self.
shapes[
'EDAnalyzer']=
'house' 305 self.
shapes[
'OutputModule']=
'invtrapezium' 306 self.
shapes[
'ESSource']=
'Mdiamond' 307 self.
shapes[
'ESProducer']=
'Msquare' 308 self.
shapes[
'Source']=
'ellipse' 309 self.
shapes[
'Service']=
'diamond' 313 Simple indenter for dot output, mainly to prettify it for human reading. 315 spaces =
lambda d:
''.
join([space]*d)
319 for line
in dot.splitlines():
321 newdot += spaces(depth)+line+
'\n' 325 newdot += spaces(depth)+line+
'\n' 327 newdot += spaces(depth)+line+
'\n' 336 re_link = re.compile(
r'^\s*?(\w*?)->(\w*?)(?:\[.*?\])?$',re.MULTILINE)
337 re_nodedef = re.compile(
r'^\s*?(\w*?)(?:\[.*?\])?$',re.MULTILINE)
338 re_title = re.compile(
r'^label=\"(.*?)\"$',re.MULTILINE)
339 re_nodeprops = re.compile(
r'^\s*?('+node+
r')\[(.*?)\]$',re.MULTILINE)
341 nodes = re_nodedef.findall(dotdata)
342 if not node
in nodes:
343 raise Exception(
"Selected node (%s) not found" % (node))
344 links_l = re_link.findall(dotdata)
349 links[link[0]] += [link[1]]
351 links[link[0]] = [link[1]]
353 links[link[1]] += [link[0]]
355 links[link[1]] = [link[0]]
357 def node_recursor(links,depthleft,start):
360 return links[start]+[start]
363 for l
in links[start]:
364 result.extend(node_recursor(links,depthleft-1,l))
370 include_nodes = set(node_recursor(links,depth-1,node))
371 include_nodes.add(node)
376 def __call__(self,match):
378 return match.group(0)
383 def __call__(self,match):
385 return match.group(0)
388 dotdata = re_link.sub(link_replacer(include_nodes),dotdata)
389 dotdata = re_nodedef.sub(node_replacer(include_nodes),dotdata)
390 dotdata = re_title.sub(
r'label="\g<1>\\nDepth '+
str(depth_s)+
r' from node ' +node+
r'"',dotdata,1)
391 dotdata = re_nodeprops.sub(
'\\g<1>[\\g<2>,color="red"]',dotdata,1)
397 Re-process the client-side image-map produces when png+map is selected. 398 DOT will only ever put a single URL in the imagemap corresponding to a node, with the 'url' parameter (after html encoding) as the url, and the 'title' parameter as the title. This isn't useful behaviour for our purposes. We want probably several link areas, or a javascript link to make a menu appear, or other more complex behaviour. 400 If the option 'urlprocess' is turned on, this function is called, and it expects to find a dictionary it can eval in the url parameter. I can't think of a less messy way of passing data to this function without having inner access to DOT at the moment. 402 This function iterates through all the areas in the mapfile, replacing each one with one or more areas according to the rules in the dictionary stored in the URL parameter. 404 The dictionary should have structure: 416 html_attribute1:"...", 417 html_attribute2:"..." 420 The imagemap is first scaled in size by scale_x and scale_y. 421 It is then split into split_x*split_y rectangular cells. 422 New areas are created for each defined cell with the defined top,left location and width,height. This will not check you aren't making new areas that overlap if you define them as such. 423 The areas then get attributes defined by html_attribute fields - eg, 'html_href':'mypage.htm' becomes 'href'='mypage.htm' in the area. Probably you want as a minimum to define html_href and html_alt. It would also be useful to set html_class to allow highlighting of different link types, or html_onclick/onmouseover for more exotic behaviour. 425 This will probably be quite sensitive to the presence of special characters, complex splitting schemes, etc. Use with caution. 427 This may be somewhat replaceable with the <html_label> and cut-down table format that graphviz provides, but I haven't had much of an experiment with that. 430 area_default = {
'split_x':1,
'scale_x':1.,
'split_y':1,
'scale_y':1.,
'cells':[]}
431 cell_default = {
'top':0,
'left':0,
'width':1,
'height':1,
'html_href':
'#'}
432 re_area = re.compile(
'<area.*?/>',re.DOTALL)
434 re_content = re.compile(
'href="(.*?)" title=".*?" alt="" coords="(-?[0-9]{1,6}),(-?[0-9]{1,6}),(-?[0-9]{1,6}),(-?[0-9]{1,6})"',re.DOTALL)
435 re_htmlunquote = re.compile(
'&#([0-9]{1,3});')
436 mapdata = re_htmlunquote.sub(
lambda x: chr(
int(x.group(1))),mapdata)
437 areas = re_area.findall(mapdata)
440 data = re_content.search(area)
441 baseurl = data.group(1)
442 x1,y1,x2,y2 =
map(int,(data.group(2),data.group(3),data.group(4),data.group(5)))
443 rad_x,rad_y =
int((x2-x1)*0.5),
int((y2-y1)*0.5)
444 centre_x,centre_y = x1+rad_x,y1+rad_y
445 basedict = eval(baseurl)
446 for ad
in area_default:
447 if not ad
in basedict:
448 basedict[ad]=area_default[ad]
449 rad_x =
int(rad_x*basedict[
'scale_x'])
450 rad_y =
int(rad_y*basedict[
'scale_y'])
451 top_x,top_y = centre_x-rad_x,centre_y-rad_y
452 split_x,split_y =
int((2*rad_x)/basedict[
'split_x']),
int((2*rad_y)/basedict[
'split_y'])
454 for cell
in basedict[
'cells']:
455 for cd
in cell_default:
457 cell[cd]=cell_default[cd]
458 x1,y1 = top_x+split_x*cell[
'left'],top_y+split_y*cell[
'top']
459 x2,y2 = x1+split_x*cell[
'width'],y1+split_y*cell[
'height']
460 area_html =
'<area shape="rect" coords="%s,%s,%s,%s" %s />' % (x1,y1,x2,y2,
' '.
join([
'%s="%s"'%(key.split(
'_',1)[1],value)
for key, value
in cell.items()
if key.startswith(
'html_')]))
461 new_areas.append(area_html)
462 return '<map id="configbrowse" name="configbrowse">\n%s\n</map>'%(
'\n'.
join(new_areas))
473 if len(dot_producer.nodes)>0:
475 if self.options[
'node_graphs']:
476 nodes = [n
for n
in dot_producer.nodes
if data.type(dot_producer.nodes[n][
'obj'])
in (
'EDAnalyzer',
'EDFilter',
'EDProducer',
'OutputModule')]
478 if self.options[
'node_graphs_restrict']
in n:
480 node_dot = self.
selectNode(dot,n,self.options[
'node_depth'])
481 self.
write_output(node_dot,filename.replace(
'.',
'_%s.'%n),filetype)
488 print(
"WARNING: Empty image. Not saved.")
492 png_header =
'\x89PNG\x0d\x0a\x1a\x0a' 494 filedata = open(filename,
'r').read(24) 495 png_data = struct.unpack('>8s4s4sII',filedata)
496 if not (png_data[0]==png_header
and png_data[2]==ihdr):
497 raise 'PNG header or IHDR not found' 498 return png_data[3],png_data[4]
503 dotfile = open(filename,
'w')
506 elif filetype==
'stdout':
508 elif filetype==
'pdf':
509 dot_p = subprocess.Popen([
'dot',
'-Tps2'],stdin=subprocess.PIPE,stdout=subprocess.PIPE)
510 ps2 = dot_p.communicate(dot)[0]
511 if not dot_p.returncode==0:
512 raise "dot returned non-zero exit code: %s"%dot_p.returncode
513 pdf_p = subprocess.Popen([
'ps2pdf',
'-',filename],stdin=subprocess.PIPE)
514 pdf_p.communicate(ps2)
515 if not pdf_p.returncode==0:
516 raise "ps2pdf returned non-zero exit code: %s"%pdf_p.returncode
517 elif filetype==
'png+map':
519 filename = filename.split(
'.')[0]
520 dot_p = subprocess.Popen([
'dot',
'-Tpng',
'-o',
'%s.png'%filename,
'-Tcmapx_np'],stdin=subprocess.PIPE,stdout=subprocess.PIPE)
521 mapdata = dot_p.communicate(dot)[0]
522 if not dot_p.returncode==0:
523 raise "dot returned non-zero exit code: %s"%dot_p.returncode
524 if self.options[
'urlprocess']:
526 mapfile = open(
'%s.map'%filename,
'w')
527 mapfile.write(mapdata)
530 if max(filesize) > self.options[
'png_max_size']:
531 print(
"png image is too large (%s pixels/%s max pixels), deleting" % (filesize,self.options[
'png_max_size']))
532 os.remove(
'%s.png'%filename)
533 os.remove(
'%s.map'%filename)
534 elif filetype==
'png':
535 dot_p = subprocess.Popen([
'dot',
'-T%s'%(filetype),
'-o',filename],stdin=subprocess.PIPE)
536 dot_p.communicate(dot)
537 if not dot_p.returncode==0:
538 raise "dot returned non-zero exit code: %s"%dot_p.returncode
540 if max(filesize) > self.options[
'png_max_size']:
541 print(
"png image is too large (%s pixels/%s max pixels), deleting" % (filesize,self.options[
'png_max_size']))
544 dot_p = subprocess.Popen([
'dot',
'-T%s'%(filetype),
'-o',filename],stdin=subprocess.PIPE)
545 dot_p.communicate(dot)
546 if not dot_p.returncode==0:
547 raise "dot returned non-zero exit code: %s"%dot_p.returncode
def makePath(self, path, endpath=False)
def replace(string, replacements)
def write_output(self, dot, filename, filetype)
S & print(S &os, JobReport::InputFile const &f)
def recurseChildren(self, obj)
def seqRecurseChildren(self, obj)
Abs< T >::type abs(const T &t)
def processMap(self, mapdata)
def selectNode(self, dotdata, node, depth_s)
def export(self, data, filename, filetype)
def dotIndenter(self, dot)
def produceServices(self)
static std::string join(char **cmd)
def get_png_size(self, filename)
def __init__(self, data, options, shapes)