From 12eab1e564e0d5ab34a341039b92612de2973f3c Mon Sep 17 00:00:00 2001 From: Jan Gelety Date: Fri, 7 Sep 2018 15:32:06 +0200 Subject: Add VXLAN scale perf tests Jira: CSIT-1273 Change-Id: Ic2a41661c158384a5aaa7d4e73f30ffca13ddf82 Signed-off-by: Jan Gelety --- resources/libraries/python/IPv4Util.py | 11 + resources/libraries/python/InterfaceUtil.py | 283 +++++++++++++++++++-- resources/libraries/python/L2Util.py | 4 +- resources/libraries/python/VatExecutor.py | 23 ++ resources/libraries/python/ssh.py | 1 - resources/libraries/robot/ip/ip4.robot | 7 + .../performance/performance_configuration.robot | 42 +++ .../robot/performance/performance_setup.robot | 2 + 8 files changed, 345 insertions(+), 28 deletions(-) (limited to 'resources/libraries') diff --git a/resources/libraries/python/IPv4Util.py b/resources/libraries/python/IPv4Util.py index fa9d0fe6a9..c40e391c36 100644 --- a/resources/libraries/python/IPv4Util.py +++ b/resources/libraries/python/IPv4Util.py @@ -18,6 +18,7 @@ from robot.api.deco import keyword from resources.libraries.python.topology import Topology from resources.libraries.python.IPv4Setup import get_node +from resources.libraries.python.VatExecutor import VatTerminal from resources.libraries.python.ssh import exec_cmd @@ -227,3 +228,13 @@ class IPv4Util(object): if ret_code != 0: raise RuntimeError("Arp set not successful, reason:{}". format(stderr)) + + @staticmethod + def vpp_show_ip_table(node): + """Get IP FIB table data from a VPP node. + + :param node: VPP node. + :type node: dict + """ + with VatTerminal(node, json_param=False) as vat: + vat.vat_terminal_exec_cmd_from_template("show_ip_fib.vat") diff --git a/resources/libraries/python/InterfaceUtil.py b/resources/libraries/python/InterfaceUtil.py index 7fc7f85723..c0f37f38de 100644 --- a/resources/libraries/python/InterfaceUtil.py +++ b/resources/libraries/python/InterfaceUtil.py @@ -11,22 +11,23 @@ # See the License for the specific language governing permissions and # limitations under the License. -"""Interface util library""" +"""Interface util library.""" from time import time, sleep from robot.api import logger -from resources.libraries.python.ssh import SSH -from resources.libraries.python.IPUtil import convert_ipv4_netmask_prefix +from resources.libraries.python.CpuUtils import CpuUtils from resources.libraries.python.DUTSetup import DUTSetup -from resources.libraries.python.ssh import exec_cmd_no_error +from resources.libraries.python.IPUtil import convert_ipv4_netmask_prefix +from resources.libraries.python.IPUtil import IPUtil +from resources.libraries.python.parsers.JsonParser import JsonParser +from resources.libraries.python.ssh import SSH, exec_cmd_no_error from resources.libraries.python.topology import NodeType, Topology from resources.libraries.python.VatExecutor import VatExecutor, VatTerminal from resources.libraries.python.VatJsonUtil import VatJsonUtil from resources.libraries.python.VPPUtil import VPPUtil -from resources.libraries.python.parsers.JsonParser import JsonParser -from resources.libraries.python.CpuUtils import CpuUtils + class InterfaceUtil(object): """General utilities for managing interfaces""" @@ -587,15 +588,15 @@ class InterfaceUtil(object): else: raise ValueError except ValueError: - logger.trace('Reading numa location failed for: {0}'\ - .format(if_pci)) + logger.trace('Reading numa location failed for: {0}' + .format(if_pci)) else: Topology.set_interface_numa_node(node, if_key, numa_node) break else: - raise RuntimeError('Update numa node failed for: {0}'\ - .format(if_pci)) + raise RuntimeError('Update numa node failed for: {0}' + .format(if_pci)) @staticmethod def update_all_numa_nodes(nodes, skip_tg=False): @@ -663,13 +664,13 @@ class InterfaceUtil(object): sw_if_index=sw_if_index, vlan=vlan) if output[0]["retval"] == 0: - sw_subif_idx = output[0]["sw_if_index"] + sw_vlan_idx = output[0]["sw_if_index"] logger.trace('VLAN subinterface with sw_if_index {} and VLAN ID {} ' - 'created on node {}'.format(sw_subif_idx, + 'created on node {}'.format(sw_vlan_idx, vlan, node['host'])) if_key = Topology.add_new_port(node, "vlan_subif") - Topology.update_interface_sw_if_index(node, if_key, sw_subif_idx) - ifc_name = InterfaceUtil.vpp_get_interface_name(node, sw_subif_idx) + Topology.update_interface_sw_if_index(node, if_key, sw_vlan_idx) + ifc_name = InterfaceUtil.vpp_get_interface_name(node, sw_vlan_idx) Topology.update_interface_name(node, if_key, ifc_name) else: raise RuntimeError('Unable to create VLAN subinterface on node {}' @@ -678,7 +679,7 @@ class InterfaceUtil(object): with VatTerminal(node, False) as vat: vat.vat_terminal_exec_cmd('exec show interfaces') - return '{}.{}'.format(interface, vlan), sw_subif_idx + return '{}.{}'.format(interface, vlan), sw_vlan_idx @staticmethod def create_vxlan_interface(node, vni, source_ip, destination_ip): @@ -840,12 +841,12 @@ class InterfaceUtil(object): type_subif=type_subif) if output[0]["retval"] == 0: - sw_subif_idx = output[0]["sw_if_index"] + sw_vlan_idx = output[0]["sw_if_index"] logger.trace('Created subinterface with index {}' - .format(sw_subif_idx)) + .format(sw_vlan_idx)) if_key = Topology.add_new_port(node, "subinterface") - Topology.update_interface_sw_if_index(node, if_key, sw_subif_idx) - ifc_name = InterfaceUtil.vpp_get_interface_name(node, sw_subif_idx) + Topology.update_interface_sw_if_index(node, if_key, sw_vlan_idx) + ifc_name = InterfaceUtil.vpp_get_interface_name(node, sw_vlan_idx) Topology.update_interface_name(node, if_key, ifc_name) else: raise RuntimeError('Unable to create sub-interface on node {}' @@ -855,7 +856,7 @@ class InterfaceUtil(object): vat.vat_terminal_exec_cmd('exec show interfaces') name = '{}.{}'.format(interface, sub_id) - return name, sw_subif_idx + return name, sw_vlan_idx @staticmethod def create_gre_tunnel_interface(node, source_ip, destination_ip): @@ -1317,13 +1318,13 @@ class InterfaceUtil(object): driver testing on DUT. :param node: DUT node. - :param iface_key: Interface key from topology file. + :param ifc_key: Interface key from topology file. :param numvfs: Number of VFs to initialize, 0 - disable the VFs. :param topology_type: Topology type. :type node: dict - :iface_key: str + :type ifc_key: str :type numvfs: int - :typ topology_type: str + :type topology_type: str :returns: Virtual Function topology interface keys. :rtype: list """ @@ -1335,8 +1336,8 @@ class InterfaceUtil(object): pf_mac_addr = Topology.get_interface_mac(node, ifc_key).split(":") uio_driver = Topology.get_uio_driver(node) kernel_driver = Topology.get_interface_driver(node, ifc_key) - current_driver = DUTSetup.get_pci_dev_driver(node,\ - pf_pci_addr.replace(':', r'\:')) + current_driver = DUTSetup.get_pci_dev_driver( + node, pf_pci_addr.replace(':', r'\:')) VPPUtil.stop_vpp_service(node) if current_driver != kernel_driver: @@ -1382,3 +1383,235 @@ class InterfaceUtil(object): vf_ifc_keys.append(vf_ifc_key) return vf_ifc_keys + + @staticmethod + def vpp_create_multiple_vxlan_ipv4_tunnels( + node, node_vxlan_if, node_vlan_if, op_node, op_node_if, + n_tunnels, vni_start, src_ip_start, dst_ip_start, ip_step, ip_limit, + bd_id_start): + """Create multiple VXLAN tunnel interfaces and VLAN sub-interfaces on + VPP node. + + Put each pair of VXLAN tunnel interface and VLAN sub-interface to + separate bridge-domain. + + :param node: VPP node to create VXLAN tunnel interfaces. + :param node_vxlan_if: VPP node interface key to create VXLAN tunnel + interfaces. + :param node_vlan_if: VPP node interface key to create VLAN + sub-interface. + :param op_node: Opposite VPP node for VXLAN tunnel interfaces. + :param op_node_if: Opposite VPP node interface key for VXLAN tunnel + interfaces. + :param n_tunnels: Number of tunnel interfaces to create. + :param vni_start: VNI start ID. + :param src_ip_start: VXLAN tunnel source IP address start. + :param dst_ip_start: VXLAN tunnel destination IP address start. + :param ip_step: IP address incremental step. + :param ip_limit: IP address limit. + :param bd_id_start: Bridge-domain ID start. + :type node: dict + :type node_vxlan_if: str + :type node_vlan_if: str + :type op_node: dict + :type op_node_if: str + :type n_tunnels: int + :type vni_start: int + :type src_ip_start: str + :type dst_ip_start: str + :type ip_step: int + :type ip_limit: str + :type bd_id_start: int + """ + # configure IPs, create VXLAN interfaces and VLAN sub-interfaces + vxlan_count = InterfaceUtil.vpp_create_vxlan_and_vlan_interfaces( + node, node_vxlan_if, node_vlan_if, n_tunnels, vni_start, + src_ip_start, dst_ip_start, ip_step, ip_limit) + + # update topology with VXLAN interfaces and VLAN sub-interfaces data + # and put interfaces up + InterfaceUtil.vpp_put_vxlan_and_vlan_interfaces_up( + node, vxlan_count, node_vlan_if) + + # configure bridge domains, ARPs and routes + InterfaceUtil.vpp_put_vxlan_and_vlan_interfaces_to_bridge_domain( + node, node_vxlan_if, vxlan_count, op_node, op_node_if, dst_ip_start, + ip_step, bd_id_start) + + @staticmethod + def vpp_create_vxlan_and_vlan_interfaces( + node, node_vxlan_if, node_vlan_if, vxlan_count, vni_start, + src_ip_start, dst_ip_start, ip_step, ip_limit): + """ + Configure IPs, create VXLAN interfaces and VLAN sub-interfaces on VPP + node. + + :param node: VPP node. + :param node_vxlan_if: VPP node interface key to create VXLAN tunnel + interfaces. + :param node_vlan_if: VPP node interface key to create VLAN + sub-interface. + :param vxlan_count: Number of tunnel interfaces to create. + :param vni_start: VNI start ID. + :param src_ip_start: VXLAN tunnel source IP address start. + :param dst_ip_start: VXLAN tunnel destination IP address start. + :param ip_step: IP address incremental step. + :param ip_limit: IP address limit. + :type node: dict + :type node_vxlan_if: str + :type node_vlan_if: str + :type vxlan_count: int + :type vni_start: int + :type src_ip_start: str + :type dst_ip_start: str + :type ip_step: int + :type ip_limit: str + :returns: Number of created VXLAN interfaces. + :rtype: int + """ + commands = list() + + src_ip_start_int = IPUtil.ip_to_int(src_ip_start) + dst_ip_start_int = IPUtil.ip_to_int(dst_ip_start) + ip_limit_int = IPUtil.ip_to_int(ip_limit) + + tmp_fn = '/tmp/create_vxlan_interfaces.config' + for i in range(0, vxlan_count): + src_ip_int = src_ip_start_int + i * ip_step + dst_ip_int = dst_ip_start_int + i * ip_step + if src_ip_int > ip_limit_int or dst_ip_int > ip_limit_int: + logger.warn("Can't do more iterations - IPv4 address limit " + "has been reached.") + vxlan_count = i + break + src_ip = IPUtil.int_to_ip(src_ip_int) + dst_ip = IPUtil.int_to_ip(dst_ip_int) + commands.append( + 'sw_interface_add_del_address sw_if_index {sw_idx} {ip}/32\n' + .format(sw_idx=Topology.get_interface_sw_index( + node, node_vxlan_if), ip=src_ip)) + commands.append( + 'vxlan_add_del_tunnel src {src_ip} dst {dst_ip} vni {vni}\n' + .format(src_ip=src_ip, dst_ip=dst_ip, vni=vni_start+i)) + commands.append( + 'create_vlan_subif sw_if_index {sw_idx} vlan {vlan}\n' + .format(sw_idx=Topology.get_interface_sw_index( + node, node_vlan_if), vlan=i+1)) + + VatExecutor().write_and_execute_script(node, tmp_fn, commands) + + return vxlan_count + + @staticmethod + def vpp_put_vxlan_and_vlan_interfaces_up(node, vxlan_count, node_vlan_if): + """ + Update topology with VXLAN interfaces and VLAN sub-interfaces data + and put interfaces up. + + :param node: VPP node. + :param vxlan_count: Number of tunnel interfaces. + :param node_vlan_if: VPP node interface key where VLAN sub-interfaces + have been created. + :type node: dict + :type vxlan_count: int + :type node_vlan_if: str + """ + with VatTerminal(node) as vat_ter: + if_data = vat_ter.vat_terminal_exec_cmd_from_template( + 'interface_dump.vat')[0] + + tmp_fn = '/tmp/put_subinterfaces_up.config' + commands = list() + for i in range(0, vxlan_count): + vxlan_subif_key = Topology.add_new_port(node, 'vxlan_tunnel') + vxlan_subif_name = 'vxlan_tunnel{nr}'.format(nr=i) + vxlan_found = False + vxlan_subif_idx = None + vlan_subif_key = Topology.add_new_port(node, 'vlan_subif') + vlan_subif_name = '{if_name}.{vlan}'.format( + if_name=Topology.get_interface_name( + node, node_vlan_if), vlan=i+1) + vlan_found = False + vlan_idx = None + for data in if_data: + if_name = data['interface_name'] + if not vxlan_found and if_name == vxlan_subif_name: + vxlan_subif_idx = data['sw_if_index'] + vxlan_found = True + elif not vlan_found and if_name == vlan_subif_name: + vlan_idx = data['sw_if_index'] + vlan_found = True + if vxlan_found and vlan_found: + break + Topology.update_interface_sw_if_index( + node, vxlan_subif_key, vxlan_subif_idx) + Topology.update_interface_name( + node, vxlan_subif_key, vxlan_subif_name) + commands.append( + 'sw_interface_set_flags sw_if_index {sw_idx} admin-up link-up\n' + .format(sw_idx=vxlan_subif_idx)) + Topology.update_interface_sw_if_index( + node, vlan_subif_key, vlan_idx) + Topology.update_interface_name( + node, vlan_subif_key, vlan_subif_name) + commands.append( + 'sw_interface_set_flags sw_if_index {sw_idx} admin-up link-up\n' + .format(sw_idx=vlan_idx)) + + VatExecutor().write_and_execute_script(node, tmp_fn, commands) + + @staticmethod + def vpp_put_vxlan_and_vlan_interfaces_to_bridge_domain( + node, node_vxlan_if, vxlan_count, op_node, op_node_if, dst_ip_start, + ip_step, bd_id_start): + """ + Configure ARPs and routes for VXLAN interfaces and put each pair of + VXLAN tunnel interface and VLAN sub-interface to separate bridge-domain. + + :param node: VPP node. + :param node_vxlan_if: VPP node interface key where VXLAN tunnel + interfaces have been created. + :param vxlan_count: Number of tunnel interfaces. + :param op_node: Opposite VPP node for VXLAN tunnel interfaces. + :param op_node_if: Opposite VPP node interface key for VXLAN tunnel + interfaces. + :param dst_ip_start: VXLAN tunnel destination IP address start. + :param ip_step: IP address incremental step. + :param bd_id_start: Bridge-domain ID start. + :type node: dict + :type node_vxlan_if: str + :type vxlan_count: int + :type op_node: dict + :type op_node_if: + :type dst_ip_start: str + :type ip_step: int + :type bd_id_start: int + """ + sw_idx_vxlan = Topology.get_interface_sw_index(node, node_vxlan_if) + + dst_ip_start_int = IPUtil.ip_to_int(dst_ip_start) + + tmp_fn = '/tmp/configure_routes_and_bridge_domains.config' + commands = list() + for i in range(0, vxlan_count): + dst_ip = IPUtil.int_to_ip(dst_ip_start_int + i * ip_step) + commands.append( + 'ip_neighbor_add_del sw_if_index {sw_idx} dst {ip} mac {mac}\n' + .format(sw_idx=sw_idx_vxlan, ip=dst_ip, + mac=Topology.get_interface_mac(op_node, op_node_if))) + commands.append( + 'ip_add_del_route {ip}/32 via {ip} sw_if_index {sw_idx}' + ' resolve-attempts 10 count 1\n'.format( + ip=dst_ip, sw_idx=sw_idx_vxlan)) + bd_id = bd_id_start + i + subif_id = i + 1 + commands.append( + 'sw_interface_set_l2_bridge sw_if_index {sw_idx} bd_id {bd_id} ' + 'shg 0 enable\n'.format(sw_idx=Topology.get_interface_sw_index( + node, 'vxlan_tunnel{nr}'.format(nr=subif_id)), bd_id=bd_id)) + commands.append( + 'sw_interface_set_l2_bridge sw_if_index {sw_idx} bd_id {bd_id} ' + 'shg 0 enable\n'.format(sw_idx=Topology.get_interface_sw_index( + node, 'vlan_subif{nr}'.format(nr=subif_id)), bd_id=bd_id)) + + VatExecutor().write_and_execute_script(node, tmp_fn, commands) diff --git a/resources/libraries/python/L2Util.py b/resources/libraries/python/L2Util.py index 2acb6dba34..97cef611c5 100644 --- a/resources/libraries/python/L2Util.py +++ b/resources/libraries/python/L2Util.py @@ -43,9 +43,9 @@ class L2Util(object): to string format (01:02:03:04:05:06). :param mac_int: MAC address in integer representation. - :type mac_int: str + :type mac_int: int :returns: String representation of MAC address. - :rtype: int + :rtype: str """ return ':'.join(wrap("{:012x}".format(mac_int), width=2)) diff --git a/resources/libraries/python/VatExecutor.py b/resources/libraries/python/VatExecutor.py index 8b9d632836..03379ba33f 100644 --- a/resources/libraries/python/VatExecutor.py +++ b/resources/libraries/python/VatExecutor.py @@ -14,6 +14,7 @@ """VAT executor library.""" import json +from os import remove from paramiko.ssh_exception import SSHException from robot.api import logger @@ -133,6 +134,28 @@ class VatExecutor(object): self._stderr = stderr self._script_name = vat_name + def write_and_execute_script(self, node, tmp_fn, commands, timeout=300, + json_out=False): + """Write VAT commands to the script, copy it to node and execute it. + + :param node: VPP node. + :param tmp_fn: Path to temporary file script. + :param commands: VAT command list. + :param timeout: Seconds to allow the script to run. + :param json_out: Require JSON output. + :type node: dict + :type tmp_fn: str + :type commands: list + :type timeout: int + :type json_out: bool + """ + with open(tmp_fn, 'w') as tmp_f: + tmp_f.writelines(commands) + + self.execute_script(tmp_fn, node, timeout=timeout, json_out=json_out, + copy_on_execute=True) + remove(tmp_fn) + def execute_script_json_out(self, vat_name, node, timeout=120): """Pass all arguments to 'execute_script' method, then cleanup returned json output. diff --git a/resources/libraries/python/ssh.py b/resources/libraries/python/ssh.py index 5e33a7cf9c..4bcfe6591f 100644 --- a/resources/libraries/python/ssh.py +++ b/resources/libraries/python/ssh.py @@ -22,7 +22,6 @@ from paramiko import RSAKey from paramiko.ssh_exception import SSHException from scp import SCPClient from robot.api import logger -from robot.utils.asserts import assert_equal __all__ = ["exec_cmd", "exec_cmd_no_error"] diff --git a/resources/libraries/robot/ip/ip4.robot b/resources/libraries/robot/ip/ip4.robot index 03964181b5..8fff0846b0 100644 --- a/resources/libraries/robot/ip/ip4.robot +++ b/resources/libraries/robot/ip/ip4.robot @@ -23,6 +23,13 @@ *** Keywords *** +| Show IP FIB On All DUTs +| | [Documentation] | Show IP FIB on all DUTs. +| | ... +| | ${duts}= | Get Matches | ${nodes} | DUT* +| | :FOR | ${dut} | IN | @{duts} +| | | VPP Show IP Table | ${nodes['${dut}']} + | Configure IPv4 addresses on all DUTs | | [Documentation] | Setup IPv4 address on all DUTs in topology | | [Arguments] | ${nodes} | ${nodes_addr} diff --git a/resources/libraries/robot/performance/performance_configuration.robot b/resources/libraries/robot/performance/performance_configuration.robot index de60cead90..e812f56f5d 100644 --- a/resources/libraries/robot/performance/performance_configuration.robot +++ b/resources/libraries/robot/performance/performance_configuration.robot @@ -1762,6 +1762,48 @@ | | Configure L2BD forwarding | ${dut1} | ${dut1_if1} | ${dut1s_vxlan} | | Configure L2BD forwarding | ${dut2} | ${dut2_if2} | ${dut2s_vxlan} +| Initialize L2 bridge domain with VLAN and VXLANoIPv4 in 3-node circular topology +| | [Documentation] +| | ... | Setup L2 bridge domain topology with VLAN and VXLANoIPv4 by connecting +| | ... | pairs of VLAN sub-interface and VXLAN interface to separate L2 bridge +| | ... | domain on each DUT. All interfaces are brought up. IPv4 addresses +| | ... | with prefix /32 are configured on interfaces between DUTs. VXLAN +| | ... | sub-interfaces has same IPv4 address as interfaces. +| | ... +| | ... | *Arguments:* +| | ... | - vxlan_count - VXLAN count. Type: integer +| | ... +| | ... | *Example:* +| | ... +| | ... | \| Initialize L2 bridge domain with VLAN and VXLANoIPv4 in 3-node \ +| | ... | \| circular topology \| ${1} \| +| | ... +| | [Arguments] | ${vxlan_count}=${1} +| | ... +| | Set interfaces in path up +| | ... +| | ${bd_id_start}= | Set Variable | ${1} +| | ${vni_start} = | Set Variable | ${20} +| | ... +| | ${ip_step} = | Set Variable | ${2} +| | ${dut1_ip_start}= | Set Variable | 172.16.0.1 +| | ${dut2_ip_start}= | Set Variable | 172.16.0.2 +| | ... +| | ${ip_limit} = | Set Variable | 255.255.255.255 +| | ... +| | Vpp create multiple VXLAN IPv4 tunnels | node=${dut1} +| | ... | node_vxlan_if=${dut1_if2} | node_vlan_if=${dut1_if1} +| | ... | op_node=${dut2} | op_node_if=${dut2_if1} | n_tunnels=${vxlan_count} +| | ... | vni_start=${vni_start} | src_ip_start=${dut1_ip_start} +| | ... | dst_ip_start=${dut2_ip_start} | ip_step=${ip_step} +| | ... | ip_limit=${ip_limit} | bd_id_start=${bd_id_start} +| | Vpp create multiple VXLAN IPv4 tunnels | node=${dut2} +| | ... | node_vxlan_if=${dut2_if1} | node_vlan_if=${dut2_if2} +| | ... | op_node=${dut1} | op_node_if=${dut1_if2} | n_tunnels=${vxlan_count} +| | ... | vni_start=${vni_start} | src_ip_start=${dut2_ip_start} +| | ... | dst_ip_start=${dut1_ip_start} | ip_step=${ip_step} +| | ... | ip_limit=${ip_limit} | bd_id_start=${bd_id_start} + | Initialize L2 bridge domains with Vhost-User and VXLANoIPv4 in 3-node circular topology | | [Documentation] | | ... | Create two Vhost-User interfaces on all defined VPP nodes. Add each diff --git a/resources/libraries/robot/performance/performance_setup.robot b/resources/libraries/robot/performance/performance_setup.robot index f92f88b219..83f5bc914f 100644 --- a/resources/libraries/robot/performance/performance_setup.robot +++ b/resources/libraries/robot/performance/performance_setup.robot @@ -670,6 +670,7 @@ | | Run Keyword If Test Failed | | ... | Traffic should pass with no loss | ${perf_trial_duration} | ${rate} | | ... | ${framesize} | ${topology_type} | fail_on_loss=${False} +| | Show IP FIB On All DUTs | Tear down performance mrr test | | [Documentation] | Common test teardown for max-received-rate performance @@ -677,6 +678,7 @@ | | ... | | Remove All Added Ports On All DUTs From Topology | ${nodes} | | Show VAT History On All DUTs | ${nodes} +| | Show IP FIB On All DUTs | Tear down performance test with wrk | | [Documentation] | Common test teardown for ndrdisc and pdrdisc performance \ -- cgit 1.2.3-korg