cachepc-linux

Fork of AMDESE/linux with modifications for CachePC side-channel attack
git clone https://git.sinitax.com/sinitax/cachepc-linux
Log | Files | Refs | README | LICENSE | sfeed.txt

attr.py (11651B)


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