xref: /openbmc/phosphor-logging/tools/elog-gen.py (revision 5d1aace69bea0c76229335dcdc09ec63eda63931)
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    f.close()
156
157
158def get_elog_data(i_elog_yaml,
159                  i_elog_meta_yaml,
160                  o_elog_data):
161    r"""
162    Parse the error and metadata yaml files in order to pull out
163    error metadata.
164
165    Description of arguments:
166    i_elog_yaml                 error yaml file
167    i_elog_meta_yaml            metadata yaml file
168    o_elog_data                 error metadata
169    """
170    errors, error_msg, error_lvl, meta, meta_data, parents = o_elog_data
171    ifile = yaml.safe_load(open(i_elog_yaml))
172    mfile = yaml.safe_load(open(i_elog_meta_yaml))
173    for i in ifile:
174        match = None
175        # Find the corresponding meta data for this entry
176        for m in mfile:
177            if m['name'] == i['name']:
178                match = m
179                break
180        if (match is None):
181            print "Error - Did not find meta data for " + i['name']
182            exit(1)
183        # Grab the main error and it's info
184        errors.append(i['name'])
185        parent = None
186        if('inherits' in i):
187            # xyz.openbmc.Foo, we need Foo
188            # Get 0th inherited error (current support - single inheritance)
189            parent = i['inherits'][0].split(".").pop()
190        parents[i['name']] = parent
191        error_msg[i['name']] = i['description']
192        error_lvl[i['name']] = match['level']
193        tmp_meta = []
194        # grab all the meta data fields and info
195        for j in match['meta']:
196            str_short = j['str'].split('=')[0]
197            tmp_meta.append(str_short)
198            meta_data[str_short] = {}
199            meta_data[str_short]['str'] = j['str']
200            meta_data[str_short]['str_short'] = str_short
201            meta_data[str_short]['type'] = get_cpp_type(j['type'])
202        meta[i['name']] = tmp_meta
203
204    # Debug
205    # for i in errors:
206    #   print "ERROR: " + errors[i]
207    #   print " MSG:  " + error_msg[errors[i]]
208    #   print " LVL:  " + error_lvl[errors[i]]
209    #   print " META: "
210    #   print meta[i]
211
212
213def main(i_args):
214    parser = OptionParser()
215
216    parser.add_option("-m", "--mako", dest="elog_mako",
217                      default="elog-gen-template.mako.hpp",
218                      help="input mako template file to use")
219
220    parser.add_option("-o", "--output", dest="output_hpp",
221                      default="elog-gen.hpp",
222                      help="output hpp to generate, elog-gen.hpp is default")
223
224    parser.add_option("-y", "--yamldir", dest="yamldir",
225                      default="./example/xyz/openbmc_project/Example",
226                      help="Base directory of yaml files to process")
227
228    parser.add_option("-t", "--templatedir", dest="templatedir",
229                      default="phosphor-logging/templates/",
230                      help="Base directory of files to process")
231
232    parser.add_option("-n", "--namespace", dest="error_namespace",
233                      default="example/xyz/openbmc_project/Example",
234                      help="Error d-bus namespace")
235
236    (options, args) = parser.parse_args(i_args)
237
238    gen_elog_hpp(options.yamldir,
239                 options.output_hpp,
240                 options.templatedir,
241                 options.elog_mako,
242                 options.error_namespace)
243
244# Only run if it's a script
245if __name__ == '__main__':
246    main(sys.argv[1:])
247