diff options
Diffstat (limited to 'recipes-test/virt-test/files/qemu-tests-cyclictest.patch')
-rw-r--r-- | recipes-test/virt-test/files/qemu-tests-cyclictest.patch | 217 |
1 files changed, 217 insertions, 0 deletions
diff --git a/recipes-test/virt-test/files/qemu-tests-cyclictest.patch b/recipes-test/virt-test/files/qemu-tests-cyclictest.patch new file mode 100644 index 0000000..f9ee831 --- /dev/null +++ b/recipes-test/virt-test/files/qemu-tests-cyclictest.patch | |||
@@ -0,0 +1,217 @@ | |||
1 | commit 61c087a0386ad04fd5579ad170b9d7933ed90c8c | ||
2 | Author: Jonas Eriksson <jonas.eriksson@enea.com> | ||
3 | Date: Fri Apr 25 16:00:12 2014 +0200 | ||
4 | |||
5 | Add cyclictest test case | ||
6 | |||
7 | Cyclictest is a part of rt-tests used to test the responsiveness of a | ||
8 | system. While not developed for non-realtime systems, it can be used to | ||
9 | detect unexpected spikes in latencies within virtual machines. | ||
10 | |||
11 | Signed-off-by: Jonas Eriksson <jonas.eriksson@enea.com> | ||
12 | Upstream-Status: Pending | ||
13 | |||
14 | diff --git a/qemu/tests/cfg/cyclictest.cfg b/qemu/tests/cfg/cyclictest.cfg | ||
15 | new file mode 100644 | ||
16 | index 0000000..d142b7d | ||
17 | --- /dev/null | ||
18 | +++ b/qemu/tests/cfg/cyclictest.cfg | ||
19 | @@ -0,0 +1,25 @@ | ||
20 | +- cyclictest: | ||
21 | + virt_test_type = qemu | ||
22 | + only Linux | ||
23 | + type = cyclictest | ||
24 | + kill_vm = yes | ||
25 | + kill_vm_gracefully = no | ||
26 | + backup_image_before_testing = yes | ||
27 | + | ||
28 | + # It is possible to tune the arguments to cyclictest using the | ||
29 | + # cyclictest_args variable. | ||
30 | + #cyclictest_args = "-D 30m --smp" | ||
31 | + # It is formatted using a dictionary where a number of properties of the | ||
32 | + # target is available, for example 'num_cpus' which is the number of cpus, | ||
33 | + # and 'mask_all_cpus', which is the string "0-[num_cpus - 1]". See | ||
34 | + # cyclictest.py for all available values. The following is for example | ||
35 | + # equivalent to "-D 30m --smp": | ||
36 | + #cyclictest_args = "-D 30m -t %(num_cpus)s -a %(mask_all_cpus)s" | ||
37 | + # Default timeout is 35 min | ||
38 | + #cyclictest_timeout_s = 2100 | ||
39 | + | ||
40 | + # After the run, the test case will check all values against the below | ||
41 | + # allowed maximum values of each result. | ||
42 | + #allowed_max = 50 | ||
43 | + #allowed_avg = 30 | ||
44 | + #allowed_act = | ||
45 | diff --git a/qemu/tests/cyclictest.py b/qemu/tests/cyclictest.py | ||
46 | new file mode 100644 | ||
47 | index 0000000..0ad447f | ||
48 | --- /dev/null | ||
49 | +++ b/qemu/tests/cyclictest.py | ||
50 | @@ -0,0 +1,167 @@ | ||
51 | +import logging | ||
52 | +import re | ||
53 | +from autotest.client import utils | ||
54 | +from autotest.client.shared import error | ||
55 | +from virttest import remote, utils_misc, utils_test | ||
56 | + | ||
57 | + | ||
58 | +# Information about cyclictest is available here: | ||
59 | +# https://rt.wiki.kernel.org/index.php/Cyclictest | ||
60 | +@error.context_aware | ||
61 | +def run(test, params, env): | ||
62 | + """ | ||
63 | + Test Steps: | ||
64 | + | ||
65 | + 1. Check availability of cyclictest | ||
66 | + 2. Get information about the target | ||
67 | + 3. Run cyclictest | ||
68 | + 4. Parse and check result | ||
69 | + | ||
70 | + Params: | ||
71 | + :param test: QEMU test object. | ||
72 | + :param params: Dictionary with the test parameters. | ||
73 | + :param env: Dictionary with test environment. | ||
74 | + """ | ||
75 | + | ||
76 | + def parse_cyclictest(output_string): | ||
77 | + """ | ||
78 | + Parses the output from a cyclictest run into a nested dictionary. | ||
79 | + | ||
80 | + Example input: | ||
81 | + # /dev/cpu_dma_latency set to 0us | ||
82 | + T: 0 ( 7064) P: 0 I:1000 C: 2000 Min: 1 Act: 2 Avg: 3 Max: 751 | ||
83 | + T: 1 ( 7065) P: 0 I:1500 C: 1334 Min: 1 Act: 3 Avg: 2 Max: 167 | ||
84 | + Example output: | ||
85 | + { '0': { 'Act': '2', | ||
86 | + 'Avg': '3', | ||
87 | + 'C': '2000', | ||
88 | + 'I': '1000', | ||
89 | + 'Max': '751', | ||
90 | + 'Min': '1', | ||
91 | + 'P': '0', | ||
92 | + 'T': '0 ( 7064)'}, | ||
93 | + '1': { 'Act': '3', | ||
94 | + 'Avg': '2', | ||
95 | + 'C': '1334', | ||
96 | + 'I': '1500', | ||
97 | + 'Max': '167', | ||
98 | + 'Min': '1', | ||
99 | + 'P': '0', | ||
100 | + 'T': '1 ( 7065)'} | ||
101 | + } | ||
102 | + | ||
103 | + Params: | ||
104 | + :param output_string: Output data from cyclictest | ||
105 | + :returns: Nested dict of parsed output | ||
106 | + """ | ||
107 | + output = output_string.split("\n") | ||
108 | + result = {} | ||
109 | + for row_string in output: | ||
110 | + # Only process lines beginning with "T:" | ||
111 | + if re.match("^T:", row_string): | ||
112 | + # Since the different segments of a line are not comma | ||
113 | + # separated (or similar), it is tricky to split them right | ||
114 | + # away. Instead, match the "header" of an entry (e.g. "P:"), | ||
115 | + # and add a line break before them. Then split the string using | ||
116 | + # the added line breaks. | ||
117 | + row_list = re.sub("\S+:", "\n\g<0>", row_string).split("\n") | ||
118 | + sub_result = {} | ||
119 | + for part in row_list: | ||
120 | + part = re.sub("\s+$", "", part) | ||
121 | + if not part == "": | ||
122 | + # The strings are now on the form "P: 0" (for example) | ||
123 | + kv = re.split(":\s*", part) | ||
124 | + sub_result[kv[0]] = kv[1] | ||
125 | + # Use the thread as an index in the outer result dict. Avoid | ||
126 | + # using an array to be able to process results in any order and | ||
127 | + # with gaps. | ||
128 | + thread = (sub_result['T'].split(" "))[0] | ||
129 | + result[thread] = sub_result | ||
130 | + return result | ||
131 | + | ||
132 | + def check_val_ceiling(allowed_ceil, val, name, cpu): | ||
133 | + """ | ||
134 | + Check that the value :val: is not larger than :allowed_ceil:, and if so | ||
135 | + log the error using :name: as the name of the value. | ||
136 | + | ||
137 | + Params: | ||
138 | + :allowed_ceil: Maximum allowed vaule of :val:. If None, "" or 0, | ||
139 | + just return 0. | ||
140 | + :val: The value to be tested | ||
141 | + :name: The name of the value, for logging | ||
142 | + :cpu: CPU number for error reporting | ||
143 | + :returns: Number of errors; 0 if successful. | ||
144 | + """ | ||
145 | + if allowed_ceil > 0 and val > allowed_ceil: | ||
146 | + logging.error("%s value too large: %d for CPU %d" % (name, | ||
147 | + val, | ||
148 | + cpu)) | ||
149 | + return 1 | ||
150 | + return 0 | ||
151 | + | ||
152 | + # Get/setup VM and session | ||
153 | + vm_name = params.get("vm_name", "main_vm") | ||
154 | + vm = env.get_vm(params[vm_name]) | ||
155 | + vm.verify_alive() | ||
156 | + timeout = int(params.get("timeout", 2400)) | ||
157 | + session = vm.wait_for_login(timeout=timeout) | ||
158 | + | ||
159 | + try: | ||
160 | + error.context("Check availability of cyclictest") | ||
161 | + if session.cmd_status("which cyclictest"): | ||
162 | + raise error.TestFail("Test application not available") | ||
163 | + | ||
164 | + error.context("Get information about the target") | ||
165 | + # Get the number of CPUs on target | ||
166 | + cmd = "grep -c '^processor[[:space:]]*:' /proc/cpuinfo" | ||
167 | + output = session.cmd_output(cmd) | ||
168 | + num_cpus = int(output) | ||
169 | + if num_cpus == 0: | ||
170 | + raise error.TestFail("Unable to get number of CPUs on target") | ||
171 | + | ||
172 | + # Get arguments from the configuration | ||
173 | + default_args = "-D 30m --smp" | ||
174 | + cyclictest_args = params.get("cyclictest_args", default_args) | ||
175 | + fmt_dict = { | ||
176 | + 'num_cpus': num_cpus, | ||
177 | + 'last_cpu': num_cpus - 1, | ||
178 | + 'mask_all_cpus': "0-%d" % (num_cpus - 1), | ||
179 | + } | ||
180 | + formatted_args = cyclictest_args % fmt_dict | ||
181 | + | ||
182 | + error.context("Run cyclictest") | ||
183 | + timeout_s = params.get("cyclictest_timeout_s", 60*35) | ||
184 | + cmd = "cyclictest -q %s" % formatted_args | ||
185 | + status, ct_output = session.cmd_status_output(cmd, | ||
186 | + timeout=timeout_s) | ||
187 | + if status: | ||
188 | + logging.error("Cyclic test output: %s" % ct_output) | ||
189 | + raise error.TestFail("Cyclictest returned %d" % status) | ||
190 | + | ||
191 | + error.context("Parse and check result") | ||
192 | + results = parse_cyclictest(ct_output) | ||
193 | + if not results: | ||
194 | + logging.error("Cyclic test output: %s" % ct_output) | ||
195 | + raise error.TestFail("Parsing of cyclictest output failed") | ||
196 | + | ||
197 | + # Get check values. By default, don't check Act. | ||
198 | + allowed_max = int(params.get("allowed_max", 50)) | ||
199 | + allowed_avg = int(params.get("allowed_avg", 30)) | ||
200 | + allowed_act = int(params.get("allowed_act", 0)) | ||
201 | + | ||
202 | + fails = 0 | ||
203 | + for key, result in results.iteritems(): | ||
204 | + cpu = int(key) | ||
205 | + max_val = int(result['Max']) | ||
206 | + avg_val = int(result['Avg']) | ||
207 | + act_val = int(result['Act']) | ||
208 | + fails += check_val_ceiling(allowed_max, max_val, "Max", cpu) | ||
209 | + fails += check_val_ceiling(allowed_avg, avg_val, "Avg", cpu) | ||
210 | + fails += check_val_ceiling(allowed_act, act_val, "Act", cpu) | ||
211 | + | ||
212 | + if fails > 0: | ||
213 | + logging.error("Cyclic test output: %s" % ct_output) | ||
214 | + raise error.TestFail("Values out of bounds, see log") | ||
215 | + | ||
216 | + finally: | ||
217 | + session.close() | ||