This (slightly misnamed) module helps greatly in the creation of reusable software. It collects the trace facilities defined by each module into a coherent whole that may be easily manipulated by outsiders -- typically via command-line switches.
The basic idea is that each module defines one or more ways of tracing its behaviour and/or reporting data during operation. The tracing "views" that each module defines are totally independent -- there is no external framework imposed by any outsider. This is essential as no conceivable pre-defined framework could adequately cover the vast range of software modules in existence. Having a set of independent views is more powerful and expressive than having a hierarchy of trace levels, as the relative importance of the trace information may be different for the creator and the user. In addition, the trace selection in a level- oriented scheme is usually very crude: if you enable "Level 3" tracing, you usually get "Level 1" and "Level 2" traces, whether you want them or not.
Capturing these views within the module implementation makes the tracing more powerful than traces generated by debuggers. This is partially because the designer's intent is captured, and partially because the traces can be tailored in ways that may be awkward for the debugger. This reduces the wasteful (and error-prone) reengineering that would otherwise be incurred by the maintainer and/or user. The reengineering saved is:
· deciding where and when to observe the system,
· creation of an accurate model of the system, and
· specification of monitor activities to the debugger.
Capturing the traces in the module is preferable as the trace code is almost certainly more portable than traces written for a specific debugger.
Another part of the design is that the trace flags enable or disable selected blocks of code -- they do not concern themselves with the processing required to generate the traces, nor do they proscribe how the trace is to be presented. This allows the traces to be far less invasive when present. In addition, each trace is defined using a macro, so the entire trace code may be excluded from the compilation simply by defining the macro(s) to expand to nothing.
The trace flags themselves are implemented as simply as possible: each trace flag register is a (32-bit) longword, and each view is assigned one bit within the register. Testing whether a trace is to be generated is merely seeing if any of the views associated with that trace have bits set in the trace flags register -- typically 3-5 instructions per trace test. Defining the register storage is the responsibility of the module -- typically each instantiation of each object in the system will have its own trace flags register.
Once all the views and trace flags have been defined, the one remaining management problem is providing access to all the flag registers so that outsiders may operate the trace facility. This is where this module comes in: It provides a place for details of all the flags, together with simple instructions for setting and clearing flag bits, to be collected together and operated via a uniform interface. The modules and objects don't contain any code to set/clear flag bits -- they merely provide means for the address of each flag register to be obtained, and details of simple mapping between ASCII characters and flag bits.
The main danger of explicitly writing traces in the sources is that some code may be incorrectly placed in the trace block which is needed when trace statements aren't invoked. This can only be found by test and inspection, or avoided by reusing trustworthy code. Hopefully the increase in reuse will offset this risk.
So this module, Tracery, collects all the flag register descriptions, together with the name of each flag register, then manipulates flags based on commands from external sources (typically via debug directives on the command line found by the main program).
Note that each module does not register itself -- this is left to the main program to do. This is so that as modules are reused for different jobs and in different applications, the name used can be chosen to be relevant to the job at hand, and namespace collisions can be avoided. In addition, the registration requires merely that the module advertise a link function to be handed to Tracery. This design allows the platform to hook all the relevant bits of the system together without needing to know about unnecessary details.
Specifying flag names and edits is done by a very simple list of name/mask/set specifications. Each entry includes a description so that the user may find out what flags are available.
The classification implied by the bits selecting each piece of trace code are often worth reporting in their own right. If all else fails, start tracing EVERYTHING and then use a worthwhile grep(!) to scan the output for patterns. Tracery caters for this by defining the variable TRACERY_FLAGS each time a block of code is expanded. This variable contains all the flags marking that block -- not merely the bits that matched the flag register. This variable can either be reported as a number, or the block can use Decode and Name to report the flags and the traced object in human- readable form. Some GCC-specific incantations have been used, however, to stop warnings if the variable is unused.
Another result of adding this extra feature is that we need to find the information about the flag register while running without imposing too much cost on the client modules. Since CPU cycles are the main resource we're trying too optimise and we're less worried about memory usage, we append a pointer to the client's flag register so that we may perform this mapping very easily. In other environments where memory is a scarcer resource, we may need to use a function to search Tracery's private list of registered objects.
Init -- Prepare module for operation Register -- Receive flags register and manipulation details Configure -- Manipulate flags based on configuration Deregister -- Cancel module registration Name -- Report the name associated with a flag register Decode -- Convert flag bits into text specifiers DisplayRegistrations -- Display details of registrations
EditFlags -- Handle detailed flag edit specification DisplayObjectBrief -- Show brief details of object DisplayObjectFull -- Show full details of object