Merge "Use monotonic time when/if available"

This commit is contained in:
Jenkins
2015-01-28 08:50:04 +00:00
committed by Gerrit Code Review
5 changed files with 143 additions and 38 deletions

View File

@@ -15,7 +15,6 @@
# under the License. # under the License.
import networkx as nx import networkx as nx
from oslo_utils import timeutils
import six import six
from taskflow import exceptions as excp from taskflow import exceptions as excp
@@ -123,8 +122,8 @@ class TreeTest(test.TestCase):
class StopWatchTest(test.TestCase): class StopWatchTest(test.TestCase):
def setUp(self): def setUp(self):
super(StopWatchTest, self).setUp() super(StopWatchTest, self).setUp()
timeutils.set_time_override() tt.StopWatch.set_now_override(now=0)
self.addCleanup(timeutils.clear_time_override) self.addCleanup(tt.StopWatch.clear_overrides)
def test_no_states(self): def test_no_states(self):
watch = tt.StopWatch() watch = tt.StopWatch()
@@ -137,23 +136,23 @@ class StopWatchTest(test.TestCase):
def test_backwards(self): def test_backwards(self):
watch = tt.StopWatch(0.1) watch = tt.StopWatch(0.1)
watch.start() watch.start()
timeutils.advance_time_seconds(0.5) tt.StopWatch.advance_time_seconds(0.5)
self.assertTrue(watch.expired()) self.assertTrue(watch.expired())
timeutils.advance_time_seconds(-1.0) tt.StopWatch.advance_time_seconds(-1.0)
self.assertFalse(watch.expired()) self.assertFalse(watch.expired())
self.assertEqual(0.0, watch.elapsed()) self.assertEqual(0.0, watch.elapsed())
def test_expiry(self): def test_expiry(self):
watch = tt.StopWatch(0.1) watch = tt.StopWatch(0.1)
watch.start() watch.start()
timeutils.advance_time_seconds(0.2) tt.StopWatch.advance_time_seconds(0.2)
self.assertTrue(watch.expired()) self.assertTrue(watch.expired())
def test_not_expired(self): def test_not_expired(self):
watch = tt.StopWatch(0.1) watch = tt.StopWatch(0.1)
watch.start() watch.start()
timeutils.advance_time_seconds(0.05) tt.StopWatch.advance_time_seconds(0.05)
self.assertFalse(watch.expired()) self.assertFalse(watch.expired())
def test_no_expiry(self): def test_no_expiry(self):
@@ -163,7 +162,7 @@ class StopWatchTest(test.TestCase):
def test_elapsed(self): def test_elapsed(self):
watch = tt.StopWatch() watch = tt.StopWatch()
watch.start() watch.start()
timeutils.advance_time_seconds(0.2) tt.StopWatch.advance_time_seconds(0.2)
# NOTE(harlowja): Allow for a slight variation by using 0.19. # NOTE(harlowja): Allow for a slight variation by using 0.19.
self.assertGreaterEqual(0.19, watch.elapsed()) self.assertGreaterEqual(0.19, watch.elapsed())
@@ -180,17 +179,17 @@ class StopWatchTest(test.TestCase):
def test_pause_resume(self): def test_pause_resume(self):
watch = tt.StopWatch() watch = tt.StopWatch()
watch.start() watch.start()
timeutils.advance_time_seconds(0.05) tt.StopWatch.advance_time_seconds(0.05)
watch.stop() watch.stop()
elapsed = watch.elapsed() elapsed = watch.elapsed()
self.assertAlmostEqual(elapsed, watch.elapsed()) self.assertAlmostEqual(elapsed, watch.elapsed())
watch.resume() watch.resume()
timeutils.advance_time_seconds(0.05) tt.StopWatch.advance_time_seconds(0.05)
self.assertNotEqual(elapsed, watch.elapsed()) self.assertNotEqual(elapsed, watch.elapsed())
def test_context_manager(self): def test_context_manager(self):
with tt.StopWatch() as watch: with tt.StopWatch() as watch:
timeutils.advance_time_seconds(0.05) tt.StopWatch.advance_time_seconds(0.05)
self.assertGreater(0.01, watch.elapsed()) self.assertGreater(0.01, watch.elapsed())
def test_splits(self): def test_splits(self):
@@ -203,7 +202,7 @@ class StopWatchTest(test.TestCase):
self.assertEqual(watch.splits[0].elapsed, self.assertEqual(watch.splits[0].elapsed,
watch.splits[0].length) watch.splits[0].length)
timeutils.advance_time_seconds(0.05) tt.StopWatch.advance_time_seconds(0.05)
watch.split() watch.split()
splits = watch.splits splits = watch.splits
self.assertEqual(2, len(splits)) self.assertEqual(2, len(splits))
@@ -221,16 +220,16 @@ class StopWatchTest(test.TestCase):
watch = tt.StopWatch() watch = tt.StopWatch()
watch.start() watch.start()
timeutils.advance_time_seconds(1) tt.StopWatch.advance_time_seconds(1)
self.assertEqual(1, watch.elapsed()) self.assertEqual(1, watch.elapsed())
timeutils.advance_time_seconds(10) tt.StopWatch.advance_time_seconds(10)
self.assertEqual(11, watch.elapsed()) self.assertEqual(11, watch.elapsed())
self.assertEqual(1, watch.elapsed(maximum=1)) self.assertEqual(1, watch.elapsed(maximum=1))
watch.stop() watch.stop()
self.assertEqual(11, watch.elapsed()) self.assertEqual(11, watch.elapsed())
timeutils.advance_time_seconds(10) tt.StopWatch.advance_time_seconds(10)
self.assertEqual(11, watch.elapsed()) self.assertEqual(11, watch.elapsed())
self.assertEqual(0, watch.elapsed(maximum=-1)) self.assertEqual(0, watch.elapsed(maximum=-1))

