1. Technical Field
The present invention relates generally to an improved data processing system. In particular, the present invention relates to a method, apparatus, and computer instructions for controlling the amount of trace outputs when debugging software.
2. Description of Related Art
Effective management and enhancement of data processing systems requires knowing how and when various system resources are being used. Performance tools are used to monitor and examine a data processing system to determine resource consumption as various software applications are executing within the data processing system. For example, a performance tool may identify the most frequently executed modules and instructions in a data processing system, or may identify those modules which allocate the largest amount of memory or perform the most I/O requests. Hardware performance tools may be built into the system or added at a later point in time. Software performance tools also are useful in data processing systems, such as personal computer systems, which typically do not contain many, if any, built-in hardware performance tools. Most modern personal computer systems (i.e., desktops and laptops) contain some type of hardware performance counters, although these hardware performance counters are generally not as extensive as those found in high-end machines.
Tracing (also known as “debug” printing) is a common technique for simplifying the development and support processes of software products. Many products, including the XL Compilers, a family of compiler products of International Business Machines Corporation (IBM), require and contain extensive tracing capabilities. A tracer may use more than one technique to provide trace information that indicates execution flows for an executing program. One technique keeps track of particular sequences of instructions by logging certain events as they occur, so-called event-based profiling technique. For example, a tracer may log every entry into, and every exit from, a module, subroutine, method, function, or system component. Alternately, a tracer may log the requester and the amounts of memory allocated for each memory allocation request. Typically, a time-stamped record is produced for each such event. Corresponding pairs of records similar to entry-exit records also are used to trace execution of arbitrary code segments, starting and completing I/O or data transmission, and for many other events of interest.
Another trace technique involves periodically sampling a program's execution flows to identify certain locations in the program in which the program appears to spend large amounts of time. This technique is based on the idea of periodically interrupting the application or data processing system execution at regular intervals, so-called sample-based profiling. At each interruption, information is recorded for a predetermined length of time or for a predetermined number of events of interest. For example, the program counter of the currently executing thread, which is a process that is part of the larger program being profiled, may be recorded during the intervals. These values may be resolved against a load map and symbol table information for the data processing system at post-processing time, and a profile of where the time is being spent may be obtained from this analysis.
The most common problem encountered with tracing is the “you can't see the forest for the trees” problem. In other words, when too much information is produced by the tracing facilities, it is difficult (and sometimes nearly impossible) for the software developer to locate the relevant part of the trace in the stream. In addition, even when that part of the trace is located, it may still contain too much information to be of any use. A software developer may often desire to qualify the profiling of an application through various types of conditions that may exist within the execution environment of the application program being profiled. For example, a software developer may desire to limit the generation of trace data so that trace data is only generated during periods of time in which a specific condition is active. This type of granularity would allow a software developer to obtain a more focused picture of the execution conditions within the profiled application.
Existing methods of addressing the problem above provide a set of tracing controls that allow developers to minimize the trace output by specifying which part of the trace they want to see. Such methods may be implemented in, for example, the Toronto Portable Optimizer (TPO), which is used by the IBM XL family of compilers for language-independent optimizations using an intermediate stack language called W-code. W-code and W-code based optimizers provide a comprehensive optimization and analysis infrastructure. Other products address the problem above by allowing an output threshold to be set by the tracers (i.e., developers or maintainers), and thus set the “level of noise” as they need it.
However, as most software products are written to reuse code, a part of the code providing a particular functionality may be called from other parts of the code multiple times. Even though the trace output for the code providing the particular functionality is enabled, it may still be difficult (and sometimes impossible) to determine from where in the code the particular functionality was activated. In addition, it may be difficult to determine if the trace output actually generated is the output (and the level of output) that the tracer required. Although locating the part of the code from where the particular functionality was activated may be achieved using a combination of global and local trace flags, this approach requires strict discipline on the part of the developer to keep track of the tracing state, as well as presents additional problems with concurrent activations.
Therefore, it would be advantageous to provide a method and apparatus that reduces the amount of data generated during tracing. It would be particularly advantageous to provide the ability to selectively enable context sensitive tracing.
The present invention provides a method, apparatus, and computer program product for using debug streams to control the amount of trace outputs when debugging software. As debug streams extend on the use of regular output streams, debug streams are very easy to use and simple to implement, while providing a high level of control over the verbosity of traces. A debug stream is created that includes a debug level setting to control the verbosity of the trace outputs. A determination is made as to whether the debug level setting in the debug stream is greater than a specified threshold level. Responsive to a determination that the debug level setting is greater than the specified threshold level, trace outputs are emitted to the debug stream.
The novel features believed characteristic of the invention are set forth in the appended claims. The invention itself, however, as well as a preferred mode of use, further objectives and advantages thereof, will best be understood by reference to the following detailed description of an illustrative embodiment when read in conjunction with the accompanying drawings, wherein:
With reference now to the figures,
In the depicted example, server 104 is connected to network 102 along with storage unit 106. In addition, clients 108, 110, and 112 are connected to network 102. These clients 108, 110, and 112 may be, for example, personal computers or network computers. In the depicted example, server 104 provides data, such as boot files, operating system images, and applications to clients 108-112. Clients 108, 110, and 112 are clients to server 104. Network data processing system 100 may include additional servers, clients, and other devices not shown. In the depicted example, network data processing system 100 is the Internet with network 102 representing a worldwide collection of networks and gateways that use the Transmission Control Protocol/Internet Protocol (TCP/IP) suite of protocols to communicate with one another. At the heart of the Internet is a backbone of high-speed data communication lines between major nodes or host computers, consisting of thousands of commercial, government, educational and other computer systems that route data and messages. Of course, network data processing system 100 also may be implemented as a number of different types of networks, such as for example, an intranet, a local area network (LAN), or a wide area network (WAN).
Referring to
Peripheral Component Interconnect (PCI) bus bridge 214 connected to I/O bus 212 provides an interface to PCI local bus 216. A number of modems may be connected to PCI local bus 216. Typical PCI bus implementations will support four PCI expansion slots or add-in connectors. Communications links to clients 108-112 in
Additional PCI bus bridges 222 and 224 provide interfaces for additional PCI local buses 226 and 228, from which additional modems or network adapters may be supported. In this manner, data processing system 200 allows connections to multiple network computers. A memory-mapped graphics adapter 230 and hard disk 232 may also be connected to I/O bus 212 as depicted, either directly or indirectly.
Those of ordinary skill in the art will appreciate that the hardware depicted in
The data processing system depicted in
With reference now to
An operating system runs on processor 302 and is used to coordinate and provide control of various components within data processing system 300 in
Those of ordinary skill in the art will appreciate that the hardware in
The present invention provides a tracing technique for simplifying the development and support of software products. In one exemplary embodiment of the present invention, this tracing technique is performed using the IBM XL family of compilers, which provide optimization and functionality delivered on various platforms to C/C++/Fortran programming languages.
To enable a particular source code program to execute on different types of data processing systems, a compiler typically generates an architecture-neutral file format, such as W-code. W-code is a machine independent code and is nonspecific to a particular computer architecture. W-code is an Intermediate Language (IL) and is used to represent the source program internally in the compiler. W-code is also used to transfer information about the source program between components of the XL Compilers (i.e., Front end ->TPO, TPO ->TOBEY). Thus, the output from one component must generate W-code and the input into another component must read/decode W-code. This simplifies the compilation process because all components recognize a common format. While W-code is architecture independent, it is not executable; it is a strictly well-defined representation for the source code that all components recognize.
The compiler generates an executable program that will run on a specific architecture. This executable can either run natively (i.e., the machine recognizes the instructions in the program and can run them itself) or run through an interpreter (i.e., another program reads the instructions in the program and executes them). In the case of C/C++/Fortran, executable machine code is generated. In the case of Java, bytecodes are generated, which must be run through an interpreter (the Java Virtual Machine).
A typical optimization scenario comprises a front-end 402, an optimization layer 404, and a back-end 406. Front-end 402 comprises multiple language specific front-ends, such as C language front-end 408, C++ language front-end 410, and Fortran language front-end 412. Each front-end component, 408, 410, 412, translates the corresponding source code program into a high-level intermediate language, such as W-code 414. W-code 414 is used to provide an intermediate representation of the particular source code.
Optimization layer 404 comprises a Toronto Portable Optimizer (TPO) 416 and libraries 418. TPO 416 receives and generates intermediate representations in W-code. TPO 416 performs language and architecture independent optimizations, such as constant propagation and dead code elimination, and also high-level loop transformations based on aggressive dataflow analysis. Libraries 418 are commonly used functions that may be called from a program. The object code for these functions reside in a separate files and must be available in order to create an executable program.
Components in the back-end 406 receive the optimized program in the form of W-code from TPO 416. The transformed W-code is converted into optimized machine code by an architecture-specific back-end. As shown, the back-end 422 comprises W-code partitions 424, Toronto Optimizing Back End with Yorktown (TOBEY) compiler 426, optimized objects 430, other objects 432, and system linker 434. TOBEY 426, which is a highly optimizing code generator for PowerPC targets, is used to translate the optimized source code from TPO (in the form of W-code) into machine code. TOBEY 426 implements architecture and implementation specific optimizations and generates the actual executable code, e.g., POWER/PowerPC executable code. Optimized objects 430 are the output of TOBEY. Object files contain the optimized machine code created by TOBEY. Other objects 432 are additional object files not created by TOBEY, but are necessary to compile the program successfully. For example, other objects 432 may include libraries that are used by the source program. System linker 434 puts all of the object files together and creates the final executable.
With the present invention, debug streams are used to control the verbosity of trace output streams when debugging software. Verbosity is the level of detail of the debug messages being printed. A higher verbosity level will result in more detailed messages, resulting in more information being printed. Similarly, a lower verbosity level results in less detailed messages, resulting in less information being printed.
Debug streams are a controlled imitation of output streams. Specifically, debug streams are C++ objects that wrap around regular output streams (ostream) or an output file. A wrapper is a piece of code that wraps or abstracts the use of another piece of code. Rather than using debug stream objects as subclasses for output streams, the debug stream objects are created as wrappers for output streams to enable controlling the output (e.g., when to print, the level to print, etc.) to the real streams.
In order to control the underlying trace output, the present invention employs constructors to build a debug stream wrapped around a specified underlying output stream. When a DebugStream( ) constructor creates a debug stream instance, the debug stream is assigned a debug control option, which is expressed as a string. The presence of a debug control option enables the debug stream.
Different DebugStream( ) constructors may be used to generate a specified trace output. For example, a developer may select a DebugStream( ) constructor that creates a debug stream, specifies a debug control option (as a string), and supplies a trace output stream. The default setting for a standard trace output is “cout”. As DebugStream( ) constructors are written in the code (i.e., added when the code is being developed), a decision should be made during development of which constructor to use.
Another constructor that may be used is a DebugStream( ) constructor that creates a debug stream, specifies a debug control option (as a string) and specifies a file name. The file name specified will be used to create a file and write the trace output to the file. The file is created for writing when the debug stream object is constructed, and closed when the object is deleted.
A third constructor that may be used is a DebugStream( ) constructor that creates a debug stream, specifies a debug control option (as a string) and specifies a DebugStream controller object. Specifying a DebugStream controller object allows the output on this debug stream to be enabled if both its debug control option is enabled and the debug stream object is enabled. This debug constructor allows for creating greater context sensitiveness beyond the absolute debug levels. In this manner, a developer may be able to trace specific parts of a code that is being generally traced, as described below.
Once a DebugStream has been constructed, the output stream can be accessed directly using the mOS member of the DebugStream object. The mOS member of a debug stream is a pointer to the actual output stream, which causes this invocation to call the original output function in a way that is controlled by the debug stream framework. The mOS command allows direct access to the output stream when needed.
Furthermore, threshold levels may be set in the debug stream to allow only specified outputs to be emitted to the output stream. The current threshold level of the debug stream may be obtained using using a GetThresholdLevel( ) method. GetThresholdLevel( ) is a method for querying the current threshold level of the debug stream. SetThresholdLevel( ) is a method for setting the current threshold level of the debug stream. A threshold level is set in order to only allow outputs with an acceptable debug level. A threshold can be set for the entire stream (i.e., trace).
A debug level may be set to allow for the output of specific trace messages, such as printing a single line. A DebugLevel class is provided to allow for temporarily modifying the debug level of a debug stream.
A DebugLevel object is constructed given a DebugStream object and a new debug level setting. The DebugLevel object then queries the DebugStream object for its current debug level. This step may be performed using a GetDebugLevel( ) method. GetDebugLevel( ) is a method for querying the current debug level setting for outputs to the debug stream. Once the current debug level is obtained, the DebugLevel object retains the current debug level and sets the DebugStream object's debug level to the new setting. This step may be performed using a SetDebugLevel( ) method. SetDebugLevel( ) a method for setting the current debug level setting for outputs to the stream. In this manner, trace outputs having a debug level higher than the threshold will not be emitted to the output stream. When the DebugLevel object is deconstructed, it restores the retained debug level setting in the DebugStream object.
A DebugStream object may be passed to any code providing some common functionality. If the DebugStream object is used by a high level stage of the compiler, for example, the object is controlled by that stage's trace control. Although the code outputs its traces to the DebugStream object, the code is oblivious to whether the debug stream is enabled or not. The traces for the provided functionality will only be enabled if the high level stage required it.
With debug streams, there is no need to pass trace flags, manage their contexts, or manage different output streams (to files, to console, to error stream, etc.). The level of convenience and control offered by using debug streams are such that, for example, every loop optimization transformation in the TPO is automatically set up with its own analysis stream and transformation stream, with separate threshold controls for each. In this manner, debug streams give a developer a greater and much more granular control over compiler traces.
In this illustrative example, lookupSymbol( ) routine 500 is used to determine a symbol for a function or global variable. LookupSymbol( ) routine 500 may be called from multiple places to provide its functionality. In this particular example, lookupSymbol( ) routine 500 is called with an enabled debug stream.
If the DebugStream is enabled with a threshold greater than or equal to 0, the text “Entering lookupSymbol” and “Exiting lookupsymbol” will be printed (step 502 and 504 respectively). The text “-Symbol found” may also be printed depending on whether the symbol was found (e.g., the symbol object is not NULL in the first line of 506). If the DebugStream is enabled with threshold greater than or equal to 1, the text “(looking for symbol# SymbolNumber)” will be printed, where SymbolNumber is the symbol number, determined during program execution. The text “(with symbol SymbolName)” or “(symbol not found)”, will also be printed depending on the symbol object. SymbolName is the name of the symbol, determined at runtime. The use of DebugLevels in this example demonstrates the easy modification of the thresholds used by DebugStreams within a single use of a DebugStream object.
Each time a DebugLevel object is used as in the above example (i.e., DebugLevel objects are constructed as temporary objects), the DebugLevel object survives only until the end of the line of code, as defined by the C++ standard. Consequently, the debug level setting modification is temporary. Therefore, upon construction, the temporary object will remember the current debug level of the debug stream, modify it, and restore it upon deconstruction, as previously described. Since the multiple DebugLevel objects are deconstructed in a reverse order to their creation order, we are assured that the original debug level setting for the debug stream will be restored.
For the most part, the decision of whether or not to output the trace is done at the highest level (as described above). Consequently, calls to traces should not have a significant impact on performance (e.g., compiler performance). However, in circumstances where some “heavy” computation needs to be performed for the purpose of outputting the trace, it would be a waste of time and resources to do that computation when the trace output is not needed. Under these circumstances, the users of the framework are encouraged to guard the computation with a call to an IsEnabled( ) method of the debug stream.
In this illustrative example, a debug stream is created in step 902. The debug stream is created with a “myopt” debug control option. Output of the traces from the debug stream will be placed in a file named “myopt.trace”. As shown in step 904, optimization analysis code is then performed, and traces with varying debug levels are output to the debug stream. In this step, a specialized DebugStream object is created. This specialized DebugStream object is enabled only if both the “myopt” and “analysis” debug control options were specified. If both debug control options were specified, the trace output is appended to the “myopt.trace” file.
Next, optimization transformation code is performed in step 906. Traces with varying debug levels are output to the debug stream. In step 906, a specialized DebugStream object is created and enabled only if both the “myopt” and “transform” debug control options were specified. If both debug control options were specified, the trace output is appended to the “myopt.trace” file. In this manner, debug stream dependencies may be provided to trace specific parts of a code that is being generally traced. Tracing specific parts of a code allows for creating greater context sensitiveness beyond the absolute debug levels.
When a DebugStream( ) constructor creates a debug stream instance, the debug stream is assigned a debug control option (step 1004). This debug control option is used to enable the debug stream. If specific trace messages are desired, the optional step of modifying the debug level of the debug stream may be performed (step 1006). A determination is then made as to the content for the output stream (step 1008). DebugStream object checks the debug stream to determine whether the debug stream is enabled (step 1010). If the debug stream is not enabled, no trace is output to the debug stream, and the process terminates thereafter.
If the debug stream is enabled, DebugStream object checks the debug stream to determine whether the current debug level setting in the debug stream is greater than a specified threshold (step 1012). The current debug level may be obtained using a GetDebugLevel( ) method, which queries the current debug level of the debug stream. If the current debug level is greater than the threshold specified, the trace outputs are not emitted to the output stream, and the process terminates thereafter.
However, if the current debug level is less than or equal to the threshold specified, trace outputs are emitted to the output stream (step 1014). The DebugLevel object restores the retained debug level setting in the DebugStream object when the DebugLevel object is deconstructed (step 1016), with the process terminating thereafter.
Thus, the present invention provides debug streams to control the amount of trace outputs when debugging software. As debug streams extend on the use of regular output streams, debug streams are very easy to use and simple to implement, while providing a high level of control over the verbosity of traces. Their design enables context sensitive tracing, which may be crucial in some scenarios, such as trying to analyze a problem in a very common part of the software.
It is important to note that while the present invention has been described in the context of a fully functioning data processing system, those of ordinary skill in the art will appreciate that the processes of the present invention are capable of being distributed in the form of a computer readable medium of instructions and a variety of forms and that the present invention applies equally regardless of the particular type of signal bearing media actually used to carry out the distribution. Examples of computer readable media include recordable-type media, such as a floppy disk, a hard disk drive, a RAM, CD-ROMs, DVD-ROMs, and transmission-type media, such as digital and analog communications links, wired or wireless communications links using transmission forms, such as, for example, radio frequency and light wave transmissions. The computer readable media may take the form of coded formats that are decoded for actual use in a particular data processing system.
The description of the present invention has been presented for purposes of illustration and description, and is not intended to be exhaustive or limited to the invention in the form disclosed. Many modifications and variations will be apparent to those of ordinary skill in the art. The embodiment was chosen and described in order to best explain the principles of the invention, the practical application, and to enable others of ordinary skill in the art to understand the invention for various embodiments with various modifications as are suited to the particular use contemplated.