Add some metadata validation
authorPhilippe Proulx <eeppeliteloop@gmail.com>
Fri, 7 Nov 2014 07:11:06 +0000 (02:11 -0500)
committerPhilippe Proulx <eeppeliteloop@gmail.com>
Fri, 7 Nov 2014 07:11:15 +0000 (02:11 -0500)
barectf/cli.py

index 2f02bd7c086a7f7b7f2415683a73caa5306eec51..2c1e0a682e9d88cba58c2b614935b9d219b8add9 100644 (file)
@@ -21,6 +21,8 @@
 # THE SOFTWARE.
 from termcolor import cprint, colored
 import argparse
+import pytsdl.tsdl
+import pytsdl.parser
 import sys
 import os
 import re
@@ -69,7 +71,252 @@ def _parse_args():
     return args
 
 
-def gen_barectf(output, prefix, static_inline, manual_clock):
+def _validate_struct(struct):
+    if type(struct) is not pytsdl.tsdl.Struct:
+        raise RuntimeError('expecting a struct')
+
+    for name, ftype in struct.fields.items():
+        if type(ftype) is pytsdl.tsdl.Sequence:
+            raise RuntimeError('field "{}" is a dynamic array'.format(name))
+        elif type(ftype) is pytsdl.tsdl.Array:
+            end = False
+            element = ftype.element
+
+            while not end:
+                if type(element) is pytsdl.tsdl.Sequence:
+                    raise RuntimeError('field "{}" contains a dynamic array'.format(name))
+                elif type(element) is pytsdl.tsdl.Variant:
+                    raise RuntimeError('field "{}" contains a variant (unsupported)'.format(name))
+                elif type(element) is pytsdl.tsdl.String:
+                    raise RuntimeError('field "{}" contains a string'.format(name))
+                elif type(element) is pytsdl.tsdl.Struct:
+                    _validate_struct(element)
+
+                if type(element) is pytsdl.tsdl.Array:
+                    element = element.element
+                else:
+                    end = True
+        elif type(ftype) is pytsdl.tsdl.Variant:
+            raise RuntimeError('field "{}" is a variant (unsupported)'.format(name))
+        elif type(ftype) is pytsdl.tsdl.String:
+            raise RuntimeError('field "{}" is a string'.format(name))
+        elif type(ftype) is pytsdl.tsdl.Struct:
+            _validate_struct(ftype)
+
+
+def _validate_integer(integer, size=None, align=None, signed=None):
+    if type(integer) is not pytsdl.tsdl.Integer:
+        raise RuntimeError('expected integer')
+
+    if size is not None:
+        if integer.size != size:
+            raise RuntimeError('expected {}-bit integer'.format(size))
+
+    if align is not None:
+        if integer.align != align:
+            raise RuntimeError('expected integer with {}-bit alignment'.format(align))
+
+    if signed is not None:
+        if integer.signed != signed:
+            raise RuntimeError('expected {} integer'.format('signed' if signed else 'unsigned'))
+
+
+def _validate_packet_header(packet_header):
+    try:
+        _validate_struct(packet_header)
+    except RuntimeError as e:
+        _perror('packet header: {}'.format(e))
+
+    # magic must be the first field
+    if 'magic' in packet_header.fields:
+        if list(packet_header.fields.keys())[0] != 'magic':
+            _perror('packet header: "magic" must be the first field')
+    else:
+        _perror('packet header: missing "magic" field')
+
+    # magic must be a 32-bit unsigned integer, 32-bit aligned
+    try:
+        _validate_integer(packet_header['magic'], 32, 32, False)
+    except RuntimeError as e:
+        _perror('packet header: "magic": {}'.format(e))
+
+    # mandatory stream_id
+    if 'stream_id' not in packet_header.fields:
+        _perror('packet header: missing "stream_id" field')
+
+    # stream_id must be an unsigned integer
+    try:
+        _validate_integer(packet_header['stream_id'], signed=False)
+    except RuntimeError as e:
+        _perror('packet header: "stream_id": {}'.format(e))
+
+
+def _dot_name_to_str(name):
+    return '.'.join(name)
+
+
+def _validate_clock(doc, name):
+    msg = '"{}" does not name an existing clock'.format(_dot_name_to_str(name))
+
+    if len(name) != 3:
+        raise RuntimeError(msg)
+
+    if name[0] != 'clock' or name[2] != 'value':
+        raise RuntimeError()
+
+    if name[1] not in doc.clocks:
+        raise RuntimeError(msg)
+
+
+def _compare_integers(int1, int2):
+    if type(int1) is not pytsdl.tsdl.Integer:
+        return False
+
+    if type(int2) is not pytsdl.tsdl.Integer:
+        return False
+
+    size = int1.size == int2.size
+    align = int1.align == int2.align
+    cmap = int1.map == int2.map
+    base = int1.base == int2.base
+    encoding = int1.encoding == int2.encoding
+    signed = int1.signed == int2.signed
+    comps = (size, align, cmap, base, encoding, signed)
+
+    return sum(comps) == len(comps)
+
+
+def _validate_packet_context(doc, stream):
+    packet_context = stream.packet_context
+    sid = stream.id
+
+    try:
+        _validate_struct(packet_context)
+    except RuntimeError as e:
+        _perror('stream {}: packet context: {}'.format(sid, e))
+
+    fields = packet_context.fields
+
+    # if timestamp_begin exists, timestamp_end must exist
+    if 'timestamp_begin' in fields or 'timestamp_end' in fields:
+        if 'timestamp_begin' not in fields or 'timestamp_end' not in fields:
+            _perror('stream {}: packet context: "timestamp_begin" must exist if "timestamp_end" exists'.format(sid))
+        else:
+            # timestamp_begin and timestamp_end must have the same integer
+            # as the event header's timestamp field (should exist by now)
+            timestamp = stream.event_header['timestamp']
+
+            if not _compare_integers(fields['timestamp_begin'], timestamp):
+                _perror('stream {}: packet context: "timestamp_begin": integer type different from event header\'s "timestamp" field'.format(sid))
+
+            if not _compare_integers(fields['timestamp_end'], timestamp):
+                _perror('stream {}: packet context: "timestamp_end": integer type different from event header\'s "timestamp" field'.format(sid))
+
+    # content_size must exist and be an unsigned integer
+    if 'content_size' not in fields:
+        _perror('stream {}: packet context: missing "content_size" field'.format(sid))
+
+    try:
+        _validate_integer(fields['content_size'], 32, 32, False)
+    except:
+        try:
+            _validate_integer(fields['content_size'], 64, 64, False)
+        except:
+            _perror('stream {}: packet context: "content_size": expecting unsigned 32-bit/64-bit integer'.format(sid))
+
+    # packet_size must exist and be an unsigned integer
+    if 'packet_size' not in fields:
+        _perror('stream {}: packet context: missing "packet_size" field'.format(sid))
+
+    try:
+        _validate_integer(fields['packet_size'], 32, 32, False)
+    except:
+        try:
+            _validate_integer(fields['packet_size'], 64, 64, False)
+        except:
+            _perror('stream {}: packet context: "packet_size": expecting unsigned 32-bit/64-bit integer'.format(sid))
+
+    # if cpu_id exists, must be an unsigned integer
+    if 'cpu_id' in fields:
+        try:
+            _validate_integer(fields['cpu_id'], signed=False)
+        except RuntimeError as e:
+            _perror('stream {}: packet context: "cpu_id": {}'.format(sid, e))
+
+
+def _validate_event_header(doc, stream):
+    event_header = stream.event_header
+    sid = stream.id
+
+    try:
+        _validate_struct(event_header)
+    except RuntimeError as e:
+        _perror('stream {}: event header: {}'.format(sid, e))
+
+    fields = event_header.fields
+
+    # id must exist and be an unsigned integer
+    if 'id' not in fields:
+        _perror('stream {}: event header: missing "id" field'.format(sid))
+
+    try:
+        _validate_integer(fields['id'], signed=False)
+    except RuntimeError as e:
+        _perror('stream {}: "id": {}'.format(sid, format(e)))
+
+
+    # timestamp must exist, be an unsigned integer and be mapped to a valid clock
+    if 'timestamp' not in fields:
+        _perror('stream {}: event header: missing "timestamp" field'.format(sid))
+
+    try:
+        _validate_integer(fields['timestamp'], signed=False)
+    except RuntimeError as e:
+        _perror('stream {}: "timestamp": {}'.format(sid, format(e)))
+
+    if fields['timestamp'].map is None:
+        _perror('stream {}: "timestamp": integer must be mapped to an existing clock'.format(sid))
+
+    try:
+        _validate_clock(doc, fields['timestamp'].map)
+    except RuntimeError as e:
+        _perror('stream {}: "timestamp": integer must be mapped to an existing clock'.format(sid))
+
+
+def _validate_headers_contexts(doc):
+    # packet header
+    _validate_packet_header(doc.trace.packet_header)
+
+    # stream stuff
+    for stream_id, stream in doc.streams.items():
+        _validate_event_header(doc, stream)
+        _validate_packet_context(doc, stream)
+
+
+def _validate_metadata(doc):
+    _validate_headers_contexts(doc)
+
+
+def gen_barectf(metadata, output, prefix, static_inline, manual_clock):
+    # open CTF metadata file
+    try:
+        with open(metadata) as f:
+            tsdl = f.read()
+    except:
+        _perror('cannot open/read CTF metadata file "{}"'.format(metadata))
+
+    # parse CTF metadata
+    parser = pytsdl.parser.Parser()
+
+    try:
+        doc = parser.parse(tsdl)
+    except pytsdl.parser.ParseError as e:
+        _perror('parse error: {}'.format(e))
+
+    # validate CTF metadata against barectf constraints
+    _validate_metadata(doc)
+
+    _pinfo(metadata)
     _pinfo(output)
     _pinfo(prefix)
     _pinfo(static_inline)
@@ -78,5 +325,5 @@ def gen_barectf(output, prefix, static_inline, manual_clock):
 
 def run():
     args = _parse_args()
-    gen_barectf(args.output, args.prefix, args.static_inline,
+    gen_barectf(args.metadata, args.output, args.prefix, args.static_inline,
                 args.manual_clock)
This page took 0.027177 seconds and 4 git commands to generate.