View File

@@ -15,7 +15,6 @@
# under the License. # under the License.
from concurrent import futures from concurrent import futures
from oslo_utils import timeutils
from oslo_utils import uuidutils from oslo_utils import uuidutils
from taskflow.engines.action_engine import executor from taskflow.engines.action_engine import executor
@@ -24,6 +23,7 @@ from taskflow import exceptions as excp
from taskflow import test from taskflow import test
from taskflow.tests import utils from taskflow.tests import utils
from taskflow.types import failure from taskflow.types import failure
from taskflow.types import timing
class TestProtocolValidation(test.TestCase): class TestProtocolValidation(test.TestCase):
@@ -94,8 +94,8 @@ class TestProtocol(test.TestCase):
def setUp(self): def setUp(self):
super(TestProtocol, self).setUp() super(TestProtocol, self).setUp()
timeutils.set_time_override() timing.StopWatch.set_now_override()
self.addCleanup(timeutils.clear_time_override) self.addCleanup(timing.StopWatch.clear_overrides)
self.task = utils.DummyTask() self.task = utils.DummyTask()
self.task_uuid = 'task-uuid' self.task_uuid = 'task-uuid'
self.task_action = 'execute' self.task_action = 'execute'
@@ -166,19 +166,19 @@ class TestProtocol(test.TestCase):
def test_pending_not_expired(self): def test_pending_not_expired(self):
req = self.request() req = self.request()
timeutils.advance_time_seconds(self.timeout - 1) timing.StopWatch.set_offset_override(self.timeout - 1)
self.assertFalse(req.expired) self.assertFalse(req.expired)
def test_pending_expired(self): def test_pending_expired(self):
req = self.request() req = self.request()
timeutils.advance_time_seconds(self.timeout + 1) timing.StopWatch.set_offset_override(self.timeout + 1)
self.assertTrue(req.expired) self.assertTrue(req.expired)
def test_running_not_expired(self): def test_running_not_expired(self):
request = self.request() request = self.request()
request.transition(pr.PENDING) request.transition(pr.PENDING)
request.transition(pr.RUNNING) request.transition(pr.RUNNING)
timeutils.advance_time_seconds(self.timeout + 1) timing.StopWatch.set_offset_override(self.timeout + 1)
self.assertFalse(request.expired) self.assertFalse(request.expired)
def test_set_result(self): def test_set_result(self):

View File

