CMS 3D CMS Logo

cmsswSequenceInfo.py
Go to the documentation of this file.
1 #!/usr/bin/env python3
2 import os
3 import re
4 import time
5 import shutil
6 import sqlite3
7 import tempfile
8 import functools
9 import subprocess
10 from collections import namedtuple
11 from collections import defaultdict
12 from multiprocessing.pool import ThreadPool
13 
14 Sequence = namedtuple("Sequence", ["seqname", "step", "era", "scenario", "mc", "data", "fast"])
15 
16 # We use two global thread pools, to avoid submitting from one Pool into itself.
17 tp = ThreadPool()
18 stp = ThreadPool()
19 
20 # SQLiteDB to write results to.
21 # Set later from commandline args.
22 DBFILE = None
23 
24 # This file will actually be opened, though the content does not matter. Only to make CMSSW start up at all.
25 INFILE = "/store/data/Run2018A/EGamma/RAW/v1/000/315/489/00000/004D960A-EA4C-E811-A908-FA163ED1F481.root"
26 
27 # Modules that will be loaded but do not come from the DQM Sequence.
28 BLACKLIST='^(TriggerResults|.*_step|DQMoutput|siPixelDigis)$'
29 
30 # Set later from commandline args
31 RELEVANTSTEPS = []
32 
33 @functools.lru_cache(maxsize=None)
34 def inspectsequence(seq):
35  sep = ":"
36  if not seq.seqname:
37  sep = ""
38 
39  wd = tempfile.mkdtemp()
40 
41  # Provide a fake GDB to prevent it from running if cmsRun crashes. It would not hurt to have it run but it takes forever.
42  with open(wd + "/gdb", "w"):
43  pass
44  os.chmod(wd + "/gdb", 0o700)
45  env = os.environ.copy()
46  env["PATH"] = wd + ":" + env["PATH"]
47 
48  # run cmsdriver
49  driverargs = [
50  "cmsDriver.py",
51  "step3",
52  "--conditions", "auto:run2_data", # conditions is mandatory, but should not affect the result.
53  "-s", seq.step+sep+seq.seqname, # running only DQM seems to be not possible, so also load a single module for RAW2DIGI
54  "--process", "DUMMY",
55  "--mc" if seq.mc else "", "--data" if seq.data else "", "--fast" if seq.fast else "", # random switches
56  "--era" if seq.era else "", seq.era, # era is important as it trigger e.g. switching phase0/pahse1/phase2
57  "--eventcontent", "DQM", "--scenario" if seq.scenario else "", seq.scenario, # sceanario should affect which DQMOffline_*_cff.py is loaded
58  "--datatier", "DQMIO", # more random switches,
59  "--customise_commands", 'process.Tracer = cms.Service("Tracer")', # the tracer will tell us which modules actually run
60  "--filein", INFILE, "-n", "0", # load an input file, but do not process any events -- it would fail anyways.
61  "--python_filename", "cmssw_cfg.py", "--no_exec"
62  ]
63  # filter out empty args
64  driverargs = [x for x in driverargs if x]
65  subprocess.check_call(driverargs, cwd=wd, stdout=2) # 2: STDERR
66 
67  # run cmsRun to get module list
68  proc = subprocess.Popen(["cmsRun", "cmssw_cfg.py"], stderr=subprocess.STDOUT, stdout=subprocess.PIPE, cwd=wd, env=env)
69  tracedump, _ = proc.communicate()
70  # for HARVESTING, the code in endJob makes most jobs crash. But that is fine,
71  # we have the data we need by then.
72  if proc.returncode and seq.step not in ("HARVESTING", "ALCAHARVEST"):
73  raise Exception("cmsRun failed for cmsDriver command %s" % driverargs)
74 
75  lines = tracedump.splitlines()
76  labelre = re.compile(b"[+]+ starting: constructing module with label '(\w+)'")
77  blacklistre = re.compile(BLACKLIST)
78  modules = []
79  for line in lines:
80  m = labelre.match(line)
81  if m:
82  label = m.group(1).decode()
83  if blacklistre.match(label):
84  continue
85  modules.append(label)
86 
87  modules = set(modules)
88 
89  # run edmConfigDump to get module config
90  configdump = subprocess.check_output(["edmConfigDump", "cmssw_cfg.py"], cwd=wd)
91  lines = configdump.splitlines()
92  modulere = re.compile(b'process[.](.*) = cms.ED.*\("(.*)",')
93 
94  # collect the config blocks out of the config dump.
95  modclass = dict()
96  modconfig = dict()
97  inconfig = None
98  for line in lines:
99  if inconfig:
100  modconfig[inconfig] += b'\n' + line
101  if line == b')':
102  inconfig = None
103  continue
104 
105  m = modulere.match(line)
106  if m:
107  label = m.group(1).decode()
108  plugin = m.group(2).decode()
109  if label in modules:
110  modclass[label] = plugin
111  modconfig[label] = line
112  inconfig = label
113 
114  # run edmPluginHelp to get module properties
115  plugininfo = tp.map(getplugininfo, modclass.values())
116 
117  # clean up the temp dir in the end.
118  shutil.rmtree(wd)
119 
120  return modconfig, modclass, dict(plugininfo)
121 
122 # using a cache here to avoid running the (rather slow) edmPluginHelp multiple
123 # times for the same module (e.g. across different wf).
124 @functools.lru_cache(maxsize=None)
125 def getplugininfo(pluginname):
126  plugindump = subprocess.check_output(["edmPluginHelp", "-p", pluginname])
127  line = plugindump.splitlines()[0].decode()
128  # we care only about the edm base class for now.
129  pluginre = re.compile(".* " + pluginname + ".*[(]((\w+)::)?(\w+)[)]")
130  m = pluginre.match(line)
131  if not m:
132  # this should never happen, but sometimes the Tracer does report things that are not actually plugins.
133  return (pluginname, ("", ""))
134  else:
135  return (pluginname, (m.group(2), m.group(3)))
136 
137 def formatsequenceinfo(modconfig, modclass, plugininfo, showlabel, showclass, showtype, showconfig):
138  # printing for command-line use.
139  out = []
140  for label in modclass.keys():
141  row = []
142  if showlabel:
143  row.append(label)
144  if showclass:
145  row.append(modclass[label])
146  if showtype:
147  row.append("::".join(plugininfo[modclass[label]]))
148  if showconfig:
149  row.append(modconfig[label].decode())
150  out.append(tuple(row))
151  for row in sorted(set(out)):
152  print("\t".join(row))
153 
154 # DB schema for the HTML based browser. The Sequence members are kept variable
155 # to make adding new fields easy.
156 SEQFIELDS = ",".join(Sequence._fields)
157 SEQPLACEHOLDER = ",".join(["?" for f in Sequence._fields])
158 DBSCHEMA = f"""
159  CREATE TABLE IF NOT EXISTS plugin(classname, edmfamily, edmbase);
160  CREATE UNIQUE INDEX IF NOT EXISTS plugins ON plugin(classname);
161  CREATE TABLE IF NOT EXISTS module(id INTEGER PRIMARY KEY, classname, instancename, variation, config);
162  CREATE UNIQUE INDEX IF NOT EXISTS modules ON module(instancename, variation);
163  CREATE UNIQUE INDEX IF NOT EXISTS configs ON module(config);
164  CREATE TABLE IF NOT EXISTS sequence(id INTEGER PRIMARY KEY, {SEQFIELDS});
165  CREATE UNIQUE INDEX IF NOT EXISTS squences ON sequence({SEQFIELDS});
166  CREATE TABLE IF NOT EXISTS workflow(wfid, sequenceid);
167  CREATE UNIQUE INDEX IF NOT EXISTS wrokflows ON workflow(sequenceid, wfid);
168  CREATE TABLE IF NOT EXISTS sequencemodule(moduleid, sequenceid);
169 """
170 
171 def storesequenceinfo(seq, modconfig, modclass, plugininfo):
172  with sqlite3.connect(DBFILE) as db:
173  cur = db.cursor()
174  cur.executescript(DBSCHEMA)
175  # first, check if we already have that one. Ideally we'd check before doing all the work, but then the lru cache will take care of that on a different level.
176  seqid = list(cur.execute(f"SELECT id FROM sequence WHERE ({SEQFIELDS}) = ({SEQPLACEHOLDER});", (seq)))
177  if seqid:
178  return
179 
180  cur.execute("BEGIN;")
181  # dump everything into a temp table first...
182  cur.execute("CREATE TEMP TABLE newmodules(instancename, classname, config);")
183  cur.executemany("INSERT INTO newmodules VALUES (?, ?, ?)", ((label, modclass[label], modconfig[label]) for label in modconfig))
184  # ... then deduplicate and version the configs in plain SQL.
185  cur.execute("""
186  INSERT OR IGNORE INTO module(classname, instancename, variation, config)
187  SELECT classname, instancename,
188  (SELECT count(*) FROM module AS existing WHERE existing.instancename = newmodules.instancename),
189  config FROM newmodules;
190  """)
191 
192  # the plugin base is rather easy.
193  cur.executemany("INSERT OR IGNORE INTO plugin VALUES (?, ?, ?);", ((plugin, edm[0], edm[1]) for plugin, edm in plugininfo.items()))
194  # for the sequence we first insert, then query for the ID, then insert the modules into the relation table.
195  cur.execute(f"INSERT OR FAIL INTO sequence({SEQFIELDS}) VALUES({SEQPLACEHOLDER});", (seq))
196  seqid = list(cur.execute(f"SELECT id FROM sequence WHERE ({SEQFIELDS}) = ({SEQPLACEHOLDER});", (seq)))
197  seqid = seqid[0][0]
198  cur.executemany("INSERT INTO sequencemodule SELECT id, ? FROM module WHERE config = ?;", ((seqid, modconfig[label]) for label in modconfig))
199  cur.execute("COMMIT;")
200 
201 def storeworkflows(seqs):
202  with sqlite3.connect(DBFILE) as db:
203  cur = db.cursor()
204  cur.execute("BEGIN;")
205  cur.executescript(DBSCHEMA)
206  pairs = [[wf] + list(seq) for wf, seqlist in seqs.items() for seq in seqlist]
207  cur.executemany(f"INSERT OR IGNORE INTO workflow SELECT ?, (SELECT id FROM sequence WHERE ({SEQFIELDS}) = ({SEQPLACEHOLDER}));", pairs)
208  cur.execute("COMMIT;")
209 
210 def inspectworkflows(wfnumber):
211  # here, we run runTheMatrix and then parse the cmsDriver command lines.
212  # Not very complicated, but a bit of work.
213 
214  # Collect the workflow number where we detected each sequence here, so we can
215  # put this data into the DB later.
216  sequences = defaultdict(list)
217 
218  if wfnumber:
219  stepdump = subprocess.check_output(["runTheMatrix.py", "-l", str(wfnumber), "-ne"])
220  else:
221  stepdump = subprocess.check_output(["runTheMatrix.py", "-ne"])
222 
223  lines = stepdump.splitlines()
224  workflow = ""
225  workflowre = re.compile(b"^([0-9]+.[0-9]+) ")
226  for line in lines:
227  # if it is a workflow header: save the number.
228  m = workflowre.match(line)
229  if m:
230  workflow = m.group(1).decode()
231  continue
232 
233  # else, we only care about cmsDriver commands.
234  if not b'cmsDriver.py' in line: continue
235 
236  args = list(reversed(line.decode().split(" ")))
237  step = ""
238  scenario = ""
239  era = ""
240  mc = False
241  data = False
242  fast = False
243  while args:
244  item = args.pop()
245  if item == '-s':
246  step = args.pop()
247  if item == '--scenario':
248  scenario = args.pop()
249  if item == '--era':
250  era = args.pop()
251  if item == '--data':
252  data = True
253  if item == '--mc':
254  mc = True
255  if item == '--fast':
256  fast = True
257  steps = step.split(",")
258  for step in steps:
259  s = step.split(":")[0]
260  if s in RELEVANTSTEPS:
261  # Special case for the default sequence, which is noted as "STEP", not "STEP:".
262  if ":" in step:
263  seqs = step.split(":")[1]
264  for seq in seqs.split("+"):
265  sequences[workflow].append(Sequence(seq, s, era, scenario, mc, data, fast))
266  else:
267  sequences[workflow].append(Sequence("", s, era, scenario, mc, data, fast))
268  return sequences
269 
270 def processseqs(seqs):
271  # launch one map_async per element to get finer grain tasks
272  tasks = [stp.map_async(lambda seq: (seq, inspectsequence(seq)), [seq]) for seq in seqs]
273 
274  # then watch te progress and write to DB as results become available.
275  # That way all the DB access is single-threaded but in parallel with the analysis.
276  while tasks:
277  time.sleep(1)
278  running = []
279  done = []
280  for t in tasks:
281  if t.ready():
282  done.append(t)
283  else:
284  running.append(t)
285  for t in done:
286  if not t.successful():
287  print("Task failed.")
288  for it in t.get(): # should only be one
289  seq, res = it
290  storesequenceinfo(seq, *res)
291  tasks = running
292 
293 
294 # A small HTML UI built around http.server. No dependencies!
295 def serve():
296  import traceback
297  import http.server
298 
299  db = sqlite3.connect(DBFILE)
300 
301  def formatseq(seq):
302  return (seq.step + ":" + seq.seqname + " " + seq.era + " " + seq.scenario
303  + (" --mc" if seq.mc else "") + (" --data" if seq.data else "")
304  + (" --fast" if seq.fast else ""))
305 
306  def index():
307  out = []
308  cur = db.cursor()
309  out.append("<H2>Sequences</H2><ul>")
310  out.append("""<p> A sequence name, given as <em>STEP:@sequencename</em> here, does not uniquely identify a sequence.
311  The modules on the sequence might depend on other cmsDriver options, such as Era, Scenario, Data vs. MC, etc.
312  This tool lists parameter combinations that were observed. However, sequences with identical contents are grouped
313  on this page. The default sequence, used when no explicit sequence is apssed to cmsDriver, is noted as <em>STEP:</em>.</p>""")
314  rows = cur.execute(f"SELECT seqname, step, count(*) FROM sequence GROUP BY seqname, step ORDER BY seqname, step;")
315  for row in rows:
316  seqname, step, count = row
317  out.append(f' <li>')
318  out += showseq(step, seqname)
319  out.append(f' </li>')
320  out.append("</ul>")
321 
322  out.append("<H2>Modules</H2><ul>")
323  rows = cur.execute(f"SELECT classname, edmfamily, edmbase FROM plugin ORDER BY edmfamily, edmbase, classname")
324  for row in rows:
325  classname, edmfamily, edmbase = row
326  if not edmfamily: edmfamily = "<em>legacy</em>"
327  out.append(f' <li>{edmfamily}::{edmbase} <a href="/plugin/{classname}/">{classname}</a></li>')
328  out.append("</ul>")
329  return out
330 
331  def showseq(step, seqname):
332  # display set of sequences sharing a name, also used on the index page.
333  out = []
334  cur = db.cursor()
335  out.append(f' <a href="/seq/{step}:{seqname}/">{step}:{seqname}</a>')
336  # this is much more complicated than it should be since we don't keep
337  # track which sequences have equal contents in the DB. So the deduplication
338  # has to happen in Python code.
339  rows = cur.execute(f"SELECT {SEQFIELDS}, moduleid, id FROM sequence INNER JOIN sequencemodule ON sequenceid = id WHERE seqname = ? and step = ?;", (seqname, step))
340 
341  seqs = defaultdict(list)
342  ids = dict()
343  for row in rows:
344  seq = Sequence(*row[:-2])
345  seqs[seq].append(row[-2])
346  ids[seq] = row[-1]
347 
348  variations = defaultdict(list)
349  for seq, mods in seqs.items():
350  variations[tuple(sorted(mods))].append(seq)
351 
352  out.append(" <ul>")
353  for mods, seqs in variations.items():
354  count = len(mods)
355  out.append(f' <li>({count} modules):')
356  for seq in seqs:
357  seqid = ids[seq]
358  out.append(f'<br><a href="/seqid/{seqid}">' + formatseq(seq) + '</a>')
359  # This query in a loop is rather slow, but this got complictated enough, so YOLO.
360  rows = cur.execute("SELECT wfid FROM workflow WHERE sequenceid = ?;", (seqid,))
361  out.append(f'<em>Used on workflows: ' + ", ".join(wfid for wfid, in rows) + "</em>")
362  out.append(' </li>')
363  out.append(" </ul>")
364  return out
365 
366  def showseqid(seqid):
367  # display a single, unique sequence.
368  seqid = int(seqid)
369  out = []
370  cur = db.cursor()
371  rows = cur.execute(f"SELECT {SEQFIELDS} FROM sequence WHERE id = ?;", (seqid,))
372  seq = formatseq(Sequence(*list(rows)[0]))
373  out.append(f"<h2>Modules on {seq}:</h2><ul>")
374  rows = cur.execute("SELECT wfid FROM workflow WHERE sequenceid = ?;", (seqid,))
375  out.append("<p><em>Used on workflows: " + ", ".join(wfid for wfid, in rows) + "</em></p>")
376  rows = cur.execute("""
377  SELECT classname, instancename, variation, moduleid
378  FROM sequencemodule INNER JOIN module ON moduleid = module.id
379  WHERE sequenceid = ?;""", (seqid,))
380  for row in rows:
381  classname, instancename, variation, moduleid = row
382  out.append(f'<li>{instancename} ' + (f'<sub>{variation}</sub>' if variation else '') + f' : <a href="/plugin/{classname}/">{classname}</a></li>')
383  out.append("</ul>")
384 
385  return out
386 
387  def showclass(classname):
388  # display all known instances of a class and where they are used.
389  # this suffers a bit from the fact that fully identifying a sequence is
390  # rather hard, we just show step/name here.
391  out = []
392  out.append(f"<h2>Plugin {classname}</h2>")
393  cur = db.cursor()
394  # First, info about the class iself.
395  rows = cur.execute("SELECT edmfamily, edmbase FROM plugin WHERE classname = ?;", (classname,))
396  edmfamily, edmbase = list(rows)[0]
397  islegcay = not edmfamily
398  if islegcay: edmfamily = "<em>legacy</em>"
399  out.append(f"<p>{classname} is a <b>{edmfamily}::{edmbase}</b>.</p>")
400  out.append("""<p>A module with a given label can have different configuration depending on options such as Era,
401  Scenario, Data vs. MC etc. If multiple configurations for the same name were found, they are listed separately
402  here and denoted using subscripts.</p>""")
403  if (edmbase != "EDProducer" and not (islegcay and edmbase == "EDAnalyzer")) or (islegcay and edmbase == "EDProducer"):
404  out.append(f"<p>This is not a DQM module.</p>")
405 
406  # then, its instances.
407  rows = cur.execute("""
408  SELECT module.id, instancename, variation, sequenceid, step, seqname
409  FROM module INNER JOIN sequencemodule ON moduleid = module.id INNER JOIN sequence ON sequence.id == sequenceid
410  WHERE classname = ? ORDER BY instancename, variation, step, seqname;""", (classname,))
411  out.append("<ul>")
412  seqsformod = defaultdict(list)
413  liformod = dict()
414  for row in rows:
415  id, instancename, variation, sequenceid, step, seqname = row
416  liformod[id] = f'<a href="/config/{id}">{instancename}' + (f"<sub>{variation}</sub>" if variation else '') + "</a>"
417  seqsformod[id].append((sequenceid, f"{step}:{seqname}"))
418  for id, li in liformod.items():
419  out.append("<li>" + li + ' Used here: ' + ", ".join(f'<a href="/seqid/{seqid}">{name}</a>' for seqid, name in seqsformod[id]) + '.</li>')
420  out.append("</ul>")
421  return out
422 
423  def showconfig(modid):
424  # finally, just dump the config of a specific module. Useful to do "diff" on it.
425  modid = int(modid)
426  out = []
427  cur = db.cursor()
428  rows = cur.execute(f"SELECT config FROM module WHERE id = ?;", (modid,))
429  config = list(rows)[0][0]
430  out.append("<pre>")
431  out.append(config.decode())
432  out.append("</pre>")
433  return out
434 
435  ROUTES = [
436  (re.compile('/$'), index),
437  (re.compile('/seq/(\w+):([@\w]*)/$'), showseq),
438  (re.compile('/seqid/(\d+)$'), showseqid),
439  (re.compile('/config/(\d+)$'), showconfig),
440  (re.compile('/plugin/(.*)/$'), showclass),
441  ]
442 
443  # the server boilerplate.
444  class Handler(http.server.SimpleHTTPRequestHandler):
445  def do_GET(self):
446  try:
447  res = None
448  for pattern, func in ROUTES:
449  m = pattern.match(self.path)
450  if m:
451  res = "\n".join(func(*m.groups())).encode("utf8")
452  break
453 
454  if res:
455  self.send_response(200, "Here you go")
456  self.send_header("Content-Type", "text/html; charset=utf-8")
457  self.end_headers()
458  self.wfile.write(b"""<html><style>
459  body {
460  font-family: sans;
461  }
462  </style><body>""")
463  self.wfile.write(res)
464  self.wfile.write(b"</body></html>")
465  else:
466  self.send_response(400, "Something went wrong")
467  self.send_header("Content-Type", "text/plain; charset=utf-8")
468  self.end_headers()
469  self.wfile.write(b"I don't understand this request.")
470  except:
471  trace = traceback.format_exc()
472  self.send_response(500, "Things went very wrong")
473  self.send_header("Content-Type", "text/plain; charset=utf-8")
474  self.end_headers()
475  self.wfile.write(trace.encode("utf8"))
476 
477  server_address = ('', 8000)
478  httpd = http.server.HTTPServer(server_address, Handler)
479  print("Serving at http://localhost:8000/ ...")
480  httpd.serve_forever()
481 
482 
483 if __name__ == "__main__":
484 
485  import argparse
486  parser = argparse.ArgumentParser(description='Collect information about DQM sequences.')
487  parser.add_argument("--sequence", default="", help="Name of the sequence")
488  parser.add_argument("--step", default="DQM", help="cmsDriver step that the sequence applies to")
489  parser.add_argument("--era", default="Run2_2018", help="CMSSW Era to use")
490  parser.add_argument("--scenario", default="pp", help="cmsDriver scenario")
491  parser.add_argument("--data", default=False, action="store_true", help="Pass --data to cmsDriver.")
492  parser.add_argument("--mc", default=False, action="store_true", help="Pass --mc to cmsDriver.")
493  parser.add_argument("--fast", default=False, action="store_true", help="Pass --fast to cmsDriver.")
494  parser.add_argument("--workflow", default=None, help="Ignore other options and inspect this workflow instead (implies --sqlite).")
495  parser.add_argument("--runTheMatrix", default=False, action="store_true", help="Ignore other options and inspect the full matrix instea (implies --sqlite).")
496  parser.add_argument("--steps", default="ALCA,ALCAPRODUCER,ALCAHARVEST,DQM,HARVESTING,VALIDATION", help="Which workflow steps to inspect from runTheMatrix.")
497  parser.add_argument("--sqlite", default=False, action="store_true", help="Write information to SQLite DB instead of stdout.")
498  parser.add_argument("--dbfile", default="sequences.db", help="Name of the DB file to use.")
499  parser.add_argument("--infile", default=INFILE, help="LFN/PFN of input file to use. Default is %s" % INFILE)
500  parser.add_argument("--threads", default=None, type=int, help="Use a fixed number of threads (default is #cores).")
501  parser.add_argument("--limit", default=None, type=int, help="Process only this many sequences.")
502  parser.add_argument("--offset", default=None, type=int, help="Process sequences starting from this index. Used with --limit to divide the work into jobs.")
503  parser.add_argument("--showpluginlabel", default=False, action="store_true", help="Print the module label for each plugin (default).")
504  parser.add_argument("--showplugintype", default=False, action="store_true", help="Print the base class for each plugin.")
505  parser.add_argument("--showpluginclass", default=False, action="store_true", help="Print the class name for each plugin.")
506  parser.add_argument("--showpluginconfig", default=False, action="store_true", help="Print the config dump for each plugin.")
507  parser.add_argument("--serve", default=False, action="store_true", help="Ignore other options and instead serve HTML UI from SQLite DB.")
508 
509  args = parser.parse_args()
510 
511  RELEVANTSTEPS += args.steps.split(",")
512  DBFILE = args.dbfile
513 
514  if args.threads:
515  tp = ThreadPool(args.threads)
516  stp = ThreadPool(args.threads)
517 
518  INFILE = args.infile
519  if args.serve:
520  serve()
521  elif args.workflow or args.runTheMatrix:
522  # the default workflow None is a magic value for inspectworkflows.
523  seqs = inspectworkflows(args.workflow)
524  seqset = set(sum(seqs.values(), []))
525  if args.offset:
526  seqset = list(sorted(seqset))[args.offset:]
527  if args.limit:
528  seqset = list(sorted(seqset))[:args.limit]
529 
530  print("Analyzing %d seqs..." % len(seqset))
531 
532  processseqs(seqset)
533  storeworkflows(seqs)
534  else:
535  # single sequence with arguments from commandline...
536  seq = Sequence(args.sequence, args.step, args.era, args.scenario, args.mc, args.data, args.fast)
537  modconfig, modclass, plugininfo = inspectsequence(seq)
538  if args.sqlite:
539  storesequenceinfo(seq, modconfig, modclass, plugininfo)
540  else:
541  # ... and output to stdout.
542  if not (args.showpluginlabel or args.showpluginclass or args.showplugintype or args.showpluginconfig):
543  args.showpluginlabel = True
544  formatsequenceinfo(modconfig, modclass, plugininfo, args.showpluginlabel, args.showpluginclass, args.showplugintype, args.showpluginconfig)
def inspectworkflows(wfnumber)
void print(TMatrixD &m, const char *label=nullptr, bool mathematicaFormat=false)
Definition: Utilities.cc:47
def storesequenceinfo(seq, modconfig, modclass, plugininfo)
def formatsequenceinfo(modconfig, modclass, plugininfo, showlabel, showclass, showtype, showconfig)
static std::string join(char **cmd)
Definition: RemoteFile.cc:21
def encode(args, files)
bool decode(bool &, std::string_view)
Definition: types.cc:72
#define str(s)
def getplugininfo(pluginname)