============================================================ 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/bind (loc "highway.ss" 3 ) speed) (printf-b "current speed: ~a" speed) (set-running! (< speed 55)) 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 third line of "highway.ss". SPEED is a FrTime behavior that always contains the *current* value of the variable named SPEED in the target program. 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 highway example, perhaps knowing the last ten speeds that 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 (changes speed))) HISTORY-B consumes a number and an event stream (CHANGES 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 SPEED. Though this is is an improvement, we still can't *use* that list as data to see what led to the exception. We might want to puase the program when something goes awry. (printf-b "last ten speeds: ~a" (history-b 10 (changes speed))) (set-running! (< speed 55)) MzTake allows you to "pause" a target program anytime during execution. Once paused, it becomes posssible to interactively explore and compute with script variables. Once satisfied, you can easily resume execution by typing "(set-running #t)", or end it with "(kill)". 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 speed))) ============================================================ Demos 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 "FrTime" 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). ./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. ./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. ./djikstra/dijkstra-mztake.ss - Debugs a buggy implementation of Dijkstra's algorithm ============================================================ 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 a library for the FrTime languages which provides 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. 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 ...] ...) > (kill) Kills the 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. _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 functions on acting on the structures of your target program in your MzTake script. ORIGINAL FILE: (define-struct foo (a b)) (let ([x (make-foo 1 2)]) x) MZTAKE SCRIPT: (require "original-file.ss") (define/bind (loc "original-file.ss" 3) x) (foo-a x) ;; this will fail! The target program and the MzTake will have different instances of the struct, and the call to FOO-A will fail. * The break button will *not* kill runaway client processes. You must type (kill) 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. * 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. ============================================================ 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 can add trace points to first-class functions, and they will send trace update from anywhere they are passed to and invoked. ============================================================ 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