1#!/usr/bin/env python3 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 24import argparse 25import os 26import json 27import yaml 28 29 30def 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 45def 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 77def 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 83def get_metadata(name, metadata): 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 96 data = [] 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 111 return data 112 113 114def 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 += [os.path.relpath( 127 os.path.join(directory, f), yaml_dir) 128 for f in [f for f in files if f.endswith('.errors.yaml')]] 129 130 return err_files 131 132 133def crosscheck(errors, policy, outfile): 134 '''Crosschecks that the errors found in the YAML are in the 135 policy file, and vice versa. 136 ''' 137 138 policy_errors = [x['err'] for x in policy] 139 yaml_errors = [x['error'] for x in errors] 140 141 out = open(outfile, 'w') 142 out.write("YAML errors not in policy table:\n\n") 143 144 for e in yaml_errors: 145 if e not in policy_errors: 146 out.write(" %s\n" % e) 147 148 out.write("\n%d total errors in the YAML\n\n" % len(yaml_errors)) 149 out.write("Policy errors not in YAML:\n\n") 150 151 for e in policy_errors: 152 if e not in yaml_errors: 153 out.write(" %s\n" % e) 154 155 num_details = 0 156 for e in policy: 157 for d in e['dtls']: 158 num_details += 1 159 160 out.write("\n%d total errors (with %d total details blocks) in the " 161 "policy table\n\n" % (len(policy_errors), num_details)) 162 163if __name__ == '__main__': 164 165 parser = argparse.ArgumentParser(description='Error log policy reports') 166 167 parser.add_argument('-y', '--yaml_dirs', 168 dest='yaml_dirs', 169 default='.', 170 help='Comma separated list of error YAML dirs') 171 parser.add_argument('-e', '--error_file', 172 dest='error_file', 173 default='obmc-errors.json', 174 help='Output Error report file') 175 parser.add_argument('-p', '--policy', 176 dest='policy_file', 177 default='condensed.json', 178 help='Condensed policy in JSON') 179 parser.add_argument('-x', '--crosscheck', 180 dest='crosscheck_file', 181 help='YAML vs policy table crosscheck output file') 182 183 args = parser.parse_args() 184 185 dirs = args.yaml_dirs.split(',') 186 errors = get_errors(dirs) 187 188 with open(args.error_file, 'w') as outfile: 189 json.dump(errors, outfile, sort_keys=True, 190 indent=2, separators=(',', ':')) 191 192 if args.crosscheck_file: 193 with open(args.policy_file) as pf: 194 policy = yaml.safe_load(pf.read()) 195 196 crosscheck(errors, policy, args.crosscheck_file) 197