From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from dpdk.org (dpdk.org [92.243.14.124]) by inbox.dpdk.org (Postfix) with ESMTP id 3B94BA0513; Thu, 16 Jan 2020 02:54:12 +0100 (CET) Received: from [92.243.14.124] (localhost [127.0.0.1]) by dpdk.org (Postfix) with ESMTP id 2FD421C197; Thu, 16 Jan 2020 02:54:12 +0100 (CET) Received: from mga11.intel.com (mga11.intel.com [192.55.52.93]) by dpdk.org (Postfix) with ESMTP id 3126B1C132 for ; Thu, 16 Jan 2020 02:54:08 +0100 (CET) X-Amp-Result: SKIPPED(no attachment in message) X-Amp-File-Uploaded: False Received: from orsmga007.jf.intel.com ([10.7.209.58]) by fmsmga102.fm.intel.com with ESMTP/TLS/DHE-RSA-AES256-GCM-SHA384; 15 Jan 2020 17:54:07 -0800 X-ExtLoop1: 1 X-IronPort-AV: E=Sophos;i="5.70,324,1574150400"; d="scan'208";a="213911293" Received: from dpdk-moyufen06.sh.intel.com ([10.67.116.222]) by orsmga007.jf.intel.com with ESMTP; 15 Jan 2020 17:54:06 -0800 From: yufengmx To: dts@dpdk.org, lei.a.yao@intel.com Cc: yufengmx Date: Thu, 16 Jan 2020 09:56:43 +0800 Message-Id: <20200116015645.21294-2-yufengx.mo@intel.com> X-Mailer: git-send-email 2.21.0 In-Reply-To: <20200116015645.21294-1-yufengx.mo@intel.com> References: <20200116015645.21294-1-yufengx.mo@intel.com> MIME-Version: 1.0 Content-Transfer-Encoding: 8bit Subject: [dts] [PATCH V1 1/3] tests/power_telemetry: upload automation script X-BeenThere: dts@dpdk.org X-Mailman-Version: 2.1.15 Precedence: list List-Id: test suite reviews and discussions List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: dts-bounces@dpdk.org Sender: "dts" The telemetry mode support for l3fwd-power is a standalone mode, in this mode l3fwd-power does simple l3fwding along with calculating empty polls, full polls, and busy percentage for each forwarding core. Signed-off-by: yufengmx --- tests/TestSuite_power_telemetry.py | 465 +++++++++++++++++++++++++++++ 1 file changed, 465 insertions(+) create mode 100644 tests/TestSuite_power_telemetry.py diff --git a/tests/TestSuite_power_telemetry.py b/tests/TestSuite_power_telemetry.py new file mode 100644 index 0000000..9c2bdbc --- /dev/null +++ b/tests/TestSuite_power_telemetry.py @@ -0,0 +1,465 @@ +# BSD LICENSE +# +# Copyright(c) 2010-2020 Intel Corporation. All rights reserved. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in +# the documentation and/or other materials provided with the +# distribution. +# * Neither the name of Intel Corporation nor the names of its +# contributors may be used to endorse or promote products derived +# from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +""" +DPDK Test suite. +l3fwd-power test suite. +""" +import os +import re +import time +import textwrap +import traceback +import json +from copy import deepcopy +from pprint import pformat + +from utils import create_mask as dts_create_mask +from settings import HEADER_SIZE +from test_case import TestCase +from pktgen import TRANSMIT_CONT +from exception import VerifyFailure + +from packet import Packet + + +class TestPowerTelemetry(TestCase): + output_path = '/tmp' + + @property + def target_dir(self): + # get absolute directory of target source code + target_dir = '/root' + self.dut.base_dir[1:] \ + if self.dut.base_dir.startswith('~') else \ + self.dut.base_dir + return target_dir + + def d_con(self, cmd): + _cmd = [cmd, '# ', 10] if isinstance(cmd, (str, unicode)) else cmd + return self.dut.send_expect(*_cmd) + + def d_a_con(self, cmd): + _cmd = [cmd, '# ', 10] if isinstance(cmd, (str, unicode)) else cmd + return self.dut.alt_session.send_expect(*_cmd) + + def get_pkt_len(self, pkt_type, frame_size=64): + headers_size = sum(map(lambda x: HEADER_SIZE[x], + ['eth', 'ip', pkt_type])) + pktlen = frame_size - headers_size + return pktlen + + def config_stream(self, dmac): + pkt_config = { + 'type': 'UDP', + 'pkt_layers': { + 'ether': {'dst': dmac}, + 'raw': {'payload': ['58'] * self.get_pkt_len('udp')}} + } + values = pkt_config + pkt_type = values.get('type') + pkt_layers = values.get('pkt_layers') + pkt = Packet(pkt_type=pkt_type) + for layer in pkt_layers.keys(): + pkt.config_layer(layer, pkt_layers[layer]) + return pkt.pktgen.pkt + + def add_stream_to_pktgen(self, option): + stream_ids = [] + topos = [[0, 1], [1, 0]] + for txport, rxport in topos: + _option = deepcopy(option) + dmac = self.dut.get_mac_address(self.dut_ports[txport]) + pkt = self.config_stream(dmac) + _option['pcap'] = pkt + stream_id = self.tester.pktgen.add_stream(txport, rxport, pkt) + self.tester.pktgen.config_stream(stream_id, _option) + stream_ids.append(stream_id) + return stream_ids + + def run_traffic(self, option): + # clear streams before add new streams + self.tester.pktgen.clear_streams() + # set stream into pktgen + stream_option = { + 'stream_config': { + 'txmode': {}, + 'transmit_mode': TRANSMIT_CONT, + 'rate': float(100), } + } + stream_ids = self.add_stream_to_pktgen(stream_option) + # run pktgen traffic + traffic_opt = option.get('traffic_opt') + result = self.tester.pktgen.measure(stream_ids, traffic_opt) + self.logger.debug(pformat(traffic_opt)) + self.logger.debug(pformat(result)) + + return result + + def preset_compilation(self): + if self.dut.skip_setup: + return + SW = "CONFIG_RTE_LIBRTE_TELEMETRY" + cmd = "sed -i -e 's/{0}=n$/{0}=y/' {1}/config/common_base".format( + SW, self.target_dir) + self.d_a_con(cmd) + # re-compile dpdk source code + self.dut.build_install_dpdk(self.target) + + def prepare_binary(self, name): + example_dir = "examples/" + name + out = self.dut.build_dpdk_apps('./' + example_dir) + self.verify("Error" not in out, "Compilation error") + self.verify("No such" not in out, "Compilation error") + binary_dir = os.path.join(self.target_dir, example_dir, 'build') + cmd = ["ls -F {0} | grep '*'".format(binary_dir), '# ', 5] + exec_file = self.d_a_con(cmd) + binary_file = os.path.join(binary_dir, exec_file[:-1]) + return binary_file + + def get_cores_mask(self, config): + ports_socket = self.dut.get_numa_id(self.dut.get_ports()[0]) + mask = dts_create_mask( + self.dut.get_core_list(config, socket=ports_socket)) + return mask + + def init_l3fwd_power(self): + self.l3fwd_power = self.prepare_binary('l3fwd-power') + + def start_l3fwd_power(self, core_config='1S/2C/1T'): + option = (' ' + '-c {core_mask} ' + '-n {mem_channel} ' + '--telemetry ' + '-- ' + '--telemetry ' + '-p 0x1 ' + '-P ' + '--config="(0,0,2)" ' + ).format(**{ + 'core_mask': self.get_cores_mask(core_config), + 'mem_channel': self.dut.get_memory_channels(), }) + prompt = 'L3FWD_POWER: entering main telemetry loop' + cmd = [' '.join([self.l3fwd_power, option]), prompt, 60] + self.d_con(cmd) + self.is_l3fwd_on = True + + def close_l3fwd_power(self): + if not self.is_l3fwd_on: + return + cmd = 'killall ' + os.path.basename(self.l3fwd_power) + self.d_a_con(cmd) + + def create_query_script(self): + ''' + usertools/dpdk-telemetry-client.py does not support save json data, + this method is used to make sure testing robust. + ''' + script_content = textwrap.dedent(""" + #! /usr/bin/env python + import argparse + import time + import json + from dpdk_telemetry_client import Client, GLOBAL_METRICS_REQ, BUFFER_SIZE + + class ClientExd(Client): + def __init__(self, json_file): + super(ClientExd, self).__init__() + self.json_file = json_file + def save_date(self, data): + with open(self.json_file, 'w') as fp: + fp.write(data) + def requestGlobalMetrics(self): + self.socket.client_fd.send(GLOBAL_METRICS_REQ) + data = self.socket.client_fd.recv(BUFFER_SIZE) + self.save_date(data) + parser = argparse.ArgumentParser(description='telemetry') + parser.add_argument('-f', + '--file', + nargs='*', + default=1, + help='message channel') + parser.add_argument('-j', + '--json_file', + nargs='*', + default=None, + help='json_file option') + args = parser.parse_args() + file_path = args.file[0] + client = ClientExd(args.json_file[0]) + client.getFilepath(file_path) + client.register() + client.requestGlobalMetrics() + time.sleep(2) + client.unregister() + client.unregistered = 1 + print("Get metrics done") + """) + fileName = 'query_tool.py' + query_script = os.path.join(self.output_path, fileName) + with open(query_script, 'wb') as fp: + fp.write('#! /usr/bin/env python' + os.linesep + script_content) + self.dut.session.copy_file_to(query_script, self.target_dir) + script_file = os.path.join(self.target_dir, fileName) + cmd = 'chmod 777 {}'.format(script_file) + self.d_a_con(cmd) + return script_file + + def init_telemetry(self): + ''' transfer dpdk-telemetry-client.py to the correct python module ''' + cmds = [ + 'rm -f {0}/dpdk_telemetry_client.py', + ('cp -f {0}/usertools/dpdk-telemetry-client.py ' + '{0}/dpdk_telemetry_client.py'), + ("sed -i -e 's/class Client:/class Client(object):/g' " + "{0}/dpdk_telemetry_client.py")] + cmd = ';'.join(cmds).format(self.target_dir) + self.d_a_con(cmd) + self.query_tool = self.create_query_script() + self.query_data = [] + + def telemetry_query(self): + json_name = 'telemetry_data.json' + json_file = os.path.join(self.target_dir, json_name) + pipe = '/var/run/some_client' + cmd = "{0} -j {1} -f {2}".format(self.query_tool, json_file, pipe) + output = self.d_a_con(cmd) + msg = 'faile to query metric data' + self.verify("Get metrics done" in output, msg) + dst_file = os.path.join(self.output_path, json_name) + self.dut.session.copy_file_from(json_file, dst_file) + msg = 'failed to get {}'.format(json_name) + self.verify(os.path.exists(dst_file), msg) + with open(dst_file, 'r') as fp: + try: + query_data = json.load(fp, encoding="utf-8") + except Exception as e: + msg = 'failed to load metrics json data' + raise VerifyFailure(msg) + self.logger.debug(pformat(query_data)) + metric_status = query_data.get('status_code') + msg = ('failed to query metric data, ' + 'return status <{}>').format(metric_status) + self.verify('status ok' in metric_status.lower(), msg) + + return query_data.get('data') + + def telemetry_query_on_traffic(self): + json_name = 'telemetry_data_on_traffic.json' + json_file = os.path.join(self.target_dir, json_name) + pipe = '/var/run/some_client' + cmd = "{0} -j {1} -f {2}".format(self.query_tool, json_file, pipe) + output = self.d_a_con(cmd) + dst_file = os.path.join(self.output_path, json_name) + self.dut.session.copy_file_from(json_file, dst_file) + + def parse_telemetry_query_on_traffic(self): + json_name = 'telemetry_data_on_traffic.json' + dst_file = os.path.join(self.output_path, json_name) + msg = 'failed to get {}'.format(json_name) + self.verify(os.path.exists(dst_file), msg) + with open(dst_file, 'r') as fp: + try: + query_data = json.load(fp, encoding="utf-8") + except Exception as e: + msg = 'failed to load metrics json data' + raise VerifyFailure(msg) + self.logger.debug(pformat(query_data)) + metric_status = query_data.get('status_code') + msg = ('failed to query metric data, ' + 'return status <{}>').format(metric_status) + self.verify('status ok' in metric_status.lower(), msg) + + return query_data.get('data') + + def get_sys_power_driver(self): + drv_file = "/sys/devices/system/cpu/cpu0/cpufreq/scaling_driver" + output = self.d_a_con('cat ' + drv_file) + if not output: + msg = 'unknown power driver' + raise VerifyFailure(msg) + drv_name = output.splitlines()[0].strip() + return drv_name + + def check_power_info_integrity(self, query_data): + expected_keys = ["empty_poll", "full_poll", "busy_percent"] + stats = query_data.get('stats') + if not stats: + msg = 'telemetry failed to get data' + raise VerifyFailure(msg) + for key in expected_keys: + for info in stats: + name = info.get('name') + value = info.get('value') + if name == key and value is not None: + break + else: + msg = 'telemetry failed to get data <{}>'.format(key) + raise VerifyFailure(msg) + + def check_busy_percent_result(self, data): + data_on_traffic = data.get('on_traffic') + data_traffic_stop = data.get('traffic_stop') + key = "busy_percent" + # busy_percent data on traffic should be non-zero number + stats = data_on_traffic[0].get('stats') + if not stats: + msg = 'telemetry failed to get data' + raise VerifyFailure(msg) + for info in stats: + name = info.get('name') + value = info.get('value') + if name == key: + break + else: + msg = 'telemetry failed to get data <{}>'.format(key) + raise VerifyFailure(msg) + if value is None or int(value) <= 0: + msg = '<{}> should be non-zero number on traffic'.format(key) + self.logger.error(value) + raise VerifyFailure(msg) + # busy_percent data on traffic should be zero number + stats = data_traffic_stop[0].get('stats') + if not stats: + msg = 'telemetry failed to get data' + raise VerifyFailure(msg) + for info in stats: + name = info.get('name') + value = info.get('value') + if name == key: + break + else: + msg = 'telemetry failed to get data <{}>'.format(key) + raise VerifyFailure(msg) + if value is None or value > 0: + msg = '<{}> should be zero after traffic stop'.format(key) + self.logger.error(value) + raise VerifyFailure(msg) + + def verify_telemetry_power_info(self): + ''' + Check power related info reported by telemetry system + ''' + except_content = None + try: + self.start_l3fwd_power() + data = self.telemetry_query() + self.check_power_info_integrity(data[0]) + except Exception as e: + self.logger.error(traceback.format_exc()) + except_content = e + finally: + self.close_l3fwd_power() + + # check verify result + if except_content: + raise VerifyFailure(except_content) + else: + msg = "test telemetry power info successful !!!" + self.logger.info(msg) + + def verify_busy_percent(self): + ''' + Check busy_percent with different injected throughput + ''' + except_content = None + try: + self.start_l3fwd_power() + option = { + 'traffic_opt': { + 'method': 'throughput', + 'duration': 15, + 'interval': 10, + 'callback': self.telemetry_query_on_traffic, }} + self.run_traffic(option) + time.sleep(5) + result = { + 'on_traffic': self.parse_telemetry_query_on_traffic(), + 'traffic_stop': self.telemetry_query(), } + self.check_busy_percent_result(result) + except Exception as e: + self.logger.error(traceback.format_exc()) + except_content = e + finally: + self.close_l3fwd_power() + + # check verify result + if except_content: + raise VerifyFailure(except_content) + else: + msg = "test busy percent successful !!!" + self.logger.info(msg) + + def verify_power_driver(self): + expected_drv = 'acpi-cpufreq' + power_drv = self.get_sys_power_driver() + msg = "{0} should work with {1} driver on DUT".format( + self.suite_name, expected_drv) + self.verify(power_drv == expected_drv, msg) + + def preset_test_environment(self): + self.is_l3fwd_on = None + # open compile switch and re-compile target source code + self.preset_compilation() + # init binary + self.init_l3fwd_power() + self.init_telemetry() + + # + # Test cases. + # + + def set_up_all(self): + """ + Run at the start of each test suite. + """ + self.verify_power_driver() + self.dut_ports = self.dut.get_ports(self.nic) + self.verify(len(self.dut_ports) >= 2, "Not enough ports") + # prepare testing environment + self.preset_test_environment() + + def tear_down_all(self): + """ Run after each test suite. """ + pass + + def set_up(self): + """ Run before each test case. """ + pass + + def tear_down(self): + """ Run after each test case. """ + self.dut.kill_all() + + def test_perf_telemetry_power_info(self): + self.verify_telemetry_power_info() + + def test_perf_busy_percent(self): + self.verify_busy_percent() -- 2.21.0