summaryrefslogtreecommitdiffstats
path: root/tools/elog-gen.py
blob: 825fc0f0009269a41c68350ec89d450440105960 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
#!/usr/bin/env python

r"""
This script will parse error log yaml file(s) and generate
a header file which will then be used by the error logging client and
server to collect and validate the error information generated by the
openbmc software components.

This code uses a mako template to provide the basic template of the header
file we're going to generate.  We then call it with information from the
yaml to generate the header file.
"""

from mako.template import Template
from optparse import OptionParser
import yaml
import sys
import os


def order_inherited_errors(i_errors, i_parents):
    # the ordered list of errors
    errors = list()
    has_inheritance = False
    for error in i_errors:
        if(i_parents[error] is not None):
            has_inheritance = True
            break

    if(has_inheritance):
        # Order the error codes list such that an error is never placed
        # before it's parent. This way generated code can ensure parent
        # definitions precede child error definitions.
        while(len(errors) < len(i_errors)):
            for error in i_errors:
                if(error in errors):
                    # already ordererd
                    continue
                if((not i_parents[error]) or (i_parents[error] in errors)):
                    # parent present, or has no parent, either way this error
                    # can be added
                    errors.append(error)
    else:
        # no inherited errors
        errors = i_errors

    return errors


def check_error_inheritance(i_errors, i_parents):
    for error in i_errors:
        if(i_parents[error] and (i_parents[error] not in i_errors)):
            print(error + " inherits " + i_parents[error] +
                  " but the latter is not defined")
            return False
    return True


# Return the yaml files with their directory structure plus the file name
# without the yaml extension, which will be used to set the namespaces.
# Ex: file xyz/openbmc_project/Error/Callout/Device.errors.yaml
# will have namespce xyz/openbmc_project/Error/Callout/Device
def get_error_yaml_files(i_yaml_dir, i_test_dir):
    yaml_files = dict()
    if i_yaml_dir != "None":
        for root, dirs, files in os.walk(i_yaml_dir):
            for files in filter(lambda file:
                                file.endswith('.errors.yaml'), files):
                splitdir = root.split(i_yaml_dir)[1] + "/" + files[:-12]
                if splitdir.startswith("/"):
                    splitdir = splitdir[1:]
                yaml_files[(os.path.join(root, files))] = splitdir
    for root, dirs, files in os.walk(i_test_dir):
        for files in filter(lambda file: file.endswith('.errors.yaml'), files):
            splitdir = root.split(i_test_dir)[1] + "/" + files[:-12]
            yaml_files[(os.path.join(root, files))] = splitdir
    return yaml_files


def get_meta_yaml_file(i_error_yaml_file):
    # the meta data will be defined in file name where we replace
    # <Interface>.errors.yaml with <Interface>.metadata.yaml
    meta_yaml = i_error_yaml_file.replace("errors", "metadata")
    return meta_yaml


def get_cpp_type(i_type):
    typeMap = {
        'int16': 'int16_t',
        'int32': 'int32_t',
        'int64': 'int64_t',
        'uint16': 'uint16_t',
        'uint32': 'uint32_t',
        'uint64': 'uint64_t',
        'double': 'double',
        # const char* aids usage of constexpr
        'string': 'const char*'}

    return typeMap[i_type]


def gen_elog_hpp(i_yaml_dir, i_test_dir, i_output_hpp,
                 i_template_dir, i_elog_mako):
    r"""
    Read  yaml file(s) under input yaml dir, grab the relevant data and call
    the mako template to generate the output header file.

    Description of arguments:
    i_yaml_dir                  directory containing base error yaml files
    i_test_dir                  directory containing test error yaml files
    i_output_hpp                name of the to be generated output hpp
    i_template_dir              directory containing error mako templates
    i_elog_mako                 error mako template to render
    """

    # Input parameters to mako template
    errors = list()  # Main error codes
    error_msg = dict()  # Error msg that corresponds to error code
    error_lvl = dict()  # Error code log level (debug, info, error, ...)
    meta = dict()  # The meta data names associated (ERRNO, FILE_NAME, ...)
    meta_data = dict()  # The meta data info (type, format)
    parents = dict()
    metadata_process = dict()  # metadata that have the 'process' keyword set

    error_yamls = get_error_yaml_files(i_yaml_dir, i_test_dir)

    for error_yaml in error_yamls:
        # Verify the error yaml file
        if (not (os.path.isfile(error_yaml))):
            print("Cannot find input yaml file " + error_yaml)
            exit(1)

        # Verify the metadata yaml file
        meta_yaml = get_meta_yaml_file(error_yaml)

        # Verify the input mako file
        template_path = "/".join((i_template_dir, i_elog_mako))
        if (not (os.path.isfile(template_path))):
            print("Cannot find input template file " + template_path)
            exit(1)

        get_elog_data(error_yaml,
                      meta_yaml,
                      error_yamls[error_yaml],
                      # Last arg is a tuple
                      (errors,
                       error_msg,
                       error_lvl,
                       meta,
                       meta_data,
                       parents,
                       metadata_process))

    if(not check_error_inheritance(errors, parents)):
        print("Error - failed to validate error inheritance")
        exit(1)

    errors = order_inherited_errors(errors, parents)

    # Load the mako template and call it with the required data
    yaml_dir = i_yaml_dir.strip("./")
    yaml_dir = yaml_dir.strip("../")
    template = Template(filename=template_path)
    f = open(i_output_hpp, 'w')
    f.write(template.render(
            errors=errors,
            error_msg=error_msg,
            error_lvl=error_lvl,
            meta=meta,
            meta_data=meta_data,
            parents=parents,
            metadata_process=metadata_process))
    f.close()

