diff options
Diffstat (limited to 'lib')
-rwxr-xr-x | lib/protocol_benchmarks.py | 1225 |
1 files changed, 1225 insertions, 0 deletions
diff --git a/lib/protocol_benchmarks.py b/lib/protocol_benchmarks.py new file mode 100755 index 0000000..f1f0f7c --- /dev/null +++ b/lib/protocol_benchmarks.py @@ -0,0 +1,1225 @@ +import bson +import cbor +import json +import msgpack +import ubjson + +import os +from filelock import FileLock + +class DummyProtocol: + def __init__(self): + self.max_serialized_bytes = None + self.enc_buf = '' + self.dec_buf = '' + self.dec_buf0 = '' + self.dec_buf1 = '' + self.dec_buf2 = '' + self.dec_index = 0 + + def assign_and_kout(self, signature, assignment): + self.new_var(signature) + self.assign_var(assignment) + self.kout_var() + + def new_var(self, signature): + self.dec_index += 1 + self.dec_buf0 += '{} dec_{:d};\n'.format(signature, self.dec_index) + + def assign_var(self, assignment): + self.dec_buf1 += 'dec_{:d} = {};\n'.format(self.dec_index, assignment) + + def get_var(self): + return 'dec_{:d}'.format(self.dec_index) + + def kout_var(self): + self.dec_buf2 += 'kout << dec_{:d};\n'.format(self.dec_index) + + def note_unsupported(self, value): + note = '// value {} has unsupported type {}\n'.format(value, type(value)) + self.enc_buf += note + self.dec_buf += note + self.dec_buf1 += note + + def is_ascii(self): + return True + + def get_encode(self): + return '' + + def get_buffer_declaration(self): + return '' + + def get_serialize(self): + return '' + + def get_deserialize(self): + return '' + + def get_decode_and_output(self): + return '' + + def get_decode_vars(self): + return '' + + def get_decode(self): + return '' + + def get_decode_output(self): + return '' + + def get_extra_files(self): + return dict() + +class ArduinoJSON(DummyProtocol): + + def __init__(self, data, bufsize = 255, int_type = 'uint16_t', float_type = 'float'): + super().__init__() + self.data = data + self.max_serialized_bytes = self.get_serialized_length() + 2 + self.children = set() + self.bufsize = bufsize + self.int_type = int_type + self.float_type = float_type + self.enc_buf += 'ArduinoJson::StaticJsonBuffer<{:d}> jsonBuffer;\n'.format(bufsize) + self.enc_buf += 'ArduinoJson::JsonObject& root = jsonBuffer.createObject();\n' + self.from_json(data, 'root') + + def get_serialized_length(self): + return len(json.dumps(self.data)) + + def get_encode(self): + return self.enc_buf + + def get_buffer_declaration(self): + return 'char buf[{:d}];\n'.format(self.max_serialized_bytes) + + def get_buffer_name(self): + return 'buf' + + def get_length_var(self): + return 'serialized_size' + + def get_serialize(self): + return 'uint16_t serialized_size = root.printTo(buf);\n' + + def get_deserialize(self): + ret = 'ArduinoJson::StaticJsonBuffer<{:d}> jsonBuffer;\n'.format(self.bufsize) + ret += 'ArduinoJson::JsonObject& root = jsonBuffer.parseObject(buf);\n' + return ret + + def get_decode_and_output(self): + return 'kout << dec << "dec:";\n' + self.dec_buf + 'kout << endl;\n'; + + def get_decode_vars(self): + return self.dec_buf0 + + def get_decode(self): + return self.dec_buf1 + + def get_decode_output(self): + return 'kout << dec << "dec:";\n' + self.dec_buf2 + 'kout << endl;\n'; + + def add_to_list(self, enc_node, dec_node, offset, value): + if type(value) == str: + if len(value) and value[0] == '$': + self.enc_buf += '{}.add({});\n'.format(enc_node, value[1:]) + self.dec_buf += 'kout << {}[{:d}].as<{}>();\n'.format(dec_node, offset, self.int_type) + self.assign_and_kout(self.int_type, '{}[{:d}].as<{}>()'.format(dec_node, offset, self.int_type)) + else: + self.enc_buf += '{}.add("{}");\n'.format(enc_node, value) + self.dec_buf += 'kout << {}[{:d}].as<const char *>();\n'.format(dec_node, offset) + self.assign_and_kout('char const*', '{}[{:d}].as<char const *>()'.format(dec_node, offset)) + + elif type(value) == list: + child = enc_node + 'l' + while child in self.children: + child += '_' + self.enc_buf += 'ArduinoJson::JsonArray& {} = {}.createNestedArray();\n'.format( + child, enc_node) + self.children.add(child) + self.from_json(value, child) + + elif type(value) == dict: + child = enc_node + 'o' + while child in self.children: + child += '_' + self.enc_buf += 'ArduinoJson::JsonObject& {} = {}.createNestedObject();\n'.format( + child, enc_node) + self.children.add(child) + self.from_json(value, child) + + elif type(value) == float: + self.enc_buf += '{}.add({});\n'.format(enc_node, value) + self.dec_buf += 'kout << {}[{:d}].as<{}>();\n'.format(dec_node, offset, self.float_type) + self.assign_and_kout(self.float_type, '{}[{:d}].as<{}>()'.format(dec_node, offset, self.float_type)) + + elif type(value) == int: + self.enc_buf += '{}.add({});\n'.format(enc_node, value) + self.dec_buf += 'kout << {}[{:d}].as<{}>();\n'.format(dec_node, offset, self.int_type) + self.assign_and_kout(self.int_type, '{}[{:d}].as<{}>()'.format(dec_node, offset, self.int_type)) + + else: + self.note_unsupported(value) + + def add_to_dict(self, enc_node, dec_node, key, value): + if type(value) == str: + if len(value) and value[0] == '$': + self.enc_buf += '{}["{}"] = {};\n'.format(enc_node, key, value[1:]) + self.dec_buf += 'kout << {}["{}"].as<{}>();\n'.format(dec_node, key, self.int_type) + self.assign_and_kout(self.int_type, '{}["{}"].as<{}>()'.format(dec_node, key, self.int_type)) + else: + self.enc_buf += '{}["{}"] = "{}";\n'.format(enc_node, key, value) + self.dec_buf += 'kout << {}["{}"].as<const char *>();\n'.format(dec_node, key) + self.assign_and_kout('char const*', '{}["{}"].as<const char *>()'.format(dec_node, key)) + + elif type(value) == list: + child = enc_node + 'l' + while child in self.children: + child += '_' + self.enc_buf += 'ArduinoJson::JsonArray& {} = {}.createNestedArray("{}");\n'.format( + child, enc_node, key) + self.children.add(child) + self.from_json(value, child, '{}["{}"]'.format(dec_node, key)) + + elif type(value) == dict: + child = enc_node + 'o' + while child in self.children: + child += '_' + self.enc_buf += 'ArduinoJson::JsonObject& {} = {}.createNestedObject("{}");\n'.format( + child, enc_node, key) + self.children.add(child) + self.from_json(value, child, '{}["{}"]'.format(dec_node, key)) + + elif type(value) == float: + self.enc_buf += '{}["{}"] = {};\n'.format(enc_node, key, value) + self.dec_buf += 'kout << {}["{}"].as<{}>();\n'.format(dec_node, key, self.float_type) + self.assign_and_kout(self.float_type, '{}["{}"].as<{}>()'.format(dec_node, key, self.float_type)) + + elif type(value) == int: + self.enc_buf += '{}["{}"] = {};\n'.format(enc_node, key, value) + self.dec_buf += 'kout << {}["{}"].as<{}>();\n'.format(dec_node, key, self.int_type) + self.assign_and_kout(self.int_type, '{}["{}"].as<{}>()'.format(dec_node, key, self.int_type)) + + else: + self.note_unsupported(tvalue) + + def from_json(self, data, enc_node = 'root', dec_node = 'root'): + if type(data) == dict: + for key in sorted(data.keys()): + self.add_to_dict(enc_node, dec_node, key, data[key]) + elif type(data) == list: + for i, elem in enumerate(data): + self.add_to_list(enc_node, dec_node, i, elem) + + +class CapnProtoC(DummyProtocol): + + def __init__(self, data, max_serialized_bytes = 128, packed = False, trail = ['benchmark'], int_type = 'uint16_t', float_type = 'float', dec_index = 0): + super().__init__() + self.data = data + self.max_serialized_bytes = max_serialized_bytes + self.packed = packed + self.name = trail[-1] + self.trail = trail + self.int_type = int_type + self.proto_int_type = self.int_type_to_proto_type(int_type) + self.float_type = float_type + self.proto_float_type = self.float_type_to_proto_type(float_type) + self.dec_index = dec_index + self.trail_name = '_'.join(map(lambda x: x.capitalize(), trail)) + self.proto_buf = '' + self.enc_buf += 'struct {} {};\n'.format(self.trail_name, self.name) + self.cc_tail = '' + self.key_counter = 0 + self.from_json(data) + + def int_type_to_proto_type(self, int_type): + sign = '' + if int_type[0] == 'u': + sign = 'U' + if '8' in int_type: + self.int_bits = 8 + return sign + 'Int8' + if '16' in int_type: + self.int_bits = 16 + return sign + 'Int16' + if '32' in int_type: + self.int_bits = 32 + return sign + 'Int32' + self.int_bits = 64 + return sign + 'Int64' + + def float_type_to_proto_type(self, float_type): + if float_type == 'float': + self.float_bits = 32 + return 'Float32' + self.float_bits = 64 + return 'Float64' + + def is_ascii(self): + return False + + def get_proto(self): + return '@0xad5b236043de2389;\n\n' + self.proto_buf + + def get_extra_files(self): + return { + 'capnp_c_bench.capnp' : self.get_proto() + } + + def get_buffer_declaration(self): + ret = 'uint8_t buf[{:d}];\n'.format(self.max_serialized_bytes) + ret += 'uint16_t serialized_size;\n' + return ret + + def get_buffer_name(self): + return 'buf' + + def get_encode(self): + ret = 'struct capn c;\n' + ret += 'capn_init_malloc(&c);\n' + ret += 'capn_ptr cr = capn_root(&c);\n' + ret += 'struct capn_segment *cs = cr.seg;\n\n' + ret += '{}_ptr {}_ptr = new_{}(cs);\n'.format( + self.trail_name, self.name, self.trail_name) + + tail = 'write_{}(&{}, {}_ptr);\n'.format( + self.trail_name, self.name, self.name) + tail += 'capn_setp(cr, 0, {}_ptr.p);\n'.format(self.name) + + return ret + self.enc_buf + self.cc_tail + tail + + def get_serialize(self): + ret = 'serialized_size = capn_write_mem(&c, buf, sizeof(buf), {:d});\n'.format(self.packed) + ret += 'capn_free(&c);\n' + return ret + + def get_deserialize(self): + ret = 'struct capn c;\n' + ret += 'capn_init_mem(&c, buf, serialized_size, 0);\n' + return ret + + def get_decode_and_output(self): + ret = '{}_ptr {}_ptr;\n'.format(self.trail_name, self.name) + ret += '{}_ptr.p = capn_getp(capn_root(&c), 0, 1);\n'.format(self.name) + ret += 'struct {} {};\n'.format(self.trail_name, self.name) + ret += 'kout << dec << "dec:";\n' + ret += self.dec_buf + ret += 'kout << endl;\n' + ret += 'capn_free(&c);\n' + return ret + + def get_decode_vars(self): + return self.dec_buf0 + + def get_decode(self): + ret = '{}_ptr {}_ptr;\n'.format(self.trail_name, self.name) + ret += '{}_ptr.p = capn_getp(capn_root(&c), 0, 1);\n'.format(self.name) + ret += 'struct {} {};\n'.format(self.trail_name, self.name) + ret += self.dec_buf1 + ret += 'capn_free(&c);\n' + return ret + + def get_decode_output(self): + return 'kout << dec << "dec:";\n' + self.dec_buf2 + 'kout << endl;\n'; + + def get_length_var(self): + return 'serialized_size' + + def add_field(self, fieldtype, key, value): + extra = '' + texttype = self.proto_int_type + + if fieldtype == str: + texttype = 'Text' + elif fieldtype == float: + texttype = self.proto_float_type + elif fieldtype == dict: + texttype = key.capitalize() + + if type(value) == list: + texttype = 'List({})'.format(texttype) + + self.proto_buf += '{} @{:d} :{};\n'.format( + key, self.key_counter, texttype) + self.key_counter += 1 + + if fieldtype == str: + self.enc_buf += 'capn_text {}_text;\n'.format(key) + self.enc_buf += '{}_text.len = {:d};\n'.format(key, len(value)) + self.enc_buf += '{}_text.str = "{}";\n'.format(key, value) + self.enc_buf += '{}_text.seg = NULL;\n'.format(key) + self.enc_buf += '{}.{} = {}_text;\n\n'.format(self.name, key, key) + self.dec_buf += 'kout << {}.{}.str;\n'.format(self.name, key) + self.assign_and_kout('char const *', '{}.{}.str'.format(self.name, key)) + elif fieldtype == dict: + pass # content is handled recursively in add_to_dict + elif type(value) == list: + if type(value[0]) == float: + self.enc_buf += '{}.{} = capn_new_list{:d}(cs, {:d});\n'.format( + self.name, key, self.float_bits, len(value)) + for i, elem in enumerate(value): + self.enc_buf += 'capn_set{:d}({}.{}, {:d}, capn_from_f{:d}({:f}));\n'.format( + self.float_bits, self.name, key, i, self.float_bits, elem) + self.dec_buf += 'kout << capn_to_f{:d}(capn_get{:d}({}.{}, {:d}));\n'.format( + self.float_bits, self.float_bits, self.name, key, i) + self.assign_and_kout(self.float_type, 'capn_to_f{:d}(capn_get{:d}({}.{}, {:d}))'.format(self.float_bits, self.float_bits, self.name, key, i)) + else: + self.enc_buf += '{}.{} = capn_new_list{:d}(cs, {:d});\n'.format( + self.name, key, self.int_bits, len(value)) + for i, elem in enumerate(value): + self.enc_buf += 'capn_set{:d}({}.{}, {:d}, {:d});\n'.format( + self.int_bits, self.name, key, i, elem) + self.dec_buf += 'kout << capn_get{:d}({}.{}, {:d});\n'.format( + self.int_bits, self.name, key, i) + self.assign_and_kout(self.int_type, 'capn_get{:d}({}.{}, {:d})'.format(self.int_bits, self.name, key, i)) + elif fieldtype == float: + self.enc_buf += '{}.{} = {};\n\n'.format(self.name, key, value) + self.dec_buf += 'kout << {}.{};\n'.format(self.name, key) + self.assign_and_kout(self.float_type, '{}.{}'.format(self.name, key)) + + elif fieldtype == int: + self.enc_buf += '{}.{} = {};\n\n'.format(self.name, key, value) + self.dec_buf += 'kout << {}.{};\n'.format(self.name, key) + self.assign_and_kout(self.int_type, '{}.{}'.format(self.name, key)) + + else: + self.note_unsupported(value) + + def add_to_dict(self, key, value): + if type(value) == str: + if len(value) and value[0] == '$': + self.add_field(int, key, value[1:]) + else: + self.add_field(str, key, value) + elif type(value) == list: + self.add_field(type(value[0]), key, value) + elif type(value) == dict: + trail = list(self.trail) + trail.append(key) + nested = CapnProtoC(value, trail = trail, int_type = self.int_type, float_type = self.float_type, dec_index = self.dec_index) + self.add_field(dict, key, value) + self.enc_buf += '{}.{} = new_{}_{}(cs);\n'.format( + self.name, key, self.trail_name, key.capitalize()) + self.enc_buf += nested.enc_buf + self.enc_buf += 'write_{}_{}(&{}, {}.{});\n'.format( + self.trail_name, key.capitalize(), key, self.name, key) + self.dec_buf += 'struct {}_{} {};\n'.format(self.trail_name, key.capitalize(), key) + self.dec_buf += 'read_{}_{}(&{}, {}.{});\n'.format(self.trail_name, key.capitalize(), key, self.name, key) + self.dec_buf += nested.dec_buf + self.dec_buf0 += nested.dec_buf0 + self.dec_buf1 += 'struct {}_{} {};\n'.format(self.trail_name, key.capitalize(), key) + self.dec_buf1 += 'read_{}_{}(&{}, {}.{});\n'.format(self.trail_name, key.capitalize(), key, self.name, key) + self.dec_buf1 += nested.dec_buf1 + self.dec_buf2 += nested.dec_buf2 + self.dec_index = nested.dec_index + self.proto_buf += nested.proto_buf + else: + self.add_field(type(value), key, value) + + def from_json(self, data): + self.proto_buf += 'struct {} {{\n'.format(self.name.capitalize()) + if type(data) == dict: + for key in sorted(data.keys()): + self.add_to_dict(key, data[key]) + self.proto_buf += '}\n' + +class ManualJSON(DummyProtocol): + + def __init__(self, data): + super().__init__() + self.data = data + self.max_serialized_bytes = self.get_serialized_length() + 2 + self.buf = 'BufferOutput<> bout(buf);\n' + self.buf += 'bout << "{";\n' + self.from_json(data) + self.buf += 'bout << "}";\n' + + def get_serialized_length(self): + return len(json.dumps(self.data)) + + def is_ascii(self): + return True + + def get_buffer_declaration(self): + return 'char buf[{:d}];\n'.format(self.max_serialized_bytes); + + def get_buffer_name(self): + return 'buf' + + def get_encode(self): + return self.buf + + def get_length_var(self): + return 'bout.size()' + + def add_to_list(self, value, is_last): + if type(value) == str: + if len(value) and value[0] == '$': + self.buf += 'bout << dec << {}'.format(value[1:]) + else: + self.buf += 'bout << "\"{}\""'.format(value) + + elif type(value) == list: + self.buf += 'bout << "[";\n' + self.from_json(value) + self.buf += 'bout << "]"' + + elif type(value) == dict: + self.buf += 'bout << "{";\n' + self.from_json(value) + self.buf += 'bout << "}"' + + else: + self.buf += 'bout << "{}"'.format(value) + + if is_last: + self.buf += ';\n'; + else: + self.buf += ' << ",";\n' + + def add_to_dict(self, key, value, is_last): + if type(value) == str: + if len(value) and value[0] == '$': + self.buf += 'bout << "\\"{}\\":" << dec << {}'.format(key, value[1:]) + else: + self.buf += 'bout << "\\"{}\\":\\"{}\\""'.format(key, value) + + elif type(value) == list: + self.buf += 'bout << "\\"{}\\":[";\n'.format(key) + self.from_json(value) + self.buf += 'bout << "]"' + + elif type(value) == dict: + # '{{' is an escaped '{' character + self.buf += 'bout << "\\"{}\\":{{";\n'.format(key) + self.from_json(value) + self.buf += 'bout << "}"' + + else: + self.buf += 'bout << "\\"{}\\":{}"'.format(key, value) + + if is_last: + self.buf += ';\n' + else: + self.buf += ' << ",";\n' + + def from_json(self, data): + if type(data) == dict: + keys = sorted(data.keys()) + for key in keys: + self.add_to_dict(key, data[key], key == keys[-1]) + elif type(data) == list: + for i, elem in enumerate(data): + self.add_to_list(elem, i == len(data) - 1) + +class ModernJSON(DummyProtocol): + + def __init__(self, data, output_format = 'json'): + super().__init__() + self.data = data + self.output_format = output_format + self.buf = 'nlohmann::json js;\n' + self.from_json(data) + + def is_ascii(self): + if self.output_format == 'json': + return True + return False + + def get_buffer_name(self): + return 'out' + + def get_encode(self): + return self.buf + + def get_serialize(self): + if self.output_format == 'json': + return 'std::string out = js.dump();\n' + elif self.output_format == 'bson': + return 'std::vector<std::uint8_t> out = nlohmann::json::to_bson(js);\n' + elif self.output_format == 'cbor': + return 'std::vector<std::uint8_t> out = nlohmann::json::to_cbor(js);\n' + elif self.output_format == 'msgpack': + return 'std::vector<std::uint8_t> out = nlohmann::json::to_msgpack(js);\n' + elif self.output_format == 'ubjson': + return 'std::vector<std::uint8_t> out = nlohmann::json::to_ubjson(js);\n' + else: + raise ValueError('invalid output format {}'.format(self.output_format)) + + def get_length_var(self): + return 'out.size()' + + def add_to_list(self, prefix, index, value): + if type(value) == str: + if len(value) and value[0] == '$': + self.buf += value[1:] + self.buf += '{}[{:d}] = {};\n'.format(prefix, index, value[1:]) + else: + self.buf += '{}[{:d}] = "{}";\n'.format(prefix, index, value) + + else: + self.buf += '{}[{:d}] = {};\n'.format(prefix, index, value) + + def add_to_dict(self, prefix, key, value): + if type(value) == str: + if len(value) and value[0] == '$': + self.buf += '{}["{}"] = {};\n'.format(prefix, key, value[1:]) + else: + self.buf += '{}["{}"] = "{}";\n'.format(prefix, key, value) + + elif type(value) == list: + self.from_json(value, '{}["{}"]'.format(prefix, key)) + + elif type(value) == dict: + self.from_json(value, '{}["{}"]'.format(prefix, key)) + + else: + self.buf += '{}["{}"] = {};\n'.format(prefix, key, value) + + def from_json(self, data, prefix = 'js'): + if type(data) == dict: + for key in sorted(data.keys()): + self.add_to_dict(prefix, key, data[key]) + elif type(data) == list: + for i, elem in enumerate(data): + self.add_to_list(prefix, i, elem) + +class MPack(DummyProtocol): + + def __init__(self, data, int_type = 'uint16_t', float_type = 'float'): + super().__init__() + self.data = data + self.max_serialized_bytes = self.get_serialized_length() + 2 + self.int_type = int_type + self.float_type = float_type + self.enc_buf += 'mpack_writer_t writer;\n' + self.enc_buf += 'mpack_writer_init(&writer, buf, sizeof(buf));\n' + self.dec_buf0 += 'char strbuf[16];\n' + self.from_json(data) + + def get_serialized_length(self): + return len(msgpack.dumps(self.data)) + + def is_ascii(self): + return False + + def get_buffer_declaration(self): + ret = 'char buf[{:d}];\n'.format(self.max_serialized_bytes) + ret += 'uint16_t serialized_size;\n' + return ret + + def get_buffer_name(self): + return 'buf' + + def get_encode(self): + return self.enc_buf + + def get_serialize(self): + ret = 'serialized_size = mpack_writer_buffer_used(&writer);\n' + ret += 'if (mpack_writer_destroy(&writer) != mpack_ok) {\n' + ret += 'kout << "Encoding failed" << endl;\n' + ret += '}\n' + return ret + + def get_deserialize(self): + ret = 'mpack_reader_t reader;\n' + ret += 'mpack_reader_init_data(&reader, buf, serialized_size);\n' + return ret + + def get_decode_and_output(self): + ret = 'kout << dec << "dec:";\n' + ret += 'char strbuf[16];\n' + return ret + self.dec_buf + 'kout << endl;\n' + + def get_decode_vars(self): + return self.dec_buf0 + + def get_decode(self): + return self.dec_buf1 + + def get_decode_output(self): + return 'kout << dec << "dec:";\n' + self.dec_buf2 + 'kout << endl;\n'; + + def get_length_var(self): + return 'serialized_size' + + def add_value(self, value): + if type(value) == str: + if len(value) and value[0] == '$': + self.enc_buf += 'mpack_write(&writer, {});\n'.format(value[1:]) + self.dec_buf += 'kout << mpack_expect_uint(&reader);\n' + self.assign_and_kout(self.int_type, 'mpack_expect_uint(&reader)') + else: + self.enc_buf += 'mpack_write_cstr_or_nil(&writer, "{}");\n'.format(value) + self.dec_buf += 'mpack_expect_cstr(&reader, strbuf, sizeof(strbuf));\n' + self.dec_buf += 'kout << strbuf;\n' + self.dec_buf1 += 'mpack_expect_cstr(&reader, strbuf, sizeof(strbuf));\n' + self.dec_buf2 += 'kout << strbuf;\n' + elif type(value) == list: + self.from_json(value) + elif type(value) == dict: + self.from_json(value) + elif type(value) == int: + self.enc_buf += 'mpack_write(&writer, ({}){:d});\n'.format(self.int_type, value) + self.dec_buf += 'kout << mpack_expect_uint(&reader);\n' + self.assign_and_kout(self.int_type, 'mpack_expect_uint(&reader)') + elif type(value) == float: + self.enc_buf += 'mpack_write(&writer, ({}){:f});\n'.format(self.float_type, value) + self.dec_buf += 'kout << mpack_expect_float(&reader);\n' + self.assign_and_kout(self.float_type, 'mpack_expect_float(&reader)') + else: + self.note_unsupported(value) + + def from_json(self, data): + if type(data) == dict: + self.enc_buf += 'mpack_start_map(&writer, {:d});\n'.format(len(data)) + self.dec_buf += 'mpack_expect_map_max(&reader, {:d});\n'.format(len(data)) + self.dec_buf1 += 'mpack_expect_map_max(&reader, {:d});\n'.format(len(data)) + for key in sorted(data.keys()): + self.enc_buf += 'mpack_write_cstr(&writer, "{}");\n'.format(key) + self.dec_buf += 'mpack_expect_cstr(&reader, strbuf, sizeof(strbuf));\n' + self.dec_buf1 += 'mpack_expect_cstr(&reader, strbuf, sizeof(strbuf));\n' + self.add_value(data[key]) + self.enc_buf += 'mpack_finish_map(&writer);\n' + self.dec_buf += 'mpack_done_map(&reader);\n' + self.dec_buf1 += 'mpack_done_map(&reader);\n' + if type(data) == list: + self.enc_buf += 'mpack_start_array(&writer, {:d});\n'.format(len(data)) + self.dec_buf += 'mpack_expect_array_max(&reader, {:d});\n'.format(len(data)) + self.dec_buf1 += 'mpack_expect_array_max(&reader, {:d});\n'.format(len(data)) + for elem in data: + self.add_value(elem); + self.enc_buf += 'mpack_finish_array(&writer);\n' + self.dec_buf += 'mpack_done_array(&reader);\n' + self.dec_buf1 += 'mpack_done_array(&reader);\n' + +class NanoPB(DummyProtocol): + + def __init__(self, data, max_serialized_bytes = 256, cardinality = 'required', use_maps = False, max_string_length = None, cc_prefix = '', name = 'Benchmark', + int_type = 'uint16_t', float_type = 'float', dec_index = 0): + super().__init__() + self.data = data + self.max_serialized_bytes = max_serialized_bytes + self.cardinality = cardinality + self.use_maps = use_maps + self.max_strlen = max_string_length + self.cc_prefix = cc_prefix + self.name = name + self.int_type = int_type + self.proto_int_type = self.int_type_to_proto_type(int_type) + self.float_type = float_type + self.proto_float_type = self.float_type_to_proto_type(float_type) + self.dec_index = dec_index + self.fieldnum = 1 + self.proto_head = 'syntax = "proto2";\nimport "src/app/prototest/nanopb.proto";\n\n' + self.proto_fields = '' + self.proto_options = '' + self.sub_protos = [] + self.cc_encoders = '' + self.from_json(data) + + def is_ascii(self): + return False + + def int_type_to_proto_type(self, int_type): + sign = 'u' + if int_type[0] != 'u': + sign = '' + if '64' in int_type: + self.int_bits = 64 + return sign + 'int64' + # Protocol Buffers only have 32 and 64 bit integers, so we default to 32 + self.int_bits = 32 + return sign + 'int32' + + def float_type_to_proto_type(self, float_type): + if float_type == 'float': + self.float_bits = 32 + else: + self.float_bits = 64 + return float_type + + def get_buffer_declaration(self): + ret = 'uint8_t buf[{:d}];\n'.format(self.max_serialized_bytes) + ret += 'uint16_t serialized_size;\n' + return ret + self.get_cc_functions() + + def get_buffer_name(self): + return 'buf' + + def get_serialize(self): + ret = 'pb_ostream_t stream = pb_ostream_from_buffer(buf, sizeof(buf));\n' + ret += 'pb_encode(&stream, Benchmark_fields, &msg);\n' + ret += 'serialized_size = stream.bytes_written;\n' + return ret + + def get_deserialize(self): + ret = 'Benchmark msg = Benchmark_init_zero;\n' + ret += 'pb_istream_t stream = pb_istream_from_buffer(buf, serialized_size);\n' + ret += 'if (pb_decode(&stream, Benchmark_fields, &msg) == false) {\n' + ret += 'kout << "deserialized failed" << endl;\n' + ret += '}\n' + return ret + + def get_decode_and_output(self): + return 'kout << dec << "dec:";\n' + self.dec_buf + 'kout << endl;\n' + + def get_decode_vars(self): + return self.dec_buf0 + + def get_decode(self): + return self.dec_buf1 + + def get_decode_output(self): + return 'kout << dec << "dec:";\n' + self.dec_buf2 + 'kout << endl;\n'; + + def get_length_var(self): + return 'serialized_size' + + def add_field(self, cardinality, fieldtype, key, value): + extra = '' + texttype = self.proto_int_type + dectype = self.int_type + if fieldtype == str: + texttype = 'string' + elif fieldtype == float: + texttype = self.proto_float_type + dectype = self.float_type + elif fieldtype == dict: + texttype = key.capitalize() + if type(value) == list: + extra = '[(nanopb).max_count = {:d}]'.format(len(value)) + self.enc_buf += 'msg.{}{}_count = {:d};\n'.format(self.cc_prefix, key, len(value)) + self.proto_fields += '{} {} {} = {:d} {};\n'.format( + cardinality, texttype, key, self.fieldnum, extra) + self.fieldnum += 1 + if fieldtype == str: + if cardinality == 'optional': + self.enc_buf += 'msg.{}has_{} = true;\n'.format(self.cc_prefix, key) + if self.max_strlen: + self.proto_options += '{}.{} max_size:{:d}\n'.format(self.name, key, self.max_strlen) + i = -1 + for i, character in enumerate(value): + self.enc_buf += '''msg.{}{}[{:d}] = '{}';\n'''.format(self.cc_prefix, key, i, character) + self.enc_buf += 'msg.{}{}[{:d}] = 0;\n'.format(self.cc_prefix, key, i+1) + self.dec_buf += 'kout << msg.{}{};\n'.format(self.cc_prefix, key) + self.assign_and_kout('char *', 'msg.{}{}'.format(self.cc_prefix, key)) + else: + self.cc_encoders += 'bool encode_{}(pb_ostream_t *stream, const pb_field_t *field, void * const *arg)\n'.format(key) + self.cc_encoders += '{\n' + self.cc_encoders += 'if (!pb_encode_tag_for_field(stream, field)) return false;\n' + self.cc_encoders += 'return pb_encode_string(stream, (uint8_t*)"{}", {:d});\n'.format(value, len(value)) + self.cc_encoders += '}\n' + self.enc_buf += 'msg.{}{}.funcs.encode = encode_{};\n'.format(self.cc_prefix, key, key) + self.dec_buf += '// TODO decode string {}{} via callback\n'.format(self.cc_prefix, key) + self.dec_buf1 += '// TODO decode string {}{} via callback\n'.format(self.cc_prefix, key) + elif fieldtype == dict: + if cardinality == 'optional': + self.enc_buf += 'msg.{}has_{} = true;\n'.format(self.cc_prefix, key) + # The rest is handled recursively in add_to_dict + elif type(value) == list: + for i, elem in enumerate(value): + self.enc_buf += 'msg.{}{}[{:d}] = {};\n'.format(self.cc_prefix, key, i, elem) + self.dec_buf += 'kout << msg.{}{}[{:d}];\n'.format(self.cc_prefix, key, i) + if fieldtype == float: + self.assign_and_kout(self.float_type, 'msg.{}{}[{:d}]'.format(self.cc_prefix, key, i)) + elif fieldtype == int: + self.assign_and_kout(self.int_type, 'msg.{}{}[{:d}]'.format(self.cc_prefix, key, i)) + elif fieldtype == int: + if cardinality == 'optional': + self.enc_buf += 'msg.{}has_{} = true;\n'.format(self.cc_prefix, key) + self.enc_buf += 'msg.{}{} = {};\n'.format(self.cc_prefix, key, value) + self.dec_buf += 'kout << msg.{}{};\n'.format(self.cc_prefix, key) + self.assign_and_kout(self.int_type, 'msg.{}{}'.format(self.cc_prefix, key)) + elif fieldtype == dict: + if cardinality == 'optional': + self.enc_buf += 'msg.{}has_{} = true;\n'.format(self.cc_prefix, key) + self.enc_buf += 'msg.{}{} = {};\n'.format(self.cc_prefix, key, value) + self.dec_buf += 'kout << msg.{}{};\n'.format(self.cc_prefix, key) + self.assign_and_kout(self.float_type, 'msg.{}{}'.format(self.cc_prefix, key)) + else: + self.note_unsupported(value) + + def get_proto(self): + return self.proto_head + '\n\n'.join(self.get_message_definitions('Benchmark')) + + def get_proto_options(self): + return self.proto_options + + def get_extra_files(self): + return { + 'nanopbbench.proto' : self.get_proto(), + 'nanopbbench.options' : self.get_proto_options() + } + + def get_message_definitions(self, msgname): + ret = list(self.sub_protos) + ret.append('message {} {{\n'.format(msgname) + self.proto_fields + '}\n') + return ret + + def get_encode(self): + ret = 'Benchmark msg = Benchmark_init_zero;\n' + return ret + self.enc_buf + + def get_cc_functions(self): + return self.cc_encoders + + def add_to_dict(self, key, value): + if type(value) == str: + if len(value) and value[0] == '$': + self.add_field(self.cardinality, int, key, value[1:]) + else: + self.add_field(self.cardinality, str, key, value) + elif type(value) == list: + self.add_field('repeated', type(value[0]), key, value) + elif type(value) == dict: + nested_proto = NanoPB( + value, max_string_length = self.max_strlen, cardinality = self.cardinality, use_maps = self.use_maps, cc_prefix = + '{}{}.'.format(self.cc_prefix, key), name = key.capitalize(), + int_type = self.int_type, float_type = self.float_type, + dec_index = self.dec_index) + self.sub_protos.extend(nested_proto.get_message_definitions(key.capitalize())) + self.proto_options += nested_proto.proto_options + self.cc_encoders += nested_proto.cc_encoders + self.add_field(self.cardinality, dict, key, value) + self.enc_buf += nested_proto.enc_buf + self.dec_buf += nested_proto.dec_buf + self.dec_buf0 += nested_proto.dec_buf0 + self.dec_buf1 += nested_proto.dec_buf1 + self.dec_buf2 += nested_proto.dec_buf2 + self.dec_index = nested_proto.dec_index + else: + self.add_field(self.cardinality, type(value), key, value) + + def from_json(self, data): + if type(data) == dict: + for key in sorted(data.keys()): + self.add_to_dict(key, data[key]) + + + +class UBJ(DummyProtocol): + + def __init__(self, data, max_serialized_bytes = 255, int_type = 'uint16_t', float_type = 'float'): + super().__init__() + self.data = data + self.max_serialized_bytes = self.get_serialized_length() + 2 + self.int_type = int_type + self.float_type = self.parse_float_type(float_type) + self.enc_buf += 'ubjw_context_t* ctx = ubjw_open_memory(buf, buf + sizeof(buf));\n' + self.enc_buf += 'ubjw_begin_object(ctx, UBJ_MIXED, 0);\n' + self.from_json('root', data) + self.enc_buf += 'ubjw_end(ctx);\n' + + def get_serialized_length(self): + return len(ubjson.dumpb(self.data)) + + def is_ascii(self): + return False + + def parse_float_type(self, float_type): + if float_type == 'float': + self.float_bits = 32 + else: + self.float_bits = 64 + return float_type + + def get_buffer_declaration(self): + ret = 'uint8_t buf[{:d}];\n'.format(self.max_serialized_bytes) + ret += 'uint16_t serialized_size;\n' + return ret + + def get_buffer_name(self): + return 'buf' + + def get_length_var(self): + return 'serialized_size' + + def get_serialize(self): + return 'serialized_size = ubjw_close_context(ctx);\n' + + def get_encode(self): + return self.enc_buf + + def get_deserialize(self): + ret = 'ubjr_context_t* ctx = ubjr_open_memory(buf, buf + serialized_size);\n' + ret += 'ubjr_dynamic_t dynamic_root = ubjr_read_dynamic(ctx);\n' + ret += 'ubjr_dynamic_t* root_values = (ubjr_dynamic_t*)dynamic_root.container_object.values;\n' + return ret + + def get_decode_and_output(self): + ret = 'kout << dec << "dec:";\n' + ret += self.dec_buf + ret += 'kout << endl;\n' + ret += 'ubjr_cleanup_dynamic(&dynamic_root);\n' # This causes the data (including all strings) to be free'd + ret += 'ubjr_close_context(ctx);\n' + return ret + + def get_decode_vars(self): + return self.dec_buf0 + + def get_decode(self): + return self.dec_buf1 + + def get_decode_output(self): + ret = 'kout << dec << "dec:";\n' + self.dec_buf2 + 'kout << endl;\n' + ret += 'ubjr_cleanup_dynamic(&dynamic_root);\n' + ret += 'ubjr_close_context(ctx);\n' + return ret + + def add_to_list(self, root, index, value): + if type(value) == str: + if len(value) and value[0] == '$': + self.enc_buf += 'ubjw_write_integer(ctx, {});\n'.format(value[1:]) + self.dec_buf += 'kout << {}_values[{:d}].integer;\n'.format(root, index) + self.assign_and_kout(self.int_type, '{}_values[{:d}].integer'.format(root, index)) + else: + self.enc_buf += 'ubjw_write_string(ctx, "{}");\n'.format(value) + self.dec_buf += 'kout << {}_values[{:d}].string;\n'.format(root, index) + self.assign_and_kout('char *', '{}_values[{:d}].string'.format(root, index)) + + elif type(value) == list: + self.enc_buf += 'ubjw_begin_array(ctx, UBJ_MIXED, 0);\n'.format(value) + self.dec_buf += '// decoding nested lists is not supported\n' + self.dec_buf1 += '// decoding nested lists is not supported\n' + self.from_json(root, value) + self.enc_buf += 'ubjw_end(ctx);\n' + + elif type(value) == dict: + self.enc_buf += 'ubjw_begin_object(ctx, UBJ_MIXED, 0);\n'.format(value) + self.dec_buf += '// decoding objects in lists is not supported\n' + self.dec_buf1 += '// decoding objects in lists is not supported\n' + self.from_json(root, value) + self.enc_buf += 'ubjw_end(ctx);\n' + + elif type(value) == float: + self.enc_buf += 'ubjw_write_float{:d}(ctx, {});\n'.format(self.float_bits, value) + self.dec_buf += 'kout << {}_values[{:d}].real;\n'.format(root, index) + self.assign_and_kout(self.float_type, '{}_values[{:d}].real'.format(root, index)) + + elif type(value) == int: + self.enc_buf += 'ubjw_write_integer(ctx, {});\n'.format(value) + self.dec_buf += 'kout << {}_values[{:d}].integer;\n'.format(root, index) + self.assign_and_kout(self.int_type, '{}_values[{:d}].integer'.format(root, index)) + + else: + raise TypeError('Cannot handle {} of type {}'.format(value, type(value))) + + def add_to_dict(self, root, index, key, value): + if type(value) == str: + if len(value) and value[0] == '$': + self.enc_buf += 'ubjw_write_key(ctx, "{}"); ubjw_write_integer(ctx, {});\n'.format(key, value[1:]) + self.dec_buf += 'kout << {}_values[{:d}].integer;\n'.format(root, index) + self.assign_and_kout(self.int_type, '{}_values[{:d}].integer'.format(root, index)) + else: + self.enc_buf += 'ubjw_write_key(ctx, "{}"); ubjw_write_string(ctx, "{}");\n'.format(key, value) + self.dec_buf += 'kout << {}_values[{:d}].string;\n'.format(root, index) + self.assign_and_kout('char *', '{}_values[{:d}].string'.format(root, index)) + + elif type(value) == list: + self.enc_buf += 'ubjw_write_key(ctx, "{}"); ubjw_begin_array(ctx, UBJ_MIXED, 0);\n'.format(key) + self.dec_buf += 'ubjr_dynamic_t *{}_values = (ubjr_dynamic_t*){}_values[{:d}].container_array.values;\n'.format( + key, root, index) + self.dec_buf1 += 'ubjr_dynamic_t *{}_values = (ubjr_dynamic_t*){}_values[{:d}].container_array.values;\n'.format( + key, root, index) + self.from_json(key, value) + self.enc_buf += 'ubjw_end(ctx);\n' + + elif type(value) == dict: + self.enc_buf += 'ubjw_write_key(ctx, "{}"); ubjw_begin_object(ctx, UBJ_MIXED, 0);\n'.format(key) + self.dec_buf += 'ubjr_dynamic_t *{}_values = (ubjr_dynamic_t*){}_values[{:d}].container_object.values;\n'.format( + key, root, index) + self.dec_buf1 += 'ubjr_dynamic_t *{}_values = (ubjr_dynamic_t*){}_values[{:d}].container_object.values;\n'.format( + key, root, index) + self.from_json(key, value) + self.enc_buf += 'ubjw_end(ctx);\n' + + elif type(value) == float: + self.enc_buf += 'ubjw_write_key(ctx, "{}"); ubjw_write_float{:d}(ctx, {});\n'.format(key, self.float_bits, value) + self.dec_buf += 'kout << {}_values[{:d}].real;\n'.format(root, index) + self.assign_and_kout(self.float_type, '{}_values[{:d}].real'.format(root, index)) + + elif type(value) == int: + self.enc_buf += 'ubjw_write_key(ctx, "{}"); ubjw_write_integer(ctx, {});\n'.format(key, value) + self.dec_buf += 'kout << {}_values[{:d}].integer;\n'.format(root, index) + self.assign_and_kout(self.int_type, '{}_values[{:d}].integer'.format(root, index)) + + else: + raise TypeError('Cannot handle {} of type {}'.format(value, type(value))) + + def from_json(self, root, data): + if type(data) == dict: + keys = sorted(data.keys()) + for i, key in enumerate(keys): + self.add_to_dict(root, i, key, data[key]) + elif type(data) == list: + for i, elem in enumerate(data): + self.add_to_list(root, i, elem) + + +class XDR(DummyProtocol): + + def __init__(self, data, max_serialized_bytes = 256, int_type = 'uint16_t', float_type = 'float'): + super().__init__() + self.data = data + self.max_serialized_bytes = 256 + self.enc_int_type = int_type + self.dec_int_type = self.parse_int_type(int_type) + self.float_type = self.parse_float_type(float_type) + self.enc_buf += 'BufferOutput<XDRStream> xdrstream(buf);\n' + self.dec_buf += 'XDRInput xdrinput(buf);\n' + self.dec_buf0 += 'XDRInput xdrinput(buf);\n' + # By default, XDR does not even include a version / protocol specifier. + # This seems rather impractical -> emulate that here. + self.enc_buf += 'xdrstream << (uint32_t)22075;\n' + self.dec_buf += 'char strbuf[16];\n' + self.dec_buf += 'xdrinput.get_uint32();\n' + self.dec_buf0 += 'char strbuf[16];\n' + self.dec_buf0 += 'xdrinput.get_uint32();\n' + self.from_json(data) + + def is_ascii(self): + return False + + def parse_int_type(self, int_type): + sign = '' + if int_type[0] == 'u': + sign = 'u' + if '64' in int_type: + self.int_bits = 64 + return sign + 'int64' + else: + self.int_bits = 32 + return sign + 'int32' + + def parse_float_type(self, float_type): + if float_type == 'float': + self.float_bits = 32 + else: + self.float_bits = 64 + return float_type + + def get_buffer_declaration(self): + ret = 'uint16_t serialized_size;\n' + ret += 'char buf[{:d}];\n'.format(self.max_serialized_bytes) + return ret + + def get_buffer_name(self): + return 'buf' + + def get_length_var(self): + return 'xdrstream.size()' + + def get_encode(self): + return self.enc_buf + + def get_decode_and_output(self): + return 'kout << dec << "dec:";\n' + self.dec_buf + 'kout << endl;\n' + + def get_decode_vars(self): + return self.dec_buf0 + + def get_decode(self): + return self.dec_buf1 + + def get_decode_output(self): + return 'kout << dec << "dec:";\n' + self.dec_buf2 + 'kout << endl;\n'; + + def from_json(self, data): + if type(data) == dict: + for key in sorted(data.keys()): + self.from_json(data[key]) + elif type(data) == list: + #self.enc_buf += 'xdrstream.setNextArrayLen({});\n'.format(len(data)) + #self.enc_buf += 'xdrstream << variable;\n' + self.enc_buf += 'xdrstream << (uint32_t){:d};\n'.format(len(data)) + self.dec_buf += 'xdrinput.get_uint32();\n' + self.dec_buf1 += 'xdrinput.get_uint32();\n' + for elem in data: + self.from_json(elem) + elif type(data) == str: + if len(data) and data[0] == '$': + self.enc_buf += 'xdrstream << ({}){};\n'.format(self.enc_int_type, data[1:]) + self.dec_buf += 'kout << xdrinput.get_{}();\n'.format(self.dec_int_type) + self.dec_buf0 += '{} dec_{};\n'.format(self.enc_int_type, self.dec_index) + self.dec_buf1 += 'dec_{} = xdrinput.get_{}();;\n'.format(self.dec_index, self.dec_int_type) + self.dec_buf2 += 'kout << dec_{};\n'.format(self.dec_index) + else: + # Kodierte Strings haben nicht immer ein Nullbyte am Ende + self.enc_buf += 'xdrstream.setNextArrayLen({});\n'.format(len(data)) + self.enc_buf += 'xdrstream << variable << "{}";\n'.format(data) + self.dec_buf += 'xdrinput.get_string(strbuf);\n' + self.dec_buf += 'kout << strbuf;\n' + self.dec_buf1 += 'xdrinput.get_string(strbuf);\n'.format(self.dec_index) + self.dec_buf2 += 'kout << strbuf;\n'.format(self.dec_index) + elif type(data) == float: + self.enc_buf += 'xdrstream << ({}){};\n'.format(self.float_type, data) + self.dec_buf += 'kout << xdrinput.get_{}();\n'.format(self.float_type) + self.dec_buf0 += '{} dec_{};\n'.format(self.float_type, self.dec_index) + self.dec_buf1 += 'dec_{} = xdrinput.get_{}();\n'.format(self.dec_index, self.float_type) + self.dec_buf2 += 'kout << dec_{};\n'.format(self.dec_index) + elif type(data) == int: + self.enc_buf += 'xdrstream << ({}){};\n'.format(self.enc_int_type, data) + self.dec_buf += 'kout << xdrinput.get_{}();\n'.format(self.dec_int_type) + self.dec_buf0 += '{} dec_{};\n'.format(self.enc_int_type, self.dec_index) + self.dec_buf1 += 'dec_{} = xdrinput.get_{}();\n'.format(self.dec_index, self.dec_int_type) + self.dec_buf2 += 'kout << dec_{};\n'.format(self.dec_index) + else: + self.enc_buf += 'xdrstream << {};\n'.format(data) + self.dec_buf += '// unsupported type {} of {}\n'.format(type(data), data) + self.dec_buf1 += '// unsupported type {} of {}\n'.format(type(data), data) + self.dec_index += 1; + +class Benchmark: + + def __init__(self, logfile): + self.logfile = logfile + + def log(self, arch, library, library_options, bench_name, bench_index, data, key, value = None, error = None): + if not library_options: + library_options = [] + libkey = '{}:{}:{}'.format(arch, library, ','.join(library_options)) + # JSON does not differentiate between int and str keys -> always use + # str(bench_index) + bench_index = str(bench_index) + with FileLock(self.logfile + '.lock'): + if os.path.exists(self.logfile): + with open(self.logfile, 'rb') as f: + benchmark_data = ubjson.load(f) + else: + benchmark_data = {} + if not libkey in benchmark_data: + benchmark_data[libkey] = dict() + if not bench_name in benchmark_data[libkey]: + benchmark_data[libkey][bench_name] = dict() + if not bench_index in benchmark_data[libkey][bench_name]: + benchmark_data[libkey][bench_name][bench_index] = dict() + this_result = benchmark_data[libkey][bench_name][bench_index] + # data is unset for log(...) calls from postprocessing + if data != None: + this_result['data'] = data + if value != None: + this_result[key] = { + 'v' : value + } + print('{} {} {} ({}) :: {} -> {}'.format( + libkey, bench_name, bench_index, data, key, value)) + else: + this_result[key] = { + 'e' : error + } + print('{} {} {} ({}) :: {} -> [E] {}'.format( + libkey, bench_name, bench_index, data, key, error)) + with open(self.logfile, 'wb') as f: + ubjson.dump(benchmark_data, f) + + def get_snapshot(self): + with FileLock(self.logfile + '.lock'): + if os.path.exists(self.logfile): + with open(self.logfile, 'rb') as f: + benchmark_data = ubjson.load(f) + else: + benchmark_data = {} + return benchmark_data |