main()¶
In the main body of the example, note that we start with the DSUI_BEGIN macro, before we pass argc and argv to our process_options() subroutine. The next DSUI instrumentation point is an event, which simply declares that the body of the experiment has begun:
DSUI_BEGIN(&argc, &argv);
DSTRM_EVENT(FUNC_MAIN, IN_MAIN_FUNC, 0);
In the next several lines we process command line options, set up signal masks, allocate memory for the threads we are intending to spawn, and spawn the threads.
Since this application has one thread per pipeline stage and it has an initial concurrency control problem because we wish to make sure that all pipeline stage threads are created and waiting to process messages flowing through the pipeline before the parent thread begins sending messages into pipeline. To achieve this policy goal, we use the PThreads condition variable mechanism. Note at the top of sigpipe.c that the thread_count_control condition variable and associated thread_count_lock mutex are created. In the body of the main routine the next statements are the standard condition variable programming pattern which is checking to see if the thread_count variable has reached the pipeline length. Each pipeline stage thread increments this variable as it is created and waits in a similar section of code testing the condition variable. The effect of using the condition variable in this way is that the main thread and all pipeline stage threads pause at the condition variable test until all threads are ready and then all proceed.
After the threads are spawned, the main thread enters a loop which will be responsible for sending the user defined number of signals to the first member of the newly created pipeline. Just before it sends a signal, we record two DSUI events that are useful during post-processing for different kinds of analysis:
unsigned int pipeline_stage = 0;
pipeline_stage <<= 27;
pipeline_stage |= sigs + 1;
DSTRM_EVENT(GAP_TEST, SIG_SENT, pipeline_stage);
DSTRM_EVENT(PIPE_TEST, PIPE_START, sigs+1);
See that we can differentiate our events using the (category-name,entity-name) pair as the event identifier. This will generate two different events, one designated (GAP_TEST,SIG_SENT) and one (PIPE_TEST,PIPE_START). Also note that we supply tag values to these two events. The (PIPE_TEST,PIPE_START) event will be measuring total time a signal spends in the pipeline. Because of this, the only data we need is the sequence number of each signal sent into the pipeline. At the end of the pipeline, a matching (PIPE_TEST,PIPE_END) event will also record the sequence number of each signal. Then, using the unique signal sequence numbers, we can differentiate between the (possibly) thousands of events that will be generated describing how signals move through the pipeline, all sharing the same namespace. Note that these two events, while part of the DSUI namespace for this experiment, are defined in different categories. Recall that the Datastream entity namespace is two level, with category names being the first component, and entity names within the category being the second. Note also that there will be many instances of each of these events and the tag data for each instance is what uniquely identifies it.
The other event recorded, (GAP_TEST,SIG_SENT) will be used to record the overhead of sending and receiving signals between pipeline stages. The post-processing problem we face is that we want to have an event representing when each message through the pipeline is sent or received at each pipeline stage. To help with this we have given each signal representing a message a unique sequence number and each stage of the pipeline has a unique ID number. The DSUI encoding challenge is how to attach these two pieces of information to a single event. Recall that each event has a 32 bit tag value by default and that we can optionally attach arbitrary data to such events if we wish. In this case, we have chosen to show how a little creativity in using the 32 bit tag value can often save the additional run-time and programming overhead of using optional data. In this case, we use the top five bits to hold the pipeline stage ID and and the lower 27 bits to hold the signal sequence number. After the parent thread has sent all of the signals the user requested it sends a final kill signal through the pipeline to tell each stage to terminate.
We use another set of events to record the TIDs of the each thread generated in our experiments:
for (i = 0; i < pipeline_len; i++){
DSTRM_EVENT(THREAD, THREAD_ID, tidlist[i]);
}
DSTRM_EVENT(THREAD, THREAD_ID, gettid());
It can not be understated how useful something as simple as recording the unique ID’s (TID or thread ID) of threads in an experiment can be. If other events also include, either in their tag or their extra data, the TID of the executing thread that generated them, then data specific to the threads of an experiment can be separated from events generated by other threads on the system. No matter where the instrumentation points are generated, or how many total events are generated by any number of total threads, the unique TIDs associated with each thread can be used later to filter the raw data from an experiment for just those events that pertain to the threads implementing an experiment. This is particularly significant, for example when using context switch events to determine execution intervals for experiment threads. These events are generated for every thread in the system. Context switch events are among the strongest motivations for an experiment to use Datastream Active Filters to reduce the effect of instrumentation load on system behavior during the experiment.
In the final lines of the main body, the main thread waits for the pipeline stage threads to finish execution, and then records the end of its own execution by generating the (FUNC_MAIN, IN_MAIN_FUNC) event before cleaning up the DSUI framework:
DSTRM_EVENT(FUNC_MAIN, IN_MAIN_FUNC, 0);
DSUI_CLEANUP();
Next we will discuss the code executed by each thread representing each stage of the pipeline.