timeline-0.0.7/ 0000755 0001750 0001750 00000000000 13525014327 015030 5 ustar cjwatson cjwatson 0000000 0000000 timeline-0.0.7/PKG-INFO 0000644 0001750 0001750 00000010125 13525014327 016124 0 ustar cjwatson cjwatson 0000000 0000000 Metadata-Version: 2.1
Name: timeline
Version: 0.0.7
Summary: Timeline module for modelling a series of actions.
Home-page: https://launchpad.net/python-timeline
Maintainer: Launchpad Developers
Maintainer-email: launchpad-dev@lists.launchpad.net
License: UNKNOWN
Description: *************************************************
python-timeline: Modelling of a series of actions
*************************************************
Copyright (c) 2011, Canonical Ltd
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, version 3 only.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with this program. If not, see .
The timeline package provides a way to model a series of actions. For instance,
during a web request the appserver might call out to a number of backend
services, make sql queries and memcached lookups. All of these actions can be
tracked in a single timeline, and that serialised or analysed for slow points.
Dependencies
============
* Python 2.6+
* pytz
Testing Dependencies
====================
* subunit (http://pypi.python.org/pypi/python-subunit) (optional)
* testtools (http://pypi.python.org/pypi/testtools)
Usage
=====
Create a timeline object:
>>> from timeline.timeline import TimeLine
>>> log = TimeLine()
Then add actions:
>>> action = log.start('mycategory', 'mydetails')
Perform your action and then tell the action it has finished:
>>> action.finish()
At this point you can start another action. If you wish to nest actions, pass
allow_nested=True to start().
One of the things needed when working with timelines in complex applications is
locating the right one. Timeline provides a helper for WSGI apps:
>>> from timeline import wsgi
>>> app = wsgi.make_app(inner_app)
Calls to app will now inject a 'timeline.timeline' variable into the wsgi
environwhich can be used by inner_app to record actions.
Installation
============
Either run setup.py in an environment with all the dependencies available, or
add the working directory to your PYTHONPATH.
Development
===========
Upstream development takes place at https://launchpad.net/python-timeline.
To setup a working area for development, if the dependencies are not
immediately available, you can use ./bootstrap.py to create bin/buildout, then
bin/py to get a python interpreter with the dependencies available.
To run the tests use the runner of your choice, the test suite is
timeline.tests.test_suite.
For instance::
$ bin/py -m testtools.run timeline.tests.test_suite
If you have testrepository you can run the tests with testr::
$ testr run
Platform: UNKNOWN
Classifier: Development Status :: 2 - Pre-Alpha
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL)
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python
Classifier: Programming Language :: Python :: 2
Classifier: Programming Language :: Python :: 3
Provides-Extra: test
timeline-0.0.7/LICENSE 0000644 0001750 0001750 00000016727 13251472703 016054 0 ustar cjwatson cjwatson 0000000 0000000 GNU LESSER GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc.
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
This version of the GNU Lesser General Public License incorporates
the terms and conditions of version 3 of the GNU General Public
License, supplemented by the additional permissions listed below.
0. Additional Definitions.
As used herein, "this License" refers to version 3 of the GNU Lesser
General Public License, and the "GNU GPL" refers to version 3 of the GNU
General Public License.
"The Library" refers to a covered work governed by this License,
other than an Application or a Combined Work as defined below.
An "Application" is any work that makes use of an interface provided
by the Library, but which is not otherwise based on the Library.
Defining a subclass of a class defined by the Library is deemed a mode
of using an interface provided by the Library.
A "Combined Work" is a work produced by combining or linking an
Application with the Library. The particular version of the Library
with which the Combined Work was made is also called the "Linked
Version".
The "Minimal Corresponding Source" for a Combined Work means the
Corresponding Source for the Combined Work, excluding any source code
for portions of the Combined Work that, considered in isolation, are
based on the Application, and not on the Linked Version.
The "Corresponding Application Code" for a Combined Work means the
object code and/or source code for the Application, including any data
and utility programs needed for reproducing the Combined Work from the
Application, but excluding the System Libraries of the Combined Work.
1. Exception to Section 3 of the GNU GPL.
You may convey a covered work under sections 3 and 4 of this License
without being bound by section 3 of the GNU GPL.
2. Conveying Modified Versions.
If you modify a copy of the Library, and, in your modifications, a
facility refers to a function or data to be supplied by an Application
that uses the facility (other than as an argument passed when the
facility is invoked), then you may convey a copy of the modified
version:
a) under this License, provided that you make a good faith effort to
ensure that, in the event an Application does not supply the
function or data, the facility still operates, and performs
whatever part of its purpose remains meaningful, or
b) under the GNU GPL, with none of the additional permissions of
this License applicable to that copy.
3. Object Code Incorporating Material from Library Header Files.
The object code form of an Application may incorporate material from
a header file that is part of the Library. You may convey such object
code under terms of your choice, provided that, if the incorporated
material is not limited to numerical parameters, data structure
layouts and accessors, or small macros, inline functions and templates
(ten or fewer lines in length), you do both of the following:
a) Give prominent notice with each copy of the object code that the
Library is used in it and that the Library and its use are
covered by this License.
b) Accompany the object code with a copy of the GNU GPL and this license
document.
4. Combined Works.
You may convey a Combined Work under terms of your choice that,
taken together, effectively do not restrict modification of the
portions of the Library contained in the Combined Work and reverse
engineering for debugging such modifications, if you also do each of
the following:
a) Give prominent notice with each copy of the Combined Work that
the Library is used in it and that the Library and its use are
covered by this License.
b) Accompany the Combined Work with a copy of the GNU GPL and this license
document.
c) For a Combined Work that displays copyright notices during
execution, include the copyright notice for the Library among
these notices, as well as a reference directing the user to the
copies of the GNU GPL and this license document.
d) Do one of the following:
0) Convey the Minimal Corresponding Source under the terms of this
License, and the Corresponding Application Code in a form
suitable for, and under terms that permit, the user to
recombine or relink the Application with a modified version of
the Linked Version to produce a modified Combined Work, in the
manner specified by section 6 of the GNU GPL for conveying
Corresponding Source.
1) Use a suitable shared library mechanism for linking with the
Library. A suitable mechanism is one that (a) uses at run time
a copy of the Library already present on the user's computer
system, and (b) will operate properly with a modified version
of the Library that is interface-compatible with the Linked
Version.
e) Provide Installation Information, but only if you would otherwise
be required to provide such information under section 6 of the
GNU GPL, and only to the extent that such information is
necessary to install and execute a modified version of the
Combined Work produced by recombining or relinking the
Application with a modified version of the Linked Version. (If
you use option 4d0, the Installation Information must accompany
the Minimal Corresponding Source and Corresponding Application
Code. If you use option 4d1, you must provide the Installation
Information in the manner specified by section 6 of the GNU GPL
for conveying Corresponding Source.)
5. Combined Libraries.
You may place library facilities that are a work based on the
Library side by side in a single library together with other library
facilities that are not Applications and are not covered by this
License, and convey such a combined library under terms of your
choice, if you do both of the following:
a) Accompany the combined library with a copy of the same work based
on the Library, uncombined with any other library facilities,
conveyed under the terms of this License.
b) Give prominent notice with the combined library that part of it
is a work based on the Library, and explaining where to find the
accompanying uncombined form of the same work.
6. Revised Versions of the GNU Lesser General Public License.
The Free Software Foundation may publish revised and/or new versions
of the GNU Lesser General Public License from time to time. Such new
versions will be similar in spirit to the present version, but may
differ in detail to address new problems or concerns.
Each version is given a distinguishing version number. If the
Library as you received it specifies that a certain numbered version
of the GNU Lesser General Public License "or any later version"
applies to it, you have the option of following the terms and
conditions either of that published version or of any later version
published by the Free Software Foundation. If the Library as you
received it does not specify a version number of the GNU Lesser
General Public License, you may choose any version of the GNU Lesser
General Public License ever published by the Free Software Foundation.
If the Library as you received it specifies that a proxy can decide
whether future versions of the GNU Lesser General Public License shall
apply, that proxy's public statement of acceptance of any version is
permanent authorization for you to choose that version for the
Library.
timeline-0.0.7/setup.py 0000755 0001750 0001750 00000003331 13525014254 016544 0 ustar cjwatson cjwatson 0000000 0000000 #!/usr/bin/env python
#
# Copyright (c) 2011, Canonical Ltd
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, version 3 only.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this program. If not, see .
import os.path
from setuptools import (
find_packages,
setup,
)
with open(os.path.join(os.path.dirname(__file__), 'README')) as f:
description = f.read()
setup(name="timeline",
version="0.0.7",
description="Timeline module for modelling a series of actions.",
long_description=description,
maintainer="Launchpad Developers",
maintainer_email="launchpad-dev@lists.launchpad.net",
url="https://launchpad.net/python-timeline",
packages=find_packages(),
classifiers = [
'Development Status :: 2 - Pre-Alpha',
'Intended Audience :: Developers',
'License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL)',
'Operating System :: OS Independent',
'Programming Language :: Python',
'Programming Language :: Python :: 2',
'Programming Language :: Python :: 3',
],
install_requires = [
'pytz',
],
extras_require = dict(
test=[
'testtools',
]
),
)
timeline-0.0.7/MANIFEST.in 0000644 0001750 0001750 00000000025 13524345475 016575 0 ustar cjwatson cjwatson 0000000 0000000 include LICENSE NEWS
timeline-0.0.7/NEWS 0000644 0001750 0001750 00000002115 13525014234 015523 0 ustar cjwatson cjwatson 0000000 0000000 timeline NEWS
+++++++++++++
Changes and improvements to timeline, grouped by release.
0.0.7
-----
* Include LICENSE, NEWS, and timeline/tests/ in the sdist. (Colin Watson)
0.0.6
-----
* Use floor division so that intervals are always represented as integer
numbers of milliseconds, preserving output compatibility between Python 2
and 3. (Colin Watson)
0.0.5
-----
* Add Python 3 support. (Colin Watson)
0.0.4
-----
* The default stack formatter now no longer includes itself in the formatted
stack. (Robert Collins, #891989)
0.0.3
-----
* Added a .testr.conf to support easy testing. (Robert Collins)
* Relicensed under LGPL 3. (Robert Collins)
* TimedAction.logTuple() now returns 5-tuples with the last element the
backtrace for the action, or None if none was set. (Robert Collins)
* Timeline now gathers backtraces by default. This can be disabled by
passing format_stack=None, or customised by passing a replacement
callable. (Robert Collins)
0.0.2
-----
* Added a WSGI integration module - very simple, just injects a Timeline into
environ as 'timeline.timeline'.
timeline-0.0.7/setup.cfg 0000644 0001750 0001750 00000000046 13525014327 016651 0 ustar cjwatson cjwatson 0000000 0000000 [egg_info]
tag_build =
tag_date = 0
timeline-0.0.7/timeline/ 0000755 0001750 0001750 00000000000 13525014327 016636 5 ustar cjwatson cjwatson 0000000 0000000 timeline-0.0.7/timeline/timeline.py 0000644 0001750 0001750 00000010076 13274300705 021021 0 ustar cjwatson cjwatson 0000000 0000000 # Copyright (c) 2010, 2011, Canonical Ltd
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, version 3 only.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this program. If not, see .
"""Coordinate a sequence of non overlapping TimedActionss."""
from __future__ import absolute_import, print_function
__all__ = ['Timeline']
__metaclass__ = type
import datetime
import sys
import traceback
from pytz import utc as UTC
from timeline.nestingtimedaction import NestingTimedAction
from timeline.timedaction import TimedAction
class OverlappingActionError(Exception):
"""A new action was attempted without finishing the prior one."""
# To make analysis easy we do not permit overlapping actions by default:
# each action that is being timed and accrued must complete before the next
# is started. This means, for instance, that sending mail cannot do SQL
# queries, as both are timed and accrued. OTOH it makes analysis and
# serialisation of timelines simpler, and for the current use cases in
# Launchpad this is sufficient. This constraint should not be considered
# sacrosanct - if, in future, we desire timelines with overlapping actions,
# as long as the OOPS analysis code is extended to generate sensible
# reports in those situations, this can be changed. In the interim, actions
# can be explicitly setup to permit nesting by passing allow_nested=True
# which will cause the action to be recorded with 0 duration and a -start
# and -stop suffix added to its category. This is potentially lossy but
# good enough to get nested metrics and can be iterated on in the future to
# do an actual stacked/tree model of actions - if needed.
def format_stack():
"""Format a stack like traceback.format_stack, but skip 2 frames.
This means the stack formatting frame isn't in the backtrace itself.
"""
return traceback.format_stack(f=sys._getframe(2))
class Timeline:
"""A sequence of TimedActions.
This is used for collecting expensive/external actions inside Launchpad
requests.
:ivar actions: The actions.
:ivar baseline: The point the timeline starts at.
"""
def __init__(self, actions=None, format_stack=format_stack):
"""Create a Timeline.
:param actions: An optional object to use to store the timeline. This
must implement the list protocol.
:param format_stack: The helper to use when gathering tracebacks. If
None tracebacks are not gathered. Must return a list of strings
for concatenation.
"""
if actions is None:
actions = []
self.actions = actions
self.baseline = datetime.datetime.now(UTC)
self.format_stack = format_stack
def start(self, category, detail, allow_nested=False):
"""Create a new TimedAction at the end of the timeline.
:param category: the category for the action.
:param detail: The detail for the action.
:param allow_nested: If True treat this action as a nested action -
record it twice with 0 duration, once at the start and once at the
finish.
:return: A TimedAction for that category and detail.
"""
if allow_nested:
result = NestingTimedAction(category, detail, self)
else:
result = TimedAction(category, detail, self)
if self.actions and self.actions[-1].duration is None:
raise OverlappingActionError(self.actions[-1], result)
self.actions.append(result)
if self.format_stack is not None:
result.backtrace = ''.join(self.format_stack())
return result
timeline-0.0.7/timeline/tests/ 0000755 0001750 0001750 00000000000 13525014327 020000 5 ustar cjwatson cjwatson 0000000 0000000 timeline-0.0.7/timeline/tests/test_timedaction.py 0000644 0001750 0001750 00000007314 13305174235 023717 0 ustar cjwatson cjwatson 0000000 0000000 # Copyright (c) 2010, 2011, Canonical Ltd
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, version 3 only.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this program. If not, see .
"""Tests of the TimedAction class."""
from __future__ import absolute_import, print_function
__metaclass__ = type
import datetime
import testtools
from timeline.timedaction import TimedAction
from timeline.timeline import Timeline
class TestTimedAction(testtools.TestCase):
def test_starts_now(self):
action = TimedAction("Sending mail", None)
self.assertIsInstance(action.start, datetime.datetime)
def test_finish_sets_duration(self):
action = TimedAction("Sending mail", None)
self.assertEqual(None, action.duration)
action.finish()
self.assertIsInstance(action.duration, datetime.timedelta)
def test__init__sets_category(self):
action = TimedAction("Sending mail", None)
self.assertEqual("Sending mail", action.category)
def test__init__sets_detail(self):
action = TimedAction(None, "fred.jones@example.com")
self.assertEqual("fred.jones@example.com", action.detail)
def test_logTuple(self):
timeline = Timeline()
action = TimedAction("foo", "bar", timeline)
# Set variable for deterministic results
action.start = timeline.baseline + datetime.timedelta(0, 0, 0, 2)
action.duration = datetime.timedelta(0, 0, 0, 4)
log_tuple = action.logTuple()
self.assertEqual(5, len(log_tuple), "!= 5 elements %s" % (log_tuple,))
# The first element is the start offset in ms.
self.assertIsInstance(log_tuple[0], int)
self.assertEqual(2, log_tuple[0])
# The second element is the end offset in ms.
self.assertIsInstance(log_tuple[1], int)
self.assertEqual(6, log_tuple[1])
self.assertEqual("foo", log_tuple[2])
self.assertEqual("bar", log_tuple[3])
# The fifth element defaults to None:
self.assertEqual(None, log_tuple[4])
def test_logTupleIncomplete(self):
# Things that start and hit a timeout *may* not get recorded as
# finishing in normal operation - they still need to generate a
# logTuple, using now as the end point.
timeline = Timeline()
action = TimedAction("foo", "bar", timeline)
# Set variable for deterministic results
action.start = timeline.baseline + datetime.timedelta(0, 0, 0, 2)
action._interval_to_now = lambda: datetime.timedelta(0, 0, 0, 3)
log_tuple = action.logTuple()
self.assertEqual(5, len(log_tuple), "!= 5 elements %s" % (log_tuple,))
self.assertIsInstance(log_tuple[0], int)
self.assertEqual(2, log_tuple[0])
self.assertIsInstance(log_tuple[1], int)
self.assertEqual(5, log_tuple[1])
self.assertEqual("foo", log_tuple[2])
self.assertEqual("bar", log_tuple[3])
def test_logTupleBacktrace(self):
# If the action has a backtrace attribute, that is placed into the
# fifth element of logTuple.
timeline = Timeline()
action = TimedAction("foo", "bar", timeline)
action.finish()
action.backtrace = "Foo Bar"
log_tuple = action.logTuple()
self.assertEqual("Foo Bar", log_tuple[4])
timeline-0.0.7/timeline/tests/test_timeline.py 0000644 0001750 0001750 00000013626 13274300755 023233 0 ustar cjwatson cjwatson 0000000 0000000 # Copyright (c) 2010, 2011, Canonical Ltd
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, version 3 only.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this program. If not, see .
"""Tests of the Timeline class."""
from __future__ import absolute_import, print_function
__metaclass__ = type
import datetime
import testtools
from testtools.matchers import EndsWith
from timeline.timedaction import TimedAction
from timeline.timeline import OverlappingActionError, Timeline
class TestTimeline(testtools.TestCase):
def test_start_returns_action(self):
timeline = Timeline()
action = timeline.start("Sending mail", "Noone")
self.assertIsInstance(action, TimedAction)
self.assertEqual("Sending mail", action.category)
self.assertEqual("Noone", action.detail)
self.assertEqual(None, action.duration)
self.assertEqual(timeline, action.timeline)
def test_can_supply_list(self):
actions = "foo"
timeline = Timeline(actions)
self.assertEqual(actions, timeline.actions)
def test_start_with_unfinished_action_fails(self):
# A design constraint of timeline says that overlapping actions are not
# permitted. See the Timeline docstrings.
timeline = Timeline()
action = timeline.start("Sending mail", "Noone")
self.assertRaises(OverlappingActionError, timeline.start,
"Sending mail", "Noone")
def test_nested_start_permitted(self):
# When explicitly requested a nested start can be done
timeline = Timeline()
action = timeline.start("Calling openid", "hostname", allow_nested=True)
child = timeline.start("SQL Callback", "SELECT...")
def test_nested_start_is_not_transitive(self):
# nesting is explicit at each level - not inherited.
timeline = Timeline()
action = timeline.start("Calling openid", "hostname", allow_nested=True)
child = timeline.start("SQL Callback", "SELECT...")
self.assertRaises(OverlappingActionError, timeline.start,
"Sending mail", "Noone")
def test_multiple_nested_children_permitted(self):
# nesting is not reset by each action that is added.
timeline = Timeline()
action = timeline.start("Calling openid", "hostname", allow_nested=True)
child = timeline.start("SQL Callback", "SELECT...")
child.finish()
child = timeline.start("SQL Callback", "SELECT...")
def test_multiple_starts_after_nested_group_prevented(self):
# nesting stops being permitted when the nesting action is finished.
timeline = Timeline()
action = timeline.start("Calling openid", "hostname", allow_nested=True)
action.finish()
child = timeline.start("SQL Callback", "SELECT...")
self.assertRaises(OverlappingActionError, timeline.start,
"Sending mail", "Noone")
def test_nesting_within_nesting_permitted(self):
timeline = Timeline()
action = timeline.start("Calling openid", "hostname", allow_nested=True)
middle = timeline.start("Calling otherlibrary", "", allow_nested=True)
child = timeline.start("SQL Callback", "SELECT...")
def test_finishing_nested_within_nested_leaves_outer_nested_nesting(self):
timeline = Timeline()
action = timeline.start("Calling openid", "hostname", allow_nested=True)
middle = timeline.start("Calling otherlibrary", "", allow_nested=True)
middle.finish()
child = timeline.start("SQL Callback", "SELECT...")
def test_nested_actions_recorded_as_two_zero_length_actions(self):
timeline = Timeline()
action = timeline.start("Calling openid", "hostname", allow_nested=True)
child = timeline.start("SQL Callback", "SELECT...")
child.finish()
action.finish()
self.assertEqual(3, len(timeline.actions))
self.assertEqual(datetime.timedelta(), timeline.actions[0].duration)
self.assertEqual(datetime.timedelta(), timeline.actions[2].duration)
def test_nested_category_labels(self):
# To identify start/stop pairs '-start' and '-stop' are put onto the
# category of nested actions:
timeline = Timeline()
action = timeline.start("Calling openid", "hostname", allow_nested=True)
action.finish()
self.assertEqual('Calling openid-start', timeline.actions[0].category)
self.assertEqual('Calling openid-stop', timeline.actions[1].category)
def test_start_after_finish_works(self):
timeline = Timeline()
action = timeline.start("Sending mail", "Noone")
action.finish()
action = timeline.start("Sending mail", "Noone")
action.finish()
self.assertEqual(2, len(timeline.actions))
def test_baseline(self):
timeline = Timeline()
self.assertIsInstance(timeline.baseline, datetime.datetime)
def test_start_sets_backtrace_by_default(self):
timeline = Timeline()
action = timeline.start("Sending mail", "Noone")
self.assertNotEqual(None, action.backtrace)
self.assertIsInstance(action.backtrace, str)
self.assertThat(action.backtrace,
EndsWith(' action = timeline.start("Sending mail", "Noone")\n'))
def test_backtraces_can_be_disabled(self):
# Passing format_stack=None to Timeline prevents backtrace gathering.
timeline = Timeline(format_stack=None)
action = timeline.start("Sending mail", "Noone")
self.assertEqual(None, action.backtrace)
timeline-0.0.7/timeline/tests/test_nestingtimedaction.py 0000644 0001750 0001750 00000002567 13274300705 025312 0 ustar cjwatson cjwatson 0000000 0000000 # Copyright (c) 2010, 2011, Canonical Ltd
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, version 3 only.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this program. If not, see .
"""Tests of the TimedAction class."""
from __future__ import absolute_import, print_function
__metaclass__ = type
import datetime
import testtools
from timeline.nestingtimedaction import NestingTimedAction
from timeline.timeline import Timeline
class TestNestingTimedAction(testtools.TestCase):
def test_finish_adds_action(self):
timeline = Timeline()
action = NestingTimedAction("Sending mail", None, timeline)
action.finish()
self.assertEqual(1, len(timeline.actions))
self.assertEqual(datetime.timedelta(), timeline.actions[-1].duration)
def test__init__sets_duration(self):
action = NestingTimedAction("Sending mail", None)
self.assertEqual(datetime.timedelta(), action.duration)
timeline-0.0.7/timeline/tests/test_wsgi.py 0000644 0001750 0001750 00000002654 13274300705 022370 0 ustar cjwatson cjwatson 0000000 0000000 # Copyright (c) 2010, 2011, Canonical Ltd
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, version 3 only.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this program. If not, see .
"""Tests of the WSGI integration."""
from __future__ import absolute_import, print_function
import testtools
from timeline.timeline import Timeline
from timeline.wsgi import make_app
class TestMakeApp(testtools.TestCase):
def test_app_passes_through_setting_environ_variable(self):
environ = {'foo': 'bar'}
expected_start_response = object()
expected_result = object()
def inner_app(environ, start_response):
self.assertIsInstance(environ['timeline.timeline'], Timeline)
self.assertEqual(expected_start_response, start_response)
self.assertEqual('bar', environ['foo'])
return expected_result
app = make_app(inner_app)
self.assertEqual(
expected_result, app(environ, expected_start_response))
timeline-0.0.7/timeline/tests/__init__.py 0000644 0001750 0001750 00000001763 13274300705 022117 0 ustar cjwatson cjwatson 0000000 0000000 # Copyright (c) 2010, 2011, Canonical Ltd
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, version 3 only.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this program. If not, see .
"""Tests for timeline."""
from __future__ import absolute_import, print_function
from unittest import TestLoader
def test_suite():
test_mod_names = [
'timeline',
'timedaction',
'nestingtimedaction',
'wsgi',
]
return TestLoader().loadTestsFromNames(
['timeline.tests.test_' + name for name in test_mod_names])
timeline-0.0.7/timeline/nestingtimedaction.py 0000644 0001750 0001750 00000003043 13274300705 023077 0 ustar cjwatson cjwatson 0000000 0000000 # Copyright (c) 2010, 2011, Canonical Ltd
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, version 3 only.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this program. If not, see .
"""Time an action which calls other timed actions."""
from __future__ import absolute_import, print_function
__all__ = ['NestingTimedAction']
__metaclass__ = type
import datetime
from timeline.timedaction import TimedAction
class NestingTimedAction(TimedAction):
"""A variation of TimedAction which creates a nested environment.
This is done by recording two 0 length timed actions in the timeline:
one at the start of the action and one at the end, with -start and
-stop appended to their categories.
See `TimedAction` for more information.
"""
def _init(self):
self.duration = datetime.timedelta()
self._category = self.category
self.category = self._category + '-start'
def finish(self):
"""Mark the TimedAction as finished."""
end = self.timeline.start(self._category + '-stop', self.detail)
end.duration = datetime.timedelta()
timeline-0.0.7/timeline/timedaction.py 0000644 0001750 0001750 00000006604 13305174235 021517 0 ustar cjwatson cjwatson 0000000 0000000 # Copyright (c) 2010, 2011, Canonical Ltd
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, version 3 only.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this program. If not, see .
"""Time a single categorised action."""
from __future__ import absolute_import, division, print_function
__all__ = ['TimedAction']
__metaclass__ = type
import datetime
import pytz
UTC = pytz.utc
class TimedAction:
"""An individual action which has been timed.
:ivar timeline: The timeline that this action took place within.
:ivar start: A datetime object with tz for the start of the action.
:ivar duration: A timedelta for the duration of the action. None for
actions which have not completed.
:ivar category: The category of the action. E.g. "sql".
:ivar detail: The detail about the action. E.g. "SELECT COUNT(*) ..."
:ivar backtrace: A backtrace for when the action was started. Useful for
detecting code paths that cause lots of actions (usually a bad thing
from a performance perspective).
"""
def __init__(self, category, detail, timeline=None):
"""Create a TimedAction.
New TimedActions have a start but no duration.
:param category: The category for the action.
:param detail: The detail about the action being timed.
:param timeline: The timeline for the action.
"""
self.start = datetime.datetime.now(UTC)
self.duration = None
self.category = category
self.detail = detail
self.timeline = timeline
self.backtrace = None
self._init()
def _init(self):
# hook for child classes.
pass
def __repr__(self):
return "<%s %s[%s]>" % (self.__class__, self.category,
self.detail[:20])
def logTuple(self):
"""Return a 4-tuple suitable for errorlog's use."""
offset = self._td_to_ms(self.start - self.timeline.baseline)
if self.duration is None:
# This action wasn't finished: pretend it has finished now
# (even though it hasn't). This is pretty normal when action ends
# are recorded by callbacks rather than stack-like structures. E.g.
# storm tracers in launchpad:
# log-trace START : starts action
# timeout-trace START : raises
# log-trace FINISH is never called.
length = self._td_to_ms(self._interval_to_now())
else:
length = self._td_to_ms(self.duration)
return (offset, offset + length, self.category, self.detail,
self.backtrace)
def _td_to_ms(self, td):
"""Tweak on a backport from python 2.7"""
return (td.microseconds + (
td.seconds + td.days * 24 * 3600) * 10**6) // 10**3
def finish(self):
"""Mark the TimedAction as finished."""
self.duration = self._interval_to_now()
def _interval_to_now(self):
return datetime.datetime.now(UTC) - self.start
timeline-0.0.7/timeline/__init__.py 0000644 0001750 0001750 00000002766 13525014257 020764 0 ustar cjwatson cjwatson 0000000 0000000 #
# Copyright (c) 2011, Canonical Ltd
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, version 3 only.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this program. If not, see .
from __future__ import absolute_import, print_function
# same format as sys.version_info: "A tuple containing the five components of
# the version number: major, minor, micro, releaselevel, and serial. All
# values except releaselevel are integers; the release level is 'alpha',
# 'beta', 'candidate', or 'final'. The version_info value corresponding to the
# Python version 2.0 is (2, 0, 0, 'final', 0)." Additionally we use a
# releaselevel of 'dev' for unreleased under-development code.
#
# If the releaselevel is 'alpha' then the major/minor/micro components are not
# established at this point, and setup.py will use a version of next-$(revno).
# If the releaselevel is 'final', then the tarball will be major.minor.micro.
# Otherwise it is major.minor.micro~$(revno).
__version__ = (0, 0, 7, 'final', 0)
__all__ = [
'Timeline',
]
from timeline.timeline import Timeline
timeline-0.0.7/timeline/wsgi.py 0000644 0001750 0001750 00000002125 13274300705 020160 0 ustar cjwatson cjwatson 0000000 0000000 # Copyright (c) 2010, 2011, Canonical Ltd
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, version 3 only.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this program. If not, see .
"""WSGI integration."""
from __future__ import absolute_import, print_function
__all__ = ['make_app']
from timeline.timeline import Timeline
def make_app(app):
"""Create a WSGI app wrapping app.
The wrapper app injects an environ variable 'timeline.timeline' which is a
Timeline object.
"""
def wrapper(environ, start_response):
environ['timeline.timeline'] = Timeline()
return app(environ, start_response)
return wrapper
timeline-0.0.7/timeline.egg-info/ 0000755 0001750 0001750 00000000000 13525014327 020330 5 ustar cjwatson cjwatson 0000000 0000000 timeline-0.0.7/timeline.egg-info/PKG-INFO 0000644 0001750 0001750 00000010125 13525014327 021424 0 ustar cjwatson cjwatson 0000000 0000000 Metadata-Version: 2.1
Name: timeline
Version: 0.0.7
Summary: Timeline module for modelling a series of actions.
Home-page: https://launchpad.net/python-timeline
Maintainer: Launchpad Developers
Maintainer-email: launchpad-dev@lists.launchpad.net
License: UNKNOWN
Description: *************************************************
python-timeline: Modelling of a series of actions
*************************************************
Copyright (c) 2011, Canonical Ltd
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, version 3 only.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with this program. If not, see .
The timeline package provides a way to model a series of actions. For instance,
during a web request the appserver might call out to a number of backend
services, make sql queries and memcached lookups. All of these actions can be
tracked in a single timeline, and that serialised or analysed for slow points.
Dependencies
============
* Python 2.6+
* pytz
Testing Dependencies
====================
* subunit (http://pypi.python.org/pypi/python-subunit) (optional)
* testtools (http://pypi.python.org/pypi/testtools)
Usage
=====
Create a timeline object:
>>> from timeline.timeline import TimeLine
>>> log = TimeLine()
Then add actions:
>>> action = log.start('mycategory', 'mydetails')
Perform your action and then tell the action it has finished:
>>> action.finish()
At this point you can start another action. If you wish to nest actions, pass
allow_nested=True to start().
One of the things needed when working with timelines in complex applications is
locating the right one. Timeline provides a helper for WSGI apps:
>>> from timeline import wsgi
>>> app = wsgi.make_app(inner_app)
Calls to app will now inject a 'timeline.timeline' variable into the wsgi
environwhich can be used by inner_app to record actions.
Installation
============
Either run setup.py in an environment with all the dependencies available, or
add the working directory to your PYTHONPATH.
Development
===========
Upstream development takes place at https://launchpad.net/python-timeline.
To setup a working area for development, if the dependencies are not
immediately available, you can use ./bootstrap.py to create bin/buildout, then
bin/py to get a python interpreter with the dependencies available.
To run the tests use the runner of your choice, the test suite is
timeline.tests.test_suite.
For instance::
$ bin/py -m testtools.run timeline.tests.test_suite
If you have testrepository you can run the tests with testr::
$ testr run
Platform: UNKNOWN
Classifier: Development Status :: 2 - Pre-Alpha
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL)
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python
Classifier: Programming Language :: Python :: 2
Classifier: Programming Language :: Python :: 3
Provides-Extra: test
timeline-0.0.7/timeline.egg-info/dependency_links.txt 0000644 0001750 0001750 00000000001 13525014327 024376 0 ustar cjwatson cjwatson 0000000 0000000
timeline-0.0.7/timeline.egg-info/requires.txt 0000644 0001750 0001750 00000000027 13525014327 022727 0 ustar cjwatson cjwatson 0000000 0000000 pytz
[test]
testtools
timeline-0.0.7/timeline.egg-info/top_level.txt 0000644 0001750 0001750 00000000011 13525014327 023052 0 ustar cjwatson cjwatson 0000000 0000000 timeline
timeline-0.0.7/timeline.egg-info/SOURCES.txt 0000644 0001750 0001750 00000000735 13525014327 022221 0 ustar cjwatson cjwatson 0000000 0000000 LICENSE
MANIFEST.in
NEWS
README
setup.py
timeline/__init__.py
timeline/nestingtimedaction.py
timeline/timedaction.py
timeline/timeline.py
timeline/wsgi.py
timeline.egg-info/PKG-INFO
timeline.egg-info/SOURCES.txt
timeline.egg-info/dependency_links.txt
timeline.egg-info/requires.txt
timeline.egg-info/top_level.txt
timeline/tests/__init__.py
timeline/tests/test_nestingtimedaction.py
timeline/tests/test_timedaction.py
timeline/tests/test_timeline.py
timeline/tests/test_wsgi.py timeline-0.0.7/README 0000644 0001750 0001750 00000005320 13251472703 015712 0 ustar cjwatson cjwatson 0000000 0000000 *************************************************
python-timeline: Modelling of a series of actions
*************************************************
Copyright (c) 2011, Canonical Ltd
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, version 3 only.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with this program. If not, see .
The timeline package provides a way to model a series of actions. For instance,
during a web request the appserver might call out to a number of backend
services, make sql queries and memcached lookups. All of these actions can be
tracked in a single timeline, and that serialised or analysed for slow points.
Dependencies
============
* Python 2.6+
* pytz
Testing Dependencies
====================
* subunit (http://pypi.python.org/pypi/python-subunit) (optional)
* testtools (http://pypi.python.org/pypi/testtools)
Usage
=====
Create a timeline object:
>>> from timeline.timeline import TimeLine
>>> log = TimeLine()
Then add actions:
>>> action = log.start('mycategory', 'mydetails')
Perform your action and then tell the action it has finished:
>>> action.finish()
At this point you can start another action. If you wish to nest actions, pass
allow_nested=True to start().
One of the things needed when working with timelines in complex applications is
locating the right one. Timeline provides a helper for WSGI apps:
>>> from timeline import wsgi
>>> app = wsgi.make_app(inner_app)
Calls to app will now inject a 'timeline.timeline' variable into the wsgi
environwhich can be used by inner_app to record actions.
Installation
============
Either run setup.py in an environment with all the dependencies available, or
add the working directory to your PYTHONPATH.
Development
===========
Upstream development takes place at https://launchpad.net/python-timeline.
To setup a working area for development, if the dependencies are not
immediately available, you can use ./bootstrap.py to create bin/buildout, then
bin/py to get a python interpreter with the dependencies available.
To run the tests use the runner of your choice, the test suite is
timeline.tests.test_suite.
For instance::
$ bin/py -m testtools.run timeline.tests.test_suite
If you have testrepository you can run the tests with testr::
$ testr run