1 Getting started with otrace and oshell¶
This tutorial introduces the main features of otrace by explaining
how to “debug” the demo program, hello_trace.py
,
included in the distribution.
1.1 Installation¶
Download the development version of otrace from Github, or the released archive from the Python Package Index The unzipped archive should contain the following files (and some more):
hello_trace.py ordereddict.py otrace.py README.rst setup.py ...
All the code for the otrace module is contained in a single file,
otrace.py
. (For python 2.6 or earlier, you will also need
ordereddict.py
.) To use otrace without installing it, just
ensure that these files are present in the module load path.
If you wish to install otrace, use:
python setup.py install
1.2 Demo program¶
hello_trace.py
is a simple multi-threaded web server using the
BaseHTTPServer
module. It listens on port 8888 and displays a simple
form where the user inputs a number and the server displays the
reciprocal of the number. Inputting a zero value will raise an exception,
which will be used to illustrate the capabilities of otrace.
All the oshell commands used below are available in the script
demo/hello_test.osh
. Once you are comfortable with the steps in
this tutorial, you can re-run the entire tutorial using the following
command (in the otrace console):
source ~~w/demo/hello_test.osh
1.3 help, cd, ls, and view commands¶
Run hello_trace.py
from the command line (user input appears after
the prompt “>”). The help
command displays all the available oshell commands:
otrace$ ./hello_trace.py
Listening on port 8888
***otrace object shell (v0.30.0+)*** (type 'help' for info)
> help
Commands:
alias cd cdls del dn edit exec help
lock ls popd pr pushd pwd quit repeat
resume rm run save set source swapd tag
trace unpatch unpickle untag untrace up view
If you omit the command, "pr" is assumed.
Use TAB key for command completion.
Type "help <command>" or "help *" for more info
See http://code.mitotic.org/otrace for documentation
globals> help pwd
pwd [-a] # Print current working "directory"
-a Print all paths in stack; top first
The pwd
command displays the current working directory.
The ls
command displays directory names and content. The special directory name ~~
refers to the most recent trace context, and is initially
/osh/globals
. There are also other special directory names of the
form ~~<letter>:
> pwd
/osh/globals
> ls ~~
/osh/globals
> ls ~~g
/osh/globals
> ls ~~w
/Users/rsarava/app4/repo/meldr-hg/otrace
The ls
options -c, -f, -m, -v can be used to selectively display
only the classes, functions/methods, modules, and variables in
a directory. Initially we examine the globals
directory, which
contains three classes, GetHandler, MultiThreadedServer, and OShell
:
> ls
BaseHTTPServer GetHandler MultiThreadedServer OShell
Page_template Receive Request_stats SocketServer
http_addr http_port http_server logging
send sleep sys test_fun
trace_shell traceassert urlparse
> ls -c
GetHandler MultiThreadedServer OShell Receive
The cd
command is used to change directories. Switching to the
directory of class GetHandler
, we note that it supports several methods, many of which
are inherited:
> cd GetHandler
osh..GetHandler> ls -f
address_string date_time_string do_GET end_headers
finish handle handle_one_request log_date_time_string
log_error log_message log_request parse_request
send_error send_header send_response setup
version_string
The ls
options -.. can be used to exclude inherited attributes
of a class, and we note that GetHandler
has just one method of its own:
osh..GetHandler> ls -f -..
do_GET
We can examine the source code for the Receive.respond
method using the
view
command with the -i (inline-display) option:
osh..GetHandler> cd ..
globals> cd Receive
osh..Receive> ls
respond
osh..Receive> view -i respond
def respond(self, request):
# Respond to request by computing reciprocal and returning response string
# Diagnostic print (initially commented out)
##if self.value <= 0.001:
## print "Client address", request.client_address
# Trace assertion (initially commented out)
##otrace.traceassert(self.value > 0.001, label="num_check")
# Compute reciprocal of number
response = "The reciprocal of %s is %s" % (self.value, 1.0/self.value)
return response
1.4 pr and exec commands¶
The pr
command prints out the value of a python expression. It is
the default command, and is assumed if no command is recognized. So
python expressions can usually be evaluated directly:
> pwd
/osh/globals
> pr Request_stats
{'count': 0, 'path': ''}
> Request_stats["count"]
0
> set safe_mode False
safe_mode = False
> abs(Request_stats["count"] - 1)
1
To prevent inadvertent modification of a running program through
function calls, parentheses are not allowed in pr
expressions by default.
Setting the safe_mode
parameter to False
allows their use.
The exec
command executes a python statement,
like assignment or import. The prefix ! may be used instead
of exec
. safe_mode
must be False
to use exec
:
> !Request_stats["count"] = 2
1.5 trace command¶
The trace
command is used to trace functions and methods. Without
any options, it simply traces exceptions. The -c <condition>
option, where <condition>
may be
call
, return
, or all
, may be used to trace function/method
calls, returns, or both. <condition>
may also be
argname1.comp1==value1,argname2!=value2,...
to trace on argument
value matching (values with commas/spaces must be quoted; the special
argument name return
may also be used).
Without any arguments, the trace
command displays currently traced names.
Next, we initiate tracing on the respond
method using the
trace
command:
globals> cd ~~g
globals> cd Receive
osh..Receive> trace respond
Tracing Receive.respond
osh..Receive> trace
Receive.respond
Now we are ready to load the URL http://localhost:8888 in the
browser, and enter numbers. Instead of using the browser, in
this demo we will use the function submit
that simulates browser
input from the user. The command “submit(22)” would be equivalent
to the user entering 22. A log message is generated for each value, and the
zero input value triggers a ZeroDivisionError
exception in the
respond
method. In the exception backtrace shown below, note
the additional methods otrace_wrapped
and
otrace_function_call
between do_GET
and respond
.
These are inserted by otrace
for tracing:
osh..Receive> submit(3)
rootW path=/?number=3
<span>The reciprocal of 3.0 is 0.333333333333</span>
osh..Receive> submit(0)
rootW path=/?number=0
Receive.respond:ex-ZeroDivisionError:05-08-45
rootE ERROR: float division by zero
Server error:
Traceback (most recent call last):
File "./hello_trace.py", line 76, in do_GET
resp = Page_template % recv.respond(self)
File "/Users/rsarava/app4/repo/mitotic/otrace/otrace.py", line 4601, in otrace_wrapped
return cls.otrace_function_call(func_info, *args, **kwargs)
File "/Users/rsarava/app4/repo/mitotic/otrace/otrace.py", line 4373, in otrace_function_call
return_value = info.fn(*args, **kwargs)
File "./hello_trace.py", line 104, in respond
response = "The reciprocal of %s is %s" % (self.value, 1.0/self.value)
ZeroDivisionError: float division by zero
When a trace condition occurs, like an exception in a traced function or method, a trace id
GetHandler.respond:ex-ZeroDivisionError:05-08-45
is generated and displayed,
as shown above. Also, the default action of the trace
command is
to create a new virtual directory
/osh/recent/exceptions/GetHandler.respond/ex-ZeroDivisionError/05-08-45
to hold the trace context for the event. The shorthand notation
~~ can be used to display the most recent trace context:
osh..Receive> ls ~~
/osh/recent/exceptions/Receive.respond/ex-ZeroDivisionError/05-08-45
osh..Receive> cd ~~
The trace context contains information about the function like argument values and the call stack.:
Receive..08-45> ls
__trc __up request self
Receive..08-45> ls -l
__trc = {exc_context, thread, framestack, frame, related, funcname, context, exc_stack, where, id, argvalues}
__up = {path_comps, __trc, __up, __down, number, self, recv, query_args}
request = <__main__.GetHandler instance at 0x106760fc8>
self = <__main__.Receive object at 0x1068cb090>
Receive..08-45> cd __trc
osh..__trc> ls
argvalues context exc_context exc_stack frame framestack funcname
id related thread where
osh..__trc> ls -l where
where =
'__bootstrap-->__bootstrap_inner-->run-->process_request_thread-->finish_request-->__init__-->handle-->handle_one_request-->do_GET-->respond'
1.6 edit and traceassert¶
The edit
command is perhaps the most useful command in otrace. It
allows you to modify (monkey patch)
any function or method in the running program. In particular, it makes
it easy to use the “oldest” debugging technique, viz., inserting
print
statements in the code,
without having to modify the actual source code files. You can also
insert calls to traceassert
, which is the equivalent of setting
conditional breakpoints in the code.
Now that we know the there is an exception occurring in the method
respond
, we pretend that we don’t know the exact cause, and will
use traceassert
to determine the cause. The traceassert
function has the signature traceassert(condition, label="", action="")
.
As long as condition
is true, traceassert
simply returns. If
condition
is false, the call is logged and a trace context
virtual directory is created.
We suspect that the exception is caused because the user entered a
number that was too small. First, we switch off safe mode, which
disallows code editing. We then use the edit
command to modify
the respond
method in the running program to insert a
call to traceassert
. (Actually hello_trace.py
already has a
traceassert
call that is commented out. We simply uncomment it,
as well as the diagnostic print
statement, via the edit
command.):
osh..__trc> cd ~~g
globals> set safe_mode False
safe_mode = False
globals> set trace_active True
trace_active = True
globals> edit Receive.respond
Patched Receive.respond:
Note that we need to activate tracing explicitly by setting parameter
trace_active
to True
to trace traceassert
calls. (This step
is not needed when the trace
command is used, because tracing is
automatically activated.)
After the edit, the statement otrace.traceassert(number > 0.001, label="num_check")
has been inserted into Receive.respond
. In the browser, enter the number
2 and then the number 0.0005. The latter value triggers a false
condition on the traceassert
. We switch to the assert trace
context directory /osh/recent/asserts/Receive.respond/as-num_check/04-57-54
,
which allows us to examine the local variables when the assertion failed:
globals> submit(2)
rootW path=/?number=2
<span>The reciprocal of 2.0 is 0.5</span>
globals> submit(0.0005)
rootW path=/?number=0.0005
Client address ('127.0.0.1', 62008)
Receive.respond:as-num_check:05-08-51
<span>The reciprocal of 0.0005 is 2000.0</span>
globals> ls ~~
/osh/recent/asserts/Receive.respond/as-num_check/05-08-51
globals> cd ~~
Receive..08-51> ls
__up __trc request self
Receive..08-51> self.value
0.0005
Receive..08-51> request.headers
Accept-Encoding: identity
Host: 127.0.0.1:8888
Connection: close
User-Agent: Python-urllib/2.7
The default action when the traceassert
condition is false is to
create the trace context directory. The action
argument to
traceassert
can be used set a breakpoint when the assertion fails
(as explained later).
For efficiency, the trace context for traceassert
does not save the
backtrace stack local variables or source code information by default.
To enable backtracing of stack and source code, set assert_context
to a non-zero value.
1.7 unpatch and untrace¶
After debugging is complete, the unpatch
command can be used to
restore the original code for Receive.respond
.
The untrace
command can be used to switch off tracing:
globals> cd /osh/patches
patches> ls
Receive.respond
patches> unpatch Receive.respond
Unpatching Receive.respond
patches> cd ~~g
patches> trace
Receive.respond
globals> untrace Receive.respond
untraced Receive.respond
globals>
1.8 Object tag tracing¶
One of the allowed actions in the trace -a <action> -c <condition> ...
command is tag
. The tag action adds a special attribute to the
self
object if the trace condition is met at the time a function returns.
The tag attribute is just a string, usually the object’s
id
, but can also be the current time or some other string.
The presence of tagged arguments can be specified as a trace condition
for subsequent tracing of a function, using the -c tagged[<argname>]
option. The commands tag
and untag
can be used to directly
add/remove the tag attribute.
In the example below, the method Receive.__init__
will tag the
self
object if self.value
equals 1 when the method returns.
Then, we trace Receive.respond
if its argument named self
is tagged. First, we submit the value 2, which does not trigger
tagging. Next, we submit the value 1, which causes the self
object to be tagged when Receive.__init__
returns, and
then triggers a trace context for Receive.respond
because
one of its arguments is tagged:
globals> cd ~~g
globals> trace -a tag -c self.value==1 Receive.__init__
Tracing -a tag -c {'self.value==': 1L} Receive.__init__
globals> trace -c taggedself Receive.respond
Tracing -c taggedself Receive.respond
globals> submit(2)
rootW path=/?number=2
<span>The reciprocal of 2.0 is 0.5</span>
globals> submit(1)
rootW path=/?number=1
Receive:tg-self.value==1;0x103231090:17-04-42
Receive.respond:tr-taggedself;tg-self.value==1;0x103231090:17-04-42.0 self=<__main__.Receive object at 0x103231090>, request=<__main__.GetHandler instance at 0x10322f950>
<span>The reciprocal of 1.0 is 1.0</span>
globals> cd ~~
Receive..04-42.0> pwd
/osh/recent/traces/Receive.respond/tr-taggedself;tg-self.value==1;0x103231090/17-04-42.0
Receive..04-42.0> ls
__trc request self
1.9 Preserving trace contexts¶
Only a limited number of trace contexts (controlled by set max_recent
)
are retained in memory. If there are too many contexts, the oldest
contexts are deleted. The save
command can be used to preserve
trace contexts in memory. The set pickle_file
command can be used
to specify a pickle database file in which to save all trace
contexts. This pickle database file can be opened at a later time
using the unpickle
command to read trace contexts into the
/osh/pickled
directory:
Receive..04-42.0> cd ~~g
globals> set pickle_file trace_test.db
pickle_file = trace_test.db
globals> trace Receive.respond
Tracing Receive.respond
globals> submit(0)
...
globals> unpickle trace_test.db
globals> cd /osh/pickled
pickled> ls
exceptions
pickled> cd exceptions/Receive.respond/ex-ZeroDivisionError/17-06-22.0/:
osh..:> ls
__trc __up request self
osh..:>
The special directory :
is used to denote the content of the trace
context.
1.10 Monitoring using the repeat command¶
The repeat
command indefinitely repeats whatever command that
follows it, erasing the screen each time before displaying the
output. The default repeat interval is 0.2 seconds, and that
can be changed via the set repeat_interval
command.
Any user input, or a trace event will end the repeat cycle.
Here’s an example of using repeat
to monitor requests
processed by the demo web server:
> repeat ls -l Request_stats/*
1.11 Breakpoints¶
Breakpoints can be set using the -a break
option for the trace
command, or the action="break"
argument to traceassert
.
In a multi-threaded program, there may be more than one breakpoint
active at a time. To resume execution, issue the resume
command
when the current directory is within the trace context of the
breakpoint to be resumed, or specify the trace id to be resumed as
the argument.
NOTE: The trace
command cannot set breakpoints at specific lines.
Use the edit
command to insert traceassert
calls to break at
specific locations within a function or method.
Other possible actions for trace
and traceassert
include
pdb
or ipdb
, which launch the respective debuggers at the
breakpoint. The continue
command of these debuggers should be used
to return control of the terminal to otrace.
Last modified: 2016-03-31