Programming Models @ BSC

Boosting parallel computing research since 1989

Instrumenting Nanos++ (2nd part)

- Written by Xavier Teruel


We continue in this article with the Nanos++ instrumentation overview started in previously. In the previous article we discussed about the different components which take part on the instrumentation process, the different types of events which can be generated by the runtime and also, how the instrumentation output can be adapted to different formats (using plugins). In this article we focus on the internal implementation and how programmers can use instrumentation services in order to generate events.

Instrumentation class

The main component of the instrumentation is the Instrumentation class. This class offers several services which can be grouped in:

  • Create event’s services: these services are focused in create specific event objects. Usually they are not called by external agents but they are used by raise event’s services (explained below).
  • Raise event’s services: these services are focused in effectively producing an event (or list of events) which will be visible by the user. Usually these functions will call one or several create event’s service(s) and finally produce an effective output by calling plugin’s addEventList() service.
  • Context switch’s services: they are used to backup/restore the instrumentation information history for the current WorkDescriptor (see InstrumentationContext class).

Instrumentation class also offers two more services to enable/disable state instrumentation. Once the user calls disableStateInstrumentation() the runtime will not produce more state events until the user enable it by calling enableStateInstrumentation(). Although no state events will be produced during this interval of time Instrumentation class will keep all potential state changes by creating a special event object: the substate event.

InstrumentationContext class

In order to reproduce the history of events in WorkDescriptor’s context switches (and taking into account that we are producing a thread-centered trace) the Instrumentation class needs a repository for this kind of information related with each WorkDescriptor. The InstrumentationContext is the responsible of keeping a history of state transitions, still opened bursts or delayed event list. InstrumentationContext is implemented through two different classes: InstrumentationContext (which defines the behavior of this component) and InstrumentationContextData (which actually keeps the information and it is embedded in the WorkDescriptor class).

InstrumentationContext behaviour is defined by the plugin itself and has several implementation according with State and Burst generation scheme. These two elements can have different behavior in a context switch. In one case we want only to generate the last event of this type (this is the usual implementation) but in other cases we wanted to generate a complete sequence of events of the same type in the same order they occur (this is the showing stacked event behavior). Currently they are four InstrumentationContext implementations:

  • InstrumentationContext
  • InstrumentationContextStackedStates
  • InstrumentationContextStackedBursts
  • InstrumentationContextStackedStatesAndBursts

The plugin itself is responsible for defining the InstrumentationContext behaviour by defining an object of this class and initializing the field _instrumentationContext with a reference to it. Example:

class InstrumentationExample: public Instrumentation
{
   private:
      InstrumentationContextStackedBursts _icLocal;
   public:
      InstrumentationExtrae() : Instrumentation(), _icLocal()
      {
         _instrumentationContext = &_icLocal;
      }
      .
      .
      .
}

Instrumentation examples

In this section we focus in the runtime instrumentation code. We discuss two different examples: a critical runtime piece of code and a work descriptor’s context switch.

Some runtime chunks of code are bound by instrumentation events in order to measure the duration of this piece of code. An example is a cache allocation. This function is bound by a state event and a burst event. State event will change the current thread’s state to CACHE and the Burst event will keep information of the memory allocation size for the specific call. Here is the example:

void * allocate( size_t size )
{
      void *result;
      NANOS_INSTRUMENT(nanos_event_key_t k);
      NANOS_INSTRUMENT(k = Instrumentor->getInstrumentorDictionary()->getEventKey("cache-malloc"));
      NANOS_INSTRUMENT(Instrumentor()->raiseOpenStateAndBurst(CACHE, k, (nanos_event_value_t) size));
      result = _T::allocate(size);
      NANOS_INSTRUMENT(Instrumentor()->raiseCloseStateAndBurst(k));
      return result;
}

WorkDescriptor’s context switch uses two instrumentation servicesi: wdLeaveCPU() and wdEnterCPU(). The wdLeaveCPU() is called from the leaving task context execution and wdEnterCPU() is called once we are executing the new task.

   .
   .
   .
   NANOS_INSTRUMENT( sys.getInstrumentor()->wdLeaveCPU(oldWD) );
   myThread->switchHelperDependent(oldWD, newWD, arg);

   myThread->setCurrentWD( *newWD );
   NANOS_INSTRUMENT( sys.getInstrumentor()->wdEnterCPU(newWD) );
   .
   .
   .

In the next article we will conclude these series of articles about the instrumentation module of Nanos++ giving an overview of the external runtime instrumentation API and showing some mechanisms which will make easier programmer’s duty.