1 # Copyright (c) 2019 Cisco and/or its affiliates.
2 # Licensed under the Apache License, Version 2.0 (the "License");
3 # you may not use this file except in compliance with the License.
4 # You may obtain a copy of the License at:
6 # http://www.apache.org/licenses/LICENSE-2.0
8 # Unless required by applicable law or agreed to in writing, software
9 # distributed under the License is distributed on an "AS IS" BASIS,
10 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11 # See the License for the specific language governing permissions and
12 # limitations under the License.
14 """wrk implementation into CSIT framework.
19 from copy import deepcopy
20 from time import sleep
22 from robot.api import logger
24 from resources.libraries.python.ssh import SSH
25 from resources.libraries.python.topology import NodeType
26 from resources.libraries.python.CpuUtils import CpuUtils
27 from resources.libraries.python.Constants import Constants
29 from resources.tools.wrk.wrk_traffic_profile_parser import WrkTrafficProfile
30 from resources.tools.wrk.wrk_errors import WrkError
33 REGEX_LATENCY_STATS = \
35 r"(\d*\.*\d*\S*)\s*" \
36 r"(\d*\.*\d*\S*)\s*" \
37 r"(\d*\.*\d*\S*)\s*" \
41 r"(\d*\.*\d*\S*)\s*" \
42 r"(\d*\.*\d*\S*)\s*" \
43 r"(\d*\.*\d*\S*)\s*" \
45 REGEX_RPS = r"Requests/sec:\s*" \
47 REGEX_BW = r"Transfer/sec:\s*" \
49 REGEX_LATENCY_DIST = \
50 r"Latency Distribution\n" \
51 r"\s*50\%\s*(\d*\.*\d*\D*)\n" \
52 r"\s*75\%\s*(\d*\.*\d*\D*)\n" \
53 r"\s*90\%\s*(\d*\.*\d*\D*)\n" \
54 r"\s*99\%\s*(\d*\.*\d*\D*)\n"
56 # Split number and multiplicand, e.g. 14.25k --> 14.25 and k
57 REGEX_NUM = r"(\d*\.*\d*)(\D*)"
60 def check_wrk(tg_node):
61 """Check if wrk is installed on the TG node.
63 :param tg_node: Traffic generator node.
65 :raises: RuntimeError if the given node is not a TG node or if the
66 command is not availble.
69 if tg_node['type'] != NodeType.TG:
70 raise RuntimeError('Node type is not a TG.')
75 ret, _, _ = ssh.exec_command(
77 "sh -c '{0}/resources/tools/wrk/wrk_utils.sh installed'".
78 format(Constants.REMOTE_FW_DIR))
80 raise RuntimeError('WRK is not installed on TG node.')
83 def run_wrk(tg_node, profile_name, tg_numa, test_type, warm_up=False):
84 """Send the traffic as defined in the profile.
86 :param tg_node: Traffic generator node.
87 :param profile_name: The name of wrk traffic profile.
88 :param tg_numa: Numa node on which wrk will run.
89 :param test_type: The type of the tests: cps, rps, bw
90 :param warm_up: If True, warm-up traffic is generated before test traffic.
91 :type profile_name: str
96 :returns: Message with measured data.
98 :raises: RuntimeError if node type is not a TG.
101 if tg_node['type'] != NodeType.TG:
102 raise RuntimeError('Node type is not a TG.')
104 # Parse and validate the profile
105 profile_path = ("resources/traffic_profiles/wrk/{0}.yaml".
106 format(profile_name))
107 profile = WrkTrafficProfile(profile_path).traffic_profile
109 cores = CpuUtils.cpu_list_per_node(tg_node, tg_numa)
110 first_cpu = cores[profile["first-cpu"]]
112 if len(profile["urls"]) == 1 and profile["cpus"] == 1:
114 "traffic_1_url_1_core",
116 str(profile["nr-of-threads"]),
117 str(profile["nr-of-connections"]),
118 "{0}s".format(profile["duration"]),
119 "'{0}'".format(profile["header"]),
120 str(profile["timeout"]),
121 str(profile["script"]),
122 str(profile["latency"]),
123 "'{0}'".format(" ".join(profile["urls"]))
126 warm_up_params = deepcopy(params)
127 warm_up_params[4] = "10s"
128 elif len(profile["urls"]) == profile["cpus"]:
130 "traffic_n_urls_n_cores",
132 str(profile["nr-of-threads"]),
133 str(profile["nr-of-connections"]),
134 "{0}s".format(profile["duration"]),
135 "'{0}'".format(profile["header"]),
136 str(profile["timeout"]),
137 str(profile["script"]),
138 str(profile["latency"]),
139 "'{0}'".format(" ".join(profile["urls"]))
142 warm_up_params = deepcopy(params)
143 warm_up_params[4] = "10s"
146 "traffic_n_urls_m_cores",
148 str(profile["cpus"] / len(profile["urls"])),
149 str(profile["nr-of-threads"]),
150 str(profile["nr-of-connections"]),
151 "{0}s".format(profile["duration"]),
152 "'{0}'".format(profile["header"]),
153 str(profile["timeout"]),
154 str(profile["script"]),
155 str(profile["latency"]),
156 "'{0}'".format(" ".join(profile["urls"]))
159 warm_up_params = deepcopy(params)
160 warm_up_params[5] = "10s"
162 args = " ".join(params)
168 warm_up_args = " ".join(warm_up_params)
169 ret, _, _ = ssh.exec_command(
170 "{0}/resources/tools/wrk/wrk_utils.sh {1}".
171 format(Constants.REMOTE_FW_DIR, warm_up_args), timeout=1800)
173 raise RuntimeError('wrk runtime error.')
176 ret, stdout, _ = ssh.exec_command(
177 "{0}/resources/tools/wrk/wrk_utils.sh {1}".
178 format(Constants.REMOTE_FW_DIR, args), timeout=1800)
180 raise RuntimeError('wrk runtime error.')
182 stats = _parse_wrk_output(stdout)
184 log_msg = "\nMeasured values:\n"
185 if test_type == "cps":
186 log_msg += "Connections/sec: Avg / Stdev / Max / +/- Stdev\n"
187 for item in stats["rps-stats-lst"]:
188 log_msg += "{0} / {1} / {2} / {3}\n".format(*item)
189 log_msg += "Total cps: {0}cps\n".format(stats["rps-sum"])
190 elif test_type == "rps":
191 log_msg += "Requests/sec: Avg / Stdev / Max / +/- Stdev\n"
192 for item in stats["rps-stats-lst"]:
193 log_msg += "{0} / {1} / {2} / {3}\n".format(*item)
194 log_msg += "Total rps: {0}rps\n".format(stats["rps-sum"])
195 elif test_type == "bw":
196 log_msg += "Transfer/sec: {0}Bps".format(stats["bw-sum"])
203 def _parse_wrk_output(msg):
204 """Parse the wrk stdout with the results.
206 :param msg: stdout of wrk.
208 :returns: Parsed results.
210 :raises: WrkError if the message does not include the results.
213 if "Thread Stats" not in msg:
214 raise WrkError("The output of wrk does not include the results.")
216 msg_lst = msg.splitlines(False)
219 "latency-dist-lst": list(),
220 "latency-stats-lst": list(),
221 "rps-stats-lst": list(),
229 if "Latency Distribution" in line:
230 # Latency distribution - 50%, 75%, 90%, 99%
232 elif "Latency" in line:
233 # Latency statistics - Avg, Stdev, Max, +/- Stdev
235 elif "Req/Sec" in line:
236 # rps statistics - Avg, Stdev, Max, +/- Stdev
237 stats["rps-stats-lst"].append((
238 _evaluate_number(re.search(REGEX_RPS_STATS, line).group(1)),
239 _evaluate_number(re.search(REGEX_RPS_STATS, line).group(2)),
240 _evaluate_number(re.search(REGEX_RPS_STATS, line).group(3)),
241 _evaluate_number(re.search(REGEX_RPS_STATS, line).group(4))))
242 elif "Requests/sec:" in line:
244 stats["rps-lst"].append(
245 _evaluate_number(re.search(REGEX_RPS, line).group(1)))
246 elif "Transfer/sec:" in line:
248 stats["bw-lst"].append(
249 _evaluate_number(re.search(REGEX_BW, line).group(1)))
251 for item in stats["rps-stats-lst"]:
252 stats["rps-sum"] += item[0]
253 stats["bw-sum"] = sum(stats["bw-lst"])
258 def _evaluate_number(num):
259 """Evaluate the numeric value of the number with multiplicands, e.g.:
262 :param num: Number to evaluate.
264 :returns: Evaluated number.
266 :raises: WrkError if it is not possible to evaluate the given number.
269 val = re.search(REGEX_NUM, num)
271 val_num = float(val.group(1))
273 raise WrkError("The output of wrk does not include the results "
274 "or the format of results has changed.")
275 val_mul = val.group(2).lower()
282 val_num *= 1000000000
290 raise WrkError("The multiplicand {0} is not defined.".