diff options
author | Dan Klein <danklein10@gmail.com> | 2016-01-04 23:31:31 +0200 |
---|---|---|
committer | Dan Klein <danklein10@gmail.com> | 2016-01-04 23:31:31 +0200 |
commit | 629b54c4c9df9c718d818a004ecf15c2cf6c770a (patch) | |
tree | 7dfc3c64c7561032d690ce6188130e80d344054e /scripts/automation/trex_control_plane | |
parent | 3757099103ed1bf56f85ccf5bb861a331287cbbb (diff) | |
parent | 857bdcf05a920b99e1cf180c700176b04801da00 (diff) |
Merge branch 'master' into dan_stateless
Diffstat (limited to 'scripts/automation/trex_control_plane')
9 files changed, 468 insertions, 29 deletions
diff --git a/scripts/automation/trex_control_plane/client/trex_client.py b/scripts/automation/trex_control_plane/client/trex_client.py index 5709b7a5..1d94dc06 100755 --- a/scripts/automation/trex_control_plane/client/trex_client.py +++ b/scripts/automation/trex_control_plane/client/trex_client.py @@ -294,6 +294,34 @@ class CTRexClient(object): finally: self.prompt_verbose_data() + def is_idle (self): + """ + Poll for TRex running status, check if TRex is in Idle state. + + :parameters: + None + + :return: + + **True** if TRex is idle. + + **False** if TRex is starting or running. + + :raises: + + :exc:`trex_exceptions.TRexIncompleteRunError`, in case one of failed TRex run (unexpected termination). + + :exc:`TypeError`, in case JSON stream decoding error. + + ProtocolError, in case of error in JSON-RPC protocol. + + """ + try: + if self.get_running_status()['state'] == TRexStatus.Idle: + return True + return False + except TRexException: + raise + except ProtocolError as err: + raise + finally: + self.prompt_verbose_data() + def get_trex_files_path (self): """ Fetches the local path in which files are stored when pushed to TRex server from client. @@ -455,6 +483,41 @@ class CTRexClient(object): results = self.get_result_obj() return results + def sample_x_seconds (self, sample_time, time_between_samples = 5): + """ + Automatically sets ongoing sampling of TRex data for sample_time seconds, with sampling rate described by time_between_samples. + Does not stop the TRex afterwards! + + .. tip:: Useful for changing the device (Router, ASA etc.) configuration after given time. + + :parameters: + sample_time : int + sample the TRex this number of seconds + + time_between_samples : int + determines the time between each sample of the server + + default value : **5** + + :return: + the first result object (see :class:`CTRexResult` for further details) of the TRex run after given sample_time. + + :raises: + + :exc:`UserWarning`, in case the TRex run ended before sample_time duration + + :exc:`trex_exceptions.TRexIncompleteRunError`, in case one of failed TRex run (unexpected termination). + + :exc:`TypeError`, in case JSON stream decoding error. + + ProtocolError, in case of error in JSON-RPC protocol. + + """ + # make sure TRex is running. raise exceptions here if any + self.wait_until_kickoff_finish() + elapsed_time = 0 + while self.is_running(): + if elapsed_time >= sample_time: + return self.get_result_obj() + time.sleep(time_between_samples) + elapsed_time += time_between_samples + raise UserWarning("TRex has stopped at %s seconds (before expected %s seconds)\nTry increasing test duration or decreasing sample_time" % (elapsed_time, sample_time)) def get_result_obj (self, copy_obj = True): """ @@ -1041,11 +1104,11 @@ class CTRexResult(object): # handle latency data if self.latency_checked: latency_pre = "trex-latency" - self._max_latency = self.get_last_value("{latency}.data".format(latency = latency_pre), ".*max-")#None # TBC + self._max_latency = self.get_last_value("{latency}.data".format(latency = latency_pre), "max-")#None # TBC # support old typo if self._max_latency is None: latency_pre = "trex-latecny" - self._max_latency = self.get_last_value("{latency}.data".format(latency = latency_pre), ".*max-") + self._max_latency = self.get_last_value("{latency}.data".format(latency = latency_pre), "max-") self._avg_latency = self.get_last_value("{latency}.data".format(latency = latency_pre), "avg-")#None # TBC self._avg_latency = CTRexResult.__avg_all_and_rename_keys(self._avg_latency) diff --git a/scripts/automation/trex_control_plane/client/trex_stateless_sim.py b/scripts/automation/trex_control_plane/client/trex_stateless_sim.py new file mode 100644 index 00000000..7655b27c --- /dev/null +++ b/scripts/automation/trex_control_plane/client/trex_stateless_sim.py @@ -0,0 +1,202 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +""" +Itay Marom +Cisco Systems, Inc. + +Copyright (c) 2015-2015 Cisco Systems, Inc. +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. +""" + +try: + # support import for Python 2 + import outer_packages +except ImportError: + # support import for Python 3 + import client.outer_packages + +from client_utils.jsonrpc_client import JsonRpcClient, BatchMessage +from client_utils.packet_builder import CTRexPktBuilder +import json + +from common.trex_streams import * + +import argparse +import tempfile +import subprocess +import os + + + +class SimRun(object): + def __init__ (self, yaml_file, dp_core_count, core_index, packet_limit, output_filename, is_valgrind, is_gdb): + + self.yaml_file = yaml_file + self.output_filename = output_filename + self.dp_core_count = dp_core_count + self.core_index = core_index + self.packet_limit = packet_limit + self.is_valgrind = is_valgrind + self.is_gdb = is_gdb + + # dummies + self.handler = 0 + self.port_id = 0 + self.mul = {"op": "abs", + "type": "raw", + "value": 1} + + self.duration = -1 + + def load_yaml_file (self): + streams_db = CStreamsDB() + stream_list = streams_db.load_yaml_file(self.yaml_file) + + streams_json = [] + for stream in stream_list.compiled: + stream_json = {"id":1, + "jsonrpc": "2.0", + "method": "add_stream", + "params": {"handler": self.handler, + "port_id": self.port_id, + "stream_id": stream.stream_id, + "stream": stream.stream} + } + + streams_json.append(stream_json) + + return streams_json + + + def generate_start_cmd (self): + return {"id":1, + "jsonrpc": "2.0", + "method": "start_traffic", + "params": {"handler": self.handler, + "port_id": self.port_id, + "mul": self.mul, + "duration": self.duration} + } + + + def run (self): + + # load the streams + cmds_json = (self.load_yaml_file()) + cmds_json.append(self.generate_start_cmd()) + + f = tempfile.NamedTemporaryFile(delete = False) + f.write(json.dumps(cmds_json)) + f.close() + + try: + cmd = ['bp-sim-64-debug', '--sl', '--cores', str(self.dp_core_count), '--core_index', str(self.core_index), '-f', f.name, '-o', self.output_filename] + if self.is_valgrind: + cmd = ['valgrind', '--leak-check=full'] + cmd + elif self.is_gdb: + cmd = ['gdb', '--args'] + cmd + + subprocess.call(cmd) + + finally: + os.unlink(f.name) + + +def is_valid_file(filename): + if not os.path.isfile(filename): + raise argparse.ArgumentTypeError("The file '%s' does not exist" % filename) + + return filename + + +def unsigned_int (x): + x = int(x) + if x <= 0: + raise argparse.ArgumentTypeError("argument must be >= 1") + + return x + +def setParserOptions(): + parser = argparse.ArgumentParser(prog="stl_sim.py") + + parser.add_argument("input_file", + help = "input file in YAML or Python format", + type = is_valid_file) + + parser.add_argument("output_file", + help = "output file in ERF format") + + + parser.add_argument("-c", "--cores", + help = "DP core count [default is 1]", + default = 1, + type = int, + choices = xrange(1, 9)) + + parser.add_argument("-n", "--core_index", + help = "DP core index to examine [default is 0]", + default = 0, + type = int) + + parser.add_argument("-j", "--join", + help = "run and join output from 0..core_count [default is False]", + default = False, + type = bool) + + parser.add_argument("-l", "--limit", + help = "limit test total packet count [default is 5000]", + default = 5000, + type = unsigned_int) + + + group = parser.add_mutually_exclusive_group() + + group.add_argument("-x", "--valgrind", + help = "run under valgrind [default is False]", + action = "store_true", + default = False) + + group.add_argument("-g", "--gdb", + help = "run under GDB [default is False]", + action = "store_true", + default = False) + + return parser + + +def validate_args (parser, options): + if options.core_index < 0 or options.core_index >= options.cores: + parser.error("DP core index valid range is 0 to {0}".format(options.cores - 1)) + + + +def main (): + parser = setParserOptions() + options = parser.parse_args() + + validate_args(parser, options) + + r = SimRun(options.input_file, + options.cores, + options.core_index, + options.limit, + options.output_file, + options.valgrind, + options.gdb) + + r.run() + + +if __name__ == '__main__': + main() + + diff --git a/scripts/automation/trex_control_plane/client_utils/external_packages.py b/scripts/automation/trex_control_plane/client_utils/external_packages.py index 3c6eb449..3982a1b2 100755 --- a/scripts/automation/trex_control_plane/client_utils/external_packages.py +++ b/scripts/automation/trex_control_plane/client_utils/external_packages.py @@ -7,15 +7,16 @@ CURRENT_PATH = os.path.dirname(os.path.realpath(__file__)) ROOT_PATH = os.path.abspath(os.path.join(CURRENT_PATH, os.pardir)) # path to trex_control_plane directory PATH_TO_PYTHON_LIB = os.path.abspath(os.path.join(ROOT_PATH, os.pardir, os.pardir, 'external_libs')) -CLIENT_UTILS_MODULES = ['zmq', - 'dpkt-1.8.6', +CLIENT_UTILS_MODULES = ['dpkt-1.8.6', 'PyYAML-3.01/lib', 'texttable-0.8.4' ] def import_client_utils_modules(): + # must be in a higher priority sys.path.insert(0, PATH_TO_PYTHON_LIB) + sys.path.append(ROOT_PATH) import_module_list(CLIENT_UTILS_MODULES) @@ -27,5 +28,41 @@ def import_module_list(modules_list): fix_path = os.path.normcase(full_path) sys.path.insert(1, full_path) + + import_platform_dirs() + + + +def import_platform_dirs (): + # handle platform dirs + + # try fedora 18 first and then cel5.9 + # we are using the ZMQ module to determine the right platform + + full_path = os.path.join(PATH_TO_PYTHON_LIB, 'platform/fedora18') + fix_path = os.path.normcase(full_path) + sys.path.insert(0, full_path) + try: + # try to import and delete it from the namespace + import zmq + del zmq + return + except: + pass + + full_path = os.path.join(PATH_TO_PYTHON_LIB, 'platform/cel59') + fix_path = os.path.normcase(full_path) + sys.path.insert(0, full_path) + try: + # try to import and delete it from the namespace + import zmq + del zmq + return + + except: + raise Exception("unable to determine platform type for ZMQ import") + + + import_client_utils_modules() diff --git a/scripts/automation/trex_control_plane/client_utils/parsing_opts.py b/scripts/automation/trex_control_plane/client_utils/parsing_opts.py index 6f9b4c6d..5cb06604 100755 --- a/scripts/automation/trex_control_plane/client_utils/parsing_opts.py +++ b/scripts/automation/trex_control_plane/client_utils/parsing_opts.py @@ -70,7 +70,10 @@ def match_multiplier_common(val, strict_abs = True): op = None else: match = re.match("^(\d+(\.\d+)?)(bps|kbps|mbps|gbps|pps|kpps|mpps|%?)([\+\-])?$", val) - op = match.group(4) + if match: + op = match.group(4) + else: + op = None result = {} diff --git a/scripts/automation/trex_control_plane/client_utils/yaml_utils.py b/scripts/automation/trex_control_plane/client_utils/yaml_utils.py index 60630a04..825d6fc9 100755 --- a/scripts/automation/trex_control_plane/client_utils/yaml_utils.py +++ b/scripts/automation/trex_control_plane/client_utils/yaml_utils.py @@ -14,7 +14,8 @@ 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 traceback +import sys import external_packages import yaml diff --git a/scripts/automation/trex_control_plane/common/rpc_defaults.yaml b/scripts/automation/trex_control_plane/common/rpc_defaults.yaml index 32631609..9325a0e4 100755 --- a/scripts/automation/trex_control_plane/common/rpc_defaults.yaml +++ b/scripts/automation/trex_control_plane/common/rpc_defaults.yaml @@ -50,9 +50,7 @@ stream: mode:
type: object
vm:
- type: array
- has_default: YES
- default: [] # no ranging instructions
+ type: object
rx_stats:
type: object
@@ -112,4 +110,15 @@ rx_stats: latency_enabled:
type: boolean
has_default: YES
- default: False
\ No newline at end of file + default: False
+
+vm:
+ instructions:
+ type: array
+ has_default: YES
+ default: []
+ split_by_var:
+ type: string
+ has_default: YES
+ default: ""
+
diff --git a/scripts/automation/trex_control_plane/common/text_opts.py b/scripts/automation/trex_control_plane/common/text_opts.py index 5a86149c..29fbd69b 100755 --- a/scripts/automation/trex_control_plane/common/text_opts.py +++ b/scripts/automation/trex_control_plane/common/text_opts.py @@ -94,9 +94,16 @@ def underline(text): def text_attribute(text, attribute): - return "{start}{txt}{stop}".format(start=TEXT_CODES[attribute]['start'], - txt=text, - stop=TEXT_CODES[attribute]['end']) + if isinstance(text, str): + return "{start}{txt}{stop}".format(start=TEXT_CODES[attribute]['start'], + txt=text, + stop=TEXT_CODES[attribute]['end']) + elif isinstance(text, unicode): + return u"{start}{txt}{stop}".format(start=TEXT_CODES[attribute]['start'], + txt=text, + stop=TEXT_CODES[attribute]['end']) + else: + raise Exception("not a string") FUNC_DICT = {'blue': blue, @@ -117,6 +124,15 @@ def format_text(text, *args): return_string = func(return_string) return return_string +def format_threshold (value, red_zone, green_zone): + if value >= red_zone[0] and value <= red_zone[1]: + return format_text("{0}".format(value), 'red') + + if value >= green_zone[0] and value <= green_zone[1]: + return format_text("{0}".format(value), 'green') + + return "{0}".format(value) + # pretty print for JSON def pretty_json (json_str, use_colors = True): pretty_str = json.dumps(json.loads(json_str), indent = 4, separators=(',', ': '), sort_keys = True) diff --git a/scripts/automation/trex_control_plane/common/trex_stats.py b/scripts/automation/trex_control_plane/common/trex_stats.py index d77d3558..4c6173c4 100755 --- a/scripts/automation/trex_control_plane/common/trex_stats.py +++ b/scripts/automation/trex_control_plane/common/trex_stats.py @@ -1,12 +1,13 @@ #!/router/bin/python -from collections import namedtuple, OrderedDict +from collections import namedtuple, OrderedDict, deque from client_utils import text_tables -from common.text_opts import format_text +from common.text_opts import format_text, format_threshold from client.trex_async_client import CTRexAsyncStats import copy import datetime import time import re +import math GLOBAL_STATS = 'g' PORT_STATS = 'p' @@ -16,6 +17,44 @@ COMPACT = {GLOBAL_STATS, PORT_STATS} ExportableStats = namedtuple('ExportableStats', ['raw_data', 'text_table']) +# use to calculate diffs relative to the previous values +# for example, BW +def calculate_diff (samples): + total = 0.0 + + weight_step = 1.0 / sum(xrange(0, len(samples))) + weight = weight_step + + for i in xrange(0, len(samples) - 1): + current = samples[i] if samples[i] > 0 else 1 + next = samples[i + 1] if samples[i + 1] > 0 else 1 + + s = 100 * ((float(next) / current) - 1.0) + + # block change by 100% + total += (min(s, 100) * weight) + weight += weight_step + + return total + + +# calculate by absolute values and not relatives (useful for CPU usage in % and etc.) +def calculate_diff_raw (samples): + total = 0.0 + + weight_step = 1.0 / sum(xrange(0, len(samples))) + weight = weight_step + + for i in xrange(0, len(samples) - 1): + current = samples[i] + next = samples[i + 1] + + total += ( (next - current) * weight ) + weight += weight_step + + return total + + class CTRexInfoGenerator(object): """ @@ -202,7 +241,7 @@ class CTRexStats(object): self.reference_stats = None self.latest_stats = {} self.last_update_ts = time.time() - + self.history = deque(maxlen = 10) def __getitem__(self, item): # override this to allow quick and clean access to fields @@ -254,6 +293,7 @@ class CTRexStats(object): def update(self, snapshot): # update self.latest_stats = snapshot + self.history.append(snapshot) diff_time = time.time() - self.last_update_ts @@ -286,6 +326,54 @@ class CTRexStats(object): else: return self.format_num(self.latest_stats[field] - self.reference_stats[field], suffix) + # get trend for a field + def get_trend (self, field, use_raw = False): + if not field in self.latest_stats: + return 0 + + if len(self.history) < 5: + return 0 + + field_samples = [sample[field] for sample in self.history] + + if use_raw: + return calculate_diff_raw(field_samples) + else: + return calculate_diff(field_samples) + + + def get_trend_gui (self, field, show_value = True, use_raw = False, up_color = 'red', down_color = 'green'): + v = self.get_trend(field, use_raw) + + value = abs(v) + arrow = u'\u25b2' if v > 0 else u'\u25bc' + color = up_color if v > 0 else down_color + + # change in 1% is not meaningful + if value < 1: + return "" + + elif value > 5: + + if show_value: + return format_text(u"{0}{0}{0} {1:.2f}%".format(arrow,v), color) + else: + return format_text(u"{0}{0}{0}".format(arrow), color) + + elif value > 2: + + if show_value: + return format_text(u"{0}{0} {1:.2f}%".format(arrow,v), color) + else: + return format_text(u"{0}{0}".format(arrow), color) + + else: + if show_value: + return format_text(u"{0} {1:.2f}%".format(arrow,v), color) + else: + return format_text(u"{0}".format(arrow), color) + + class CGlobalStats(CTRexStats): @@ -300,11 +388,19 @@ class CGlobalStats(CTRexStats): port=self.connection_info.get("sync_port"))), ("version", "{ver}, UUID: {uuid}".format(ver=self.server_version.get("version", "N/A"), uuid="N/A")), - ("cpu_util", "{0}%".format(self.get("m_cpu_util"))), - ("total_tx", self.get("m_tx_bps", format=True, suffix="b/sec")), - ("total_rx", self.get("m_rx_bps", format=True, suffix="b/sec")), - ("total_pps", self.format_num(self.get("m_tx_pps") + self.get("m_rx_pps"), - suffix="pkt/sec")), + + ("cpu_util", u"{0}% {1}".format( format_threshold(self.get("m_cpu_util"), [85, 100], [0, 85]), + self.get_trend_gui("m_cpu_util", use_raw = True))), + + ("total_tx", u"{0} {1}".format( self.get("m_tx_bps", format=True, suffix="b/sec"), + self.get_trend_gui("m_tx_bps"))), + + ("total_rx", u"{0} {1}".format( self.get("m_rx_bps", format=True, suffix="b/sec"), + self.get_trend_gui("m_rx_bps"))), + + ("total_pps", u"{0} {1}".format( self.get("m_tx_pps", format=True, suffix="pkt/sec"), + self.get_trend_gui("m_tx_pps"))), + ("total_streams", sum([len(port_obj.streams) for _, port_obj in self._ports_dict.iteritems()])), ("active_ports", sum([port_obj.is_active() @@ -335,11 +431,19 @@ class CPortStats(CTRexStats): "rx-pkts": self.get_rel("ipackets", format = True, suffix = "pkts"), "---": "", - "Tx bps": self.get("m_total_tx_bps", format = True, suffix = "bps"), - "Rx bps": self.get("m_total_rx_bps", format = True, suffix = "bps"), + "Tx bps": u"{0} {1}".format(self.get_trend_gui("m_total_tx_bps", show_value = False, up_color = None, down_color = None), + self.get("m_total_tx_bps", format = True, suffix = "bps")), + + "Rx bps": u"{0} {1}".format(self.get_trend_gui("m_total_rx_bps", show_value = False, up_color = None, down_color = None), + self.get("m_total_rx_bps", format = True, suffix = "bps")), + "----": "", - "Tx pps": self.get("m_total_tx_pps", format = True, suffix = "pps"), - "Rx pps": self.get("m_total_rx_pps", format = True, suffix = "pps"), + "Tx pps": u"{0} {1}".format(self.get_trend_gui("m_total_tx_pps", show_value = False, up_color = None, down_color = None), + self.get("m_total_tx_pps", format = True, suffix = "pps")), + + "Rx pps": u"{0} {1}".format(self.get_trend_gui("m_total_rx_pps", show_value = False, up_color = None, down_color = None), + self.get("m_total_rx_pps", format = True, suffix = "pps")), + } diff --git a/scripts/automation/trex_control_plane/server/outer_packages.py b/scripts/automation/trex_control_plane/server/outer_packages.py index 2234c734..3d4f039a 100755 --- a/scripts/automation/trex_control_plane/server/outer_packages.py +++ b/scripts/automation/trex_control_plane/server/outer_packages.py @@ -3,13 +3,14 @@ import sys import os -CURRENT_PATH = os.path.dirname(os.path.realpath(__file__)) -ROOT_PATH = os.path.abspath(os.path.join(CURRENT_PATH, os.pardir)) # path to trex_control_plane directory -PATH_TO_PYTHON_LIB = os.path.abspath(os.path.join(ROOT_PATH, os.pardir, os.pardir, 'external_libs')) +CURRENT_PATH = os.path.dirname(os.path.realpath(__file__)) +ROOT_PATH = os.path.abspath(os.path.join(CURRENT_PATH, os.pardir)) # path to trex_control_plane directory +PATH_TO_PYTHON_LIB = os.path.abspath(os.path.join(ROOT_PATH, os.pardir, os.pardir, 'external_libs')) +PATH_TO_PLATFORM_LIB = os.path.abspath(os.path.join(PATH_TO_PYTHON_LIB, 'platform/fedora18')) SERVER_MODULES = ['enum34-1.0.4', - 'jsonrpclib-pelix-0.2.5', 'zmq', + 'jsonrpclib-pelix-0.2.5', 'python-daemon-2.0.5', 'lockfile-0.10.2', 'termstyle' @@ -19,6 +20,7 @@ SERVER_MODULES = ['enum34-1.0.4', def import_server_modules(): # must be in a higher priority sys.path.insert(0, PATH_TO_PYTHON_LIB) + sys.path.insert(0, PATH_TO_PLATFORM_LIB) sys.path.append(ROOT_PATH) import_module_list(SERVER_MODULES) @@ -30,4 +32,6 @@ def import_module_list(modules_list): fix_path = os.path.normcase(full_path) sys.path.insert(1, full_path) + import_server_modules() + |