Merge "Move & refactor atomic actions"
This commit is contained in:
commit
c539eeca9b
@ -47,8 +47,7 @@ def tempest_log_wrapper(func):
|
|||||||
total, tests = scenario_obj.context["verifier"].parse_results(
|
total, tests = scenario_obj.context["verifier"].parse_results(
|
||||||
kwargs["log_file"])
|
kwargs["log_file"])
|
||||||
if total and tests:
|
if total and tests:
|
||||||
scenario_obj._add_atomic_actions("test_execution",
|
scenario_obj._atomic_actions["test_execution"] = total.get("time")
|
||||||
total.get("time"))
|
|
||||||
if total.get("errors") or total.get("failures"):
|
if total.get("errors") or total.get("failures"):
|
||||||
raise TempestBenchmarkFailure([
|
raise TempestBenchmarkFailure([
|
||||||
test for test in six.itervalues(tests)
|
test for test in six.itervalues(tests)
|
||||||
|
@ -13,11 +13,75 @@
|
|||||||
# 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 functools
|
||||||
|
|
||||||
from rally.task import scenario
|
from rally.common import costilius
|
||||||
|
from rally.common import utils
|
||||||
|
|
||||||
# TODO(boris-42): In future we will move code from scenario to atomic action
|
|
||||||
# to simplify migration to new code we will make just links for
|
class ActionTimerMixin(object):
|
||||||
# now
|
|
||||||
ActionTimer = scenario.AtomicAction
|
def __init__(self):
|
||||||
action_timer = scenario.atomic_action_timer
|
self._atomic_actions = costilius.OrderedDict()
|
||||||
|
|
||||||
|
def atomic_actions(self):
|
||||||
|
"""Returns the content of each atomic action."""
|
||||||
|
return self._atomic_actions
|
||||||
|
|
||||||
|
|
||||||
|
class ActionTimer(utils.Timer):
|
||||||
|
"""A class to measure the duration of atomic operations
|
||||||
|
|
||||||
|
This would simplify the way measure atomic operation duration
|
||||||
|
in certain cases. For example if we want to get the duration
|
||||||
|
for each operation which runs in an iteration
|
||||||
|
for i in range(repetitions):
|
||||||
|
with atomic.ActionTimer(instance_of_action_timer, "name_of_action"):
|
||||||
|
self.clients(<client>).<operation>
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, instance, name):
|
||||||
|
"""Create a new instance of the AtomicAction.
|
||||||
|
|
||||||
|
:param instance: instance of subclass of ActionTimerMixin
|
||||||
|
:param name: name of the ActionBuilder
|
||||||
|
"""
|
||||||
|
super(ActionTimer, self).__init__()
|
||||||
|
self.instance = instance
|
||||||
|
self.name = self._get_atomic_action_name(name)
|
||||||
|
self.instance._atomic_actions[self.name] = None
|
||||||
|
|
||||||
|
def _get_atomic_action_name(self, name):
|
||||||
|
# TODO(boris-42): It was quite bad idea to store atomic actions
|
||||||
|
# inside {}. We should refactor this in 0.2.0 release
|
||||||
|
# and store them inside array, that will allow us to
|
||||||
|
# store atomic actions with the same name
|
||||||
|
if name not in self.instance._atomic_actions:
|
||||||
|
return name
|
||||||
|
|
||||||
|
name_template = name + " (%i)"
|
||||||
|
i = 2
|
||||||
|
while name_template % i in self.instance._atomic_actions:
|
||||||
|
i += 1
|
||||||
|
return name_template % i
|
||||||
|
|
||||||
|
def __exit__(self, type_, value, tb):
|
||||||
|
super(ActionTimer, self).__exit__(type_, value, tb)
|
||||||
|
if type_ is None:
|
||||||
|
self.instance._atomic_actions[self.name] = self.duration()
|
||||||
|
|
||||||
|
|
||||||
|
def action_timer(name):
|
||||||
|
"""Provide measure of execution time.
|
||||||
|
|
||||||
|
Decorates methods of the Scenario class.
|
||||||
|
This provides duration in seconds of each atomic action.
|
||||||
|
"""
|
||||||
|
def wrap(func):
|
||||||
|
@functools.wraps(func)
|
||||||
|
def func_atomic_actions(self, *args, **kwargs):
|
||||||
|
with ActionTimer(self, name):
|
||||||
|
f = func(self, *args, **kwargs)
|
||||||
|
return f
|
||||||
|
return func_atomic_actions
|
||||||
|
return wrap
|
||||||
|
@ -13,18 +13,18 @@
|
|||||||
# 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 functools
|
|
||||||
import random
|
import random
|
||||||
import time
|
import time
|
||||||
|
|
||||||
import six
|
import six
|
||||||
|
|
||||||
from rally.common import costilius
|
|
||||||
from rally.common import log as logging
|
from rally.common import log as logging
|
||||||
from rally.common.plugin import plugin
|
from rally.common.plugin import plugin
|
||||||
from rally.common import utils
|
from rally.common import utils
|
||||||
from rally import consts
|
from rally import consts
|
||||||
from rally import exceptions
|
from rally import exceptions
|
||||||
|
from rally.task import atomic
|
||||||
from rally.task import functional
|
from rally.task import functional
|
||||||
|
|
||||||
|
|
||||||
@ -91,7 +91,9 @@ class ConfigurePluginMeta(type):
|
|||||||
|
|
||||||
|
|
||||||
@six.add_metaclass(ConfigurePluginMeta)
|
@six.add_metaclass(ConfigurePluginMeta)
|
||||||
class Scenario(plugin.Plugin, functional.FunctionalMixin):
|
class Scenario(plugin.Plugin,
|
||||||
|
atomic.ActionTimerMixin,
|
||||||
|
functional.FunctionalMixin):
|
||||||
"""This is base class for any benchmark scenario.
|
"""This is base class for any benchmark scenario.
|
||||||
|
|
||||||
You should create subclass of this class. And your test scenarios will
|
You should create subclass of this class. And your test scenarios will
|
||||||
@ -101,9 +103,9 @@ class Scenario(plugin.Plugin, functional.FunctionalMixin):
|
|||||||
RESOURCE_NAME_LENGTH = 10
|
RESOURCE_NAME_LENGTH = 10
|
||||||
|
|
||||||
def __init__(self, context=None):
|
def __init__(self, context=None):
|
||||||
|
super(Scenario, self).__init__()
|
||||||
self.context = context
|
self.context = context
|
||||||
self._idle_duration = 0
|
self._idle_duration = 0
|
||||||
self._atomic_actions = costilius.OrderedDict()
|
|
||||||
|
|
||||||
# TODO(amaretskiy): consider about prefix part of benchmark uuid
|
# TODO(amaretskiy): consider about prefix part of benchmark uuid
|
||||||
@classmethod
|
@classmethod
|
||||||
@ -167,76 +169,3 @@ class Scenario(plugin.Plugin, functional.FunctionalMixin):
|
|||||||
def idle_duration(self):
|
def idle_duration(self):
|
||||||
"""Returns duration of all sleep_between."""
|
"""Returns duration of all sleep_between."""
|
||||||
return self._idle_duration
|
return self._idle_duration
|
||||||
|
|
||||||
def _register_atomic_action(self, name):
|
|
||||||
"""Registers an atomic action by its name."""
|
|
||||||
self._atomic_actions[name] = None
|
|
||||||
|
|
||||||
def _atomic_action_registered(self, name):
|
|
||||||
"""Checks whether an atomic action has been already registered."""
|
|
||||||
return name in self._atomic_actions
|
|
||||||
|
|
||||||
def _add_atomic_actions(self, name, duration):
|
|
||||||
"""Adds the duration of an atomic action by its name."""
|
|
||||||
self._atomic_actions[name] = duration
|
|
||||||
|
|
||||||
def atomic_actions(self):
|
|
||||||
"""Returns the content of each atomic action."""
|
|
||||||
return self._atomic_actions
|
|
||||||
|
|
||||||
|
|
||||||
def atomic_action_timer(name):
|
|
||||||
"""Provide measure of execution time.
|
|
||||||
|
|
||||||
Decorates methods of the Scenario class.
|
|
||||||
This provides duration in seconds of each atomic action.
|
|
||||||
"""
|
|
||||||
def wrap(func):
|
|
||||||
@functools.wraps(func)
|
|
||||||
def func_atomic_actions(self, *args, **kwargs):
|
|
||||||
with AtomicAction(self, name):
|
|
||||||
f = func(self, *args, **kwargs)
|
|
||||||
return f
|
|
||||||
return func_atomic_actions
|
|
||||||
return wrap
|
|
||||||
|
|
||||||
|
|
||||||
class AtomicAction(utils.Timer):
|
|
||||||
"""A class to measure the duration of atomic operations
|
|
||||||
|
|
||||||
This would simplify the way measure atomic operation duration
|
|
||||||
in certain cases. For example if we want to get the duration
|
|
||||||
for each operation which runs in an iteration
|
|
||||||
for i in range(repetitions):
|
|
||||||
with scenario_utils.AtomicAction(instance_of_base_scenario_subclass,
|
|
||||||
"name_of_action"):
|
|
||||||
self.clients(<client>).<operation>
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, scenario_instance, name):
|
|
||||||
"""Create a new instance of the AtomicAction.
|
|
||||||
|
|
||||||
:param scenario_instance: instance of subclass of base scenario
|
|
||||||
:param name: name of the ActionBuilder
|
|
||||||
"""
|
|
||||||
super(AtomicAction, self).__init__()
|
|
||||||
self.scenario_instance = scenario_instance
|
|
||||||
self.name = self._get_atomic_action_name(name)
|
|
||||||
self.scenario_instance._register_atomic_action(self.name)
|
|
||||||
|
|
||||||
def _get_atomic_action_name(self, name):
|
|
||||||
if not self.scenario_instance._atomic_action_registered(name):
|
|
||||||
return name
|
|
||||||
else:
|
|
||||||
name_template = name + " (%i)"
|
|
||||||
atomic_action_iteration = 2
|
|
||||||
while self.scenario_instance._atomic_action_registered(
|
|
||||||
name_template % atomic_action_iteration):
|
|
||||||
atomic_action_iteration += 1
|
|
||||||
return name_template % atomic_action_iteration
|
|
||||||
|
|
||||||
def __exit__(self, type_, value, tb):
|
|
||||||
super(AtomicAction, self).__exit__(type_, value, tb)
|
|
||||||
if type_ is None:
|
|
||||||
self.scenario_instance._add_atomic_actions(self.name,
|
|
||||||
self.duration())
|
|
||||||
|
57
tests/unit/task/test_atomic.py
Normal file
57
tests/unit/task/test_atomic.py
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
# Copyright 2015: Mirantis Inc.
|
||||||
|
# All Rights Reserved.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
import mock
|
||||||
|
|
||||||
|
from rally.common import costilius
|
||||||
|
from rally.task import atomic
|
||||||
|
from tests.unit import test
|
||||||
|
|
||||||
|
|
||||||
|
class ActionTimerMixinTestCase(test.TestCase):
|
||||||
|
|
||||||
|
def test_atomic_actions(self):
|
||||||
|
inst = atomic.ActionTimerMixin()
|
||||||
|
self.assertEqual(inst._atomic_actions, inst.atomic_actions())
|
||||||
|
|
||||||
|
|
||||||
|
class AtomicActionTestCase(test.TestCase):
|
||||||
|
|
||||||
|
@mock.patch("time.time", side_effect=[1, 3, 6, 10, 15, 21])
|
||||||
|
def test_action_timer_context(self, mock_time):
|
||||||
|
inst = atomic.ActionTimerMixin()
|
||||||
|
|
||||||
|
with atomic.ActionTimer(inst, "test"):
|
||||||
|
with atomic.ActionTimer(inst, "test"):
|
||||||
|
with atomic.ActionTimer(inst, "some"):
|
||||||
|
pass
|
||||||
|
|
||||||
|
expected = [("test", 20), ("test (2)", 12), ("some", 4)]
|
||||||
|
self.assertEqual(costilius.OrderedDict(expected),
|
||||||
|
inst.atomic_actions())
|
||||||
|
|
||||||
|
@mock.patch("time.time", side_effect=[1, 3])
|
||||||
|
def test_action_timer_decorator(self, mock_time):
|
||||||
|
|
||||||
|
class Some(atomic.ActionTimerMixin):
|
||||||
|
|
||||||
|
@atomic.action_timer("some")
|
||||||
|
def some_func(self, a, b):
|
||||||
|
return a + b
|
||||||
|
|
||||||
|
inst = Some()
|
||||||
|
self.assertEqual(5, inst.some_func(2, 3))
|
||||||
|
self.assertEqual(costilius.OrderedDict({"some": 2}),
|
||||||
|
inst.atomic_actions())
|
@ -271,22 +271,3 @@ class ScenarioTestCase(test.TestCase):
|
|||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
len_by_prefix(result, scenario.Scenario.RESOURCE_NAME_PREFIX),
|
len_by_prefix(result, scenario.Scenario.RESOURCE_NAME_PREFIX),
|
||||||
range_num)
|
range_num)
|
||||||
|
|
||||||
|
|
||||||
class AtomicActionTestCase(test.TestCase):
|
|
||||||
def test__init__(self):
|
|
||||||
fake_scenario_instance = fakes.FakeScenario()
|
|
||||||
c = scenario.AtomicAction(fake_scenario_instance, "asdf")
|
|
||||||
self.assertEqual(c.scenario_instance, fake_scenario_instance)
|
|
||||||
self.assertEqual(c.name, "asdf")
|
|
||||||
|
|
||||||
@mock.patch("tests.unit.fakes.FakeScenario._add_atomic_actions")
|
|
||||||
@mock.patch("rally.common.utils.time")
|
|
||||||
def test__exit__(self, mock_time, mock_fake_scenario__add_atomic_actions):
|
|
||||||
fake_scenario_instance = fakes.FakeScenario()
|
|
||||||
self.start = mock_time.time()
|
|
||||||
with scenario.AtomicAction(fake_scenario_instance, "asdf"):
|
|
||||||
pass
|
|
||||||
duration = mock_time.time() - self.start
|
|
||||||
mock_fake_scenario__add_atomic_actions.assert_called_once_with(
|
|
||||||
"asdf", duration)
|
|
||||||
|
Loading…
Reference in New Issue
Block a user