Generating TuningFork Traces from C++ Applications

The TuningFork C++ Trace Generation Library supports generating TuningFork traces from a C++ program. The library does not use special C++ extensions but does require linking against a POSIX-compliant threading library.

An example of an instrumented C++ program is available for reference; you can cut and paste snippets from there to instrument your program. The example uses multiple threads producing different trace events.

Here we'll explain the basic principles behind C++-level generation of TuningFork traces and how to use the individual APIs.

Creating a Logger

In order to generate traces, you need to create a logger. A logger can either generate a file or output to a socket. To create a file logger, use
	ILogger *logger = LoggerFactory::makeFileLogger(portLibrary, fileNameOrPortNumber);
where filename is the String with the filename. The generated trace can then be viewed in TuningFork using the "Open Trace" operation from the File menu. Note that a portLibrary must be passed into constructors of various classes. This object is internally used to allow us to port the library to different platforms. An instance of this can be created with
 
        PortLibrary *portLibrary = new PortLibrary(); 

To create a socket logger, use

    ILogger* logger = LoggerFactory::makeServerLogger(portNumber);
where portNumber is the integer port number to use for the socket. The generated trace can then be viewed in TuningFork as the instrumented program is running by connecting to the trace socket with "Connect to a Trace" from the File menu.

There is also a logger that simply ignores all logging requests. This is useful so that you don't have to sprinkle if (LOGGING) conditionals throughout your code. You can simply decide at the beginning of execution whether you actually want to generate a trace. If you don't, create a null logger:

 ILogger* logger = LoggerFactory::makeNullLogger(portLibrary);

Creating and Binding Feedlets

A feedlet is a stream of data within the trace. Feedlets are unsynchronized in order to keep tracing overhead as low as possible. Typically, you create one feedlet per thread of interest, although you can also share a feedlet across threads if you synchronize access to it. However, be aware that this synchronization can itself perturb the execution order of your program.

The TuningFork tool will take care of re-assembling the events in the feedlets in order, so you can use the feedlets without worrying about time synchronization between them. To create a feedlet, use

    
  IFeedlet* myFeedlet = logger.makeFeedlet("My Feedlet", "Primary feedlet for my program");
  myFeedlet->setName("My Feedlet");
The feedlet name is purely for your convenience, so you can use anything you like.

You can add events to feedlets explicitly, but it's more convenient to associate a feedlet with a thread, which you can do with

    myFeedlet->bindToCurrentThread();
After that, if you log a trace event on that thread without specifying a feedlet, it will be placed in the feedlet you bound to the thread.

Creating Event Types

The trace library supports the generation of two different kinds of events: timers and values. Timers are used to mark the beginning and end of operations, for instance a phase of a program or the entry and exit of a method. Values record a single quantity at a point in time, for instance the size of a data structure or the current value of a control port. Events must be registered once and may then be used repeatedly throughout the execution of the program. Events are not specific to a feedlet, and can be used in any feedlet in the trace.

To create a timer event, use

   ITimerEvent* phaseTimer = logger->makeTimerEvent("Phase of My Program");
The timer event can now be used to add events to the log. Similarly, to create a value event, use
   IValueEvent* sizeEvent = logger->makeValueEvent("Size of My Primary Data Structure");

Value events record a single double value along with their timestamp.

Adding Events to the Log

Once you've created a logger, created one or more feedlets, and created some events, you're ready to add events to the trace. To add a value event to the log, you could use
    sizeEvent->addValue(myDataStructure->size());
where myDataStructure is some object whose size you want to monitor.

To add a timer event marking the beginning and end of some operation, you could use

    peacetimes->start();
    myApplicationWork();
    phaseTimer->stop();
where myApplicationWork() is a method in your program whose timing you want to measure.

Properties

It is often useful to add information about the environment in which a trace was collected to the trace itself -- that way you don't have to try to remember that information or put it in a separate file. That's where properties come in. A trace property is simply a pair of strings, the property and its value. The data and time at which a trace is created is always added to the properties when a logger is created.

Properties should only be added once; they are not events. If you have some repeating occurrence that you want to log, use a value event and not a property.

For example, you could add information about the way the program was run:

    logger->addProperty("Command", arg);
where arg is the concatentation of strings passed to your main() function.