Matt Spinler | 3048ecc | 2018-03-28 10:15:12 -0500 | [diff] [blame] | 1 | #!/usr/bin/env python |
| 2 | |
| 3 | '''Generates 2 reports about OpenBMC error logs: |
| 4 | |
| 5 | 1) Dumps every error defined in the errors.yaml files passed in |
| 6 | into a single JSON file that looks like: |
| 7 | |
| 8 | { |
| 9 | "desc":"Callout IIC device", |
| 10 | "error":"xyz.openbmc_project.Common.Callout.Error.IIC", |
| 11 | "file":"xyz/openbmc_project/Common/Callout.errors.yaml", |
| 12 | "metadata":[ |
| 13 | "CALLOUT_IIC_BUS", |
| 14 | "CALLOUT_IIC_ADDR", |
| 15 | "Inherits xyz.openbmc_project.Common.Callout.Error.Device" |
| 16 | ] |
| 17 | } |
| 18 | |
| 19 | 2) Crosschecks this generated JSON with the IBM error policy table, |
| 20 | showing if any errors are in one file but not the other. |
| 21 | |
| 22 | ''' |
| 23 | |
| 24 | import argparse |
| 25 | import os |
| 26 | import json |
| 27 | import yaml |
| 28 | |
| 29 | |
| 30 | def get_errors(yaml_dirs): |
| 31 | '''Finds all of the errors in all of the error YAML files in |
| 32 | the directories passed in.''' |
| 33 | |
| 34 | all_errors = [] |
| 35 | for yaml_dir in yaml_dirs: |
| 36 | error_data = [] |
| 37 | yaml_files = get_yaml(yaml_dir) |
| 38 | |
| 39 | for yaml_file in yaml_files: |
| 40 | all_errors += read_error_yaml(yaml_dir, yaml_file) |
| 41 | |
| 42 | return all_errors |
| 43 | |
| 44 | |
| 45 | def read_error_yaml(yaml_dir, yaml_file): |
| 46 | '''Returns a list of dictionary objects reflecting the error YAML.''' |
| 47 | |
| 48 | all_errors = [] |
| 49 | |
| 50 | #xyz/openbmc_project/x.errors.yaml -> xyz.openbmc_project.x.Error |
| 51 | error_base = yaml_file.replace(os.sep, '.') |
| 52 | error_base = error_base.replace('.errors.yaml', '') |
| 53 | error_base += '.Error.' |
| 54 | |
| 55 | #Also needs to look up the metadata from the .metadata.yaml files |
| 56 | metadata_file = yaml_file.replace('errors.yaml', 'metadata.yaml') |
| 57 | metadata = [] |
| 58 | |
| 59 | if os.path.exists(os.path.join(yaml_dir, metadata_file)): |
| 60 | with open(os.path.join(yaml_dir, metadata_file)) as mfd: |
| 61 | metadata = yaml.safe_load(mfd.read()) |
| 62 | |
| 63 | with open(os.path.join(yaml_dir, yaml_file)) as fd: |
| 64 | data = yaml.safe_load(fd.read()) |
| 65 | |
| 66 | for e in data: |
| 67 | error = {} |
| 68 | error['error'] = error_base + e['name'] |
| 69 | error['desc'] = e['description'] |
| 70 | error['metadata'] = get_metadata(e['name'], metadata) |
| 71 | error['file'] = yaml_file |
| 72 | all_errors.append(error) |
| 73 | |
| 74 | return all_errors |
| 75 | |
| 76 | |
Matt Spinler | a5ae51c | 2018-03-28 10:22:18 -0500 | [diff] [blame] | 77 | def add_error(val): |
| 78 | '''Adds the '.Error' before the last segment of an error string.''' |
| 79 | dot = val.rfind('.') |
| 80 | return val[:dot] + '.Error' + val[dot:] |
| 81 | |
| 82 | |
Matt Spinler | 3048ecc | 2018-03-28 10:15:12 -0500 | [diff] [blame] | 83 | def get_metadata(name, metadata): |
Matt Spinler | a5ae51c | 2018-03-28 10:22:18 -0500 | [diff] [blame] | 84 | '''Finds metadata entries for the error in the metadata |
| 85 | dictionary parsed out of the *.metadata.yaml files. |
| 86 | |
| 87 | The metadata YAML looks something like: |
| 88 | - name: SlaveDetectionFailure |
| 89 | meta: |
| 90 | - str: "ERRNO=%d" |
| 91 | type: int32 |
| 92 | inherits: |
| 93 | - xyz.openbmc_project.Callout |
| 94 | ''' |
| 95 | |
Matt Spinler | 3048ecc | 2018-03-28 10:15:12 -0500 | [diff] [blame] | 96 | data = [] |
Matt Spinler | a5ae51c | 2018-03-28 10:22:18 -0500 | [diff] [blame] | 97 | for m in metadata: |
| 98 | if m['name'] == name: |
| 99 | if 'meta' in m: |
| 100 | for entry in m['meta']: |
| 101 | #Get the name from name=value |
| 102 | n = entry['str'].split('=')[0] |
| 103 | data.append(n) |
| 104 | |
| 105 | #inherits is a list, return it comma separated |
| 106 | if 'inherits' in m: |
| 107 | vals = list(map(add_error, m['inherits'])) |
| 108 | i = ','.join(vals) |
| 109 | data.append("Inherits %s" % i) |
| 110 | |
Matt Spinler | 3048ecc | 2018-03-28 10:15:12 -0500 | [diff] [blame] | 111 | return data |
| 112 | |
| 113 | |
| 114 | def get_yaml(yaml_dir): |
| 115 | '''Finds all of the *.errors.yaml files in the directory passed in. |
| 116 | Returns a list of entries like xyz/openbmc_project/Common.Errors.yaml. |
| 117 | ''' |
| 118 | |
| 119 | err_files = [] |
| 120 | metadata_files = [] |
| 121 | if os.path.exists(yaml_dir): |
| 122 | for directory, _, files in os.walk(yaml_dir): |
| 123 | if not files: |
| 124 | continue |
| 125 | |
| 126 | err_files += map( |
| 127 | lambda f: os.path.relpath( |
| 128 | os.path.join(directory, f), |
| 129 | yaml_dir), |
| 130 | filter(lambda f: f.endswith('.errors.yaml'), files)) |
| 131 | |
| 132 | return err_files |
| 133 | |
| 134 | |
Matt Spinler | 3911b05 | 2018-03-28 10:23:58 -0500 | [diff] [blame] | 135 | def crosscheck(errors, policy, outfile): |
| 136 | '''Crosschecks that the errors found in the YAML are in the |
| 137 | policy file, and vice versa. |
| 138 | ''' |
| 139 | |
| 140 | policy_errors = [x['err'] for x in policy] |
| 141 | yaml_errors = [x['error'] for x in errors] |
| 142 | |
| 143 | out = open(outfile, 'w') |
| 144 | out.write("YAML errors not in policy table:\n\n") |
| 145 | |
| 146 | for e in yaml_errors: |
| 147 | if e not in policy_errors: |
| 148 | out.write(" %s\n" % e) |
| 149 | |
| 150 | out.write("\n%d total errors in the YAML\n\n" % len(yaml_errors)) |
| 151 | out.write("Policy errors not in YAML:\n\n") |
| 152 | |
| 153 | for e in policy_errors: |
| 154 | if e not in yaml_errors: |
| 155 | out.write(" %s\n" % e) |
| 156 | |
| 157 | num_details = 0 |
| 158 | for e in policy: |
| 159 | for d in e['dtls']: |
| 160 | num_details += 1 |
| 161 | |
| 162 | out.write("\n%d total errors (with %d total details blocks) in the " |
| 163 | "policy table\n\n" % (len(policy_errors), num_details)) |
| 164 | |
Matt Spinler | 3048ecc | 2018-03-28 10:15:12 -0500 | [diff] [blame] | 165 | if __name__ == '__main__': |
| 166 | |
| 167 | parser = argparse.ArgumentParser(description='Error log policy reports') |
| 168 | |
| 169 | parser.add_argument('-y', '--yaml_dirs', |
| 170 | dest='yaml_dirs', |
| 171 | default='.', |
| 172 | help='Comma separated list of error YAML dirs') |
| 173 | parser.add_argument('-e', '--error_file', |
| 174 | dest='error_file', |
| 175 | default='obmc-errors.json', |
| 176 | help='Output Error report file') |
| 177 | parser.add_argument('-p', '--policy', |
| 178 | dest='policy_file', |
| 179 | default='condensed.json', |
| 180 | help='Condensed policy in JSON') |
Matt Spinler | 3911b05 | 2018-03-28 10:23:58 -0500 | [diff] [blame] | 181 | parser.add_argument('-x', '--crosscheck', |
| 182 | dest='crosscheck_file', |
| 183 | help='YAML vs policy table crosscheck output file') |
Matt Spinler | 3048ecc | 2018-03-28 10:15:12 -0500 | [diff] [blame] | 184 | |
| 185 | args = parser.parse_args() |
| 186 | |
| 187 | dirs = args.yaml_dirs.split(',') |
| 188 | errors = get_errors(dirs) |
| 189 | |
| 190 | with open(args.error_file, 'w') as outfile: |
| 191 | json.dump(errors, outfile, sort_keys=True, |
| 192 | indent=2, separators=(',', ':')) |
| 193 | |
Matt Spinler | 3911b05 | 2018-03-28 10:23:58 -0500 | [diff] [blame] | 194 | if args.crosscheck_file: |
| 195 | with open(args.policy_file) as pf: |
| 196 | policy = yaml.safe_load(pf.read()) |
| 197 | |
| 198 | crosscheck(errors, policy, args.crosscheck_file) |