1 from __future__
import print_function
11 import FWCore.ParameterSet.Config
as cms
12 import FWCore.ParameterSet.Modules
as mod
13 import FWCore.ParameterSet.Types
as typ
17 from FWCore.GuiBrowsers.FileExportPlugin
import FileExportPlugin
35 if self.data.process():
36 for tlo
in self.data.children(self.data.topLevelObjects()[0]):
37 children = self.data.children(tlo)
39 all_toplevel[tlo._label]=children
43 for tlo
in self.data.topLevelObjects():
44 if self.data.type(tlo)==
'Sequence':
45 if 'sequences' in all_toplevel:
46 all_toplevel[
'sequences']+=[tlo]
48 all_toplevel[
'sequences']=[tlo]
49 if self.data.type(tlo)==
'Path':
50 if 'paths' in all_toplevel:
51 all_toplevel[
'paths']+=[tlo]
53 all_toplevel[
'paths']=[tlo]
54 if self.data.type(tlo)
in (
'EDAnalyzer',
'EDFilter',
'EDProducer',
'OutputModule'):
55 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}
56 if self.
options[
'services']
and self.data.type(tlo)==
'Service':
57 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}
58 if self.
options[
'es']
and self.data.type(tlo)
in (
'ESSource',
'ESProducer'):
59 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}
63 children = self.data.children(obj)
65 seqlabel = self.data.label(obj)
67 seqlabel +=
'\\n%s:%s' % (self.data.pypackage(obj),self.data.lineNumber(obj))
68 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'])
74 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}
75 return '%s\n'%self.data.label(obj)
79 children=self.data.children(obj)
89 result = self.data.label(obj)
91 result +=
'\\n%s'%self.data.classname(obj)
93 result +=
'\\n%s:%s'%(self.data.pypackage(obj),self.data.lineNumber(obj))
98 classname = self.data.classname(obj)
99 pypath = self.data.pypath(obj)
100 pyline = self.data.lineNumber(obj)
106 pathlabel = self.data.label(path)
108 pathlabel +=
'\\n%s:%s'%(self.data.pypackage(path),self.data.lineNumber(path))
110 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'])
112 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'])
115 self.endstarts.append(
'endstart_%s'%self.data.label(path))
116 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}
118 self.pathstarts.append(
'start_%s'%self.data.label(path))
119 self.pathends.append(
'end_%s'%self.data.label(path))
120 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}
121 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}
126 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}
127 labels.append(self.data.label(c))
129 pathresult +=
'->'.
join(labels)+
'\n' 132 pathresult +=
'\n'.
join(labels)+
'\n' 134 if self.data.children(path):
135 for path_child
in self.data.children(path):
138 if len(labels)>0
and self.
options[
'seqconnect']:
140 pathresult +=
'endstart_%s->%s\n' % (self.data.label(path),labels[0])
142 pathresult +=
'start_%s->%s\n%s->end_%s\n' % (self.data.label(path),labels[0],labels[-1],self.data.label(path))
153 for path
in self.
toplevel[
'endpaths']:
156 for seq
in self.
toplevel[
'sequences']:
164 result+=
"%s->%s\n" % (p,p2)
171 allobjects = [self.
nodes[n][
'obj']
for n
in self.
nodes if self.
nodes[n][
'inpath']]
172 self.data.readConnections(allobjects)
173 connections = self.data.connections()
174 for objects,names
in connections.items():
176 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'])
178 result +=
'%s->%s[color="%s"]\n' % (objects[0],objects[1],self.
options[
'color_inputtag'])
189 self.
nodes[
'source']={
'obj':s,
'n_label':self.data.classname(s),
'n_shape':self.
shapes[
'Source']}
192 result +=
'source->%s\n' % (p)
202 for e
in self.
toplevel[
'essources']:
203 servicenodes.append(self.data.label(e))
204 self.
nodes[self.data.label(e)]={
'obj':e,
'n_label':self.
nodeLabel(e),
'n_shape':self.
shapes[
'ESSource'],
'inpath':
False}
206 for e
in self.
toplevel[
'esproducers']:
207 servicenodes.append(self.data.label(e))
208 self.
nodes[self.data.label(e)]={
'obj':e,
'n_label':self.
nodeLabel(e),
'n_shape':self.
shapes[
'ESProducer'],
'inpath':
False}
212 self.servicenodes.append(self.data.label(s))
213 self.
nodes[self.data.label(s)]={
'obj':s,
'n_label':self.
nodeLabel(e),
'n_shape':self.
shapes[
'Service'],
'inpath':
False}
216 maxendpath=
max([len(
recurseChildren(path)
for path
in self.toplevel.get(
'endpaths',(0,)))])
220 for i,s
in enumerate(servicenodes[:-1]):
221 if not i%(maxpath+maxendpath)==(maxpath+maxendpath)-1:
222 result+=
'%s->%s[style=invis]\n' % (s,servicenodes[i+1])
232 result +=
"%s[%s]\n" % (n,
','.
join([
'%s="%s"' % (k[2:],v)
for k,v
in self.
nodes[n].
items()
if k[0:2]==
'n_']))
237 Return a legend subgraph using current shape and colour preferences. 239 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'])
255 if self.data.process():
256 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))
258 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))
264 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. 267 'legend':(
'Show Legend',
'boolean',
True),
268 'source':(
'Show Source',
'boolean',
True),
269 'es':(
'Show Event Setup',
'boolean',
False),
270 'tagconnect':(
'Connect InputTags',
'boolean',
True),
271 'seqconnect':(
'Connect Module Sequence',
'boolean',
False),
272 'services':(
'Show Services',
'boolean',
False),
273 'endpath':(
'Show EndPaths',
'boolean',
True),
274 'seq':(
'Group Sequences',
'boolean',
True),
275 'class':(
'Show Class',
'boolean',
True),
276 'file':(
'Show File',
'boolean',
True),
277 'schedule':(
'Show Schedule',
'boolean',
False),
278 'taglabel':(
'Show Tag Labels',
'boolean',
True),
279 'color_path':(
'Path Color',
'color',
'#ff00ff'),
280 'color_endpath':(
'EndPath Color',
'color',
'#ff0000'),
281 'color_sequence':(
'Sequence Color',
'color',
'#00ff00'),
282 'color_inputtag':(
'InputTag Color',
'color',
'#0000ff'),
283 'color_schedule':(
'Schedule Color',
'color',
'#00ffff'),
284 'url':(
'Include URLs',
'boolean',
False),
285 'urlprocess':(
'Postprocess URL (for client-side imagemaps)',
'boolean',
False),
286 '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'}]}"),
287 'node_graphs':(
'Produce individual graphs focussing on each node',
'boolean',
False),
288 'node_graphs_restrict':(
'Select which nodes to make node graphs for',
'string',
''),
289 'node_depth':(
'Search depth for individual node graphs',
'int',1),
290 'font_name':(
'Font name',
'string',
'Times-Roman'),
291 'font_size':(
'Font size',
'int',8),
292 'png_max_size':(
'Maximum edge for png image',
'int',16768)
294 plugin_name=
'DOT Export' 295 file_types=(
'bmp',
'dot',
'eps',
'gif',
'jpg',
'pdf',
'png',
'ps',
'svg',
'tif',
'png+map',
'stdout')
297 FileExportPlugin.__init__(self)
301 self.
shapes[
'EDProducer']=
'box' 302 self.
shapes[
'EDFilter']=
'invhouse' 303 self.
shapes[
'EDAnalyzer']=
'house' 304 self.
shapes[
'OutputModule']=
'invtrapezium' 305 self.
shapes[
'ESSource']=
'Mdiamond' 306 self.
shapes[
'ESProducer']=
'Msquare' 307 self.
shapes[
'Source']=
'ellipse' 308 self.
shapes[
'Service']=
'diamond' 312 Simple indenter for dot output, mainly to prettify it for human reading. 314 spaces =
lambda d:
''.
join([space]*d)
318 for line
in dot.splitlines():
320 newdot += spaces(depth)+line+
'\n' 324 newdot += spaces(depth)+line+
'\n' 326 newdot += spaces(depth)+line+
'\n' 335 re_link = re.compile(
r'^\s*?(\w*?)->(\w*?)(?:\[.*?\])?$',re.MULTILINE)
336 re_nodedef = re.compile(
r'^\s*?(\w*?)(?:\[.*?\])?$',re.MULTILINE)
337 re_title = re.compile(
r'^label=\"(.*?)\"$',re.MULTILINE)
338 re_nodeprops = re.compile(
r'^\s*?('+node+
r')\[(.*?)\]$',re.MULTILINE)
340 nodes = re_nodedef.findall(dotdata)
341 if not node
in nodes:
342 raise Exception(
"Selected node (%s) not found" % (node))
343 links_l = re_link.findall(dotdata)
348 links[link[0]] += [link[1]]
350 links[link[0]] = [link[1]]
352 links[link[1]] += [link[0]]
354 links[link[1]] = [link[0]]
356 def node_recursor(links,depthleft,start):
359 return links[start]+[start]
362 for l
in links[start]:
363 result.extend(node_recursor(links,depthleft-1,l))
369 include_nodes = set(node_recursor(links,depth-1,node))
370 include_nodes.add(node)
375 def __call__(self,match):
377 return match.group(0)
382 def __call__(self,match):
384 return match.group(0)
387 dotdata = re_link.sub(link_replacer(include_nodes),dotdata)
388 dotdata = re_nodedef.sub(node_replacer(include_nodes),dotdata)
389 dotdata = re_title.sub(
r'label="\g<1>\\nDepth '+
str(depth_s)+
r' from node ' +node+
r'"',dotdata,1)
390 dotdata = re_nodeprops.sub(
'\\g<1>[\\g<2>,color="red"]',dotdata,1)
396 Re-process the client-side image-map produces when png+map is selected. 397 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. 399 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. 401 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. 403 The dictionary should have structure: 415 html_attribute1:"...", 416 html_attribute2:"..." 419 The imagemap is first scaled in size by scale_x and scale_y. 420 It is then split into split_x*split_y rectangular cells. 421 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. 422 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. 424 This will probably be quite sensitive to the presence of special characters, complex splitting schemes, etc. Use with caution. 426 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. 429 area_default = {
'split_x':1,
'scale_x':1.,
'split_y':1,
'scale_y':1.,
'cells':[]}
430 cell_default = {
'top':0,
'left':0,
'width':1,
'height':1,
'html_href':
'#'}
431 re_area = re.compile(
'<area.*?/>',re.DOTALL)
433 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)
434 re_htmlunquote = re.compile(
'&#([0-9]{1,3});')
435 mapdata = re_htmlunquote.sub(
lambda x: chr(
int(x.group(1))),mapdata)
436 areas = re_area.findall(mapdata)
439 data = re_content.search(area)
440 baseurl = data.group(1)
441 x1,y1,x2,y2 =
map(int,(data.group(2),data.group(3),data.group(4),data.group(5)))
442 rad_x,rad_y =
int((x2-x1)*0.5),
int((y2-y1)*0.5)
443 centre_x,centre_y = x1+rad_x,y1+rad_y
444 basedict = eval(baseurl)
445 for ad
in area_default:
446 if not ad
in basedict:
447 basedict[ad]=area_default[ad]
448 rad_x =
int(rad_x*basedict[
'scale_x'])
449 rad_y =
int(rad_y*basedict[
'scale_y'])
450 top_x,top_y = centre_x-rad_x,centre_y-rad_y
451 split_x,split_y =
int((2*rad_x)/basedict[
'split_x']),
int((2*rad_y)/basedict[
'split_y'])
453 for cell
in basedict[
'cells']:
454 for cd
in cell_default:
456 cell[cd]=cell_default[cd]
457 x1,y1 = top_x+split_x*cell[
'left'],top_y+split_y*cell[
'top']
458 x2,y2 = x1+split_x*cell[
'width'],y1+split_y*cell[
'height']
459 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_')]))
460 new_areas.append(area_html)
461 return '<map id="configbrowse" name="configbrowse">\n%s\n</map>'%(
'\n'.
join(new_areas))
472 if len(dot_producer.nodes)>0:
474 if self.options[
'node_graphs']:
475 nodes = [n
for n
in dot_producer.nodes
if data.type(dot_producer.nodes[n][
'obj'])
in (
'EDAnalyzer',
'EDFilter',
'EDProducer',
'OutputModule')]
477 if self.options[
'node_graphs_restrict']
in n:
479 node_dot = self.
selectNode(dot,n,self.options[
'node_depth'])
480 self.
write_output(node_dot,filename.replace(
'.',
'_%s.'%n),filetype)
487 print(
"WARNING: Empty image. Not saved.")
491 png_header =
'\x89PNG\x0d\x0a\x1a\x0a' 493 filedata = open(filename,
'r').read(24) 494 png_data = struct.unpack('>8s4s4sII',filedata)
495 if not (png_data[0]==png_header
and png_data[2]==ihdr):
496 raise 'PNG header or IHDR not found' 497 return png_data[3],png_data[4]
502 dotfile = open(filename,
'w')
505 elif filetype==
'stdout':
507 elif filetype==
'pdf':
508 dot_p = subprocess.Popen([
'dot',
'-Tps2'],stdin=subprocess.PIPE,stdout=subprocess.PIPE)
509 ps2 = dot_p.communicate(dot)[0]
510 if not dot_p.returncode==0:
511 raise "dot returned non-zero exit code: %s"%dot_p.returncode
512 pdf_p = subprocess.Popen([
'ps2pdf',
'-',filename],stdin=subprocess.PIPE)
513 pdf_p.communicate(ps2)
514 if not pdf_p.returncode==0:
515 raise "ps2pdf returned non-zero exit code: %s"%pdf_p.returncode
516 elif filetype==
'png+map':
518 filename = filename.split(
'.')[0]
519 dot_p = subprocess.Popen([
'dot',
'-Tpng',
'-o',
'%s.png'%filename,
'-Tcmapx_np'],stdin=subprocess.PIPE,stdout=subprocess.PIPE)
520 mapdata = dot_p.communicate(dot)[0]
521 if not dot_p.returncode==0:
522 raise "dot returned non-zero exit code: %s"%dot_p.returncode
523 if self.options[
'urlprocess']:
525 mapfile = open(
'%s.map'%filename,
'w')
526 mapfile.write(mapdata)
529 if max(filesize) > self.options[
'png_max_size']:
530 print(
"png image is too large (%s pixels/%s max pixels), deleting" % (filesize,self.options[
'png_max_size']))
531 os.remove(
'%s.png'%filename)
532 os.remove(
'%s.map'%filename)
533 elif filetype==
'png':
534 dot_p = subprocess.Popen([
'dot',
'-T%s'%(filetype),
'-o',filename],stdin=subprocess.PIPE)
535 dot_p.communicate(dot)
536 if not dot_p.returncode==0:
537 raise "dot returned non-zero exit code: %s"%dot_p.returncode
539 if max(filesize) > self.options[
'png_max_size']:
540 print(
"png image is too large (%s pixels/%s max pixels), deleting" % (filesize,self.options[
'png_max_size']))
543 dot_p = subprocess.Popen([
'dot',
'-T%s'%(filetype),
'-o',filename],stdin=subprocess.PIPE)
544 dot_p.communicate(dot)
545 if not dot_p.returncode==0:
546 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)