From 02645fc8b7530828de329b37f695c11776023c65 Mon Sep 17 00:00:00 2001 From: Eric Torrence <eric.torrence@cern.ch> Date: Fri, 13 Sep 2019 08:49:36 -0700 Subject: [PATCH] First commit of the offline monitoring code --- Monitoring/CMakeLists.txt | 15 ++ Monitoring/python/LegacySelector.py | 65 ++++++ Monitoring/python/Monitor1DValue.py | 200 ++++++++++++++++ Monitoring/python/MonitorValueBase.py | 253 +++++++++++++++++++++ Monitoring/python/MonitorValueManager.py | 200 ++++++++++++++++ Monitoring/python/README | 11 + Monitoring/python/__init__.py | 2 + Monitoring/share/simpleMonitoringTest.json | 106 +++++++++ Monitoring/share/simpleMonitoringTest.py | 41 ++++ 9 files changed, 893 insertions(+) create mode 100644 Monitoring/CMakeLists.txt create mode 100644 Monitoring/python/LegacySelector.py create mode 100755 Monitoring/python/Monitor1DValue.py create mode 100644 Monitoring/python/MonitorValueBase.py create mode 100755 Monitoring/python/MonitorValueManager.py create mode 100644 Monitoring/python/README create mode 100644 Monitoring/python/__init__.py create mode 100644 Monitoring/share/simpleMonitoringTest.json create mode 100755 Monitoring/share/simpleMonitoringTest.py diff --git a/Monitoring/CMakeLists.txt b/Monitoring/CMakeLists.txt new file mode 100644 index 00000000..7e33f8b8 --- /dev/null +++ b/Monitoring/CMakeLists.txt @@ -0,0 +1,15 @@ +################################################################################ +# Package: Monitoring +# Contains python code to implement offline monitoring +################################################################################ + + +# Declare the package name: +atlas_subdir( Monitoring ) + +# External dependencies: +find_package( ROOT COMPONENTS Core Tree ) + +# Install files from the package: +atlas_install_python_modules( python/*.py ) +atlas_install_scripts( share/*.py ) diff --git a/Monitoring/python/LegacySelector.py b/Monitoring/python/LegacySelector.py new file mode 100644 index 00000000..0481d7e6 --- /dev/null +++ b/Monitoring/python/LegacySelector.py @@ -0,0 +1,65 @@ +# +# LegacySelector.py +# +# Class to select values from legacy ntuples +# +# E.Torrence, Sept. 2019 +# +# +# Use Python3-style print statements +from __future__ import print_function + +class LegacySelector (): + """Class to select values from Legacy ntuple""" + + def __init__(self): + """Constructor""" + + self.conf = None + + # Pass MonitorValue conf object + def init(self, conf): + """Initialization""" + + # Just save full conf object + self.conf = conf + + # Return list of values from data object event + def get1DValue(self, event, varName): + + # print ("LegacySelector.get1DValue called for", varName) + + values = [] + + #varName = self.conf.xvar + + # Variable name should first give the list, followed by the specific parameter + # Only the event number doesn't follow this pattern + if varName == 'fEventNumber': + # OK, this is weird, event is actually the tree, while event.event is the FaserEvent class. + # For the lists, we can get them directly from event... + return event.event.GetEventNumber() + + theListName = varName.split('.')[0] + + theList = getattr(event, theListName) + + # Loop over truth list + for obj in theList: + # Try to parse the variable string + # Find chunks separated by a point + # Each is either a method or an attribute + theObj = obj + varList = self.conf.xvar.split('.')[1:] # Skip the list [1:] + for varName in varList: + # Is this an attribute or a method? + if callable(getattr(theObj, varName)): + theObj = getattr(theObj, varName)() # Method (function) + else: + theObj = getattr(theObj, varName) # Attribute (value) + + # Once we have walked through the list, have our value + values.append(theObj) + + + return values diff --git a/Monitoring/python/Monitor1DValue.py b/Monitoring/python/Monitor1DValue.py new file mode 100755 index 00000000..1f43dc62 --- /dev/null +++ b/Monitoring/python/Monitor1DValue.py @@ -0,0 +1,200 @@ +#!/bin/env python +# +# Monitor1DValue.py +# +# Defines classes to implement monitored values +# +# Classes +# Monitor1DValue +# Monitor1DValueConfig +# +# E.Torrence, Aug. 2019 +# +# +# Use Python3-style print statements +from __future__ import print_function + +import sys +import ROOT + +from Monitoring.MonitorValueBase import MonitorValueBase, MonitorConfigBase + +# +# 1D Monitoring class and configuration +# +class Monitor1DValueConfig(MonitorConfigBase): + """Configuration for Monitor1DValue""" + + # Everything in this configuration must be simple values so JSON works + + def __init__(self): + """Constructor providing default values""" + + MonitorConfigBase.__init__(self) + # Defines tag, type, and desc + + # Variable + self.selector = None # Text string describing selector class + + self.xvar = None # Text string describing value to fill + + # Scale factor + self.xscale = 1 + + # Histogram parameters + self.nbins = 0 + self.xlo = 0. + self.xhi = 0. + self.xlabel = "" + self.ylabel = "" + + # Plot under/overflows + self.overflow = True + + # Draw as linear or log scale by default + self.logy = False + + # Utility function to make it easier to define hist parameters + def setBinning(self, nbins, xlo, xhi, xlabel="", ylabel=""): + """Define histogram bins and range""" + + # Utility function to set histogram parameters in one go + self.nbins = nbins + self.xlo = xlo + self.xhi = xhi + self.xlabel = xlabel + self.ylabel = ylabel + + +class Monitor1DValue (MonitorValueBase): + """One dimensional monitored value class""" + + def __init__(self, tag=''): + """Constructor, tag is a unique identification string""" + + # Instantiate the configuration object + self.conf = Monitor1DValueConfig() + + # Set the tag if provided + self.conf.tag = tag + + def init(self): + """Initialize class and create histogram""" + + # First call the base class, which checks a few things + MonitorValueBase.init(self) + + # Now create our histogram with the parameters in self.conf + # Check if we want to store integers or floats + if isinstance(self.conf.xlo, int) and isinstance(self.conf.xhi, int): + self.hist = ROOT.TH1I(self.conf.tag+"_h", "", + self.conf.nbins, self.conf.xlo, self.conf.xhi) + else: + self.hist = ROOT.TH1D(self.conf.tag+"_h", "", + self.conf.nbins, self.conf.xlo, self.conf.xhi) + + # Also set labels + self.hist.GetXaxis().SetTitle(self.conf.xlabel) + self.hist.GetYaxis().SetTitle(self.conf.ylabel) + + def fill(self, event): + """Fill histogram from event object""" + + self.fillValue(self.selector.get1DValue(event, self.conf.xvar)) + + #varstr = self.conf.xvar + #theVar = getattr(event, varstr) + #self.fillValue(theVar) + + def fillValue(self, val): + """Fill histogram with one or more values""" + + #print ("Monitor1DValue.fillValue called with", val) + + # Check if this is a single value or an iterable sequence + if hasattr(val, '__iter__'): + for v in val: + self.hist.Fill(v*self.conf.xscale) + else: + self.hist.Fill(val*self.conf.xscale) + + + def plot(self, c): + """Plot histogram to canvas c""" + + # Log or lin? + if self.conf.logy: + c.SetLogy(True) + else: + c.SetLogy(False) + + # Do something fancy to draw the overflow? + if self.conf.overflow: + # Must make a new histogram and fill under/overflow in first/last bins + h = self.hist.Clone() + nb = h.GetNbinsX() + + under = h.GetBinContent(0) + h.SetBinContent(1, h.GetBinContent(1)+under) + over = h.GetBinContent(nb+1) + h.SetBinContent(nb, h.GetBinContent(nb)+over) + + else: + h = self.hist + + # Draw with default parameters + h.DrawCopy() + +# Testing code +if __name__ == '__main__': + + mv1 = Monitor1DValue('test1') + mv1.conf.setBinning(30, -3., 3.) + mv1.init() + + mv2 = Monitor1DValue('test2') + mv2.conf.setBinning(30, -3., 3.) + mv2.init() + + # Fill a list + import random + values = [random.gauss(0., 1.) for x in range(100)] + mv1.fill(values) + + # Try one more + mv1.fill(random.gauss(0., 1.)) + + # Print something out + mv1.dump() + + # Write to file + print() + print("Test writing to ROOT file") + tf = ROOT.TFile("test.root", "recreate") + mv1.writeRoot(tf) + mv2.writeRoot(tf) + #writeMonitorValue(tf, mv1) + #writeMonitorValue(tf, mv2) + tf.Close() + + print() + print("Test reading from ROOT file") + tf = ROOT.TFile('test.root') + + print() + print("Directory listing") + tf.ls() + + from Monitoring.MonitorValueBase import readMonitorValueFromRoot + + print() + print("Read objects back from ROOT file") + mvr1 = readMonitorValueFromRoot(tf, "test1") + mvr1.dump() + mvr2 = readMonitorValueFromRoot(tf, "test2") + mvr2.dump() + try: + readMonitorValueFromRoot(tf, "test3") + except Exception as e: + print(e) + diff --git a/Monitoring/python/MonitorValueBase.py b/Monitoring/python/MonitorValueBase.py new file mode 100644 index 00000000..82220c35 --- /dev/null +++ b/Monitoring/python/MonitorValueBase.py @@ -0,0 +1,253 @@ +# +# MonitorValueBase.py +# +# Defines base classes to implement monitored values +# +# Functions +# readMonitorValueFromRoot - read MonitorValue from ROOT file +# monitorValueFromJSON - construct MonitorValue from JSON +# +# Classes +# MonitorValueBase +# MonitorConfigBase +# +# E.Torrence, Aug. 2019 +# +# +# Use Python3-style print statements +from __future__ import print_function + +import sys +import ROOT +import json + +# Utility function to create object instance from ROOT file +def readMonitorValueFromRoot(f, tag): + """Read class named tag from open root file f""" + + # Objects stored in subdirectory, make sure it exists + if not f.cd(tag): + print("Object", tag, "not present in file!") + f.ls() + raise ObjectNotFoundError("Error: directory not found for object "+tag+"!") + + #print ("Changed to directory", tag) + #f.ls() + + # Read histogram first + histName = tag+"_h" + #print("Reading histogram", histName) + hist = ROOT.gROOT.FindObject(histName) + if hist is None: + f.cd("..") + raise ObjectNotFoundError("Error: unable to read histogram", histName,"!") + + # Read JSON string stored in a TNamed object + objectName = tag+"_j" + tobj = ROOT.gROOT.FindObject(objectName) + if tobj is None: + f.cd("..") + raise ObjectNotFoundError("Error: unable to read JSON from",objectName, "!") + + # Get JSON string from TNamed + jsonString = tobj.GetTitle() + + #print(jsonString) + #obj = pickle.loads(unicode(pickleString.GetTitle(), 'utf-8')) + + # Call our utility function to create a new (un-initalized) object + obj = monitorValueFromJSON(json.loads(jsonString)) + + # And put the histogram back + obj.hist = hist + + # Return to the parent directory in the ROOT file + f.cd("..") + + # Return the recreated object + return obj + + +# Utility to create a new class from a JSON description +# This does not initialize the class +def monitorValueFromJSON(jsonDict): + + # jsonDict = json.loads(jsonString) + + # Get the object class name + className = jsonDict.get('type', None) + if className is None: + print ("monitorValueFromJSON: no class type specified!") + return None + + # Find the class object in our module + # This is some voodoo + # Monitoring is the package name + # className is both the class but also the submodule to search in + # The fromlist=[] needs to specify the submodules + # So this works as long as each MV type has a unique module + # Need a better way to do this (perhaps using __init__.py? + class_ = getattr(__import__("Monitoring."+className, fromlist=[className]), className) + + # Instantiate this class + obj = class_() + + if obj is None: + f.cd("..") + raise ObjectNotFoundError("Error: unable to instantiate object", objectName, "!") + + # Update the configuration from the JSON file + obj.conf.fromJSON(jsonDict) + + # Also instantiate the selector + className = jsonDict.get('selector', None) + + if className is None: + obj.selector = None + print ("monitorValueFromJSON: No selector specified for value", jsonDict["tag"]) + + else: + # Find the class name in our module + class_ = getattr(__import__("Monitoring."+className, fromlist=[className]), className) + # And instantiate this class + obj.selector = class_() + + if obj.selector is None: + raise ObjectNotFoundError("Error: unable to instantiate object", objectName, "!") + + # Done with selector object + + # Don't call obj.init here, make user do this + return obj + +# Error in case histogram can't be read +class ObjectNotFoundError(KeyError): + pass + +class MonitorValueBase (): + """Base class for monitored values""" + + def __init__(self): + """Dummy Constructor""" + + # Configuration object to store all needed parameters + self.conf = None + + # Histogram (or other ROOT class) containing data + self.hist = None + + # Selector class to fill histogram + self.selector = None + + # Common init functions + def init(self): + + if self.conf is None: + print ("MonitorValueBase: init() called with no valid configuration!") + return + + # Set type if not configured + actualType = self.__class__.__name__ + if self.conf.type == None: + self.conf.type = actualType + + # Check that type is correct + elif actualType != self.conf.type: + print ("actualType", actualType, "does not match configured type", self.conf.type,"!") + print ("...overriding conf.type") + self.conf.type = actualType + + # Initialize the selector + if self.selector is not None: + self.selector.init(self.conf) + + + def fill(self, v): + # Dummy fill function + pass + + def plot(self, c): + # Dummy plot function, must pass canvas in case we want to make log scale + pass + + def dump(self): + """Write histogram to specified stream (stdout by default)""" + + #print("Dump called for", self.conf.tag) + print(self.conf) + if self.hist is not None: + self.hist.Print() + + def __str__(self): + return str(self.conf) + + def writeRoot(self, f): + """Write hist and conf to open root file f""" + + # Make a subdirectory to contain the components and switch + cwd = f.mkdir(self.conf.tag) + cwd.cd() + + # Write the histogram first (if it isn't NULL) + if self.hist is not None: + self.hist.Write() + + # Also write the conf JSON to TString + # ROOT name is tag_p + jsonString = self.conf.toJSON() + ts = ROOT.TNamed(self.conf.tag+"_j", jsonString) + ts.Write() + + # Also write pickled object to TString + # pickleString = pickle.dumps(self) + # ts = ROOT.TNamed(self.tag+"_p", pickleString.decode("utf-8")) + # ts.Write() + + # Now go back to starting directory to not screw things up + cwd.cd("..") + + # These aren't needed any more + # Remove hist from elements to pickle (we save this separately) + def __getstate__(self): + state = self.__dict__.copy() + # Don't pickle hist + del state["hist"] + return state + + def __setstate__(self, state): + self.__dict__.update(state) + # Add hist back since it doesn't exist in the pickle + self.hist = None + +# +# Base class for configuration objects +class MonitorConfigBase: + + def __init__(self): + """Constructor provides default values""" + self.tag = "" # Unique identifier string + self.type = None # Class name of instantiated MonitorValue object + self.desc = "" # Helpful description string + + def toJSON(self, indentLevel=0): + """Return JSON string describing values in this class""" + return json.dumps(self, default=lambda o: o.__dict__, + sort_keys=True, indent=indentLevel) + + def fromJSONStr(self, jsonString): + """Update class attributes using values read in from JSON string""" + + # Note, values not specified will not be changed + jsonDict = json.loads(jsonString) + self.fromJSON(jsonDict) + + def fromJSON(self, jsonDict): + """Update class attributes using values in parsed JSON dict""" + + # This will only work for simple parameters + for (key, val) in jsonDict.iteritems(): + setattr(self, key, val) + + def __str__(self): + return self.toJSON(indentLevel=2) + diff --git a/Monitoring/python/MonitorValueManager.py b/Monitoring/python/MonitorValueManager.py new file mode 100755 index 00000000..af869e22 --- /dev/null +++ b/Monitoring/python/MonitorValueManager.py @@ -0,0 +1,200 @@ +#!/bin/env python +# +# MonitorValueManager.py +# +# Defines the MonitorValueManager class that coordinates actions +# for a large set of instantiated MonitorValue objects +# +# Classes +# MonitorValueManager +# +# E.Torrence, Aug. 2019 +# +# Use python3-style print statements +from __future__ import print_function + +import ROOT +import json + +from Monitoring.MonitorValueBase import monitorValueFromJSON + +# Need to import all defined MV types so we can find them below +from Monitoring.Monitor1DValue import Monitor1DValue + + +class MonitorValueManager: + """Class to instantiate and manage a large set of MonitorValue objects""" + + def __init__(self): + """Constructor""" + + # Flag to print out some monitoring information + self.debug = True + + # dict containing all MonitorValues, tags are used for keys + self.mvalDict = dict() + + # Key list, used to keep order straight + self.tagList = [] + + def addValue(self, val): + """Add an instantiated MonitorValue to the dictionary""" + + tag = val.conf.tag + if tag in self.mvalDict.keys(): + print("Value with tag", tag,"already exists!") + print(self.mvalDict[tag].conf) + return + + self.mvalDict[tag] = val + self.tagList.append(tag) + + def initValues(self): + """Call the init method on all stored MVs""" + # Loop through all MonitorValues defined and call the init() function + for val in self.mvalDict.itervalues(): + if (self.debug): + print("Initializing value", val.conf.tag) + + val.init() + + def fillValues(self, event): + """Call the fill method on all stored MVs""" + for val in self.mvalDict.itervalues(): + val.fill(event) + + def writeValues(self, filename): + """Write out value histograms to file""" + + # Simplest implementation for now + # Add some directory structure later + f = ROOT.TFile(filename, "RECREATE") + + for val in self.mvalDict.itervalues(): + val.writeRoot(f) + + # Cleanup + f.Close() + + def plotValuesToPDF(self, filename): + """Call the plot methods with output going to a PDF file""" + + # Lets see if this works + c = ROOT.TCanvas() + c.Print(filename+"[") + + for tag in self.tagList: + val = self.mvalDict[tag] + val.plot(c) + c.Print(filename) + + c.Print(filename+"]") + + def createValuesFromJSON(self, filename): + """ Create values from JSON file of name filename""" + + if self.debug: print("Opening JSON file", filename) + # Open file + try: + f = open(filename) + except Exception as e: + print("Error opening file", filename) + print(e) + return + + # Parse JSON + if self.debug: print("Parsing JSON") + jsonList = json.load(f) + + # Close the file + f.close() + + # Keep track of what we are doing + count = 0 + if self.debug: print("Creating objects from JSON") + for item in jsonList: + try: + newval = monitorValueFromJSON(item) + except Exception as e: + # On error, print error and continue + print("MonitorValueManager.createValuesFromJSON - Error creating object", item, "!") + print(e) + continue + + self.addValue(newval) + count += 1 + + # Print out some results + if (self.debug): + print("Created", count, "new values") + print("Full list of MonitorValue tags:") + for key in sorted(self.mvalDict.keys()): + print(key) + + def listValues(self): + """Print out type and tags for all defined values""" + + + print ("Listing of MonitorValues") + + if len(self.mvalDict) == 0: + print("Value list is empty!") + return + + + print("Tag ValueType") + print("--- ---------") + for tag in self.tagList: + print(tag, self.mvalDict[tag].conf.type) + + def dumpValues(self): + """Print out verbose information for all defined values""" + + if len(self.mvalDict) == 0: + print("Value list is empty!") + return + + for tag in self.tagList: # Sort to get alphabetical listing + print() + print(tag, self.mvalDict[tag].conf.type) + self.mvalDict[tag].dump() + + def writeJSON(self, stream): + """Write out JSON for all configured objects to specified stream""" + + jsonList = [] + for tag in self.tagList: + val = self.mvalDict[tag] + jsonList.append(val.conf) + + json.dump(jsonList, stream, default=lambda o: o.__dict__, + sort_keys=True, indent=2) + +# Testing code +if __name__ == '__main__': + + from Monitoring.Monitor1DValue import Monitor1DValue + + mv1 = Monitor1DValue('test1') + mv1.conf.setBinning(30, -3., 3.) + + mv2 = Monitor1DValue('test2') + mv2.conf.setBinning(60, -3., 3.) + + mvm = MonitorValueManager() + + mvm.addValue(mv1) # Add already defined mv + mvm.addValue(mv2) + mvm.initValues() # Initialize all values + mvm.listValues() # Brief printout of all values + mvm.dumpValues() + + + import sys + mvm.writeJSON(sys.stdout) + print() + + mvm.createValuesFromJSON('test.json') + + mvm.listValues() + diff --git a/Monitoring/python/README b/Monitoring/python/README new file mode 100644 index 00000000..fb7bb8fe --- /dev/null +++ b/Monitoring/python/README @@ -0,0 +1,11 @@ +Things to do: +-) Add some structure to the JSON config file, in particular, create + some kind of set structure that will be reflected in ROOT file, + but also groups monitored quantities by type. + +-) Remove JSON text from ROOT file (or store in separate structure) + +-) Figure out how to read ntuple variables. Possibly have a 'handler' by + data type, i.e. TruthParticle or SpacePoints. These will know how to + decode variables by simple string, and also might have their own config + parameters to be set. diff --git a/Monitoring/python/__init__.py b/Monitoring/python/__init__.py new file mode 100644 index 00000000..447ad57d --- /dev/null +++ b/Monitoring/python/__init__.py @@ -0,0 +1,2 @@ +# +__author__ = "Eric Torrence" diff --git a/Monitoring/share/simpleMonitoringTest.json b/Monitoring/share/simpleMonitoringTest.json new file mode 100644 index 00000000..60e8fe25 --- /dev/null +++ b/Monitoring/share/simpleMonitoringTest.json @@ -0,0 +1,106 @@ +[ + { + "tag": "eventNumber", + "type": "Monitor1DValue", + "desc": "Event Number", + "selector": "LegacySelector", + "xvar": "fEventNumber", + "nbins": 1000, + "xlo": 0, + "xhi": 10000, + "xlabel": "Event Number", + "ylabel": "Entries", + "overflow": true, + "logy": false + }, + { + "tag": "pdgCode", + "type": "Monitor1DValue", + "desc": "Truth particle PDG Code", + "selector": "LegacySelector", + "xvar": "fParticles.PdgCode", + "nbins": 53, + "xlo": -12, + "xhi": 40, + "xlabel": "PDG Code", + "ylabel": "Entries", + "overflow": true, + "logy": true + }, + { + "tag": "truthEnergy", + "type": "Monitor1DValue", + "desc": "Truth particle energy", + "selector": "LegacySelector", + "xvar": "fParticles.Energy", + "xscale": 0.001, + "nbins": 50, + "xlo": 0.0, + "xhi": 1000.0, + "xlabel": "Energy (GeV)", + "ylabel": "Entries", + "overflow": true, + "logy": false + }, + { + "tag": "truthVertexX", + "type": "Monitor1DValue", + "desc": "Truth particle X Vertex", + "selector": "LegacySelector", + "xvar": "fParticles.Vertex.x", + "xscale": 0.001, + "nbins": 50, + "xlo": -0.5, + "xhi": 0.5, + "xlabel": "Truth Vertex X (m)", + "ylabel": "Entries", + "overflow": true, + "logy": false + }, + { + "tag": "truthVertexY", + "type": "Monitor1DValue", + "desc": "Truth particle Y Vertex", + "selector": "LegacySelector", + "xvar": "fParticles.Vertex.y", + "xscale": 0.001, + "nbins": 50, + "xlo": -0.5, + "xhi": 0.5, + "xlabel": "Truth Vertex Y (m)", + "ylabel": "Entries", + "overflow": true, + "logy": false + }, + { + "tag": "truthVertexZ", + "type": "Monitor1DValue", + "desc": "Truth particle Z Vertex", + "selector": "LegacySelector", + "xvar": "fParticles.Vertex.z", + "xscale": 0.001, + "nbins": 50, + "xlo": -2.5, + "xhi": 2.5, + "xlabel": "Truth Vertex Z (m)", + "ylabel": "Entries", + "overflow": true, + "logy": false + }, + + { + "tag": "trackerDigiPlane", + "type": "Monitor1DValue", + "desc": "Tracker Digi Plane", + "selector": "LegacySelector", + "xvar": "fTrackerDigis.Plane", + "nbins": 11, + "xlo": -1, + "xhi": 10, + "xlabel": "Plane", + "ylabel": "Digis", + "overflow": true, + "logy": false + } + +] \ No newline at end of file diff --git a/Monitoring/share/simpleMonitoringTest.py b/Monitoring/share/simpleMonitoringTest.py new file mode 100755 index 00000000..f33301e0 --- /dev/null +++ b/Monitoring/share/simpleMonitoringTest.py @@ -0,0 +1,41 @@ +#!/bin/env python +# +# simpleMonitoringTest.py +# +# Example of simple monitoring job using hte MonitorValueManager +# Need JSON file simpleMonitoringTest.json and a TDR-style +# ntuple in the run directory +# +# E. Torrence, Aug. 2019 +# +# Use python3-style print statements +from __future__ import print_function + +from Monitoring.MonitorValueManager import MonitorValueManager + +import ROOT + +# Start by instantiating mvm +mvm = MonitorValueManager() + +# Load the configuration +mvm.createValuesFromJSON('simpleMonitoringTest.json') +mvm.initValues() +# mvm.listValues() # Check what we have +mvm.dumpValues() # Check what we have + +# Open ntuple +tf = ROOT.TFile('ntuple.root') +tt = tf.Get('faser') + +# Loop over events +for event in tt: + mvm.fillValues(event) + +tf.Close() + +# Write to file +mvm.writeValues('simpleMonitoringTest.root') + +# Plot values +mvm.plotValuesToPDF('simpleMonitoringTest.pdf') -- GitLab