tracer.ads |
package Tracer specification |
tracer.adb |
package Tracer body |
tracer_messages.ads |
package Tracer_Messages. It is actually a renaming of Tracer_Messages_English or Tracer_Messages_French, allowing Tracer to speak your preferred language. Contributions for other languages welcome! |
tracer_messages_english.ads |
Package Tracer_Messages_English, specification (no body required). Contains all messages used by Tracer, English version. |
tracer_messages_french.ads |
Package Tracer_Messages_French, specification (no body required). Contains all messages used by Tracer, French version |
tracer-assert.ads |
package Tracer.Assert specification |
tracer-assert.adb |
package Tracer.Assert body |
tracer-timing.ads |
package Tracer.Timing specification. |
tracer-timing.adb |
package Tracer.Timing body. |
tracer.html |
(this file) package Tracer documentation |
tracer_implementation.html |
package Tracer implementation notes |
ttracer.adb |
test program and example for package Tracer |
ttracer2.ads |
auxiliary package for test program (spec) |
ttracer2.adb |
auxiliary package for test program (body) |
protection.ads |
package Protection specification |
protection.adb |
package Protection body |
protection.html |
package Protection documentation |
COPYING |
GNU General Public License |
Package Tracer makes use of package Protection, which is available separately from Adalog's software components page. As a convenience, we provide it with this distribution of Tracer, although it is an independent component.
Fully portable to any compiler supporting System-Programming
and Real-Time annexes.
This package uses no compiler specific feature; it uses features
defined in the System-Programming and Real-Times annexes, and
does not use any feature defined in other annexes.
The Tracer component (Packages Tracer, Tracer.Assert and Tracer.Timing and the associated documentation) is Copyright © 1997, 2021 ADALOG.
The Tracer component is free software; you can redistribute it and/or modify it under terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This component is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License distributed with this program; see file COPYING. If not, write to the Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
As a special exception, if other files instantiate generics from the units of this component, or if you link units provided with this component with other files to produce an executable, these units do not by themselves cause the resulting executable to be covered by the GNU General Public License. This exception does not however invalidate any other reasons why the executable file might be covered by the GNU Public License.
This component is offered with this very liberal license as a demonstration of Adalog's know-how; please do not remove the reference to Adalog in the header comment of each unit.
Although we do not require it, you are very welcome, if you find this component useful, to send us a note at rosen@adalog.fr. We also welcome critics, suggestions for improvement, etc.
ADALOG is providing training, consultancy, expertise and custom development in Ada and related software engineering techniques. For more information about our services:
2 rue du Docteur Lombard 92130 ISSY-LES-MOULINEAUX FRANCE |
Tel: +33 1 41 24 31 40 Fax: +33 1 41 24 07 36 |
E-m: info@adalog.fr
URL: https://www.adalog.fr/en/adalog.html |
This package provides a sophisticated trace facility,
especially valuable for multi-tasking programs. The child package
Tracer.Timing
also provides
utilities for measuring execution time, and the child package Tracer.Assert provides utilities for checking that
certain properties of your program are actually met. To get an
idea, compile and run the test program (ttracer.adb
).
The user interface of Tracer can be taylored to speak various
languages; currently, English and French are supported, and
English is the default. If you want Tracer to use another
language, please read the comments in package Tracer_Messages
.
We will gladly accept any voluntarily effort to support other
languages.
To use the package, you put calls to various Trace
procedures into your code (don't forget to "with"
package Tracer
and/or package Tracer.Timing
or package Tracer.Assert
). You can specify when you
enter or exit a procedure, so that trace messages are properly
indented. Indentation is kept on a per-task basis, and traces
from different tasks are clearly separated. You can even chose to
trace messages only from a given, or several, tasks (or except
from one or several tasks).
When you start your program, Tracer is automatically activated and pauses with the following message:
**Tracer** : Pause (Trace Mode: Enter=Normal, 'S'=Step, 'I'=Ignore Tracer) **Tracer** Command? ('H' for Help) :
Current_Error
. trace
. If the file exists, you
are prompted for permission to overwrite it. These are the most common answers at that point, but actually, any response allowed at a regular pause (see Controlling traces) is allowed.
Once your program is debugged, remove the trace calls and the "with
Tracer;"
from your code. Remove the Tracer package
itself from your path, and recompile. If you left inadvertantly
some trace calls, the compiler will tell you... This is why it is
MUCH better to use this package rather than simple calls to Put_Line
.
procedure Trace (Message : String);
This is the simplest trace procedure. It simply prints its
argument, according to the current indentation level. If the
message includes control characters, they are translated to ^X
notation (i.e. a CR will be displayed as ^M
). This
can be handy in dectecting problems caused by control characters
in strings.
type Trace_Flag is (Start, Stop); procedure Trace (Flag : Trace_Flag; Message : String);
This procedure is intended to trace subprograms calls. Put a Trace(Start,...)
when you enter a subprogram, and a Trace(Stop,...)
when you exit it. The message can be anything, but generally you
will like to put the name of the entered subprogram here.
Entering and exiting subprograms is marked with arrows ("=>
"
or "<=
") in the trace, and the
indentation level is changed accordingly.
type Tracer_String (<>) is limited private; type Tracer_String_Access is access Tracer_String; function "+" (Item : String) return Tracer_String_Access; type Auto_Tracer (Message : Tracer_String_Access) is limited private;
Alternatively, you can declare an object of type Auto_Tracer
,
initialized with the name of the subprogram, such as in:
Tracer: Auto_Tracer (+"My_Subprogram");
The initialization of this object will automatically call Trace
(Start, ...)
, and its finalization will call Trace (Stop,
...)
. It is especially convenient for tracing functions
that return complex expressions, since the Finalization (and
therefore the call to Trace (Stop...)
) happens after
the evaluation of the return expression.
procedure Trace (Message : String; Value : Boolean);
This procedure allows you to trace boolean values. It prints
the message, followed by the image of Value
.
type Any_Int is range System.Min_Int .. System.Max_Int; procedure Trace (Message : String; Value : Any_Int);
This procedure allows you to trace integer values. It prints
the message, followed by the image of Value
. Since
the type Any_Int
is compatible with any integer
type, you can call it as:
Trace("Value of X:", Any_Int(X));
whatever the type of X is.
type Any_Float is digits System.Max_Digits; procedure Trace (Message : String; Value : Any_Float);
This procedure allows you to trace float values. It prints the
message, followed by the image of Value
. Since type Any_Float
is compatible with any floating point type, you can call it as:
Trace("Value of X:", Any_Float(X));
whatever the type of X
is.
procedure Trace (Message : String; Raised_Exception : Ada.Exceptions.Exception_Occurrence);
This procedure is useful to trace exceptions. It prints the
message, followed by the exception name of Raised_Exception
.
It is typically put in an exception handler :
exception when Occ : others => Trace ("Unexpected exception raised : ", Occ); raise;
procedure Trace (Message : String; Value : Ada.Tags.Tag);
This procedure allows you to trace tags (it prints the expanded name of the tag). Typically, when you expect a given tagged type and the one you get is not the one you expected, you can write:
Trace("Tag of X:", X'Tag);
type Tracer_Stream_Access is access all Ada.Streams.Root_Stream_Type'Class; Trace_Stream : constant Tracer_Stream_Access;
This stream allows you to trace any type, by writing something like:
X : T; begin T'Write (Trace_Stream, X);
Output will be the hexadecimal representation of the stream elements and their interpretation as Ascii characters. Note that the representation can be compiler dependent, so check you compiler's documentation to interpret the values; especially note that on little endians (Intel) platforms, bytes may be swapped. Note also that for composite values, this will result to a call to 'Write for each elementary type (unless you have redefined the 'Write attribute), so you will obtain several trace messages. Any use of 'Read on this stream will raise Program_Error.
type Tracer_Counter is range 1..25; function Tracer_Count (Counter : Tracer_Counter) return String;
Tracer provides 25 counters. Each call to Tracer_Count increments the corresponding counter and returns its value as a string. This can be useful to trace where you are in a loop:
loop Trace ("Iteration" & Tracer_Count (1)); ...
procedure Pause; procedure Pause (Message : String);
These procedures simply pause (i.e. stop execution and wait
for input); this allows you to view the trace messages. At this
point, you are also given the opportunity to change trace
parameters; see Controlling traces for more
details. The form without a parameter prints a default message,
and the form with a Message
prints the message when
the Pause
is executed. Note that it is not
equivalent to calling Trace
followed by Pause
without Message
, because in the latter case some
other task could insert its own messages inbetween.
The message is output to the current target, but the pause prompt is always printed on the console. In other words, if you are tracing to a file, the pause message will not appear on the terminal. However, if you issue a '?' command, it will show you the current pause message.
procedure Keep_Trace (...); procedure Flush_Trace;
Sometimes, you don't want Tracer output to be mixed with your
own output. In this case, use Keep_Trace
instead of Trace
.
There is a Keep_Trace
procedure for each Trace
,
with the same parameters (actually, there is an extra parameter,
but you can ignore it for now, the default is OK).
With Keep_Trace
, the message is saved, and will
be printed as soon as you call any regular Trace
, or
you can force output by calling Flush_Trace
. Of
course, proper ordering of trace messages is preserved. If your
program terminates, whether normally or abnormally, for any
reason, all outstanding kept messages are printed.
Trace messages originate from source tasks and are directed to a target file. By default, all tasks in the program are sources, and the target file is the console, but this can be changed either by calling subprograms provided by package Tracer, or by giving commands when the program pauses. We give here the description of the commands; corresponding subprograms are described under Advanced features.
Whenever the program pauses, you are prompted with the following message :
Command? ('H' for help) :
At that point, you may simply type 'Enter', or a string
consisting of characters representing trace commands. Execution
will resume after all commands have been analyzed, if the string
includes any of the "Go" commands described below.
"Enter" alone is equivalent to repeating the previous
"Go" command. The key letters described here are for
the English version of Tracer; if you compiled Tracer for a
different language, please refer to the Tracer_Messages_XXX
package for your language.
Pause
. By convention, 'G0' (zero)
means "don't pause unless there is an
explicit call to pause". 'G' alone is a
shorthand for 'G0'.Marked
'
and 'Unmarked'
. The "selected
task" is the one that originated the last
message; if in doubt, issue the '?' command which
will tell you which is the currently selected
task. trace
.
The first time this command is issued, the file
is created, or overwritten (after asking for
permission) if it exists. Current_Error
).
Pause
is forced. All kept
messages will be printed. This is useful if you
suspect infinite loops or dead-locks. Once you
have gotten control back, you may for example
call the user watch procedure to inspect the
state of your variables. You can also call one of
your own procedures instead of Pause
when the timer expires; see Advanced
features. By convention, 'D0' (zero) cancels
the timer. 'D' alone is a shorthand for 'D0'.You can use the previous facilities normally from several tasks. The package is completely protected for multi-tasking, so outputs (and pauses) from several tasks will be properly serialized, and no race condition can occur within Tracer nor can any deadlock occur due to Tracer.
When Tracer discovers a call that does not come from the main task, it enters multi-tasking mode. In this mode, trace messages are prefixed with the Task_ID (except for messages that originate directly from Tracer internal tasks). When it enters this mode, Tracer tells you the Task_ID of the main task, so you can identify it in the subsequent traces.
Whenever there is a task switch, traces are separated with a line of hyphens. Indentation levels are kept on a per-task basis, so they are meaningful even in multi-task mode. Note also that task IDs are at the beginning of the line: if you output the trace to a file, and then sort the file (taking as a key the text before the colon), you'll get a complete trace for each task independently. Under Unix, the magic formula is:
sort -s -t: +0 -1 trace >trace.sorted
Another use is to "grep" the trace file with the task_ID to extract all messages output by a given task.
There are special issues if you want to trace calls to protected operations.
Keep_Trace
procedures are not potentially blocking, so it
is safe to call them. All other procedures are
potentially blocking. Current_Task
is not meaningful if called
from within a entry body. Since Keep_Trace
cannot call Current_Task
to know the
currently executing task, you must tell it; that's why
all Keep_Trace
procedures have an extra
parameter (Caller
) of type Task_ID
.
The default value is Current_Task
, but from
an entry body, you should provide it with the 'Caller
for the entry. In short, use it as: entry body E when ... is Keep_Trace (Start, "Entering E", Caller => E'Caller); ... Keep_Trace (Stop, "Leaving E", Caller => E'Caller); end;
Note that if you forget to mention the Caller
parameter in a Keep_Trace
in an entry body,
GNAT will warn you with the message
"Current_Task" should not be used in
entry body (RM C.7(17))
A task may be marked, and you can chose to trace only marked
tasks. In other words, when a Trace
(or Keep_Trace
)
procedure is called, the request is ignored if the calling task
is not marked. Conversely, you can chose to trace all but the
marked tasks; see Controlling traces.
The mark status of a task can be toggled during a pause (see Controlling traces). Alternatively, it can be set (and retrieved) by program:
package ATI renames Ada.Task_Identification; procedure Mark (Target_Task : ATI.Task_ID := ATI.Current_Task) procedure Unmark (Target_Task : ATI.Task_ID := ATI.Current_Task) function Is_Marked (Target_Task : ATI.Task_ID := ATI.Current_Task) return Boolean;
Note that by default these procedures mark or unmark the
current task, but that you can mark or unmark any task. This
allows for example the main program to mark the tasks that are to
be traced, or a server to mark its clients (thanks to the E'Caller
attribute).
During its initialization, Tracer checks for a file named trace.ini
in the current directory. If it exists, it reads its command from
this file, and returns to the console when the file is exhausted.
Blank lines and lines whose first character is '#' are ignored.
While reading from the file, all confirmation questions (whose
responses are Yes/No) allways default to Yes and are not read
from the file nor from the console. This means that for example,
if you are tracing to a file and the file already exists, Tracer
will automatically overwrite it without asking the question. This
is intended to allow the same script to work whether the trace
file exists or not.
As a special case, if the file trace.ini
exists
and is empty (like a symbolic link to /dev/null
under Unix for example), Tracer will automatically enter "Ignore"
mode and will not print anything. This allows leaving calls to
Tracer in a completely silent way, in order, for example, to
provide "beta" versions to your testers. If something
goes wrong, you just need to delete the trace.ini
file to reactivate Tracer.
type Procedure_Access is access procedure; procedure Set_Watch (To : Procedure_Access);
This procedure allows you to pass a pointer-to-procedure to Tracer. The procedure will be called during a pause when the 'W' command is issued (see Controlling traces). Such a procedure can be used to print the values of internal variables, thus providing a kind of "watch" facility. All regular Tracer procedures can be used from the watch procedure.
type Trace_Source is (All_Tasks, Marked_Only, Marked_Excepted); procedure Set_Source (To : Trace_Source); function Current_Source return Trace_Source;
These subprograms allow you to set or query the source tasks at any time.
type Trace_Target is (Console, File, None); procedure Set_Target (To : Trace_Target); function Current_Target return Trace_Target; File_Trace_Denied : exception;
These subprograms allow you to change or query the current
target. There is no restriction: you can switch freely between File
and Console
for example. The first time you switch
to File
, if a file named trace
already
exists, the user is asked for permission to overwrite. If
overwriting is denied, then the File_Trace_Denied
exception is raised.
procedure Abort_Main;
This procedure unconditionnally terminates your program, including all tasks. All kept messages are printed. This is useful when you have reached the point where your program is having trouble, and you want to stop it immediately without caring for task terminations. See caveat however.
generic with procedure To_Do; Label : String := ""; package Last_Will is private ... end Last_Will;
When instantiated, this (empty) package registers the
procedure passed as To_Do
to execute it after the
main program is left. This is quite similar to a "watch"
procedure, except that the given procedure is automatically
called after program termination (for whatever reason, of course,
including general abort). Any Tracer service can be called from
the To_Do
procedure. This can be quite handy to
check, for example, that a counter of ressources has reached 0
when the program terminates. It is also possible to give Tracer's
procedure Pause as a To_Do
procedure, therefore
giving you a last chance to check your program after the
main program has been left.
This package can only be instantiated at accessibility level 0
(i.e. in a library package, but not in a procedure, not
even the main program). We strongly recommend to instantiate this
package at the very end of the containing package; this will
ensure that your To_Do
procedure is called before
anything in the package gets finalized.
type Procedure_Access is access procedure; Off : constant Duration := 0.0; procedure Set_Timer (How_Long : Duration; Processing : Procedure_Access := null);
This subprogram allows you to change the remaining time before
the timer is triggered, and optionaly the procedure called when
it is triggered. You can restart the timer at any time, or
disable it by calling Set_Timer(Off)
.
If you pass a pointer to a parameterless procedure as the
second argument (Processing
), the provided procedure
will be called instead of Pause
(the default). This
is useful if you want to print the value of variables, etc. once
you're deadlocked. Note however that given accessibility rules,
the provided procedure must be global. And YES, Trace
(and even Keep_Trace
) procedures can safely be used
from the Processing
procedure. You may also wish to
call Set_Timer
from your provided procedure in order
to restart the timer; this allows you to periodically sample the
state of your program. Another common usage is to pass Abort_Main
as the Processing
procedure, in order to kill your
program once it is dead-locked. If you don't specify Processing
,
the current Processing
procedure is left unchanged.
function Remaining_Time return Duration;
This function returns the time remaining until the timer is triggered.
procedure Suspend_Timer; procedure Resume_Timer;
Suspend_Timer
allows you to temporarily suspend
the timer; remaining time is saved. The timer will restart with
the saved value when you call Resume_Timer
.
Note: The timer is disabled as soon as a general abort is in progress, as a consequence of a 'q' command or of a call to Abort_Main. Calls to timer-related subprograms are ignored.
Using Abort_Main
, whether directly or by leaving
the program by answering 'q' to a pause, is very demanding on the
tasking implementation. It relies on a very precise semantic
between abort
and finalization which is difficult
for compiler-makers to implement.
As of currently, we have tried package Tracer on Gnat 3.15p for
Linux, Gnat 3.15p for Windows, and ObjectAda 7.2.1a for Windows,
as well as on earlier versions of these compilers. Some had
problems when Abort_Main
was used. Observed
behaviours were :
This is not too worrysome since all messages are printed in any case, but the exit may not be very clean, or, in the case of 3), you may have to kill the process manually. If you have the opportunity to try Tracer with other compilers, please keep us informed how it behaved by sending a note to rosen@adalog.fr.
This package provides a simple timing facility for measuring execution time of code. We wanted the timing facility itself to be as short as possible, in order to minimize the execution time of the procedures and provide more accurate timings. As a consequence, this package is not protected against tasking, reentrancy, or even recursion.
The package provides 10 timers (this can be changed by
modifying the constant Max_Timer
in the
specification - no other change is required). Each timer can be
started and stopped at will, it keeps a record of the total time
and the number of times it was started. A Report
procedure displays these values, as well as the average execution
time.
Max_Timer : constant := 10; type Timer_Index is range 1..Max_Timer; procedure Start (The_Timer : Timer_Index);
This procedure starts the given timer. If the timer is already started, the call is ignored.
procedure Stop (The_Timer : Timer_Index; With_Trace : Boolean := False);
This procedure stops the given timer. If the timer is not
started, the call is ignored. If With_Trace
is True
,
a trace message is printed giving the time elapsed between Start
and Stop
for this timer.
type Auto_Timer (The_Timer : Timer_Index; With_Trace : Boolean) is limited private;
Alternatively, you can declare an object of type Auto_Timer
,
such as in:
Tracer: Auto_Timer (The_Timer => 5, With_Trace => False);
The initialization of this object will automatically call Start
on the given timer, and its finalization will call the
corresponding Stop
. If With_Trace
is True
,
a message reporting the time spent in the subprogram is printed
for each call.
Using the auto-timer is especially convenient for timing
procedures with multiple returns, or functions that return
complex expressions, since the Finalization (and therefore the
call to Stop
) happens after the evaluation of the
return expression. On the other hand, controlled types add some
overhead, therefore the measurement is less accurate.
procedure Reset (The_Timer : Timer_Index);
This procedure resets the timer to the initial state; all associated counters are cleared.
procedure Name (The_Timer : Timer_Index; As : String);
This procedure associates a name (the String
argument) with the timer; if defined, the name will be printed by
the Report
procedure. This is useful if you don't
remember what you are timing!
If the timer is active or has never been activated, an appropriate message will tell you.
procedure Report (The_Timer : Timer_Index);
This procedure prints the timer number, its associated name if
any, the total time spent between calls to Start
and
Stop
, the number of timing sessions, and the average
time (total time divided by number of calls). Note that printing
is done by calling Trace
; tracing conditions and
target are determined by the current state of Tracer
.
procedure Report_All;
This procedure calls Report
for all counters
whose call count is not zero. It is automatically invoked when
the program terminates. This way, you don't need to bother about
reporting: just put Start
and Stop
calls around the pieces of code you want to measure, you'll
automatically get the statistics when the program terminates.
Note also that the profile of Report_All
is
conformant with the type Procedure_Access
: you can
use it as a watch procedure.
This package supports run-time assertion, i.e. checking that certain things happen (or don't happen) in your program.
procedure Check (Assumption : Boolean; Message : String);
If Assumption
is False
, calls Trace
with the given message.
Max_Detector : constant := 10; type Detector_Index is range 1..Max_Detector; type R_Detector (Num : Detector_Index; Message : Tracer_String_Access) is limited private;
This is intended to help you discover reentrancy problems, i.e.
when two tasks call some subprograms at the same time when they
should not. If a task calls a subprogram which declares an object
of type R_Detector
(shorthand for Reentrancy
Detector) while another task is executing a subprogram which also
declares an object of type R_Detector
with the same Num
value, Trace
is called with a message identifying
the tasks, and both messages. For example, detecting plain
reentrancy in a subprogram just requires you to declare:
Detector : R_Detector (1, +"My_Subprogram_Name");
Tracer_String_Access
,
see "The basic trace procedures".
If you need more different detectors, simply change the
value of the constant Max_Detector
, no other
change is necessary.Q:How do I skip the next 100 messages?
A:From a pause, give the command "NG100": set
target to none, pause after 100 messages. Then resume with "CS"
for example: set target to console, step.
Q:What is the difference between the "N"
command (trace to None) and the "I" command (Ignore
Tracer)?
A: "N" temporarily disables output, but Tracer is
still active. All pauses are executed, giving you a chance to
resume traces. When you issue an "I" command, Tracer is
totally deactivated, there is no way to reestablish it (actually,
it even stops its internal tasks and releases as much space as it
can).
Q: How to identify which tasks corresponds to a task ID
?
A: Start the task with a trace like : Trace ("This
task is the controller")
; Since the message shows the
task-id, you'll know the correspondance. Note that the task ID
image provided by Gnat includes the task's name.
Q: There is a Trace
function for floating-point
types, but not for fixed-points. Why?
A: It is more difficult to define an "Any_Fixed"
type than an "Any_Float". And since fixed points can be
converted to floating points, it is just as easy to trace fixed
point values with the trace function for floating points.
Q: Why provide a fixed number of counters? It seems
simple to declare a Counter type for example...
A: Yes it is, but this would require the user to declare
such objects. The idea of Tracer is that you just add calls to
Trace, and as far as possible, you don't need to add anything
else, especially declarations. 25 counters should be more than
enough; normally, you don't leave many traces in your program,
you add them when you have a problem, and you remove them when
you have found the cause. Therefore, we favour ease of editing
over generality. Of course, this is due to the particular context
of Tracer, not applicable to general Ada programming!
Q: Why provide two different Pause
functions, with and without message, rather than simply a Pause
with a default value ?
A: The parameterless Pause
can be provided as
the Processing
action to the timer, but not the
other one.
Q: During a pause, I asked to trace only marked tasks,
and I had only one such marked task. However, after I typed 'G',
I got messages from other tasks.
A: Those messages were sent by other tasks while you were
in the pause (don't forget that during a pause, other tasks
continue to execute in the background). Of course, the messages
were blocked until you typed 'G', and they appear as soon as you
restart execution.
Q: After a 'Q' answer during a pause, the program
doesn't seem to stop, it even executes pauses...
A: As with the previous question, you still get messages
which were sent during the the pause, and also messages that are
sent by finalization routines executed as a consequence of the
general abort. If you have some automatic pause (as in Step mode),
they are normally executed. If you want to leave immediately and
silently, type "IQ" (Ignore and Quit).
Q: I call my 'watch' procedure from a Pause, but
nothing is printed...
A: If you are currently tracing to a file, traces from
your 'watch' procedure go to the file, like all other traces. Set
traces back to console ('C'), call your watch procedure ('W'),
and then return the trace to the file ('F').
Q: Can I trace finalizations ?
A: Yes, and no message will ever be lost, even if you
finalize your own global variables due to an abort of the main
task... Guaranteed!
Q: Are you really, REALLY sure that no message
can be lost ?
A: OK, if you insist :-)... There is one case where
messages are lost: if you run short of memory, since Tracer uses
allocators. However, in that case, you'll see a message telling
you that messages have been lost. And of course, if you later
release space, Tracer will resume its normal operation and the
messages will appear at the right place. Note that the task-Id of
this "Messages lost" message is the one of the first
lost message.
Q: How can I trace deadlocks with protected types ?
A: Trace your protected operations (with Keep_Trace
,
of course); In the main program, call Set_Timer
with
a duration long enough to allow the dead-lock to occur. The main
program will pause at that time, all kept messages will be
printed, and you'll be free to call a user watch procedure to get
more information.
Q: I gave a 'D' command with a delay value, but Tracer
still says that the timer value is infinite...
A: You are pausing while a general abort is going on ('q'
command, or a call to Abort_Main
). The timer is
disabled in this case. If you issue a '?' command, Tracer will
tell you that a general abort is in progress.
If you are curious about how package Tracer works, you can view the implementation notes. But of course, this is not necessary to use the package.
If you found this package useful...
If you think that it would have taken an awful time to write it
yourself...
If it showed you some usages of Ada that you didn't think about...
Maybe you should consider using Adalog's consulting and training services !