Make configuration and metadata objects immutable
[barectf.git] / barectf / config_parse.py
CommitLineData
7f4429f2
PP
1# The MIT License (MIT)
2#
3# Copyright (c) 2015-2016 Philippe Proulx <pproulx@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
23from barectf import metadata
24from barectf import config
25import collections
26import datetime
27import barectf
28import enum
29import yaml
30import uuid
31import copy
32import re
33import os
34
35
36class _ConfigParseErrorCtx:
37 def __init__(self, name, msg=None):
38 self._name = name
39 self._msg = msg
40
41 @property
42 def name(self):
43 return self._name
44
45 @property
46 def msg(self):
47 return self._msg
48
49
50class ConfigParseError(RuntimeError):
51 def __init__(self, init_ctx_name, init_ctx_msg=None):
52 self._ctx = []
53 self.append_ctx(init_ctx_name, init_ctx_msg)
54
55 @property
56 def ctx(self):
57 return self._ctx
58
59 def append_ctx(self, name, msg=None):
60 self._ctx.append(_ConfigParseErrorCtx(name, msg))
61
62
63def _opt_to_public(obj):
64 if obj is None:
65 return
66
67 return obj.to_public()
68
69
70class _PseudoObj:
71 def __init__(self):
72 self._public = None
73
74 def to_public(self):
75 if self._public is None:
76 self._public = self._to_public()
77
78 return self._public
79
80 def _to_public(self):
81 raise NotImplementedError
82
83
84class _PropertyMapping(_PseudoObj):
85 def __init__(self):
86 super().__init__()
87 self.object = None
88 self.prop = None
89
90 def _to_public(self):
91 return metadata.PropertyMapping(self.object.to_public(), self.prop)
92
93
94class _Integer(_PseudoObj):
95 def __init__(self):
96 super().__init__()
97 self.size = None
98 self.byte_order = None
99 self.reset_align()
100 self.reset_signed()
101 self.reset_base()
102 self.reset_encoding()
103 self.reset_property_mappings()
104
105 def reset_align(self):
106 self.align = None
107
108 def reset_signed(self):
109 self.signed = False
110
111 def reset_base(self):
112 self.base = 10
113
114 def reset_encoding(self):
115 self.encoding = metadata.Encoding.NONE
116
117 def reset_property_mappings(self):
118 self.property_mappings = []
119
120 @property
121 def real_align(self):
122 if self.align is None:
123 if self.size % 8 == 0:
124 return 8
125 else:
126 return 1
127 else:
128 return self.align
129
130 def _to_public(self):
131 prop_mappings = [pm.to_public() for pm in self.property_mappings]
132 return metadata.Integer(self.size, self.byte_order, self.align,
133 self.signed, self.base, self.encoding,
134 prop_mappings)
135
136
137class _FloatingPoint(_PseudoObj):
138 def __init__(self):
139 super().__init__()
140 self.exp_size = None
141 self.mant_size = None
142 self.byte_order = None
143 self.reset_align()
144
145 def reset_align(self):
146 self.align = 8
147
148 @property
149 def real_align(self):
150 return self.align
151
152 def _to_public(self):
153 return metadata.FloatingPoint(self.exp_size, self.mant_size,
154 self.byte_order, self.align)
155
156
157class _Enum(_PseudoObj):
158 def __init__(self):
159 super().__init__()
160 self.value_type = None
161 self.members = collections.OrderedDict()
162
163 @property
164 def last_value(self):
165 if len(self.members) == 0:
166 return
167
168 return list(self.members.values())[-1][1]
169
170 @property
171 def real_align(self):
172 return self.value_type.real_align
173
174 def _to_public(self):
175 return metadata.Enum(self.value_type.to_public(), self.members)
176
177
178class _String(_PseudoObj):
179 def __init__(self):
180 super().__init__()
181 self.reset_encoding()
182
183 def reset_encoding(self):
184 self.encoding = metadata.Encoding.UTF8
185
186 real_align = 8
187
188 def _to_public(self):
189 return metadata.String(self.encoding)
190
191
192class _Array(_PseudoObj):
193 def __init__(self):
194 super().__init__()
195 self.element_type = None
196 self.length = None
197
198 @property
199 def real_align(self):
200 return self.element_type.real_align
201
202 def _to_public(self):
203 return metadata.Array(self.element_type.to_public(), self.length)
204
205
206class _Struct(_PseudoObj):
207 def __init__(self):
208 super().__init__()
209 self.reset_min_align()
210 self.reset_fields()
211
212 def reset_min_align(self):
213 self.min_align = 1
214
215 def reset_fields(self):
216 self.fields = collections.OrderedDict()
217
218 @property
219 def real_align(self):
220 align = self.min_align
221
222 for pseudo_field in self.fields.values():
223 if pseudo_field.real_align > align:
224 align = pseudo_field.real_align
225
226 return align
227
228 def _to_public(self):
229 fields = []
230
231 for name, pseudo_field in self.fields.items():
232 fields.append((name, pseudo_field.to_public()))
233
234 return metadata.Struct(self.min_align, collections.OrderedDict(fields))
235
236
237class _Trace(_PseudoObj):
238 def __init__(self):
239 super().__init__()
240 self.byte_order = None
241 self.uuid = None
242 self.packet_header_type = None
243
244 def _to_public(self):
245 return metadata.Trace(self.byte_order, self.uuid,
246 _opt_to_public(self.packet_header_type))
247
248
249class _Clock(_PseudoObj):
250 def __init__(self):
251 super().__init__()
252 self.reset_name()
253 self.reset_uuid()
254 self.reset_description()
255 self.reset_freq()
256 self.reset_error_cycles()
257 self.reset_offset_seconds()
258 self.reset_offset_cycles()
259 self.reset_absolute()
260 self.reset_return_ctype()
261
262 def reset_name(self):
263 self.name = None
264
265 def reset_uuid(self):
266 self.uuid = None
267
268 def reset_description(self):
269 self.description = None
270
271 def reset_freq(self):
272 self.freq = int(1e9)
273
274 def reset_error_cycles(self):
275 self.error_cycles = 0
276
277 def reset_offset_seconds(self):
278 self.offset_seconds = 0
279
280 def reset_offset_cycles(self):
281 self.offset_cycles = 0
282
283 def reset_absolute(self):
284 self.absolute = False
285
286 def reset_return_ctype(self):
287 self.return_ctype = 'uint32_t'
288
289 def _to_public(self):
290 return metadata.Clock(self.name, self.uuid, self.description, self.freq,
291 self.error_cycles, self.offset_seconds,
292 self.offset_cycles, self.absolute,
293 self.return_ctype)
294
295
296class _Event(_PseudoObj):
297 def __init__(self):
298 super().__init__()
299 self.id = None
300 self.name = None
301 self.log_level = None
302 self.payload_type = None
303 self.context_type = None
304
305 def _to_public(self):
306 return metadata.Event(self.id, self.name, self.log_level,
307 _opt_to_public(self.payload_type),
308 _opt_to_public(self.context_type))
309
310
311class _Stream(_PseudoObj):
312 def __init__(self):
313 super().__init__()
314 self.id = None
315 self.name = None
316 self.packet_context_type = None
317 self.event_header_type = None
318 self.event_context_type = None
319 self.events = collections.OrderedDict()
320
321 def is_event_empty(self, event):
322 total_fields = 0
323
324 if self.event_header_type is not None:
325 total_fields += len(self.event_header_type.fields)
326
327 if self.event_context_type is not None:
328 total_fields += len(self.event_context_type.fields)
329
330 if event.context_type is not None:
331 total_fields += len(event.context_type.fields)
332
333 if event.payload_type is not None:
334 total_fields += len(event.payload_type.fields)
335
336 return total_fields == 0
337
338 def _to_public(self):
339 events = []
340
341 for name, pseudo_ev in self.events.items():
342 events.append((name, pseudo_ev.to_public()))
343
344 return metadata.Stream(self.id, self.name,
345 _opt_to_public(self.packet_context_type),
346 _opt_to_public(self.event_header_type),
347 _opt_to_public(self.event_context_type),
348 collections.OrderedDict(events))
349
350
351class _Metadata(_PseudoObj):
352 def __init__(self):
353 super().__init__()
354 self.trace = None
355 self.env = None
356 self.clocks = None
357 self.streams = None
358 self.default_stream_name = None
359
360 def _to_public(self):
361 clocks = []
362
363 for name, pseudo_clock in self.clocks.items():
364 clocks.append((name, pseudo_clock.to_public()))
365
366 streams = []
367
368 for name, pseudo_stream in self.streams.items():
369 streams.append((name, pseudo_stream.to_public()))
370
371 return metadata.Metadata(self.trace.to_public(), self.env,
372 collections.OrderedDict(clocks),
373 collections.OrderedDict(streams),
374 self.default_stream_name)
375
376
377def _is_assoc_array_prop(node):
378 return isinstance(node, dict)
379
380
381def _is_array_prop(node):
382 return isinstance(node, list)
383
384
385def _is_int_prop(node):
386 return type(node) is int
387
388
389def _is_str_prop(node):
390 return type(node) is str
391
392
393def _is_bool_prop(node):
394 return type(node) is bool
395
396
397def _is_valid_alignment(align):
398 return ((align & (align - 1)) == 0) and align > 0
399
400
401def _byte_order_str_to_bo(bo_str):
402 bo_str = bo_str.lower()
403
404 if bo_str == 'le':
405 return metadata.ByteOrder.LE
406 elif bo_str == 'be':
407 return metadata.ByteOrder.BE
408
409
410def _encoding_str_to_encoding(encoding_str):
411 encoding_str = encoding_str.lower()
412
413 if encoding_str == 'utf-8' or encoding_str == 'utf8':
414 return metadata.Encoding.UTF8
415 elif encoding_str == 'ascii':
416 return metadata.Encoding.ASCII
417 elif encoding_str == 'none':
418 return metadata.Encoding.NONE
419
420
421_re_iden = re.compile(r'^[a-zA-Z][a-zA-Z0-9_]*$')
422_ctf_keywords = set([
423 'align',
424 'callsite',
425 'clock',
426 'enum',
427 'env',
428 'event',
429 'floating_point',
430 'integer',
431 'stream',
432 'string',
433 'struct',
434 'trace',
435 'typealias',
436 'typedef',
437 'variant',
438])
439
440
441def _is_valid_identifier(iden):
442 if not _re_iden.match(iden):
443 return False
444
445 if _re_iden in _ctf_keywords:
446 return False
447
448 return True
449
450
451def _get_first_unknown_prop(node, known_props):
452 for prop_name in node:
453 if prop_name in known_props:
454 continue
455
456 return prop_name
457
458
459# This validator validates the configured metadata for barectf specific
460# needs.
461#
462# barectf needs:
463#
464# * all header/contexts are at least byte-aligned
465# * all integer and floating point number sizes to be <= 64
466# * no inner structures or arrays
467class _BarectfMetadataValidator:
468 def __init__(self):
469 self._type_to_validate_type_func = {
470 _Integer: self._validate_int_type,
471 _FloatingPoint: self._validate_float_type,
472 _Enum: self._validate_enum_type,
473 _String: self._validate_string_type,
474 _Struct: self._validate_struct_type,
475 _Array: self._validate_array_type,
476 }
477
478 def _validate_int_type(self, t, entity_root):
479 if t.size > 64:
480 raise ConfigParseError('Integer type', 'Size must be lesser than or equal to 64 bits')
481
482 def _validate_float_type(self, t, entity_root):
483 if t.exp_size + t.mant_size > 64:
484 raise ConfigParseError('Floating point number type', 'Size must be lesser than or equal to 64 bits')
485
486 def _validate_enum_type(self, t, entity_root):
487 if t.value_type.size > 64:
488 raise ConfigParseError('Enumeration type', 'Integer type\'s size must be lesser than or equal to 64 bits')
489
490 def _validate_string_type(self, t, entity_root):
491 pass
492
493 def _validate_struct_type(self, t, entity_root):
494 if not entity_root:
495 raise ConfigParseError('Structure type', 'Inner structure types are not supported as of this version')
496
497 for field_name, field_type in t.fields.items():
498 if entity_root and self._cur_entity is _Entity.TRACE_PACKET_HEADER:
499 if field_name == 'uuid':
500 # allow
501 continue
502
503 try:
504 self._validate_type(field_type, False)
505 except ConfigParseError as exc:
506 exc.append_ctx('Structure type\' field "{}"'.format(field_name))
507 raise
508
509 def _validate_array_type(self, t, entity_root):
510 raise ConfigParseError('Array type', 'Not supported as of this version')
511
512 def _validate_type(self, t, entity_root):
513 self._type_to_validate_type_func[type(t)](t, entity_root)
514
515 def _validate_entity(self, t):
516 if t is None:
517 return
518
519 # make sure entity is byte-aligned
520 if t.real_align < 8:
521 raise ConfigParseError('Root type', 'Alignment must be at least byte-aligned')
522
523 # make sure entity is a structure
524 if type(t) is not _Struct:
525 raise ConfigParseError('Root type', 'Expecting a structure type')
526
527 # validate types
528 self._validate_type(t, True)
529
530 def _validate_entities_and_names(self, meta):
531 self._cur_entity = _Entity.TRACE_PACKET_HEADER
532
533 try:
534 self._validate_entity(meta.trace.packet_header_type)
535 except ConfigParseError as exc:
536 exc.append_ctx('Trace', 'Invalid packet header type')
537 raise
538
539 for stream_name, stream in meta.streams.items():
540 if not _is_valid_identifier(stream_name):
541 raise ConfigParseError('Trace', 'Stream name "{}" is not a valid C identifier'.format(stream_name))
542
543 self._cur_entity = _Entity.STREAM_PACKET_CONTEXT
544
545 try:
546 self._validate_entity(stream.packet_context_type)
547 except ConfigParseError as exc:
548 exc.append_ctx('Stream "{}"'.format(stream_name),
549 'Invalid packet context type')
550 raise
551
552 self._cur_entity = _Entity.STREAM_EVENT_HEADER
553
554 try:
555 self._validate_entity(stream.event_header_type)
556 except ConfigParseError as exc:
557 exc.append_ctx('Stream "{}"'.format(stream_name),
558 'Invalid event header type')
559 raise
560
561 self._cur_entity = _Entity.STREAM_EVENT_CONTEXT
562
563 try:
564 self._validate_entity(stream.event_context_type)
565 except ConfigParseError as exc:
566 exc.append_ctx('Stream "{}"'.format(stream_name),
567 'Invalid event context type'.format(stream_name))
568 raise
569
570 try:
571 for ev_name, ev in stream.events.items():
572 if not _is_valid_identifier(ev_name):
573 raise ConfigParseError('Stream "{}"'.format(stream_name),
574 'Event name "{}" is not a valid C identifier'.format(ev_name))
575
576 self._cur_entity = _Entity.EVENT_CONTEXT
577
578 try:
579 self._validate_entity(ev.context_type)
580 except ConfigParseError as exc:
581 exc.append_ctx('Event "{}"'.format(ev_name),
582 'Invalid context type')
583 raise
584
585 self._cur_entity = _Entity.EVENT_PAYLOAD
586
587 try:
588 self._validate_entity(ev.payload_type)
589 except ConfigParseError as exc:
590 exc.append_ctx('Event "{}"'.format(ev_name),
591 'Invalid payload type')
592 raise
593
594 if stream.is_event_empty(ev):
595 raise ConfigParseError('Event "{}"'.format(ev_name), 'Empty')
596 except ConfigParseError as exc:
597 exc.append_ctx('Stream "{}"'.format(stream_name))
598 raise
599
600 def _validate_default_stream(self, meta):
601 if meta.default_stream_name:
602 if meta.default_stream_name not in meta.streams.keys():
603 raise ConfigParseError('barectf metadata', 'Default stream name ("{}") does not exist'.format(meta.default_stream_name))
604
605 def validate(self, meta):
606 self._validate_entities_and_names(meta)
607 self._validate_default_stream(meta)
608
609
610# This validator validates special fields of trace, stream, and event
611# types. For example, if checks that the "stream_id" field exists in the
612# trace packet header if there's more than one stream, and much more.
613class _MetadataSpecialFieldsValidator:
614 def _validate_trace_packet_header_type(self, t):
615 # needs "stream_id" field?
616 if len(self._meta.streams) > 1:
617 # yes
618 if t is None:
619 raise ConfigParseError('"packet-header-type" property',
620 'Need "stream_id" field (more than one stream), but trace packet header type is missing')
621
622 if type(t) is not _Struct:
623 raise ConfigParseError('"packet-header-type" property',
624 'Need "stream_id" field (more than one stream), but trace packet header type is not a structure type')
625
626 if 'stream_id' not in t.fields:
627 raise ConfigParseError('"packet-header-type" property',
628 'Need "stream_id" field (more than one stream)')
629
630 # validate "magic" and "stream_id" types
631 if type(t) is not _Struct:
632 return
633
634 for i, (field_name, field_type) in enumerate(t.fields.items()):
635 if field_name == 'magic':
636 if type(field_type) is not _Integer:
637 raise ConfigParseError('"packet-header-type" property',
638 '"magic" field must be an integer type')
639
640 if field_type.signed or field_type.size != 32:
641 raise ConfigParseError('"packet-header-type" property',
642 '"magic" field must be a 32-bit unsigned integer type')
643
644 if i != 0:
645 raise ConfigParseError('"packet-header-type" property',
646 '"magic" field must be the first trace packet header type\'s field')
647 elif field_name == 'stream_id':
648 if type(field_type) is not _Integer:
649 raise ConfigParseError('"packet-header-type" property',
650 '"stream_id" field must be an integer type')
651
652 if field_type.signed:
653 raise ConfigParseError('"packet-header-type" property',
654 '"stream_id" field must be an unsigned integer type')
655
656 # "id" size can fit all event IDs
657 if len(self._meta.streams) > (1 << field_type.size):
658 raise ConfigParseError('"packet-header-type" property',
659 '"stream_id" field\' size is too small for the number of trace streams')
660 elif field_name == 'uuid':
661 if self._meta.trace.uuid is None:
662 raise ConfigParseError('"packet-header-type" property',
663 '"uuid" field specified, but no trace UUID provided')
664
665 if type(field_type) is not _Array:
666 raise ConfigParseError('"packet-header-type" property',
667 '"uuid" field must be an array')
668
669 if field_type.length != 16:
670 raise ConfigParseError('"packet-header-type" property',
671 '"uuid" field must be an array of 16 bytes')
672
673 element_type = field_type.element_type
674
675 if type(element_type) is not _Integer:
676 raise ConfigParseError('"packet-header-type" property',
677 '"uuid" field must be an array of 16 unsigned bytes')
678
679 if element_type.size != 8:
680 raise ConfigParseError('"packet-header-type" property',
681 '"uuid" field must be an array of 16 unsigned bytes')
682
683 if element_type.signed:
684 raise ConfigParseError('"packet-header-type" property',
685 '"uuid" field must be an array of 16 unsigned bytes')
686
687 if element_type.real_align != 8:
688 raise ConfigParseError('"packet-header-type" property',
689 '"uuid" field must be an array of 16 unsigned, byte-aligned bytes')
690
691 def _validate_trace(self, meta):
692 self._validate_trace_packet_header_type(meta.trace.packet_header_type)
693
694 def _validate_stream_packet_context(self, stream):
695 t = stream.packet_context_type
696
697 if type(t) is None:
698 raise ConfigParseError('Stream',
699 'Missing "packet-context-type" property')
700
701 if type(t) is not _Struct:
702 raise ConfigParseError('"packet-context-type" property',
703 'Expecting a structure type')
704
705 # "timestamp_begin", if exists, is an unsigned integer type,
706 # mapped to a clock
707 ts_begin = None
708
709 if 'timestamp_begin' in t.fields:
710 ts_begin = t.fields['timestamp_begin']
711
712 if type(ts_begin) is not _Integer:
713 raise ConfigParseError('"packet-context-type" property',
714 '"timestamp_begin" field must be an integer type')
715
716 if ts_begin.signed:
717 raise ConfigParseError('"packet-context-type" property',
718 '"timestamp_begin" field must be an unsigned integer type')
719
720 if not ts_begin.property_mappings:
721 raise ConfigParseError('"packet-context-type" property',
722 '"timestamp_begin" field must be mapped to a clock')
723
724 # "timestamp_end", if exists, is an unsigned integer type,
725 # mapped to a clock
726 ts_end = None
727
728 if 'timestamp_end' in t.fields:
729 ts_end = t.fields['timestamp_end']
730
731 if type(ts_end) is not _Integer:
732 raise ConfigParseError('"packet-context-type" property',
733 '"timestamp_end" field must be an integer type')
734
735 if ts_end.signed:
736 raise ConfigParseError('"packet-context-type" property',
737 '"timestamp_end" field must be an unsigned integer type')
738
739 if not ts_end.property_mappings:
740 raise ConfigParseError('"packet-context-type" property',
741 '"timestamp_end" field must be mapped to a clock')
742
743 # "timestamp_begin" and "timestamp_end" exist together
744 if (('timestamp_begin' in t.fields) ^ ('timestamp_end' in t.fields)):
745 raise ConfigParseError('"timestamp_begin" and "timestamp_end" fields must be defined together in stream packet context type')
746
747 # "timestamp_begin" and "timestamp_end" are mapped to the same clock
748 if ts_begin is not None and ts_end is not None:
749 if ts_begin.property_mappings[0].object.name != ts_end.property_mappings[0].object.name:
750 raise ConfigParseError('"timestamp_begin" and "timestamp_end" fields must be mapped to the same clock object in stream packet context type')
751
752 # "events_discarded", if exists, is an unsigned integer type
753 if 'events_discarded' in t.fields:
754 events_discarded = t.fields['events_discarded']
755
756 if type(events_discarded) is not _Integer:
757 raise ConfigParseError('"packet-context-type" property',
758 '"events_discarded" field must be an integer type')
759
760 if events_discarded.signed:
761 raise ConfigParseError('"packet-context-type" property',
762 '"events_discarded" field must be an unsigned integer type')
763
764 # "packet_size" and "content_size" must exist
765 if 'packet_size' not in t.fields:
766 raise ConfigParseError('"packet-context-type" property',
767 'Missing "packet_size" field in stream packet context type')
768
769 packet_size = t.fields['packet_size']
770
771 # "content_size" and "content_size" must exist
772 if 'content_size' not in t.fields:
773 raise ConfigParseError('"packet-context-type" property',
774 'Missing "content_size" field in stream packet context type')
775
776 content_size = t.fields['content_size']
777
778 # "packet_size" is an unsigned integer type
779 if type(packet_size) is not _Integer:
780 raise ConfigParseError('"packet-context-type" property',
781 '"packet_size" field in stream packet context type must be an integer type')
782
783 if packet_size.signed:
784 raise ConfigParseError('"packet-context-type" property',
785 '"packet_size" field in stream packet context type must be an unsigned integer type')
786
787 # "content_size" is an unsigned integer type
788 if type(content_size) is not _Integer:
789 raise ConfigParseError('"packet-context-type" property',
790 '"content_size" field in stream packet context type must be an integer type')
791
792 if content_size.signed:
793 raise ConfigParseError('"packet-context-type" property',
794 '"content_size" field in stream packet context type must be an unsigned integer type')
795
796 # "packet_size" size should be greater than or equal to "content_size" size
797 if content_size.size > packet_size.size:
798 raise ConfigParseError('"packet-context-type" property',
799 '"content_size" field size must be lesser than or equal to "packet_size" field size')
800
801 def _validate_stream_event_header(self, stream):
802 t = stream.event_header_type
803
804 # needs "id" field?
805 if len(stream.events) > 1:
806 # yes
807 if t is None:
808 raise ConfigParseError('"event-header-type" property',
809 'Need "id" field (more than one event), but stream event header type is missing')
810
811 if type(t) is not _Struct:
812 raise ConfigParseError('"event-header-type" property',
813 'Need "id" field (more than one event), but stream event header type is not a structure type')
814
815 if 'id' not in t.fields:
816 raise ConfigParseError('"event-header-type" property',
817 'Need "id" field (more than one event)')
818
819 # validate "id" and "timestamp" types
820 if type(t) is not _Struct:
821 return
822
823 # "timestamp", if exists, is an unsigned integer type,
824 # mapped to a clock
825 if 'timestamp' in t.fields:
826 ts = t.fields['timestamp']
827
828 if type(ts) is not _Integer:
829 raise ConfigParseError('"event-header-type" property',
830 '"timestamp" field must be an integer type')
831
832 if ts.signed:
833 raise ConfigParseError('"event-header-type" property',
834 '"timestamp" field must be an unsigned integer type')
835
836 if not ts.property_mappings:
837 raise ConfigParseError('"event-header-type" property',
838 '"timestamp" field must be mapped to a clock')
839
840 if 'id' in t.fields:
841 eid = t.fields['id']
842
843 # "id" is an unsigned integer type
844 if type(eid) is not _Integer:
845 raise ConfigParseError('"event-header-type" property',
846 '"id" field must be an integer type')
847
848 if eid.signed:
849 raise ConfigParseError('"event-header-type" property',
850 '"id" field must be an unsigned integer type')
851
852 # "id" size can fit all event IDs
853 if len(stream.events) > (1 << eid.size):
854 raise ConfigParseError('"event-header-type" property',
855 '"id" field\' size is too small for the number of stream events')
856
857 def _validate_stream(self, stream):
858 self._validate_stream_packet_context(stream)
859 self._validate_stream_event_header(stream)
860
861 def validate(self, meta):
862 self._meta = meta
863 self._validate_trace(meta)
864
865 for stream in meta.streams.values():
866 try:
867 self._validate_stream(stream)
868 except ConfigParseError as exc:
869 exc.append_ctx('Stream "{}"'.format(stream.name), 'Invalid')
870 raise
871
872
873# Entities. Order of values is important here.
874@enum.unique
875class _Entity(enum.IntEnum):
876 TRACE_PACKET_HEADER = 0
877 STREAM_PACKET_CONTEXT = 1
878 STREAM_EVENT_HEADER = 2
879 STREAM_EVENT_CONTEXT = 3
880 EVENT_CONTEXT = 4
881 EVENT_PAYLOAD = 5
882
883
884# Since type inheritance allows types to be only partially defined at
885# any place in the configuration, this validator validates that actual
886# trace, stream, and event types are all complete and valid. Therefore
887# an invalid, but unusued type alias is accepted.
888class _MetadataTypesHistologyValidator:
889 def __init__(self):
890 self._type_to_validate_type_histology_func = {
891 _Integer: self._validate_integer_histology,
892 _FloatingPoint: self._validate_float_histology,
893 _Enum: self._validate_enum_histology,
894 _String: self._validate_string_histology,
895 _Struct: self._validate_struct_histology,
896 _Array: self._validate_array_histology,
897 }
898
899 def _validate_integer_histology(self, t):
900 # size is set
901 if t.size is None:
902 raise ConfigParseError('Integer type', 'Missing size')
903
904 def _validate_float_histology(self, t):
905 # exponent digits is set
906 if t.exp_size is None:
907 raise ConfigParseError('Floating point number type',
908 'Missing exponent size')
909
910 # mantissa digits is set
911 if t.mant_size is None:
912 raise ConfigParseError('Floating point number type',
913 'Missing mantissa size')
914
915 # exponent and mantissa sum is a multiple of 8
916 if (t.exp_size + t.mant_size) % 8 != 0:
917 raise ConfigParseError('Floating point number type',
918 'Mantissa and exponent sizes sum must be a multiple of 8')
919
920 def _validate_enum_histology(self, t):
921 # integer type is set
922 if t.value_type is None:
923 raise ConfigParseError('Enumeration type', 'Missing value type')
924
925 # there's at least one member
926 if not t.members:
927 raise ConfigParseError('Enumeration type', 'At least one member required')
928
929 # no overlapping values and all values are valid considering
930 # the value type
931 ranges = []
932
933 if t.value_type.signed:
934 value_min = -(1 << t.value_type.size - 1)
935 value_max = (1 << (t.value_type.size - 1)) - 1
936 else:
937 value_min = 0
938 value_max = (1 << t.value_type.size) - 1
939
940 for label, value in t.members.items():
941 for rg in ranges:
942 if value[0] <= rg[1] and rg[0] <= value[1]:
943 raise ConfigParseError('Enumeration type\'s member "{}"',
944 'Overlaps another member'.format(label))
945
946 name_fmt = 'Enumeration type\'s member "{}"'
947 msg_fmt = 'Value {} is outside the value type range [{}, {}]'
948
949 if value[0] < value_min or value[0] > value_max:
950 raise ConfigParseError(name_fmt.format(label),
951 msg_fmt.format(value[0], value_min, value_max))
952
953 if value[1] < value_min or value[1] > value_max:
954 raise ConfigParseError(name_fmt.format(label),
955 msg_fmt.format(value[0], value_min, value_max))
956
957 ranges.append(value)
958
959 def _validate_string_histology(self, t):
960 # always valid
961 pass
962
963 def _validate_struct_histology(self, t):
964 # all fields are valid
965 for field_name, field_type in t.fields.items():
966 try:
967 self._validate_type_histology(field_type)
968 except ConfigParseError as exc:
969 exc.append_ctx('Structure type\'s field "{}"'.format(field_name))
970 raise
971
972 def _validate_array_histology(self, t):
973 # length is set
974 if t.length is None:
975 raise ConfigParseError('Array type', 'Missing length')
976
977 # element type is set
978 if t.element_type is None:
979 raise ConfigParseError('Array type', 'Missing element type')
980
981 # element type is valid
982 try:
983 self._validate_type_histology(t.element_type)
984 except ConfigParseError as exc:
985 exc.append_ctx('Array type', 'Invalid element type')
986 raise
987
988 def _validate_type_histology(self, t):
989 if t is None:
990 return
991
992 self._type_to_validate_type_histology_func[type(t)](t)
993
994 def _validate_entity_type_histology(self, t):
995 if t is None:
996 return
997
998 if type(t) is not _Struct:
999 raise ConfigParseError('Root type', 'Expecting a structure type')
1000
1001 self._validate_type_histology(t)
1002
1003 def _validate_event_types_histology(self, ev):
1004 ev_name = ev.name
1005
1006 # validate event context type
1007 try:
1008 self._validate_entity_type_histology(ev.context_type)
1009 except ConfigParseError as exc:
1010 exc.append_ctx('Event "{}"'.format(ev.name),
1011 'Invalid context type')
1012 raise
1013
1014 # validate event payload type
1015 try:
1016 self._validate_entity_type_histology(ev.payload_type)
1017 except ConfigParseError as exc:
1018 exc.append_ctx('Event "{}"'.format(ev.name),
1019 'Invalid payload type')
1020 raise
1021
1022 def _validate_stream_types_histology(self, stream):
1023 stream_name = stream.name
1024
1025 # validate stream packet context type
1026 try:
1027 self._validate_entity_type_histology(stream.packet_context_type)
1028 except ConfigParseError as exc:
1029 exc.append_ctx('Stream "{}"'.format(stream_name),
1030 'Invalid packet context type')
1031 raise
1032
1033 # validate stream event header type
1034 try:
1035 self._validate_entity_type_histology(stream.event_header_type)
1036 except ConfigParseError as exc:
1037 exc.append_ctx('Stream "{}"'.format(stream_name),
1038 'Invalid event header type')
1039 raise
1040
1041 # validate stream event context type
1042 try:
1043 self._validate_entity_type_histology(stream.event_context_type)
1044 except ConfigParseError as exc:
1045 exc.append_ctx('Stream "{}"'.format(stream_name),
1046 'Invalid event context type')
1047 raise
1048
1049 # validate events
1050 for ev in stream.events.values():
1051 try:
1052 self._validate_event_types_histology(ev)
1053 except ConfigParseError as exc:
1054 exc.append_ctx('Stream "{}"'.format(stream_name),
1055 'Invalid event')
1056 raise
1057
1058 def validate(self, meta):
1059 # validate trace packet header type
1060 try:
1061 self._validate_entity_type_histology(meta.trace.packet_header_type)
1062 except ConfigParseError as exc:
1063 exc.append_ctx('Metadata\'s trace', 'Invalid packet header type')
1064 raise
1065
1066 # validate streams
1067 for stream in meta.streams.values():
1068 self._validate_stream_types_histology(stream)
1069
1070
1071class _YamlConfigParser:
1072 def __init__(self, include_dirs, ignore_include_not_found, dump_config):
1073 self._class_name_to_create_type_func = {
1074 'int': self._create_integer,
1075 'integer': self._create_integer,
1076 'flt': self._create_float,
1077 'float': self._create_float,
1078 'floating-point': self._create_float,
1079 'enum': self._create_enum,
1080 'enumeration': self._create_enum,
1081 'str': self._create_string,
1082 'string': self._create_string,
1083 'struct': self._create_struct,
1084 'structure': self._create_struct,
1085 'array': self._create_array,
1086 }
1087 self._type_to_create_type_func = {
1088 _Integer: self._create_integer,
1089 _FloatingPoint: self._create_float,
1090 _Enum: self._create_enum,
1091 _String: self._create_string,
1092 _Struct: self._create_struct,
1093 _Array: self._create_array,
1094 }
1095 self._include_dirs = include_dirs
1096 self._ignore_include_not_found = ignore_include_not_found
1097 self._dump_config = dump_config
1098
1099 def _set_byte_order(self, metadata_node):
1100 if 'trace' not in metadata_node:
1101 raise ConfigParseError('Metadata', 'Missing "trace" property')
1102
1103 trace_node = metadata_node['trace']
1104
1105 if not _is_assoc_array_prop(trace_node):
1106 raise ConfigParseError('Metadata\'s "trace" property',
1107 'Must be an associative array')
1108
1109 if 'byte-order' not in trace_node:
1110 raise ConfigParseError('Metadata\'s "trace" property',
1111 'Missing "byte-order" property')
1112
1113 bo_node = trace_node['byte-order']
1114
1115 if not _is_str_prop(bo_node):
1116 raise ConfigParseError('Metadata\'s "trace" property',
1117 '"byte-order" property must be a string ("le" or "be")')
1118
1119 self._bo = _byte_order_str_to_bo(bo_node)
1120
1121 if self._bo is None:
1122 raise ConfigParseError('Metadata\'s "trace" property',
1123 'Invalid "byte-order" property: must be "le" or "be"')
1124
1125 def _lookup_type_alias(self, name):
1126 if name in self._tas:
1127 return copy.deepcopy(self._tas[name])
1128
1129 def _set_int_clock_prop_mapping(self, int_obj, prop_mapping_node):
1130 unk_prop = _get_first_unknown_prop(prop_mapping_node, ['type', 'name', 'property'])
1131
1132 if unk_prop:
1133 raise ConfigParseError('Integer type\'s clock property mapping',
1134 'Unknown property: "{}"'.format(unk_prop))
1135
1136 if 'name' not in prop_mapping_node:
1137 raise ConfigParseError('Integer type\'s clock property mapping',
1138 'Missing "name" property')
1139
1140 if 'property' not in prop_mapping_node:
1141 raise ConfigParseError('Integer type\'s clock property mapping',
1142 'Missing "property" property')
1143
1144 clock_name = prop_mapping_node['name']
1145 prop = prop_mapping_node['property']
1146
1147 if not _is_str_prop(clock_name):
1148 raise ConfigParseError('Integer type\'s clock property mapping',
1149 '"name" property must be a string')
1150
1151 if not _is_str_prop(prop):
1152 raise ConfigParseError('Integer type\'s clock property mapping',
1153 '"property" property must be a string')
1154
1155 if clock_name not in self._clocks:
1156 raise ConfigParseError('Integer type\'s clock property mapping',
1157 'Invalid clock name "{}"'.format(clock_name))
1158
1159 if prop != 'value':
1160 raise ConfigParseError('Integer type\'s clock property mapping',
1161 'Invalid "property" property: "{}"'.format(prop))
1162
1163 mapped_clock = self._clocks[clock_name]
1164 prop_mapping = _PropertyMapping()
1165 prop_mapping.object = mapped_clock
1166 prop_mapping.prop = prop
1167 int_obj.property_mappings.append(prop_mapping)
1168
1169 def _get_first_unknown_type_prop(self, type_node, known_props):
1170 kp = known_props + ['inherit', 'class']
1171
1172 if self._version >= 201:
1173 kp.append('$inherit')
1174
1175 return _get_first_unknown_prop(type_node, kp)
1176
1177 def _create_integer(self, obj, node):
1178 if obj is None:
1179 # create integer object
1180 obj = _Integer()
1181
1182 unk_prop = self._get_first_unknown_type_prop(node, [
1183 'size',
1184 'align',
1185 'signed',
1186 'byte-order',
1187 'base',
1188 'encoding',
1189 'property-mappings',
1190 ])
1191
1192 if unk_prop:
1193 raise ConfigParseError('Integer type',
1194 'Unknown property: "{}"'.format(unk_prop))
1195
1196 # size
1197 if 'size' in node:
1198 size = node['size']
1199
1200 if not _is_int_prop(size):
1201 raise ConfigParseError('Integer type',
1202 '"size" property of integer type object must be an integer')
1203
1204 if size < 1:
1205 raise ConfigParseError('Integer type',
1206 'Invalid integer size: {}'.format(size))
1207
1208 obj.size = size
1209
1210 # align
1211 if 'align' in node:
1212 align = node['align']
1213
1214 if align is None:
1215 obj.reset_align()
1216 else:
1217 if not _is_int_prop(align):
1218 raise ConfigParseError('Integer type',
1219 '"align" property of integer type object must be an integer')
1220
1221 if not _is_valid_alignment(align):
1222 raise ConfigParseError('Integer type',
1223 'Invalid alignment: {}'.format(align))
1224
1225 obj.align = align
1226
1227 # signed
1228 if 'signed' in node:
1229 signed = node['signed']
1230
1231 if signed is None:
1232 obj.reset_signed()
1233 else:
1234 if not _is_bool_prop(signed):
1235 raise ConfigParseError('Integer type',
1236 '"signed" property of integer type object must be a boolean')
1237
1238 obj.signed = signed
1239
1240 # byte order
1241 if 'byte-order' in node:
1242 byte_order = node['byte-order']
1243
1244 if byte_order is None:
1245 obj.byte_order = self._bo
1246 else:
1247 if not _is_str_prop(byte_order):
1248 raise ConfigParseError('Integer type',
1249 '"byte-order" property of integer type object must be a string ("le" or "be")')
1250
1251 byte_order = _byte_order_str_to_bo(byte_order)
1252
1253 if byte_order is None:
1254 raise ConfigParseError('Integer type',
1255 'Invalid "byte-order" property in integer type object')
1256
1257 obj.byte_order = byte_order
1258 else:
1259 obj.byte_order = self._bo
1260
1261 # base
1262 if 'base' in node:
1263 base = node['base']
1264
1265 if base is None:
1266 obj.reset_base()
1267 else:
1268 if not _is_str_prop(base):
1269 raise ConfigParseError('Integer type',
1270 '"base" property of integer type object must be a string ("bin", "oct", "dec", or "hex")')
1271
1272 if base == 'bin':
1273 base = 2
1274 elif base == 'oct':
1275 base = 8
1276 elif base == 'dec':
1277 base = 10
1278 elif base == 'hex':
1279 base = 16
1280 else:
1281 raise ConfigParseError('Integer type',
1282 'Unknown "base" property value: "{}" ("bin", "oct", "dec", and "hex" are accepted)'.format(base))
1283
1284 obj.base = base
1285
1286 # encoding
1287 if 'encoding' in node:
1288 encoding = node['encoding']
1289
1290 if encoding is None:
1291 obj.reset_encoding()
1292 else:
1293 if not _is_str_prop(encoding):
1294 raise ConfigParseError('Integer type',
1295 '"encoding" property of integer type object must be a string ("none", "ascii", or "utf-8")')
1296
1297 encoding = _encoding_str_to_encoding(encoding)
1298
1299 if encoding is None:
1300 raise ConfigParseError('Integer type',
1301 'Invalid "encoding" property in integer type object')
1302
1303 obj.encoding = encoding
1304
1305 # property mappings
1306 if 'property-mappings' in node:
1307 prop_mappings = node['property-mappings']
1308
1309 if prop_mappings is None:
1310 obj.reset_property_mappings()
1311 else:
1312 if not _is_array_prop(prop_mappings):
1313 raise ConfigParseError('Integer type',
1314 '"property-mappings" property of integer type object must be an array')
1315
1316 if len(prop_mappings) > 1:
1317 raise ConfigParseError('Integer type',
1318 'Length of "property-mappings" array in integer type object must be 1')
1319
1320 for index, prop_mapping in enumerate(prop_mappings):
1321 if not _is_assoc_array_prop(prop_mapping):
1322 raise ConfigParseError('Integer type',
1323 'Elements of "property-mappings" property of integer type object must be associative arrays')
1324
1325 if 'type' not in prop_mapping:
1326 raise ConfigParseError('Integer type',
1327 'Missing "type" property in integer type object\'s "property-mappings" array\'s element #{}'.format(index))
1328
1329 prop_type = prop_mapping['type']
1330
1331 if not _is_str_prop(prop_type):
1332 raise ConfigParseError('Integer type',
1333 '"type" property of integer type object\'s "property-mappings" array\'s element #{} must be a string'.format(index))
1334
1335 if prop_type == 'clock':
1336 self._set_int_clock_prop_mapping(obj, prop_mapping)
1337 else:
1338 raise ConfigParseError('Integer type',
1339 'Unknown property mapping type "{}" in integer type object\'s "property-mappings" array\'s element #{}'.format(prop_type, index))
1340
1341 return obj
1342
1343 def _create_float(self, obj, node):
1344 if obj is None:
1345 # create floating point number object
1346 obj = _FloatingPoint()
1347
1348 unk_prop = self._get_first_unknown_type_prop(node, [
1349 'size',
1350 'align',
1351 'byte-order',
1352 ])
1353
1354 if unk_prop:
1355 raise ConfigParseError('Floating point number type',
1356 'Unknown property: "{}"'.format(unk_prop))
1357
1358 # size
1359 if 'size' in node:
1360 size = node['size']
1361
1362 if not _is_assoc_array_prop(size):
1363 raise ConfigParseError('Floating point number type',
1364 '"size" property must be an associative array')
1365
1366 unk_prop = _get_first_unknown_prop(size, ['exp', 'mant'])
1367
1368 if unk_prop:
1369 raise ConfigParseError('Floating point number type\'s "size" property',
1370 'Unknown property: "{}"'.format(unk_prop))
1371
1372 if 'exp' in size:
1373 exp = size['exp']
1374
1375 if not _is_int_prop(exp):
1376 raise ConfigParseError('Floating point number type\'s "size" property',
1377 '"exp" property must be an integer')
1378
1379 if exp < 1:
1380 raise ConfigParseError('Floating point number type\'s "size" property',
1381 'Invalid exponent size: {}')
1382
1383 obj.exp_size = exp
1384
1385 if 'mant' in size:
1386 mant = size['mant']
1387
1388 if not _is_int_prop(mant):
1389 raise ConfigParseError('Floating point number type\'s "size" property',
1390 '"mant" property must be an integer')
1391
1392 if mant < 1:
1393 raise ConfigParseError('Floating point number type\'s "size" property',
1394 'Invalid mantissa size: {}')
1395
1396 obj.mant_size = mant
1397
1398 # align
1399 if 'align' in node:
1400 align = node['align']
1401
1402 if align is None:
1403 obj.reset_align()
1404 else:
1405 if not _is_int_prop(align):
1406 raise ConfigParseError('Floating point number type',
1407 '"align" property must be an integer')
1408
1409 if not _is_valid_alignment(align):
1410 raise ConfigParseError('Floating point number type',
1411 'Invalid alignment: {}'.format(align))
1412
1413 obj.align = align
1414
1415 # byte order
1416 if 'byte-order' in node:
1417 byte_order = node['byte-order']
1418
1419 if byte_order is None:
1420 obj.byte_order = self._bo
1421 else:
1422 if not _is_str_prop(byte_order):
1423 raise ConfigParseError('Floating point number type',
1424 '"byte-order" property must be a string ("le" or "be")')
1425
1426 byte_order = _byte_order_str_to_bo(byte_order)
1427
1428 if byte_order is None:
1429 raise ConfigParseError('Floating point number type',
1430 'Invalid "byte-order" property')
1431 else:
1432 obj.byte_order = self._bo
1433
1434 return obj
1435
1436 def _create_enum(self, obj, node):
1437 if obj is None:
1438 # create enumeration object
1439 obj = _Enum()
1440
1441 unk_prop = self._get_first_unknown_type_prop(node, [
1442 'value-type',
1443 'members',
1444 ])
1445
1446 if unk_prop:
1447 raise ConfigParseError('Enumeration type',
1448 'Unknown property: "{}"'.format(unk_prop))
1449
1450 # value type
1451 if 'value-type' in node:
1452 value_type_node = node['value-type']
1453
1454 try:
1455 obj.value_type = self._create_type(value_type_node)
1456 except ConfigParseError as exc:
1457 exc.append_ctx('Enumeration type', 'Cannot create integer type')
1458 raise
1459
1460 # members
1461 if 'members' in node:
1462 members_node = node['members']
1463
1464 if not _is_array_prop(members_node):
1465 raise ConfigParseError('Enumeration type',
1466 '"members" property must be an array')
1467
1468 cur = 0
1469 last_value = obj.last_value
1470
1471 if last_value is None:
1472 cur = 0
1473 else:
1474 cur = last_value + 1
1475
1476 for index, m_node in enumerate(members_node):
1477 if not _is_str_prop(m_node) and not _is_assoc_array_prop(m_node):
1478 raise ConfigParseError('Enumeration type',
1479 'Invalid member #{}: expecting a string or an associative array'.format(index))
1480
1481 if _is_str_prop(m_node):
1482 label = m_node
1483 value = (cur, cur)
1484 cur += 1
1485 else:
1486 unk_prop = _get_first_unknown_prop(m_node, [
1487 'label',
1488 'value',
1489 ])
1490
1491 if unk_prop:
1492 raise ConfigParseError('Enumeration type',
1493 'Unknown member object property: "{}"'.format(unk_prop))
1494
1495 if 'label' not in m_node:
1496 raise ConfigParseError('Enumeration type',
1497 'Missing "label" property in member #{}'.format(index))
1498
1499 label = m_node['label']
1500
1501 if not _is_str_prop(label):
1502 raise ConfigParseError('Enumeration type',
1503 '"label" property of member #{} must be a string'.format(index))
1504
1505 if 'value' not in m_node:
1506 raise ConfigParseError('Enumeration type',
1507 'Missing "value" property in member ("{}")'.format(label))
1508
1509 value = m_node['value']
1510
1511 if not _is_int_prop(value) and not _is_array_prop(value):
1512 raise ConfigParseError('Enumeration type',
1513 'Invalid member ("{}"): expecting an integer or an array'.format(label))
1514
1515 if _is_int_prop(value):
1516 cur = value + 1
1517 value = (value, value)
1518 else:
1519 if len(value) != 2:
1520 raise ConfigParseError('Enumeration type',
1521 'Invalid member ("{}"): range must have exactly two items'.format(label))
1522
1523 mn = value[0]
1524 mx = value[1]
1525
1526 if mn > mx:
1527 raise ConfigParseError('Enumeration type',
1528 'Invalid member ("{}"): invalid range ({} > {})'.format(label, mn, mx))
1529
1530 value = (mn, mx)
1531 cur = mx + 1
1532
1533 obj.members[label] = value
1534
1535 return obj
1536
1537 def _create_string(self, obj, node):
1538 if obj is None:
1539 # create string object
1540 obj = _String()
1541
1542 unk_prop = self._get_first_unknown_type_prop(node, [
1543 'encoding',
1544 ])
1545
1546 if unk_prop:
1547 raise ConfigParseError('String type',
1548 'Unknown object property: "{}"'.format(unk_prop))
1549
1550 # encoding
1551 if 'encoding' in node:
1552 encoding = node['encoding']
1553
1554 if encoding is None:
1555 obj.reset_encoding()
1556 else:
1557 if not _is_str_prop(encoding):
1558 raise ConfigParseError('String type',
1559 '"encoding" property of must be a string ("none", "ascii", or "utf-8")')
1560
1561 encoding = _encoding_str_to_encoding(encoding)
1562
1563 if encoding is None:
1564 raise ConfigParseError('String type',
1565 'Invalid "encoding" property')
1566
1567 obj.encoding = encoding
1568
1569 return obj
1570
1571 def _create_struct(self, obj, node):
1572 if obj is None:
1573 # create structure object
1574 obj = _Struct()
1575
1576 unk_prop = self._get_first_unknown_type_prop(node, [
1577 'min-align',
1578 'fields',
1579 ])
1580
1581 if unk_prop:
1582 raise ConfigParseError('Structure type',
1583 'Unknown object property: "{}"'.format(unk_prop))
1584
1585 # minimum alignment
1586 if 'min-align' in node:
1587 min_align = node['min-align']
1588
1589 if min_align is None:
1590 obj.reset_min_align()
1591 else:
1592 if not _is_int_prop(min_align):
1593 raise ConfigParseError('Structure type',
1594 '"min-align" property must be an integer')
1595
1596 if not _is_valid_alignment(min_align):
1597 raise ConfigParseError('Structure type',
1598 'Invalid minimum alignment: {}'.format(min_align))
1599
1600 obj.min_align = min_align
1601
1602 # fields
1603 if 'fields' in node:
1604 fields = node['fields']
1605
1606 if fields is None:
1607 obj.reset_fields()
1608 else:
1609 if not _is_assoc_array_prop(fields):
1610 raise ConfigParseError('Structure type',
1611 '"fields" property must be an associative array')
1612
1613 for field_name, field_node in fields.items():
1614 if not _is_valid_identifier(field_name):
1615 raise ConfigParseError('Structure type',
1616 '"{}" is not a valid field name'.format(field_name))
1617
1618 try:
1619 obj.fields[field_name] = self._create_type(field_node)
1620 except ConfigParseError as exc:
1621 exc.append_ctx('Structure type',
1622 'Cannot create field "{}"'.format(field_name))
1623 raise
1624
1625 return obj
1626
1627 def _create_array(self, obj, node):
1628 if obj is None:
1629 # create array object
1630 obj = _Array()
1631
1632 unk_prop = self._get_first_unknown_type_prop(node, [
1633 'length',
1634 'element-type',
1635 ])
1636
1637 if unk_prop:
1638 raise ConfigParseError('Array type',
1639 'Unknown property: "{}"'.format(unk_prop))
1640
1641 # length
1642 if 'length' in node:
1643 length = node['length']
1644
1645 if not _is_int_prop(length):
1646 raise ConfigParseError('Array type',
1647 '"length" property must be an integer')
1648
1649 if type(length) is int and length < 0:
1650 raise ConfigParseError('Array type',
1651 'Invalid length: {}'.format(length))
1652
1653 obj.length = length
1654
1655 # element type
1656 if 'element-type' in node:
1657 element_type_node = node['element-type']
1658
1659 try:
1660 obj.element_type = self._create_type(node['element-type'])
1661 except ConfigParseError as exc:
1662 exc.append_ctx('Array type', 'Cannot create element type')
1663 raise
1664
1665 return obj
1666
1667 def _create_type(self, type_node):
1668 if type(type_node) is str:
1669 t = self._lookup_type_alias(type_node)
1670
1671 if t is None:
1672 raise ConfigParseError('Type',
1673 'Unknown type alias "{}"'.format(type_node))
1674
1675 return t
1676
1677 if not _is_assoc_array_prop(type_node):
1678 raise ConfigParseError('Type',
1679 'Expecting associative arrays or string (type alias name)')
1680
1681 # inherit:
1682 # v2.0: "inherit"
1683 # v2.1+: "$inherit"
1684 inherit_node = None
1685
1686 if self._version >= 200:
1687 if 'inherit' in type_node:
1688 inherit_prop = 'inherit'
1689 inherit_node = type_node[inherit_prop]
1690
1691 if self._version >= 201:
1692 if '$inherit' in type_node:
1693 if inherit_node is not None:
1694 raise ConfigParseError('Type',
1695 'Cannot specify both "inherit" and "$inherit" properties of type object: prefer "$inherit"')
1696
1697 inherit_prop = '$inherit'
1698 inherit_node = type_node[inherit_prop]
1699
1700 if inherit_node is not None and 'class' in type_node:
1701 raise ConfigParseError('Type',
1702 'Cannot specify both "{}" and "class" properties in type object'.format(inherit_prop))
1703
1704 if inherit_node is not None:
1705 if not _is_str_prop(inherit_node):
1706 raise ConfigParseError('Type',
1707 '"{}" property of type object must be a string'.format(inherit_prop))
1708
1709 base = self._lookup_type_alias(inherit_node)
1710
1711 if base is None:
1712 raise ConfigParseError('Type',
1713 'Cannot inherit from type alias "{}": type alias does not exist at this point'.format(inherit_node))
1714
1715 func = self._type_to_create_type_func[type(base)]
1716 else:
1717 if 'class' not in type_node:
1718 raise ConfigParseError('Type',
1719 'Does not inherit, therefore must have a "class" property')
1720
1721 class_name = type_node['class']
1722
1723 if type(class_name) is not str:
1724 raise ConfigParseError('Type', '"class" property must be a string')
1725
1726 if class_name not in self._class_name_to_create_type_func:
1727 raise ConfigParseError('Type',
1728 'Unknown class "{}"'.format(class_name))
1729
1730 base = None
1731 func = self._class_name_to_create_type_func[class_name]
1732
1733 return func(base, type_node)
1734
1735 def _register_type_aliases(self, metadata_node):
1736 self._tas = dict()
1737
1738 if 'type-aliases' not in metadata_node:
1739 return
1740
1741 ta_node = metadata_node['type-aliases']
1742
1743 if ta_node is None:
1744 return
1745
1746 if not _is_assoc_array_prop(ta_node):
1747 raise ConfigParseError('Metadata',
1748 '"type-aliases" property must be an associative array')
1749
1750 for ta_name, ta_type in ta_node.items():
1751 if ta_name in self._tas:
1752 raise ConfigParseError('Metadata',
1753 'Duplicate type alias "{}"'.format(ta_name))
1754
1755 try:
1756 t = self._create_type(ta_type)
1757 except ConfigParseError as exc:
1758 exc.append_ctx('Metadata',
1759 'Cannot create type alias "{}"'.format(ta_name))
1760 raise
1761
1762 self._tas[ta_name] = t
1763
1764 def _create_clock(self, node):
1765 # create clock object
1766 clock = _Clock()
1767
1768 if not _is_assoc_array_prop(node):
1769 raise ConfigParseError('Metadata',
1770 'Clock objects must be associative arrays')
1771
1772 known_props = [
1773 'uuid',
1774 'description',
1775 'freq',
1776 'error-cycles',
1777 'offset',
1778 'absolute',
1779 'return-ctype',
1780 ]
1781
1782 if self._version >= 201:
1783 known_props.append('$return-ctype')
1784
1785 unk_prop = _get_first_unknown_prop(node, known_props)
1786
1787 if unk_prop:
1788 raise ConfigParseError('Clock',
1789 'Unknown property: "{}"'.format(unk_prop))
1790
1791 # UUID
1792 if 'uuid' in node:
1793 uuidp = node['uuid']
1794
1795 if uuidp is None:
1796 clock.reset_uuid()
1797 else:
1798 if not _is_str_prop(uuidp):
1799 raise ConfigParseError('Clock',
1800 '"uuid" property must be a string')
1801
1802 try:
1803 uuidp = uuid.UUID(uuidp)
1804 except:
1805 raise ConfigParseError('Clock', 'Malformed UUID: "{}"'.format(uuidp))
1806
1807 clock.uuid = uuidp
1808
1809 # description
1810 if 'description' in node:
1811 desc = node['description']
1812
1813 if desc is None:
1814 clock.reset_description()
1815 else:
1816 if not _is_str_prop(desc):
1817 raise ConfigParseError('Clock',
1818 '"description" property must be a string')
1819
1820 clock.description = desc
1821
1822 # frequency
1823 if 'freq' in node:
1824 freq = node['freq']
1825
1826 if freq is None:
1827 clock.reset_freq()
1828 else:
1829 if not _is_int_prop(freq):
1830 raise ConfigParseError('Clock',
1831 '"freq" property must be an integer')
1832
1833 if freq < 1:
1834 raise ConfigParseError('Clock',
1835 'Invalid frequency: {}'.format(freq))
1836
1837 clock.freq = freq
1838
1839 # error cycles
1840 if 'error-cycles' in node:
1841 error_cycles = node['error-cycles']
1842
1843 if error_cycles is None:
1844 clock.reset_error_cycles()
1845 else:
1846 if not _is_int_prop(error_cycles):
1847 raise ConfigParseError('Clock',
1848 '"error-cycles" property must be an integer')
1849
1850 if error_cycles < 0:
1851 raise ConfigParseError('Clock',
1852 'Invalid error cycles: {}'.format(error_cycles))
1853
1854 clock.error_cycles = error_cycles
1855
1856 # offset
1857 if 'offset' in node:
1858 offset = node['offset']
1859
1860 if offset is None:
1861 clock.reset_offset_seconds()
1862 clock.reset_offset_cycles()
1863 else:
1864 if not _is_assoc_array_prop(offset):
1865 raise ConfigParseError('Clock',
1866 '"offset" property must be an associative array')
1867
1868 unk_prop = _get_first_unknown_prop(offset, ['cycles', 'seconds'])
1869
1870 if unk_prop:
1871 raise ConfigParseError('Clock',
1872 'Unknown offset property: "{}"'.format(unk_prop))
1873
1874 # cycles
1875 if 'cycles' in offset:
1876 offset_cycles = offset['cycles']
1877
1878 if offset_cycles is None:
1879 clock.reset_offset_cycles()
1880 else:
1881 if not _is_int_prop(offset_cycles):
1882 raise ConfigParseError('Clock\'s "offset" property',
1883 '"cycles" property must be an integer')
1884
1885 if offset_cycles < 0:
1886 raise ConfigParseError('Clock\'s "offset" property',
1887 'Invalid cycles: {}'.format(offset_cycles))
1888
1889 clock.offset_cycles = offset_cycles
1890
1891 # seconds
1892 if 'seconds' in offset:
1893 offset_seconds = offset['seconds']
1894
1895 if offset_seconds is None:
1896 clock.reset_offset_seconds()
1897 else:
1898 if not _is_int_prop(offset_seconds):
1899 raise ConfigParseError('Clock\'s "offset" property',
1900 '"seconds" property must be an integer')
1901
1902 if offset_seconds < 0:
1903 raise ConfigParseError('Clock\'s "offset" property',
1904 'Invalid seconds: {}'.format(offset_seconds))
1905
1906 clock.offset_seconds = offset_seconds
1907
1908 # absolute
1909 if 'absolute' in node:
1910 absolute = node['absolute']
1911
1912 if absolute is None:
1913 clock.reset_absolute()
1914 else:
1915 if not _is_bool_prop(absolute):
1916 raise ConfigParseError('Clock',
1917 '"absolute" property must be a boolean')
1918
1919 clock.absolute = absolute
1920
1921 # return C type:
1922 # v2.0: "return-ctype"
1923 # v2.1+: "$return-ctype"
1924 return_ctype_node = None
1925
1926 if self._version >= 200:
1927 if 'return-ctype' in node:
1928 return_ctype_prop = 'return-ctype'
1929 return_ctype_node = node[return_ctype_prop]
1930
1931 if self._version >= 201:
1932 if '$return-ctype' in node:
1933 if return_ctype_node is not None:
1934 raise ConfigParseError('Clock',
1935 'Cannot specify both "return-ctype" and "$return-ctype" properties: prefer "$return-ctype"')
1936
1937 return_ctype_prop = '$return-ctype'
1938 return_ctype_node = node[return_ctype_prop]
1939
1940 if return_ctype_node is not None:
1941 if return_ctype_node is None:
1942 clock.reset_return_ctype()
1943 else:
1944 if not _is_str_prop(return_ctype_node):
1945 raise ConfigParseError('Clock',
1946 '"{}" property of must be a string'.format(return_ctype_prop))
1947
1948 clock.return_ctype = return_ctype_node
1949
1950 return clock
1951
1952 def _register_clocks(self, metadata_node):
1953 self._clocks = collections.OrderedDict()
1954
1955 if 'clocks' not in metadata_node:
1956 return
1957
1958 clocks_node = metadata_node['clocks']
1959
1960 if clocks_node is None:
1961 return
1962
1963 if not _is_assoc_array_prop(clocks_node):
1964 raise ConfigParseError('Metadata',
1965 '"clocks" property must be an associative array')
1966
1967 for clock_name, clock_node in clocks_node.items():
1968 if not _is_valid_identifier(clock_name):
1969 raise ConfigParseError('Metadata',
1970 'Invalid clock name: "{}"'.format(clock_name))
1971
1972 if clock_name in self._clocks:
1973 raise ConfigParseError('Metadata',
1974 'Duplicate clock "{}"'.format(clock_name))
1975
1976 try:
1977 clock = self._create_clock(clock_node)
1978 except ConfigParseError as exc:
1979 exc.append_ctx('Metadata',
1980 'Cannot create clock "{}"'.format(clock_name))
1981 raise
1982
1983 clock.name = clock_name
1984 self._clocks[clock_name] = clock
1985
1986 def _create_env(self, metadata_node):
1987 env = collections.OrderedDict()
1988
1989 if 'env' not in metadata_node:
1990 return env
1991
1992 env_node = metadata_node['env']
1993
1994 if env_node is None:
1995 return env
1996
1997 if not _is_assoc_array_prop(env_node):
1998 raise ConfigParseError('Metadata',
1999 '"env" property must be an associative array')
2000
2001 for env_name, env_value in env_node.items():
2002 if env_name in env:
2003 raise ConfigParseError('Metadata',
2004 'Duplicate environment variable "{}"'.format(env_name))
2005
2006 if not _is_valid_identifier(env_name):
2007 raise ConfigParseError('Metadata',
2008 'Invalid environment variable name: "{}"'.format(env_name))
2009
2010 if not _is_int_prop(env_value) and not _is_str_prop(env_value):
2011 raise ConfigParseError('Metadata',
2012 'Invalid environment variable value ("{}"): expecting integer or string'.format(env_name))
2013
2014 env[env_name] = env_value
2015
2016 return env
2017
2018 def _register_log_levels(self, metadata_node):
2019 self._log_levels = dict()
2020
2021 # log levels:
2022 # v2.0: "log-levels"
2023 # v2.1+: "$log-levels"
2024 log_levels_node = None
2025
2026 if self._version >= 200:
2027 if 'log-levels' in metadata_node:
2028 log_levels_prop = 'log-levels'
2029 log_levels_node = metadata_node[log_levels_prop]
2030
2031 if self._version >= 201:
2032 if '$log-levels' in metadata_node:
2033 if log_levels_node is not None:
2034 raise ConfigParseError('Metadata',
2035 'Cannot specify both "log-levels" and "$log-levels" properties of metadata object: prefer "$log-levels"')
2036
2037 log_levels_prop = '$log-levels'
2038 log_levels_node = metadata_node[log_levels_prop]
2039
2040 if log_levels_node is None:
2041 return
2042
2043 if not _is_assoc_array_prop(log_levels_node):
2044 raise ConfigParseError('Metadata',
2045 '"{}" property (metadata) must be an associative array'.format(log_levels_prop))
2046
2047 for ll_name, ll_value in log_levels_node.items():
2048 if ll_name in self._log_levels:
2049 raise ConfigParseError('"{}" property"'.format(log_levels_prop),
2050 'Duplicate entry "{}"'.format(ll_name))
2051
2052 if not _is_int_prop(ll_value):
2053 raise ConfigParseError('"{}" property"'.format(log_levels_prop),
2054 'Invalid entry ("{}"): expecting an integer'.format(ll_name))
2055
2056 if ll_value < 0:
2057 raise ConfigParseError('"{}" property"'.format(log_levels_prop),
2058 'Invalid entry ("{}"): value must be positive'.format(ll_name))
2059
2060 self._log_levels[ll_name] = ll_value
2061
2062 def _create_trace(self, metadata_node):
2063 # create trace object
2064 trace = _Trace()
2065
2066 if 'trace' not in metadata_node:
2067 raise ConfigParseError('Metadata', 'Missing "trace" property')
2068
2069 trace_node = metadata_node['trace']
2070
2071 if not _is_assoc_array_prop(trace_node):
2072 raise ConfigParseError('Metadata',
2073 '"trace" property must be an associative array')
2074
2075 unk_prop = _get_first_unknown_prop(trace_node, [
2076 'byte-order',
2077 'uuid',
2078 'packet-header-type',
2079 ])
2080
2081 if unk_prop:
2082 raise ConfigParseError('Trace',
2083 'Unknown property: "{}"'.format(unk_prop))
2084
2085 # set byte order (already parsed)
2086 trace.byte_order = self._bo
2087
2088 # UUID
2089 if 'uuid' in trace_node and trace_node['uuid'] is not None:
2090 uuidp = trace_node['uuid']
2091
2092 if not _is_str_prop(uuidp):
2093 raise ConfigParseError('Trace',
2094 '"uuid" property must be a string')
2095
2096 if uuidp == 'auto':
2097 uuidp = uuid.uuid1()
2098 else:
2099 try:
2100 uuidp = uuid.UUID(uuidp)
2101 except:
2102 raise ConfigParseError('Trace',
2103 'Malformed UUID: "{}"'.format(uuidp))
2104
2105 trace.uuid = uuidp
2106
2107 # packet header type
2108 if 'packet-header-type' in trace_node and trace_node['packet-header-type'] is not None:
2109 try:
2110 ph_type = self._create_type(trace_node['packet-header-type'])
2111 except ConfigParseError as exc:
2112 exc.append_ctx('Trace',
2113 'Cannot create packet header type')
2114 raise
2115
2116 trace.packet_header_type = ph_type
2117
2118 return trace
2119
2120 def _lookup_log_level(self, ll):
2121 if _is_int_prop(ll):
2122 return ll
2123 elif _is_str_prop(ll) and ll in self._log_levels:
2124 return self._log_levels[ll]
2125
2126 def _create_event(self, event_node):
2127 event = _Event()
2128
2129 if not _is_assoc_array_prop(event_node):
2130 raise ConfigParseError('Event', 'Expecting associative array')
2131
2132 unk_prop = _get_first_unknown_prop(event_node, [
2133 'log-level',
2134 'context-type',
2135 'payload-type',
2136 ])
2137
2138 if unk_prop:
2139 raise ConfigParseError('Event',
2140 'Unknown property: "{}"'.format(unk_prop))
2141
2142 if 'log-level' in event_node and event_node['log-level'] is not None:
2143 ll_node = event_node['log-level']
2144
2145 if _is_str_prop(ll_node):
2146 ll_value = self._lookup_log_level(event_node['log-level'])
2147
2148 if ll_value is None:
2149 raise ConfigParseError('Event\'s "log-level" property',
2150 'Cannot find log level "{}"'.format(ll_node))
2151
2152 ll = metadata.LogLevel(event_node['log-level'], ll_value)
2153 elif _is_int_prop(ll_node):
2154 if ll_node < 0:
2155 raise ConfigParseError('Event\'s "log-level" property',
2156 'Invalid value {}: value must be positive'.format(ll_node))
2157
2158 ll = metadata.LogLevel(None, ll_node)
2159 else:
2160 raise ConfigParseError('Event\'s "log-level" property',
2161 'Must be either a string or an integer')
2162
2163 event.log_level = ll
2164
2165 if 'context-type' in event_node and event_node['context-type'] is not None:
2166 ctx_type_node = event_node['context-type']
2167
2168 try:
2169 t = self._create_type(event_node['context-type'])
2170 except ConfigParseError as exc:
2171 exc.append_ctx('Event',
2172 'Cannot create context type object')
2173 raise
2174
2175 event.context_type = t
2176
2177 if 'payload-type' in event_node and event_node['payload-type'] is not None:
2178 try:
2179 t = self._create_type(event_node['payload-type'])
2180 except ConfigParseError as exc:
2181 exc.append_ctx('Event',
2182 'Cannot create payload type object')
2183 raise
2184
2185 event.payload_type = t
2186
2187 return event
2188
2189 def _create_stream(self, stream_name, stream_node):
2190 stream = _Stream()
2191
2192 if not _is_assoc_array_prop(stream_node):
2193 raise ConfigParseError('Stream objects must be associative arrays')
2194
2195 known_props = [
2196 'packet-context-type',
2197 'event-header-type',
2198 'event-context-type',
2199 'events',
2200 ]
2201
2202 if self._version >= 202:
2203 known_props.append('$default')
2204
2205 unk_prop = _get_first_unknown_prop(stream_node, known_props)
2206
2207 if unk_prop:
2208 add = ''
2209
2210 if unk_prop == '$default':
2211 add = ' (use version 2.2 or greater)'
2212
2213 raise ConfigParseError('Stream',
2214 'Unknown property{}: "{}"'.format(add, unk_prop))
2215
2216 if 'packet-context-type' in stream_node and stream_node['packet-context-type'] is not None:
2217 try:
2218 t = self._create_type(stream_node['packet-context-type'])
2219 except ConfigParseError as exc:
2220 exc.append_ctx('Stream',
2221 'Cannot create packet context type object')
2222 raise
2223
2224 stream.packet_context_type = t
2225
2226 if 'event-header-type' in stream_node and stream_node['event-header-type'] is not None:
2227 try:
2228 t = self._create_type(stream_node['event-header-type'])
2229 except ConfigParseError as exc:
2230 exc.append_ctx('Stream',
2231 'Cannot create event header type object')
2232 raise
2233
2234 stream.event_header_type = t
2235
2236 if 'event-context-type' in stream_node and stream_node['event-context-type'] is not None:
2237 try:
2238 t = self._create_type(stream_node['event-context-type'])
2239 except ConfigParseError as exc:
2240 exc.append_ctx('Stream',
2241 'Cannot create event context type object')
2242 raise
2243
2244 stream.event_context_type = t
2245
2246 if 'events' not in stream_node:
2247 raise ConfigParseError('Stream', 'Missing "events" property')
2248
2249 events = stream_node['events']
2250
2251 if events is not None:
2252 if not _is_assoc_array_prop(events):
2253 raise ConfigParseError('Stream',
2254 '"events" property must be an associative array')
2255
2256 if not events:
2257 raise ConfigParseError('Stream', 'At least one event is needed')
2258
2259 cur_id = 0
2260
2261 for ev_name, ev_node in events.items():
2262 try:
2263 ev = self._create_event(ev_node)
2264 except ConfigParseError as exc:
2265 exc.append_ctx('Stream',
2266 'Cannot create event "{}"'.format(ev_name))
2267 raise
2268
2269 ev.id = cur_id
2270 ev.name = ev_name
2271 stream.events[ev_name] = ev
2272 cur_id += 1
2273
2274 if '$default' in stream_node and stream_node['$default'] is not None:
2275 default_node = stream_node['$default']
2276
2277 if not _is_bool_prop(default_node):
2278 raise ConfigParseError('Stream',
2279 'Invalid "$default" property: expecting a boolean')
2280
2281 if default_node:
2282 if self._meta.default_stream_name is not None and self._meta.default_stream_name != stream_name:
2283 fmt = 'Cannot specify more than one default stream (default stream already set to "{}")'
2284 raise ConfigParseError('Stream',
2285 fmt.format(self._meta.default_stream_name))
2286
2287 self._meta.default_stream_name = stream_name
2288
2289 return stream
2290
2291 def _create_streams(self, metadata_node):
2292 streams = collections.OrderedDict()
2293
2294 if 'streams' not in metadata_node:
2295 raise ConfigParseError('Metadata',
2296 'Missing "streams" property')
2297
2298 streams_node = metadata_node['streams']
2299
2300 if not _is_assoc_array_prop(streams_node):
2301 raise ConfigParseError('Metadata',
2302 '"streams" property must be an associative array')
2303
2304 if not streams_node:
2305 raise ConfigParseError('Metadata\'s "streams" property',
2306 'At least one stream is needed')
2307
2308 cur_id = 0
2309
2310 for stream_name, stream_node in streams_node.items():
2311 try:
2312 stream = self._create_stream(stream_name, stream_node)
2313 except ConfigParseError as exc:
2314 exc.append_ctx('Metadata',
2315 'Cannot create stream "{}"'.format(stream_name))
2316 raise
2317
2318 stream.id = cur_id
2319 stream.name = str(stream_name)
2320 streams[stream_name] = stream
2321 cur_id += 1
2322
2323 return streams
2324
2325 def _create_metadata(self, root):
2326 self._meta = _Metadata()
2327
2328 if 'metadata' not in root:
2329 raise ConfigParseError('Configuration',
2330 'Missing "metadata" property')
2331
2332 metadata_node = root['metadata']
2333
2334 if not _is_assoc_array_prop(metadata_node):
2335 raise ConfigParseError('Configuration\'s "metadata" property',
2336 'Must be an associative array')
2337
2338 known_props = [
2339 'type-aliases',
2340 'log-levels',
2341 'trace',
2342 'env',
2343 'clocks',
2344 'streams',
2345 ]
2346
2347 if self._version >= 201:
2348 known_props.append('$log-levels')
2349
2350 if self._version >= 202:
2351 known_props.append('$default-stream')
2352
2353 unk_prop = _get_first_unknown_prop(metadata_node, known_props)
2354
2355 if unk_prop:
2356 add = ''
2357
2358 if unk_prop == '$include':
2359 add = ' (use version 2.1 or greater)'
2360
2361 if unk_prop == '$default-stream':
2362 add = ' (use version 2.2 or greater)'
2363
2364 raise ConfigParseError('Metadata',
2365 'Unknown property{}: "{}"'.format(add, unk_prop))
2366
2367 if '$default-stream' in metadata_node and metadata_node['$default-stream'] is not None:
2368 default_stream_node = metadata_node['$default-stream']
2369
2370 if not _is_str_prop(default_stream_node):
2371 raise ConfigParseError('Metadata\'s "$default-stream" property',
2372 'Expecting a string')
2373
2374 self._meta.default_stream_name = default_stream_node
2375
2376 self._set_byte_order(metadata_node)
2377 self._register_clocks(metadata_node)
2378 self._meta.clocks = self._clocks
2379 self._register_type_aliases(metadata_node)
2380 self._meta.env = self._create_env(metadata_node)
2381 self._meta.trace = self._create_trace(metadata_node)
2382 self._register_log_levels(metadata_node)
2383 self._meta.streams = self._create_streams(metadata_node)
2384
2385 # validate metadata
2386 try:
2387 validator = _MetadataTypesHistologyValidator()
2388 validator.validate(self._meta)
2389 validator = _MetadataSpecialFieldsValidator()
2390 validator.validate(self._meta)
2391 except ConfigParseError as exc:
2392 exc.append_ctx('Metadata')
2393 raise
2394
2395 try:
2396 validator = _BarectfMetadataValidator()
2397 validator.validate(self._meta)
2398 except ConfigParseError as exc:
2399 exc.append_ctx('barectf metadata')
2400 raise
2401
2402 return self._meta
2403
2404 def _get_version(self, root):
2405 if 'version' not in root:
2406 raise ConfigParseError('Configuration',
2407 'Missing "version" property')
2408
2409 version_node = root['version']
2410
2411 if not _is_str_prop(version_node):
2412 raise ConfigParseError('Configuration\'s "version" property',
2413 'Must be a string')
2414
2415 version_node = version_node.strip()
2416
2417 if version_node not in ['2.0', '2.1', '2.2']:
2418 raise ConfigParseError('Configuration',
2419 'Unsupported version ({}): versions 2.0, 2.1, and 2.2 are supported'.format(version_node))
2420
2421 # convert version string to comparable version integer
2422 parts = version_node.split('.')
2423 version = int(parts[0]) * 100 + int(parts[1])
2424
2425 return version
2426
2427 def _get_prefix(self, root):
2428 def_prefix = 'barectf_'
2429
2430 if 'prefix' not in root:
2431 return def_prefix
2432
2433 prefix_node = root['prefix']
2434
2435 if prefix_node is None:
2436 return def_prefix
2437
2438 if not _is_str_prop(prefix_node):
2439 raise ConfigParseError('Configuration\'s "prefix" property',
2440 'Must be a string')
2441
2442 if not _is_valid_identifier(prefix_node):
2443 raise ConfigParseError('Configuration\'s "prefix" property',
2444 'Must be a valid C identifier')
2445
2446 return prefix_node
2447
2448 def _get_options(self, root):
2449 if 'options' not in root:
2450 return config.ConfigOptions()
2451
2452 options_node = root['options']
2453
2454 if not _is_assoc_array_prop(options_node):
2455 raise ConfigParseError('Configuration\'s "options" property',
2456 'Must be an associative array')
2457
2458 known_props = [
2459 'gen-prefix-def',
2460 'gen-default-stream-def',
2461 ]
2462 unk_prop = _get_first_unknown_prop(options_node, known_props)
2463
2464 if unk_prop:
2465 raise ConfigParseError('Configuration\'s "options" property',
2466 'Unknown property: "{}"'.format(unk_prop))
2467
2468 gen_prefix_def = None
2469
2470 if 'gen-prefix-def' in options_node and options_node['gen-prefix-def'] is not None:
2471 gen_prefix_def_node = options_node['gen-prefix-def']
2472
2473 if not _is_bool_prop(gen_prefix_def_node):
2474 raise ConfigParseError('Configuration\'s "options" property',
2475 'Invalid option "gen-prefix-def": expecting a boolean')
2476
2477 gen_prefix_def = gen_prefix_def_node
2478
2479 gen_default_stream_def = None
2480
2481 if 'gen-default-stream-def' in options_node and options_node['gen-default-stream-def'] is not None:
2482 gen_default_stream_def_node = options_node['gen-default-stream-def']
2483
2484 if not _is_bool_prop(gen_default_stream_def_node):
2485 raise ConfigParseError('Configuration\'s "options" property',
2486 'Invalid option "gen-default-stream-def": expecting a boolean')
2487
2488 gen_default_stream_def = gen_default_stream_def_node
2489
2490 return config.ConfigOptions(gen_prefix_def, gen_default_stream_def)
2491
2492 def _get_last_include_file(self):
2493 if self._include_stack:
2494 return self._include_stack[-1]
2495
2496 return self._root_yaml_path
2497
2498 def _load_include(self, yaml_path):
2499 for inc_dir in self._include_dirs:
2500 # current include dir + file name path
2501 # note: os.path.join() only takes the last arg if it's absolute
2502 inc_path = os.path.join(inc_dir, yaml_path)
2503
2504 # real path (symbolic links resolved)
2505 real_path = os.path.realpath(inc_path)
2506
2507 # normalized path (weird stuff removed!)
2508 norm_path = os.path.normpath(real_path)
2509
2510 if not os.path.isfile(norm_path):
2511 # file does not exist: skip
2512 continue
2513
2514 if norm_path in self._include_stack:
2515 base_path = self._get_last_include_file()
2516 raise ConfigParseError('In "{}"',
2517 'Cannot recursively include file "{}"'.format(base_path, norm_path))
2518
2519 self._include_stack.append(norm_path)
2520
2521 # load raw content
2522 return self._yaml_ordered_load(norm_path)
2523
2524 if not self._ignore_include_not_found:
2525 base_path = self._get_last_include_file()
2526 raise ConfigParseError('In "{}"',
2527 'Cannot include file "{}": file not found in include directories'.format(base_path, yaml_path))
2528
2529 return None
2530
2531 def _get_include_paths(self, include_node):
2532 if include_node is None:
2533 return []
2534
2535 if _is_str_prop(include_node):
2536 return [include_node]
2537
2538 if _is_array_prop(include_node):
2539 for include_path in include_node:
2540 if not _is_str_prop(include_path):
2541 raise ConfigParseError('"$include" property',
2542 'Expecting array of strings')
2543
2544 return include_node
2545
2546 raise ConfigParseError('"$include" property',
2547 'Expecting string or array of strings')
2548
2549 def _update_node(self, base_node, overlay_node):
2550 for olay_key, olay_value in overlay_node.items():
2551 if olay_key in base_node:
2552 base_value = base_node[olay_key]
2553
2554 if _is_assoc_array_prop(olay_value) and _is_assoc_array_prop(base_value):
2555 # merge dictionaries
2556 self._update_node(base_value, olay_value)
2557 elif _is_array_prop(olay_value) and _is_array_prop(base_value):
2558 # append extension array items to base items
2559 base_value += olay_value
2560 else:
2561 # fall back to replacing
2562 base_node[olay_key] = olay_value
2563 else:
2564 base_node[olay_key] = olay_value
2565
2566 def _process_node_include(self, last_overlay_node, name,
2567 process_base_include_cb,
2568 process_children_include_cb=None):
2569 if not _is_assoc_array_prop(last_overlay_node):
2570 raise ConfigParseError('"$include" property',
2571 '{} objects must be associative arrays'.format(name))
2572
2573 # process children inclusions first
2574 if process_children_include_cb:
2575 process_children_include_cb(last_overlay_node)
2576
2577 if '$include' in last_overlay_node:
2578 include_node = last_overlay_node['$include']
2579 else:
2580 # no includes!
2581 return last_overlay_node
2582
2583 include_paths = self._get_include_paths(include_node)
2584 cur_base_path = self._get_last_include_file()
2585 base_node = None
2586
2587 # keep the include paths and remove the include property
2588 include_paths = copy.deepcopy(include_paths)
2589 del last_overlay_node['$include']
2590
2591 for include_path in include_paths:
2592 # load raw YAML from included file
2593 overlay_node = self._load_include(include_path)
2594
2595 if overlay_node is None:
2596 # cannot find include file, but we're ignoring those
2597 # errors, otherwise _load_include() itself raises
2598 # a config error
2599 continue
2600
2601 # recursively process includes
2602 try:
2603 overlay_node = process_base_include_cb(overlay_node)
2604 except ConfigParseError as exc:
2605 exc.append_ctx('In "{}"'.format(cur_base_path))
2606 raise
2607
2608 # pop include stack now that we're done including
2609 del self._include_stack[-1]
2610
2611 # at this point, base_node is fully resolved (does not
2612 # contain any include property)
2613 if base_node is None:
2614 base_node = overlay_node
2615 else:
2616 self._update_node(base_node, overlay_node)
2617
2618 # finally, we update the latest base node with our last overlay
2619 # node
2620 if base_node is None:
2621 # nothing was included, which is possible when we're
2622 # ignoring include errors
2623 return last_overlay_node
2624
2625 self._update_node(base_node, last_overlay_node)
2626
2627 return base_node
2628
2629 def _process_event_include(self, event_node):
2630 return self._process_node_include(event_node, 'event',
2631 self._process_event_include)
2632
2633 def _process_stream_include(self, stream_node):
2634 def process_children_include(stream_node):
2635 if 'events' in stream_node:
2636 events_node = stream_node['events']
2637
2638 if not _is_assoc_array_prop(events_node):
2639 raise ConfigParseError('"$include" property',
2640 '"events" property must be an associative array')
2641
2642 events_node_keys = list(events_node.keys())
2643
2644 for key in events_node_keys:
2645 event_node = events_node[key]
2646
2647 try:
2648 events_node[key] = self._process_event_include(event_node)
2649 except ConfigParseError as exc:
2650 exc.append_ctx('"$include" property',
2651 'Cannot process includes of event object "{}"'.format(key))
2652 raise
2653
2654 return self._process_node_include(stream_node, 'stream',
2655 self._process_stream_include,
2656 process_children_include)
2657
2658 def _process_trace_include(self, trace_node):
2659 return self._process_node_include(trace_node, 'trace',
2660 self._process_trace_include)
2661
2662 def _process_clock_include(self, clock_node):
2663 return self._process_node_include(clock_node, 'clock',
2664 self._process_clock_include)
2665
2666 def _process_metadata_include(self, metadata_node):
2667 def process_children_include(metadata_node):
2668 if 'trace' in metadata_node:
2669 metadata_node['trace'] = self._process_trace_include(metadata_node['trace'])
2670
2671 if 'clocks' in metadata_node:
2672 clocks_node = metadata_node['clocks']
2673
2674 if not _is_assoc_array_prop(clocks_node):
2675 raise ConfigParseError('"$include" property',
2676 '"clocks" property must be an associative array')
2677
2678 clocks_node_keys = list(clocks_node.keys())
2679
2680 for key in clocks_node_keys:
2681 clock_node = clocks_node[key]
2682
2683 try:
2684 clocks_node[key] = self._process_clock_include(clock_node)
2685 except ConfigParseError as exc:
2686 exc.append_ctx('"$include" property',
2687 'Cannot process includes of clock object "{}"'.format(key))
2688 raise
2689
2690 if 'streams' in metadata_node:
2691 streams_node = metadata_node['streams']
2692
2693 if not _is_assoc_array_prop(streams_node):
2694 raise ConfigParseError('"$include" property',
2695 '"streams" property must be an associative array')
2696
2697 streams_node_keys = list(streams_node.keys())
2698
2699 for key in streams_node_keys:
2700 stream_node = streams_node[key]
2701
2702 try:
2703 streams_node[key] = self._process_stream_include(stream_node)
2704 except ConfigParseError as exc:
2705 exc.append_ctx('"$include" property',
2706 'Cannot process includes of stream object "{}"'.format(key))
2707 raise
2708
2709 return self._process_node_include(metadata_node, 'metadata',
2710 self._process_metadata_include,
2711 process_children_include)
2712
2713 def _process_root_includes(self, root):
2714 # The following config objects support includes:
2715 #
2716 # * Metadata object
2717 # * Trace object
2718 # * Stream object
2719 # * Event object
2720 #
2721 # We need to process the event includes first, then the stream
2722 # includes, then the trace includes, and finally the metadata
2723 # includes.
2724 #
2725 # In each object, only one of the $include and $include-replace
2726 # special properties is allowed.
2727 #
2728 # We keep a stack of absolute paths to included files to detect
2729 # recursion.
2730 if 'metadata' in root:
2731 root['metadata'] = self._process_metadata_include(root['metadata'])
2732
2733 return root
2734
2735 def _yaml_ordered_dump(self, node, **kwds):
2736 class ODumper(yaml.Dumper):
2737 pass
2738
2739 def dict_representer(dumper, node):
2740 return dumper.represent_mapping(yaml.resolver.BaseResolver.DEFAULT_MAPPING_TAG,
2741 node.items())
2742
2743 ODumper.add_representer(collections.OrderedDict, dict_representer)
2744
2745 return yaml.dump(node, Dumper=ODumper, **kwds)
2746
2747 def _yaml_ordered_load(self, yaml_path):
2748 class OLoader(yaml.Loader):
2749 pass
2750
2751 def construct_mapping(loader, node):
2752 loader.flatten_mapping(node)
2753
2754 return collections.OrderedDict(loader.construct_pairs(node))
2755
2756 OLoader.add_constructor(yaml.resolver.BaseResolver.DEFAULT_MAPPING_TAG,
2757 construct_mapping)
2758
2759 # YAML -> Python
2760 try:
2761 with open(yaml_path, 'r') as f:
2762 node = yaml.load(f, OLoader)
2763 except (OSError, IOError) as e:
2764 raise ConfigParseError('Configuration',
2765 'Cannot open file "{}"'.format(yaml_path))
2766 except ConfigParseError as exc:
2767 exc.append_ctx('Configuration',
2768 'Unknown error while trying to load file "{}"'.format(yaml_path))
2769 raise
2770
2771 # loaded node must be an associate array
2772 if not _is_assoc_array_prop(node):
2773 raise ConfigParseError('Configuration',
2774 'Root of YAML file "{}" must be an associative array'.format(yaml_path))
2775
2776 return node
2777
2778 def _reset(self):
2779 self._version = None
2780 self._include_stack = []
2781
2782 def parse(self, yaml_path):
2783 self._reset()
2784 self._root_yaml_path = yaml_path
2785
2786 try:
2787 root = self._yaml_ordered_load(yaml_path)
2788 except ConfigParseError as exc:
2789 exc.append_ctx('Configuration',
2790 'Cannot parse YAML file "{}"'.format(yaml_path))
2791 raise
2792
2793 if not _is_assoc_array_prop(root):
2794 raise ConfigParseError('Configuration',
2795 'Must be an associative array')
2796
2797 # get the config version
2798 self._version = self._get_version(root)
2799
2800 known_props = [
2801 'version',
2802 'prefix',
2803 'metadata',
2804 ]
2805
2806 if self._version >= 202:
2807 known_props.append('options')
2808
2809 unk_prop = _get_first_unknown_prop(root, known_props)
2810
2811 if unk_prop:
2812 add = ''
2813
2814 if unk_prop == 'options':
2815 add = ' (use version 2.2 or greater)'
2816
2817 raise ConfigParseError('Configuration',
2818 'Unknown property{}: "{}"'.format(add, unk_prop))
2819
2820 # process includes if supported
2821 if self._version >= 201:
2822 root = self._process_root_includes(root)
2823
2824 # dump config if required
2825 if self._dump_config:
2826 print(self._yaml_ordered_dump(root, indent=2,
2827 default_flow_style=False))
2828
2829 # get prefix and metadata
2830 prefix = self._get_prefix(root)
2831 meta = self._create_metadata(root)
2832 opts = self._get_options(root)
2833
2834 return config.Config(meta.to_public(), prefix, opts)
2835
2836
2837def _from_file(path, include_dirs, ignore_include_not_found, dump_config):
2838 try:
2839 parser = _YamlConfigParser(include_dirs, ignore_include_not_found,
2840 dump_config)
2841 return parser.parse(path)
2842 except ConfigParseError as exc:
2843 exc.append_ctx('Configuration',
2844 'Cannot create configuration from YAML file "{}"'.format(path))
2845 raise
This page took 0.123764 seconds and 4 git commands to generate.