@@ -14,12 +14,10 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
import datetime
import threading import threading
import time import time
from oslo.utils import reflection from oslo.utils import reflection
from oslo.utils import timeutils
from taskflow.engines.worker_based import protocol as pr from taskflow.engines.worker_based import protocol as pr
from taskflow.engines.worker_based import types as worker_types from taskflow.engines.worker_based import types as worker_types
@@ -33,6 +31,7 @@ class TestWorkerTypes(test.TestCase):
def setUp(self): def setUp(self):
super(TestWorkerTypes, self).setUp() super(TestWorkerTypes, self).setUp()
self.addCleanup(timing.StopWatch.clear_overrides)
self.task = utils.DummyTask() self.task = utils.DummyTask()
self.task_uuid = 'task-uuid' self.task_uuid = 'task-uuid'
self.task_action = 'execute' self.task_action = 'execute'
@@ -52,15 +51,12 @@ class TestWorkerTypes(test.TestCase):
def test_requests_cache_expiry(self): def test_requests_cache_expiry(self):
# Mock out the calls the underlying objects will soon use to return # Mock out the calls the underlying objects will soon use to return
# times that we can control more easily... # times that we can control more easily...
now = timeutils.utcnow()
overrides = [ overrides = [
now, 0,
now, 1,
now + datetime.timedelta(seconds=1), self.timeout + 1,
now + datetime.timedelta(seconds=self.timeout + 1),
] ]
timeutils.set_time_override(overrides) timing.StopWatch.set_now_override(overrides)
self.addCleanup(timeutils.clear_time_override)
cache = worker_types.RequestsCache() cache = worker_types.RequestsCache()
cache[self.task_uuid] = self.request() cache[self.task_uuid] = self.request()

View File

@@ -15,10 +15,14 @@
# under the License. # under the License.
from oslo_utils import reflection from oslo_utils import reflection
from oslo_utils import timeutils
from taskflow.utils import misc
from taskflow.utils import threading_utils from taskflow.utils import threading_utils
# Find a monotonic providing time (or fallback to using time.time()
# which isn't *always* accurate but will suffice).
_now = misc.find_monotonic(allow_time_time=True)
class Timeout(object): class Timeout(object):
"""An object which represents a timeout. """An object which represents a timeout.
@@ -86,6 +90,12 @@ class StopWatch(object):
_STARTED = 'STARTED' _STARTED = 'STARTED'
_STOPPED = 'STOPPED' _STOPPED = 'STOPPED'
"""
Class variables that should only be used for testing purposes only...
"""
_now_offset = None
_now_override = None
def __init__(self, duration=None): def __init__(self, duration=None):
if duration is not None: if duration is not None:
if duration < 0: if duration < 0:
@@ -105,7 +115,7 @@ class StopWatch(object):
""" """
if self._state == self._STARTED: if self._state == self._STARTED:
return self return self
self._started_at = timeutils.utcnow() self._started_at = self._now()
self._stopped_at = None self._stopped_at = None
self._state = self._STARTED self._state = self._STARTED
self._splits = [] self._splits = []
@@ -121,7 +131,7 @@ class StopWatch(object):
if self._state == self._STARTED: if self._state == self._STARTED:
elapsed = self.elapsed() elapsed = self.elapsed()
if self._splits: if self._splits:
length = max(0.0, elapsed - self._splits[-1].elapsed) length = self._delta_seconds(self._splits[-1].elapsed, elapsed)
else: else:
length = elapsed length = elapsed
self._splits.append(Split(elapsed, length)) self._splits.append(Split(elapsed, length))
@@ -137,17 +147,88 @@ class StopWatch(object):
self.start() self.start()
return self return self
@classmethod
def clear_overrides(cls):
"""Clears all overrides/offsets.
**Only to be used for testing (affects all watch instances).**
"""
cls._now_override = None
cls._now_offset = None
@classmethod
def set_offset_override(cls, offset):
"""Sets a offset that is applied to each time fetch.
**Only to be used for testing (affects all watch instances).**
"""
cls._now_offset = offset
@classmethod
def advance_time_seconds(cls, offset):
"""Advances/sets a offset that is applied to each time fetch.
NOTE(harlowja): if a previous offset exists (not ``None``) then this
offset will be added onto the existing one (if you want to reset
the offset completely use the :meth:`.set_offset_override`
method instead).
**Only to be used for testing (affects all watch instances).**
"""
if cls._now_offset is None:
cls.set_offset_override(offset)
else:
cls.set_offset_override(cls._now_offset + offset)
@classmethod
def set_now_override(cls, now=None):
"""Sets time override to use (if none, then current time is fetched).
NOTE(harlowja): if a list/tuple is provided then the first element of
the list will be used (and removed) each time a time fetch occurs (once
it becomes empty the override/s will no longer be applied). If a
numeric value is provided then it will be used (and never removed
until the override(s) are cleared via the :meth:`.clear_overrides`
method).
**Only to be used for testing (affects all watch instances).**
"""
if isinstance(now, (list, tuple)):
cls._now_override = list(now)
else:
if now is None:
now = _now()
cls._now_override = now
@staticmethod
def _delta_seconds(earlier, later):
return max(0.0, later - earlier)
@classmethod
def _now(cls):
if cls._now_override is not None:
if isinstance(cls._now_override, list):
try:
now = cls._now_override.pop(0)
except IndexError:
now = _now()
else:
now = cls._now_override
else:
now = _now()
if cls._now_offset is not None:
now = now + cls._now_offset
return now
def elapsed(self, maximum=None): def elapsed(self, maximum=None):
"""Returns how many seconds have elapsed.""" """Returns how many seconds have elapsed."""
if self._state not in (self._STOPPED, self._STARTED): if self._state not in (self._STOPPED, self._STARTED):
raise RuntimeError("Can not get the elapsed time of a stopwatch" raise RuntimeError("Can not get the elapsed time of a stopwatch"
" if it has not been started/stopped") " if it has not been started/stopped")
if self._state == self._STOPPED: if self._state == self._STOPPED:
elapsed = max(0.0, float(timeutils.delta_seconds( elapsed = self._delta_seconds(self._started_at, self._stopped_at)
self._started_at, self._stopped_at)))
else: else:
elapsed = max(0.0, float(timeutils.delta_seconds( elapsed = self._delta_seconds(self._started_at, self._now())
self._started_at, timeutils.utcnow())))
if maximum is not None and elapsed > maximum: if maximum is not None and elapsed > maximum:
elapsed = max(0.0, maximum) elapsed = max(0.0, maximum)
return elapsed return elapsed
@@ -201,6 +282,6 @@ class StopWatch(object):
if self._state != self._STARTED: if self._state != self._STARTED:
raise RuntimeError("Can not stop a stopwatch that has not been" raise RuntimeError("Can not stop a stopwatch that has not been"
" started") " started")
self._stopped_at = timeutils.utcnow() self._stopped_at = self._now()
self._state = self._STOPPED self._state = self._STOPPED
return self return self

