diff --git a/Monitoring/CMakeLists.txt b/Monitoring/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..7e33f8b89c3ccf3c64f6f0501a0b0fa3a65398f6
--- /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 0000000000000000000000000000000000000000..0481d7e6e7181bf88d84df41e59706b6b644afa5
--- /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 0000000000000000000000000000000000000000..1f43dc623bb5114293cf61bb2ec61a40c9a8e10e
--- /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 0000000000000000000000000000000000000000..82220c35ca5d513952bb1fd71ecac5cf4f38fd96
--- /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 0000000000000000000000000000000000000000..af869e229b78400ef924d123a3768f51988ff959
--- /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 0000000000000000000000000000000000000000..fb7bb8fe124c876a975384381d9c177cac6bbfa0
--- /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 0000000000000000000000000000000000000000..447ad57d59139400d95dde3451518bc23c49b705
--- /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 0000000000000000000000000000000000000000..60e8fe2505d2447789a5e6077540cc5bbc810454
--- /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 0000000000000000000000000000000000000000..f33301e0deea4a9b0142b5826d7e046126c56063
--- /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')