diff options
Diffstat (limited to 'tools/perf/tests/shell/lib')
| -rw-r--r-- | tools/perf/tests/shell/lib/perf_json_output_lint.py | 13 | ||||
| -rw-r--r-- | tools/perf/tests/shell/lib/perf_metric_validation.py | 574 | ||||
| -rw-r--r-- | tools/perf/tests/shell/lib/perf_metric_validation_rules.json | 398 | ||||
| -rw-r--r-- | tools/perf/tests/shell/lib/stat_output.sh | 169 | 
4 files changed, 1150 insertions, 4 deletions
diff --git a/tools/perf/tests/shell/lib/perf_json_output_lint.py b/tools/perf/tests/shell/lib/perf_json_output_lint.py index 61f3059ca54b..ea55d5ea1ced 100644 --- a/tools/perf/tests/shell/lib/perf_json_output_lint.py +++ b/tools/perf/tests/shell/lib/perf_json_output_lint.py @@ -14,6 +14,7 @@ ap.add_argument('--system-wide', action='store_true')  ap.add_argument('--event', action='store_true')  ap.add_argument('--per-core', action='store_true')  ap.add_argument('--per-thread', action='store_true') +ap.add_argument('--per-cache', action='store_true')  ap.add_argument('--per-die', action='store_true')  ap.add_argument('--per-node', action='store_true')  ap.add_argument('--per-socket', action='store_true') @@ -47,12 +48,14 @@ def check_json_output(expected_items):        'counter-value': lambda x: is_counter_value(x),        'cgroup': lambda x: True,        'cpu': lambda x: isint(x), +      'cache': lambda x: True,        'die': lambda x: True,        'event': lambda x: True,        'event-runtime': lambda x: isfloat(x),        'interval': lambda x: isfloat(x),        'metric-unit': lambda x: True,        'metric-value': lambda x: isfloat(x), +      'metricgroup': lambda x: True,        'node': lambda x: True,        'pcnt-running': lambda x: isfloat(x),        'socket': lambda x: True, @@ -63,10 +66,12 @@ def check_json_output(expected_items):    for item in json.loads(input):      if expected_items != -1:        count = len(item) -      if count != expected_items and count >= 1 and count <= 4 and 'metric-value' in item: +      if count != expected_items and count >= 1 and count <= 6 and 'metric-value' in item:          # Events that generate >1 metric may have isolated metric -        # values and possibly other prefixes like interval, core and -        # aggregate-number. +        # values and possibly other prefixes like interval, core, +        # aggregate-number, or event-runtime/pcnt-running from multiplexing. +        pass +      elif count != expected_items and count >= 1 and count <= 5 and 'metricgroup' in item:          pass        elif count != expected_items:          raise RuntimeError(f'wrong number of fields. counted {count} expected {expected_items}' @@ -83,7 +88,7 @@ try:      expected_items = 7    elif args.interval or args.per_thread or args.system_wide_no_aggr:      expected_items = 8 -  elif args.per_core or args.per_socket or args.per_node or args.per_die: +  elif args.per_core or args.per_socket or args.per_node or args.per_die or args.per_cache:      expected_items = 9    else:      # If no option is specified, don't check the number of items. diff --git a/tools/perf/tests/shell/lib/perf_metric_validation.py b/tools/perf/tests/shell/lib/perf_metric_validation.py new file mode 100644 index 000000000000..50a34a9cc040 --- /dev/null +++ b/tools/perf/tests/shell/lib/perf_metric_validation.py @@ -0,0 +1,574 @@ +#SPDX-License-Identifier: GPL-2.0 +import re +import csv +import json +import argparse +from pathlib import Path +import subprocess + +class Validator: +    def __init__(self, rulefname, reportfname='', t=5, debug=False, datafname='', fullrulefname='', workload='true', metrics=''): +        self.rulefname = rulefname +        self.reportfname = reportfname +        self.rules = None +        self.collectlist:str = metrics +        self.metrics = self.__set_metrics(metrics) +        self.skiplist = set() +        self.tolerance = t + +        self.workloads = [x for x in workload.split(",") if x] +        self.wlidx = 0 # idx of current workloads +        self.allresults = dict() # metric results of all workload +        self.allignoremetrics = dict() # metrics with no results or negative results +        self.allfailtests = dict() +        self.alltotalcnt = dict() +        self.allpassedcnt = dict() +        self.allerrlist = dict() + +        self.results = dict() # metric results of current workload +        # vars for test pass/failure statistics +        self.ignoremetrics= set() # metrics with no results or negative results, neg result counts as a failed test +        self.failtests = dict() +        self.totalcnt = 0 +        self.passedcnt = 0 +        # vars for errors +        self.errlist = list() + +        # vars for Rule Generator +        self.pctgmetrics = set() # Percentage rule + +        # vars for debug +        self.datafname = datafname +        self.debug = debug +        self.fullrulefname = fullrulefname + +    def __set_metrics(self, metrics=''): +        if metrics != '': +            return set(metrics.split(",")) +        else: +            return set() + +    def read_json(self, filename: str) -> dict: +        try: +            with open(Path(filename).resolve(), "r") as f: +                data = json.loads(f.read()) +        except OSError as e: +            print(f"Error when reading file {e}") +            sys.exit() + +        return data + +    def json_dump(self, data, output_file): +        parent = Path(output_file).parent +        if not parent.exists(): +            parent.mkdir(parents=True) + +        with open(output_file, "w+") as output_file: +            json.dump(data, +                      output_file, +                      ensure_ascii=True, +                      indent=4) + +    def get_results(self, idx:int = 0): +        return self.results[idx] + +    def get_bounds(self, lb, ub, error, alias={}, ridx:int = 0) -> list: +        """ +        Get bounds and tolerance from lb, ub, and error. +        If missing lb, use 0.0; missing ub, use float('inf); missing error, use self.tolerance. + +        @param lb: str/float, lower bound +        @param ub: str/float, upper bound +        @param error: float/str, error tolerance +        @returns: lower bound, return inf if the lower bound is a metric value and is not collected +                  upper bound, return -1 if the upper bound is a metric value and is not collected +                  tolerance, denormalized base on upper bound value +        """ +        # init ubv and lbv to invalid values +        def get_bound_value (bound, initval, ridx): +            val = initval +            if isinstance(bound, int) or isinstance(bound, float): +                val = bound +            elif isinstance(bound, str): +                if bound == '': +                    val = float("inf") +                elif bound in alias: +                    vall = self.get_value(alias[ub], ridx) +                    if vall: +                        val = vall[0] +                elif bound.replace('.', '1').isdigit(): +                    val = float(bound) +                else: +                    print("Wrong bound: {0}".format(bound)) +            else: +                print("Wrong bound: {0}".format(bound)) +            return val + +        ubv = get_bound_value(ub, -1, ridx) +        lbv = get_bound_value(lb, float('inf'), ridx) +        t = get_bound_value(error, self.tolerance, ridx) + +        # denormalize error threshold +        denormerr = t * ubv / 100 if ubv != 100 and ubv > 0 else t + +        return lbv, ubv, denormerr + +    def get_value(self, name:str, ridx:int = 0) -> list: +        """ +        Get value of the metric from self.results. +        If result of this metric is not provided, the metric name will be added into self.ignoremetics and self.errlist. +        All future test(s) on this metric will fail. + +        @param name: name of the metric +        @returns: list with value found in self.results; list is empty when value is not found. +        """ +        results = [] +        data = self.results[ridx] if ridx in self.results else self.results[0] +        if name not in self.ignoremetrics: +            if name in data: +                results.append(data[name]) +            elif name.replace('.', '1').isdigit(): +                results.append(float(name)) +            else: +                self.ignoremetrics.add(name) +        return results + +    def check_bound(self, val, lb, ub, err): +        return True if val <= ub + err and val >= lb - err else False + +    # Positive Value Sanity check +    def pos_val_test(self): +        """ +        Check if metrics value are non-negative. +        One metric is counted as one test. +        Failure: when metric value is negative or not provided. +        Metrics with negative value will be added into the self.failtests['PositiveValueTest'] and self.ignoremetrics. +        """ +        negmetric = dict() +        pcnt = 0 +        tcnt = 0 +        rerun = list() +        for name, val in self.get_results().items(): +            if val < 0: +                negmetric[name] = val +                rerun.append(name) +            else: +                pcnt += 1 +            tcnt += 1 +        if len(rerun) > 0 and len(rerun) < 20: +            second_results = dict() +            self.second_test(rerun, second_results) +            for name, val in second_results.items(): +                if name not in negmetric: continue +                if val >= 0: +                    del negmetric[name] +                    pcnt += 1 + +        self.failtests['PositiveValueTest']['Total Tests'] = tcnt +        self.failtests['PositiveValueTest']['Passed Tests'] = pcnt +        if len(negmetric.keys()): +            self.ignoremetrics.update(negmetric.keys()) +            negmessage = ["{0}(={1:.4f})".format(name, val) for name, val in negmetric.items()] +            self.failtests['PositiveValueTest']['Failed Tests'].append({'NegativeValue': negmessage}) + +        return + +    def evaluate_formula(self, formula:str, alias:dict, ridx:int = 0): +        """ +        Evaluate the value of formula. + +        @param formula: the formula to be evaluated +        @param alias: the dict has alias to metric name mapping +        @returns: value of the formula is success; -1 if the one or more metric value not provided +        """ +        stack = [] +        b = 0 +        errs = [] +        sign = "+" +        f = str() + +        #TODO: support parenthesis? +        for i in range(len(formula)): +            if i+1 == len(formula) or formula[i] in ('+', '-', '*', '/'): +                s = alias[formula[b:i]] if i+1 < len(formula) else alias[formula[b:]] +                v = self.get_value(s, ridx) +                if not v: +                    errs.append(s) +                else: +                    f = f + "{0}(={1:.4f})".format(s, v[0]) +                    if sign == "*": +                        stack[-1] = stack[-1] * v +                    elif sign == "/": +                        stack[-1] = stack[-1] / v +                    elif sign == '-': +                        stack.append(-v[0]) +                    else: +                        stack.append(v[0]) +                if i + 1 < len(formula): +                    sign = formula[i] +                    f += sign +                    b = i + 1 + +        if len(errs) > 0: +            return -1, "Metric value missing: "+','.join(errs) + +        val = sum(stack) +        return val, f + +    # Relationships Tests +    def relationship_test(self, rule: dict): +        """ +        Validate if the metrics follow the required relationship in the rule. +        eg. lower_bound <= eval(formula)<= upper_bound +        One rule is counted as ont test. +        Failure: when one or more metric result(s) not provided, or when formula evaluated outside of upper/lower bounds. + +        @param rule: dict with metric name(+alias), formula, and required upper and lower bounds. +        """ +        alias = dict() +        for m in rule['Metrics']: +            alias[m['Alias']] = m['Name'] +        lbv, ubv, t = self.get_bounds(rule['RangeLower'], rule['RangeUpper'], rule['ErrorThreshold'], alias, ridx=rule['RuleIndex']) +        val, f = self.evaluate_formula(rule['Formula'], alias, ridx=rule['RuleIndex']) +        if val == -1: +            self.failtests['RelationshipTest']['Failed Tests'].append({'RuleIndex': rule['RuleIndex'], 'Description':f}) +        elif not self.check_bound(val, lbv, ubv, t): +            lb = rule['RangeLower'] +            ub = rule['RangeUpper'] +            if isinstance(lb, str): +                if lb in alias: +                    lb = alias[lb] +            if isinstance(ub, str): +                if ub in alias: +                    ub = alias[ub] +            self.failtests['RelationshipTest']['Failed Tests'].append({'RuleIndex': rule['RuleIndex'], 'Formula':f, +                                                                       'RangeLower': lb, 'LowerBoundValue': self.get_value(lb), +                                                                       'RangeUpper': ub, 'UpperBoundValue':self.get_value(ub), +                                                                       'ErrorThreshold': t, 'CollectedValue': val}) +        else: +            self.passedcnt += 1 +            self.failtests['RelationshipTest']['Passed Tests'] += 1 +        self.totalcnt += 1 +        self.failtests['RelationshipTest']['Total Tests'] += 1 + +        return + + +    # Single Metric Test +    def single_test(self, rule:dict): +        """ +        Validate if the metrics are in the required value range. +        eg. lower_bound <= metrics_value <= upper_bound +        One metric is counted as one test in this type of test. +        One rule may include one or more metrics. +        Failure: when the metric value not provided or the value is outside the bounds. +        This test updates self.total_cnt and records failed tests in self.failtest['SingleMetricTest']. + +        @param rule: dict with metrics to validate and the value range requirement +        """ +        lbv, ubv, t = self.get_bounds(rule['RangeLower'], rule['RangeUpper'], rule['ErrorThreshold']) +        metrics = rule['Metrics'] +        passcnt = 0 +        totalcnt = 0 +        faillist = list() +        failures = dict() +        rerun = list() +        for m in metrics: +            totalcnt += 1 +            result = self.get_value(m['Name']) +            if len(result) > 0 and self.check_bound(result[0], lbv, ubv, t) or m['Name'] in self.skiplist: +                passcnt += 1 +            else: +                failures[m['Name']] = result +                rerun.append(m['Name']) + +        if len(rerun) > 0 and len(rerun) < 20: +            second_results = dict() +            self.second_test(rerun, second_results) +            for name, val in second_results.items(): +                if name not in failures: continue +                if self.check_bound(val, lbv, ubv, t): +                    passcnt += 1 +                    del failures[name] +                else: +                    failures[name] = val +                    self.results[0][name] = val + +        self.totalcnt += totalcnt +        self.passedcnt += passcnt +        self.failtests['SingleMetricTest']['Total Tests'] += totalcnt +        self.failtests['SingleMetricTest']['Passed Tests'] += passcnt +        if len(failures.keys()) != 0: +            faillist = [{'MetricName':name, 'CollectedValue':val} for name, val in failures.items()] +            self.failtests['SingleMetricTest']['Failed Tests'].append({'RuleIndex':rule['RuleIndex'], +                                                                       'RangeLower': rule['RangeLower'], +                                                                       'RangeUpper': rule['RangeUpper'], +                                                                       'ErrorThreshold':rule['ErrorThreshold'], +                                                                       'Failure':faillist}) + +        return + +    def create_report(self): +        """ +        Create final report and write into a JSON file. +        """ +        alldata = list() +        for i in range(0, len(self.workloads)): +            reportstas = {"Total Rule Count": self.alltotalcnt[i], "Passed Rule Count": self.allpassedcnt[i]} +            data = {"Metric Validation Statistics": reportstas, "Tests in Category": self.allfailtests[i], +                    "Errors":self.allerrlist[i]} +            alldata.append({"Workload": self.workloads[i], "Report": data}) + +        json_str = json.dumps(alldata, indent=4) +        print("Test validation finished. Final report: ") +        print(json_str) + +        if self.debug: +            allres = [{"Workload": self.workloads[i], "Results": self.allresults[i]} for i in range(0, len(self.workloads))] +            self.json_dump(allres, self.datafname) + +    def check_rule(self, testtype, metric_list): +        """ +        Check if the rule uses metric(s) that not exist in current platform. + +        @param metric_list: list of metrics from the rule. +        @return: False when find one metric out in Metric file. (This rule should not skipped.) +                 True when all metrics used in the rule are found in Metric file. +        """ +        if testtype == "RelationshipTest": +            for m in metric_list: +                if m['Name'] not in self.metrics: +                    return False +        return True + +    # Start of Collector and Converter +    def convert(self, data: list, metricvalues:dict): +        """ +        Convert collected metric data from the -j output to dict of {metric_name:value}. +        """ +        for json_string in data: +            try: +                result =json.loads(json_string) +                if "metric-unit" in result and result["metric-unit"] != "(null)" and result["metric-unit"] != "": +                    name = result["metric-unit"].split("  ")[1] if len(result["metric-unit"].split("  ")) > 1 \ +                        else result["metric-unit"] +                    metricvalues[name.lower()] = float(result["metric-value"]) +            except ValueError as error: +                continue +        return + +    def _run_perf(self, metric, workload: str): +        tool = 'perf' +        command = [tool, 'stat', '-j', '-M', f"{metric}", "-a"] +        wl = workload.split() +        command.extend(wl) +        print(" ".join(command)) +        cmd = subprocess.run(command, stderr=subprocess.PIPE, encoding='utf-8') +        data = [x+'}' for x in cmd.stderr.split('}\n') if x] +        return data + + +    def collect_perf(self, workload: str): +        """ +        Collect metric data with "perf stat -M" on given workload with -a and -j. +        """ +        self.results = dict() +        print(f"Starting perf collection") +        print(f"Long workload: {workload}") +        collectlist = dict() +        if self.collectlist != "": +            collectlist[0] = {x for x in self.collectlist.split(",")} +        else: +            collectlist[0] = set(list(self.metrics)) +        # Create metric set for relationship rules +        for rule in self.rules: +            if rule["TestType"] == "RelationshipTest": +                metrics = [m["Name"] for m in rule["Metrics"]] +                if not any(m not in collectlist[0] for m in metrics): +                    collectlist[rule["RuleIndex"]] = [",".join(list(set(metrics)))] + +        for idx, metrics in collectlist.items(): +            if idx == 0: wl = "true" +            else: wl = workload +            for metric in metrics: +                data = self._run_perf(metric, wl) +                if idx not in self.results: self.results[idx] = dict() +                self.convert(data, self.results[idx]) +        return + +    def second_test(self, collectlist, second_results): +        workload = self.workloads[self.wlidx] +        for metric in collectlist: +            data = self._run_perf(metric, workload) +            self.convert(data, second_results) + +    # End of Collector and Converter + +    # Start of Rule Generator +    def parse_perf_metrics(self): +        """ +        Read and parse perf metric file: +        1) find metrics with '1%' or '100%' as ScaleUnit for Percent check +        2) create metric name list +        """ +        command = ['perf', 'list', '-j', '--details', 'metrics'] +        cmd = subprocess.run(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding='utf-8') +        try: +            data = json.loads(cmd.stdout) +            for m in data: +                if 'MetricName' not in m: +                    print("Warning: no metric name") +                    continue +                name = m['MetricName'].lower() +                self.metrics.add(name) +                if 'ScaleUnit' in m and (m['ScaleUnit'] == '1%' or m['ScaleUnit'] == '100%'): +                    self.pctgmetrics.add(name.lower()) +        except ValueError as error: +            print(f"Error when parsing metric data") +            sys.exit() + +        return + +    def remove_unsupported_rules(self, rules): +        new_rules = [] +        for rule in rules: +            add_rule = True +            for m in rule["Metrics"]: +                if m["Name"] in self.skiplist or m["Name"] not in self.metrics: +                    add_rule = False +                    break +            if add_rule: +                new_rules.append(rule) +        return new_rules + +    def create_rules(self): +        """ +        Create full rules which includes: +        1) All the rules from the "relationshi_rules" file +        2) SingleMetric rule for all the 'percent' metrics + +        Reindex all the rules to avoid repeated RuleIndex +        """ +        data = self.read_json(self.rulefname) +        rules = data['RelationshipRules'] +        self.skiplist = set([name.lower() for name in data['SkipList']]) +        self.rules = self.remove_unsupported_rules(rules) +        pctgrule = {'RuleIndex':0, +                    'TestType':'SingleMetricTest', +                    'RangeLower':'0', +                    'RangeUpper': '100', +                    'ErrorThreshold': self.tolerance, +                    'Description':'Metrics in percent unit have value with in [0, 100]', +                    'Metrics': [{'Name': m.lower()} for m in self.pctgmetrics]} +        self.rules.append(pctgrule) + +        # Re-index all rules to avoid repeated RuleIndex +        idx = 1 +        for r in self.rules: +            r['RuleIndex'] = idx +            idx += 1 + +        if self.debug: +            #TODO: need to test and generate file name correctly +            data = {'RelationshipRules':self.rules, 'SupportedMetrics': [{"MetricName": name} for name in self.metrics]} +            self.json_dump(data, self.fullrulefname) + +        return +    # End of Rule Generator + +    def _storewldata(self, key): +        ''' +        Store all the data of one workload into the corresponding data structure for all workloads. +        @param key: key to the dictionaries (index of self.workloads). +        ''' +        self.allresults[key] = self.results +        self.allignoremetrics[key] = self.ignoremetrics +        self.allfailtests[key] = self.failtests +        self.alltotalcnt[key] = self.totalcnt +        self.allpassedcnt[key] = self.passedcnt +        self.allerrlist[key] = self.errlist + +    #Initialize data structures before data validation of each workload +    def _init_data(self): + +        testtypes = ['PositiveValueTest', 'RelationshipTest', 'SingleMetricTest'] +        self.results = dict() +        self.ignoremetrics= set() +        self.errlist = list() +        self.failtests = {k:{'Total Tests':0, 'Passed Tests':0, 'Failed Tests':[]} for k in testtypes} +        self.totalcnt = 0 +        self.passedcnt = 0 + +    def test(self): +        ''' +        The real entry point of the test framework. +        This function loads the validation rule JSON file and Standard Metric file to create rules for +        testing and namemap dictionaries. +        It also reads in result JSON file for testing. + +        In the test process, it passes through each rule and launch correct test function bases on the +        'TestType' field of the rule. + +        The final report is written into a JSON file. +        ''' +        if not self.collectlist: +            self.parse_perf_metrics() +        self.create_rules() +        for i in range(0, len(self.workloads)): +            self.wlidx = i +            self._init_data() +            self.collect_perf(self.workloads[i]) +            # Run positive value test +            self.pos_val_test() +            for r in self.rules: +                # skip rules that uses metrics not exist in this platform +                testtype = r['TestType'] +                if not self.check_rule(testtype, r['Metrics']): +                    continue +                if  testtype == 'RelationshipTest': +                    self.relationship_test(r) +                elif testtype == 'SingleMetricTest': +                    self.single_test(r) +                else: +                    print("Unsupported Test Type: ", testtype) +                    self.errlist.append("Unsupported Test Type from rule: " + r['RuleIndex']) +            self._storewldata(i) +            print("Workload: ", self.workloads[i]) +            print("Total metrics collected: ", self.failtests['PositiveValueTest']['Total Tests']) +            print("Non-negative metric count: ", self.failtests['PositiveValueTest']['Passed Tests']) +            print("Total Test Count: ", self.totalcnt) +            print("Passed Test Count: ", self.passedcnt) + +        self.create_report() +        return sum(self.alltotalcnt.values()) != sum(self.allpassedcnt.values()) +# End of Class Validator + + +def main() -> None: +    parser = argparse.ArgumentParser(description="Launch metric value validation") + +    parser.add_argument("-rule", help="Base validation rule file", required=True) +    parser.add_argument("-output_dir", help="Path for validator output file, report file", required=True) +    parser.add_argument("-debug", help="Debug run, save intermediate data to files", action="store_true", default=False) +    parser.add_argument("-wl", help="Workload to run while data collection", default="true") +    parser.add_argument("-m", help="Metric list to validate", default="") +    args = parser.parse_args() +    outpath = Path(args.output_dir) +    reportf = Path.joinpath(outpath, 'perf_report.json') +    fullrule = Path.joinpath(outpath, 'full_rule.json') +    datafile = Path.joinpath(outpath, 'perf_data.json') + +    validator = Validator(args.rule, reportf, debug=args.debug, +                        datafname=datafile, fullrulefname=fullrule, workload=args.wl, +                        metrics=args.m) +    ret = validator.test() + +    return ret + + +if __name__ == "__main__": +    import sys +    sys.exit(main()) + + + diff --git a/tools/perf/tests/shell/lib/perf_metric_validation_rules.json b/tools/perf/tests/shell/lib/perf_metric_validation_rules.json new file mode 100644 index 000000000000..eb6f59e018b7 --- /dev/null +++ b/tools/perf/tests/shell/lib/perf_metric_validation_rules.json @@ -0,0 +1,398 @@ +{ +    "SkipList": [ +        "tsx_aborted_cycles", +        "tsx_transactional_cycles", +        "C2_Pkg_Residency", +        "C6_Pkg_Residency", +        "C1_Core_Residency", +        "C6_Core_Residency", +        "tma_false_sharing", +        "tma_remote_cache", +        "tma_contested_accesses" +    ], +    "RelationshipRules": [ +        { +            "RuleIndex": 1, +            "Formula": "a+b", +            "TestType": "RelationshipTest", +            "RangeLower": "c", +            "RangeUpper": "c", +            "ErrorThreshold": 5.0, +            "Description": "Intel(R) Optane(TM) Persistent Memory(PMEM)  bandwidth total includes Intel(R) Optane(TM) Persistent Memory(PMEM) read bandwidth and Intel(R) Optane(TM) Persistent Memory(PMEM) write bandwidth", +            "Metrics": [ +                { +                    "Name": "pmem_memory_bandwidth_read", +                    "Alias": "a" +                }, +                { +                    "Name": "pmem_memory_bandwidth_write", +                    "Alias": "b" +                }, +                { +                    "Name": "pmem_memory_bandwidth_total", +                    "Alias": "c" +                } +            ] +        }, +        { +            "RuleIndex": 2, +            "Formula": "a+b", +            "TestType": "RelationshipTest", +            "RangeLower": "c", +            "RangeUpper": "c", +            "ErrorThreshold": 5.0, +            "Description": "DDR memory bandwidth total includes DDR memory read bandwidth and DDR memory write bandwidth", +            "Metrics": [ +                { +                    "Name": "memory_bandwidth_read", +                    "Alias": "a" +                }, +                { +                    "Name": "memory_bandwidth_write", +                    "Alias": "b" +                }, +                { +                    "Name": "memory_bandwidth_total", +                    "Alias": "c" +                } +            ] +        }, +        { +            "RuleIndex": 3, +            "Formula": "a+b", +            "TestType": "RelationshipTest", +            "RangeLower": "100", +            "RangeUpper": "100", +            "ErrorThreshold": 5.0, +            "Description": "Total memory read accesses includes memory reads from last level cache (LLC) addressed to local DRAM and memory reads from the last level cache (LLC) addressed to remote DRAM.", +            "Metrics": [ +                { +                    "Name": "numa_reads_addressed_to_local_dram", +                    "Alias": "a" +                }, +                { +                    "Name": "numa_reads_addressed_to_remote_dram", +                    "Alias": "b" +                } +            ] +        }, +        { +            "RuleIndex": 4, +            "Formula": "a", +            "TestType": "SingleMetricTest", +            "RangeLower": "0.125", +            "RangeUpper": "", +            "ErrorThreshold": "", +            "Description": "", +            "Metrics": [ +                { +                    "Name": "cpi", +                    "Alias": "a" +                } +            ] +        }, +        { +            "RuleIndex": 5, +            "Formula": "", +            "TestType": "SingleMetricTest", +            "RangeLower": "0", +            "RangeUpper": "1", +            "ErrorThreshold": 5.0, +            "Description": "Ratio values should be within value range [0,1)", +            "Metrics": [ +                { +                    "Name": "loads_per_instr", +                    "Alias": "" +                }, +                { +                    "Name": "stores_per_instr", +                    "Alias": "" +                }, +                { +                    "Name": "l1d_mpi", +                    "Alias": "" +                }, +                { +                    "Name": "l1d_demand_data_read_hits_per_instr", +                    "Alias": "" +                }, +                { +                    "Name": "l1_i_code_read_misses_with_prefetches_per_instr", +                    "Alias": "" +                }, +                { +                    "Name": "l2_demand_data_read_hits_per_instr", +                    "Alias": "" +                }, +                { +                    "Name": "l2_mpi", +                    "Alias": "" +                }, +                { +                    "Name": "l2_demand_data_read_mpi", +                    "Alias": "" +                }, +                { +                    "Name": "l2_demand_code_mpi", +                    "Alias": "" +                } +            ] +        }, +        { +            "RuleIndex": 6, +            "Formula": "a+b+c+d", +            "TestType": "RelationshipTest", +            "RangeLower": "100", +            "RangeUpper": "100", +            "ErrorThreshold": 5.0, +            "Description": "Sum of TMA level 1 metrics should be 100%", +            "Metrics": [ +                { +                    "Name": "tma_frontend_bound", +                    "Alias": "a" +                }, +                { +                    "Name": "tma_bad_speculation", +                    "Alias": "b" +                }, +                { +                    "Name": "tma_backend_bound", +                    "Alias": "c" +                }, +                { +                    "Name": "tma_retiring", +                    "Alias": "d" +                } +            ] +        }, +        { +            "RuleIndex": 7, +            "Formula": "a+b", +            "TestType": "RelationshipTest", +            "RangeLower": "c", +            "RangeUpper": "c", +            "ErrorThreshold": 5.0, +            "Description": "Sum of the level 2 children should equal level 1 parent", +            "Metrics": [ +                { +                    "Name": "tma_fetch_latency", +                    "Alias": "a" +                }, +                { +                    "Name": "tma_fetch_bandwidth", +                    "Alias": "b" +                }, +                { +                    "Name": "tma_frontend_bound", +                    "Alias": "c" +                } +            ] +        }, +        { +            "RuleIndex": 8, +            "Formula": "a+b", +            "TestType": "RelationshipTest", +            "RangeLower": "c", +            "RangeUpper": "c", +            "ErrorThreshold": 5.0, +            "Description": "Sum of the level 2 children should equal level 1 parent", +            "Metrics": [ +                { +                    "Name": "tma_branch_mispredicts", +                    "Alias": "a" +                }, +                { +                    "Name": "tma_machine_clears", +                    "Alias": "b" +                }, +                { +                    "Name": "tma_bad_speculation", +                    "Alias": "c" +                } +            ] +        }, +        { +            "RuleIndex": 9, +            "Formula": "a+b", +            "TestType": "RelationshipTest", +            "RangeLower": "c", +            "RangeUpper": "c", +            "ErrorThreshold": 5.0, +            "Description": "Sum of the level 2 children should equal level 1 parent", +            "Metrics": [ +                { +                    "Name": "tma_memory_bound", +                    "Alias": "a" +                }, +                { +                    "Name": "tma_core_bound", +                    "Alias": "b" +                }, +                { +                    "Name": "tma_backend_bound", +                    "Alias": "c" +                } +            ] +        }, +        { +            "RuleIndex": 10, +            "Formula": "a+b", +            "TestType": "RelationshipTest", +            "RangeLower": "c", +            "RangeUpper": "c", +            "ErrorThreshold": 5.0, +            "Description": "Sum of the level 2 children should equal level 1 parent", +            "Metrics": [ +                { +                    "Name": "tma_light_operations", +                    "Alias": "a" +                }, +                { +                    "Name": "tma_heavy_operations", +                    "Alias": "b" +                }, +                { +                    "Name": "tma_retiring", +                    "Alias": "c" +                } +            ] +        }, +        { +            "RuleIndex": 11, +            "Formula": "a+b+c", +            "TestType": "RelationshipTest", +            "RangeLower": "100", +            "RangeUpper": "100", +            "ErrorThreshold": 5.0, +            "Description": "The all_requests includes the memory_page_empty, memory_page_misses, and memory_page_hits equals.", +            "Metrics": [ +                { +                    "Name": "memory_page_empty_vs_all_requests", +                    "Alias": "a" +                }, +                { +                    "Name": "memory_page_misses_vs_all_requests", +                    "Alias": "b" +                }, +                { +                    "Name": "memory_page_hits_vs_all_requests", +                    "Alias": "c" +                } +            ] +        }, +        { +            "RuleIndex": 12, +            "Formula": "a-b", +            "TestType": "RelationshipTest", +            "RangeLower": "0", +            "RangeUpper": "", +            "ErrorThreshold": 5.0, +            "Description": "CPU utilization in kernel mode should always be <= cpu utilization", +            "Metrics": [ +                { +                    "Name": "cpu_utilization", +                    "Alias": "a" +                }, +                { +                    "Name": "cpu_utilization_in_kernel_mode", +                    "Alias": "b" +                } +            ] +        }, +        { +            "RuleIndex": 13, +            "Formula": "a-b", +            "TestType": "RelationshipTest", +            "RangeLower": "0", +            "RangeUpper": "", +            "ErrorThreshold": 5.0, +            "Description": "Total L2 misses per instruction should be >= L2 demand data read misses per instruction", +            "Metrics": [ +                { +                    "Name": "l2_mpi", +                    "Alias": "a" +                }, +                { +                    "Name": "l2_demand_data_read_mpi", +                    "Alias": "b" +                } +            ] +        }, +        { +            "RuleIndex": 14, +            "Formula": "a-b", +            "TestType": "RelationshipTest", +            "RangeLower": "0", +            "RangeUpper": "", +            "ErrorThreshold": 5.0, +            "Description": "Total L2 misses per instruction should be >= L2 demand code misses per instruction", +            "Metrics": [ +                { +                    "Name": "l2_mpi", +                    "Alias": "a" +                }, +                { +                    "Name": "l2_demand_code_mpi", +                    "Alias": "b" +                } +            ] +        }, +        { +            "RuleIndex": 15, +            "Formula": "b+c+d", +            "TestType": "RelationshipTest", +            "RangeLower": "a", +            "RangeUpper": "a", +            "ErrorThreshold": 5.0, +            "Description": "L3 data read, rfo, code misses per instruction equals total L3 misses per instruction.", +            "Metrics": [ +                { +                    "Name": "llc_mpi", +                    "Alias": "a" +                }, +                { +                    "Name": "llc_data_read_mpi_demand_plus_prefetch", +                    "Alias": "b" +                }, +                { +                    "Name": "llc_rfo_read_mpi_demand_plus_prefetch", +                    "Alias": "c" +                }, +                { +                    "Name": "llc_code_read_mpi_demand_plus_prefetch", +                    "Alias": "d" +                } +            ] +        }, +        { +            "RuleIndex": 16, +            "Formula": "a", +            "TestType": "SingleMetricTest", +            "RangeLower": "0", +            "RangeUpper": "8", +            "ErrorThreshold": 0.0, +            "Description": "Setting generous range for allowable frequencies", +            "Metrics": [ +                { +                    "Name": "uncore_freq", +                    "Alias": "a" +                } +            ] +        }, +        { +            "RuleIndex": 17, +            "Formula": "a", +            "TestType": "SingleMetricTest", +            "RangeLower": "0", +            "RangeUpper": "8", +            "ErrorThreshold": 0.0, +            "Description": "Setting generous range for allowable frequencies", +            "Metrics": [ +                { +                    "Name": "cpu_operating_frequency", +                    "Alias": "a" +                } +            ] +        } +    ] +}
\ No newline at end of file diff --git a/tools/perf/tests/shell/lib/stat_output.sh b/tools/perf/tests/shell/lib/stat_output.sh new file mode 100644 index 000000000000..698343f0ecf9 --- /dev/null +++ b/tools/perf/tests/shell/lib/stat_output.sh @@ -0,0 +1,169 @@ +# SPDX-License-Identifier: GPL-2.0 + +# Return true if perf_event_paranoid is > $1 and not running as root. +function ParanoidAndNotRoot() +{ +	 [ "$(id -u)" != 0 ] && [ "$(cat /proc/sys/kernel/perf_event_paranoid)" -gt $1 ] +} + +# $1 name $2 extra_opt +check_no_args() +{ +        echo -n "Checking $1 output: no args " +        perf stat $2 true +        commachecker --no-args +        echo "[Success]" +} + +check_system_wide() +{ +	echo -n "Checking $1 output: system wide " +	if ParanoidAndNotRoot 0 +	then +		echo "[Skip] paranoid and not root" +		return +	fi +	perf stat -a $2 true +	commachecker --system-wide +	echo "[Success]" +} + +check_system_wide_no_aggr() +{ +	echo -n "Checking $1 output: system wide no aggregation " +	if ParanoidAndNotRoot 0 +	then +		echo "[Skip] paranoid and not root" +		return +	fi +	perf stat -A -a --no-merge $2 true +	commachecker --system-wide-no-aggr +	echo "[Success]" +} + +check_interval() +{ +	echo -n "Checking $1 output: interval " +	perf stat -I 1000 $2 true +	commachecker --interval +	echo "[Success]" +} + +check_event() +{ +	echo -n "Checking $1 output: event " +	perf stat -e cpu-clock $2 true +	commachecker --event +	echo "[Success]" +} + +check_per_core() +{ +	echo -n "Checking $1 output: per core " +	if ParanoidAndNotRoot 0 +	then +		echo "[Skip] paranoid and not root" +		return +	fi +	perf stat --per-core -a $2 true +	commachecker --per-core +	echo "[Success]" +} + +check_per_thread() +{ +	echo -n "Checking $1 output: per thread " +	if ParanoidAndNotRoot 0 +	then +		echo "[Skip] paranoid and not root" +		return +	fi +	perf stat --per-thread -a $2 true +	commachecker --per-thread +	echo "[Success]" +} + +check_per_cache_instance() +{ +	echo -n "Checking $1 output: per cache instance " +	if ParanoidAndNotRoot 0 +	then +		echo "[Skip] paranoid and not root" +		return +	fi +	perf stat --per-cache -a $2 true +	commachecker --per-cache +	echo "[Success]" +} + +check_per_die() +{ +	echo -n "Checking $1 output: per die " +	if ParanoidAndNotRoot 0 +	then +		echo "[Skip] paranoid and not root" +		return +	fi +	perf stat --per-die -a $2 true +	commachecker --per-die +	echo "[Success]" +} + +check_per_node() +{ +	echo -n "Checking $1 output: per node " +	if ParanoidAndNotRoot 0 +	then +		echo "[Skip] paranoid and not root" +		return +	fi +	perf stat --per-node -a $2 true +	commachecker --per-node +	echo "[Success]" +} + +check_per_socket() +{ +	echo -n "Checking $1 output: per socket " +	if ParanoidAndNotRoot 0 +	then +		echo "[Skip] paranoid and not root" +		return +	fi +	perf stat --per-socket -a $2 true +	commachecker --per-socket +	echo "[Success]" +} + +# The perf stat options for per-socket, per-core, per-die +# and -A ( no_aggr mode ) uses the info fetched from this +# directory: "/sys/devices/system/cpu/cpu*/topology". For +# example, socket value is fetched from "physical_package_id" +# file in topology directory. +# Reference: cpu__get_topology_int in util/cpumap.c +# If the platform doesn't expose topology information, values +# will be set to -1. For example, incase of pSeries platform +# of powerpc, value for  "physical_package_id" is restricted +# and set to -1. Check here validates the socket-id read from +# topology file before proceeding further + +FILE_LOC="/sys/devices/system/cpu/cpu*/topology/" +FILE_NAME="physical_package_id" + +function check_for_topology() +{ +	if ! ParanoidAndNotRoot 0 +	then +		socket_file=`ls $FILE_LOC/$FILE_NAME | head -n 1` +		[ -z $socket_file ] && { +			echo 0 +			return +		} +		socket_id=`cat $socket_file` +		[ $socket_id == -1 ] && { +			echo 1 +			return +		} +	fi +	echo 0 +}  |