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