racket/collects/mztake/doc.txt
Eli Barzilay cfce6631b3 props etc
svn: r187
2005-06-16 00:22:41 +00:00

732 lines
28 KiB
Plaintext

============================================================
About MzTake
_MzTake_ is a _scripted debugger_ for PLT Scheme. It helps
programmers monitor the execution of a target program as it
unfolds (and optionally pause or resume its execution!). MzTake
gives you the power to easily write real programs that debug real
programs. You are no longer limited to a tool chest of buttons
like "add breakpoint", "step-next", "step-into", and "step-over".
MzTake scripts are written in the FrTime programming
language, which is bundled with DrScheme. FrTime supports the
implementation of reactive systems in a functional style.
The key abstraction it adds is a type of value called a 'signal',
which can change over time. FrTime infers dataflow dependencies
between signals and automatically recomputes them when necessary.
With signals (implemented as "event streams" and "behaviors"),
it is possible to respond to outside events concisely (without
using callbacks). Consider a MzTake script to monitor the
behavior of the program "highway.ss", in the demos directory
of the MzTake collection:
(define-mztake-process radar-program
("highway.ss" [values-of-speed 3 4 bind 'speed]))
(printf-b "current speed: ~a" (hold values-of-speed))
(start/resume radar-program)
This code executes a target module in the file "highway.ss"
after installing a _trace point_ (also known as a
_watch point_) just before the Scheme expression on the fourth
column of the third line of "highway.ss". VALUES-OF-SPEED
is a FrTime event stream that always contains the *current*
value (and potentially every past value) of the variable named
SPEED, as it is bound to the values corresponding to that
syntactic location.
PRINTF-B works like Scheme's PRINTF function, consuming a
format-string and fill-values, printing the result in
DrScheme's interaction pane. Whereas PRINTF accumulates
outdated text on the screen, PRINTF-B will replace old text
with updated text if any of the fill-values change. In this
invocation, it prints the current speed to screen, throughout
the execution of "highway.ss".
MzTake scripts are also powerful tools for building external
test suites. Whereas typical test cases may only assert that
the result of a computation is correct, MzTake scripts
can dynamically break open an execution, record inner state,
and *compute* with it. This allows you to confirm that the
intermediate steps which lead to a correct answer were
also correct. In the example below, we use a version of map
that operates over events in an event stream, instead of
elements in a list. We assert that all recorded speeds are
less than 55, otherwise we raise an exception:
(map-e (lambda (a-speed)
(when (>= a-speed 55) (raise 'too-fast!!)))
values-of-speed)
Of course, like most test suites, this only tells you
something went wrong. Perhaps knowing the last ten speeds that
led to this would prove useful. You could PRINTF the value
onto a new line each time, but after ten updates the screen
is already starting to fill up with information -- we are
only interested in the last ten speeds, after all.
One possible solution:
(printf-b "last ten speeds: ~a" (history-b 10 values-of-speed))
(map-e (lambda (a-speed)
(when (>= a-speed 55) (raise 'too-fast!!)))
values-of-speed)
HISTORY-B consumes a number and an event stream (VALUES-OF-SPEED),
returning a FrTime behavior containing a FIFO ordered list of
the last ten values emitted on that event stream. In this case,
HISTORY-B maintains a list of the ten most recent SPEEDS seen
on VALUES-OF-SPEED (up until the exception is raised). Though
this is is an improvement, we still can't *use* that list as
data to see what led to the exception. One possible solution:
(define last-ten (history-b 10 values-of-speed))
(printf-b "last ten speeds: ~a" last-ten)
(map-e (lambda (a-speed)
(when (>= a-speed 55) (pause radar-program)))
values-of-speed)
MzTake allows you to "pause" a target program anytime during
execution. Once paused, it becomes trivial to interactively
explore and compute with script variables (such as LAST-TEN)
in the interaction pane. Once satisfied, you can easily resume
execution by typing "(start/resume radar-program)", or end it
with "(kill radar-program)", where RADAR-PROGRAM is any MzTake
process.
Finally, FrTime provides a rich animation library. Combined
with the MzTake debugger, it takes only a few lines to animate
your algorithms and see them in action, easily letting you
confirm (or refute!) that they are working correctly.
(display-shapes (make-speed-gauge (hold values-of-speed)))
============================================================
Installing MzTake
MzTake is a DrScheme tool distributed as a self-installing
".PLT" file from the following web site:
http://www.cs.brown.edu/research/plt/software/mztake/
MzTake requires PLT Scheme v208 and higher.
============================================================
Demos
If you installed MzTake using the .PLT distribution, you can
find the demos in the following directories:
On Linux:
~/.plt-scheme/208/collects/mztake/demos
On Windows (typically):
C:\Documents and Settings\Jono\Application Data\PLT Scheme\208\collects\mztake\demos
where "Jono" is your username, and "208" is the version of
DrScheme you are running.
The demos directory contains a sub-directory for each of the demos.
For instance, the highway directory contains "highway.ss" and
"highway-mztake.ss". To run this demo, switch to the "MzTake" language
level from the "Experimental Languages" section of DrScheme's language
dialog, load "highway-mztake.ss", and click "Run". What you see is
generated by the debugging script. Each demo directory contains
the following two files: one is the program being debugged
(named after the directory), and the other is a file ending
in "...-mztake.ss" (the MzTake script).
The demos are in order of increasing complexity. When you open
them in DrScheme, don't let the amount of text overwhelm you --
the scripts themselves are only a few lines of code. However, the
commenting is *extensive* to aid even a FrTime novice who has never
written a FrTime script before!
./highway/highway-mztake.ss - The program simulates a very simple
speedometer, and the MzTake script
monitors it.
./sine/sine-mztake.ss - Plots values extracted from a program
which generates coordinates for a
single sine wave.
./montecarlo/montecarlo-mztake.ss - Visualizes the Monte Carlo integration
("throwing darts at a dartboard") used
to derive the value of pi.
./random/random-mztake.ss - Tests the quality of Scheme's random
number generator with a histogram.
./exception/exception-mztake.ss - Demonstrates how MzTake catches exceptions.
./first-class/first-class-mztake.ss - Demonstrates how you can add multiple traces
to the same variable in a file to `record'
its evolution, and how you can trace first-class
functions, such as those passed to map.
./djikstra/dijkstra-mztake.ss - Debugs a buggy implementation of
Dijkstra's algorithm
If you have just downloaded MzTake and are coming directly
to the demos, know that once started, you can easily end
execution of a debugger scipt by typing "(kill p)" into the
Interactions window (freeing up resources). You can also pause
a script with "(pause p)", and resume it with with "(start/resume p)",
where P is any MzTake process. In the "highway" demo, P is
"radar-program", and P is "p" (meaning "process") for the others.
============================================================
Functions
The demos demonstrate many ways to debug with MzTake using
FrTime, even if you are not very familiar with the language.
That said, in order to become more proficient in using MzTake,
you will want to learn more about the FrTime language.
You can refer to FrTime's own documentation by searching for
"frtime" in DrScheme's Help window. It explains how to use
time-varying behaviors and event streams in greater depth, and
also describes the many useful functions FrTime provides to work
with them.
============================================================
_Debugging with MzTake_
Conceptually, MzTake is an extension of FrTime, providing
functions that execute a target program (or many!), and
"connect" to points in its code. MzTake then provides the
running FrTime script with interesting information (such as
a variable's current value) which it derives from these
"connections". FrTime then handles the rest.
FrTime takes that information and lets the script author
compute with it, verify it, print it, make visualizations
with it, anything you would like to do.
Currently, other than the powerful monitoring facilities that
MzTake provides (see BIND and ENTRY in the next section), user
interaction is limited to pausing/resuming the running program,
and operating on a few properties and predicates over general
program state (see PROCESS:EXITED?, PROCESS:RUNTIME/SECONDS,
and PROCESS:EXCEPTIONS). In the future, MzTake will offer other
types of interaction and information, such as inspecting
(and stepping through!) the call stack.
In more depth, the debugger works on a model roughly as follows:
* A single debugging script file contains all the MzTake
processes, traces, bindings, animations, and other FrTime
code fragments that do the debugging and program monitoring.
* This script file is loaded into the Definitions window in
DrScheme, and run using the MzTake language. User interaction
with the debugger is provided through the Interactions window.
* A MzTake *process* is like an operating system that runs a group of
programs, installs hooks into them to monitor their execution,
and provides FrTime with these hooks to do computations.
* Also like an operating system, each MzTake process runs
independently of all other MzTake processes; one will not affect
another. They can "interact" in the script by adding traces and
computing with those traces.
* A MzTake process accepts a number of target-files as "clients" to
debug and monitor. Each client should contain a module program,
of the form:
(module mod-name mzscheme
... program-body ... )
MzTake does not support debugging anything other than modules.
* The first client defined for each MzTake process is *always* the
main ("top level") module. That is, START/RESUME runs the main
client module, in much the same way that you would run it in
DrScheme (in the "module ..." language). It is assumed the module
has "side-effects" which start the target program.
The rest of the files traced in a DEFINE-MZTAKE-PROCESS are modules
used *by* the main module, allowing you to see what is going
on deeper than in the main-module. For example:
(define-mztake-process p1
("my-stack-tests.ss")
((lib "my-stack.ss" "my-stack") [s-push-p1 3 29 bind 'insert-value]
[s-pop-p1 10 16 bind 'return-value]))
"my-stack-tests.ss" is the main module. Suppose it is a test-suite
for "my-stack.ss"; the test suite asserts that the stacks are not
working as expected. You may want to use these traces to test how
"my-stack.ss" is operating "inside" during the test-suite.
Watch the pushes and pops and see how they correlate to what you expect.
* The same module can be traced differently for each MzTake processe.
Lets say that in the same script you want to see why the stack is using
a lot more memory than expected. You can set traces to count how many times
spaces is allocated and cleared and see if they are equal.
(define-mztake-process p2
("my-stack-tests.ss")
((lib "my-stack.ss" "my-stack") [s-allocates-p2 22 2 entry]
[s-clears-p2 28 2 entry]))
This installs an ENTRY trace at the function entry point for
ALLOCATE-STACK and CLEAR-STACK in "my-stack.ss". Every time
those functions get called, these traces will send a "#t" event,
and could be counted using COUNT-B.
* Once a MzTake processe is defined, and all the script code operating
on traces is defined, START/RESUME can be called on the process
to begin its execution.
* All of the variables defined by traces (BINDs and ENTRYs on the active
MzTake processes) are available simulatenously in the script.
* Lastly, proceses can be started, paused, resumed, and terminated.
See START/RESUME, PAUSE, KILL, KILL-ALL.
============================================================
MzTake itself defines the following functions:
_Installing Trace Points_
Currently, MzTake offers two types of traces: ENTRY and BIND.
ENTRYs are event streams that get a "#t" event every time the
target program reaches the trace point. Binds are event streams that
ping the value of one or more variables when the trace point is reached.
> (define-mztake-process process-name
[target-filename trace-clause ...] ...)
Where trace-clause is one of the following:
<1> [trace-name line-number column-number ENTRY]
<2> [trace-name line-number column-number bind 'variable-name]
<3> [trace-name line-number column-number bind '(variable-name ...)]
DEFINE-MZTAKE-PROCESS defines the variable process-name,
whose value is a MzTake process object. That object
can be passed to functions such as START/RESUME, KILL,
and "process:runtime/milliseconds", documented in the next
section.
DEFINE-MZTAKE-PROCESS installs trace points in one or many files,
as indicated by the trace-clauses. The target-filename can
be any file specification accepted by the standard REQUIRE
syntax for modules:
* Absolute path:
(define-mztake-process p [(file "/home/me/test.ss") [brk 10 7 ENTRY]])
* Relative path:
(define-mztake-process p ["../test.ss" [brk 10 7 ENTRY]])
* Library path:
(define-mztake-process p [(lib "test.ss" "collect-dir") [brk 10 7 ENTRY]])
For each trace-clause in the call to DEFINE-MZTAKE-PROCESS,
the trace-name is a variable name bound at the
top-level, whose value is a FrTime event
stream. Each time the execution of the target
reaches the given line-number and column[*], the
debugger emits an event on that stream. The value of
that event depends on which of the three kinds of
trace-clause was used, as follows:
<1> The value of the event is #t (an ENTRY trace).
<2> The value of the event is the value of variable-name,
in the target program, at the location of the
trace point (a BIND trace).
<3> The value of the event is a list containing one
element for each variable name given. The value
of each element is taken from the variable of
that name in the target (as in <2>).
Trace points do not themselves pause the
program. Unless a MzTake process is suspended using
the PAUSE function (below), execution resumes after
the MzTake script processed the event.
[*] Valid locations to add traces to are almost
always one character to the left of open-parentheses, "(",
open-square-braces, "[", or to the left of the first
character of a symbol/name (LET is a special exception,
see Known Problems for more information on tracing LET):
(code [more-code ...] ...)
^^ ^^ ^
[*] To obtain accurate line/column information when
setting up trace points, make sure you turn off
DrScheme's "Wrap Text" feature under the "Edit"
menu. Alternatively, you can position your cursor
at the location where you want to add a trace,
and click MzTake's "Syntax Location" button on the
main DrScheme toolbar. A message-box will tell
the correct line and column numbers to use.
_Operations on MzTake Processes_
The following functions operate on MzTake processes,
and can be used in the Interactions window.
> (start/resume process-name)
Start the execution and monitoring of the DEFINE-MZTAKE-PROCESS,
process-name. If the process given to START/RESUME is already
running, and was paused with the function PAUSE (below),
START/RESUME resumes its execution.
Script statements are executed top-down, sequentially.
In general, you want to call start/resume at the end of
the script, or in the interactions pane after you
start running the script. Otherwise, a race condition may
develop, where your script may miss events from the
beginning of the execution.
> (pause process)
Suspends the execution of the given mztake
process. Use START/RESUME to resume execution.
> (kill process)
Kills the target process and releases all resources
it used -- you cannot START/RESUME after a KILL.
Closing a FrTime animation/graphics window will *not*
kill a running MzTake process. If it does not terminate
on its own, you may kill it with "(kill p-name)" or
"(kill-all)" in the Interactions window.
> (kill-all)
kill-all kills all the processes currently running
under MzTake -- use this when it seems a process is
out of control and needs to be stopped immediately.
Has the same effect of calling KILL on each process
you defined and START/RESUME'd in the script.
> (process:exceptions process)
Returns an event stream. If the target process
throws an uncaught exception, the exception will
appear on this stream.
> (process:runtime/seconds process)
Returns a FrTime time-varying value which counts the
number of seconds elapsed in the execution of the
given process (not counting time spent suspended by
PAUSE). Includes garbage-collection time.
> (process:runtime/milliseconds process)
Returns a FrTime time-varying value which counts the
number of milliseconds elapsed in the execution of the
given process (not counting time spent suspended by
PAUSE). Includes garbage-collection time.
> (process:exited? process)
Return a time-varying Boolean value which becomes
true after the given MzTake process exited/killed.
_Useful Functions for Time-Varying Values_
Note: FrTime uses a naming convention where functions which
return behaviors have names that end in "-b", and
functions that return event streams end in "-e".
Tips: When you have a behavior that you want to turn into
an event, use (changes behavior).
When you have an event that you want to be a
behavior, use (hold event)
MzTake defines a few functions on time-varying values
that are particularly useful when debugging:
> (history-e stream)
> (history-b stream)
Keeps a complete history of all the values seen
on an event stream as a list, oldest events last.
Use with BINDs: (history-b x-trace)
> (history-e n stream)
> (history-b n stream)
Keeps a list of the last n values of a behavior
Returns a list of at most n elements, where the
elements are the n last values seem on the stream,
in order, oldest first.
> (count-b stream)
Counts number of events seen on an eventstream.
Often used directly on ENTRY traces, counting how many
times ENTRY occured: (count-b entry-trace)
Also useful to count how many times a BIND changed
by calling: (count-b (changes bind-trace))
> (largest-val-b stream)
> (smallest-val-b stream)
Keeps track of the largest/smallest values seen on a stream.
Use with BINDs: (largest-val-b (changes bind-trace)).
> (sequence-match? seq stream)
Matches a sequence of items in a list to the history
of event pings, on the event stream evs. Returns #t
when it matches, and #f otherwise. Use when you expect
a certain pattern to show up, and want to know when:
(sequence-match? '(0 1 2 1 0) (changes bind-trace))
> (printf-b format-string arg ...)
Displays the value of the behaviors with the given format,
using "~a" just like in Scheme's FORMAT function.
============================================================
Known Problems
* In general, you should not REQUIRE or use any methods
in your MzTake script that were defined in any of the files
you are putting bind-traces on:
ORIGINAL FILE:
(define (my-fun some-struct) ...)
MZTAKE SCRIPT:
(require "original-file.ss")
(define-mztake-process p ("original-file.ss" [val 10 12 bind 'my-struct]))
(my-fun (hold val))
Sometimes this causes unusual errors. These problems usually only
show up if you are binding to structs (defined in the same file) and
passing those bindings to functions (defined in the same file).
You have been warned.
* The break button will *not* kill runaway client processes.
You must type (kill process-name) or (kill-all).
* Some legal syntax locations (used in setting trace points)
are unreachable during program execution (they do not get
triggered and produce empty eventstreams). So far, this only
shows up in LETs (the trace point being one line above,
and one character to the left of the carrot):
(define x 12)
(let ([x (add1 x)]) x)
^ ^^^
Recommended syntax locations to use for trace points:
(define x 12)
(let ([x (add1 x)]) x)
^ ^^ ^ ^
* Don't rely entirely on MzTake to complain when you change
target code and your line/col locations in the script are out
of date. It can only raise an error if the locations are invalid.
* MzTake has not been tested for stability if the target is using
multiple threads. This only applies to threaded modules
*with* traces on them -- other REQUIRE'd modules will work
as expected.
* Error handling is not perfect -- e.g., the little "bug"
buttons on syntax errors don't reference the correct code.
However, the messages that are printed are as accurate as
possible.
* process:running? tells you if the process is currently,
actively, running. It might be useful to you, and will
be in the next release.
* On particularly fast computers, when running scripts with a
very high trace point density (traces are hit constantly,
potentially hundreds in a second, like in the Monte Carlo,
random-xs, and sine demos), the FrTime animation window may
appear unresponsive because of how fast it is redrawing.
* Currently, if you are running traces on two modules with the
same name, IN the same process, though in different directories,
there will probably be some sort of name-clash and strange error.
This will be fixed.
* If you find that sometimes it seems one of the breakpoints you
set in a file REQUIRE'd by the main client module, your problem
may be that the file-specification you used is different in the
script than it is in the main client module (occuring in REQUIREs
that use sub-directories):
MAIN CLIENT:
(require (lib "my-lib.ss" "mycollect" "private"))
MZTAKE SCRIPT:
(define-mztake-process p ("main.ss")
((lib "my-lib.ss" "mycollect/private") [traces...])
This seems to be an issue with DrScheme rather than MzTake.
For instance, you get an error if you make a module like this
on Windows:
(module m mzscheme
(require (lib "my-lib.ss" "mycollect" "private"))
(provide (lib "my-lib.ss" "mycollect/private")))
This will be looked into, but keep your eyes open for it.
============================================================
Tips and Tricks
* If output seems difficult to read in the script, e.g. you ever
see "struct:signal" and a lot of garbage, try (print-struct #f)
before you do any printing, or use (value-now behavior-name) to
get a more usable/printable version of a FrTime behavior (the
caveat is that it is no longer 'reactive' and it may be out of
date after the moment it is processed).
* You may want to bind more than one variable at a certain point
so that you only get one change event -- otherwise, you will
get multiple change events even if at the same trace point
(see Known Problems).
For instance, if you trace 'x and 'y separately:
* First 'x and 'y are up-to-date.
* Then 'x updates and 'y is out-of-date.
* Then 'y updates, and both are up-to-date.
But code that draws using a position derived from X and Y
will draw twice, in two locations, one for each update,
the second one being correct.
* Order matters -- if you have more than one trace at an identical
syntax location (in the same file), the order that trace events
get updated is identical to the order they exist in the script.
For example:
(define-mztake-process p ("file.ss" [a-bind 5 55 bind 'x]
[some-bind 2 22 bind 'irrelevent]
[a-entry 5 55 entry]
[another-bind 5 55 bind 'y]))
When that trace gets evaluated, A-BIND will get the new value
of X, and relevant FrTime code will get re-evaluated. *Then*
A-ENTRY will be notified about the trace and a #t will be emitted,
(at this point in time, Y is out-of-date, but X is up-to-date). Lastly,
ANOTHER-BIND will get the new value of Y, and the trace is complete.
Of course, you will typically want ENTRY as the first trace,
and all other BINDs to be in a list, so that you get two updates,
as explained in the previous tip:
(define-mztake-process p ("file.ss" [a-entry 5 55 entry]
[x/y-bind 5 55 bind '(x y)]
[some-bind 2 22 bind 'irrelevent]))
* You can trace the *same* file in different ways by using
multiple processes on the same file, under different
contexts, and compare results. For example, in
"demos/misc/first-class-mztake.ss":
(define-mztake-process p ("first-class.ss" [x-before-let 3 29 bind 'x]
[x-in-let 4 25 bind 'x]
[x-after-let 5 11 bind 'x]))
(... code omitted ...)
(start/resume p)
is functionally equivalent to:
(define-mztake-process p1 ("first-class.ss" [x-before-let 3 29 bind 'x]))
(define-mztake-process p2 ("first-class.ss" [x-in-let 4 25 bind 'x]))
(define-mztake-process p3 ("first-class.ss" [x-after-let 5 11 bind 'x]))
(... code omitted ...)
(start/resume p1) (start/resume p2) (start/resume p3)
All the variable bindings can still be used as they were before.
* Code such as (when (= num 100) (pause p)) pauses *after*
num reaches 100, the next time a trace point is hit.
However, the next point is not processed until you
START/RESUME. See the random-xs demo.
* When you pause a MzTake process, you can play with
current bindings and explore script code interactively.
You *may* dynamically evaluate/add FrTime code to do
things like pause or kill a MzTake process based on runtime,
etc. You can even define new MzTake processes dynamically
and start/resume them, integrating and exploring the traces.
You cannot add or change existing traces dynamically.
* You can add trace points to first-class functions, and they
will send trace update from anywhere they are passed to and
evaluated.
* FrTime has two methods for drawing graphics. One runs in
constant time, and is fast, because it simply accumulates
pixels on the screen and doesn't redraw a list of objects.
See the "Monte Carlo" or "random-xs" demos for this in action.
The other method is primarily for animations which need
redrawing because things move. It slows down pretty quickly
after you have more than 1000 objects to the shape list.
See the "sine" or "highway" demos for this in action.
For more information, refer to the FrTime documentation.
============================================================
Authors and Thanks
MzTake is an experimental debugger. It should enable new
debugging approaches that were not possible (easily) before.
Please send feedback to the PLT-Scheme mailing list:
http://www.plt-scheme.org/maillist/
We are eager to hear about how you are using MzTake!
Jonathan Spiro
Guillaume Marceau
Gregory Cooper
John Clements
Shriram Krishnamurthi
Please send bug reports to: jspiro@cs.brown.edu
---
Icons for MzTake come from the Gnome Project: Nautilus Emblems.
These are provided under the GPL license.
http://jimmac.musichall.cz/ikony.php3