In the previous QuantLib post I talked about the error handling macros. QuantLib also provides a number of macros that facilitate tracing of code execution. Tracing essentially means writing lots of output to the screen or some file that tells you exactly what your code is doing. Tracing is an alternative to using a debugger and is often the preferred choice for finding errors in software that performs long numeric calculations. It is also useful for finding and correcting errors found by beta testers.

The following tracing macros are defined in the headertracing.hpp.

QL_TRACE
QL_TRACE_ENTER_FUNCTION
QL_TRACE_EXIT_FUNCTION
QL_TRACE_VARIABLE
QL_TRACE_LOCATION
QL_TRACE_ENABLE
QL_TRACE_DISABLE
QL_TRACE_ON

In addition the macro

QL_ENABLE_TRACING

controls whether tracing is enabled or disabled.

To enable tracing in your code you should first define the QL_ENALBLE_TRACING macro before including the header file utilities/tracing.hpp or the general QuantLib header files utilities/all.hpp or quantlib.hpp.

#define QL_ENABLE_TRACING
#include <ql/utilities/tracing.hpp>

Tracing Statements

The macro QL_TRACE can be used to write out an arbitrary trace message. The usage of QL_TRACE is

QL_TRACE(message);

where message is the trace message. As with the QL_ASSERT macro discussed it the previous post, message can be anything that can be piped into a std::ostream. An example could be

QL_TRACE("Interest rate at " << today << " is " << rate);

This will produce the output

trace[0]: Interest rate at July 31st, 2012 is 0.03

The QL_TRACE macro is the most general of the tracing macros. It allows to write out any message which can be written to a stream. In addition, some more specific macros are supplied which make tracing more convenient in many cases. QL_TRACE_ENTER_FUNCTION and QL_TRACE_EXIT_FUNCTION can be used at the entry and exit of a function. They should always be used in pairs and their usage is simple

void some_function() {
  QL_TRACE_ENTER_FUNCTION;

  // do something here

  QL_TRACE_EXIT_FUNCTION;
}

This will produce the following output

trace[1]: Entering void some_function()
trace[1]: Exiting void some_function()

Note that these macros use the BOOST_FUNCTION_NAME which returns the name of the current function provided that the compiler provides this information. The two macros QL_TRACE_ENTER_FUNCTION
and QL_TRACE_EXIT_FUNCTION are a very convenient tool for tracing program flow. If used consistently throughout the code, the tracing output will allow you to always follow the execution flow.

Another convenient macro is QL_TRACE_VARIABLE. This let’s you quickly write out the name and value of a variable. Its use is

QL_TRACE_VARIABLE(rate);

which will produce the output

trace[0]: rate = 0.03

Note how the output contains both the name and the value of the variable. In order for this macro to work the std::ostream<< operator has to be defined for the variable.

Finally QL_TRACE_LOCATION will write the file and line number of the trace statement. The usage is

QL_TRACE_LOCATION;

and this will produce the output

trace[0]: At line 36 in test_trace.cpp

This macro is useful to check if certain parts of the code are reached. Consider the following snippet

double rate;
std::cin >> rate;
if (rate < 0.0) {
  QL_TRACE_LOCATION;
  // do something
} else {
  QL_TRACE_LOCATION;
  // do something else
}

The QL_TRACE_LOCATION macro lets you see which of the two branches of the if statements was chosen.

Controlling Output

While tracing is an invaluable tool for debugging, the output produced by tracing statements can get overwhelming. If not controlled, the size of the tracing log can quickly become large and finding the relevant information turns near impossible. In order to suppress output for those parts of the code that are not under investigation the macros QL_TRACE_ENABLE and QL_TRACE_DISABLE are provided. QL_TRACE_DISABLE will disable tracing output. All calls to the tracing macros described above will be ignored and no output will be produced until a call to QL_TRACE_ENABLE is issued. In fact, tracing is disabled by default and in order for any tracing output to be produced, a call to QL_TRACE_ENABLE must be made. This is useful for tracing only parts of the code that need debugging

void some_function_with_bugs() {
  QL_TRACE_ENABLE

  // do something with tracing enabled

  QL_TRACE_DISABLE
}

Alternatively one can enable tracing at the beginning of the main function with QL_TRACE_ENABLE and then switch it off only in the parts of the code that are known to be stable.

int main() {
  QL_TRACE_ENABLE;

  // start program execution
}
void some_stable_function() {
  QL_TRACE_DISABLE
  // do something with tracing disabled
  QL_TRACE_ENABLE
}

In the default configuration all tracing messages will be written to the standard output via std::cout. This is not always convenient for two reasons. One disadvantage is that the tracing output is mixed together with the normal program output. This might make either hard to read and it is not always easy to separate the two. The other disadvantage is more subtle. The std::cout is a buffered stream. This means that messages are not guaranteed to be printed out immediately. If the program crashes, it can happen that the last few tracing messages are lost. Both problems can be solved by writing the tracing output to a different output stream. This can easily be achieved with the QL_TRACE_ON macro. The usage is

QL_TRACE_ON(ostr);

The ostr argument is a reference to a std::ostream. The reference is stored internally and all subsequent tracing output will be written to that stream. Note that you have to make sure that the stream you pass to this macro will be a valid stream at all times when a tracing macro is called.

Production Runs

Once you have finished debugging your code and you want to put it into production you will want to disable all tracing output. You might think that simply calling QL_TRACE_DISABLE should do the job. The problem with this solution is the runtime cost of the tracing statements. Even if no output is produced, every time a tracing macro is called an internal test is performed that checks if tracing is enabled or not. This costs time and also increases the size of the executable code. In addition you might have lots of QL_TRACE_ENABLE calls spread around your code. These would all have to be removed manually.

It is possible to completely remove any code associated with the tracing macros, simply by removing the definition of QL_ENABLE_TRACING before the inclusion of the QuantLib headers. Even better is to undefine the macro.

#undef QL_ENABLE_TRACING
#include <ql/utilities/tracing.hpp>

With this, all tracing macros will be defined as empty macros, producing absolutely no code whatsoever.

Tagged with:
 

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>