View File

@@ -23,6 +23,7 @@ import os
import re import re
import sys import sys
import threading import threading
import time
import types import types
from oslo_serialization import jsonutils from oslo_serialization import jsonutils
@@ -44,6 +45,34 @@ NUMERIC_TYPES = six.integer_types + (float,)
# see RFC 3986 section 3.1 # see RFC 3986 section 3.1
_SCHEME_REGEX = re.compile(r"^([A-Za-z][A-Za-z0-9+.-]*):") _SCHEME_REGEX = re.compile(r"^([A-Za-z][A-Za-z0-9+.-]*):")
_MONOTONIC_LOCATIONS = tuple([
# The built-in/expected location in python3.3+
'time.monotonic',
# NOTE(harlowja): Try to use the pypi module that provides this
# functionality for older versions of python less than 3.3 so that
# they to can benefit from better timing...
#
# See: http://pypi.python.org/pypi/monotonic
'monotonic.monotonic',
])
def find_monotonic(allow_time_time=False):
"""Tries to find a monotonic time providing function (and returns it)."""
for import_str in _MONOTONIC_LOCATIONS:
mod_str, _sep, attr_str = import_str.rpartition('.')
mod = importutils.try_import(mod_str)
if mod is None:
continue
func = getattr(mod, attr_str, None)
if func is not None:
return func
# Finally give up and use time.time (which isn't monotonic)...
if allow_time_time:
return time.time
else:
return None
def merge_uri(uri, conf): def merge_uri(uri, conf):
"""Merges a parsed uri into the given configuration dictionary. """Merges a parsed uri into the given configuration dictionary.