4.4. The API and the event processing pipeline

The API provides methods that allow you to dynamically change the contents of the event processing pipeline. You can add event processors at any point in the pipeline, remove them from any point in the pipeline and iterate the pipeline.

The example we will show in this section will be one that can swap out, and back, entire event processing pipelines. Our specific use case will be to save the contents of an event processing pipeline so that it can be restored later, clear the pipeline and insert the event processor that can unpack filter files.

In libTcl++ and adding commands to SpecTcl, we will combine the class we're writing here with a Tcl command added to SpecTcl to allow us to switch dynamically between analyzing raw event files and analyzing filter files.

The class we're going to write has the following interface specification:

Example 4-10. Class to switch event analysis pipelines


#ifndef EVPSWITCHER_H
#define EVPSWITCHER_H
#include <vector>
#include <string>

class CEventProcessor;
class CFilterEventProcessor;
class EVPSwitcher {
private:
  std::vector<std::pair<std::string, CEventProcessor*> > m_savedPipeline;
  CEventProcessor&                                m_FilterProcessor;
public:
  EVPSwitcher();
  ~EVPSwitcher();
  
  void save();
  void restore();
  void useFilterProcessor();

};

#endif

            

The concept is fairly simple. The save method will save the current event processing pipeline processor names and object pointers into the m_savedPipeline member. Similarly restor, restore will replace the current event processing pipeline with the processors saved in m_savedPipeline.

useFilterProcessor will save the pipeline and replace it with one that contains the filter event processor in m_FilterProcessor. This attribute will be created by the constructor and destroyed by the destructor.

Let's have a look at the constructor and destructor as they're closely linked:

Example 4-11. EVPSwitcher constructor and destrutor


  #include "EVPSwitcher.h"
#include <EventProcessor.h>
#include <FilterEventProcessor.h>
#include <SpecTcl.h>
#include <TCLAnalyzer.h>


EVPSwitcher::EVPSwitcher() :
  m_FilterProcessor(*(new CFilterEventProcessor))
{}

EVPSwitcher::~EVPSwitcher()
{
  delete &m_FilterProcessor;
}
              
            

Straightforward code. The constructor creates a filter event processor, saving a reference to it in m_FilterProcessor. The destructor prevents this from being a memory leak if we are destroyed.

The save method will:

  1. Clear the m_savedPipeline vector. We only need to clear the vector. We don't destroy any objects.

  2. Walk the event processing pipeline using the SpecTcl::ProcessingPipelineBegin and SpecTcl::ProcessingPeiplineEnd methods to get iterators that "point" to successive event processign pipeline elements.

    Each event processing pipeline element is a pair containing the name of the pipeline element and a pointer to the event processor in that position. Ths is exactly what we need.

The std::vector::insert method makes this all pretty trivial:

Example 4-12. EVPSwitcher::save implementation


void
EVPSwitcher::save()
{
  m_savedPipeline.clear();
  SpecTcl& api(*SpecTcl::getInstance());

  m_savedPipeline.insert(m_savedPipeline.end(),
			 api.ProcessingPipelineBegin(),
			 api.ProcessingPipelineEnd());
}

            

restore is a matter of:

  1. Removing event processing pipeline elements until the event processing pipeline is empty.

  2. Inserting each event processor into the now emptied event processing pipeline.

Since emptying the event processing pipeline is also needed for useFilterProcessotr, we'll introduce a utility method; clearPipeline. Here's the implementatiuon of that utility and restore.

Example 4-13. EVPSwitcher::restore implementation


void
EVPSwitcher::clearPipeline()
{
  SpecTcl& api(*SpecTcl::getInstance());

  while(api.ProcessingPipelineBegin() != api.ProcessingPipelineEnd()) {
    api.RemoveEventProcessor(api.ProcessingPipelineBegin());
  }
}

void
EVPSwitcher::restore()
{
  clearPipeline();
  SpecTcl& api(*SpecTcl::getInstance());

  for (int i = 0; i < m_savedPipeline.size(); i++) {
    api.AddEventProcessor(*m_savedPipeline[i].second, m_savedPipeline[i].first.c_str());
  }
}

            

Much of this should be straightforward. The only bits that are interesting is that the call to SpecTcl::AddEventProcessor requires a reference to the event processor and a const char* name.

Finally to set up SpecTcl to read data from filtered data files, we need to:

  1. Clear the event processing pipeline

  2. Add our filter event processor to the pipeline.

In all of this, the assumption is that prior to this, if someone wants the event processing pipeline restored, they will have first invoked save to do that.

Example 4-14. EVPSwitcher::useFilterProcessor implementation


void
EVPSwitcher::useFilterProcessor()
{
  clearPipeline();
  SpecTcl::getInstance()->AddEventProcessor(m_FilterProcessor, "Filter-decoder");
}
            

I think again, no explanation is needed.