SlideShare uma empresa Scribd logo
1 de 65
How I Built a Power Debugger
Out of the Standard Library and
Things I Found on the Internet
Doug Hellmann
PyOhio 2015
I Built a Power Debugger
Doug Hellmann
PyOhio 2015
How
I Built a Power Debugger
Doug Hellmann
PyOhio 2015
github.com/dhellmann/smiley
smiley.readthedocs.org
Features
• Record calls with data
• Remote monitoring, local
analysis
• Browse history
• New tools
flickr/Kangrex
class Publisher(object):
def __init__(self, endpoint,
high_water_mark=10000):
self.context = zmq.Context()
self.pub_socket = 
self.context.socket(zmq.PUSH)
self.pub_socket.bind(endpoint)
self.pub_socket.identity = 'publisher'
self.pub_socket.hwm = high_water_mark
def send(self, msg_type, data):
msg = [
msg_type,
json.dumps(data,
default=self._json_special_types),
]
self.pub_socket.send_multipart(msg)
class Tracer(object):
def __init__(self, publisher):
self.publisher = publisher
self.run_id = None
def trace_calls(self, frame, event, arg):
co = frame.f_code
filename = co.co_filename
if filename in (__file__,):
# Ignore ourself
return
self._send_notice(frame, event, arg)
return self.trace_calls
def _send_notice(self, frame, event, arg):
co = frame.f_code
func_name = co.co_name
line_no = frame.f_lineno
filename = os.path.abspath(co.co_filename)
for d in IGNORE_DIRS:
if filename.startswith(d):
return
# …
# …
interesting_locals = {
n: v
for n, v in frame.f_locals.items()
if (not inspect.ismodule(v)
and not inspect.isfunction(v)
and not inspect.ismethod(v)
and (n[:2] != '__' and n[-2:] != '__'))
}
# …
# …
self.publisher.send(
event,
{'func_name': func_name,
'line_no': line_no,
'filename': filename,
'arg': arg,
'locals': interesting_locals,
'timestamp': time.time(),
'run_id': self.run_id,
})
$ smiley help
usage: smiley [--version] [-v] [--log-file LOG_FILE]
[-q] [-h] [--debug]
smiley spies on your apps as they run
optional arguments:
--version show program's version number
and exit
-v, --verbose Increase verbosity of output.
--log-file LOG_FILE Specify a file to log output.
-q, --quiet suppress output except warnings
-h, --help show this help message and exit
--debug show tracebacks on errors
Commands:
complete print bash completion command
help print detailed help for another command
monitor Listen for running programs and show
their progress.
run Run another program with monitoring
enabled.
$ smiley help run
usage: smiley run [-h] [--socket SOCKET] command
[command ...]
Run another program with monitoring enabled.
positional arguments:
command the command to spy on
optional arguments:
-h, --help show this help message and exit
--socket SOCKET URL for the socket where the listener
will be (tcp://127.0.0.1:5556)
$ smiley help monitor
usage: smiley monitor [-h] [--socket SOCKET]
Listen for running programs and show their progress.
optional arguments:
-h, --help show this help message and exit
--socket SOCKET URL for the socket where to monitor on
(tcp://127.0.0.1:5556)
def _process_message(self, msg):
print 'MESSAGE:', msg
msg_type, msg_payload = msg
if msg_type == 'start_run':
print (‘Starting new run:',
msg_payload.get(‘command_line'))
elif msg_type == 'end_run':
print 'Finished run'
else:
line = linecache.getline(msg_payload['filename'],
msg_payload['line_no']).rstrip()
if msg_type == 'return':
print '%s:%4s: return>>> %s' % (
msg_payload['filename'],
msg_payload[‘line_no'], msg_payload['arg'])
else:
print '%s:%4s: %s' % (
msg_payload['filename'],
msg_payload[‘line_no'], line)
if msg_payload.get('locals'):
for n, v in sorted(msg_payload['locals'].items()):
print '%s %s = %s' % (
' ' * len(msg_payload['filename']),
n,
v,
)
print
def gen(m):
for i in xrange(m):
yield i
def c(input):
print 'input =', input
data = list(gen(input))
print 'Leaving c()'
def b(arg):
val = arg * 5
c(val)
print 'Leaving b()'
return val
def a():
print 'args:', sys.argv
b(2)
print 'Leaving a()'
a()
u'command_line': [u'simple.py']}}]
/Users/dhellmann/Devel/smiley/scratch/simple.py: 8: def c(input):
command_line = [u'simple.py']
self = <smiley.tracer.Tracer object at 0x103caff10>
MESSAGE: ['line', {u'run_id': u'e87302b2-402a-4243-bb27-69a467d4bd8e', u'timestamp':
1436367235.144169, u'line_no': 14, u'filename': u'/Users/dhellmann/Devel/smiley/scratch/simple.py',
u'func_name': u'<module>', u'arg': None, u'locals': {u'self': u'<smiley.tracer.Tracer object at 0x103caff10>',
u'command_line': [u'simple.py']}}]
/Users/dhellmann/Devel/smiley/scratch/simple.py: 14: def b(arg):
command_line = [u'simple.py']
self = <smiley.tracer.Tracer object at 0x103caff10>
MESSAGE: ['line', {u'run_id': u'e87302b2-402a-4243-bb27-69a467d4bd8e', u'timestamp':
1436367235.144468, u'line_no': 21, u'filename': u'/Users/dhellmann/Devel/smiley/scratch/simple.py',
u'func_name': u'<module>', u'arg': None, u'locals': {u'self': u'<smiley.tracer.Tracer object at 0x103caff10>',
u'command_line': [u'simple.py']}}]
/Users/dhellmann/Devel/smiley/scratch/simple.py: 21: def a():
command_line = [u'simple.py']
self = <smiley.tracer.Tracer object at 0x103caff10>
MESSAGE: ['line', {u'run_id': u'e87302b2-402a-4243-bb27-69a467d4bd8e', u'timestamp':
1436367235.144719, u'line_no': 26, u'filename': u'/Users/dhellmann/Devel/smiley/scratch/simple.py',
u'func_name': u'<module>', u'arg': None, u'locals': {u'self': u'<smiley.tracer.Tracer object at 0x103caff10>',
u'command_line': [u'simple.py']}}]
/Users/dhellmann/Devel/smiley/scratch/simple.py: 26: a()
command_line = [u'simple.py']
self = <smiley.tracer.Tracer object at 0x103caff10>
MESSAGE: ['call', {u'run_id': u'e87302b2-402a-4243-bb27-69a467d4bd8e', u'timestamp':
1436367235.144927, u'line_no': 21, u'filename': u'/Users/dhellmann/Devel/smiley/scratch/simple.py',
u'func_name': u'a', u'arg': None, u'locals': {}}]
/Users/dhellmann/Devel/smiley/scratch/simple.py: 21: def a():
MESSAGE: ['line', {u'run_id': u'e87302b2-402a-4243-bb27-69a467d4bd8e', u'timestamp':
u'command_line': [u'simple.py']}}]
/Users/dhellmann/Devel/smiley/scratch/simple.py: 8: def c(input):
command_line = [u'simple.py']
self = <smiley.tracer.Tracer object at 0x103caff10>
MESSAGE: ['line', {u'run_id': u'e87302b2-402a-4243-bb27-69a467d4bd8e', u'timestamp':
1436367235.144169, u'line_no': 14, u'filename': u'/Users/dhellmann/Devel/smiley/scratch/simple.py',
u'func_name': u'<module>', u'arg': None, u'locals': {u'self': u'<smiley.tracer.Tracer object at 0x103caff10>',
u'command_line': [u'simple.py']}}]
/Users/dhellmann/Devel/smiley/scratch/simple.py: 14: def b(arg):
command_line = [u'simple.py']
self = <smiley.tracer.Tracer object at 0x103caff10>
MESSAGE: ['line', {u'run_id': u'e87302b2-402a-4243-bb27-69a467d4bd8e', u'timestamp':
1436367235.144468, u'line_no': 21, u'filename': u'/Users/dhellmann/Devel/smiley/scratch/simple.py',
u'func_name': u'<module>', u'arg': None, u'locals': {u'self': u'<smiley.tracer.Tracer object at 0x103caff10>',
u'command_line': [u'simple.py']}}]
/Users/dhellmann/Devel/smiley/scratch/simple.py: 21: def a():
command_line = [u'simple.py']
self = <smiley.tracer.Tracer object at 0x103caff10>
MESSAGE: ['line', {u'run_id': u'e87302b2-402a-4243-bb27-69a467d4bd8e', u'timestamp':
1436367235.144719, u'line_no': 26, u'filename': u'/Users/dhellmann/Devel/smiley/scratch/simple.py',
u'func_name': u'<module>', u'arg': None, u'locals': {u'self': u'<smiley.tracer.Tracer object at 0x103caff10>',
u'command_line': [u'simple.py']}}]
/Users/dhellmann/Devel/smiley/scratch/simple.py: 26: a()
command_line = [u'simple.py']
self = <smiley.tracer.Tracer object at 0x103caff10>
MESSAGE: ['call', {u'run_id': u'e87302b2-402a-4243-bb27-69a467d4bd8e', u'timestamp':
1436367235.144927, u'line_no': 21, u'filename': u'/Users/dhellmann/Devel/smiley/scratch/simple.py',
u'func_name': u'a', u'arg': None, u'locals': {}}]
/Users/dhellmann/Devel/smiley/scratch/simple.py: 21: def a():
MESSAGE: ['line', {u'run_id': u'e87302b2-402a-4243-bb27-69a467d4bd8e', u'timestamp':
MESSAGE: ['call', {u'run_id': u'e87302b2-402a-4243-bb27-69a467d4bd8e', u'timestamp':
1436367235.144927, u'line_no': 21, u'filename': u'/Users/dhellmann/Devel/smiley/scratch/simple.py',
u'func_name': u'a', u'arg': None, u'locals': {}}]
/Users/dhellmann/Devel/smiley/scratch/simple.py: 21: def a():
MESSAGE: ['line', {u'run_id': u'e87302b2-402a-4243-bb27-69a467d4bd8e', u'timestamp':
1436367235.145128, u'line_no': 22, u'filename': u'/Users/dhellmann/Devel/smiley/scratch/simple.py',
u'func_name': u'a', u'arg': None, u'locals': {}}]
/Users/dhellmann/Devel/smiley/scratch/simple.py: 22: print 'args:', sys.argv
MESSAGE: ['line', {u'run_id': u'e87302b2-402a-4243-bb27-69a467d4bd8e', u'timestamp':
1436367235.145343, u'line_no': 23, u'filename': u'/Users/dhellmann/Devel/smiley/scratch/simple.py',
u'func_name': u'a', u'arg': None, u'locals': {}}]
/Users/dhellmann/Devel/smiley/scratch/simple.py: 23: b(2)
class DB(object):
def __init__(self, name):
self.conn = sqlite3.connect(name)
self.conn.row_factory = sqlite3.Row
try:
cursor = self.conn.cursor()
cursor.execute('select * from run')
LOG.debug('database already initialized')
except sqlite3.OperationalError:
LOG.debug('initializing database')
schema = pkgutil.get_data('smiley',
'schema.sql')
cursor.executescript(schema)
create table run (
id text primary key not null,
cwd text,
description text,
start_time int,
end_time int,
error_message text,
traceback text
);
create table trace (
id integer primary key autoincrement not null,
run_id text not null references run(id),
filename text,
line_no int,
func_name text,
trace_arg text,
locals text,
time_stamp int
);
def start_run(self, run_id, cwd, description,
start_time):
"Record the beginning of a run."
with transaction(self.conn) as c:
c.execute(
"""
INSERT INTO run (id, cwd, description,
start_time)
VALUES
(:id, :cwd, :description, :start_time)
""",
{'id': run_id,
'cwd': cwd,
'description': description,
'start_time': start_time}
)
def _process_message(self, msg):
msg_type, msg_payload = msg
if msg_type == 'start_run':
command_line = ' ‘.join(
msg_payload.get('command_line', []))
self._cwd = msg_payload.get('cwd', '')
if self._cwd:
self._cwd = (self._cwd.rstrip(os.sep) +
os.sep)
self.db.start_run(
run_id=msg_payload.get('run_id'),
cwd=self._cwd,
description=command_line,
start_time=msg_payload.get('timestamp'),
)
elif msg_type == 'end_run':
self.log.info('Finished run')
self.db.end_run(
run_id=msg_payload['run_id'],
end_time=msg_payload.get('timestamp'),
message=msg_payload.get('message'),
traceback=msg_payload.get('traceback'),
)
else:
self.db.trace(
run_id=msg_payload['run_id'],
event=msg_type,
func_name=msg_payload.get('func_name'),
line_no=msg_payload.get('line_no'),
filename=msg_payload.get('filename'),
trace_arg=msg_payload.get('arg'),
locals=msg_payload.get('locals'),
timestamp=msg_payload.get('timestamp'),
)
• Replay past runs
• Complex data types
flickr/Chris Marquardt
import json
import traceback
import types
def _json_special_types(obj):
if isinstance(obj, types.TracebackType):
return traceback.extract_tb(obj)
if isinstance(obj, type):
# We don't want to return classes
return repr(obj)
try:
data = dict(vars(obj))
data['__class__'] = obj.__class__.__name__
data['__module__'] = obj.__class__.__module__
except Exception as err:
data = repr(obj)
return data
def dumps(data):
return json.dumps(data, default=_json_special_types)
?
class EventProcessor(object):
__metaclass__ = abc.ABCMeta
@abc.abstractmethod
def start_run(self, run_id, cwd, description,
start_time):
"""Called when a 'start_run' event is seen.
"""
@abc.abstractmethod
def end_run(self, run_id, end_time, message,
traceback):
"""Called when an 'end_run' event is seen.
"""
@abc.abstractmethod
def trace(self, run_id, event,
func_name, line_no, filename,
trace_arg, local_vars,
timestamp):
"""Called when any other event type is seen.
"""
def get_runs(self):
"Return the runs available to browse."
with transaction(self.conn) as c:
c.execute(
"""
SELECT
id, cwd, description, start_time,
end_time, error_message
FROM run
"""
)
return c.fetchall()
def take_action(self, parsed_args):
self.out = output.OutputFormatter(
linecache.getline)
self.db = db.DB(parsed_args.database)
run_details = self.db.get_run(parsed_args.run_id)
self.out.start_run(
run_details.id,
run_details.cwd,
run_details.description,
run_details.start_time,
)
for t in self.db.get_trace(parsed_args.run_id):
self.out.trace(
t.run_id, t.event, t.func_name,
t.line_no, t.filename, t.trace_arg,
t.local_vars, t.timestamp,
)
self.out.end_run(
run_details.id,
run_details.end_time,
run_details.error_message,
None) # run_details.traceback
MESSAGE: ['call', {u'run_id': u'e87302b2-402a-4243-bb27-69a467d4bd8e', u'timestamp':
1436367235.144927, u'line_no': 21, u'filename': u'/Users/dhellmann/Devel/smiley/scratch/simple.py',
u'func_name': u'a', u'arg': None, u'locals': {}}]
/Users/dhellmann/Devel/smiley/scratch/simple.py: 21: def a():
MESSAGE: ['line', {u'run_id': u'e87302b2-402a-4243-bb27-69a467d4bd8e', u'timestamp':
1436367235.145128, u'line_no': 22, u'filename': u'/Users/dhellmann/Devel/smiley/scratch/simple.py',
u'func_name': u'a', u'arg': None, u'locals': {}}]
/Users/dhellmann/Devel/smiley/scratch/simple.py: 22: print 'args:', sys.argv
MESSAGE: ['line', {u'run_id': u'e87302b2-402a-4243-bb27-69a467d4bd8e', u'timestamp':
1436367235.145343, u'line_no': 23, u'filename': u'/Users/dhellmann/Devel/smiley/scratch/simple.py',
u'func_name': u'a', u'arg': None, u'locals': {}}]
/Users/dhellmann/Devel/smiley/scratch/simple.py: 23: b(2)
class DBLineCache(object):
def __init__(self, db, run_id):
self._db = db
self._run_id = run_id
self._files = {}
def getline(self, filename, line_no):
if filename not in self._files:
body = self._db.get_cached_file(
self._run_id, filename)
self._files[filename] = body.splitlines()
try:
return self._files[filename][line_no]
except IndexError:
# Line number is out of range
return ''
def take_action(self, parsed_args):
# Fix import path
cwd = os.getcwd()
if (cwd not in sys.path and
os.curdir not in sys.path):
sys.path.insert(0, cwd)
# Fix command line args
sys.argv = parsed_args.command
# Run the app
p = publisher.Publisher(parsed_args.socket)
t = tracer.Tracer(p)
t.run(parsed_args.command)
def take_action(self, parsed_args):
# Fix import path
cwd = os.getcwd()
if (cwd not in sys.path and
os.curdir not in sys.path):
sys.path.insert(0, cwd)
# Fix command line args
sys.argv = parsed_args.command
# Run the app
if parsed_args.mode == 'remote':
p = publisher.Publisher(parsed_args.socket)
else:
p = db.DB(parsed_args.database)
t = tracer.Tracer(p)
t.run(parsed_args.command)
1.ZMQ
2.Tracing
3.Command line interface
4.Database
5.Complex objects
6.EventProcessor API
7.Replay command
8.Store source files
9.Local database
1.ZMQ
2.Tracing
3.Command line interface
4.Database
5.Complex objects
6.EventProcessor API
7.Replay command
8.Store source files
9.Local database
UI Tools
class FileController(RestController):
@expose(generic=True, template='file.html')
@nav.active_section('runs')
def get_one(self, run_id, file_id):
filename, body = 
request.db.get_cached_file_by_id(run_id,
file_id)
run = request.db.get_run(run_id)
lexer = guess_lexer_for_filename(filename, body)
formatter = HtmlFormatter(linenos=True)
styled_body = highlight(body, lexer, formatter)
return {
'run_id': run_id,
'run': run,
'filename': filename,
'body': body,
'styled_body': styled_body,
}
class StyledLineCache(object):
def __init__(self, db, run_id):
self._db = db
self._run_id = run_id
self._files = {}
EXPECTED_PREFIX = '<div class="highlight"><pre>'
EXPECTED_SUFFIX = '</pre></div>'
def getline(self, filename, line_no):
if filename not in self._files:
body = self._db.get_cached_file(self._run_id,
filename)
styled_body = apply_style(filename, body,
linenos=False)
start = len(self.EXPECTED_PREFIX)
end = -1 * (len(self.EXPECTED_SUFFIX) + 1)
middle_body = styled_body[start:end].rstrip('n')
self._files[filename] = middle_body.splitlines()
try:
return self._files[filename][line_no-1]
except IndexError:
# Line number is out of range
return ''
✓ Web UI
✓ Profiling Data
✓ Call Graph
✓ Syntax Highlighting
• Only Changed Variables
• Comments
def _mk_seq(d):
return sorted(
(k, pformat(v, width=20))
for k, v in d.iteritems()
)
def get_variable_changes(older, newer):
s_a = _mk_seq(older)
s_b = _mk_seq(newer)
matcher = difflib.SequenceMatcher(None, s_a, s_b)
for tag, i1, i2, j1, j2 in matcher.get_opcodes():
if tag in {'insert', 'replace'}:
for i in s_b[j1:j2]:
yield i
Other Features
Tracing
Threaded
Applications
Static
HTML
Reports
DB
Export/
Import
Pagination
in
Web UI
Other Features
Tracing
Threaded
Applications
Static
HTML
Reports
DB
Export/
Import
Pagination
in
Web UI
0
1000
2000
3000
4000
2013-05-25
2013-05-26
2013-05-27
2013-05-28
2013-06-02
2013-06-03
2013-06-05
2013-06-06
2013-06-08
2013-06-16
2013-06-23
2013-07-14
2013-07-27
2013-07-28
2013-09-01
2013-12-28
2013-12-29
2013-12-30
2014-01-04
2014-01-12
2014-07-06
2014-08-01
0
1000
2000
3000
4000
2013-05-25
2013-05-26
2013-05-27
2013-05-28
2013-06-02
2013-06-03
2013-06-05
2013-06-06
2013-06-08
2013-06-16
2013-06-23
2013-07-14
2013-07-27
2013-07-28
2013-09-01
2013-12-28
2013-12-29
2013-12-30
2014-01-04
2014-01-12
2014-07-06
2014-08-01
• Performance
• Standard I/O
• GUI
• Compare runs
flickr/Mike Mozart
Freenode: dhellmann
doug@doughellmann.com
github.com/dhellmann/smiley
smiley.readthedocs.org
How I Built a Power Debugger Out of the Standard Library and Things I Found on the Internet

Mais conteúdo relacionado

Mais procurados

Be RESTful (Symfony Camp 2008)
Be RESTful (Symfony Camp 2008)Be RESTful (Symfony Camp 2008)
Be RESTful (Symfony Camp 2008)Fabien Potencier
 
Micropage in microtime using microframework
Micropage in microtime using microframeworkMicropage in microtime using microframework
Micropage in microtime using microframeworkRadek Benkel
 
Zf2 how arrays will save your project
Zf2   how arrays will save your projectZf2   how arrays will save your project
Zf2 how arrays will save your projectMichelangelo van Dam
 
20191116 custom operators in swift
20191116 custom operators in swift20191116 custom operators in swift
20191116 custom operators in swiftChiwon Song
 
Advanced symfony Techniques
Advanced symfony TechniquesAdvanced symfony Techniques
Advanced symfony TechniquesKris Wallsmith
 
Electrify your code with PHP Generators
Electrify your code with PHP GeneratorsElectrify your code with PHP Generators
Electrify your code with PHP GeneratorsMark Baker
 
sfDay Cologne - Sonata Admin Bundle
sfDay Cologne - Sonata Admin BundlesfDay Cologne - Sonata Admin Bundle
sfDay Cologne - Sonata Admin Bundleth0masr
 
Introduction to CloudForecast / YAPC::Asia 2010 Tokyo
Introduction to CloudForecast / YAPC::Asia 2010 TokyoIntroduction to CloudForecast / YAPC::Asia 2010 Tokyo
Introduction to CloudForecast / YAPC::Asia 2010 TokyoMasahiro Nagano
 
Bonnes pratiques de développement avec Node js
Bonnes pratiques de développement avec Node jsBonnes pratiques de développement avec Node js
Bonnes pratiques de développement avec Node jsFrancois Zaninotto
 
Cpp11 multithreading and_simd_linux_code
Cpp11 multithreading and_simd_linux_codeCpp11 multithreading and_simd_linux_code
Cpp11 multithreading and_simd_linux_codeRussell Childs
 
PHP tips and tricks
PHP tips and tricks PHP tips and tricks
PHP tips and tricks Damien Seguy
 
international PHP2011_Bastian Feder_jQuery's Secrets
international PHP2011_Bastian Feder_jQuery's Secretsinternational PHP2011_Bastian Feder_jQuery's Secrets
international PHP2011_Bastian Feder_jQuery's Secretssmueller_sandsmedia
 
[PL] Jak nie zostać "programistą" PHP?
[PL] Jak nie zostać "programistą" PHP?[PL] Jak nie zostać "programistą" PHP?
[PL] Jak nie zostać "programistą" PHP?Radek Benkel
 
13 PHPUnit #burningkeyboards
13 PHPUnit #burningkeyboards13 PHPUnit #burningkeyboards
13 PHPUnit #burningkeyboardsDenis Ristic
 
Php unit the-mostunknownparts
Php unit the-mostunknownpartsPhp unit the-mostunknownparts
Php unit the-mostunknownpartsBastian Feder
 

Mais procurados (20)

Symfony2 - OSIDays 2010
Symfony2 - OSIDays 2010Symfony2 - OSIDays 2010
Symfony2 - OSIDays 2010
 
Be RESTful (Symfony Camp 2008)
Be RESTful (Symfony Camp 2008)Be RESTful (Symfony Camp 2008)
Be RESTful (Symfony Camp 2008)
 
Micropage in microtime using microframework
Micropage in microtime using microframeworkMicropage in microtime using microframework
Micropage in microtime using microframework
 
PhpBB meets Symfony2
PhpBB meets Symfony2PhpBB meets Symfony2
PhpBB meets Symfony2
 
Zf2 how arrays will save your project
Zf2   how arrays will save your projectZf2   how arrays will save your project
Zf2 how arrays will save your project
 
20191116 custom operators in swift
20191116 custom operators in swift20191116 custom operators in swift
20191116 custom operators in swift
 
Advanced symfony Techniques
Advanced symfony TechniquesAdvanced symfony Techniques
Advanced symfony Techniques
 
Electrify your code with PHP Generators
Electrify your code with PHP GeneratorsElectrify your code with PHP Generators
Electrify your code with PHP Generators
 
PHP5.5 is Here
PHP5.5 is HerePHP5.5 is Here
PHP5.5 is Here
 
sfDay Cologne - Sonata Admin Bundle
sfDay Cologne - Sonata Admin BundlesfDay Cologne - Sonata Admin Bundle
sfDay Cologne - Sonata Admin Bundle
 
Introduction to CloudForecast / YAPC::Asia 2010 Tokyo
Introduction to CloudForecast / YAPC::Asia 2010 TokyoIntroduction to CloudForecast / YAPC::Asia 2010 Tokyo
Introduction to CloudForecast / YAPC::Asia 2010 Tokyo
 
Bonnes pratiques de développement avec Node js
Bonnes pratiques de développement avec Node jsBonnes pratiques de développement avec Node js
Bonnes pratiques de développement avec Node js
 
Cpp11 multithreading and_simd_linux_code
Cpp11 multithreading and_simd_linux_codeCpp11 multithreading and_simd_linux_code
Cpp11 multithreading and_simd_linux_code
 
Cyclejs introduction
Cyclejs introductionCyclejs introduction
Cyclejs introduction
 
PHP tips and tricks
PHP tips and tricks PHP tips and tricks
PHP tips and tricks
 
international PHP2011_Bastian Feder_jQuery's Secrets
international PHP2011_Bastian Feder_jQuery's Secretsinternational PHP2011_Bastian Feder_jQuery's Secrets
international PHP2011_Bastian Feder_jQuery's Secrets
 
[PL] Jak nie zostać "programistą" PHP?
[PL] Jak nie zostać "programistą" PHP?[PL] Jak nie zostać "programistą" PHP?
[PL] Jak nie zostać "programistą" PHP?
 
13 PHPUnit #burningkeyboards
13 PHPUnit #burningkeyboards13 PHPUnit #burningkeyboards
13 PHPUnit #burningkeyboards
 
Php unit the-mostunknownparts
Php unit the-mostunknownpartsPhp unit the-mostunknownparts
Php unit the-mostunknownparts
 
Web 8 | Introduction to PHP
Web 8 | Introduction to PHPWeb 8 | Introduction to PHP
Web 8 | Introduction to PHP
 

Semelhante a How I Built a Power Debugger Out of the Standard Library and Things I Found on the Internet

Re-Design with Elixir/OTP
Re-Design with Elixir/OTPRe-Design with Elixir/OTP
Re-Design with Elixir/OTPMustafa TURAN
 
Ansible tips & tricks
Ansible tips & tricksAnsible tips & tricks
Ansible tips & tricksbcoca
 
OpenERP e l'arte della gestione aziendale con Python
OpenERP e l'arte della gestione aziendale con PythonOpenERP e l'arte della gestione aziendale con Python
OpenERP e l'arte della gestione aziendale con PythonPyCon Italia
 
Map/Confused? A practical approach to Map/Reduce with MongoDB
Map/Confused? A practical approach to Map/Reduce with MongoDBMap/Confused? A practical approach to Map/Reduce with MongoDB
Map/Confused? A practical approach to Map/Reduce with MongoDBUwe Printz
 
And the Greatest of These Is ... Rack Support
And the Greatest of These Is ... Rack SupportAnd the Greatest of These Is ... Rack Support
And the Greatest of These Is ... Rack SupportBen Scofield
 
Laravel5 Introduction and essentials
Laravel5 Introduction and essentialsLaravel5 Introduction and essentials
Laravel5 Introduction and essentialsPramod Kadam
 
Rooted 2010 ppp
Rooted 2010 pppRooted 2010 ppp
Rooted 2010 pppnoc_313
 
QConSP 2015 - Dicas de Performance para Aplicações Web
QConSP 2015 - Dicas de Performance para Aplicações WebQConSP 2015 - Dicas de Performance para Aplicações Web
QConSP 2015 - Dicas de Performance para Aplicações WebFabio Akita
 
Railsconf2011 deployment tips_for_slideshare
Railsconf2011 deployment tips_for_slideshareRailsconf2011 deployment tips_for_slideshare
Railsconf2011 deployment tips_for_slidesharetomcopeland
 
Monitoring with Syslog and EventMachine (RailswayConf 2012)
Monitoring  with  Syslog and EventMachine (RailswayConf 2012)Monitoring  with  Syslog and EventMachine (RailswayConf 2012)
Monitoring with Syslog and EventMachine (RailswayConf 2012)Wooga
 
CONFidence 2015: DTrace + OSX = Fun - Andrzej Dyjak
CONFidence 2015: DTrace + OSX = Fun - Andrzej Dyjak   CONFidence 2015: DTrace + OSX = Fun - Andrzej Dyjak
CONFidence 2015: DTrace + OSX = Fun - Andrzej Dyjak PROIDEA
 
Helping Data Teams with Puppet / Puppet Camp London - Apr 13, 2015
Helping Data Teams with Puppet / Puppet Camp London - Apr 13, 2015Helping Data Teams with Puppet / Puppet Camp London - Apr 13, 2015
Helping Data Teams with Puppet / Puppet Camp London - Apr 13, 2015Sergii Khomenko
 
Puppet Camp London 2015 - Helping Data Teams with Puppet
Puppet Camp London 2015 - Helping Data Teams with PuppetPuppet Camp London 2015 - Helping Data Teams with Puppet
Puppet Camp London 2015 - Helping Data Teams with PuppetPuppet
 
Assignment no39
Assignment no39Assignment no39
Assignment no39Jay Patel
 
Javascript is your (Auto)mate
Javascript is your (Auto)mateJavascript is your (Auto)mate
Javascript is your (Auto)mateCodemotion
 
What the heck went wrong?
What the heck went wrong?What the heck went wrong?
What the heck went wrong?Andy McKay
 
Background Jobs - Com BackgrounDRb
Background Jobs - Com BackgrounDRbBackground Jobs - Com BackgrounDRb
Background Jobs - Com BackgrounDRbJuan Maiz
 

Semelhante a How I Built a Power Debugger Out of the Standard Library and Things I Found on the Internet (20)

Re-Design with Elixir/OTP
Re-Design with Elixir/OTPRe-Design with Elixir/OTP
Re-Design with Elixir/OTP
 
Ansible tips & tricks
Ansible tips & tricksAnsible tips & tricks
Ansible tips & tricks
 
OpenERP e l'arte della gestione aziendale con Python
OpenERP e l'arte della gestione aziendale con PythonOpenERP e l'arte della gestione aziendale con Python
OpenERP e l'arte della gestione aziendale con Python
 
Map/Confused? A practical approach to Map/Reduce with MongoDB
Map/Confused? A practical approach to Map/Reduce with MongoDBMap/Confused? A practical approach to Map/Reduce with MongoDB
Map/Confused? A practical approach to Map/Reduce with MongoDB
 
And the Greatest of These Is ... Rack Support
And the Greatest of These Is ... Rack SupportAnd the Greatest of These Is ... Rack Support
And the Greatest of These Is ... Rack Support
 
Laravel5 Introduction and essentials
Laravel5 Introduction and essentialsLaravel5 Introduction and essentials
Laravel5 Introduction and essentials
 
Rooted 2010 ppp
Rooted 2010 pppRooted 2010 ppp
Rooted 2010 ppp
 
QConSP 2015 - Dicas de Performance para Aplicações Web
QConSP 2015 - Dicas de Performance para Aplicações WebQConSP 2015 - Dicas de Performance para Aplicações Web
QConSP 2015 - Dicas de Performance para Aplicações Web
 
Railsconf2011 deployment tips_for_slideshare
Railsconf2011 deployment tips_for_slideshareRailsconf2011 deployment tips_for_slideshare
Railsconf2011 deployment tips_for_slideshare
 
Monitoring with Syslog and EventMachine (RailswayConf 2012)
Monitoring  with  Syslog and EventMachine (RailswayConf 2012)Monitoring  with  Syslog and EventMachine (RailswayConf 2012)
Monitoring with Syslog and EventMachine (RailswayConf 2012)
 
CONFidence 2015: DTrace + OSX = Fun - Andrzej Dyjak
CONFidence 2015: DTrace + OSX = Fun - Andrzej Dyjak   CONFidence 2015: DTrace + OSX = Fun - Andrzej Dyjak
CONFidence 2015: DTrace + OSX = Fun - Andrzej Dyjak
 
Helping Data Teams with Puppet / Puppet Camp London - Apr 13, 2015
Helping Data Teams with Puppet / Puppet Camp London - Apr 13, 2015Helping Data Teams with Puppet / Puppet Camp London - Apr 13, 2015
Helping Data Teams with Puppet / Puppet Camp London - Apr 13, 2015
 
Puppet Camp London 2015 - Helping Data Teams with Puppet
Puppet Camp London 2015 - Helping Data Teams with PuppetPuppet Camp London 2015 - Helping Data Teams with Puppet
Puppet Camp London 2015 - Helping Data Teams with Puppet
 
Assignment no39
Assignment no39Assignment no39
Assignment no39
 
Javascript is your (Auto)mate
Javascript is your (Auto)mateJavascript is your (Auto)mate
Javascript is your (Auto)mate
 
What the heck went wrong?
What the heck went wrong?What the heck went wrong?
What the heck went wrong?
 
Intro to Ember.JS 2016
Intro to Ember.JS 2016Intro to Ember.JS 2016
Intro to Ember.JS 2016
 
SOLID Ruby, SOLID Rails
SOLID Ruby, SOLID RailsSOLID Ruby, SOLID Rails
SOLID Ruby, SOLID Rails
 
Background Jobs - Com BackgrounDRb
Background Jobs - Com BackgrounDRbBackground Jobs - Com BackgrounDRb
Background Jobs - Com BackgrounDRb
 
Tt subtemplates-caching
Tt subtemplates-cachingTt subtemplates-caching
Tt subtemplates-caching
 

Mais de doughellmann

Reno: A new way to manage release notes
Reno: A new way to manage release notesReno: A new way to manage release notes
Reno: A new way to manage release notesdoughellmann
 
Reno A New Way to Manage Release Notes
Reno   A New Way to Manage Release NotesReno   A New Way to Manage Release Notes
Reno A New Way to Manage Release Notesdoughellmann
 
How OpenStack Makes Python Better (and vice-versa)
How OpenStack Makes Python Better (and vice-versa)How OpenStack Makes Python Better (and vice-versa)
How OpenStack Makes Python Better (and vice-versa)doughellmann
 
Rolling with the Times: Using wheels, pbr, and Twine for Distributing and Ins...
Rolling with the Times: Using wheels, pbr, and Twine for Distributing and Ins...Rolling with the Times: Using wheels, pbr, and Twine for Distributing and Ins...
Rolling with the Times: Using wheels, pbr, and Twine for Distributing and Ins...doughellmann
 
Herding cats into boxes
Herding cats into boxesHerding cats into boxes
Herding cats into boxesdoughellmann
 
OpenStack 5th Birthday
OpenStack 5th BirthdayOpenStack 5th Birthday
OpenStack 5th Birthdaydoughellmann
 
Regexes and-performance-testing
Regexes and-performance-testingRegexes and-performance-testing
Regexes and-performance-testingdoughellmann
 
OpenStack Atlanta-2014-12-18
OpenStack Atlanta-2014-12-18OpenStack Atlanta-2014-12-18
OpenStack Atlanta-2014-12-18doughellmann
 
Taking the Long View: How the Oslo Program Reduces Technical Debt
Taking the Long View: How the Oslo Program Reduces Technical DebtTaking the Long View: How the Oslo Program Reduces Technical Debt
Taking the Long View: How the Oslo Program Reduces Technical Debtdoughellmann
 
Oslo Program Overview, OpenStack Atlanta
Oslo Program Overview, OpenStack AtlantaOslo Program Overview, OpenStack Atlanta
Oslo Program Overview, OpenStack Atlantadoughellmann
 
Dynamic Code Patterns: Extending Your Applications with Plugins
Dynamic Code Patterns: Extending Your Applications with PluginsDynamic Code Patterns: Extending Your Applications with Plugins
Dynamic Code Patterns: Extending Your Applications with Pluginsdoughellmann
 
Better Documentation Through Automation: Creating docutils & Sphinx Extensions
Better Documentation Through Automation: Creating docutils & Sphinx ExtensionsBetter Documentation Through Automation: Creating docutils & Sphinx Extensions
Better Documentation Through Automation: Creating docutils & Sphinx Extensionsdoughellmann
 
Hidden Treasures of the Python Standard Library
Hidden Treasures of the Python Standard LibraryHidden Treasures of the Python Standard Library
Hidden Treasures of the Python Standard Librarydoughellmann
 
An Introduction to the Zen of Python
An Introduction to the Zen of PythonAn Introduction to the Zen of Python
An Introduction to the Zen of Pythondoughellmann
 

Mais de doughellmann (14)

Reno: A new way to manage release notes
Reno: A new way to manage release notesReno: A new way to manage release notes
Reno: A new way to manage release notes
 
Reno A New Way to Manage Release Notes
Reno   A New Way to Manage Release NotesReno   A New Way to Manage Release Notes
Reno A New Way to Manage Release Notes
 
How OpenStack Makes Python Better (and vice-versa)
How OpenStack Makes Python Better (and vice-versa)How OpenStack Makes Python Better (and vice-versa)
How OpenStack Makes Python Better (and vice-versa)
 
Rolling with the Times: Using wheels, pbr, and Twine for Distributing and Ins...
Rolling with the Times: Using wheels, pbr, and Twine for Distributing and Ins...Rolling with the Times: Using wheels, pbr, and Twine for Distributing and Ins...
Rolling with the Times: Using wheels, pbr, and Twine for Distributing and Ins...
 
Herding cats into boxes
Herding cats into boxesHerding cats into boxes
Herding cats into boxes
 
OpenStack 5th Birthday
OpenStack 5th BirthdayOpenStack 5th Birthday
OpenStack 5th Birthday
 
Regexes and-performance-testing
Regexes and-performance-testingRegexes and-performance-testing
Regexes and-performance-testing
 
OpenStack Atlanta-2014-12-18
OpenStack Atlanta-2014-12-18OpenStack Atlanta-2014-12-18
OpenStack Atlanta-2014-12-18
 
Taking the Long View: How the Oslo Program Reduces Technical Debt
Taking the Long View: How the Oslo Program Reduces Technical DebtTaking the Long View: How the Oslo Program Reduces Technical Debt
Taking the Long View: How the Oslo Program Reduces Technical Debt
 
Oslo Program Overview, OpenStack Atlanta
Oslo Program Overview, OpenStack AtlantaOslo Program Overview, OpenStack Atlanta
Oslo Program Overview, OpenStack Atlanta
 
Dynamic Code Patterns: Extending Your Applications with Plugins
Dynamic Code Patterns: Extending Your Applications with PluginsDynamic Code Patterns: Extending Your Applications with Plugins
Dynamic Code Patterns: Extending Your Applications with Plugins
 
Better Documentation Through Automation: Creating docutils & Sphinx Extensions
Better Documentation Through Automation: Creating docutils & Sphinx ExtensionsBetter Documentation Through Automation: Creating docutils & Sphinx Extensions
Better Documentation Through Automation: Creating docutils & Sphinx Extensions
 
Hidden Treasures of the Python Standard Library
Hidden Treasures of the Python Standard LibraryHidden Treasures of the Python Standard Library
Hidden Treasures of the Python Standard Library
 
An Introduction to the Zen of Python
An Introduction to the Zen of PythonAn Introduction to the Zen of Python
An Introduction to the Zen of Python
 

Último

5 Signs You Need a Fashion PLM Software.pdf
5 Signs You Need a Fashion PLM Software.pdf5 Signs You Need a Fashion PLM Software.pdf
5 Signs You Need a Fashion PLM Software.pdfWave PLM
 
Cloud Management Software Platforms: OpenStack
Cloud Management Software Platforms: OpenStackCloud Management Software Platforms: OpenStack
Cloud Management Software Platforms: OpenStackVICTOR MAESTRE RAMIREZ
 
(Genuine) Escort Service Lucknow | Starting ₹,5K To @25k with A/C 🧑🏽‍❤️‍🧑🏻 89...
(Genuine) Escort Service Lucknow | Starting ₹,5K To @25k with A/C 🧑🏽‍❤️‍🧑🏻 89...(Genuine) Escort Service Lucknow | Starting ₹,5K To @25k with A/C 🧑🏽‍❤️‍🧑🏻 89...
(Genuine) Escort Service Lucknow | Starting ₹,5K To @25k with A/C 🧑🏽‍❤️‍🧑🏻 89...gurkirankumar98700
 
Building Real-Time Data Pipelines: Stream & Batch Processing workshop Slide
Building Real-Time Data Pipelines: Stream & Batch Processing workshop SlideBuilding Real-Time Data Pipelines: Stream & Batch Processing workshop Slide
Building Real-Time Data Pipelines: Stream & Batch Processing workshop SlideChristina Lin
 
Unveiling the Tech Salsa of LAMs with Janus in Real-Time Applications
Unveiling the Tech Salsa of LAMs with Janus in Real-Time ApplicationsUnveiling the Tech Salsa of LAMs with Janus in Real-Time Applications
Unveiling the Tech Salsa of LAMs with Janus in Real-Time ApplicationsAlberto González Trastoy
 
Steps To Getting Up And Running Quickly With MyTimeClock Employee Scheduling ...
Steps To Getting Up And Running Quickly With MyTimeClock Employee Scheduling ...Steps To Getting Up And Running Quickly With MyTimeClock Employee Scheduling ...
Steps To Getting Up And Running Quickly With MyTimeClock Employee Scheduling ...MyIntelliSource, Inc.
 
Professional Resume Template for Software Developers
Professional Resume Template for Software DevelopersProfessional Resume Template for Software Developers
Professional Resume Template for Software DevelopersVinodh Ram
 
A Secure and Reliable Document Management System is Essential.docx
A Secure and Reliable Document Management System is Essential.docxA Secure and Reliable Document Management System is Essential.docx
A Secure and Reliable Document Management System is Essential.docxComplianceQuest1
 
The Ultimate Test Automation Guide_ Best Practices and Tips.pdf
The Ultimate Test Automation Guide_ Best Practices and Tips.pdfThe Ultimate Test Automation Guide_ Best Practices and Tips.pdf
The Ultimate Test Automation Guide_ Best Practices and Tips.pdfkalichargn70th171
 
What is Binary Language? Computer Number Systems
What is Binary Language?  Computer Number SystemsWhat is Binary Language?  Computer Number Systems
What is Binary Language? Computer Number SystemsJheuzeDellosa
 
Project Based Learning (A.I).pptx detail explanation
Project Based Learning (A.I).pptx detail explanationProject Based Learning (A.I).pptx detail explanation
Project Based Learning (A.I).pptx detail explanationkaushalgiri8080
 
EY_Graph Database Powered Sustainability
EY_Graph Database Powered SustainabilityEY_Graph Database Powered Sustainability
EY_Graph Database Powered SustainabilityNeo4j
 
Advancing Engineering with AI through the Next Generation of Strategic Projec...
Advancing Engineering with AI through the Next Generation of Strategic Projec...Advancing Engineering with AI through the Next Generation of Strategic Projec...
Advancing Engineering with AI through the Next Generation of Strategic Projec...OnePlan Solutions
 
HR Software Buyers Guide in 2024 - HRSoftware.com
HR Software Buyers Guide in 2024 - HRSoftware.comHR Software Buyers Guide in 2024 - HRSoftware.com
HR Software Buyers Guide in 2024 - HRSoftware.comFatema Valibhai
 
Reassessing the Bedrock of Clinical Function Models: An Examination of Large ...
Reassessing the Bedrock of Clinical Function Models: An Examination of Large ...Reassessing the Bedrock of Clinical Function Models: An Examination of Large ...
Reassessing the Bedrock of Clinical Function Models: An Examination of Large ...harshavardhanraghave
 
Short Story: Unveiling the Reasoning Abilities of Large Language Models by Ke...
Short Story: Unveiling the Reasoning Abilities of Large Language Models by Ke...Short Story: Unveiling the Reasoning Abilities of Large Language Models by Ke...
Short Story: Unveiling the Reasoning Abilities of Large Language Models by Ke...kellynguyen01
 
Unit 1.1 Excite Part 1, class 9, cbse...
Unit 1.1 Excite Part 1, class 9, cbse...Unit 1.1 Excite Part 1, class 9, cbse...
Unit 1.1 Excite Part 1, class 9, cbse...aditisharan08
 
Hand gesture recognition PROJECT PPT.pptx
Hand gesture recognition PROJECT PPT.pptxHand gesture recognition PROJECT PPT.pptx
Hand gesture recognition PROJECT PPT.pptxbodapatigopi8531
 
The Real-World Challenges of Medical Device Cybersecurity- Mitigating Vulnera...
The Real-World Challenges of Medical Device Cybersecurity- Mitigating Vulnera...The Real-World Challenges of Medical Device Cybersecurity- Mitigating Vulnera...
The Real-World Challenges of Medical Device Cybersecurity- Mitigating Vulnera...ICS
 

Último (20)

5 Signs You Need a Fashion PLM Software.pdf
5 Signs You Need a Fashion PLM Software.pdf5 Signs You Need a Fashion PLM Software.pdf
5 Signs You Need a Fashion PLM Software.pdf
 
Cloud Management Software Platforms: OpenStack
Cloud Management Software Platforms: OpenStackCloud Management Software Platforms: OpenStack
Cloud Management Software Platforms: OpenStack
 
(Genuine) Escort Service Lucknow | Starting ₹,5K To @25k with A/C 🧑🏽‍❤️‍🧑🏻 89...
(Genuine) Escort Service Lucknow | Starting ₹,5K To @25k with A/C 🧑🏽‍❤️‍🧑🏻 89...(Genuine) Escort Service Lucknow | Starting ₹,5K To @25k with A/C 🧑🏽‍❤️‍🧑🏻 89...
(Genuine) Escort Service Lucknow | Starting ₹,5K To @25k with A/C 🧑🏽‍❤️‍🧑🏻 89...
 
Building Real-Time Data Pipelines: Stream & Batch Processing workshop Slide
Building Real-Time Data Pipelines: Stream & Batch Processing workshop SlideBuilding Real-Time Data Pipelines: Stream & Batch Processing workshop Slide
Building Real-Time Data Pipelines: Stream & Batch Processing workshop Slide
 
Unveiling the Tech Salsa of LAMs with Janus in Real-Time Applications
Unveiling the Tech Salsa of LAMs with Janus in Real-Time ApplicationsUnveiling the Tech Salsa of LAMs with Janus in Real-Time Applications
Unveiling the Tech Salsa of LAMs with Janus in Real-Time Applications
 
Steps To Getting Up And Running Quickly With MyTimeClock Employee Scheduling ...
Steps To Getting Up And Running Quickly With MyTimeClock Employee Scheduling ...Steps To Getting Up And Running Quickly With MyTimeClock Employee Scheduling ...
Steps To Getting Up And Running Quickly With MyTimeClock Employee Scheduling ...
 
Professional Resume Template for Software Developers
Professional Resume Template for Software DevelopersProfessional Resume Template for Software Developers
Professional Resume Template for Software Developers
 
A Secure and Reliable Document Management System is Essential.docx
A Secure and Reliable Document Management System is Essential.docxA Secure and Reliable Document Management System is Essential.docx
A Secure and Reliable Document Management System is Essential.docx
 
The Ultimate Test Automation Guide_ Best Practices and Tips.pdf
The Ultimate Test Automation Guide_ Best Practices and Tips.pdfThe Ultimate Test Automation Guide_ Best Practices and Tips.pdf
The Ultimate Test Automation Guide_ Best Practices and Tips.pdf
 
What is Binary Language? Computer Number Systems
What is Binary Language?  Computer Number SystemsWhat is Binary Language?  Computer Number Systems
What is Binary Language? Computer Number Systems
 
Project Based Learning (A.I).pptx detail explanation
Project Based Learning (A.I).pptx detail explanationProject Based Learning (A.I).pptx detail explanation
Project Based Learning (A.I).pptx detail explanation
 
EY_Graph Database Powered Sustainability
EY_Graph Database Powered SustainabilityEY_Graph Database Powered Sustainability
EY_Graph Database Powered Sustainability
 
Advancing Engineering with AI through the Next Generation of Strategic Projec...
Advancing Engineering with AI through the Next Generation of Strategic Projec...Advancing Engineering with AI through the Next Generation of Strategic Projec...
Advancing Engineering with AI through the Next Generation of Strategic Projec...
 
HR Software Buyers Guide in 2024 - HRSoftware.com
HR Software Buyers Guide in 2024 - HRSoftware.comHR Software Buyers Guide in 2024 - HRSoftware.com
HR Software Buyers Guide in 2024 - HRSoftware.com
 
Reassessing the Bedrock of Clinical Function Models: An Examination of Large ...
Reassessing the Bedrock of Clinical Function Models: An Examination of Large ...Reassessing the Bedrock of Clinical Function Models: An Examination of Large ...
Reassessing the Bedrock of Clinical Function Models: An Examination of Large ...
 
Short Story: Unveiling the Reasoning Abilities of Large Language Models by Ke...
Short Story: Unveiling the Reasoning Abilities of Large Language Models by Ke...Short Story: Unveiling the Reasoning Abilities of Large Language Models by Ke...
Short Story: Unveiling the Reasoning Abilities of Large Language Models by Ke...
 
Unit 1.1 Excite Part 1, class 9, cbse...
Unit 1.1 Excite Part 1, class 9, cbse...Unit 1.1 Excite Part 1, class 9, cbse...
Unit 1.1 Excite Part 1, class 9, cbse...
 
Hand gesture recognition PROJECT PPT.pptx
Hand gesture recognition PROJECT PPT.pptxHand gesture recognition PROJECT PPT.pptx
Hand gesture recognition PROJECT PPT.pptx
 
Call Girls In Mukherjee Nagar 📱 9999965857 🤩 Delhi 🫦 HOT AND SEXY VVIP 🍎 SE...
Call Girls In Mukherjee Nagar 📱  9999965857  🤩 Delhi 🫦 HOT AND SEXY VVIP 🍎 SE...Call Girls In Mukherjee Nagar 📱  9999965857  🤩 Delhi 🫦 HOT AND SEXY VVIP 🍎 SE...
Call Girls In Mukherjee Nagar 📱 9999965857 🤩 Delhi 🫦 HOT AND SEXY VVIP 🍎 SE...
 
The Real-World Challenges of Medical Device Cybersecurity- Mitigating Vulnera...
The Real-World Challenges of Medical Device Cybersecurity- Mitigating Vulnera...The Real-World Challenges of Medical Device Cybersecurity- Mitigating Vulnera...
The Real-World Challenges of Medical Device Cybersecurity- Mitigating Vulnera...
 

How I Built a Power Debugger Out of the Standard Library and Things I Found on the Internet

  • 1. How I Built a Power Debugger Out of the Standard Library and Things I Found on the Internet Doug Hellmann PyOhio 2015
  • 2. I Built a Power Debugger Doug Hellmann PyOhio 2015
  • 3. How I Built a Power Debugger Doug Hellmann PyOhio 2015
  • 5.
  • 6.
  • 7. Features • Record calls with data • Remote monitoring, local analysis • Browse history • New tools flickr/Kangrex
  • 8.
  • 9. class Publisher(object): def __init__(self, endpoint, high_water_mark=10000): self.context = zmq.Context() self.pub_socket = self.context.socket(zmq.PUSH) self.pub_socket.bind(endpoint) self.pub_socket.identity = 'publisher' self.pub_socket.hwm = high_water_mark def send(self, msg_type, data): msg = [ msg_type, json.dumps(data, default=self._json_special_types), ] self.pub_socket.send_multipart(msg)
  • 10. class Tracer(object): def __init__(self, publisher): self.publisher = publisher self.run_id = None
  • 11. def trace_calls(self, frame, event, arg): co = frame.f_code filename = co.co_filename if filename in (__file__,): # Ignore ourself return self._send_notice(frame, event, arg) return self.trace_calls
  • 12. def _send_notice(self, frame, event, arg): co = frame.f_code func_name = co.co_name line_no = frame.f_lineno filename = os.path.abspath(co.co_filename) for d in IGNORE_DIRS: if filename.startswith(d): return # …
  • 13. # … interesting_locals = { n: v for n, v in frame.f_locals.items() if (not inspect.ismodule(v) and not inspect.isfunction(v) and not inspect.ismethod(v) and (n[:2] != '__' and n[-2:] != '__')) } # …
  • 14. # … self.publisher.send( event, {'func_name': func_name, 'line_no': line_no, 'filename': filename, 'arg': arg, 'locals': interesting_locals, 'timestamp': time.time(), 'run_id': self.run_id, })
  • 15. $ smiley help usage: smiley [--version] [-v] [--log-file LOG_FILE] [-q] [-h] [--debug] smiley spies on your apps as they run optional arguments: --version show program's version number and exit -v, --verbose Increase verbosity of output. --log-file LOG_FILE Specify a file to log output. -q, --quiet suppress output except warnings -h, --help show this help message and exit --debug show tracebacks on errors Commands: complete print bash completion command help print detailed help for another command monitor Listen for running programs and show their progress. run Run another program with monitoring enabled.
  • 16. $ smiley help run usage: smiley run [-h] [--socket SOCKET] command [command ...] Run another program with monitoring enabled. positional arguments: command the command to spy on optional arguments: -h, --help show this help message and exit --socket SOCKET URL for the socket where the listener will be (tcp://127.0.0.1:5556)
  • 17. $ smiley help monitor usage: smiley monitor [-h] [--socket SOCKET] Listen for running programs and show their progress. optional arguments: -h, --help show this help message and exit --socket SOCKET URL for the socket where to monitor on (tcp://127.0.0.1:5556)
  • 18. def _process_message(self, msg): print 'MESSAGE:', msg msg_type, msg_payload = msg if msg_type == 'start_run': print (‘Starting new run:', msg_payload.get(‘command_line')) elif msg_type == 'end_run': print 'Finished run' else: line = linecache.getline(msg_payload['filename'], msg_payload['line_no']).rstrip() if msg_type == 'return': print '%s:%4s: return>>> %s' % ( msg_payload['filename'], msg_payload[‘line_no'], msg_payload['arg']) else: print '%s:%4s: %s' % ( msg_payload['filename'], msg_payload[‘line_no'], line) if msg_payload.get('locals'): for n, v in sorted(msg_payload['locals'].items()): print '%s %s = %s' % ( ' ' * len(msg_payload['filename']), n, v, ) print
  • 19. def gen(m): for i in xrange(m): yield i def c(input): print 'input =', input data = list(gen(input)) print 'Leaving c()' def b(arg): val = arg * 5 c(val) print 'Leaving b()' return val def a(): print 'args:', sys.argv b(2) print 'Leaving a()' a()
  • 20. u'command_line': [u'simple.py']}}] /Users/dhellmann/Devel/smiley/scratch/simple.py: 8: def c(input): command_line = [u'simple.py'] self = <smiley.tracer.Tracer object at 0x103caff10> MESSAGE: ['line', {u'run_id': u'e87302b2-402a-4243-bb27-69a467d4bd8e', u'timestamp': 1436367235.144169, u'line_no': 14, u'filename': u'/Users/dhellmann/Devel/smiley/scratch/simple.py', u'func_name': u'<module>', u'arg': None, u'locals': {u'self': u'<smiley.tracer.Tracer object at 0x103caff10>', u'command_line': [u'simple.py']}}] /Users/dhellmann/Devel/smiley/scratch/simple.py: 14: def b(arg): command_line = [u'simple.py'] self = <smiley.tracer.Tracer object at 0x103caff10> MESSAGE: ['line', {u'run_id': u'e87302b2-402a-4243-bb27-69a467d4bd8e', u'timestamp': 1436367235.144468, u'line_no': 21, u'filename': u'/Users/dhellmann/Devel/smiley/scratch/simple.py', u'func_name': u'<module>', u'arg': None, u'locals': {u'self': u'<smiley.tracer.Tracer object at 0x103caff10>', u'command_line': [u'simple.py']}}] /Users/dhellmann/Devel/smiley/scratch/simple.py: 21: def a(): command_line = [u'simple.py'] self = <smiley.tracer.Tracer object at 0x103caff10> MESSAGE: ['line', {u'run_id': u'e87302b2-402a-4243-bb27-69a467d4bd8e', u'timestamp': 1436367235.144719, u'line_no': 26, u'filename': u'/Users/dhellmann/Devel/smiley/scratch/simple.py', u'func_name': u'<module>', u'arg': None, u'locals': {u'self': u'<smiley.tracer.Tracer object at 0x103caff10>', u'command_line': [u'simple.py']}}] /Users/dhellmann/Devel/smiley/scratch/simple.py: 26: a() command_line = [u'simple.py'] self = <smiley.tracer.Tracer object at 0x103caff10> MESSAGE: ['call', {u'run_id': u'e87302b2-402a-4243-bb27-69a467d4bd8e', u'timestamp': 1436367235.144927, u'line_no': 21, u'filename': u'/Users/dhellmann/Devel/smiley/scratch/simple.py', u'func_name': u'a', u'arg': None, u'locals': {}}] /Users/dhellmann/Devel/smiley/scratch/simple.py: 21: def a(): MESSAGE: ['line', {u'run_id': u'e87302b2-402a-4243-bb27-69a467d4bd8e', u'timestamp':
  • 21. u'command_line': [u'simple.py']}}] /Users/dhellmann/Devel/smiley/scratch/simple.py: 8: def c(input): command_line = [u'simple.py'] self = <smiley.tracer.Tracer object at 0x103caff10> MESSAGE: ['line', {u'run_id': u'e87302b2-402a-4243-bb27-69a467d4bd8e', u'timestamp': 1436367235.144169, u'line_no': 14, u'filename': u'/Users/dhellmann/Devel/smiley/scratch/simple.py', u'func_name': u'<module>', u'arg': None, u'locals': {u'self': u'<smiley.tracer.Tracer object at 0x103caff10>', u'command_line': [u'simple.py']}}] /Users/dhellmann/Devel/smiley/scratch/simple.py: 14: def b(arg): command_line = [u'simple.py'] self = <smiley.tracer.Tracer object at 0x103caff10> MESSAGE: ['line', {u'run_id': u'e87302b2-402a-4243-bb27-69a467d4bd8e', u'timestamp': 1436367235.144468, u'line_no': 21, u'filename': u'/Users/dhellmann/Devel/smiley/scratch/simple.py', u'func_name': u'<module>', u'arg': None, u'locals': {u'self': u'<smiley.tracer.Tracer object at 0x103caff10>', u'command_line': [u'simple.py']}}] /Users/dhellmann/Devel/smiley/scratch/simple.py: 21: def a(): command_line = [u'simple.py'] self = <smiley.tracer.Tracer object at 0x103caff10> MESSAGE: ['line', {u'run_id': u'e87302b2-402a-4243-bb27-69a467d4bd8e', u'timestamp': 1436367235.144719, u'line_no': 26, u'filename': u'/Users/dhellmann/Devel/smiley/scratch/simple.py', u'func_name': u'<module>', u'arg': None, u'locals': {u'self': u'<smiley.tracer.Tracer object at 0x103caff10>', u'command_line': [u'simple.py']}}] /Users/dhellmann/Devel/smiley/scratch/simple.py: 26: a() command_line = [u'simple.py'] self = <smiley.tracer.Tracer object at 0x103caff10> MESSAGE: ['call', {u'run_id': u'e87302b2-402a-4243-bb27-69a467d4bd8e', u'timestamp': 1436367235.144927, u'line_no': 21, u'filename': u'/Users/dhellmann/Devel/smiley/scratch/simple.py', u'func_name': u'a', u'arg': None, u'locals': {}}] /Users/dhellmann/Devel/smiley/scratch/simple.py: 21: def a(): MESSAGE: ['line', {u'run_id': u'e87302b2-402a-4243-bb27-69a467d4bd8e', u'timestamp':
  • 22. MESSAGE: ['call', {u'run_id': u'e87302b2-402a-4243-bb27-69a467d4bd8e', u'timestamp': 1436367235.144927, u'line_no': 21, u'filename': u'/Users/dhellmann/Devel/smiley/scratch/simple.py', u'func_name': u'a', u'arg': None, u'locals': {}}] /Users/dhellmann/Devel/smiley/scratch/simple.py: 21: def a(): MESSAGE: ['line', {u'run_id': u'e87302b2-402a-4243-bb27-69a467d4bd8e', u'timestamp': 1436367235.145128, u'line_no': 22, u'filename': u'/Users/dhellmann/Devel/smiley/scratch/simple.py', u'func_name': u'a', u'arg': None, u'locals': {}}] /Users/dhellmann/Devel/smiley/scratch/simple.py: 22: print 'args:', sys.argv MESSAGE: ['line', {u'run_id': u'e87302b2-402a-4243-bb27-69a467d4bd8e', u'timestamp': 1436367235.145343, u'line_no': 23, u'filename': u'/Users/dhellmann/Devel/smiley/scratch/simple.py', u'func_name': u'a', u'arg': None, u'locals': {}}] /Users/dhellmann/Devel/smiley/scratch/simple.py: 23: b(2)
  • 23.
  • 24.
  • 25. class DB(object): def __init__(self, name): self.conn = sqlite3.connect(name) self.conn.row_factory = sqlite3.Row try: cursor = self.conn.cursor() cursor.execute('select * from run') LOG.debug('database already initialized') except sqlite3.OperationalError: LOG.debug('initializing database') schema = pkgutil.get_data('smiley', 'schema.sql') cursor.executescript(schema)
  • 26. create table run ( id text primary key not null, cwd text, description text, start_time int, end_time int, error_message text, traceback text ); create table trace ( id integer primary key autoincrement not null, run_id text not null references run(id), filename text, line_no int, func_name text, trace_arg text, locals text, time_stamp int );
  • 27. def start_run(self, run_id, cwd, description, start_time): "Record the beginning of a run." with transaction(self.conn) as c: c.execute( """ INSERT INTO run (id, cwd, description, start_time) VALUES (:id, :cwd, :description, :start_time) """, {'id': run_id, 'cwd': cwd, 'description': description, 'start_time': start_time} )
  • 28. def _process_message(self, msg): msg_type, msg_payload = msg if msg_type == 'start_run': command_line = ' ‘.join( msg_payload.get('command_line', [])) self._cwd = msg_payload.get('cwd', '') if self._cwd: self._cwd = (self._cwd.rstrip(os.sep) + os.sep) self.db.start_run( run_id=msg_payload.get('run_id'), cwd=self._cwd, description=command_line, start_time=msg_payload.get('timestamp'), )
  • 29. elif msg_type == 'end_run': self.log.info('Finished run') self.db.end_run( run_id=msg_payload['run_id'], end_time=msg_payload.get('timestamp'), message=msg_payload.get('message'), traceback=msg_payload.get('traceback'), ) else: self.db.trace( run_id=msg_payload['run_id'], event=msg_type, func_name=msg_payload.get('func_name'), line_no=msg_payload.get('line_no'), filename=msg_payload.get('filename'), trace_arg=msg_payload.get('arg'), locals=msg_payload.get('locals'), timestamp=msg_payload.get('timestamp'), )
  • 30. • Replay past runs • Complex data types flickr/Chris Marquardt
  • 31. import json import traceback import types def _json_special_types(obj): if isinstance(obj, types.TracebackType): return traceback.extract_tb(obj) if isinstance(obj, type): # We don't want to return classes return repr(obj) try: data = dict(vars(obj)) data['__class__'] = obj.__class__.__name__ data['__module__'] = obj.__class__.__module__ except Exception as err: data = repr(obj) return data def dumps(data): return json.dumps(data, default=_json_special_types)
  • 32. ?
  • 33. class EventProcessor(object): __metaclass__ = abc.ABCMeta @abc.abstractmethod def start_run(self, run_id, cwd, description, start_time): """Called when a 'start_run' event is seen. """ @abc.abstractmethod def end_run(self, run_id, end_time, message, traceback): """Called when an 'end_run' event is seen. """ @abc.abstractmethod def trace(self, run_id, event, func_name, line_no, filename, trace_arg, local_vars, timestamp): """Called when any other event type is seen. """
  • 34. def get_runs(self): "Return the runs available to browse." with transaction(self.conn) as c: c.execute( """ SELECT id, cwd, description, start_time, end_time, error_message FROM run """ ) return c.fetchall()
  • 35. def take_action(self, parsed_args): self.out = output.OutputFormatter( linecache.getline) self.db = db.DB(parsed_args.database) run_details = self.db.get_run(parsed_args.run_id) self.out.start_run( run_details.id, run_details.cwd, run_details.description, run_details.start_time, ) for t in self.db.get_trace(parsed_args.run_id): self.out.trace( t.run_id, t.event, t.func_name, t.line_no, t.filename, t.trace_arg, t.local_vars, t.timestamp, ) self.out.end_run( run_details.id, run_details.end_time, run_details.error_message, None) # run_details.traceback
  • 36. MESSAGE: ['call', {u'run_id': u'e87302b2-402a-4243-bb27-69a467d4bd8e', u'timestamp': 1436367235.144927, u'line_no': 21, u'filename': u'/Users/dhellmann/Devel/smiley/scratch/simple.py', u'func_name': u'a', u'arg': None, u'locals': {}}] /Users/dhellmann/Devel/smiley/scratch/simple.py: 21: def a(): MESSAGE: ['line', {u'run_id': u'e87302b2-402a-4243-bb27-69a467d4bd8e', u'timestamp': 1436367235.145128, u'line_no': 22, u'filename': u'/Users/dhellmann/Devel/smiley/scratch/simple.py', u'func_name': u'a', u'arg': None, u'locals': {}}] /Users/dhellmann/Devel/smiley/scratch/simple.py: 22: print 'args:', sys.argv MESSAGE: ['line', {u'run_id': u'e87302b2-402a-4243-bb27-69a467d4bd8e', u'timestamp': 1436367235.145343, u'line_no': 23, u'filename': u'/Users/dhellmann/Devel/smiley/scratch/simple.py', u'func_name': u'a', u'arg': None, u'locals': {}}] /Users/dhellmann/Devel/smiley/scratch/simple.py: 23: b(2)
  • 37. class DBLineCache(object): def __init__(self, db, run_id): self._db = db self._run_id = run_id self._files = {} def getline(self, filename, line_no): if filename not in self._files: body = self._db.get_cached_file( self._run_id, filename) self._files[filename] = body.splitlines() try: return self._files[filename][line_no] except IndexError: # Line number is out of range return ''
  • 38. def take_action(self, parsed_args): # Fix import path cwd = os.getcwd() if (cwd not in sys.path and os.curdir not in sys.path): sys.path.insert(0, cwd) # Fix command line args sys.argv = parsed_args.command # Run the app p = publisher.Publisher(parsed_args.socket) t = tracer.Tracer(p) t.run(parsed_args.command)
  • 39. def take_action(self, parsed_args): # Fix import path cwd = os.getcwd() if (cwd not in sys.path and os.curdir not in sys.path): sys.path.insert(0, cwd) # Fix command line args sys.argv = parsed_args.command # Run the app if parsed_args.mode == 'remote': p = publisher.Publisher(parsed_args.socket) else: p = db.DB(parsed_args.database) t = tracer.Tracer(p) t.run(parsed_args.command)
  • 40. 1.ZMQ 2.Tracing 3.Command line interface 4.Database 5.Complex objects 6.EventProcessor API 7.Replay command 8.Store source files 9.Local database
  • 41. 1.ZMQ 2.Tracing 3.Command line interface 4.Database 5.Complex objects 6.EventProcessor API 7.Replay command 8.Store source files 9.Local database
  • 43.
  • 44.
  • 45.
  • 46. class FileController(RestController): @expose(generic=True, template='file.html') @nav.active_section('runs') def get_one(self, run_id, file_id): filename, body = request.db.get_cached_file_by_id(run_id, file_id) run = request.db.get_run(run_id) lexer = guess_lexer_for_filename(filename, body) formatter = HtmlFormatter(linenos=True) styled_body = highlight(body, lexer, formatter) return { 'run_id': run_id, 'run': run, 'filename': filename, 'body': body, 'styled_body': styled_body, }
  • 47.
  • 48. class StyledLineCache(object): def __init__(self, db, run_id): self._db = db self._run_id = run_id self._files = {} EXPECTED_PREFIX = '<div class="highlight"><pre>' EXPECTED_SUFFIX = '</pre></div>' def getline(self, filename, line_no): if filename not in self._files: body = self._db.get_cached_file(self._run_id, filename) styled_body = apply_style(filename, body, linenos=False) start = len(self.EXPECTED_PREFIX) end = -1 * (len(self.EXPECTED_SUFFIX) + 1) middle_body = styled_body[start:end].rstrip('n') self._files[filename] = middle_body.splitlines() try: return self._files[filename][line_no-1] except IndexError: # Line number is out of range return ''
  • 49.
  • 50.
  • 51.
  • 52.
  • 53. ✓ Web UI ✓ Profiling Data ✓ Call Graph ✓ Syntax Highlighting • Only Changed Variables • Comments
  • 54. def _mk_seq(d): return sorted( (k, pformat(v, width=20)) for k, v in d.iteritems() ) def get_variable_changes(older, newer): s_a = _mk_seq(older) s_b = _mk_seq(newer) matcher = difflib.SequenceMatcher(None, s_a, s_b) for tag, i1, i2, j1, j2 in matcher.get_opcodes(): if tag in {'insert', 'replace'}: for i in s_b[j1:j2]: yield i
  • 55.
  • 56.
  • 57.
  • 58.
  • 63. • Performance • Standard I/O • GUI • Compare runs flickr/Mike Mozart

Notas do Editor

  1. Good morning, and thank you all for coming. I am Doug Hellmann, and I am currently working as an OpenStack contributor for HP. Today I am going to talk a bit about how I glued together modules from the standard library and a few other packages to make a python power debugger. The project I will be talking about runs along side your program, and watches not just how the code is executed but also all of the values of all variables, recording them to produce a complete trace of the project’s execution history for you to examine at your leisure. But before we go too far into detail…
  2. …I want to make sure that I’ve set your expectations clearly. Most project-based presentations are set up to cover the project and its features, often with demos. Those are great talks, and they’re a big part of why we all come to conferences. If I was giving a talk like that it would have a shorter title. But that’s not what I want to do today,…
  3. …instead I want to talk about HOW I built it. It’s easy to forget, when you watch a bunch of conference talks announcing new projects, that these things don’t spring fully formed into the world. They take continual iterative development over time. I’ve found that it’s important to talk about that evolutionary process for projects with newer developers, as examples of how to start with something simple and build something more complex. How to add features carefully, adjusting existing code as you go. So what I want to focus on, more than the implementation details of the program, are the thought processes I followed as I was working and…
  4. …the story of how I created the project I call SMILEY. I will show code and talk about the tools as I tell the story, but there’s too much code to really walk through all of it, so if you’re the sort of person who reads the last page of a book before starting it, you may want to go ahead and jump into the github repo and look around. [PAUSE] First things first, what’s up with the name? When I started building this tool, I thought of it as “spying” on what the program was doing. So of course, …
  5. … I started thinking of spy names. This guy was way too obvious. So I opted to go with…
  6. …George Smiley, the John le Carré character. He’s more intellectual, less flashy, and is focuses on the spying rather than shooting things up. That’s more my style. With the naming question…
  7. …resolved, I could get down to business and start working on the features I wanted smiley to have. I wanted to record the same data you might expect to see in a live debugger like pdb, but without tediously stepping through calls. I also wanted the best of “print” based debugging, without having to decide in advance which values to print out. RECORD CALLS & DATA REMOTE MONITORING BROWSE HISTORY & ANALYZE LOCALLY I was also motivated to build something a little audacious, to see how far I could take the idea, and learn some new tools along the way. To start out…
  8. …I knew Python’s trace API because I had used it before, so I knew enough to believe I could make that work for me. And I’ve done network programming before, so I knew if it came down to it I could build the remote execution part. But I also wanted to learn some new tools as part of the project, so I started by figuring out how to make the networking library ZeroMQ work for me. That involved reading a lot of documentation and poking around with some different implementations that I didn’t save in the history. I mostly had no idea what I was doing, so I just keep trying to get the 2 programs to talk until they did. Initially I used PUB/SUB…
  9. …sockets because I was communicating one way, but quickly realized that was a mistake and switched to a PUSH/PULL socket pair. PUB/SUB doesn’t ensure message delivery and I didn’t want to miss the beginning of the trace. So I created a pair of classes that I could use to send arbitrary messages one way between two processes, a publisher and a listener. After I had the publisher and listener classes…
  10. …I created a tracer class that uses the communication classes to publish data about what is happening inside a program as it runs. The tracer works…
  11. …by installing its trace_calls() method using sys.settrace() so that every time the python interpreter does something it calls this method. On each call, the trace function is given the stack frame and a string with the name of the event type (e.g., CALL, LINE, RETURN, EXCEPTION). Some of those event types include an extra argument, which is passed as the final parameter. The work for sending the event is in…
  12. …the send_notice() method which does three things. First, it figures out if the event SHOULD be recorded, based on the NAME OF THE FILE where it happened. I started out thinking I would just want to ignore the standard library, but over time the facility for ignoring directories changed a bit. If a file should be included in the trace, then…
  13. …it builds a list of interesting local variables by looking at the stack frame. The FRAME includes EVERYTHING, but I ignore any modules, functions, and methods because those aren’t as likely to change value over time as other types of variables. And I get the source code being executed by reading those files directly. Finally, it builds…
  14. …a standard data structure to hold the trace event data. At this point I was not worried about the database schema or anything like that, so it’s a simple flat dictionary describing the event. The event type from the trace call is used as the message type, and I added a few other message types for starting a trace and ending a trace. The next step was to…
  15. …build some sort of a UI for running programs and recording the trace. I started with some simple demo scripts built on argparse, but as I thought about it I realized I would need several different commands for different tasks. I switched to cliff, a framework I had created for use in OpenStack. It relies on argparse for parsing options and setuptools entry points to discover available sub-commands defined as plugins. That means applications built on cliff can be extended by packages written by someone else. As a developer tool, I wanted to make smiley easy to extend. I needed a RUN command…
  16. …to set up the trace code and publish events to a ZMQ socket. And a MONITOR command…
  17. …to listen for those messages and print them to the console. Both commands default to a localhost socket, but it’s possible to specify another using a URL argument so remote monitoring is possible. The monitor command…
  18. …sets up a listener to call this process_message() method to actually print the output. The PATTERN of looking at the EVENT TYPE and doing something DIFFERENT repeats a few times in smiley. I considered making each event type its OWN CLASS, but since the messages are fairly low level and DON’T HAVE ANY BEHAVIOR themselves that seemed like overkill. I would still need to do the work of turning a transmitted message into the right class anyway, and I wanted to keep the display and database LOGIC ISOLATED in their own classes. So I chose to KEEP the basic data object as SIMPLE as possible. To test all of this out I created a little script…
  19. …with a few functions that call each other with different arguments and return values. It was simple but still interesting enough to be useful for some of the edge cases like loops and generators. In one terminal I can use “smiley run simple.py” to run the test script and then…
  20. …in another terminal “smiley monitor” collects the output. You can see that it isn’t terribly readable, but it was good enough to keep experimenting. It includes the event types…
  21. …file names, and line numbers. And later in the output, when the program is inside a function,…
  22. …MONITOR shows the function name for each line as well. At this point the RUN command traces a user’s program and sends the results to the MONITOR command, which prints everything out on the console. That’s interesting, and a good early step, but not necessarily a “minimum viable product.” The next step was to figure out…
  23. …where else to send the output. I decided I was going to want several commands for writing to different outputs so I needed to do some cleanup work and to refactor the existing monitor command to make a reusable base class. A side note here: To be HISTORICALLY accurate, I’m showing the EARLY, UGLY, versions of most of the code & output today, so it may not match what you find if you check out the git repository. I won’t go through the refactoring line-by-line, you can read the git logs if you want that. I’m going to focus on the CONCEPTUAL changes. Now I needed decide how to record runs for browsing. I knew…
  24. …I wanted some sort of database. I’ve used both ZODB and MongoDB before with good results, but neither quite had the query semantics that I wanted, in terms of being able to fetch a subset of the run easily. After thinking about how I might implement replay, I convinced myself that the query patterns I anticipated meant I should use a relational database. I didn’t want to depend on having a separate server running, so I went with SQLite, since it is built into the standard library and available in most environments. Smiley doesn’t use an ORM,…
  25. …but I did create a class to hold the query API that I would support. I assume that if the database doesn’t exist it should be initialized, so there’s no separate “make me a database” step like with some projects. This is a DESKTOP APP not a server tool, so creating the file automatically felt more friendly. There’s no database SCHEMA VERSIONING facility or migrations. Right now, I just delete the database if I need to add a column, but I’ll probably have to do something better than that at some point. The schema definition…
  26. …is kept in a separate file distributed as package data. To start I had these TWO TABLES, though a few more were added later, along with some indexes. For now, though: RUN holds the start/end time and the result of running the script. TRACE holds all of the trace event data. The database API…
  27. …defines the logical operations, such as start_run(). This lets me separate the data representation from the way I manipulate it. The arguments to start a run don’t completely fill in a row, since some of the values are only known when the run is completed. The first version of the API only knew how to START a run, but using that one operation I could prove to myself that a message coming from the RUN command process would make it through the ZMQ socket to the process recording the data then into the database where I could retrieve it later. That would ensure I had all of the communication parts working, and then I could expand the API for more features. The RECORD command class has a…
  28. …method for processing the messages that looks at the type to decide which DB API method to call, similar to how the MONITOR command checks the type before formatting the output. This is the only place that has to know about both the line-format for the messages coming in from the RUN command and the arguments needed for the database. After I had enough of the pieces working to record the start of the run…
  29. …I could update both the MONITOR command and DB API to support ending a run and recording the intermediate trace items. Iterating like this let me focus on one piece at a time: First set up ZMQ communication, then hook into the TRACE to observe the program, then send that data to the CONSOLE to make sure I had it right, then build up the DATABASE handling piece by piece. Each step along the way I only needed to learn one tool at a time or think about one API at a time. Now that I had a basic way to record program runs,…
  30. … I had to decide what to tackle next. I could start building the database query API to let me REPLAY old runs, OR I could look at some of the more COMPLEX DATA TYPES. I had reserved a column for recording the final exception from a program that dies with an error, but at this point I was only recording the error message and not the full traceback. I needed to do more to produce a useful representation of the traceback, so I decided to look into how to transmit more complex data types so I could have that working before I wrote output or replay code that I might have to rewrite for those more complex types. So, I started writing some…
  31. …custom JSON encoding logic to handle complex types. The traceback module has an EXTRACT_TB function tailor-made for getting the traceback data into a structure that is easier to format, so I started with that. Classes and other type objects are sent using their REPR value. INSTANCES of other complex types are represented using a dictionary containing the CLASS NAME, the MODULE where the class can be found (in case the name is not unique) and then the ATTRIBUTES of the object. So now I could send complex data…
  32. …but I still had no way to replay old runs from the database. I knew I had the formatting code I needed in the monitor command, so I started by refactoring that implementation out into its own module so I could reuse it from the new command I was planning. And that’s where I had a sort of epiphany…
  33. … It seems like a small thing in retrospect, but at the time it made a big difference to realize that the output formatter, database API, and the ZMQ publisher all shared a common pattern, they all need to perform the same 3 operations. I realized with a little work they could all share a common API, so I defined a new EventProcessor base class and fixed up the APIs for the existing classes to match it, including renaming some methods and arguments. I used the abc module to make an abstract base class, since there’s not really a lot of implementation to be shared. Then I went on to work on the REPLAY command, except…
  34. …I realized there was no way for me to TEST that command until I had a way to get a list of the runs from the database. Up until this point I had been looking at the database directly or reading monitor output, so I hadn’t needed a list command. Now I did and, as you can see, at this stage adding a new command still meant adding to the db API. So, first a LIST command, then the REPLAY command…
  35. …which, with the new EventProcessor API turned out to be pretty straightforward. I added new methods to the DB API to return the RUN record and related TRACE records, and then loop over the results calling the appropriate methods on the output formatter. One of the features…
  36. …of both the MONITOR and REPLAY commands is showing the line of source code being executed with the variables as context. This was implemented originally using the LINECACHE module from the standard library, which has an API for finding line N of a given file by name. However, as I continued to run tests and experiment, I realized that replaying a very old run was showing the WRONG source lines because I was changing the input program. The feature also wouldn’t work properly for remote monitoring, since the source files might not be on the MONITOR and REPLAY machine at all. I was going to need to save the source code of the program as it ran, too, in order for the history to be accurate. So, I added…
  37. …some calls to the database API to let me store the source code there in a new table. And then I created a DBLineCache class with the same getline() API as the linecache module, but that reads the files out of the database instead of from the filesystem. It’s not a complicated class, but it let me substitute an instance of the new class where I was using linecache before, and not change the output formatter implementation. At this point…
  38. …I was running MONITOR and RUN commands over and over, and getting a little tired of using them as two commands. I wanted to keep MONITOR for remote debugging, but when working locally I knew I could just use the RUN command if I taught it how to write to a database instead of a socket. Since my DB API was in part derived from the EventProcessor base class, I knew…
  39. …I could just instantiate a DB and pass it to the tracer. I added a flag to RUN to tell me whether to use a remote connection or a local database, and updated RUN to check that flag. That’s two cases where by identifying an abstract API, I could provide separate concrete implementations for maximum code reuse. Now I had something…
  40. … that was usable for very simple python programs and an iterative pattern was emerging in my development process. I learned to use a NEW TOOL, used it to build a FEATURE, evolve the feature by ENHANCING it and FIXING bugs, and then moved on to the next cycle. LEARN, BUILD, EVOLVE It’s not a perfect description of how the work happened, because sometimes I skipped steps, but it’s generally how I approached it. So far I’ve talked about…
  41. …NINE of these iterations, starting with the basics of ZMQ and SQLite and moving on to various smaller usability enhancements. As I worked, I built up quite a database of test runs, and I wanted a better UI for BROWSING them instead of typing lots of JOB ID values on the command line. I transitioned into the next major feature addition cycle by starting the web interface or “server” mode. As with the command line tools…
  42. …I didn’t want to build everything from scratch, and this was definitely an area where I needed to go outside of the standard library. I’m no web designer, so I knew I was going to need a framework to do most of the heavy lifting. I started out with PureCSS, which I had used for my blog recently, but ended up switching to BOOTSTRAP because it has better widget support. I chose PECAN, since I had used it recently for some other work and I know the maintainers through the Atlanta meetup. Pecan uses MAKO templates by default, and I hadn’t really used those much, so between figuring out Mako and Bootstrap it took me a while to put together a basic page. As with REPLAY…
  43. …I had to start with showing a list of the existing runs. The table showing run IDs, times, and outcome, is a little easier to read here than the LIST command output was on the console. Next I turned those run values in the left column into links and…
  44. …wrote a view for the trace data. The trace shows each line of the script being run, just like the console app. For the web app…
  45. … I wanted to be able to show the complete contents of source files, too. So I needed a new view for that. Of course, plain text in a browser window is boring…
  46. …so I hooked up the PYGMENTS library so I could have syntax highlighting. That involved reading the body of the file from the database, and passing it to pygments to render as HTML, and adding the appropriate CSS block to the stylesheet for the app. The results…
  47. …could probably use some help from a more skilled designer, but they’re an improvement over the plain text. It was a little more challenging to add syntax highlighting to the trace view, which is showing one line at a time. Pygments doesn’t always know how to parse a partial statement or snippet of source code…
  48. …so I wanted to feed the whole file to it to ensure the parser would understand it. I noticed that the pygments output preserved the original lines, with a little bit of prefix and suffix text for the whole block of output. Using that information, I went back and created another line cache implementation. This one takes the full pygments output for a file, trims the header and footer, saves the part that represented the actual source code, and return lines from that instead of the raw plain text in the database. Again, I was able to drop this class in to replace the old version, and then the web UI…
  49. …had syntax highlighting in the trace output, seen here in the statement column. Adding syntax highlighting to the local variable definitions and return values in the far right column…
  50. …was as simple as adding a view filter to the Mako templates. It’s mostly useful for strings, but there are a few special values like None that are treated as keywords, too. Now I could see what my program was doing, and easily browse through the trace output. The next thing to do was make it easier to understand where it was spending lots of time. I had originally thought I would keep up with this sort of data myself using unique IDs for each call and using the time values to build the necessary statistics.
  51. Then I looked again at Python’s profiler and trace APIs, and realized they used different hooks, so they could both be used together. Instead of building my own profiler, I just turned on the built-in profiler and collected the data, sending it across the socket at the end of a run. I added a new web view to show the profile stats data as a table. Then Ryan Petrello showed me…
  52. … how to generate a visualization with gprof2dot so I added another view that shows that diagram. The diagram uses color to indicate where in the call tree the program spends most of its time. Unfortunately the implementation that I have generates the image on each invocation, so it’s not exactly efficient, but for the current class of applications smiley supports it was fine. [PAUSE] After adding the profiler support, I had reached another plateau in my feature work…
  53. … and was ready to move to the next step in that LEARN, BUILD, EVOLVE cycle. I decided to shift from ADDING major features to REFINING some of the features I already had. I noticed that the web UI was showing a lot of the same data over and over, so I decided to make it only show local variables as their values CHANGED from line to line. I also thought about how important COMMENTS are in understanding a program, so I decided to try to show comments near code that had been executed. I started with…
  54. …detecting changes in variables. The first version of this used difflib on a sequence of sorted variable names and their representations. This was pretty convenient, since difflib works on any 2 sequences and would tell me what had been added or changed (to be displayed). That let me…
  55. … remove this duplicated DATA, by comparing the local variables at each line with the ones from the previous line. Any time execution enters or leaves a function,…
  56. …it automatically triggers re-dumping the local values for the new context. Within a given function scope, only the variables that actually changed are shown. The result was output with lots of lines without any variables, because they didn’t change very much. That gave me the idea to collapse those rows together…
  57. … and show them as a block of source instead of one line at a time. I wrapped the existing iterator of trace data in another generator function that produced the aggregate values. That wrapper also replaces the single line number value with a tuple containing the start and stop points of the range of lines to show. I added a getlines() method to the line cache to retrieve multiple lines with one call so the template didn’t have to make a bunch of calls to get each line separately. Next, I extended getlines()…
  58. …to include comment lines immediately preceding the start of the requested range. It scans backwards, allowing a single blank line between the code and a comment block. If it hits anything that isn’t a comment, or a second blank line, it stops and uses the range of lines it has identified. At this point I was doing quite a bit of processing of the data in the UI, and it was starting to seem slow. I knew that the problem would only become worse as the length of program runs grew so, I decided to add pagination…
  59. …to the UI to address that. After pagination was working, I added a few other features. Multi-threaded programs record the thread id of each step of the program, and the UI lets you filter the output by thread. There is a command for generating a static HTML report,…
  60. …which can then be published on a website and shared. It works much like the interactive browser, except it is for a single run and all of the pages are static. And there are also commands for exporting run data from one database and importing it into another. Each of these feature development cycles followed the same LEARN, BUILD, EVOLVE process. [PAUSE] All of this work was done over the course…
  61. …of a couple of years, though not full time since this was a fun side project. This chart shows the days on which I had commits, and the lines of code at the end of each day. There were almost always more than one commit on any given day, but I collapsed those to make the graph easier to follow. If we accept lines of code…
  62. …as a rough analog for project feature and complexity growth, you can see a relatively steady progression resulting from that iterative process. The one notable exception is that big jump around the end of 2013, which is when I committed a large batch of CSS and template code for the static report generator. And there were plenty of days where I read or experimented or otherwise worked without writing any code. But slowly, over time, smiley GREW from a very RUDIMENTARY trace monitoring tool, to have a full browser-based GUI and some fairly advanced collaboration features. There are still a lot of things I would like to add…
  63. …to make smiley even more useful. Performance work is probably the biggest need. Transmitting or recording all of that data is expensive, so offloading it to another process or figuring out how to send less data would be valuable. Pre-calculating and storing the collapsed trace views would also be an improvement. There are several UI enhancements on the list, too. If you are interested in getting involved,…
  64. …please check out the code and drop me an email. I would love to have some other folks helping to build smiley into a more usable tool.
  65. …resolved, I could get down to business and start working on the features I wanted smiley to have. I wanted to record the same data you might expect to see in a live debugger like pdb, but without stepping through calls individually. Combining the best of “print” based debugging with an interactive debugger that has access to variables you didn’t think to print out. I was also motivated to build something a little audacious, to see how far I could take the idea, and learn some new tools along the way. So, where did I start?
  66. …with Gene Hackman But it’s not really a spy movie, per se.
  67. …where to send the output. I decided I was going to want several commands for listening in different ways (console output, database, etc.) so the next phase was some cleanup work and to refactor the existing monitor command to make a base class. A side note here: To be historically accurate, I’m showing the early, ugly version of most of the code here today, so it may not match what you find if you check out the git repository. I won’t go through the refactoring line-by-line, you can read the git logs if you want that. I’m going to focus on the conceptual changes, instead. The next step was to decide how to record runs for browsing. I knew…
  68. … I had to decide what to tackle next. I could start building the database query API to let me replay old runs, or I could look at some of the more complex data types. I had reserved a column for recording the final exception from a program that dies with an error, but at this point I was only recording the error message and not the full traceback. I needed to do more to produce a useful representation of the traceback, so I decided to look into how to transmit more complex data types so I could have that working before I wrote output or replay code that I might have to rewrite for those types. So, I started writing some…
  69. …but I still had no way to replay old runs from the database. I knew I had the formatting code I needed in the monitor command, so I started by refactoring that implementation out into its own module so I could reuse it from the new command I was planning. And that’s where I had a sort of epiphany…
  70. …I had something that was usable for very simple python programs. The iterative pattern in the development process should be clear. I have been learning to use a new tool, using it to build a feature, making the feature a little better by enhancing it and fixing bugs, and then moving on to the next cycle. It’s not a perfect description of how the work happens, because sometimes you skip steps, but it’s generally how I approached it. So far I’ve talked about…
  71. …I had something that was usable for very simple python programs. The iterative pattern in the development process should be clear. I have been learning to use a new tool, using it to build a feature, evolving the feature to make it a little better by enhancing it and fixing bugs, and then moving on to the next cycle. It’s not a perfect description of how the work happens, because sometimes you skip steps, but it’s generally how I approached it. So far I’ve talked about…
  72. …NINE of these iterations, starting with the basics of ZMQ and SQLite and moving on to various smaller usability enhancements. By this time, I was building up quite a database of test runs, so I wanted to think about a better UI for browsing them instead of typing lots of UUID values on the command line. I transitioned into the next major feature addition cycle by starting the web interface or “server” mode. As with the command line tools…
  73. …I didn’t want to build everything from scratch, and this was definitely an area where I needed to go outside of the standard library. I used the pecan framework, since I had used it recently for some other work and I know the maintainers through the Atlanta meetup and working at Dreamhost. I used python’s built-in WSGI server, and although it is single-threaded, performed adequately for my needs in this case. I’m not building a massive front-end being hit by a bunch of users at once, just a dev tool, and this was easy to embed for deployment. I’m no web designer, so I knew I was going to want to use some sort of framework to do most of the heavy lifting. I started out with the PureCSS framework, which I had used for my blog recently, but ended up dumping it and switching to Bootstrap for better widget support. As with REPLAY…
  74. …I went on to implement a few other features. Multi-threaded programs record the thread id of each step of the program, and the UI lets you filter the output by thread. There is a command for generating a static HTML report, which can then be published on a website and shared. It works much like the interactive browser, except it is for a single run and all of the pages are static. And there are commands for exporting run data from one database and importing it into another. There are still plenty of things I would like to add…
  75. …to make smiley even more useful. Performance work is probably the biggest need. Transmitting or recording all of that data is expensive, so offloading it to another process or figuring out how to send less data would be valuable. Pre-calculating and storing the collapsed trace views would also be an improvement. There are several UI enhancements on the list, too. If you are interested in getting involved,…
  76. …to make smiley even more useful. Performance work is probably the biggest need. Transmitting or recording all of that data is expensive, so offloading it to another process or figuring out how to send less data would be valuable. Pre-calculating and storing the collapsed trace views would also be an improvement. There are several UI enhancements on the list, too. If you are interested in getting involved,…
  77. …now I needed to know how many pages of output it would produce. So I still had to calculate the entire collapsed trace, but for small runs I could cache the results in memory if I assumed the user would be moving through it page by page — or even jumping around. I decided to hard-code the number of items to show per page, while I debugged my pagination viewer code. The results looked like..
  78. …this. But showing each page number in the list of pages was quickly going to cause layout issues, so in addition to letting the user control how many items to show on a page I changed how those buttons were displayed so there are only a few on the screen at a time. The pagination code evolved a few times, …
  79. … because as I added support for “many” pages, the UI for “few” pages broke once or twice. Smiley’s test coverage isn’t what I would like it to be. I already mentioned that I was not really doing test driven development because it is a fun exploratory project. I do have some tests, but definitely wasn’t writing them first and this is one place where that hurt a little. After pagination was working…
  80. …I wrapped the existing iterator of trace data in another generator function that produced the aggregate values. It also replaces the single line number value with a tuple containing the start and stop points of the range of lines to show. I added a getlines() method to the line cache to retrieve multiple lines with one call. Next, I extended getlines()…