Back to home page

Project CMSSW displayed by LXR

 
 

    


File indexing completed on 2021-09-16 03:24:00

0001 #!/usr/bin/env python3
0002 
0003 from builtins import range
0004 import sys, os.path, json
0005 from collections import defaultdict
0006 import ROOT
0007 ROOT.PyConfig.IgnoreCommandLineOptions = True
0008 ROOT.gROOT.SetBatch(True)
0009 
0010 
0011 class FileData:
0012     def __init__(self,data):
0013         self._json = data
0014         for k,v in data.items():
0015             setattr(self,k,v)
0016         self.Events = self.trees["Events"]
0017         self.nevents = self.Events["entries"]
0018         self.Runs = self.trees["Runs"]
0019         self.nruns = self.Runs["entries"]
0020         self.LuminosityBlocks = self.trees["LuminosityBlocks"]
0021         self.nluminosityblocks = self.LuminosityBlocks["entries"]
0022 
0023 class Branch:
0024     def __init__(self, tree, branch):
0025         self.tree = tree
0026         self.branch = branch
0027         self.name = branch.GetName()
0028         self.doc = branch.GetTitle()
0029         self.tot  = branch.GetZipBytes()/1024.0
0030         self.entries = None; 
0031         self.single = True
0032         self.kind   = "Unknown"
0033         if branch.GetNleaves() != 1:
0034             sys.stderr.write("Cannot parse branch '%s' in tree %s (%d leaves)\n"%(branch.GetName(), tree.GetName(), branch.GetNleaves()))
0035             return
0036         self.leaf = branch.FindLeaf(branch.GetName())
0037         if not self.leaf:
0038             sys.stderr.write("Cannot parse branch '%s' in tree %s (no leaf)\n"%(branch.GetName(), tree.GetName()))
0039             return
0040         self.kind = self.leaf.GetTypeName()
0041         if "Idx" in self.name:
0042                 self.kind+="(index to %s)"%((self.name[self.name.find("_")+1:self.name.find("Idx")]).title())
0043         if self.leaf.GetLen() == 0 and self.leaf.GetLeafCount() != None:
0044             self.single = False
0045             self.counter = self.leaf.GetLeafCount().GetName()
0046     def toJSON(self):
0047         return ( self.name, dict(name = self.name, doc = self.doc, tot=self.tot, entries=self.entries, single=self.single, kind=self.kind, counter = getattr(self,'counter','')) )
0048 
0049 class BranchGroup:
0050     def __init__(self, name):
0051         self.name = name
0052         self.tot  = 0
0053         self.entries = None; 
0054         self.subs = []
0055         self.kind   = None
0056         self.doc    = ''
0057     def append(self, sub):
0058         self.subs.append(sub)
0059         self.tot += sub.tot
0060         if not self.doc: self.doc = sub.doc
0061     def getKind(self):
0062         if self.kind: return self.kind
0063         if len(self.subs) == 1:
0064             if self.subs[0].single: self.kind = "Variable"
0065             else: 
0066                 self.kind = "Vector"
0067                 self.counter = self.subs[0].counter
0068         else:
0069             allsingles, commonCounter = True, True
0070             counter = None
0071             for s in self.subs:
0072                 if not s.single:
0073                     allsingles = False
0074                     if counter == None: counter = s.counter
0075                     elif counter != s.counter:
0076                         commonCounter = False
0077             if allsingles:
0078                 self.kind = "Singleton"
0079             elif commonCounter:
0080                 self.kind = "Collection"
0081                 self.counter = counter
0082             else:
0083                 self.kind = "ItsComplicated"
0084         return self.kind
0085     def toJSON(self):
0086         return (self.name, dict(name = self.name, doc = self.doc, kind = self.kind, tot = self.tot, entries = self.entries, subs = [s.name for s in self.subs]))
0087 
0088 
0089 def inspectRootFile(infile):
0090     if not os.path.isfile(infile): raise RuntimeError
0091     filesize = os.path.getsize(infile)/1024.0
0092     tfile = ROOT.TFile.Open(infile)
0093     trees = {}
0094     for treeName in "Events", "Runs", "LuminosityBlocks":
0095         toplevelDoc={}
0096         tree = tfile.Get(treeName)
0097         entries = tree.GetEntries()
0098         trees[treeName] = tree
0099         branchList = tree.GetListOfBranches()
0100         allbranches = [ Branch(tree, br) for br in branchList ]
0101         branchmap = dict((b.name,b) for b in allbranches)
0102         branchgroups = {}
0103         # make list of counters and countees
0104         counters = defaultdict(list)
0105         for b in allbranches:
0106             if not b.single:
0107                 counters[b.counter].append(b.name)
0108             else:
0109                 b.entries = entries
0110         c1 = ROOT.TCanvas("c1","c1")
0111         for counter,countees in counters.items():
0112             n = tree.Draw(counter+">>htemp")
0113             if n != 0:
0114                 htemp = ROOT.gROOT.FindObject("htemp")
0115                 n = htemp.GetEntries() * htemp.GetMean()
0116                 htemp.Delete()
0117             branchmap[counter].entries = entries
0118             for c in countees:
0119                 br = branchmap[c] 
0120                 br.entries = n
0121         # now we start to create branch groups
0122         for b in allbranches:
0123             if b.name in counters:
0124                  if len(b.doc) > 0:
0125                      toplevelDoc[b.name[1:]]=b.doc
0126                  continue # skip counters
0127             if "_" in b.name:
0128                 head, tail = b.name.split("_",1)
0129             else:
0130                 head = b.name
0131                 toplevelDoc[b.name]=b.doc
0132             if head not in branchgroups:
0133                 branchgroups[head] = BranchGroup(head)
0134             branchgroups[head].append(b)
0135         for bg in branchgroups.values():
0136             if bg.name in toplevelDoc:
0137                 bg.doc = toplevelDoc[bg.name]
0138             kind = bg.getKind()
0139             bg.entries = bg.subs[0].entries
0140             if kind == "Vector" or kind == "Collection":
0141                 bg.append(branchmap[bg.counter])
0142             elif kind == "ItsComplicated":
0143                 for counter in set(s.counter for s in bg.subs if not s.single):
0144                     bg.append(branchmap[counter])
0145         allsize_c = sum(b.tot for b in allbranches)
0146         allsize = sum(b.tot for b in branchgroups.values())
0147         if abs(allsize_c - allsize) > 1e-6*(allsize_c+allsize):
0148             sys.stderr.write("Total size mismatch for tree %s: %10.4f kb vs %10.4f kb\n" % (treeName, allsize, allsize_c))
0149         trees[treeName] =  dict(
0150                 entries = entries,
0151                 allsize = allsize,
0152                 branches = dict(b.toJSON() for b in allbranches),
0153                 branchgroups = dict(bg.toJSON() for bg in branchgroups.values()),
0154             )
0155         c1.Close()
0156     tfile.Close()
0157     return dict(filename = os.path.basename(infile), filesize = filesize, trees = trees)
0158 
0159 def makeSurvey(treeName, treeData):
0160     allsize = treeData['allsize']
0161     entries = treeData['entries']
0162     survey = list(treeData['branchgroups'].values())
0163     survey.sort(key = lambda bg : - bg['tot'])
0164     scriptdata = []
0165     runningtotal = 0
0166     unit = treeName[:-1].lower()
0167     for s in survey:
0168         if s['tot'] < 0.01*allsize:
0169             tag = "<b>Others</b><br/>Size: %.0f b/%s (%.1f%%)" % ((allsize - runningtotal)/entries*1024, unit, (allsize-runningtotal)/allsize*100)
0170             scriptdata.append( "{ 'label':'others', 'tag':'top', 'size':%s, 'tip':'%s' }" % ((allsize-runningtotal)/entries, tag) )
0171             break
0172         else:
0173             tag = "<b><a href=\"#%s\">%s</a></b><br/>" % (s['name'],s['name']);
0174             tag += "Size: %.0f b/%s (%.1f%%)" % (s['tot']/entries*1024, unit, s['tot']/allsize*100);
0175             if (s['kind'] in ("Vector","Collection")) and s['entries'] > 0:
0176                 tag += "<br/>Items/%s:  %.1f, %.0f b/item" %(unit, float(s['entries'])/entries, s['tot']/s['entries']*1024);
0177             scriptdata.append( "{ 'label':'%s', 'tag':'%s', 'size':%s, 'tip':'%s' }" % ( s['name'], s['name'], s['tot']/entries, tag) )
0178         runningtotal += s['tot']
0179     return (survey, "\n,\t".join(scriptdata))
0180   
0181 def writeSizeReport(fileData, trees, stream):
0182     filename = fileData.filename
0183     filesize = fileData.filesize
0184     events = fileData.nevents
0185     surveys = {}
0186     for treename, treeData in trees.items():
0187         surveys[treename] = makeSurvey(treename, treeData)
0188     title = "%s (%.3f Mb, %d events, %.2f kb/event)" % (filename, filesize/1024.0, events, filesize/events)
0189     stream.write("""
0190     <html>
0191     <head>
0192         <title>{title}</title>
0193         <link rel="stylesheet" type="text/css" href="http://gpetrucc.web.cern.ch/gpetrucc/micro/patsize.css" />
0194         <script type="text/javascript" src="http://gpetrucc.web.cern.ch/gpetrucc/rgraph/RGraph.common.core.js"></script>
0195         <script type="text/javascript" src="http://gpetrucc.web.cern.ch/gpetrucc/rgraph/RGraph.pie.js"></script>
0196         <script type="text/javascript" src="http://gpetrucc.web.cern.ch/gpetrucc/rgraph/RGraph.common.dynamic.js"></script>
0197         <script type="text/javascript" src="http://gpetrucc.web.cern.ch/gpetrucc/rgraph/RGraph.common.tooltips.js"></script>
0198         <script type="text/javascript" src="http://gpetrucc.web.cern.ch/gpetrucc/rgraph/RGraph.common.key.js"></script>
0199     </head>
0200     <body>
0201     <a name="top" id="top"><h1>{title}</h1></a>
0202     """.format(title=title))
0203     stream.write("\n".join("""
0204     <h1>{treename} data</h1>
0205     <canvas id="{treename}Canvas" width="800" height="300">[No canvas support]</canvas>
0206     """.format(treename=treename) for treename in surveys.keys()))
0207     stream.write('    <script type="text/javascript">\n')
0208     stream.write("\n".join("""
0209         var data_{treename} = [ {scriptdata} ];
0210         """.format(treename=treename, scriptdata=scriptdata)
0211         for treename, (_, scriptdata) in surveys.items()))
0212     stream.write("""
0213         window.onload = function() {{
0214             {0}
0215         }}
0216     </script>
0217     """.format(
0218             "\n".join("""
0219             values = [];
0220             labels = [];
0221             keys   = [];
0222             tips   = [];
0223             for (var i = 0; i < data_{treename}.length; i++) {{
0224                 values.push( data_{treename}[i].size );
0225                 labels.push( data_{treename}[i].label );
0226                 keys.push( data_{treename}[i].label );
0227                 tips.push( data_{treename}[i].tip );
0228             }}
0229             var chart_{treename} = new RGraph.Pie("{treename}Canvas", values)
0230                         .Set('exploded', 7)
0231                         .Set('tooltips', tips)
0232                         .Set('tooltips.event', 'onmousemove')
0233                         .Set('key', labels)
0234                         .Set('key.position.graph.boxed', false)
0235                         .Draw();
0236             """.format(treename=treename, scriptdata=scriptdata)
0237             for treename, (_, scriptdata) in surveys.items())))
0238 
0239     if len(trees) > 1:
0240         stream.write("    <h1>Collections summary</h1>\n")
0241     stream.write("    <table>\n")
0242     stream.write("<tr class='header'><th>" + "</th><th>".join([ "collection", "kind", "vars", "items/evt", "kb/evt", "b/item", "plot", "%" ]) + "</th><th colspan=\"2\">cumulative %</th></tr>\n");
0243     runningtotal = 0
0244     for treename, (survey, _) in surveys.items():
0245         treetotal = trees[treename]['allsize']
0246         treerunningtotal = 0
0247         for s in survey:
0248             stream.write("<tr><th title=\"%s\"><a href='#%s'>%s</a></th><td style='text-align : left;'>%s</td><td>%d</td>" % (s['doc'],s['name'],s['name'],s['kind'].lower(),len(s['subs'])))
0249             stream.write("<td>%.2f</td><td>%.3f</td><td>%.1f</td>" % (s['entries']/events, s['tot']/events, s['tot']/s['entries']*1024 if s['entries'] else 0))
0250             stream.write("<td class=\"img\"><img src='http://gpetrucc.web.cern.ch/gpetrucc/micro/blue-dot.gif' width='%d' height='%d' /></td>" % (s['tot']/treetotal*200,10))
0251             stream.write("<td>%.1f%%</td>" % ( s['tot']/treetotal * 100.0))
0252             stream.write("<td>%.1f%%</td>" % ( (runningtotal+s['tot'])/treetotal * 100.0))
0253             stream.write("<td>%.1f%%</td>" % ( (treetotal-runningtotal)/treetotal * 100.0))
0254             stream.write("</tr>\n")
0255             treerunningtotal += s['tot']
0256         runningtotal += treerunningtotal
0257 
0258         # all known data
0259         stream.write("<tr><th>All %s data</th>" % treename)
0260         stream.write("<td>&nbsp;</td><td>&nbsp;</td><td>&nbsp;</td><td><b>%.2f</b></td><td>&nbsp;</td>"  % (treetotal/events))
0261         stream.write("<td class=\"img\"><img src=\"http://gpetrucc.web.cern.ch/gpetrucc/micro/green-dot.gif\" width='%d' height='10' />" % ( treetotal/filesize*100.0))
0262         stream.write("</td><td>%.1f%%<sup>a</sup></td>" % (treetotal/filesize*100.0))
0263         stream.write("</tr>\n")
0264 
0265         if treename == "Events":
0266             # non-event
0267             stream.write("<tr><th>Non per-event data or overhead</th>")
0268             stream.write("<td>&nbsp;</td><td>&nbsp;</td><td>&nbsp;</td><td>%.2f</td><td>&nbsp;</td>" % ( (filesize-treetotal)/events))
0269             stream.write("<td class=\"img\"><img src='http://gpetrucc.web.cern.ch/gpetrucc/micro/red-dot.gif' width='%d' height='%d' /></td>" % ( (filesize-treetotal)/filesize * 100, 10 ))
0270             stream.write("<td>%.1f%%<sup>a</sup></td>" % ( (filesize-treetotal)/filesize * 100.0 ))
0271             stream.write("</tr>\n")
0272 
0273     if len(surveys) > 1:
0274         # other, unknown overhead
0275         stream.write("<tr><th>Overhead</th>")
0276         stream.write("<td>&nbsp;</td><td>&nbsp;</td><td>&nbsp;</td><td>%.2f</td><td>&nbsp;</td>" % ( (filesize-runningtotal)/events))
0277         stream.write("<td class=\"img\"><img src='http://gpetrucc.web.cern.ch/gpetrucc/micro/red-dot.gif' width='%d' height='%d' /></td>" % ( (filesize-runningtotal)/filesize * 100, 10 ))
0278         stream.write("<td>%.1f%%<sup>a</sup></td>" % ( (filesize-runningtotal)/filesize * 100.0 ))
0279         stream.write("</tr>\n")
0280 
0281     # all file
0282     stream.write("<tr><th>File size</th>")
0283     stream.write("<td>&nbsp;</td><td>&nbsp;</td><td>&nbsp;</td><td><b>%.2f</b></td><td>&nbsp;</td>" % (filesize/events))
0284     stream.write("<td>&nbsp;</td><td>&nbsp;</td></tr>\n")
0285 
0286     stream.write("""
0287     </table>
0288     Note: size percentages of individual event products are relative to the total size of Event data only (or equivalent for non-Events trees).<br />
0289     Percentages with <sup>a</sup> are instead relative to the full file size.
0290     """)
0291     for treename, treeData in trees.items():
0292         stream.write("""
0293         <h1>%s detail</h1>
0294         """ % treename)
0295         for s in sorted(surveys[treename][0], key = lambda s : s['name']):
0296             stream.write("<h2><a name='%s' id='%s'>%s</a> (%.1f items/evt, %.3f kb/evt) <sup><a href=\"#top\">[back to top]</a></sup></h2>" % (s['name'], s['name'], s['name'], s['entries']/events, s['tot']/events))
0297             stream.write("<table>\n")
0298             stream.write("<tr class='header'><th>" + "</th><th>".join( [ "branch", "kind", "b/event", "b/item", "plot", "%" ]) + "</th></tr>\n")
0299             subs = [ treeData['branches'][b] for b in s['subs'] ]
0300             for b in sorted(subs, key = lambda s : - s['tot']):
0301                 stream.write("<tr><th title=\"%s\">%s</th><td style='text-align : left;'>%s</td><td>%.1f</td><td>%.1f</td>" % (b['doc'],b['name'], b['kind'], b['tot']/events*1024, b['tot']/s['entries']*1024 if s['entries'] else 0))
0302                 stream.write("<td class=\"img\"><img src='http://gpetrucc.web.cern.ch/gpetrucc/micro/blue-dot.gif' width='%d' height='%d' /></td>" % ( b['tot']/s['tot']*200, 10 ))
0303                 stream.write("<td>%.1f%%</td>" % (b['tot']/s['tot'] * 100.0))
0304                 stream.write("</tr>\n")
0305             stream.write("</table>\n")
0306     stream.write("""
0307     </body></html>
0308     """)
0309 
0310 def writeDocReport(fileName, trees, stream):
0311     stream.write( """
0312     <html>
0313     <head>
0314         <title>Documentation for {filename} </title>
0315         <link rel="stylesheet" type="text/css" href="http://gpetrucc.web.cern.ch/gpetrucc/micro/patsize.css" />
0316     </head>
0317     <body>
0318     """.format(filename=fileName))
0319     for treename, treeData in trees.items():
0320         stream.write("""
0321         <h1>{treename} Content</h1>
0322         <table>
0323         """.format(treename=treename))
0324         stream.write( "<tr class='header'><th>Collection</th><th>Description</th></tr>\n" )
0325         groups = list(treeData['branchgroups'].values())
0326         groups.sort(key = lambda s : s['name'])
0327         for s in groups:
0328             stream.write( "<th><a href='#%s'>%s</a></th><td style='text-align : left;'>%s</td></tr>\n" % (s['name'],s['name'],s['doc']) )
0329         stream.write("</table>\n\n<h1>{treename} detail</h1>\n".format(treename=treename))
0330         for s in groups:
0331             stream.write( "<h2><a name='%s' id='%s'>%s</a> <sup><a href=\"#top\">[back to top]</a></sup></h2>\n" % (s['name'], s['name'], s['name']) )
0332             stream.write( "<table>\n" )
0333             stream.write( "<tr class='header'><th>Object property</th><th>Type</th><th>Description</th></tr>\n" )
0334             subs = [ treeData['branches'][b] for b in s['subs'] ]
0335             for b in sorted(subs, key = lambda s : s['name']):
0336                 stream.write( "<th>%s</th><td style='text-align : left;'>%s</td><td style='text-align : left;'>%s</td>" % (b['name'], b['kind'], b['doc']) )
0337                 stream.write( "</tr>\n" )
0338             stream.write( "</table>\n" )
0339     stream.write( """
0340     </body></html>
0341     """ )
0342 
0343 def writeMarkdownSizeReport(fileData, trees, stream):
0344     filename = fileData.filename
0345     filesize = fileData.filesize
0346     events = fileData.nevents
0347     surveys = {}
0348     for treename, treeData in trees.items():
0349         surveys[treename] = makeSurvey(treename, treeData)[0]
0350 
0351     stream.write("**%s (%.3f Mb, %d events, %.2f kb/event)**\n" % (filename, filesize/1024.0, events, filesize/events))
0352     stream.write("\n# Event data\n" if len(trees) == 1 else "\n# Collection data\n")
0353     stream.write("| collection | kind | vars | items/evt | kb/evt | b/item | plot   | % | ascending cumulative % | descending cumulative % |\n")
0354     stream.write("| - | - | - | - | - | - | - | - | - | - |\n")
0355     runningtotal = 0
0356     for treename, survey in surveys.items():
0357         treetotal = trees[treename]['allsize']
0358         treerunningtotal = 0
0359         for s in survey:
0360             stream.write("| [**%s**](#%s '%s') | %s | %d" % (s['name'], s['name'].lower(), s['doc'].replace('|', '\|').replace('\'', '\"'), s['kind'].lower(), len(s['subs'])))
0361             stream.write("| %.2f|%.3f|%.1f" % (s['entries']/events, s['tot']/events, s['tot'] / s['entries'] * 1024 if s['entries'] else 0))
0362             stream.write("| <img src='http://gpetrucc.web.cern.ch/gpetrucc/micro/blue-dot.gif' width='%d' height='%d' />" % (s['tot'] / treetotal * 200, 10))
0363             stream.write("| %.1f%%" % (s['tot'] / treetotal * 100.0))
0364             stream.write("| %.1f%%" % ((runningtotal+s['tot'])/treetotal * 100.0))
0365             stream.write("| %.1f%% |\n" % ((treetotal-runningtotal)/treetotal * 100.0))
0366             runningtotal += s['tot']
0367 
0368         # all known data
0369         stream.write("**All %s data**" % treename)
0370         stream.write("| | | | **%.2f**"  % (treetotal/events))
0371         stream.write("| | <img src='http://gpetrucc.web.cern.ch/gpetrucc/micro/green-dot.gif' width='%d' height='%d' />" % (treetotal / filesize * 100.0, 10))
0372         stream.write("| %.1f%%<sup>a</sup> | | |\n" % (treetotal/filesize * 100.0))
0373 
0374         if treename == "Events":
0375             # non-event
0376             stream.write("**Non per-event data or overhead**")
0377             stream.write("| | | | %.2f" % ((filesize-treetotal)/events))
0378             stream.write("| | <img src='http://gpetrucc.web.cern.ch/gpetrucc/micro/red-dot.gif' width='%d' height='%d' />" % ((filesize - treetotal) / filesize * 100, 10))
0379             stream.write("| %.1f%%<sup>a</sup> | | |\n" % ((filesize-treetotal)/filesize * 100.0))
0380 
0381     if len(surveys) > 1:
0382         # other, unknown overhead
0383         stream.write("**Overhead**")
0384         stream.write("| | | | %.2f" % ((filesize-runningtotal)/events))
0385         stream.write("| | <img src='http://gpetrucc.web.cern.ch/gpetrucc/micro/red-dot.gif' width='%d' height='%d' />" % ((filesize - runningtotal) / filesize * 100, 10))
0386         stream.write("| %.1f%%<sup>a</sup> | | |\n" % ((filesize-runningtotal)/filesize * 100.0))
0387 
0388     # all file
0389     stream.write("**File size**")
0390     stream.write("| | | | **%.2f**" % (filesize/events))
0391     stream.write("| | | | | |\n\n")
0392 
0393     stream.write("Note: size percentages of individual event products are relative to the total size of Event data only (or equivalent for non-Events trees).\\\n")
0394     stream.write("Percentages with <sup>a</sup> are instead relative to the full file size.\n\n")
0395 
0396     for treename, survey in surveys.items():
0397         stream.write("# %s detail\n" % treename)
0398         for s in sorted(survey, key=lambda s: s['name']):
0399             stream.write("## <a id='%s'></a>%s (%.1f items/evt, %.3f kb/evt) [<sup>[back to top]</sup>](#event-data)\n" % (s['name'].lower(), s['name'], s['entries'] / events, s['tot'] / events))
0400             stream.write("| branch | kind | b/event | b/item | plot | % |\n")
0401             stream.write("| - | - | - | - | - | - |\n")
0402             subs = [trees[treename]['branches'][b] for b in s['subs']]
0403             for b in sorted(subs, key = lambda s: - s['tot']):
0404                 stream.write("| <b title='%s'>%s</b> | %s | %.1f | %.1f" % (b['doc'].replace('|', '\|').replace('\'', '\"'), b['name'], b['kind'], b['tot'] / events * 1024, b['tot'] / s['entries'] * 1024 if s['entries'] else 0))
0405                 stream.write("| <img src='http://gpetrucc.web.cern.ch/gpetrucc/micro/blue-dot.gif' width='%d' height='%d' />" % (b['tot'] / s['tot'] * 200, 10))
0406                 stream.write("| %.1f%% |\n" % (b['tot'] / s['tot'] * 100.0))
0407             stream.write("\n")
0408 
0409 def writeMarkdownDocReport(trees, stream):
0410     for treename, treeData in trees.items():
0411         stream.write("# %s Content\n" % treename)
0412         stream.write("\n| Collection | Description |\n")
0413         stream.write("| - | - |\n")
0414         groups = list(treeData['branchgroups'].values())
0415         groups.sort(key = lambda s : s['name'])
0416         for s in groups:
0417             stream.write("| [**%s**](#%s) | %s |\n" % (s['name'], s['name'].lower(), s['doc']))
0418         stream.write("\n# %s detail\n" % treename)
0419         for s in groups:
0420             stream.write("\n## <a id='%s'></a>%s [<sup>[back to top]</sup>](#content)\n" % (s['name'].lower(), s['name']))
0421             stream.write("| Object property | Type | Description |\n")
0422             stream.write("| - | - | - |\n")
0423             subs = [treeData['branches'][b] for b in s['subs']]
0424             for b in sorted(subs, key = lambda s : s['name']):
0425                 stream.write("| **%s** | %s| %s |\n" % (b['name'], b['kind'], b['doc']))
0426         stream.write("\n")
0427 
0428 def _maybeOpen(filename):
0429     return open(filename, 'w') if filename != "-" else sys.stdout
0430 
0431 if __name__ == '__main__':
0432     from optparse import OptionParser
0433     parser = OptionParser(usage="%prog [options] inputFile")
0434     parser.add_option("-j", "--json", dest="json", type="string", default=None, help="Write out json file")
0435     parser.add_option("-d", "--doc", dest="doc", type="string", default=None, help="Write out html doc")
0436     parser.add_option("-s", "--size", dest="size", type="string", default=None, help="Write out html size report")
0437     parser.add_option("--docmd", dest="docmd", type="string", default=None, help="Write out markdown doc")
0438     parser.add_option("--sizemd", dest="sizemd", type="string", default=None, help="Write out markdown size report")
0439     (options, args) = parser.parse_args()
0440     if len(args) != 1: raise RuntimeError("Please specify one input file")
0441 
0442     if args[0].endswith(".root"):
0443         filedata = FileData(inspectRootFile(args[0]))
0444     elif args[0].endswith(".json"):
0445         filedata = FileData(json.load(open(args[0],'r')))
0446     else: raise RuntimeError("Input file %s is not a root or json file" % args[0])
0447     
0448     if options.json:
0449         json.dump(filedata._json, _maybeOpen(options.json), indent=4)
0450         sys.stderr.write("JSON output saved to %s\n" % options.json)
0451 
0452     treedata = {}  # trees for (HTML or markdown) doc report
0453     if len(filedata.Runs["branches"]) > 1:  # default: run number
0454         treedata["Runs"] = filedata.Runs
0455     if len(filedata.LuminosityBlocks["branches"]) > 2:  # default: run number, lumiblock
0456         treedata["LuminosityBlocks"] = filedata.LuminosityBlocks
0457     treedata["Events"] = filedata.Events
0458 
0459     if options.doc:
0460         writeDocReport(filedata.filename, treedata, _maybeOpen(options.doc))
0461         sys.stderr.write("HTML documentation saved to %s\n" % options.doc)
0462     if options.size:
0463         writeSizeReport(filedata, treedata, _maybeOpen(options.size))
0464         sys.stderr.write("HTML size report saved to %s\n" % options.size)
0465     if options.docmd:
0466         writeMarkdownDocReport(treedata, _maybeOpen(options.docmd))
0467         sys.stderr.write("Markdown documentation saved to %s\n" % options.docmd)
0468     if options.sizemd:
0469         writeMarkdownSizeReport(filedata, treedata, _maybeOpen(options.sizemd))
0470         sys.stderr.write("Markdown size report saved to %s\n" % options.sizemd)