1
# Copyright 2010 Canonical Ltd. This software is licensed under the
2
# GNU Affero General Public License version 3 (see the file LICENSE).
4
"""Coordinate a sequence of non overlapping TimedActionss."""
12
from pytz import utc as UTC
14
from timedaction import TimedAction
15
from nestingtimedaction import NestingTimedAction
18
class OverlappingActionError(Exception):
19
"""A new action was attempted without finishing the prior one."""
20
# To make analysis easy we do not permit overlapping actions by default:
21
# each action that is being timed and accrued must complete before the next
22
# is started. This means, for instance, that sending mail cannot do SQL
23
# queries, as both are timed and accrued. OTOH it makes analysis and
24
# serialisation of timelines simpler, and for the current use cases in
25
# Launchpad this is sufficient. This constraint should not be considered
26
# sacrosanct - if, in future, we desire timelines with overlapping actions,
27
# as long as the OOPS analysis code is extended to generate sensible
28
# reports in those situations, this can be changed. In the interim, actions
29
# can be explicitly setup to permit nesting by passing allow_nested=True
30
# which will cause the action to be recorded with 0 duration and a -start
31
# and -stop suffix added to its category. This is potentially lossy but
32
# good enough to get nested metrics and can be iterated on in the future to
33
# do an actual stacked/tree model of actions - if needed.
37
"""A sequence of TimedActions.
39
This is used for collecting expensive/external actions inside Launchpad
42
:ivar actions: The actions.
43
:ivar baseline: The point the timeline starts at.
46
def __init__(self, actions=None):
49
:param actions: An optional object to use to store the timeline. This
50
must implement the list protocol.
54
self.actions = actions
55
self.baseline = datetime.datetime.now(UTC)
57
def start(self, category, detail, allow_nested=False):
58
"""Create a new TimedAction at the end of the timeline.
60
:param category: the category for the action.
61
:param detail: The detail for the action.
62
:param allow_nested: If True treat this action as a nested action -
63
record it twice with 0 duration, once at the start and once at the
65
:return: A TimedAction for that category and detail.
68
result = NestingTimedAction(category, detail, self)
70
result = TimedAction(category, detail, self)
71
if self.actions and self.actions[-1].duration is None:
72
raise OverlappingActionError(self.actions[-1], result)
73
self.actions.append(result)