3
# Copyright 2016 Jeremy Kerr <jk@ozlabs.org>
5
# Licensed under the Apache License, Version 2.0 (the "License");
6
# you may not use this file except in compliance with the License.
7
# You may obtain a copy of the License at
9
# http://www.apache.org/licenses/LICENSE-2.0
11
# Unless required by applicable law or agreed to in writing, software
12
# distributed under the License is distributed on an "AS IS" BASIS,
13
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
15
# See the License for the specific language governing permissions and
16
# limitations under the License.
24
from pyparsing import Regex, Literal, Word, Combine, OneOrMore, QuotedString, \
30
'separators': (',', ': '),
34
# Match a C-style comment starting with two *s
35
comment = Regex(r'/\*\*(?P<content>.*?)\*/', re.DOTALL)
37
# Match an @fwts-<tag> annotation (within the comment), plus the proceeding
39
annotation = Regex(r'@fwts-(?P<tag>\w+)\W+(?P<text>.*?)(?=@fwts-|\Z)',
42
# Match the following prlog() call
43
log_call = (Literal("prlog") +
44
Literal('(').suppress() +
45
Word(string.letters + string.digits + '_') +
46
Literal(',').suppress() +
47
Combine(OneOrMore(QuotedString('"')), adjacent=False) +
48
(Literal(')') | Literal(',')).suppress()
51
pattern = comment + log_call
52
pattern.setWhitespaceChars(string.whitespace + '\n')
54
def comment_action(tok):
56
for result in annotation.scanString(tok['content']):
57
patterns.update(result[0][0])
60
def annotation_action(tok):
62
tok['tag']: cleanup_content(tok['text'])
65
comment.setParseAction(comment_action)
66
annotation.setParseAction(annotation_action)
67
pattern.parseWithTabs()
71
def find_sources(dirname):
75
return fname.endswith('.c')
77
def add_fn(s, dname, fnames):
78
s.extend([ os.path.join(dname, fname)
79
for fname in fnames if is_source(fname) ])
81
os.path.walk(dirname, add_fn, sources)
84
def cleanup_content(content):
85
comment_prefix_re = re.compile(r'^\s*\*\s*', re.MULTILINE)
86
whitespace_re = re.compile(r'\s+')
88
content = comment_prefix_re.sub(' ', content)
89
content = whitespace_re.sub(' ', content)
90
return content.strip()
92
def warn(loc, message):
93
print >>sys.stderr, 'WARNING:%s:%d: %s' % (loc[0], loc[1], message)
95
def log_level_to_fwts(level):
97
'PR_EMERG': 'LOG_LEVEL_CRITICAL',
98
'PR_ALERT': 'LOG_LEVEL_CRITICAL',
99
'PR_CRIT': 'LOG_LEVEL_CRITICAL',
100
'PR_ERR': 'LOG_LEVEL_CRITICAL',
101
'PR_WARNING': 'LOG_LEVEL_HIGH',
102
'PR_NOTICE': 'LOG_LEVEL_MEDIUM',
103
'PR_PRINTF': 'LOG_LEVEL_MEDIUM',
105
return level_map.get(level, 'LOG_LEVEL_LOW')
107
def message_to_pattern(loc, msg):
108
""" Convert a C printf()-style template to a pattern suitable for fwts """
110
# Somewhat-simplified match for a %-template
111
template_re = re.compile(
112
'%(?P<flag>[-#0 +]*)'
113
'(?P<width>(?:[0-9]*|\*))?'
114
'(?P<precision>\.*(?:[1-9][0-9]*|\*))?'
115
'(?:hh|h|ll|l|L|j|z|t)?'
116
'(?P<conversion>[a-zA-Z%])')
120
def expand_template(match):
122
c = match.group('conversion').lower()
126
if c in ['d', 'i', 'u']:
133
return '(0x[0-9a-f]+|nil)'
137
warn(loc, "Unknown template conversion '%s'" % match.group(0))
140
escape_re = re.compile(r'\\(?P<char>.)', re.DOTALL)
141
def expand_escape(match):
143
c = match.group('char')
146
elif c in ['\\', '"']:
149
warn(loc, "Unhandled escape sequence '%s'" % match.group(0))
153
pattern = template_re.sub(expand_template, msg)
154
pattern = escape_re.sub(expand_escape, pattern)
155
pattern = pattern.strip()
157
compare_mode = "string"
159
compare_mode = "regex"
161
return (compare_mode, pattern)
163
def parse_patterns(parser, fname):
165
data = open(fname).read()
167
for result in parser.scanString(data):
168
(token, loc, _) = result
169
(annotations, logfn, level, msg) = token
171
loc = (fname, lineno(loc, data))
174
warn(loc, "unknown log output function '%s'" % logfn)
176
compare_mode, pattern_str = message_to_pattern(loc, msg)
179
'log_level': log_level_to_fwts(level),
180
'compare_mode': compare_mode,
181
'pattern': pattern_str,
184
pattern.update(annotations)
186
if not 'label' in pattern:
187
warn(loc, "missing label")
188
pattern['label'] = '%s:%d' % (fname, i)
191
if not 'advice' in pattern:
192
warn(loc, "missing advice")
194
allowed_data = ['compare_mode', 'log_level',
195
'pattern', 'advice', 'label']
196
extras = set(pattern.keys()) - set(allowed_data)
198
warn(loc, "unknown pattern annotation: %s" %
199
','.join([ "'%s'" % e for e in extras]))
203
patterns.append(pattern)
207
if __name__ == '__main__':
208
argparser = argparse.ArgumentParser(
209
description='Generate FWTS olog definitions from the skiboot '
211
argparser.add_argument('directories', metavar='DIR', nargs='*',
212
help='path to source files (default .)', default=['.'])
213
argparser.add_argument('--output', '-o', metavar='FILE',
214
type=argparse.FileType('w'), default=sys.stdout,
215
help='output to FILE (default to stdout)', nargs='?')
216
args = argparser.parse_args()
219
for directory in args.directories:
220
sources.extend(find_sources(directory))
222
parser = create_parser()
224
for source in sources:
225
patterns.extend(parse_patterns(parser, source))
227
data = {'olog_error_warning_patterns': patterns}
229
args.output.write(json.dumps(data, **json_params) + '\n')