diff options
author | Jing Zhang <jing.c.zhang@nokia.com> | 2017-04-11 10:52:30 -0400 |
---|---|---|
committer | Jing Zhang <jing.c.zhang@nokia.com> | 2017-07-20 09:31:31 -0400 |
commit | 5478e77dab55d30ebd3e49b8b8f6002a03b4a065 (patch) | |
tree | 2599f4ab95a9235b6fdbde5d77f9a23579105daa /yardstick/benchmark/scenarios/networking/pktgen.py | |
parent | 77ac1aa890964b40aaf12b43301d37575ae753d3 (diff) |
Add multiqueue support
Problem:
Neither OVS nor SRIOV multi-queue is not supported. Guest VM lacks tuning to reach high throughput.
Solution:
(1) Build SRIOV multi-queue capable guest image by recompiling the igxbevf driver (make CFLAGS_EXTRA=-DIXGBE_ENABLE_VF_MQ install).
(2) Change pktgen to send on multiple queues.
(3) Add tuning in guest VM (Disable irqbalance and setup vNIC interrupt affinity to vCPUs)
Update 1: Takes care comments plus adds unit test cases
Update 2: Jenkins reports code coverage 81%, local reports 96%, add more unit test cases
Update 3: Manually rebased to adapt to SSH.from_node()
Update 4: Takes care comments for vnic_type, vnic_name
Change-Id: Ieb15381c653b13697487d095efa4be6c3c49fa42
JIRA: YARDSTICK-619
Signed-off-by: Jing Zhang <jing.c.zhang@nokia.com>
Diffstat (limited to 'yardstick/benchmark/scenarios/networking/pktgen.py')
-rw-r--r-- | yardstick/benchmark/scenarios/networking/pktgen.py | 242 |
1 files changed, 238 insertions, 4 deletions
diff --git a/yardstick/benchmark/scenarios/networking/pktgen.py b/yardstick/benchmark/scenarios/networking/pktgen.py index e6aa7e5fb..8ca1ca60e 100644 --- a/yardstick/benchmark/scenarios/networking/pktgen.py +++ b/yardstick/benchmark/scenarios/networking/pktgen.py @@ -9,6 +9,7 @@ from __future__ import absolute_import from __future__ import print_function +import os import logging import pkg_resources @@ -19,6 +20,9 @@ from yardstick.benchmark.scenarios import base LOG = logging.getLogger(__name__) +VNIC_TYPE_LIST = ["ovs", "sriov"] +SRIOV_DRIVER_LIST = ["ixgbevf", "i40evf"] + class Pktgen(base.Scenario): """Execute pktgen between two hosts @@ -44,7 +48,11 @@ class Pktgen(base.Scenario): def __init__(self, scenario_cfg, context_cfg): self.scenario_cfg = scenario_cfg self.context_cfg = context_cfg + self.vnic_name = "eth0" + self.vnic_type = "ovs" + self.queue_number = 1 self.setup_done = False + self.multiqueue_setup_done = False def setup(self): """scenario setup""" @@ -67,6 +75,212 @@ class Pktgen(base.Scenario): self.setup_done = True + def multiqueue_setup(self): + # one time setup stuff + cmd = "sudo sysctl -w net.core.netdev_budget=3000" + self.server.send_command(cmd) + self.client.send_command(cmd) + + cmd = "sudo sysctl -w net.core.netdev_max_backlog=100000" + self.server.send_command(cmd) + self.client.send_command(cmd) + + """multiqueue setup""" + if not self._is_irqbalance_disabled(): + self._disable_irqbalance() + + vnic_driver_name = self._get_vnic_driver_name() + if vnic_driver_name in SRIOV_DRIVER_LIST: + self.vnic_type = "sriov" + + # one time setup stuff + cmd = "sudo ethtool -G %s rx 4096 tx 4096" % self.vnic_name + self.server.send_command(cmd) + self.client.send_command(cmd) + + self.queue_number = self._get_sriov_queue_number() + self._setup_irqmapping_sriov(self.queue_number) + else: + self.vnic_type = "ovs" + self.queue_number = self._enable_ovs_multiqueue() + self._setup_irqmapping_ovs(self.queue_number) + + self.multiqueue_setup_done = True + + def _get_vnic_driver_name(self): + cmd = "readlink /sys/class/net/%s/device/driver" % self.vnic_name + LOG.debug("Executing command: %s", cmd) + status, stdout, stderr = self.server.execute(cmd) + if status: + raise RuntimeError(stderr) + return os.path.basename(stdout.strip()) + + def _is_irqbalance_disabled(self): + """Did we disable irqbalance already in the guest?""" + is_disabled = False + cmd = "grep ENABLED /etc/default/irqbalance" + status, stdout, stderr = self.server.execute(cmd) + if status: + raise RuntimeError(stderr) + if "0" in stdout: + is_disabled = True + + return is_disabled + + def _disable_irqbalance(self): + cmd = "sudo sed -i -e 's/ENABLED=\"1\"/ENABLED=\"0\"/g' " \ + "/etc/default/irqbalance" + status, stdout, stderr = self.server.execute(cmd) + status, stdout, stderr = self.client.execute(cmd) + if status: + raise RuntimeError(stderr) + + cmd = "sudo service irqbalance stop" + status, stdout, stderr = self.server.execute(cmd) + status, stdout, stderr = self.client.execute(cmd) + if status: + raise RuntimeError(stderr) + + cmd = "sudo service irqbalance disable" + status, stdout, stderr = self.server.execute(cmd) + status, stdout, stderr = self.client.execute(cmd) + if status: + raise RuntimeError(stderr) + + def _setup_irqmapping_ovs(self, queue_number): + cmd = "grep 'virtio0-input.0' /proc/interrupts |" \ + "awk '{match($0,/ +[0-9]+/)} " \ + "{print substr($1,RSTART,RLENGTH-1)}'" + status, stdout, stderr = self.server.execute(cmd) + if status: + raise RuntimeError(stderr) + + cmd = "echo 1 | sudo tee /proc/irq/%s/smp_affinity" % (int(stdout)) + status, stdout, stderr = self.server.execute(cmd) + status, stdout, stderr = self.client.execute(cmd) + if status: + raise RuntimeError(stderr) + + cmd = "grep 'virtio0-output.0' /proc/interrupts |" \ + "awk '{match($0,/ +[0-9]+/)} " \ + "{print substr($1,RSTART,RLENGTH-1)}'" + status, stdout, stderr = self.server.execute(cmd) + if status: + raise RuntimeError(stderr) + + cmd = "echo 1 | sudo tee /proc/irq/%s/smp_affinity" % (int(stdout)) + status, stdout, stderr = self.server.execute(cmd) + status, stdout, stderr = self.client.execute(cmd) + if status: + raise RuntimeError(stderr) + + if queue_number == 1: + return + + for i in range(1, queue_number): + cmd = "grep 'virtio0-input.%s' /proc/interrupts |" \ + "awk '{match($0,/ +[0-9]+/)} " \ + "{print substr($1,RSTART,RLENGTH-1)}'" % (i) + status, stdout, stderr = self.server.execute(cmd) + if status: + raise RuntimeError(stderr) + + cmd = "echo %s | sudo tee /proc/irq/%s/smp_affinity" \ + % (1 << i, int(stdout)) + status, stdout, stderr = self.server.execute(cmd) + status, stdout, stderr = self.client.execute(cmd) + if status: + raise RuntimeError(stderr) + + cmd = "grep 'virtio0-output.%s' /proc/interrupts |" \ + "awk '{match($0,/ +[0-9]+/)} " \ + "{print substr($1,RSTART,RLENGTH-1)}'" % (i) + status, stdout, stderr = self.server.execute(cmd) + if status: + raise RuntimeError(stderr) + + cmd = "echo %s | sudo tee /proc/irq/%s/smp_affinity" \ + % (1 << i, int(stdout)) + status, stdout, stderr = self.server.execute(cmd) + status, stdout, stderr = self.client.execute(cmd) + if status: + raise RuntimeError(stderr) + + def _setup_irqmapping_sriov(self, queue_number): + cmd = "grep '%s-TxRx-0' /proc/interrupts |" \ + "awk '{match($0,/ +[0-9]+/)} " \ + "{print substr($1,RSTART,RLENGTH-1)}'" % self.vnic_name + status, stdout, stderr = self.server.execute(cmd) + if status: + raise RuntimeError(stderr) + + cmd = "echo 1 | sudo tee /proc/irq/%s/smp_affinity" % (int(stdout)) + status, stdout, stderr = self.server.execute(cmd) + status, stdout, stderr = self.client.execute(cmd) + if status: + raise RuntimeError(stderr) + + if queue_number == 1: + return + + for i in range(1, queue_number): + cmd = "grep '%s-TxRx-%s' /proc/interrupts |" \ + "awk '{match($0,/ +[0-9]+/)} " \ + "{print substr($1,RSTART,RLENGTH-1)}'" % (self.vnic_name, i) + status, stdout, stderr = self.server.execute(cmd) + if status: + raise RuntimeError(stderr) + + cmd = "echo %s | sudo tee /proc/irq/%s/smp_affinity" \ + % (1 << i, int(stdout)) + status, stdout, stderr = self.server.execute(cmd) + status, stdout, stderr = self.client.execute(cmd) + if status: + raise RuntimeError(stderr) + + def _get_sriov_queue_number(self): + """Get queue number from server as both VMs are the same""" + cmd = "grep %s-TxRx- /proc/interrupts | wc -l" % self.vnic_name + LOG.debug("Executing command: %s", cmd) + status, stdout, stderr = self.server.execute(cmd) + if status: + raise RuntimeError(stderr) + return int(stdout) + + def _get_available_queue_number(self): + """Get queue number from client as both VMs are the same""" + cmd = "sudo ethtool -l %s | grep Combined | head -1 |" \ + "awk '{printf $2}'" % self.vnic_name + LOG.debug("Executing command: %s", cmd) + status, stdout, stderr = self.server.execute(cmd) + if status: + raise RuntimeError(stderr) + return int(stdout) + + def _get_usable_queue_number(self): + """Get queue number from client as both VMs are the same""" + cmd = "sudo ethtool -l %s | grep Combined | tail -1 |" \ + "awk '{printf $2}'" % self.vnic_name + LOG.debug("Executing command: %s", cmd) + status, stdout, stderr = self.server.execute(cmd) + if status: + raise RuntimeError(stderr) + return int(stdout) + + def _enable_ovs_multiqueue(self): + available_queue_number = self._get_available_queue_number() + usable_queue_number = self._get_usable_queue_number() + if available_queue_number > 1 and \ + available_queue_number != usable_queue_number: + cmd = "sudo ethtool -L %s combined %s" % \ + (self.vnic_name, available_queue_number) + LOG.debug("Executing command: %s", cmd) + status, stdout, stderr = self.server.execute(cmd) + status, stdout, stderr = self.client.execute(cmd) + if status: + raise RuntimeError(stderr) + return available_queue_number + def _iptables_setup(self): """Setup iptables on server to monitor for received packets""" cmd = "sudo iptables -F; " \ @@ -99,6 +313,14 @@ class Pktgen(base.Scenario): options = self.scenario_cfg['options'] packetsize = options.get("packetsize", 60) self.number_of_ports = options.get("number_of_ports", 10) + self.vnic_name = options.get("vnic_name", "eth0") + ovs_dpdk = options.get("ovs_dpdk", False) + pps = options.get("pps", 1000000) + multiqueue = options.get("multiqueue", False) + + if multiqueue and not self.multiqueue_setup_done: + self.multiqueue_setup() + # if run by a duration runner duration_time = self.scenario_cfg["runner"].get("duration", None) \ if "runner" in self.scenario_cfg else None @@ -114,8 +336,18 @@ class Pktgen(base.Scenario): self._iptables_setup() - cmd = "sudo bash pktgen.sh %s %s %s %s" \ - % (ipaddr, self.number_of_ports, packetsize, duration) + queue_number = self.queue_number + + # For native OVS, half of vCPUs are used by vhost kernel threads + # hence set the queue_number to half number of vCPUs + # e.g. set queue_number to 2 if there are 4 vCPUs + if self.vnic_type == "ovs" and not ovs_dpdk and self.queue_number > 1: + queue_number = self.queue_number / 2 + + cmd = "sudo bash pktgen.sh %s %s %s %s %s %s" \ + % (ipaddr, self.number_of_ports, packetsize, + duration, queue_number, pps) + LOG.debug("Executing command: %s", cmd) status, stdout, stderr = self.client.execute(cmd) @@ -131,12 +363,15 @@ class Pktgen(base.Scenario): sent = result['packets_sent'] received = result['packets_received'] ppm = 1000000 * (sent - received) / sent + # if ppm is 1, then 11 out of 10 million is no pass + ppm += (sent - received) % sent > 0 + LOG.debug("Lost packets %d - Lost ppm %d", (sent - received), ppm) sla_max_ppm = int(self.scenario_cfg["sla"]["max_ppm"]) assert ppm <= sla_max_ppm, "ppm %d > sla_max_ppm %d; " \ % (ppm, sla_max_ppm) -def _test(): +def _test(): # pragma: no cover """internal test function""" key_filename = pkg_resources.resource_filename('yardstick.resources', 'files/yardstick_key') @@ -165,6 +400,5 @@ def _test(): p.run(result) print(result) - if __name__ == '__main__': _test() |