timeline-0.0.7/0000755000175000017500000000000013525014327015030 5ustar cjwatsoncjwatson00000000000000timeline-0.0.7/PKG-INFO0000644000175000017500000001012513525014327016124 0ustar cjwatsoncjwatson00000000000000Metadata-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/LICENSE0000644000175000017500000001672713251472703016054 0ustar cjwatsoncjwatson00000000000000 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.py0000755000175000017500000000333113525014254016544 0ustar cjwatsoncjwatson00000000000000#!/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.in0000644000175000017500000000002513524345475016575 0ustar cjwatsoncjwatson00000000000000include LICENSE NEWS timeline-0.0.7/NEWS0000644000175000017500000000211513525014234015523 0ustar cjwatsoncjwatson00000000000000timeline 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.cfg0000644000175000017500000000004613525014327016651 0ustar cjwatsoncjwatson00000000000000[egg_info] tag_build = tag_date = 0 timeline-0.0.7/timeline/0000755000175000017500000000000013525014327016636 5ustar cjwatsoncjwatson00000000000000timeline-0.0.7/timeline/timeline.py0000644000175000017500000001007613274300705021021 0ustar cjwatsoncjwatson00000000000000# 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/0000755000175000017500000000000013525014327020000 5ustar cjwatsoncjwatson00000000000000timeline-0.0.7/timeline/tests/test_timedaction.py0000644000175000017500000000731413305174235023717 0ustar cjwatsoncjwatson00000000000000# 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.py0000644000175000017500000001362613274300755023233 0ustar cjwatsoncjwatson00000000000000# 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.py0000644000175000017500000000256713274300705025312 0ustar cjwatsoncjwatson00000000000000# 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.py0000644000175000017500000000265413274300705022370 0ustar cjwatsoncjwatson00000000000000# 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__.py0000644000175000017500000000176313274300705022117 0ustar cjwatsoncjwatson00000000000000# 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.py0000644000175000017500000000304313274300705023077 0ustar cjwatsoncjwatson00000000000000# 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.py0000644000175000017500000000660413305174235021517 0ustar cjwatsoncjwatson00000000000000# 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__.py0000644000175000017500000000276613525014257020764 0ustar cjwatsoncjwatson00000000000000# # 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.py0000644000175000017500000000212513274300705020160 0ustar cjwatsoncjwatson00000000000000# 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/0000755000175000017500000000000013525014327020330 5ustar cjwatsoncjwatson00000000000000timeline-0.0.7/timeline.egg-info/PKG-INFO0000644000175000017500000001012513525014327021424 0ustar cjwatsoncjwatson00000000000000Metadata-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.txt0000644000175000017500000000000113525014327024376 0ustar cjwatsoncjwatson00000000000000 timeline-0.0.7/timeline.egg-info/requires.txt0000644000175000017500000000002713525014327022727 0ustar cjwatsoncjwatson00000000000000pytz [test] testtools timeline-0.0.7/timeline.egg-info/top_level.txt0000644000175000017500000000001113525014327023052 0ustar cjwatsoncjwatson00000000000000timeline timeline-0.0.7/timeline.egg-info/SOURCES.txt0000644000175000017500000000073513525014327022221 0ustar cjwatsoncjwatson00000000000000LICENSE 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.pytimeline-0.0.7/README0000644000175000017500000000532013251472703015712 0ustar cjwatsoncjwatson00000000000000************************************************* 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