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 json 26import os 27 28import yaml 29 30 31def get_errors(yaml_dirs): 32 """Finds all of the errors in all of the error YAML files in 33 the directories passed in.""" 34 35 all_errors = [] 36 for yaml_dir in yaml_dirs: 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 if os.path.exists(yaml_dir): 121 for directory, _, files in os.walk(yaml_dir): 122 if not files: 123 continue 124 125 err_files += [ 126 os.path.relpath(os.path.join(directory, f), yaml_dir) 127 for f in [f for f in files if f.endswith(".errors.yaml")] 128 ] 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( 161 "\n%d total errors (with %d total details blocks) in the " 162 "policy table\n\n" % (len(policy_errors), num_details) 163 ) 164 165 166if __name__ == "__main__": 167 parser = argparse.ArgumentParser(description="Error log policy reports") 168 169 parser.add_argument( 170 "-y", 171 "--yaml_dirs", 172 dest="yaml_dirs", 173 default=".", 174 help="Comma separated list of error YAML dirs", 175 ) 176 parser.add_argument( 177 "-e", 178 "--error_file", 179 dest="error_file", 180 default="obmc-errors.json", 181 help="Output Error report file", 182 ) 183 parser.add_argument( 184 "-p", 185 "--policy", 186 dest="policy_file", 187 default="condensed.json", 188 help="Condensed policy in JSON", 189 ) 190 parser.add_argument( 191 "-x", 192 "--crosscheck", 193 dest="crosscheck_file", 194 help="YAML vs policy table crosscheck output file", 195 ) 196 197 args = parser.parse_args() 198 199 dirs = args.yaml_dirs.split(",") 200 errors = get_errors(dirs) 201 202 with open(args.error_file, "w") as outfile: 203 json.dump( 204 errors, outfile, sort_keys=True, indent=2, separators=(",", ":") 205 ) 206 207 if args.crosscheck_file: 208 with open(args.policy_file) as pf: 209 policy = yaml.safe_load(pf.read()) 210 211 crosscheck(errors, policy, args.crosscheck_file) 212