perf test: Add mechanism for skipping attr tests on auxiliary vector values
[platform/kernel/linux-rpi.git] / tools / perf / tests / attr.py
1 # SPDX-License-Identifier: GPL-2.0
2
3 from __future__ import print_function
4
5 import os
6 import sys
7 import glob
8 import optparse
9 import tempfile
10 import logging
11 import re
12 import shutil
13 import subprocess
14
15 try:
16     import configparser
17 except ImportError:
18     import ConfigParser as configparser
19
20 def data_equal(a, b):
21     # Allow multiple values in assignment separated by '|'
22     a_list = a.split('|')
23     b_list = b.split('|')
24
25     for a_item in a_list:
26         for b_item in b_list:
27             if (a_item == b_item):
28                 return True
29             elif (a_item == '*') or (b_item == '*'):
30                 return True
31
32     return False
33
34 class Fail(Exception):
35     def __init__(self, test, msg):
36         self.msg = msg
37         self.test = test
38     def getMsg(self):
39         return '\'%s\' - %s' % (self.test.path, self.msg)
40
41 class Notest(Exception):
42     def __init__(self, test, arch):
43         self.arch = arch
44         self.test = test
45     def getMsg(self):
46         return '[%s] \'%s\'' % (self.arch, self.test.path)
47
48 class Unsup(Exception):
49     def __init__(self, test):
50         self.test = test
51     def getMsg(self):
52         return '\'%s\'' % self.test.path
53
54 class Event(dict):
55     terms = [
56         'cpu',
57         'flags',
58         'type',
59         'size',
60         'config',
61         'sample_period',
62         'sample_type',
63         'read_format',
64         'disabled',
65         'inherit',
66         'pinned',
67         'exclusive',
68         'exclude_user',
69         'exclude_kernel',
70         'exclude_hv',
71         'exclude_idle',
72         'mmap',
73         'comm',
74         'freq',
75         'inherit_stat',
76         'enable_on_exec',
77         'task',
78         'watermark',
79         'precise_ip',
80         'mmap_data',
81         'sample_id_all',
82         'exclude_host',
83         'exclude_guest',
84         'exclude_callchain_kernel',
85         'exclude_callchain_user',
86         'wakeup_events',
87         'bp_type',
88         'config1',
89         'config2',
90         'branch_sample_type',
91         'sample_regs_user',
92         'sample_stack_user',
93     ]
94
95     def add(self, data):
96         for key, val in data:
97             log.debug("      %s = %s" % (key, val))
98             self[key] = val
99
100     def __init__(self, name, data, base):
101         log.debug("    Event %s" % name);
102         self.name  = name;
103         self.group = ''
104         self.add(base)
105         self.add(data)
106
107     def equal(self, other):
108         for t in Event.terms:
109             log.debug("      [%s] %s %s" % (t, self[t], other[t]));
110             if t not in self or t not in other:
111                 return False
112             if not data_equal(self[t], other[t]):
113                 return False
114         return True
115
116     def optional(self):
117         if 'optional' in self and self['optional'] == '1':
118             return True
119         return False
120
121     def diff(self, other):
122         for t in Event.terms:
123             if t not in self or t not in other:
124                 continue
125             if not data_equal(self[t], other[t]):
126                 log.warning("expected %s=%s, got %s" % (t, self[t], other[t]))
127
128 # Test file description needs to have following sections:
129 # [config]
130 #   - just single instance in file
131 #   - needs to specify:
132 #     'command' - perf command name
133 #     'args'    - special command arguments
134 #     'ret'     - Skip test if Perf doesn't exit with this value (0 by default)
135 #     'test_ret'- If set to 'true', fail test instead of skipping for 'ret' argument
136 #     'arch'    - architecture specific test (optional)
137 #                 comma separated list, ! at the beginning
138 #                 negates it.
139 #     'auxv'    - Truthy statement that is evaled in the scope of the auxv map. When false,
140 #                 the test is skipped. For example 'auxv["AT_HWCAP"] == 10'. (optional)
141 #
142 # [eventX:base]
143 #   - one or multiple instances in file
144 #   - expected values assignments
145 class Test(object):
146     def __init__(self, path, options):
147         parser = configparser.SafeConfigParser()
148         parser.read(path)
149
150         log.warning("running '%s'" % path)
151
152         self.path     = path
153         self.test_dir = options.test_dir
154         self.perf     = options.perf
155         self.command  = parser.get('config', 'command')
156         self.args     = parser.get('config', 'args')
157
158         try:
159             self.ret  = parser.get('config', 'ret')
160         except:
161             self.ret  = 0
162
163         self.test_ret = parser.getboolean('config', 'test_ret', fallback=False)
164
165         try:
166             self.arch  = parser.get('config', 'arch')
167             log.warning("test limitation '%s'" % self.arch)
168         except:
169             self.arch  = ''
170
171         self.auxv = parser.get('config', 'auxv', fallback=None)
172         self.expect   = {}
173         self.result   = {}
174         log.debug("  loading expected events");
175         self.load_events(path, self.expect)
176
177     def is_event(self, name):
178         if name.find("event") == -1:
179             return False
180         else:
181             return True
182
183     def skip_test_auxv(self):
184         def new_auxv(a, pattern):
185             items = list(filter(None, pattern.split(a)))
186             # AT_HWCAP is hex but doesn't have a prefix, so special case it
187             if items[0] == "AT_HWCAP":
188                 value = int(items[-1], 16)
189             else:
190                 try:
191                     value = int(items[-1], 0)
192                 except:
193                     value = items[-1]
194             return (items[0], value)
195
196         if not self.auxv:
197             return False
198         auxv = subprocess.check_output("LD_SHOW_AUXV=1 sleep 0", shell=True) \
199                .decode(sys.stdout.encoding)
200         pattern = re.compile(r"[: ]+")
201         auxv = dict([new_auxv(a, pattern) for a in auxv.splitlines()])
202         return not eval(self.auxv)
203
204     def skip_test_arch(self, myarch):
205         # If architecture not set always run test
206         if self.arch == '':
207             # log.warning("test for arch %s is ok" % myarch)
208             return False
209
210         # Allow multiple values in assignment separated by ','
211         arch_list = self.arch.split(',')
212
213         # Handle negated list such as !s390x,ppc
214         if arch_list[0][0] == '!':
215             arch_list[0] = arch_list[0][1:]
216             log.warning("excluded architecture list %s" % arch_list)
217             for arch_item in arch_list:
218                 # log.warning("test for %s arch is %s" % (arch_item, myarch))
219                 if arch_item == myarch:
220                     return True
221             return False
222
223         for arch_item in arch_list:
224             # log.warning("test for architecture '%s' current '%s'" % (arch_item, myarch))
225             if arch_item == myarch:
226                 return False
227         return True
228
229     def load_events(self, path, events):
230         parser_event = configparser.SafeConfigParser()
231         parser_event.read(path)
232
233         # The event record section header contains 'event' word,
234         # optionaly followed by ':' allowing to load 'parent
235         # event' first as a base
236         for section in filter(self.is_event, parser_event.sections()):
237
238             parser_items = parser_event.items(section);
239             base_items   = {}
240
241             # Read parent event if there's any
242             if (':' in section):
243                 base = section[section.index(':') + 1:]
244                 parser_base = configparser.SafeConfigParser()
245                 parser_base.read(self.test_dir + '/' + base)
246                 base_items = parser_base.items('event')
247
248             e = Event(section, parser_items, base_items)
249             events[section] = e
250
251     def run_cmd(self, tempdir):
252         junk1, junk2, junk3, junk4, myarch = (os.uname())
253
254         if self.skip_test_arch(myarch):
255             raise Notest(self, myarch)
256
257         if self.skip_test_auxv():
258             raise Notest(self, "auxv skip")
259
260         cmd = "PERF_TEST_ATTR=%s %s %s -o %s/perf.data %s" % (tempdir,
261               self.perf, self.command, tempdir, self.args)
262         ret = os.WEXITSTATUS(os.system(cmd))
263
264         log.info("  '%s' ret '%s', expected '%s'" % (cmd, str(ret), str(self.ret)))
265
266         if not data_equal(str(ret), str(self.ret)):
267             if self.test_ret:
268                 raise Fail(self, "Perf exit code failure")
269             else:
270                 raise Unsup(self)
271
272     def compare(self, expect, result):
273         match = {}
274
275         log.debug("  compare");
276
277         # For each expected event find all matching
278         # events in result. Fail if there's not any.
279         for exp_name, exp_event in expect.items():
280             exp_list = []
281             res_event = {}
282             log.debug("    matching [%s]" % exp_name)
283             for res_name, res_event in result.items():
284                 log.debug("      to [%s]" % res_name)
285                 if (exp_event.equal(res_event)):
286                     exp_list.append(res_name)
287                     log.debug("    ->OK")
288                 else:
289                     log.debug("    ->FAIL");
290
291             log.debug("    match: [%s] matches %s" % (exp_name, str(exp_list)))
292
293             # we did not any matching event - fail
294             if not exp_list:
295                 if exp_event.optional():
296                     log.debug("    %s does not match, but is optional" % exp_name)
297                 else:
298                     if not res_event:
299                         log.debug("    res_event is empty");
300                     else:
301                         exp_event.diff(res_event)
302                     raise Fail(self, 'match failure');
303
304             match[exp_name] = exp_list
305
306         # For each defined group in the expected events
307         # check we match the same group in the result.
308         for exp_name, exp_event in expect.items():
309             group = exp_event.group
310
311             if (group == ''):
312                 continue
313
314             for res_name in match[exp_name]:
315                 res_group = result[res_name].group
316                 if res_group not in match[group]:
317                     raise Fail(self, 'group failure')
318
319                 log.debug("    group: [%s] matches group leader %s" %
320                          (exp_name, str(match[group])))
321
322         log.debug("  matched")
323
324     def resolve_groups(self, events):
325         for name, event in events.items():
326             group_fd = event['group_fd'];
327             if group_fd == '-1':
328                 continue;
329
330             for iname, ievent in events.items():
331                 if (ievent['fd'] == group_fd):
332                     event.group = iname
333                     log.debug('[%s] has group leader [%s]' % (name, iname))
334                     break;
335
336     def run(self):
337         tempdir = tempfile.mkdtemp();
338
339         try:
340             # run the test script
341             self.run_cmd(tempdir);
342
343             # load events expectation for the test
344             log.debug("  loading result events");
345             for f in glob.glob(tempdir + '/event*'):
346                 self.load_events(f, self.result);
347
348             # resolve group_fd to event names
349             self.resolve_groups(self.expect);
350             self.resolve_groups(self.result);
351
352             # do the expectation - results matching - both ways
353             self.compare(self.expect, self.result)
354             self.compare(self.result, self.expect)
355
356         finally:
357             # cleanup
358             shutil.rmtree(tempdir)
359
360
361 def run_tests(options):
362     for f in glob.glob(options.test_dir + '/' + options.test):
363         try:
364             Test(f, options).run()
365         except Unsup as obj:
366             log.warning("unsupp  %s" % obj.getMsg())
367         except Notest as obj:
368             log.warning("skipped %s" % obj.getMsg())
369
370 def setup_log(verbose):
371     global log
372     level = logging.CRITICAL
373
374     if verbose == 1:
375         level = logging.WARNING
376     if verbose == 2:
377         level = logging.INFO
378     if verbose >= 3:
379         level = logging.DEBUG
380
381     log = logging.getLogger('test')
382     log.setLevel(level)
383     ch  = logging.StreamHandler()
384     ch.setLevel(level)
385     formatter = logging.Formatter('%(message)s')
386     ch.setFormatter(formatter)
387     log.addHandler(ch)
388
389 USAGE = '''%s [OPTIONS]
390   -d dir  # tests dir
391   -p path # perf binary
392   -t test # single test
393   -v      # verbose level
394 ''' % sys.argv[0]
395
396 def main():
397     parser = optparse.OptionParser(usage=USAGE)
398
399     parser.add_option("-t", "--test",
400                       action="store", type="string", dest="test")
401     parser.add_option("-d", "--test-dir",
402                       action="store", type="string", dest="test_dir")
403     parser.add_option("-p", "--perf",
404                       action="store", type="string", dest="perf")
405     parser.add_option("-v", "--verbose",
406                       default=0, action="count", dest="verbose")
407
408     options, args = parser.parse_args()
409     if args:
410         parser.error('FAILED wrong arguments %s' %  ' '.join(args))
411         return -1
412
413     setup_log(options.verbose)
414
415     if not options.test_dir:
416         print('FAILED no -d option specified')
417         sys.exit(-1)
418
419     if not options.test:
420         options.test = 'test*'
421
422     try:
423         run_tests(options)
424
425     except Fail as obj:
426         print("FAILED %s" % obj.getMsg())
427         sys.exit(-1)
428
429     sys.exit(0)
430
431 if __name__ == '__main__':
432     main()