def get_elog_data(i_elog_yaml,
                  i_elog_meta_yaml,
                  i_namespace,
                  o_elog_data):
    r"""
    Parse the error and metadata yaml files in order to pull out
    error metadata.

    Use default values if metadata yaml file is not found.

    Description of arguments:
    i_elog_yaml                 error yaml file
    i_elog_meta_yaml            metadata yaml file
    i_namespace                 namespace data
    o_elog_data                 error metadata
    """
    (errors, error_msg, error_lvl, meta,
     meta_data, parents, metadata_process) = o_elog_data
    ifile = yaml.safe_load(open(i_elog_yaml))

    #for all the errors in error yaml file
    for error in ifile:
        if 'name' not in error:
            print("Error - Did not find name in entry %s in file %s " % (
                str(error), i_elog_yaml))
            exit(1)
        fullname = i_namespace.replace('/', '.') + ('.') + error['name']
        errors.append(fullname)

        if 'description' in error:
            error_msg[fullname] = error['description'].strip()

        #set default values
        error_lvl[fullname] = "ERR"
        parents[fullname] = None

        #check if meta data yaml file is found
        if not os.path.isfile(i_elog_meta_yaml):
            continue
        mfile = yaml.safe_load(open(i_elog_meta_yaml))

        # Find the meta data entry
        match = None
        for meta_entry in mfile:
            if meta_entry['name'] == error['name']:
                match = meta_entry
                break

        if match is None:
            print("Error - Did not find name in" + i_elog_meta_yaml)
            continue

        error_lvl[fullname] = match.get('level', 'ERR')

        # Get 0th inherited error (current support - single inheritance)
        if 'inherits' in match:
            parents[fullname]  = match['inherits'][0]

        # Put all errors in meta[] even the meta is empty
        # so that child errors could inherits such error without meta
        tmp_meta = []
        if 'meta' in match:
            # grab all the meta data fields and info
            for i in match['meta']:
                str_short = i['str'].split('=')[0]
                tmp_meta.append(str_short)
                meta_data[str_short] = {}
                meta_data[str_short]['str'] = i['str']
                meta_data[str_short]['str_short'] = str_short
                meta_data[str_short]['type'] = get_cpp_type(i['type'])
                if ('process' in i) and (True == i['process']):
                    metadata_process[str_short] = fullname + "." + str_short
        meta[fullname] = tmp_meta

    # Debug
    # for i in errors:
    #   print "ERROR: " + errors[i]
    #   print " MSG:  " + error_msg[errors[i]]
    #   print " LVL:  " + error_lvl[errors[i]]
    #   print " META: "
    #   print meta[i]


def main(i_args):
    parser = OptionParser()

    parser.add_option("-m", "--mako", dest="elog_mako",
                      default="elog-gen-template.mako.hpp",
                      help="input mako template file to use")

    parser.add_option("-o", "--output", dest="output_hpp",
                      default="elog-errors.hpp",
                      help="output hpp to generate, elog-errors.hpp default")

    parser.add_option("-y", "--yamldir", dest="yamldir",
                      default="None",
                      help="Base directory of yaml files to process")

    parser.add_option("-u", "--testdir", dest="testdir",
                      default="./tools/example/",
                      help="Unit test directory of yaml files to process")

    parser.add_option("-t", "--templatedir", dest="templatedir",
                      default="phosphor-logging/templates/",
                      help="Base directory of files to process")

    (options, args) = parser.parse_args(i_args)

    gen_elog_hpp(options.yamldir,
                 options.testdir,
                 options.output_hpp,
                 options.templatedir,
                 options.elog_mako)

# Only run if it's a script
if __name__ == '__main__':
    main(sys.argv[1:])
OpenPOWER on IntegriCloud