Add Network Utilization Scenario 09/17109/2
authorJingLu5 <lvjing5@huawei.com>
Tue, 19 Jul 2016 04:15:20 +0000 (12:15 +0800)
committerJingLu5 <lvjing5@huawei.com>
Tue, 19 Jul 2016 09:21:50 +0000 (17:21 +0800)
This scenario reads network interface utilization stats and data sent/receive
rate using "sar -n".

Change-Id: I9c69f03c017bc2f8a5d87a4de286af147e8a086a
Signed-off-by: JingLu5 <lvjing5@huawei.com>
samples/netutilization.yaml [new file with mode: 0644]
tests/unit/benchmark/scenarios/networking/netutilization_sample_output1.txt [new file with mode: 0644]
tests/unit/benchmark/scenarios/networking/netutilization_sample_output2.txt [new file with mode: 0644]
tests/unit/benchmark/scenarios/networking/test_netutilization.py [new file with mode: 0644]
yardstick/benchmark/scenarios/networking/netutilization.py [new file with mode: 0644]

diff --git a/samples/netutilization.yaml b/samples/netutilization.yaml
new file mode 100644 (file)
index 0000000..598a5af
--- /dev/null
@@ -0,0 +1,32 @@
+---
+# Sample benchmark task config file
+# Reading network interface utilization statistics
+
+schema: "yardstick:task:0.1"
+
+scenarios:
+-
+  type: NetUtilization
+  options:
+    interval: 1
+    count: 1
+
+  host: apollo.demo
+
+  runner:
+    type: Iteration
+    interval: 1
+
+context:
+  name: demo
+  image: yardstick-trusty-server
+  flavor: yardstick-flavor
+  user: ubuntu
+
+  servers:
+    apollo:
+      floating_ip: true
+
+  networks:
+    test:
+      cidr: '10.0.1.0/24'
diff --git a/tests/unit/benchmark/scenarios/networking/netutilization_sample_output1.txt b/tests/unit/benchmark/scenarios/networking/netutilization_sample_output1.txt
new file mode 100644 (file)
index 0000000..f90457c
--- /dev/null
@@ -0,0 +1,9 @@
+Linux 3.19.0-25-generic (huawei-pod4)  07/19/2016      _x86_64_        (1 CPU)
+
+02:01:50 PM     IFACE   rxpck/s   txpck/s    rxkB/s    txkB/s   rxcmp/s   txcmp/s  rxmcst/s   %ifutil
+02:01:51 PM      eth0      0.00      0.00      0.00      0.00      0.00      0.00      0.00      0.00
+02:01:51 PM        lo      0.00      0.00      0.00      0.00      0.00      0.00      0.00      0.00
+
+Average:        IFACE   rxpck/s   txpck/s    rxkB/s    txkB/s   rxcmp/s   txcmp/s  rxmcst/s   %ifutil
+Average:         eth0      0.00      0.00      0.00      0.00      0.00      0.00      0.00      0.00
+Average:           lo      0.00      0.00      0.00      0.00      0.00      0.00      0.00      0.00
diff --git a/tests/unit/benchmark/scenarios/networking/netutilization_sample_output2.txt b/tests/unit/benchmark/scenarios/networking/netutilization_sample_output2.txt
new file mode 100644 (file)
index 0000000..417613e
--- /dev/null
@@ -0,0 +1,13 @@
+Linux 3.19.0-25-generic (huawei-pod4)  07/19/2016      _x86_64_        (1 CPU)
+
+02:01:50 PM     IFACE   rxpck/s   txpck/s    rxkB/s    txkB/s   rxcmp/s   txcmp/s  rxmcst/s   %ifutil
+02:01:51 PM      eth0      0.00      0.00      0.00      0.00      0.00      0.00      0.00      0.00
+02:01:51 PM        lo      0.00      0.00      0.00      0.00      0.00      0.00      0.00      0.00
+
+02:01:52 PM     IFACE   rxpck/s   txpck/s    rxkB/s    txkB/s   rxcmp/s   txcmp/s  rxmcst/s   %ifutil
+02:01:53 PM      eth0      0.00      0.00      0.00      0.00      0.00      0.00      0.00      0.00
+02:01:53 PM        lo      0.00      0.00      0.00      0.00      0.00      0.00      0.00      0.00
+
+Average:        IFACE   rxpck/s   txpck/s    rxkB/s    txkB/s   rxcmp/s   txcmp/s  rxmcst/s   %ifutil
+Average:         eth0      0.00      0.00      0.00      0.00      0.00      0.00      0.00      0.00
+Average:           lo      0.00      0.00      0.00      0.00      0.00      0.00      0.00      0.00
diff --git a/tests/unit/benchmark/scenarios/networking/test_netutilization.py b/tests/unit/benchmark/scenarios/networking/test_netutilization.py
new file mode 100644 (file)
index 0000000..eb6626f
--- /dev/null
@@ -0,0 +1,225 @@
+#!/usr/bin/env python
+
+##############################################################################
+# Copyright (c) 2016 Huawei Technologies Co.,Ltd and other.
+#
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Apache License, Version 2.0
+# which accompanies this distribution, and is available at
+# http://www.apache.org/licenses/LICENSE-2.0
+##############################################################################
+
+# Unittest for yardstick.benchmark.scenarios.networking.netutilization.NetUtilization
+
+import mock
+import unittest
+import os
+
+from yardstick.benchmark.scenarios.networking import netutilization
+
+
+@mock.patch('yardstick.benchmark.scenarios.networking.netutilization.ssh')
+class NetUtilizationTestCase(unittest.TestCase):
+
+    def setUp(self):
+        self.ctx = {
+            'host': {
+                'ip': '172.16.0.137',
+                'user': 'cirros',
+                'key_filename': "mykey.key"
+            }
+        }
+
+        self.result = {}
+
+    def test_setup_success(self, mock_ssh):
+        options = {
+            "interval": 1,
+            "count": 1
+        }
+        args = {'options': options}
+
+        n = netutilization.NetUtilization(args, self.ctx)
+        mock_ssh.SSH().execute.return_value = (0, '', '')
+
+        n.setup()
+        self.assertIsNotNone(n.client)
+        self.assertTrue(n.setup_done)
+
+    def test_execute_command_success(self, mock_ssh):
+        options = {
+            "interval": 1,
+            "count": 1
+        }
+        args = {'options': options}
+
+        n = netutilization.NetUtilization(args, self.ctx)
+        mock_ssh.SSH().execute.return_value = (0, '', '')
+        n.setup()
+
+        expected_result = 'abcdefg'
+        mock_ssh.SSH().execute.return_value = (0, expected_result, '')
+        result = n._execute_command("foo")
+        self.assertEqual(result, expected_result)
+
+    def test_execute_command_failed(self, mock_ssh):
+        options = {
+            "interval": 1,
+            "count": 1
+        }
+        args = {'options': options}
+
+        n = netutilization.NetUtilization(args, self.ctx)
+        mock_ssh.SSH().execute.return_value = (0, '', '')
+        n.setup()
+
+        mock_ssh.SSH().execute.return_value = (127, '', 'abcdefg')
+        self.assertRaises(RuntimeError, n._execute_command,
+                          "failed")
+
+    def test_get_network_utilization_success(self, mock_ssh):
+        options = {
+            "interval": 1,
+            "count": 1
+        }
+        args = {'options': options}
+
+        n = netutilization.NetUtilization(args, self.ctx)
+        mock_ssh.SSH().execute.return_value = (0, '', '')
+        n.setup()
+
+        mpstat_output = self._read_file("netutilization_sample_output1.txt")
+        mock_ssh.SSH().execute.return_value = (0, mpstat_output, '')
+        result = n._get_network_utilization()
+
+        expected_result = \
+            {"network_utilization_maximun": {
+                "lo": {"rxcmp/s": "0.00",
+                       "%ifutil": "0.00",
+                       "txcmp/s": "0.00",
+                       "txkB/s": "0.00",
+                       "rxkB/s": "0.00",
+                       "rxpck/s": "0.00",
+                       "txpck/s": "0.00",
+                       "rxmcst/s": "0.00"},
+                "eth0": {"rxcmp/s": "0.00",
+                         "%ifutil": "0.00",
+                         "txcmp/s": "0.00",
+                         "txkB/s": "0.00",
+                         "rxkB/s": "0.00",
+                         "rxpck/s": "0.00",
+                         "txpck/s": "0.00",
+                         "rxmcst/s": "0.00"}},
+             "network_utilization_average": {
+                "lo": {"rxcmp/s": "0.00",
+                       "%ifutil": "0.00",
+                       "txcmp/s": "0.00",
+                       "txkB/s": "0.00",
+                       "rxkB/s": "0.00",
+                       "rxpck/s": "0.00",
+                       "txpck/s": "0.00",
+                       "rxmcst/s": "0.00"},
+                "eth0": {"rxcmp/s": "0.00",
+                         "%ifutil": "0.00",
+                         "txcmp/s": "0.00",
+                         "txkB/s": "0.00",
+                         "rxkB/s": "0.00",
+                         "rxpck/s": "0.00",
+                         "txpck/s": "0.00",
+                         "rxmcst/s": "0.00"}},
+             "network_utilization_minimum": {
+                "lo": {"rxcmp/s": "0.00",
+                       "%ifutil": "0.00",
+                       "txcmp/s": "0.00",
+                       "txkB/s": "0.00",
+                       "rxkB/s": "0.00",
+                       "rxpck/s": "0.00",
+                       "txpck/s": "0.00",
+                       "rxmcst/s": "0.00"},
+                "eth0": {"rxcmp/s": "0.00",
+                         "%ifutil": "0.00",
+                         "txcmp/s": "0.00",
+                         "txkB/s": "0.00",
+                         "rxkB/s": "0.00",
+                         "rxpck/s": "0.00",
+                         "txpck/s": "0.00",
+                         "rxmcst/s": "0.00"}}}
+
+        self.assertDictEqual(result, expected_result)
+
+    def test_get_network_utilization_2_success(self, mock_ssh):
+        options = {
+            "interval": 1,
+            "count": 2
+        }
+        args = {'options': options}
+
+        n = netutilization.NetUtilization(args, self.ctx)
+        mock_ssh.SSH().execute.return_value = (0, '', '')
+        n.setup()
+
+        mpstat_output = self._read_file("netutilization_sample_output2.txt")
+        mock_ssh.SSH().execute.return_value = (0, mpstat_output, '')
+        result = n._get_network_utilization()
+
+        expected_result = \
+            {"network_utilization_maximun": {
+                "lo": {"rxcmp/s": "0.00",
+                       "%ifutil": "0.00",
+                       "txcmp/s": "0.00",
+                       "txkB/s": "0.00",
+                       "rxkB/s": "0.00",
+                       "rxpck/s": "0.00",
+                       "txpck/s": "0.00",
+                       "rxmcst/s": "0.00"},
+                "eth0": {"rxcmp/s": "0.00",
+                         "%ifutil": "0.00",
+                         "txcmp/s": "0.00",
+                         "txkB/s": "0.00",
+                         "rxkB/s": "0.00",
+                         "rxpck/s": "0.00",
+                         "txpck/s": "0.00",
+                         "rxmcst/s": "0.00"}},
+             "network_utilization_average": {
+                "lo": {"rxcmp/s": "0.00",
+                       "%ifutil": "0.00",
+                       "txcmp/s": "0.00",
+                       "txkB/s": "0.00",
+                       "rxkB/s": "0.00",
+                       "rxpck/s": "0.00",
+                       "txpck/s": "0.00",
+                       "rxmcst/s": "0.00"},
+                "eth0": {"rxcmp/s": "0.00",
+                         "%ifutil": "0.00",
+                         "txcmp/s": "0.00",
+                         "txkB/s": "0.00",
+                         "rxkB/s": "0.00",
+                         "rxpck/s": "0.00",
+                         "txpck/s": "0.00",
+                         "rxmcst/s": "0.00"}},
+             "network_utilization_minimum": {
+                "lo": {"rxcmp/s": "0.00",
+                       "%ifutil": "0.00",
+                       "txcmp/s": "0.00",
+                       "txkB/s": "0.00",
+                       "rxkB/s": "0.00",
+                       "rxpck/s": "0.00",
+                       "txpck/s": "0.00",
+                       "rxmcst/s": "0.00"},
+                "eth0": {"rxcmp/s": "0.00",
+                         "%ifutil": "0.00",
+                         "txcmp/s": "0.00",
+                         "txkB/s": "0.00",
+                         "rxkB/s": "0.00",
+                         "rxpck/s": "0.00",
+                         "txpck/s": "0.00",
+                         "rxmcst/s": "0.00"}}}
+
+        self.assertDictEqual(result, expected_result)
+
+    def _read_file(self, filename):
+        curr_path = os.path.dirname(os.path.abspath(__file__))
+        output = os.path.join(curr_path, filename)
+        with open(output) as f:
+            sample_output = f.read()
+        return sample_output
diff --git a/yardstick/benchmark/scenarios/networking/netutilization.py b/yardstick/benchmark/scenarios/networking/netutilization.py
new file mode 100644 (file)
index 0000000..ea43e60
--- /dev/null
@@ -0,0 +1,186 @@
+##############################################################################
+# Copyright (c) 2016 Huawei Technologies Co.,Ltd and other.
+#
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Apache License, Version 2.0
+# which accompanies this distribution, and is available at
+# http://www.apache.org/licenses/LICENSE-2.0
+##############################################################################
+import logging
+import re
+
+import yardstick.ssh as ssh
+from yardstick.benchmark.scenarios import base
+
+LOG = logging.getLogger(__name__)
+
+
+class NetUtilization(base.Scenario):
+    """Collect network utilization statistics.
+
+    This scenario reads statistics from the network devices on a Linux host.
+    Network utilization statistics are read using the utility 'sar'.
+
+    The following values are displayed:
+
+    IFACE: Name of the network interface for which statistics are reported.
+
+    rxpck/s: Total number of packets received per second.
+
+    txpck/s: Total number of packets transmitted per second.
+
+    rxkB/s: Total number of kilobytes received per second.
+
+    txkB/s: Total number of kilobytes transmitted per second.
+
+    rxcmp/s: Number of compressed packets received per second (for cslip etc.).
+
+    txcmp/s: Number of compressed packets transmitted per second.
+
+    rxmcst/s: Number of multicast packets received per second.
+
+    %ifutil: Utilization percentage of the network interface. For half-duplex
+    interfaces, utilization is calculated using the sum of rxkB/s and txkB/s
+    as a percentage of the interface speed. For full-duplex, this is the
+    greater  of rxkB/S or txkB/s.
+
+    Parameters
+        interval - Time interval to measure network utilization.
+            type:       [int]
+            unit:       seconds
+            default:    1
+
+        count - Number of times to measure network utilization.
+            type:       [int]
+            unit:       N/A
+            default:    1
+    """
+
+    __scenario_type__ = "NetUtilization"
+
+    NET_UTILIZATION_FIELD_SIZE = 8
+
+    def __init__(self, scenario_cfg, context_cfg):
+        """Scenario construction."""
+        self.scenario_cfg = scenario_cfg
+        self.context_cfg = context_cfg
+        self.setup_done = False
+
+    def setup(self):
+        """Scenario setup."""
+        host = self.context_cfg['host']
+        user = host.get('user', 'ubuntu')
+        ip = host.get('ip', None)
+        key_filename = host.get('key_filename', '~/.ssh/id_rsa')
+
+        LOG.info("user:%s, host:%s", user, ip)
+        self.client = ssh.SSH(user, ip, key_filename=key_filename)
+        self.client.wait(timeout=600)
+
+        self.setup_done = True
+
+    def _execute_command(self, cmd):
+        """Execute a command on target."""
+        LOG.info("Executing: %s" % cmd)
+        status, stdout, stderr = self.client.execute(cmd)
+        if status:
+            raise RuntimeError("Failed executing command: ",
+                               cmd, stderr)
+        return stdout
+
+    def _filtrate_result(self, raw_result):
+        """Filtrate network utilization statistics."""
+        fields = []
+        maximum = {}
+        minimum = {}
+        average = {}
+
+        time_marker = re.compile("^([0-9]+):([0-9]+):([0-9]+)$")
+        ampm_marker = re.compile("(AM|PM)$")
+
+        # Parse network utilization stats
+        for row in raw_result.split('\n'):
+            line = row.split()
+
+            if line and re.match(time_marker, line[0]):
+                if re.match(ampm_marker, line[1]):
+                    del line[:2]
+
+                if line[0] == 'IFACE':
+                    # header fields
+                    fields = line[1:]
+                    if len(fields) != NetUtilization.\
+                            NET_UTILIZATION_FIELD_SIZE:
+                        raise RuntimeError("network_utilization: unexpected\
+                                           field size", fields)
+                else:
+                    # value fields
+                    net_interface = line[0]
+                    values = line[1:]
+
+                    if values and len(values) == len(fields):
+                        temp_dict = dict(zip(fields, values))
+                        if net_interface not in maximum:
+                            maximum[net_interface] = temp_dict
+                        else:
+                            for item in temp_dict:
+                                if float(maximum[net_interface][item]) <\
+                                   float(temp_dict[item]):
+                                    maximum[net_interface][item] = \
+                                        temp_dict[item]
+
+                        if net_interface not in minimum:
+                            minimum[net_interface] = temp_dict
+                        else:
+                            for item in temp_dict:
+                                if float(minimum[net_interface][item]) >\
+                                   float(temp_dict[item]):
+                                    minimum[net_interface][item] = \
+                                        temp_dict[item]
+                    else:
+                        raise RuntimeError("network_utilization: parse error",
+                                           fields, line)
+
+            elif line and line[0] == 'Average:':
+                del line[:1]
+
+                if line[0] == 'IFACE':
+                    # header fields
+                    fields = line[1:]
+                    if len(fields) != NetUtilization.\
+                            NET_UTILIZATION_FIELD_SIZE:
+                        raise RuntimeError("network_utilization average: \
+                                           unexpected field size", fields)
+                else:
+                    # value fields
+                    net_interface = line[0]
+                    values = line[1:]
+                    if values and len(values) == len(fields):
+                        average[net_interface] = dict(zip(fields, values))
+                    else:
+                        raise RuntimeError("network_utilization average: \
+                                           parse error", fields, line)
+
+        return {'network_utilization_maximun': maximum,
+                'network_utilization_minimum': minimum,
+                'network_utilization_average': average}
+
+    def _get_network_utilization(self):
+        """Get network utilization statistics using sar."""
+        options = self.scenario_cfg["options"]
+        interval = options.get('interval', 1)
+        count = options.get('count', 1)
+
+        cmd = "sudo sar -n DEV %d %d" % (interval, count)
+
+        raw_result = self._execute_command(cmd)
+        result = self._filtrate_result(raw_result)
+
+        return result
+
+    def run(self, result):
+        """Read statistics."""
+        if not self.setup_done:
+            self.setup()
+
+        result.update(self._get_network_utilization())