As their is no way automatically to obtain the name of the function the compiler is processing, you must provide the name of the function to the constructor of the OTC_Tracer class when you create it. This string will be used in the messages displayed when the function is entered and exited. The class does not make a copy of the string you pass to the constructor. The string should either be a string literal, or otherwise be guaranteed not to be deleted prior to the block being exited.
An example of what your code would look like is given below.
#include <OTC/debug/tracer.hh>When your program is run, the above code will have the effect of displaying the following messages each time the function is executed.
function()
{
OTC_Tracer tracer("function()");
// ...
}
@enter - function()
@exit - function()
main(int argc, char* argv[])Invoking `tracer()' as shown in the example, will result in a reference to an instance of the ostream class being returned. Any operations which you would normally perform on an ostream, such as formatting commands, you can also use here. In addition, any objects you would normally display on an ostream may also be displayed using the OTC_Tracer class.
{
OTC_Tracer tracer("main(int argc, char* argv[])");
tracer() << "argc = " << argc << endl;
for (int i=0; i<argc; i++)
{
tracer() << "argv[" << i << "] = "
<< argv[i] << endl;
}
}
If you have not created an instance of the OTC_Tracer class, but still need to display information about what is happening in a function, you can use the global function otclib_tracer(). This will return a reference to the same stream as returned when calling `tracer()' in the above example. In fact, the following example is equivalent to the above code.
main(int argc, char* argv[])Note that using the OTC_Tracer class as shown, is not safe within an multi-threaded application.
{
OTC_Tracer tracer("main(int argc, char* argv[])");
otclib_tracer() << "argc = " << argc << endl;
for (int i=0; i<argc; i++)
{
otclib_tracer() << "argv[" << i << "] = "
<< argv[i] << endl;
}
}
The general strategy employed to monitor the lifetimes of objects, is to use the OTC_Tracer class in the constructor and destructor of the class type you wish to monitor. In order that you can match up the constructor and destructor call for a specific instance of an object, you should output a value in both the constructor and destructor which uniquely identifies that instance of the class. This value could be the address of the object in memory, or may be an identifier which has particular meaning within the context of your application.
A suggested format is illustrated below.
Foo::Foo()It is recommended that you use easily identifiable tags to denote the creation and destruction of the object, as this will allow you to write automated tools which can scan the program output and determine if everything is working as expected.
{
OTC_Tracer tracer("Foo::Foo()");
tracer() << "@create Foo - " << (void*)this << endl;
}
Foo::~Foo()
{
OTC_Tracer tracer("Foo::~Foo()");
tracer() << "@destroy Foo - " << (void*)this << endl;
}
As with monitoring the lifetimes of objects, the idea is to generate tagged information within the trace output which you can either manually or automatically analyse. A suggested format to use is given in the following example.
void function()Generation of the numeric values to identify each block would be tedious if done manually. You may consider writing a tool which would automatically insert and keep the tags up to date.
{
OTC_Tracer tracer("void function()");
tracer() << "@block 1" << endl;
if (...)
{
tracer() << "@block 1.1" << endl;
...
if (...)
{
tracer() << "@block 1.1.1" << endl;
...
}
...
if (...)
{
tracer() << "@block 1.1.2" << endl;
...
}
}
if (...)
{
tracer() << "@block 1.2" << endl;
...
}
}
An example of where you might dump out the information held in the chain of OTC_Tracer classes, is in functions which abort the program due to an unexpected error occurring. Code for traversing the chain of OTC_Tracer classes and displaying the name of each function is illustrated below.
if (OTC_Tracer::level() != 0)
{
OTC_Tracer const* aTracer = OTC_Tracer::last();
if (aTracer != 0)
{
OTC_Logger::notify(
OTCLIB_LOG_DEBUG,"Tracer stack dump:");
while (aTracer != 0)
{
if (aTracer->prototype() != 0)
OTC_Logger::notify(
OTCLIB_LOG_DEBUG,aTracer->prototype());
aTracer = aTracer->prev();
}
}
}
As the output file will be truncated by the program, you should avoid having multiple programs use the same output file. This means that if you have a process that forks and then executes a subprocess, you should use the system routine putenv() to set the OTCLIB_TRACEFILE environment variable to a different file name, prior to executing the new process.
Filtering of trace output can be achieved in a number of ways, the simplest of which is to pipe the output of the program directly into the filter program.
myprogram 2>&1 | awk -f myscriptWhen you do this, it is important to remember that the stream clog, which is used to generate messages, displays them on the standard error output. Therefore, you need to merge standard error output with standard output when you run your program, so it can be piped to the filter process.
The problem with this approach is that messages sent to standard output or standard error output which are not part of the trace output, will also be sent to the filter program. These additional messages could confuse the filter program, alternatively you may not wish to have the normal messages formatted in the same way as the trace output.
To split the trace output into its own stream so that it can be processed separately, you can specify a file descriptor to which trace output should be sent instead of that for standard error output. To have this occur, you should set the environment variable OTCLIB_TRACEFD to the file descriptor number to which output should be directed, before you run your program. To make use of this feature directly, you will need to be using a shell which supports the ability to pipe the output of a specific file descriptor to a process. One shell which allows this is `rc', in which it is possible to write:
OTCLIB_TRACEFD=3 myprogram |[3] awk -f myscriptAn alternative way of accessing this feature is to write a program in C, C++ or a scripting language, in which you can invoke a filter program, and then execute your program; passing through the environment variable OTCLIB_TRACEFD, the number of the file descriptor on which the filter program is listening.
main()Because of the way streams work, you are not restricted to sending output to a file, you can assign any type of ostream to clog. As an example, you could direct the trace output to another process by using the system routine popen() to create the process, creating an instance of the class ofstream with the file descriptor for the process, and then assigning the instance of ofstream to clog. Alternatively, you could create your own special streams class which directed trace output to a graphics windows, and assign that to clog.
{
ofstream* outs = new ofstream("LOG");
clog = *outs;
OTC_Tracer tracer("main()");
tracer() << "Hello World" << endl;
return 0;
}
When assigning a new stream to clog, you should ensure that the stream which you have created has been created using operator new(). You should never try to delete the stream once you have assigned it to clog. This is necessary to ensure that clog is still valid if trace output is produced from the destructors of static objects.
It is important to note, that since you would be assigning to clog at the commencement of the main() routine, any output of the OTC_Tracer class which occurred during the initialisation of static objects will not go where you wanted it to go. This method of redirecting trace output therefore may not always be suitable.
An alternative to assigning a new stream to clog, is to call the function OTC_Tracer::setStream(). The same restrictions which apply to assigning to clog, also apply when using this function. An example of using this function is given below.
main()Once the function OTC_Tracer::setStream() has been called, assigning a stream to clog will not change where trace output is displayed.
{
ofstream* outs = new ofstream("LOG");
OTC_Tracer::setStream(outs);
OTC_Tracer tracer("main()");
tracer() << "Hello World" << endl;
return 0;
}
Traditionally this has been done using the preprocessor in the following manner.
main()This tends to clutter code and make it look unnecessarily complicated. To overcome this problem, macros are provided which you can use instead of using the OTC_Tracer class directly. By using the macros you can conditionally compile in the trace code to your program only when you require it. Using the macros supplied, the code above is rewritten as:
{
#ifdef DEBUG
OTC_Tracer tracer("main()");
tracer() << "Hello World" << endl;
#endif
return 0;
}
main()Note that versions of OSE prior to release 3.0 provided a OTCLIB_DOTRACE() macro. This is equivalent to the OTCLIB_MARKBLOCK() macro, with a first argument of `1'. The OTCLIB_DOTRACE() macro is kept for backward compatability.
{
OTCLIB_MARKBLOCK(1,"main()");
OTCLIB_TRACER(1) << "Hello World" << endl;
return 0;
}
The macros mean the code is similar to what it originally was, but eliminates the `#ifdef' and `#endif'. If you want the trace code to be included into your program, you need to define the preprocessor symbol OTCLIB_TRACE when running the compiler. If you are using makeit, you would include the definition,
CPPFLAGS += -DOTCLIB_TRACEin your makefile.
When using the macros, there are two things you will need to be careful about. Firstly, it is not obvious that the code can be left out and so you may accidentally place code in the OTCLIB_TRACER() statement which has a side effect which affects the operation of the program. Obviously you should avoid this as not including the trace code will mean your program will run differently. Secondly, the OTCLIB_MARKBLOCK() macro uses the same name each time it is used, for the instance of the OTC_Tracer class which it creates. This means that you should only use the OTCLIB_MARKBLOCK() macro once within each block of code. For example,
main()is okay, but the following is not.
{
OTCLIB_MARKBLOCK(1,"main()");
OTCLIB_TRACER(1) << "Hello World" << endl;
if (1)
{
OTCLIB_MARKBLOCK(1,"main() - nested block");
}
return 0;
}
main()Note that it is not necessary for you to have used the OTCLIB_MARKBLOCK() in a block to be able to use the OTCLIB_TRACER() macro. For example, you can write:
{
OTCLIB_MARKBLOCK(1,"main()");
OTCLIB_TRACER(1) << "Hello World" << endl;
OTCLIB_MARKBLOCK(1,"main() - same block");
return 0;
}
main()The only difference will be that the `@enter' and `@exit' tags for entry and exit of the function will not be displayed.
{
OTCLIB_TRACER(1) << "Hello World" << endl;
return 0;
}
Although the OTCLIB_MARKBLOCK() and OTCLIB_TRACER() macros eliminate the need for you to use the `#ifdef' and `#endif' statements in the above examples, you will need to resort back to using them in certain circumstances. An example of such a situation is when a conditional statement or loop is required only when trace output is being generated. An example of this is shown below.
main(int argc, char* argv[])If you did not enclose the `for' loop with `#ifdef' and `#endif', redundant statements would be executed by your program. Although not a major problem in this example, it could be in other situations.
{
OTCLIB_MARKBLOCK(1,"main(int argc, char* argv[])");
OTCLIB_TRACER(1) << "argc = " << argc << endl;
#ifdef OTCLIB_TRACE
for (int i=0; i<argc; i++)
{
OTCLIB_TRACER(1) << "argv[" << i << "] = ";
OTCLIB_TRACER(1) << argv[i] << endl;
}
#endif
}
If you use a trace level of `0' the statement will always be executed. The entry and exit tags for a function will be displayed only if the trace level threshold is set to a value of `1' or higher. In general you would use the value `1', however if you have information which need not be displayed always, you should use higher values.
When you run your program, you can set the trace level threshold in two ways. The first method is to set the environment variable OTCLIB_TRACELEVEL to the value desired, before you run your program. If you do not set the OTCLIB_TRACELEVEL environment variable, the default trace level threshold of `0' will be used.
The second way of setting the trace level threshold is by adding code into your program to set it. For example:
#include <OTC/debug/tracer.hh>Since the trace level threshold will only be set inside main(), any trace output generated from constructors for static objects will not be displayed unless the OTCLIB_TRACELEVEL environment variable were also set. Once the set level command has been executed though, the value to which the trace level threshold has been set will take precedence over that defined in the OTCLIB_TRACELEVEL environment variable.
main()
{
OTC_Tracer::setLevel(1);
OTCLIB_MARKBLOCK(1,"main()");
...
}
@location - "file.cc", line 42When displayed, this will appear immediately before the `@enter' line generated by the OTCLIB_MARKBLOCK() macro.