Merge "Bugfix: task_id parameter from API can not pass to yardstick core"
[yardstick.git] / yardstick / benchmark / scenarios / networking / netutilization.py
1 ##############################################################################
2 # Copyright (c) 2016 Huawei Technologies Co.,Ltd and other.
3 #
4 # All rights reserved. This program and the accompanying materials
5 # are made available under the terms of the Apache License, Version 2.0
6 # which accompanies this distribution, and is available at
7 # http://www.apache.org/licenses/LICENSE-2.0
8 ##############################################################################
9 from __future__ import absolute_import
10 import logging
11 import re
12
13 import yardstick.ssh as ssh
14 from yardstick.benchmark.scenarios import base
15 from six.moves import zip
16
17 LOG = logging.getLogger(__name__)
18
19
20 class NetUtilization(base.Scenario):
21     """Collect network utilization statistics.
22
23     This scenario reads statistics from the network devices on a Linux host.
24     Network utilization statistics are read using the utility 'sar'.
25
26     The following values are displayed:
27
28     IFACE: Name of the network interface for which statistics are reported.
29
30     rxpck/s: Total number of packets received per second.
31
32     txpck/s: Total number of packets transmitted per second.
33
34     rxkB/s: Total number of kilobytes received per second.
35
36     txkB/s: Total number of kilobytes transmitted per second.
37
38     rxcmp/s: Number of compressed packets received per second (for cslip etc.).
39
40     txcmp/s: Number of compressed packets transmitted per second.
41
42     rxmcst/s: Number of multicast packets received per second.
43
44     %ifutil: Utilization percentage of the network interface. For half-duplex
45     interfaces, utilization is calculated using the sum of rxkB/s and txkB/s
46     as a percentage of the interface speed. For full-duplex, this is the
47     greater  of rxkB/S or txkB/s.
48
49     Parameters
50         interval - Time interval to measure network utilization.
51             type:       [int]
52             unit:       seconds
53             default:    1
54
55         count - Number of times to measure network utilization.
56             type:       [int]
57             unit:       N/A
58             default:    1
59     """
60
61     __scenario_type__ = "NetUtilization"
62
63     NET_UTILIZATION_FIELD_SIZE = 8
64
65     def __init__(self, scenario_cfg, context_cfg):
66         """Scenario construction."""
67         self.scenario_cfg = scenario_cfg
68         self.context_cfg = context_cfg
69         self.setup_done = False
70
71     def setup(self):
72         """Scenario setup."""
73         host = self.context_cfg['host']
74         user = host.get('user', 'ubuntu')
75         ssh_port = host.get("ssh_port", ssh.DEFAULT_PORT)
76         ip = host.get('ip', None)
77         key_filename = host.get('key_filename', '~/.ssh/id_rsa')
78
79         LOG.info("user:%s, host:%s", user, ip)
80         self.client = ssh.SSH(user, ip, key_filename=key_filename,
81                               port=ssh_port)
82         self.client.wait(timeout=600)
83
84         self.setup_done = True
85
86     def _execute_command(self, cmd):
87         """Execute a command on target."""
88         LOG.info("Executing: %s", cmd)
89         status, stdout, stderr = self.client.execute(cmd)
90         if status:
91             raise RuntimeError("Failed executing command: ",
92                                cmd, stderr)
93         return stdout
94
95     def _filtrate_result(self, raw_result):
96         """Filtrate network utilization statistics."""
97         fields = []
98         maximum = {}
99         minimum = {}
100         average = {}
101
102         time_marker = re.compile("^([0-9]+):([0-9]+):([0-9]+)$")
103         ampm_marker = re.compile("(AM|PM)$")
104
105         # Parse network utilization stats
106         for row in raw_result.split('\n'):
107             line = row.split()
108
109             if line and re.match(time_marker, line[0]):
110                 if re.match(ampm_marker, line[1]):
111                     del line[:2]
112
113                 if line[0] == 'IFACE':
114                     # header fields
115                     fields = line[1:]
116                     if len(fields) != NetUtilization.\
117                             NET_UTILIZATION_FIELD_SIZE:
118                         raise RuntimeError("network_utilization: unexpected\
119                                            field size", fields)
120                 else:
121                     # value fields
122                     net_interface = line[0]
123                     values = line[1:]
124
125                     if values and len(values) == len(fields):
126                         temp_dict = dict(list(zip(fields, values)))
127                         if net_interface not in maximum:
128                             maximum[net_interface] = temp_dict
129                         else:
130                             for item in temp_dict:
131                                 if float(maximum[net_interface][item]) <\
132                                    float(temp_dict[item]):
133                                     maximum[net_interface][item] = \
134                                         temp_dict[item]
135
136                         if net_interface not in minimum:
137                             minimum[net_interface] = temp_dict
138                         else:
139                             for item in temp_dict:
140                                 if float(minimum[net_interface][item]) >\
141                                    float(temp_dict[item]):
142                                     minimum[net_interface][item] = \
143                                         temp_dict[item]
144                     else:
145                         raise RuntimeError("network_utilization: parse error",
146                                            fields, line)
147
148             elif line and line[0] == 'Average:':
149                 del line[:1]
150
151                 if line[0] == 'IFACE':
152                     # header fields
153                     fields = line[1:]
154                     if len(fields) != NetUtilization.\
155                             NET_UTILIZATION_FIELD_SIZE:
156                         raise RuntimeError("network_utilization average: \
157                                            unexpected field size", fields)
158                 else:
159                     # value fields
160                     net_interface = line[0]
161                     values = line[1:]
162                     if values and len(values) == len(fields):
163                         average[net_interface] = dict(
164                             list(zip(fields, values)))
165                     else:
166                         raise RuntimeError("network_utilization average: \
167                                            parse error", fields, line)
168
169         return {'network_utilization_maximun': maximum,
170                 'network_utilization_minimum': minimum,
171                 'network_utilization_average': average}
172
173     def _get_network_utilization(self):
174         """Get network utilization statistics using sar."""
175         options = self.scenario_cfg["options"]
176         interval = options.get('interval', 1)
177         count = options.get('count', 1)
178
179         cmd = "sudo sar -n DEV %d %d" % (interval, count)
180
181         raw_result = self._execute_command(cmd)
182         result = self._filtrate_result(raw_result)
183
184         return result
185
186     def run(self, result):
187         """Read statistics."""
188         if not self.setup_done:
189             self.setup()
190
191         result.update(self._get_network_utilization())