xref: /openbmc/phosphor-logging/tools/elog-gen.py (revision 17944e13e5d00d17427b5d49c6e84dbfd0033b45)
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
59def get_error_yaml_files(i_yaml_dir):
60    yaml_files = filter(
61        lambda file: file.endswith('.errors.yaml'),
62        os.listdir(i_yaml_dir))
63    return yaml_files
64
65
66def get_meta_yaml_file(i_error_yaml_file):
67    # the meta data will be defined in file name where we replace
68    # <Interface>.errors.yaml with <Interface>.metadata.yaml
69    meta_yaml = i_error_yaml_file.replace("errors", "metadata")
70    return meta_yaml
71
72
73def get_cpp_type(i_type):
74    typeMap = {
75        'int16': 'int16_t',
76        'int32': 'int32_t',
77        'int64': 'int64_t',
78        'uint16': 'uint16_t',
79        'uint32': 'uint32_t',
80        'uint64': 'uint64_t',
81        'double': 'double',
82        # const char* aids usage of constexpr
83        'string': 'const char*'}
84
85    return typeMap[i_type]
86
87
88def gen_elog_hpp(i_yaml_dir, i_output_hpp,
89                 i_template_dir, i_elog_mako, i_error_namespace):
90    r"""
91    Read  yaml file(s) under input yaml dir, grab the relevant data and call
92    the mako template to generate the output header file.
93
94    Description of arguments:
95    i_yaml_dir                  directory containing error yaml files
96    i_output_hpp                name of the to be generated output hpp
97    i_template_dir              directory containing error mako templates
98    i_elog_mako                 error mako template to render
99    """
100
101    # Input parameters to mako template
102    errors = list()  # Main error codes
103    error_msg = dict()  # Error msg that corresponds to error code
104    error_lvl = dict()  # Error code log level (debug, info, error, ...)
105    meta = dict()  # The meta data names associated (ERRNO, FILE_NAME, ...)
106    meta_data = dict()  # The meta data info (type, format)
107    parents = dict()
108
109    error_yamls = get_error_yaml_files(i_yaml_dir)
110
111    for error_yaml in error_yamls:
112        # Verify the error yaml file
113        error_yaml = "/".join((i_yaml_dir, error_yaml))
114        if (not (os.path.isfile(error_yaml))):
115            print("Can not find input yaml file " + error_yaml)
116            exit(1)
117
118        # Verify the metadata yaml file
119        meta_yaml = get_meta_yaml_file(error_yaml)
120        if (not (os.path.isfile(meta_yaml))):
121            print("Can not find meta yaml file " + meta_yaml)
122            exit(1)
123
124        # Verify the input mako file
125        template_path = "/".join((i_template_dir, i_elog_mako))
126        if (not (os.path.isfile(template_path))):
127            print("Can not find input template file " + template_path)
128            exit(1)
129
130        get_elog_data(error_yaml,
131                      meta_yaml,
132                      # 3rd arg is a tuple
133                      (errors,
134                       error_msg,
135                       error_lvl,
136                       meta,
137                       meta_data,
138                       parents))
139
140    if(not check_error_inheritance(errors, parents)):
141        print("Error - failed to validate error inheritance")
142        exit(1)
143
144    errors = order_inherited_errors(errors, parents)
145
146    # Load the mako template and call it with the required data
147    yaml_dir = i_yaml_dir.strip("./")
148    yaml_dir = yaml_dir.strip("../")
149    template = Template(filename=template_path)
150    f = open(i_output_hpp, 'w')
151    f.write(template.render(
152            errors=errors, error_msg=error_msg,
153            error_lvl=error_lvl, meta=meta,
154            meta_data=meta_data, error_namespace=i_error_namespace,
155            parents=parents))
156    f.close()
157
158
159def get_elog_data(i_elog_yaml,
160                  i_elog_meta_yaml,
161                  o_elog_data):
162    r"""
163    Parse the error and metadata yaml files in order to pull out
164    error metadata.
165
166    Description of arguments:
167    i_elog_yaml                 error yaml file
168    i_elog_meta_yaml            metadata yaml file
169    o_elog_data                 error metadata
170    """
171    errors, error_msg, error_lvl, meta, meta_data, parents = o_elog_data
172    ifile = yaml.safe_load(open(i_elog_yaml))
173    mfile = yaml.safe_load(open(i_elog_meta_yaml))
174    for i in mfile:
175        match = None
176        # Find the corresponding meta data for this entry
177        for j in ifile:
178            if j['name'] == i['name']:
179                match = j
180                break
181        if (match is None):
182            print("Error - Did not find meta data for " + i['name'])
183            exit(1)
184        # Grab the main error and it's info
185        errors.append(i['name'])
186        parent = None
187        if('inherits' in i):
188            # xyz.openbmc.Foo, we need Foo
189            # Get 0th inherited error (current support - single inheritance)
190            parent = i['inherits'][0].split(".").pop()
191        parents[i['name']] = parent
192        error_msg[i['name']] = match['description']
193        error_lvl[i['name']] = i['level']
194        tmp_meta = []
195        # grab all the meta data fields and info
196        for j in i['meta']:
197            str_short = j['str'].split('=')[0]
198            tmp_meta.append(str_short)
199            meta_data[str_short] = {}
200            meta_data[str_short]['str'] = j['str']
201            meta_data[str_short]['str_short'] = str_short
202            meta_data[str_short]['type'] = get_cpp_type(j['type'])
203        meta[i['name']] = tmp_meta
204
205    # Debug
206    # for i in errors:
207    #   print "ERROR: " + errors[i]
208    #   print " MSG:  " + error_msg[errors[i]]
209    #   print " LVL:  " + error_lvl[errors[i]]
210    #   print " META: "
211    #   print meta[i]
212
213
214def main(i_args):
215    parser = OptionParser()
216
217    parser.add_option("-m", "--mako", dest="elog_mako",
218                      default="elog-gen-template.mako.hpp",
219                      help="input mako template file to use")
220
221    parser.add_option("-o", "--output", dest="output_hpp",
222                      default="elog-gen.hpp",
223                      help="output hpp to generate, elog-gen.hpp is default")
224
225    parser.add_option("-y", "--yamldir", dest="yamldir",
226                      default="./example/xyz/openbmc_project/Example",
227                      help="Base directory of yaml files to process")
228
229    parser.add_option("-t", "--templatedir", dest="templatedir",
230                      default="phosphor-logging/templates/",
231                      help="Base directory of files to process")
232
233    parser.add_option("-n", "--namespace", dest="error_namespace",
234                      default="example/xyz/openbmc_project/Example",
235                      help="Error d-bus namespace")
236
237    (options, args) = parser.parse_args(i_args)
238
239    gen_elog_hpp(options.yamldir,
240                 options.output_hpp,
241                 options.templatedir,
242                 options.elog_mako,
243                 options.error_namespace)
244
245# Only run if it's a script
246if __name__ == '__main__':
247    main(sys.argv[1:])
248