487 lines
19 KiB
Racket
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.
|
|
}
|