Validate stream event context
[deliverable/barectf.git] / barectf / cli.py
1 # The MIT License (MIT)
2 #
3 # Copyright (c) 2014 Philippe Proulx <philippe.proulx@efficios.com>
4 #
5 # Permission is hereby granted, free of charge, to any person obtaining a copy
6 # of this software and associated documentation files (the "Software"), to deal
7 # in the Software without restriction, including without limitation the rights
8 # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 # copies of the Software, and to permit persons to whom the Software is
10 # furnished to do so, subject to the following conditions:
11 #
12 # The above copyright notice and this permission notice shall be included in
13 # all copies or substantial portions of the Software.
14 #
15 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 # THE SOFTWARE.
22 from termcolor import cprint, colored
23 import argparse
24 import pytsdl.tsdl
25 import pytsdl.parser
26 import sys
27 import os
28 import re
29
30
31 def _perror(msg, exit_code=1):
32 cprint('Error: {}'.format(msg), 'red', attrs=['bold'], file=sys.stderr)
33 sys.exit(exit_code)
34
35
36 def _pinfo(msg):
37 cprint(':: {}'.format(msg), 'blue', attrs=['bold'], file=sys.stderr)
38
39
40 def _parse_args():
41 ap = argparse.ArgumentParser()
42
43 ap.add_argument('-O', '--output', metavar='OUTPUT', action='store',
44 default=os.getcwd(),
45 help='output directory of C files')
46 ap.add_argument('-p', '--prefix', metavar='PREFIX', action='store',
47 default='barectf',
48 help='custom prefix for C function and structure names')
49 ap.add_argument('-s', '--static-inline', action='store_true',
50 help='generate static inline C functions')
51 ap.add_argument('-c', '--manual-clock', action='store_true',
52 help='do not use a clock callback: pass clock value to tracing functions')
53 ap.add_argument('metadata', metavar='METADATA', action='store',
54 help='CTF metadata input file')
55
56 # parse args
57 args = ap.parse_args()
58
59 # validate output directory
60 if not os.path.isdir(args.output):
61 _perror('"{}" is not an existing directory'.format(args.output))
62
63 # validate prefix
64 if not re.match(r'^[a-zA-Z_][a-zA-Z0-9_]*$', args.prefix):
65 _perror('"{}" is not a valid C identifier'.format(args.prefix))
66
67 # validate that metadata file exists
68 if not os.path.isfile(args.metadata):
69 _perror('"{}" is not an existing file'.format(args.metadata))
70
71 return args
72
73
74 def _validate_struct(struct):
75 if type(struct) is not pytsdl.tsdl.Struct:
76 raise RuntimeError('expecting a struct')
77
78 for name, ftype in struct.fields.items():
79 if type(ftype) is pytsdl.tsdl.Sequence:
80 raise RuntimeError('field "{}" is a dynamic array'.format(name))
81 elif type(ftype) is pytsdl.tsdl.Array:
82 end = False
83 element = ftype.element
84
85 while not end:
86 if type(element) is pytsdl.tsdl.Sequence:
87 raise RuntimeError('field "{}" contains a dynamic array'.format(name))
88 elif type(element) is pytsdl.tsdl.Variant:
89 raise RuntimeError('field "{}" contains a variant (unsupported)'.format(name))
90 elif type(element) is pytsdl.tsdl.String:
91 raise RuntimeError('field "{}" contains a string'.format(name))
92 elif type(element) is pytsdl.tsdl.Struct:
93 _validate_struct(element)
94
95 if type(element) is pytsdl.tsdl.Array:
96 element = element.element
97 else:
98 end = True
99 elif type(ftype) is pytsdl.tsdl.Variant:
100 raise RuntimeError('field "{}" is a variant (unsupported)'.format(name))
101 elif type(ftype) is pytsdl.tsdl.String:
102 raise RuntimeError('field "{}" is a string'.format(name))
103 elif type(ftype) is pytsdl.tsdl.Struct:
104 _validate_struct(ftype)
105
106
107 def _validate_context_field(struct):
108 if type(struct) is not pytsdl.tsdl.Struct:
109 raise RuntimeError('expecting a struct')
110
111 for name, ftype in struct.fields.items():
112 if type(ftype) is pytsdl.tsdl.Variant:
113 raise RuntimeError('field "{}" is a variant (unsupported)'.format(name))
114 elif type(ftype) is pytsdl.tsdl.Struct:
115 _validate_struct(ftype)
116
117
118 def _validate_integer(integer, size=None, align=None, signed=None):
119 if type(integer) is not pytsdl.tsdl.Integer:
120 raise RuntimeError('expected integer')
121
122 if size is not None:
123 if integer.size != size:
124 raise RuntimeError('expected {}-bit integer'.format(size))
125
126 if align is not None:
127 if integer.align != align:
128 raise RuntimeError('expected integer with {}-bit alignment'.format(align))
129
130 if signed is not None:
131 if integer.signed != signed:
132 raise RuntimeError('expected {} integer'.format('signed' if signed else 'unsigned'))
133
134
135 def _validate_packet_header(packet_header):
136 try:
137 _validate_struct(packet_header)
138 except RuntimeError as e:
139 _perror('packet header: {}'.format(e))
140
141 # magic must be the first field
142 if 'magic' in packet_header.fields:
143 if list(packet_header.fields.keys())[0] != 'magic':
144 _perror('packet header: "magic" must be the first field')
145 else:
146 _perror('packet header: missing "magic" field')
147
148 # magic must be a 32-bit unsigned integer, 32-bit aligned
149 try:
150 _validate_integer(packet_header['magic'], 32, 32, False)
151 except RuntimeError as e:
152 _perror('packet header: "magic": {}'.format(e))
153
154 # mandatory stream_id
155 if 'stream_id' not in packet_header.fields:
156 _perror('packet header: missing "stream_id" field')
157
158 # stream_id must be an unsigned integer
159 try:
160 _validate_integer(packet_header['stream_id'], signed=False)
161 except RuntimeError as e:
162 _perror('packet header: "stream_id": {}'.format(e))
163
164
165 def _dot_name_to_str(name):
166 return '.'.join(name)
167
168
169 def _validate_clock(doc, name):
170 msg = '"{}" does not name an existing clock'.format(_dot_name_to_str(name))
171
172 if len(name) != 3:
173 raise RuntimeError(msg)
174
175 if name[0] != 'clock' or name[2] != 'value':
176 raise RuntimeError()
177
178 if name[1] not in doc.clocks:
179 raise RuntimeError(msg)
180
181
182 def _compare_integers(int1, int2):
183 if type(int1) is not pytsdl.tsdl.Integer:
184 return False
185
186 if type(int2) is not pytsdl.tsdl.Integer:
187 return False
188
189 size = int1.size == int2.size
190 align = int1.align == int2.align
191 cmap = int1.map == int2.map
192 base = int1.base == int2.base
193 encoding = int1.encoding == int2.encoding
194 signed = int1.signed == int2.signed
195 comps = (size, align, cmap, base, encoding, signed)
196
197 return sum(comps) == len(comps)
198
199
200 def _validate_packet_context(doc, stream):
201 packet_context = stream.packet_context
202 sid = stream.id
203
204 try:
205 _validate_struct(packet_context)
206 except RuntimeError as e:
207 _perror('stream {}: packet context: {}'.format(sid, e))
208
209 fields = packet_context.fields
210
211 # if timestamp_begin exists, timestamp_end must exist
212 if 'timestamp_begin' in fields or 'timestamp_end' in fields:
213 if 'timestamp_begin' not in fields or 'timestamp_end' not in fields:
214 _perror('stream {}: packet context: "timestamp_begin" must exist if "timestamp_end" exists'.format(sid))
215 else:
216 # timestamp_begin and timestamp_end must have the same integer
217 # as the event header's timestamp field (should exist by now)
218 timestamp = stream.event_header['timestamp']
219
220 if not _compare_integers(fields['timestamp_begin'], timestamp):
221 _perror('stream {}: packet context: "timestamp_begin": integer type different from event header\'s "timestamp" field'.format(sid))
222
223 if not _compare_integers(fields['timestamp_end'], timestamp):
224 _perror('stream {}: packet context: "timestamp_end": integer type different from event header\'s "timestamp" field'.format(sid))
225
226 # content_size must exist and be an unsigned integer
227 if 'content_size' not in fields:
228 _perror('stream {}: packet context: missing "content_size" field'.format(sid))
229
230 try:
231 _validate_integer(fields['content_size'], 32, 32, False)
232 except:
233 try:
234 _validate_integer(fields['content_size'], 64, 64, False)
235 except:
236 _perror('stream {}: packet context: "content_size": expecting unsigned 32-bit/64-bit integer'.format(sid))
237
238 # packet_size must exist and be an unsigned integer
239 if 'packet_size' not in fields:
240 _perror('stream {}: packet context: missing "packet_size" field'.format(sid))
241
242 try:
243 _validate_integer(fields['packet_size'], 32, 32, False)
244 except:
245 try:
246 _validate_integer(fields['packet_size'], 64, 64, False)
247 except:
248 _perror('stream {}: packet context: "packet_size": expecting unsigned 32-bit/64-bit integer'.format(sid))
249
250 # if cpu_id exists, must be an unsigned integer
251 if 'cpu_id' in fields:
252 try:
253 _validate_integer(fields['cpu_id'], signed=False)
254 except RuntimeError as e:
255 _perror('stream {}: packet context: "cpu_id": {}'.format(sid, e))
256
257
258 def _validate_event_header(doc, stream):
259 event_header = stream.event_header
260 sid = stream.id
261
262 try:
263 _validate_struct(event_header)
264 except RuntimeError as e:
265 _perror('stream {}: event header: {}'.format(sid, e))
266
267 fields = event_header.fields
268
269 # id must exist and be an unsigned integer
270 if 'id' not in fields:
271 _perror('stream {}: event header: missing "id" field'.format(sid))
272
273 try:
274 _validate_integer(fields['id'], signed=False)
275 except RuntimeError as e:
276 _perror('stream {}: "id": {}'.format(sid, format(e)))
277
278
279 # timestamp must exist, be an unsigned integer and be mapped to a valid clock
280 if 'timestamp' not in fields:
281 _perror('stream {}: event header: missing "timestamp" field'.format(sid))
282
283 try:
284 _validate_integer(fields['timestamp'], signed=False)
285 except RuntimeError as e:
286 _perror('stream {}: "timestamp": {}'.format(sid, format(e)))
287
288 if fields['timestamp'].map is None:
289 _perror('stream {}: "timestamp": integer must be mapped to an existing clock'.format(sid))
290
291 try:
292 _validate_clock(doc, fields['timestamp'].map)
293 except RuntimeError as e:
294 _perror('stream {}: "timestamp": integer must be mapped to an existing clock'.format(sid))
295
296
297 def _validate_stream_event_context(doc, stream):
298 stream_event_context = stream.event_context
299 sid = stream.id
300
301 if stream_event_context is None:
302 return
303
304 try:
305 _validate_context_field(stream_event_context)
306 except RuntimeError as e:
307 _perror('stream {}: event context: {}'.format(sid, e))
308
309
310 def _validate_headers_contexts(doc):
311 # packet header
312 _validate_packet_header(doc.trace.packet_header)
313
314 # stream stuff
315 for stream_id, stream in doc.streams.items():
316 _validate_event_header(doc, stream)
317 _validate_packet_context(doc, stream)
318 _validate_stream_event_context(doc, stream)
319
320
321 def _validate_metadata(doc):
322 _validate_headers_contexts(doc)
323
324
325 def gen_barectf(metadata, output, prefix, static_inline, manual_clock):
326 # open CTF metadata file
327 try:
328 with open(metadata) as f:
329 tsdl = f.read()
330 except:
331 _perror('cannot open/read CTF metadata file "{}"'.format(metadata))
332
333 # parse CTF metadata
334 parser = pytsdl.parser.Parser()
335
336 try:
337 doc = parser.parse(tsdl)
338 except pytsdl.parser.ParseError as e:
339 _perror('parse error: {}'.format(e))
340
341 # validate CTF metadata against barectf constraints
342 _validate_metadata(doc)
343
344 _pinfo(metadata)
345 _pinfo(output)
346 _pinfo(prefix)
347 _pinfo(static_inline)
348 _pinfo(manual_clock)
349
350
351 def run():
352 args = _parse_args()
353 gen_barectf(args.metadata, args.output, args.prefix, args.static_inline,
354 args.manual_clock)
This page took 0.038184 seconds and 5 git commands to generate.