Skip to main content

A simple stack-based performance logger

Project description

A simple stack-based performance logger.

Wallclock provides some simple tools for identifying slow parts of your code.
It maintains a stack of running timers, and reports the tree of execution
times when the stack empties out.

Simple Usage
------------

To time code with wallclock, use the ``push`` and ``pop`` functions provided
by the wallclock module::

import wallclock

def slow_function():
wallclock.push('slow function')
import time
time.sleep(3)
wallclock.pop('slow function')

def main():
wallclock.push('handling one request', enable=True)
slow_function()
wallclock.pop('handling one request')

main()

This will produce a small tree on ``stderr`` summarizing the time taken to
execute ``main()``::

[3.001 sec] handling one request
[3.001 sec] slow function

The ``push`` function takes one positional argument, which is the label of the
timer being pushed onto wallclock's stack. This is normally a short,
descriptive label for the kind of work that happens under timing. ``push``
also takes one optional keyword argument, ``enable``, which controls whether
this call to ``push`` should start timing (if it's not already started).
``wallclock`` ignores calls to ``push`` until it has been enabled, and
disables itself automatically when the enabling ``push`` is ``pop``ped.

The ``pop`` function takes one positional argument, which is the label to pop.
This allows for simple insertion of ``push``/``pop`` pairs surrounding code
that might return, or might raise an exception, without adding extra
``try``/``except`` blocks or similar (but see below for a better approach).
``wallclock`` will pop timers off of the stack until it finds a timer pushed
with the passed label, or until it empties the timer stack.

Configuration
-------------

``wallclock`` exposes two module-scoped symbols that can be used to control
its behaviour:

* ``wallclock.threshold`` is a number (``float``, ``int``, or ``long``) of
seconds. Any timers whose duration is strictly less than ``threshold`` will
not be recorded. By default, the threshold is approximately 10 milliseconds
(0.01 seconds). Setting the threshold to ``0`` will record every timer.
* ``wallclock.output`` is a callable object used to print recorded times. By
default, it's set to ``wallclock.stderr_output``, which writes the tree to
standard error, but applications can replace this with their own callables.
``wallclock`` invokes ``output`` with one argument, which is the root timer
object. Timer objects have three readable properties:
1. ``label``, which is the label passed to ``push`` when the timer was
created.
2. ``duration``, which is the total time recorded on the timer (in seconds).
3. ``children``, which is a sequence of timer objects that were recorded
while the passed timer object was at the top of the stack.

Automatic Timer Management
--------------------------

Manually inserting calls to ``push`` and ``pop`` works for debugging, but not
so well for leaving wallclock in place afterwards. The ``wallclock`` module
provides some tidier alternatives.

Context Manager
~~~~~~~~~~~~~~~

The ``wallclock.block`` context manager automatically calls ``push`` before
evaluating the block, and ensures ``pop`` is called after exiting the block by
any means::

import wallclock

def slow_function():
with wallclock.block('slow function'):
import time
time.sleep(3)

def main():
with wallclock.block('handling one request', enable=True):
slow_function()

main()

The ``block`` context manager accepts the same arguments that ``push``
accepts: a positional argument labelling the block, and an optional keyword
argument called ``enable`` for controlling whether the block should start
wallclock (if it's not already started).

Decorators
~~~~~~~~~~

The ``wallclock.function`` and ``wallclock.trace_function`` decorators
automatically call ``push`` before executing the decorated function, and
automatically call ``pop`` after the function exits, but before returning to
the caller::

import wallclock

@wallclock.function
def slow_function():
import time
time.sleep(3)

@wallclock.trace_function
def main():
slow_function()

main()

Both decorators determine the label for the pushed timer by examining the
called function's module and name. ``trace_function`` enables wallclock, while
``function`` does not.

Threads
-------

Wallclock makes a reasonable effort to keep a timer stack for each thread.
Timer stacks are kept in a ``threading.threadlocal`` object and are enabled
and disabled on a per-thread basis.

Performance
-----------

Timing your code is not free. While wallclock is noticably faster than a full
profiler, it still introduces overhead to manage the stack of pending timers
and the tree of completed timers. I've found the overhead to be surprisingly
large (a decorated empty function takes ~100x as long as a naked empty
function), but still well within usable limits.

Removing calls that enable wallclock reduces this overhead considerably. You
can further reduce overhead by disabling ``wallclock`` completely by calling
``wallclock.smash()``. This is an irreversible operation which replaces the
``push`` and ``pop`` operations, the ``block`` context manager, and the
``function`` and ``trace_function`` decorators with no-op equivalents.

Project details


Release history Release notifications | RSS feed

This version

1.0

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

wallclock-1.0.tar.gz (6.6 kB view hashes)

Uploaded Source

Built Distributions

wallclock-1.0-py2.7.egg (10.8 kB view hashes)

Uploaded Source

wallclock-1.0-py2.6.egg (10.9 kB view hashes)

Uploaded Source

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page