Merge "Remove TRex installer from ansible directory"
[yardstick.git] / yardstick / benchmark / runners / arithmetic.py
1 # Copyright 2014: Mirantis Inc.
2 # All Rights Reserved.
3 #
4 #    Licensed under the Apache License, Version 2.0 (the "License"); you may
5 #    not use this file except in compliance with the License. You may obtain
6 #    a copy of the License at
7 #
8 #         http://www.apache.org/licenses/LICENSE-2.0
9 #
10 #    Unless required by applicable law or agreed to in writing, software
11 #    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12 #    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13 #    License for the specific language governing permissions and limitations
14 #    under the License.
15
16 # yardstick comment: this is a modified copy of
17 # rally/rally/benchmark/runners/constant.py
18
19 """A runner that every run arithmetically steps specified input value(s) to
20 the scenario. This just means step value(s) is added to the previous value(s).
21 It is possible to combine several named input values and run with those either
22 as nested for loops or combine each i:th index of each "input value list"
23 until the end of the shortest list is reached (optimally all lists should be
24 defined with the same number of values when using such iter_type).
25 """
26
27 from __future__ import absolute_import
28
29 import itertools
30 import logging
31 import multiprocessing
32 import os
33 import time
34 import traceback
35
36 import six
37 from six.moves import range
38
39 from yardstick.benchmark.runners import base
40 from yardstick.common import exceptions as y_exc
41
42 LOG = logging.getLogger(__name__)
43
44
45 def _worker_process(queue, cls, method_name, scenario_cfg,
46                     context_cfg, aborted, output_queue):
47
48     sequence = 1
49
50     runner_cfg = scenario_cfg['runner']
51
52     interval = runner_cfg.get("interval", 1)
53     if 'options' in scenario_cfg:
54         options = scenario_cfg['options']
55     else:  # options must be instatiated if not present in yaml
56         options = {}
57         scenario_cfg['options'] = options
58
59     runner_cfg['runner_id'] = os.getpid()
60
61     LOG.info("worker START, class %s", cls)
62
63     benchmark = cls(scenario_cfg, context_cfg)
64     benchmark.setup()
65     method = getattr(benchmark, method_name)
66
67     sla_action = None
68     if "sla" in scenario_cfg:
69         sla_action = scenario_cfg["sla"].get("action", "assert")
70
71     # To both be able to include the stop value and handle backwards stepping
72     def margin(start, stop):
73         return -1 if start > stop else 1
74
75     param_iters = \
76         [range(d['start'], d['stop'] + margin(d['start'], d['stop']),
77                d['step']) for d in runner_cfg['iterators']]
78     param_names = [d['name'] for d in runner_cfg['iterators']]
79
80     iter_type = runner_cfg.get("iter_type", "nested_for_loops")
81
82     if iter_type == 'nested_for_loops':
83         # Create a complete combination set of all parameter lists
84         loop_iter = itertools.product(*param_iters)
85     elif iter_type == 'tuple_loops':
86         # Combine each i;th index of respective parameter list
87         loop_iter = six.moves.zip(*param_iters)
88     else:
89         LOG.warning("iter_type unrecognized: %s", iter_type)
90         raise TypeError("iter_type unrecognized: %s" % iter_type)
91
92     # Populate options and run the requested method for each value combination
93     for comb_values in loop_iter:
94
95         if aborted.is_set():
96             break
97
98         LOG.debug("runner=%(runner)s seq=%(sequence)s START",
99                   {"runner": runner_cfg["runner_id"], "sequence": sequence})
100
101         for i, value in enumerate(comb_values):
102             options[param_names[i]] = value
103
104         data = {}
105         errors = ""
106
107         try:
108             result = method(data)
109         except y_exc.SLAValidationError as error:
110             # SLA validation failed in scenario, determine what to do now
111             if sla_action == "assert":
112                 raise
113             elif sla_action == "monitor":
114                 LOG.warning("SLA validation failed: %s", error.args)
115                 errors = error.args
116         except Exception as e:  # pylint: disable=broad-except
117             errors = traceback.format_exc()
118             LOG.exception(e)
119         else:
120             if result:
121                 output_queue.put(result)
122
123         time.sleep(interval)
124
125         benchmark_output = {
126             'timestamp': time.time(),
127             'sequence': sequence,
128             'data': data,
129             'errors': errors
130         }
131
132         queue.put(benchmark_output)
133
134         LOG.debug("runner=%(runner)s seq=%(sequence)s END",
135                   {"runner": runner_cfg["runner_id"], "sequence": sequence})
136
137         sequence += 1
138
139         if errors and sla_action is None:
140             break
141
142     try:
143         benchmark.teardown()
144     except Exception:
145         # catch any exception in teardown and convert to simple exception
146         # never pass exceptions back to multiprocessing, because some exceptions can
147         # be unpicklable
148         # https://bugs.python.org/issue9400
149         LOG.exception("")
150         raise SystemExit(1)
151     LOG.info("worker END")
152     LOG.debug("queue.qsize() = %s", queue.qsize())
153     LOG.debug("output_queue.qsize() = %s", output_queue.qsize())
154
155
156 class ArithmeticRunner(base.Runner):
157     """Run a scenario arithmetically stepping input value(s)
158
159   Parameters
160     interval - time to wait between each scenario invocation
161         type:    int
162         unit:    seconds
163         default: 1 sec
164     iter_type: - Iteration type of input parameter(s): nested_for_loops
165                  or tuple_loops
166         type:    string
167         unit:    na
168         default: nested_for_loops
169     -
170       name - name of scenario option that will be increased for each invocation
171           type:    string
172           unit:    na
173           default: na
174       start - value to use in first invocation of scenario
175           type:    int
176           unit:    na
177           default: none
178       stop - value indicating end of invocation. Can be set to same
179              value as start for one single value.
180           type:    int
181           unit:    na
182           default: none
183       step - value added to start value in next invocation of scenario.
184              Must not be set to zero. Can be set negative if start > stop
185           type:    int
186           unit:    na
187           default: none
188     -
189       name - and so on......
190     """
191
192     __execution_type__ = 'Arithmetic'
193
194     def _run_benchmark(self, cls, method, scenario_cfg, context_cfg):
195         name = "{}-{}-{}".format(self.__execution_type__, scenario_cfg.get("type"), os.getpid())
196         self.process = multiprocessing.Process(
197             name=name,
198             target=_worker_process,
199             args=(self.result_queue, cls, method, scenario_cfg,
200                   context_cfg, self.aborted, self.output_queue))
201         self.process.start()