xref: /openbmc/phosphor-logging/tools/elog-gen.py (revision 465aaeccc5c01a437b3a3b23a9566482af0c32b5)
1#!/usr/bin/env python
2
3r"""
4This script will parse error log yaml file(s) and generate
5a header file which will then be used by the error logging client and
6server to collect and validate the error information generated by the
7openbmc software components.
8
9This code uses a mako template to provide the basic template of the header
10file we're going to generate.  We then call it with information from the
11yaml to generate the header file.
12"""
13
14from mako.template import Template
15from optparse import OptionParser
16import yaml
17import sys
18import os
19
20
21def order_inherited_errors(i_errors, i_parents):
22    # the ordered list of errors
23    errors = list()
24    has_inheritance = False
25    for error in i_errors:
26        if(i_parents[error] is not None):
27            has_inheritance = True
28            break
29
30    if(has_inheritance):
31        # Order the error codes list such that an error is never placed
32        # before it's parent. This way generated code can ensure parent
33        # definitions preceed child error definitions.
34        while(len(errors) < len(i_errors)):
35            for error in i_errors:
36                if(error in errors):
37                    # already ordererd
38                    continue
39                if((not i_parents[error]) or (i_parents[error] in errors)):
40                    # parent present, or has no parent, either way this error
41                    # can be added
42                    errors.append(error)
43    else:
44        # no inherited errors
45        errors = i_errors
46
47    return errors
48
49
50def check_error_inheritance(i_errors, i_parents):
51    for error in i_errors:
52        if(i_parents[error] and (i_parents[error] not in i_errors)):
53            print(error + " inherits " + i_parents[error] +
54                  " but the latter is not defined")
55            return False
56    return True
57
58
59# Return the yaml files with their directory structure plus the file name
60# without the yaml extension, which will be used to set the namespaces.
61# Ex: file xyz/openbmc_project/Error/Callout/Device.errors.yaml
62# will have namespce xyz/openbmc_project/Error/Callout/Device
63def get_error_yaml_files(i_yaml_dir, i_test_dir):
64    yaml_files = dict()
65    if i_yaml_dir != "None":
66        for root, dirs, files in os.walk(i_yaml_dir):
67            for files in filter(lambda file:
68                                file.endswith('.errors.yaml'), files):
69                splitdir = root.split(i_yaml_dir)[1] + "/" + files[:-12]
70                if splitdir.startswith("/"):
71                    splitdir = splitdir[1:]
72                yaml_files[(os.path.join(root, files))] = splitdir
73    for root, dirs, files in os.walk(i_test_dir):
74        for files in filter(lambda file: file.endswith('.errors.yaml'), files):
75            splitdir = root.split(i_test_dir)[1] + "/" + files[:-12]
76            yaml_files[(os.path.join(root, files))] = splitdir
77    return yaml_files
78
79
80def get_meta_yaml_file(i_error_yaml_file):
81    # the meta data will be defined in file name where we replace
82    # <Interface>.errors.yaml with <Interface>.metadata.yaml
83    meta_yaml = i_error_yaml_file.replace("errors", "metadata")
84    return meta_yaml
85
86
87def get_cpp_type(i_type):
88    typeMap = {
89        'int16': 'int16_t',
90        'int32': 'int32_t',
91        'int64': 'int64_t',
92        'uint16': 'uint16_t',
93        'uint32': 'uint32_t',
94        'uint64': 'uint64_t',
95        'double': 'double',
96        # const char* aids usage of constexpr
97        'string': 'const char*'}
98
99    return typeMap[i_type]
100
101
102def gen_elog_hpp(i_yaml_dir, i_test_dir, i_output_hpp,
103                 i_template_dir, i_elog_mako):
104    r"""
105    Read  yaml file(s) under input yaml dir, grab the relevant data and call
106    the mako template to generate the output header file.
107
108    Description of arguments:
109    i_yaml_dir                  directory containing base error yaml files
110    i_test_dir                  directory containing test error yaml files
111    i_output_hpp                name of the to be generated output hpp
112    i_template_dir              directory containing error mako templates
113    i_elog_mako                 error mako template to render
114    """
115
116    # Input parameters to mako template
117    errors = list()  # Main error codes
118    error_msg = dict()  # Error msg that corresponds to error code
119    error_lvl = dict()  # Error code log level (debug, info, error, ...)
120    meta = dict()  # The meta data names associated (ERRNO, FILE_NAME, ...)
121    meta_data = dict()  # The meta data info (type, format)
122    parents = dict()
123
124    error_yamls = get_error_yaml_files(i_yaml_dir, i_test_dir)
125
126    for error_yaml in error_yamls:
127        # Verify the error yaml file
128        if (not (os.path.isfile(error_yaml))):
129            print("Can not find input yaml file " + error_yaml)
130            exit(1)
131
132        # Verify the metadata yaml file
133        meta_yaml = get_meta_yaml_file(error_yaml)
134        if (not (os.path.isfile(meta_yaml))):
135            print("Can not find meta yaml file " + meta_yaml)
136            exit(1)
137
138        # Verify the input mako file
139        template_path = "/".join((i_template_dir, i_elog_mako))
140        if (not (os.path.isfile(template_path))):
141            print("Can not find input template file " + template_path)
142            exit(1)
143
144        get_elog_data(error_yaml,
145                      meta_yaml,
146                      error_yamls[error_yaml],
147                      # Last arg is a tuple
148                      (errors,
149                       error_msg,
150                       error_lvl,
151                       meta,
152                       meta_data,
153                       parents))
154
155    if(not check_error_inheritance(errors, parents)):
156        print("Error - failed to validate error inheritance")
157        exit(1)
158
159    errors = order_inherited_errors(errors, parents)
160
161    # Load the mako template and call it with the required data
162    yaml_dir = i_yaml_dir.strip("./")
163    yaml_dir = yaml_dir.strip("../")
164    template = Template(filename=template_path)
165    f = open(i_output_hpp, 'w')
166    f.write(template.render(
167            errors=errors, error_msg=error_msg,
168            error_lvl=error_lvl, meta=meta,
169            meta_data=meta_data,
170            parents=parents))
171    f.close()
172
173
174def get_elog_data(i_elog_yaml,
175                  i_elog_meta_yaml,
176                  i_namespace,
177                  o_elog_data):
178    r"""
179    Parse the error and metadata yaml files in order to pull out
180    error metadata.
181
182    Description of arguments:
183    i_elog_yaml                 error yaml file
184    i_elog_meta_yaml            metadata yaml file
185    i_namespace                 namespace data
186    o_elog_data                 error metadata
187    """
188    errors, error_msg, error_lvl, meta, meta_data, parents = o_elog_data
189    ifile = yaml.safe_load(open(i_elog_yaml))
190    mfile = yaml.safe_load(open(i_elog_meta_yaml))
191    for i in mfile:
192        match = None
193        # Find the corresponding meta data for this entry
194        for j in ifile:
195            if j['name'] == i['name']:
196                match = j
197                break
198        if (match is None):
199            print("Error - Did not find meta data for " + i['name'])
200            exit(1)
201        # Grab the main error and it's info
202        fullname = i_namespace.replace('/', '.') + ('.') + i['name']
203        errors.append(fullname)
204        parent = None
205        if('inherits' in i):
206            # Get 0th inherited error (current support - single inheritance)
207            parent = i['inherits'][0]
208        parents[fullname] = parent
209        error_msg[fullname] = match['description']
210        try:
211            error_lvl[fullname] = i['level']
212        except:
213            print ("No level found for: " + i['name'] + ", using INFO")
214            error_lvl[fullname] = "INFO"
215        tmp_meta = []
216        # grab all the meta data fields and info
217        for j in i['meta']:
218            str_short = j['str'].split('=')[0]
219            tmp_meta.append(str_short)
220            meta_data[str_short] = {}
221            meta_data[str_short]['str'] = j['str']
222            meta_data[str_short]['str_short'] = str_short
223            meta_data[str_short]['type'] = get_cpp_type(j['type'])
224        meta[fullname] = tmp_meta
225
226    # Debug
227    # for i in errors:
228    #   print "ERROR: " + errors[i]
229    #   print " MSG:  " + error_msg[errors[i]]
230    #   print " LVL:  " + error_lvl[errors[i]]
231    #   print " META: "
232    #   print meta[i]
233
234
235def main(i_args):
236    parser = OptionParser()
237
238    parser.add_option("-m", "--mako", dest="elog_mako",
239                      default="elog-gen-template.mako.hpp",
240                      help="input mako template file to use")
241
242    parser.add_option("-o", "--output", dest="output_hpp",
243                      default="elog-errors.hpp",
244                      help="output hpp to generate, elog-errors.hpp default")
245
246    parser.add_option("-y", "--yamldir", dest="yamldir",
247                      default="None",
248                      help="Base directory of yaml files to process")
249
250    parser.add_option("-u", "--testdir", dest="testdir",
251                      default="./tools/example/",
252                      help="Unit test directory of yaml files to process")
253
254    parser.add_option("-t", "--templatedir", dest="templatedir",
255                      default="phosphor-logging/templates/",
256                      help="Base directory of files to process")
257
258    (options, args) = parser.parse_args(i_args)
259
260    gen_elog_hpp(options.yamldir,
261                 options.testdir,
262                 options.output_hpp,
263                 options.templatedir,
264                 options.elog_mako)
265
266# Only run if it's a script
267if __name__ == '__main__':
268    main(sys.argv[1:])
269