racket/collects/errortrace/scribblings/errortrace.scrbl

487 lines
19 KiB
Racket

#lang scribble/doc
@(require scribble/manual
scribble/bnf
(for-label scheme
errortrace
errortrace/errortrace-lib
errortrace/stacktrace))
@title[#:tag "top"]{@bold{Errortrace}: Debugging and Profiling}
@bold{Errortrace} is a stack-trace-on-exceptions, profiler, and
coverage tool for MzScheme. It is not a complete debugger; DrScheme
provides more. Meanwhile, using Errortrace might be better than
MzScheme's limited stack-trace reporting.
@table-of-contents[]
@; ----------------------------------------------
@section[#:tag "quick-instructions"]{Quick Instructions}
First, throw away @filepath{.zo} versions of your program---at least
for the modules or files that should be instrumented for error
reporting or profiling.
Then,
@itemize[
@item{If your program has a module file @nonterm{prog}, run it with
@commandline{mzscheme -l errortrace -t @nonterm{prog}}}
@item{If you program is a non-module top-level sequence of
definitions and expressions, you can instead add
@schemeblock[(require errortrace)]
to the beginning of the program or start MzScheme with the @Flag{l} option before the
arguments to load your program:
@commandline{mzscheme -l errortrace ...}}
@item{If you have no main program and you want to use
MzScheme interactively, include the @Flag{i} flag
before @Flag{l}:
@commandline{mzscheme -i -l errortrace}}
]
After starting @schememodname[errortrace] in one of these ways, when an
exception occurs, the exception handler something like a stack trace
with most recent contexts first.
The @schememodname[errortrace] module is strange: Don't import it
into another module. Instead, the @schememodname[errortrace]
module is meant to be invoked from the top-level, so that it can install
an evaluation handler, exception handler, etc.
To reuse parts of the code of @schememodname[errortrace], import
@schememodname[errortrace/errortrace-lib]. That library contains all
of the bindings described here, but does not set the compilation
handler or the error display handler.
@; ----------------------------------------------
@section[#:tag "installing-errortrace"]{Installing Errortrace}
Invoking the
@schememodname[errortrace] module sets the compilation
handler to instrument Scheme source code. It also sets the error
display handler to report source information for an exception, and it
sets the @scheme[use-compiled-file-paths] parameter to trigger the use
of Errortrace-specific @filepath{.zo} files.
NOTE: @schememodname[errortrace] has no effect on code
loaded as compiled byte code (i.e., from a @filepath{.zo} file) or
native code (i.e., from a @filepath{.dll}, @filepath{.so} or
@filepath{.dylib} file). You can use the @DFlag{mode errortrace} flag
to @exec{setup-plt} to create @filepath{.zo} files with
Errortrace information.
Explicitly requiring @schememodname[errortrace] within a module is
generally a bad idea, since @schememodname[errortrace] sets various
parameters.
@; ---------------------------------------------
@section[#:tag "using-errortrace"]{Using Errortrace}
@defmodule[errortrace #:use-sources (errortrace/errortrace-lib)]
See @secref["quick-instructions"] for information on starting with
@schememodname[errortrace]. This chapter provides information on the
configuration of @schememodname[errortrace] after it is loaded and
installed.
Don't import @schememodname[errortrace] into another module and expect
it to work on that module. Instead, the @schememodname[errortrace]
module is meant to be invoked from the top-level (as described in
@secref["quick-instructions"]) so it can install handlers. The
functions documented in this chapter then can be used at the
top-level. The functions also can be accessed by importing
@schememodname[errortrace/errortrace-lib], which does not install any
handlers.
@; ---------------------------------------------
@subsection[#:tag "instrumentation-and-profiling"]{Instrumentation and Profiling}
By default, @schememodname[errortrace] only instruments for
stack-trace-on-exception. Profiling and coverage need to be enabled
separately.
@defboolparam[instrumenting-enabled on?]{
A parameter that determines whether tracing instrumentation is
enabled, @scheme[#t] by default. Affects only the way that source code is
compiled, not the way that exception information is reported. The
instrumentation for storing exception information slows most programs
by a factor of 2 or 3.}
@defboolparam[profiling-enabled on?]{
Errortrace's profiling instrumentation is @scheme[#f] by default. To use it,
you also need to ensure that @scheme[instrumenting-enabled] is on.
Also, profiling only records information about the time taken on the thread
that compiled the code (more precisely, the thread that instruments the code via
the @scheme[errortrace-compile-handler]).
}
@defboolparam[profiling-record-enabled on?]{
Enables/disables the recording of profiling info for the instrumented code.
The default is @scheme[#t].
Profiling information is accumulated in a hash table. If a procedure
is redefined, new profiling information is accumulated for the new
version of the procedure, but the old information is also preserved.
Depending of the source program, profiling usually induces a factor of
2 to 4 slowdown, in addition to any slowdown from the
exception-information instrumentation.
}
@defproc[(output-profile-results [paths? any/c] [sort-time? any/c]) void?]{
Gets the current profile results using @scheme[get-profile-results] and
displays them. It optionally shows paths information (if it is recorded),
and sorts by either time or call counts.}
@defproc[(get-profile-results [thd thread? (current-thread)]) list?]{
Returns a list of lists that contain all profiling information accumulated
so far (for the thread @scheme[thd]):
@itemize[
@item{the number of times a procedure was called.}
@item{the number of milliseconds consumed by the procedure's body across
all calls (including the time consumed by any nested non-tail call
within the procedure, but not including time consumed by a
tail-call from the procedure).}
@item{an inferred name (or @scheme[#f]) for the procedure.}
@item{the procedure's source in the form of a syntax object (which might,
in turn, provide a source location file and position).}
@item{optionally, a list of unique call paths (i.e. stack traces)
recorded if @scheme[profile-paths-enabled] is set to @scheme[#t].
Each call path is a pair of
@itemize[
@item{a count (the number of times the path occurred), and}
@item{a list containing two-element lists. Each two-element list
contains
@itemize[
@item{the calling procedure's name or source expression,
and}
@item{the calling procedure's source file or @scheme[#f].}]
}
]
Collecting this information is relatively expensive.}
]}
@defboolparam[profile-paths-enabled on?]{
Enables/disables collecting path information for profiling. The default is
@scheme[#f], but setting the parameter to @scheme[#t] immediately affects
all procedures instrumented for profiling information.}
@defproc[(clear-profile-results) void?]{
Clears accumulated profile results for the current thread.}
@; ------------------------------------------------
@subsection[#:tag "coverage"]{Coverage}
Errortrace can produce coverage information in two flavors: both count
the number of times each expression in the source was used during
execution. The first flavor uses a simple approach, where each
expression is counted when executed; the second one uses the same
annotations that the profiler uses, so only function bodies are
counted. To see the difference between the two approaches, try this
program:
@schemeblock[(define (foo x) (if x 1 2))
(equal? (foo #t) 1)]
The first approach will produce exact results, but it is more
expensive; use it when you want to know how covered your code is (when
the expected counts are small). The second approach produces coarser
results (which, in the above case, will miss the @scheme[2] expression),
but is less expensive; use it when you want to use the counts for
profiling (when the expected counts are large).
@deftogether[(
@defboolparam[coverage-counts-enabled on?]
@defboolparam[execute-counts-enabled on?])]{
Parameters that determine if the first (exact coverage) or second
(profiler-based coverage) are enabled. Remember that setting
@scheme[instrumenting-enabled] to @scheme[#f] also disables both.}
@defproc[(get-coverage) (listof (cons/c syntax? boolean?))]{
Returns a list of pairs, one for each instrumented expression. The
first element of the pair is a @scheme[syntax?] object (usually containing
source location information) for the original expression, and the
second element of the pair indicates if the code has been executed.
This list is snapshot of the current state of the computation.}
@defproc[(get-execute-counts) (list (cons/c syntax? number?))])]{
Returns a list of pairs, one for each instrumented expression. The
first element of the pair is a @scheme[syntax?] object (usually containing
source location information) for the original expression, and the
second element of the pair is the number of times that the
expression has been evaluated.
This list is snapshot of the current state of the computation.}
@deftogether[(
@defproc[(annotate-covered-file
[filename-path path-string?]
[display-string (or/c string? #f) #f])
void?]
@defproc[(annotate-executed-file
[filename-path path-string?]
[display-string (or/c string? #t #f) "^.,"])
void?])]{
Writes the named file to the @scheme[current-output-port], inserting an
additional line between each source line to reflect execution counts
(as reported by @scheme[get-coverage-counts] or @scheme[get-execute-counts]).
The optional @scheme[display-string] is used for the annotation: the first
character is used for expressions that were visited 0 times, the
second character for 1 time, ..., and the last character for
expressions that were visited more times. It can also be
@scheme[#f] for a minimal display, @scheme["#."], or, in
the case of @scheme[annotate-executed-file],
@scheme[#t] for a maximal display, @scheme["0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"].
}
@defparam[test-coverage-info ht hasheq?]{
The hash-table in this parameter is used to store the profile results.
}
@; ------------------------------------------------------
@subsection[#:tag "other-errortrace-bindings"]{Other Errortrace Bindings}
The @schememodname[errortrace] module also exports:
@defproc[(print-error-trace [output-port output-port?] [exn exn?]) void?]{
The @scheme[print-error-trace] procedure takes a port and exception and
prints the Errortrace-collected debugging information contained in the
exception. It is used by the exception handler installed by
Errortrace.}
@defparam[error-context-display-depth d integer?]{The
@scheme[error-context-display-depth] parameter controls how much context
Errortrace's exception handler displays. The default value is 10,000.}
@; ------------------------------------------------------
@section[#:tag "errortrace-library"]{Errortrace Library}
@defmodule[errortrace/errortrace-lib]{
The @schememodname[errortrace/errortrace-lib] module exports all of the
exports of @schememodname[errortrace], plus a few more. It does
not install any handlers.}
The additional exports are as follows:
@defproc[(errortrace-compile-handler (stx any/c) (immediate-eval? any/c))
compiled-expression?]{
Compiles @scheme[stx] using the compilation handler that was active
when the @schememodname[errortrace/errortrace-lib] module was
executed, but first instruments the code for Errortrace information.
The code is instrumented only if
@schemeblock[(namespace-module-registry (current-namespace))]
is the same as when the
@schememodname[errortrace/errortrace-lib] module was executed. This
procedure is suitable for use as a compilation handler via
@scheme[current-compile].}
@defproc[(make-errortrace-compile-handler)
(-> any/c any/c compiled-expression)]{
Produces a compile handler that is like
@scheme[errortrace-compile-handler], except that the code that the
it produces is instrumented if the value of
@schemeblock[(namespace-module-registry (current-namespace))]
is the same as when the original thunk is invoked.
In addition, when the thunk is invoked, it uses
@scheme[namespace-attach-module] to attach the
@schememodname[errortrace/errortrace-key] module and the
@schememodname['#%kernel] module to the @scheme[current-namespace].
}
@defproc[(errortrace-error-display-handler (string string?) (exn exn?)) void?]{
Displays information about the exception; this procedure is suitable
for use as an error display handler.}
@defproc[(errortrace-annotate (stx any/c)) any/c]{
Macro-expands and instruments the given top-level form. If the form is
a module named @schemeidfont{errortrace-key}, no instrumentation is
applied. This annotation function is used by
@scheme[errortrace-compile-handler].}
@defproc[(annotate-top [stx any/c][phase-level exact-integer?]) any/c]{
Like @scheme[errortrace-annotate], but given an explicit phase level
for @scheme[stx]; @scheme[(namespace-base-phase)] is typically the
right value for the @scheme[phase-level] argument.
Unlike @scheme[errortrace-annotate], there no special case for
a module named @scheme[errortrace-key]. Also, if @scheme[stx] is a module
declaration, it is not enriched with imports to explicitly load
Errortrace run-time support.}
@; -----------------------------------------------
@section[#:tag "stacktrace"]{Re-using Errortrace Stack Tracing}
@(define-syntax-rule (schemein id) (sigelem stacktrace-imports^ id))
@(define-syntax-rule (schemeout id) (sigelem stacktrace^ id))
@defmodule[errortrace/stacktrace]{
The errortrace collection also includes a
@schememodname[errortrace/stacktrace] library. It exports
the @scheme[stacktrace@] unit, its import signature
@scheme[stacktrace-imports^], and its export signature
@scheme[stacktrace^].}
@defthing[stacktrace@ unit?]{
Imports @scheme[stacktrace-imports^] and exports @scheme[stacktrace^].}
@defsignature[stacktrace^ ()]{
@deftogether[(
@defproc[(annotate (stx syntax?) (phase-level exact-integer?)) syntax?]
@defproc[(annotate-top (stx syntax?) (phase-level exact-integer?)) syntax?])]{
Annotate expressions with errortrace information. The
@schemeout[annotate-top] function should be called with a top-level
expression, and @schemeout[annotate] should be called with a nested
expression (e.g., by @schemein[initialize-profile-point]). The
@scheme[phase-level] argument indicates the phase level of the
expression, typically @scheme[(namespace-base-phase)] for a top-level
expression.}
@deftogether[(
@defproc[(make-st-mark (syntax syntax?)) (or/c #f st-mark?)]
@defproc[(st-mark-source (st-mark st-mark?)) syntax?]
@defproc[(st-mark-bindings (st-mark st-mark?)) list?])]{
The @schemeout[st-mark-source] and @schemeout[st-mark-bindings]
functions extract information from a particular kind of value.
The value must be created by @schemeout[make-st-mark]
(the shape of the value is guaranteed to be writable and not to be @scheme[#f], but otherwise unspecified).
The @scheme[make-st-mark] function returns @scheme[#f] when there is
no source location information in the syntax object.
The @schemeout[st-mark-source] extracts the value originally provided to
the expression-maker, and @schemeout[st-mark-bindings] returns local
binding information (if available) as a list of two element (syntax?
any/c) lists. The @schemeout[st-mark-bindings] function is currently
hardwired to return @scheme[null]. }
}
@defsignature[stacktrace-imports^ ()]{
@defproc[(with-mark (source-stx any/c) (dest-stx any/c)) any/c]{
Called by @schemeout[annotate] and @schemeout[annotate-top] to wrap
expressions with @scheme[with-continuation-mark]. The first argument
is the source expression and the second argument is the expression to
be wrapped.}
@defboolparam[test-coverage-enabled on?]{
Determines if the test coverage annotation is inserted into the code.
This parameter controls how compilation happens---it does not affect the
dynamic behavior of the already compiled code. If the parameter is set,
calls to @schemein[test-covered] are inserted into the code (and
@schemein[initialize-test-coverage-point] is called during compilation).
If not, no calls to test-covered are inserted.}
@defproc[(test-covered (key any/c)) (or/c (-> void?) syntax? #f)]{
This is called during compilation of the program with a key value once
for each point with the key for that program point that was passed to
@schemein[initialize-test-coverage-point].
If the result is @scheme[#f], this program point is not
instrumented. If the result is syntax, it is inserted into the code,
and if it is a thunk, the thunk is inserted into the code in an
application. In either case, the syntax or the thunk should register
that the relevant point was covered.}
@defproc[(initialize-test-coverage-point (key any/c) (stx any/c)) void?]{
During compilation of the program, this function is called with each
sub-expression of the program. The first argument is a special key
used to identify this program point. The second argument is the
syntax of this program point.}
@defthing[profile-key any/c]{
Only used for profiling paths.}
@defboolparam[profiling-enabled on?]{
Determines if profiling information is currently collected (affects
the behavior of compiling the code---does not affect running code).
If this always returns @scheme[#f], the other profiling functions are
never called.}
@defproc[(initialize-profile-point (key any/c)
(name (or/c syntax? false/c))
(stx any/c))
void?]{
Called as the program is compiled for each profiling point that
might be encountered during the program's execution. The first
argument is a key identifying this code. The second argument is the
inferred name at this point and the final argument is the syntax of
this expression.}
@defproc[(register-profile-start (key any/c)) (or/c number? false/c)]{
Called when some profiled code is about to be executed. If the
result is a number, it is expected to be the current number of
milliseconds. @scheme[key] is unique to this fragment of code---it is
the same key passed to @schemein[initialize-profile-point] for this code
fragment.}
@defproc[(register-profile-done (key any/c)
(start (or/c number? false/c)))
void?]{
This function is called when some profiled code is finished executing.
Note that @schemein[register-profile-start] and
@schemein[register-profile-done] can be called in a nested manner; in
this case, the result of @schemein[register-profile-start] should be
@scheme[#f].}
}
@section{Errortrace Key}
@defmodule[errortrace/errortrace-key]
This module depends only on @schememodname['#%kernel].
@defthing[errortrace-key symbol?]{
A key used by errortrace via @scheme[with-continuation-mark] to
record stack information.
}