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 2AE98A0352; Thu, 16 Jan 2020 03:30:04 +0100 (CET) Received: from [92.243.14.124] (localhost [127.0.0.1]) by dpdk.org (Postfix) with ESMTP id 1AACB1BFCC; Thu, 16 Jan 2020 03:30:04 +0100 (CET) Received: from mga01.intel.com (mga01.intel.com [192.55.52.88]) by dpdk.org (Postfix) with ESMTP id 5EE341BFCC for ; Thu, 16 Jan 2020 03:30:02 +0100 (CET) X-Amp-Result: SKIPPED(no attachment in message) X-Amp-File-Uploaded: False Received: from fmsmga008.fm.intel.com ([10.253.24.58]) by fmsmga101.fm.intel.com with ESMTP/TLS/DHE-RSA-AES256-GCM-SHA384; 15 Jan 2020 18:30:01 -0800 X-ExtLoop1: 1 X-IronPort-AV: E=Sophos;i="5.70,324,1574150400"; d="scan'208";a="220225619" Received: from fmsmsx106.amr.corp.intel.com ([10.18.124.204]) by fmsmga008.fm.intel.com with ESMTP; 15 Jan 2020 18:30:01 -0800 Received: from shsmsx606.ccr.corp.intel.com (10.109.6.216) by FMSMSX106.amr.corp.intel.com (10.18.124.204) with Microsoft SMTP Server (TLS) id 14.3.439.0; Wed, 15 Jan 2020 18:30:01 -0800 Received: from shsmsx602.ccr.corp.intel.com (10.109.6.142) by SHSMSX606.ccr.corp.intel.com (10.109.6.216) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256) id 15.1.1713.5; Thu, 16 Jan 2020 10:29:59 +0800 Received: from shsmsx602.ccr.corp.intel.com ([10.109.6.142]) by SHSMSX602.ccr.corp.intel.com ([10.109.6.142]) with mapi id 15.01.1713.004; Thu, 16 Jan 2020 10:29:59 +0800 From: "Yao, Lei A" To: "Mo, YufengX" , "dts@dpdk.org" Thread-Topic: [dts][PATCH V1 1/3] tests/power_telemetry: upload automation script Thread-Index: AQHVzA/dQR1+L4Y5SUmZ/CAJEvthCqfskXsQ Date: Thu, 16 Jan 2020 02:29:58 +0000 Message-ID: <463f04019f384ce290b27288e125aef0@intel.com> References: <20200116015645.21294-1-yufengx.mo@intel.com> <20200116015645.21294-2-yufengx.mo@intel.com> In-Reply-To: <20200116015645.21294-2-yufengx.mo@intel.com> Accept-Language: en-US Content-Language: en-US X-MS-Has-Attach: X-MS-TNEF-Correlator: dlp-version: 11.2.0.6 dlp-reaction: no-action dlp-product: dlpe-windows x-originating-ip: [10.239.127.36] Content-Type: text/plain; charset="us-ascii" Content-Transfer-Encoding: quoted-printable MIME-Version: 1.0 Subject: Re: [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" > -----Original Message----- > From: Mo, YufengX > Sent: Thursday, January 16, 2020 9:57 AM > To: dts@dpdk.org; Yao, Lei A > Cc: Mo, YufengX > Subject: [dts][PATCH V1 1/3] tests/power_telemetry: upload automation > script >=20 >=20 > 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. >=20 > Signed-off-by: yufengmx Acked-by : Lei Yao > --- > tests/TestSuite_power_telemetry.py | 465 > +++++++++++++++++++++++++++++ > 1 file changed, 465 insertions(+) > create mode 100644 tests/TestSuite_power_telemetry.py >=20 > 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 =3D '/tmp' > + > + @property > + def target_dir(self): > + # get absolute directory of target source code > + target_dir =3D '/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 =3D [cmd, '# ', 10] if isinstance(cmd, (str, unicode)) else= cmd > + return self.dut.send_expect(*_cmd) > + > + def d_a_con(self, cmd): > + _cmd =3D [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=3D64): > + headers_size =3D sum(map(lambda x: HEADER_SIZE[x], > + ['eth', 'ip', pkt_type])) > + pktlen =3D frame_size - headers_size > + return pktlen > + > + def config_stream(self, dmac): > + pkt_config =3D { > + 'type': 'UDP', > + 'pkt_layers': { > + 'ether': {'dst': dmac}, > + 'raw': {'payload': ['58'] * self.get_pkt_len('udp')}} > + } > + values =3D pkt_config > + pkt_type =3D values.get('type') > + pkt_layers =3D values.get('pkt_layers') > + pkt =3D Packet(pkt_type=3Dpkt_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 =3D [] > + topos =3D [[0, 1], [1, 0]] > + for txport, rxport in topos: > + _option =3D deepcopy(option) > + dmac =3D self.dut.get_mac_address(self.dut_ports[txport]) > + pkt =3D self.config_stream(dmac) > + _option['pcap'] =3D pkt > + stream_id =3D 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 =3D { > + 'stream_config': { > + 'txmode': {}, > + 'transmit_mode': TRANSMIT_CONT, > + 'rate': float(100), } > + } > + stream_ids =3D self.add_stream_to_pktgen(stream_option) > + # run pktgen traffic > + traffic_opt =3D option.get('traffic_opt') > + result =3D 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 =3D "CONFIG_RTE_LIBRTE_TELEMETRY" > + cmd =3D "sed -i -e 's/{0}=3Dn$/{0}=3Dy/' {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 =3D "examples/" + name > + out =3D 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 =3D os.path.join(self.target_dir, example_dir, 'build= ') > + cmd =3D ["ls -F {0} | grep '*'".format(binary_dir), '# ', 5] > + exec_file =3D self.d_a_con(cmd) > + binary_file =3D os.path.join(binary_dir, exec_file[:-1]) > + return binary_file > + > + def get_cores_mask(self, config): > + ports_socket =3D self.dut.get_numa_id(self.dut.get_ports()[0]) > + mask =3D dts_create_mask( > + self.dut.get_core_list(config, socket=3Dports_socket)) > + return mask > + > + def init_l3fwd_power(self): > + self.l3fwd_power =3D self.prepare_binary('l3fwd-power') > + > + def start_l3fwd_power(self, core_config=3D'1S/2C/1T'): > + option =3D (' ' > + '-c {core_mask} ' > + '-n {mem_channel} ' > + '--telemetry ' > + '-- ' > + '--telemetry ' > + '-p 0x1 ' > + '-P ' > + '--config=3D"(0,0,2)" ' > + ).format(**{ > + 'core_mask': self.get_cores_mask(core_config), > + 'mem_channel': self.dut.get_memory_channels(), }) > + prompt =3D 'L3FWD_POWER: entering main telemetry loop' > + cmd =3D [' '.join([self.l3fwd_power, option]), prompt, 60] > + self.d_con(cmd) > + self.is_l3fwd_on =3D True > + > + def close_l3fwd_power(self): > + if not self.is_l3fwd_on: > + return > + cmd =3D '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 da= ta, > + this method is used to make sure testing robust. > + ''' > + script_content =3D 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 =3D 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 =3D self.socket.client_fd.recv(BUFFER_SIZE) > + self.save_date(data) > + parser =3D argparse.ArgumentParser(description=3D'telemetry'= ) > + parser.add_argument('-f', > + '--file', > + nargs=3D'*', > + default=3D1, > + help=3D'message channel') > + parser.add_argument('-j', > + '--json_file', > + nargs=3D'*', > + default=3DNone, > + help=3D'json_file option') > + args =3D parser.parse_args() > + file_path =3D args.file[0] > + client =3D ClientExd(args.json_file[0]) > + client.getFilepath(file_path) > + client.register() > + client.requestGlobalMetrics() > + time.sleep(2) > + client.unregister() > + client.unregistered =3D 1 > + print("Get metrics done") > + """) > + fileName =3D 'query_tool.py' > + query_script =3D os.path.join(self.output_path, fileName) > + with open(query_script, 'wb') as fp: > + fp.write('#! /usr/bin/env python' + os.linesep + script_cont= ent) > + self.dut.session.copy_file_to(query_script, self.target_dir) > + script_file =3D os.path.join(self.target_dir, fileName) > + cmd =3D '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 modu= le ''' > + cmds =3D [ > + '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 =3D ';'.join(cmds).format(self.target_dir) > + self.d_a_con(cmd) > + self.query_tool =3D self.create_query_script() > + self.query_data =3D [] > + > + def telemetry_query(self): > + json_name =3D 'telemetry_data.json' > + json_file =3D os.path.join(self.target_dir, json_name) > + pipe =3D '/var/run/some_client' > + cmd =3D "{0} -j {1} -f {2}".format(self.query_tool, json_file, p= ipe) > + output =3D self.d_a_con(cmd) > + msg =3D 'faile to query metric data' > + self.verify("Get metrics done" in output, msg) > + dst_file =3D os.path.join(self.output_path, json_name) > + self.dut.session.copy_file_from(json_file, dst_file) > + msg =3D 'failed to get {}'.format(json_name) > + self.verify(os.path.exists(dst_file), msg) > + with open(dst_file, 'r') as fp: > + try: > + query_data =3D json.load(fp, encoding=3D"utf-8") > + except Exception as e: > + msg =3D 'failed to load metrics json data' > + raise VerifyFailure(msg) > + self.logger.debug(pformat(query_data)) > + metric_status =3D query_data.get('status_code') > + msg =3D ('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 =3D 'telemetry_data_on_traffic.json' > + json_file =3D os.path.join(self.target_dir, json_name) > + pipe =3D '/var/run/some_client' > + cmd =3D "{0} -j {1} -f {2}".format(self.query_tool, json_file, p= ipe) > + output =3D self.d_a_con(cmd) > + dst_file =3D 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 =3D 'telemetry_data_on_traffic.json' > + dst_file =3D os.path.join(self.output_path, json_name) > + msg =3D 'failed to get {}'.format(json_name) > + self.verify(os.path.exists(dst_file), msg) > + with open(dst_file, 'r') as fp: > + try: > + query_data =3D json.load(fp, encoding=3D"utf-8") > + except Exception as e: > + msg =3D 'failed to load metrics json data' > + raise VerifyFailure(msg) > + self.logger.debug(pformat(query_data)) > + metric_status =3D query_data.get('status_code') > + msg =3D ('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 =3D "/sys/devices/system/cpu/cpu0/cpufreq/scaling_drive= r" > + output =3D self.d_a_con('cat ' + drv_file) > + if not output: > + msg =3D 'unknown power driver' > + raise VerifyFailure(msg) > + drv_name =3D output.splitlines()[0].strip() > + return drv_name > + > + def check_power_info_integrity(self, query_data): > + expected_keys =3D ["empty_poll", "full_poll", "busy_percent"] > + stats =3D query_data.get('stats') > + if not stats: > + msg =3D 'telemetry failed to get data' > + raise VerifyFailure(msg) > + for key in expected_keys: > + for info in stats: > + name =3D info.get('name') > + value =3D info.get('value') > + if name =3D=3D key and value is not None: > + break > + else: > + msg =3D 'telemetry failed to get data <{}>'.format(key) > + raise VerifyFailure(msg) > + > + def check_busy_percent_result(self, data): > + data_on_traffic =3D data.get('on_traffic') > + data_traffic_stop =3D data.get('traffic_stop') > + key =3D "busy_percent" > + # busy_percent data on traffic should be non-zero number > + stats =3D data_on_traffic[0].get('stats') > + if not stats: > + msg =3D 'telemetry failed to get data' > + raise VerifyFailure(msg) > + for info in stats: > + name =3D info.get('name') > + value =3D info.get('value') > + if name =3D=3D key: > + break > + else: > + msg =3D 'telemetry failed to get data <{}>'.format(key) > + raise VerifyFailure(msg) > + if value is None or int(value) <=3D 0: > + msg =3D '<{}> should be non-zero number on traffic'.format(k= ey) > + self.logger.error(value) > + raise VerifyFailure(msg) > + # busy_percent data on traffic should be zero number > + stats =3D data_traffic_stop[0].get('stats') > + if not stats: > + msg =3D 'telemetry failed to get data' > + raise VerifyFailure(msg) > + for info in stats: > + name =3D info.get('name') > + value =3D info.get('value') > + if name =3D=3D key: > + break > + else: > + msg =3D 'telemetry failed to get data <{}>'.format(key) > + raise VerifyFailure(msg) > + if value is None or value > 0: > + msg =3D '<{}> 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 =3D None > + try: > + self.start_l3fwd_power() > + data =3D self.telemetry_query() > + self.check_power_info_integrity(data[0]) > + except Exception as e: > + self.logger.error(traceback.format_exc()) > + except_content =3D e > + finally: > + self.close_l3fwd_power() > + > + # check verify result > + if except_content: > + raise VerifyFailure(except_content) > + else: > + msg =3D "test telemetry power info successful !!!" > + self.logger.info(msg) > + > + def verify_busy_percent(self): > + ''' > + Check busy_percent with different injected throughput > + ''' > + except_content =3D None > + try: > + self.start_l3fwd_power() > + option =3D { > + 'traffic_opt': { > + 'method': 'throughput', > + 'duration': 15, > + 'interval': 10, > + 'callback': self.telemetry_query_on_traffic, }} > + self.run_traffic(option) > + time.sleep(5) > + result =3D { > + '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 =3D e > + finally: > + self.close_l3fwd_power() > + > + # check verify result > + if except_content: > + raise VerifyFailure(except_content) > + else: > + msg =3D "test busy percent successful !!!" > + self.logger.info(msg) > + > + def verify_power_driver(self): > + expected_drv =3D 'acpi-cpufreq' > + power_drv =3D self.get_sys_power_driver() > + msg =3D "{0} should work with {1} driver on DUT".format( > + self.suite_name, expected_drv) > + self.verify(power_drv =3D=3D expected_drv, msg) > + > + def preset_test_environment(self): > + self.is_l3fwd_on =3D 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 =3D self.dut.get_ports(self.nic) > + self.verify(len(self.dut_ports) >=3D 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