docs: def run( self ):
          """Run the experiment, using the parameters set using :meth:`set`.
          A "run" consists of calling :meth:`setUp`, :meth:`do`, and :meth:`tearDown`,
          followed by collecting and storing (and returning) the
          experiment's results. If running the experiment raises an
          exception, that will be returned in the metadata along with
          its traceback to help with experiment debugging.
  
          :returns: a :term:`results dict`"""
          
          # perform the experiment protocol
          params = self.parameters()
          self._metadata = dict()
          self._results = None
          res = None
          doneSetupTime = doneExperimentTime = doneTeardownTime = None
          try:
              # do the phases in order, recording the wallclock times at each phase
              startTime = datetime.now()
              self.setUp(params)
              doneSetupTime = datetime.now()
              res = self.do(params)
              doneExperimentTime = datetime.now() 
              self.tearDown()
              doneTeardownTime = datetime.now() 
              
              # record the various timings
              self._metadata[self.START_TIME] = startTime
              self._metadata[self.END_TIME] = doneTeardownTime
              self._metadata[self.ELAPSED_TIME] = (doneTeardownTime - startTime).total_seconds()
              self._metadata[self.SETUP_TIME] = (doneSetupTime - startTime).total_seconds()
              self._metadata[self.EXPERIMENT_TIME] = (doneExperimentTime - doneSetupTime).total_seconds()
              self._metadata[self.TEARDOWN_TIME] = (doneTeardownTime - doneExperimentTime).total_seconds()
              
              # set the success flag
              self._metadata[self.STATUS] = True
          except Exception as e:
              print("Caught exception in experiment: {e}".format(e = e))
  
              # grab the traceback before we do anything else
              #_, _, tb = sys.exc_info()
              tb = traceback.format_exc()
              
              # decide on the cleanup actions that need doing
              if (doneSetupTime is not None) and (doneExperimentTime is None):
                  # we did the setup and then failed in the experiment, so
                  # we need to do the teardown
                  try:
                      self.tearDown()
                  except:
                      pass
                  
              # set the failure flag and record the exception
              # (there will be no timing information recorded)
              self._metadata[self.STATUS] = False
              self._metadata[self.EXCEPTION] = e
              self._metadata[self.TRACEBACK] = tb
                  
          # report the results
          self._results = res
          return self.report(params,
                             self._metadata,
                             res)