From 9fe99ba7106c4de5655c40c28d175e136ddd29d2 Mon Sep 17 00:00:00 2001 From: Joshua Harlow Date: Mon, 12 Jan 2015 19:57:50 -0800 Subject: [PATCH] Add split time capturing to the stop watch For cases where it is useful to capture the elapsed time of a watch and later examine those split times create a method on the stop watch that allows for these kinds of captures and iterations that correspond to the common stop watch split capability. Also adds basic docstrings to the stop watch public methods so that people know what they are and can use them. Change-Id: Ic52ae5dfcca9a117ccd0dda5cc62a14c09e15ce0 --- taskflow/tests/unit/test_types.py | 24 ++++++++++++ taskflow/types/timing.py | 61 +++++++++++++++++++++++++++++++ 2 files changed, 85 insertions(+) diff --git a/taskflow/tests/unit/test_types.py b/taskflow/tests/unit/test_types.py index 28b57251..85d6e557 100644 --- a/taskflow/tests/unit/test_types.py +++ b/taskflow/tests/unit/test_types.py @@ -193,6 +193,30 @@ class StopWatchTest(test.TestCase): timeutils.advance_time_seconds(0.05) self.assertGreater(0.01, watch.elapsed()) + def test_splits(self): + watch = tt.StopWatch() + watch.start() + self.assertEqual(0, len(watch.splits)) + + watch.split() + self.assertEqual(1, len(watch.splits)) + self.assertEqual(watch.splits[0].elapsed, + watch.splits[0].length) + + timeutils.advance_time_seconds(0.05) + watch.split() + splits = watch.splits + self.assertEqual(2, len(splits)) + self.assertNotEqual(splits[0].elapsed, splits[1].elapsed) + self.assertEqual(splits[1].length, + splits[1].elapsed - splits[0].elapsed) + + watch.stop() + self.assertEqual(2, len(watch.splits)) + + watch.start() + self.assertEqual(0, len(watch.splits)) + class TableTest(test.TestCase): def test_create_valid_no_rows(self): diff --git a/taskflow/types/timing.py b/taskflow/types/timing.py index c7cb38db..2cbeee9b 100644 --- a/taskflow/types/timing.py +++ b/taskflow/types/timing.py @@ -44,6 +44,34 @@ class Timeout(object): self._event.clear() +class Split(object): + """A *immutable* stopwatch split. + + See: http://en.wikipedia.org/wiki/Stopwatch for what this is/represents. + """ + + __slots__ = ['_elapsed', '_length'] + + def __init__(self, elapsed, length): + self._elapsed = elapsed + self._length = length + + @property + def elapsed(self): + """Duration from stopwatch start.""" + return self._elapsed + + @property + def length(self): + """Seconds from last split (or the elapsed time if no prior split).""" + return self._length + + def __repr__(self): + r = self.__class__.__name__ + r += "(elapsed=%s, length=%s)" % (self._elapsed, self._length) + return r + + class StopWatch(object): """A simple timer/stopwatch helper class. @@ -67,22 +95,49 @@ class StopWatch(object): self._started_at = None self._stopped_at = None self._state = None + self._splits = [] def start(self): + """Starts the watch (if not already started). + + NOTE(harlowja): resets any splits previously captured (if any). + """ if self._state == self._STARTED: return self self._started_at = timeutils.utcnow() self._stopped_at = None self._state = self._STARTED + self._splits = [] return self + @property + def splits(self): + """Accessor to all/any splits that have been captured.""" + return tuple(self._splits) + + def split(self): + """Captures a split/elapsed since start time (and doesn't stop).""" + if self._state == self._STARTED: + elapsed = self.elapsed() + if self._splits: + length = max(0.0, elapsed - self._splits[-1].elapsed) + else: + length = elapsed + self._splits.append(Split(elapsed, length)) + return self._splits[-1] + else: + raise RuntimeError("Can not create a split time of a stopwatch" + " if it has not been started") + def restart(self): + """Restarts the watch from a started/stopped state.""" if self._state == self._STARTED: self.stop() self.start() return self def elapsed(self): + """Returns how many seconds have elapsed.""" if self._state == self._STOPPED: return max(0.0, float(timeutils.delta_seconds(self._started_at, self._stopped_at))) @@ -94,16 +149,19 @@ class StopWatch(object): " if it has not been started/stopped") def __enter__(self): + """Starts the watch.""" self.start() return self def __exit__(self, type, value, traceback): + """Stops the watch (ignoring errors if stop fails).""" try: self.stop() except RuntimeError: pass def leftover(self): + """Returns how many seconds are left until the watch expires.""" if self._duration is None: raise RuntimeError("Can not get the leftover time of a watch that" " has no duration") @@ -113,6 +171,7 @@ class StopWatch(object): return max(0.0, self._duration - self.elapsed()) def expired(self): + """Returns if the watch has expired (ie, duration provided elapsed).""" if self._duration is None: return False if self._state is None: @@ -123,6 +182,7 @@ class StopWatch(object): return False def resume(self): + """Resumes the watch from a stopped state.""" if self._state == self._STOPPED: self._state = self._STARTED return self @@ -131,6 +191,7 @@ class StopWatch(object): " stopped") def stop(self): + """Stops the watch.""" if self._state == self._STOPPED: return self if self._state != self._STARTED: