From: Otto Moerbeek Date: Mon, 28 Apr 2025 11:45:41 +0000 (+0200) Subject: OpenTelemetry trace data protobuf encode/decode using protozero X-Git-Tag: rec-5.3.0-alpha1^2~20 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=8e0df8682880b7d015d4fc4b889e48ef8797541e;p=thirdparty%2Fpdns.git OpenTelemetry trace data protobuf encode/decode using protozero Signed-off-by: Otto Moerbeek --- diff --git a/pdns/recursordist/decode_ot.py b/pdns/recursordist/decode_ot.py new file mode 100755 index 0000000000..4970a10c48 --- /dev/null +++ b/pdns/recursordist/decode_ot.py @@ -0,0 +1,22 @@ +#!/usr/bin/env python3 + +# run protoc -I ~/opentelemetry-proto ~/opentelemetry-proto/opentelemetry/proto/trace/v1/trace.proto --python_out=. +# run protoc -I ~/opentelemetry-proto ~/opentelemetry-proto/opentelemetry/proto/common/v1/common.proto --python_out=. +# run protoc -I ~/opentelemetry-proto ~/opentelemetry-proto/opentelemetry/proto/resource/v1/resource.proto --python_out=. +# to generate opentelemetry directory + +import sys + +import google.protobuf.message +import google.protobuf.json_format + +import opentelemetry.proto.trace.v1.trace_pb2 + +data = sys.stdin.buffer.read() + +msg = opentelemetry.proto.trace.v1.trace_pb2.TracesData() +msg.ParseFromString(data) + +json_string = google.protobuf.json_format.MessageToJson(msg) +print(json_string) + diff --git a/pdns/recursordist/encode_ot.py b/pdns/recursordist/encode_ot.py new file mode 100755 index 0000000000..fbdfc459f6 --- /dev/null +++ b/pdns/recursordist/encode_ot.py @@ -0,0 +1,20 @@ +#!/usr/bin/env python3 + +# run protoc -I ~/opentelemetry-proto ~/opentelemetry-proto/opentelemetry/proto/trace/v1/trace.proto --python_out=. +# run protoc -I ~/opentelemetry-proto ~/opentelemetry-proto/opentelemetry/proto/common/v1/common.proto --python_out=. +# run protoc -I ~/opentelemetry-proto ~/opentelemetry-proto/opentelemetry/proto/resource/v1/resource.proto --python_out=. +# to generate opentelemetry directory + +import sys + +import google.protobuf.message +import google.protobuf.json_format + +import opentelemetry.proto.trace.v1.trace_pb2 + +json = sys.stdin.buffer.read() + +msg = opentelemetry.proto.trace.v1.trace_pb2.TracesData() +google.protobuf.json_format.Parse(json, msg); + +sys.stdout.buffer.write(msg.SerializeToString()) diff --git a/pdns/recursordist/meson.build b/pdns/recursordist/meson.build index efa8e5c603..b81584dd61 100644 --- a/pdns/recursordist/meson.build +++ b/pdns/recursordist/meson.build @@ -135,6 +135,7 @@ common_sources += files( src_dir / 'negcache.cc', src_dir / 'nsecrecords.cc', src_dir / 'protozero.cc', + src_dir / 'protozero-trace.cc', src_dir / 'proxy-protocol.cc', src_dir / 'pubsuffixloader.cc', src_dir / 'qtype.cc', @@ -491,6 +492,7 @@ test_sources += files( src_dir / 'test-mtasker.cc', src_dir / 'test-negcache_cc.cc', src_dir / 'test-packetcache_hh.cc', + src_dir / 'test-protozero-trace.cc', src_dir / 'test-rcpgenerator_cc.cc', src_dir / 'test-rec-system-resolve.cc', src_dir / 'test-rec-taskqueue.cc', @@ -534,6 +536,7 @@ if get_option('unit-tests') dep_boost_test, dep_lua, dep_nod, + dep_protozero, dep_recrust, dep_rust_recrust, librec_signers_openssl, diff --git a/pdns/recursordist/protozero-trace.cc b/pdns/recursordist/protozero-trace.cc new file mode 100644 index 0000000000..054885e136 --- /dev/null +++ b/pdns/recursordist/protozero-trace.cc @@ -0,0 +1,505 @@ +/* + * This file is part of PowerDNS or dnsdist. + * Copyright -- PowerDNS.COM B.V. and its contributors + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * In addition, for the avoidance of any doubt, permission is granted to + * link this program with OpenSSL and to (re)distribute the binaries + * produced as the result of such linking. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "protozero-trace.hh" + +namespace pdns::trace +{ + +void AnyValue::encode(protozero::pbf_writer& writer) const +{ + if (std::holds_alternative(*this)) { + pdns::trace::encode(writer, 1, std::get(*this), true); + } + else if (std::holds_alternative(*this)) { + pdns::trace::encode(writer, 2, std::get(*this), true); + } + else if (std::holds_alternative(*this)) { + pdns::trace::encode(writer, 3, std::get(*this), true); + } + else if (std::holds_alternative(*this)) { + pdns::trace::encode(writer, 4, std::get(*this), true); + } + else if (std::holds_alternative(*this)) { + protozero::pbf_writer sub{writer, 5}; + std::get(*this).encode(sub); + } + else if (std::holds_alternative(*this)) { + protozero::pbf_writer sub{writer, 6}; + std::get(*this).encode(sub); + } + else if (std::holds_alternative>(*this)) { + pdns::trace::encode(writer, 7, std::get>(*this), true); + } +} + +AnyValue AnyValue::decode(protozero::pbf_reader& reader) +{ + while (reader.next()) { + switch (reader.tag()) { + case 1: + return AnyValue{reader.get_string()}; + break; + case 2: + return AnyValue{reader.get_bool()}; + break; + case 3: + return AnyValue{reader.get_int64()}; + break; + case 4: + return AnyValue{reader.get_double()}; + break; + case 5: { + protozero::pbf_reader arrayvalue = reader.get_message(); + return AnyValue{ArrayValue::decode(arrayvalue)}; + break; + } + case 6: { + protozero::pbf_reader kvlist = reader.get_message(); + return AnyValue{KeyValueList::decode(kvlist)}; + break; + } + case 7: { + auto value = reader.get_view(); + std::vector data{}; + data.reserve(value.size()); + for (size_t i = 0; i < value.size(); ++i) { + data.push_back(static_cast(value.data()[i])); + } + return AnyValue{std::move(data)}; + break; + } + default: + break; + } + } + + return {}; +} + +void EntityRef::encode(protozero::pbf_writer& writer) const +{ + pdns::trace::encode(writer, 1, schema_url); + pdns::trace::encode(writer, 2, type); + for (auto const& element : id_keys) { + pdns::trace::encode(writer, 3, element); + } + for (auto const& element : description_keys) { + pdns::trace::encode(writer, 4, element); + } +} + +EntityRef EntityRef::decode(protozero::pbf_reader& reader) +{ + EntityRef ret; + while (reader.next()) { + switch (reader.tag()) { + case 1: + ret.schema_url = reader.get_string(); + break; + case 2: + ret.type = reader.get_string(); + break; + case 3: + ret.id_keys.emplace_back(reader.get_string()); + break; + case 4: + ret.description_keys.emplace_back(reader.get_string()); + break; + default: + break; + } + } + return ret; +} + +void KeyValue::encode(protozero::pbf_writer& writer) const +{ + pdns::trace::encode(writer, 1, key); + { + protozero::pbf_writer val_sub{writer, 2}; + value.encode(val_sub); + } +} + +void Resource::encode(protozero::pbf_writer& writer) const +{ + pdns::trace::encode(writer, 1, attributes); + pdns::trace::encode(writer, 2, dropped_attributes_count); + pdns::trace::encode(writer, 3, entity_refs); +} + +Resource Resource::decode(protozero::pbf_reader& reader) +{ + Resource ret; + while (reader.next()) { + switch (reader.tag()) { + case 1: { + auto sub = reader.get_message(); + ret.attributes.emplace_back(KeyValue::decode(sub)); + break; + } + case 2: + ret.dropped_attributes_count = reader.get_uint32(); + break; + case 3: { + auto sub = reader.get_message(); + ret.entity_refs.emplace_back(EntityRef::decode(sub)); + break; + } + default: + break; + } + } + return ret; +} + +void InstrumentationScope::encode(protozero::pbf_writer& writer) const +{ + pdns::trace::encode(writer, 1, name); + pdns::trace::encode(writer, 2, version); + pdns::trace::encode(writer, 3, attributes); + pdns::trace::encode(writer, 4, dropped_attributes_count); +} + +InstrumentationScope InstrumentationScope::decode(protozero::pbf_reader& reader) +{ + InstrumentationScope ret; + while (reader.next()) { + switch (reader.tag()) { + case 1: + ret.name = reader.get_string(); + break; + case 2: + ret.version = reader.get_string(); + break; + case 3: { + auto sub = reader.get_message(); + ret.attributes.emplace_back(KeyValue::decode(sub)); + break; + } + case 4: + ret.dropped_attributes_count = reader.get_uint32(); + break; + default: + break; + } + } + return ret; +} + +void Status::encode(protozero::pbf_writer& writer) const +{ + pdns::trace::encode(writer, 2, message); + pdns::trace::encode(writer, 3, uint32_t(code)); +} + +Status Status::decode(protozero::pbf_reader& reader) +{ + Status ret; + while (reader.next()) { + switch (reader.tag()) { + case 2: + ret.message = reader.get_string(); + break; + case 3: + ret.code = static_cast(reader.get_uint32()); + break; + default: + break; + } + } + return ret; +} + +void Span::Event::encode(protozero::pbf_writer& writer) const +{ + pdns::trace::encodeFixed(writer, 1, time_unix_nano); + pdns::trace::encode(writer, 2, name); + pdns::trace::encode(writer, 3, attributes); + pdns::trace::encode(writer, 4, dropped_attribute_count); +} + +Span::Event Span::Event::decode(protozero::pbf_reader& reader) +{ + Span::Event ret; + while (reader.next()) { + switch (reader.tag()) { + case 1: + ret.time_unix_nano = reader.get_fixed64(); + break; + case 2: + ret.name = reader.get_string(); + break; + case 3: { + auto sub = reader.get_message(); + ret.attributes.emplace_back(KeyValue::decode(sub)); + break; + } + case 4: + ret.dropped_attribute_count = reader.get_uint32(); + default: + break; + } + } + return ret; +} + +void Span::Link::encode(protozero::pbf_writer& writer) const +{ + pdns::trace::encode(writer, 1, trace_id); + pdns::trace::encode(writer, 2, span_id); + pdns::trace::encode(writer, 3, trace_state); + pdns::trace::encode(writer, 4, attributes); + pdns::trace::encode(writer, 5, dropped_attribute_count); + pdns::trace::encodeFixed(writer, 6, flags); +} + +Span::Link Span::Link::decode(protozero::pbf_reader& reader) +{ + Link ret; + while (reader.next()) { + switch (reader.tag()) { + case 1: + ret.trace_id = decodeTraceID(reader); + break; + case 2: + ret.span_id = decodeSpanID(reader); + break; + case 3: + ret.trace_state = reader.get_string(); + break; + case 4: { + auto sub = reader.get_message(); + ret.attributes.emplace_back(KeyValue::decode(sub)); + break; + } + case 5: + ret.dropped_attribute_count = reader.get_uint32(); + break; + case 6: + ret.flags = reader.get_uint32(); + default: + break; + } + } + return ret; +} + +void Span::encode(protozero::pbf_writer& writer) const +{ + pdns::trace::encode(writer, 1, trace_id); + pdns::trace::encode(writer, 2, span_id); + pdns::trace::encode(writer, 3, trace_state); + pdns::trace::encode(writer, 4, parent_span_id); + pdns::trace::encode(writer, 5, name); + pdns::trace::encode(writer, 6, uint32_t(kind)); + pdns::trace::encodeFixed(writer, 7, start_time_unix_nano); + pdns::trace::encodeFixed(writer, 8, end_time_unix_nano); + pdns::trace::encode(writer, 9, attributes); + pdns::trace::encode(writer, 10, dropped_attribute_count); + pdns::trace::encode(writer, 11, events); + pdns::trace::encode(writer, 12, dropped_events_count); + pdns::trace::encode(writer, 13, links); + pdns::trace::encode(writer, 14, dropped_links_count); + if (status.code != Status::StatusCode::STATUS_CODE_UNSET || !status.message.empty()) { + protozero::pbf_writer sub{writer, 15}; + status.encode(sub); + } +} + +Span Span::decode(protozero::pbf_reader& reader) +{ + Span ret; + while (reader.next()) { + switch (reader.tag()) { + case 1: + ret.trace_id = decodeTraceID(reader); + break; + case 2: + ret.span_id = decodeSpanID(reader); + break; + case 3: + ret.trace_state = reader.get_string(); + break; + case 4: + ret.parent_span_id = decodeSpanID(reader); + break; + case 5: + ret.name = reader.get_string(); + break; + case 6: + ret.kind = static_cast(reader.get_uint32()); + break; + case 7: + ret.start_time_unix_nano = reader.get_fixed64(); + break; + case 8: + ret.end_time_unix_nano = reader.get_fixed64(); + break; + case 9: { + auto sub = reader.get_message(); + ret.attributes.emplace_back(KeyValue::decode(sub)); + break; + } + case 10: + ret.dropped_attribute_count = reader.get_uint32(); + break; + case 11: { + auto sub = reader.get_message(); + ret.events.emplace_back(Span::Event::decode(sub)); + break; + } + case 12: + ret.dropped_events_count = reader.get_uint32(); + break; + case 13: { + auto sub = reader.get_message(); + ret.links.emplace_back(Span::Link::decode(sub)); + break; + } + case 14: + ret.dropped_links_count = reader.get_uint32(); + break; + case 15: { + auto sub = reader.get_message(); + ret.status = Status::decode(sub); + break; + } + default: + break; + } + } + return ret; +} + +void ScopeSpans::encode(protozero::pbf_writer& writer) const +{ + { + protozero::pbf_writer sub{writer, 1}; + scope.encode(sub); + } + pdns::trace::encode(writer, 2, spans); + pdns::trace::encode(writer, 3, schema_url); +} + +ScopeSpans ScopeSpans::decode(protozero::pbf_reader& reader) +{ + ScopeSpans ret; + while (reader.next()) { + switch (reader.tag()) { + case 1: { + auto sub = reader.get_message(); + ret.scope = InstrumentationScope::decode(sub); + break; + } + case 2: { + auto sub = reader.get_message(); + ret.spans.emplace_back(Span::decode(sub)); + break; + } + case 3: + ret.schema_url = reader.get_string(); + default: + break; + } + } + return ret; +} + +void ResourceSpans::encode(protozero::pbf_writer& writer) const +{ + { + protozero::pbf_writer sub{writer, 1}; + resource.encode(sub); + } + pdns::trace::encode(writer, 2, scope_spans); + pdns::trace::encode(writer, 3, schema_url); +} + +ResourceSpans ResourceSpans::decode(protozero::pbf_reader& reader) +{ + ResourceSpans ret; + while (reader.next()) { + switch (reader.tag()) { + case 1: { + protozero::pbf_reader sub = reader.get_message(); + ret.resource = Resource::decode(sub); + break; + } + case 2: { + protozero::pbf_reader sub = reader.get_message(); + ret.scope_spans.emplace_back(ScopeSpans::decode(sub)); + break; + } + case 3: + ret.schema_url = reader.get_string(); + default: + break; + } + } + return ret; +} + +void TracesData::encode(protozero::pbf_writer& writer) const +{ + pdns::trace::encode(writer, 1, resource_spans); +} + +TracesData TracesData::decode(protozero::pbf_reader& reader) +{ + TracesData ret; + while (reader.next()) { + switch (reader.tag()) { + case 1: { + auto sub = reader.get_message(); + ret.resource_spans.emplace_back(ResourceSpans::decode(sub)); + break; + } + default: + break; + } + } + return ret; +} + +KeyValue KeyValue::decode(protozero::pbf_reader& reader) +{ + KeyValue value; + while (reader.next()) { + switch (reader.tag()) { + case 1: + value.key = reader.get_string(); + break; + case 2: { + protozero::pbf_reader sub = reader.get_message(); + value.value = AnyValue::decode(sub); + break; + } + default: + break; + } + } + return value; +} + +} diff --git a/pdns/recursordist/protozero-trace.hh b/pdns/recursordist/protozero-trace.hh new file mode 100644 index 0000000000..695635df4a --- /dev/null +++ b/pdns/recursordist/protozero-trace.hh @@ -0,0 +1,356 @@ +/* + * This file is part of PowerDNS or dnsdist. + * Copyright -- PowerDNS.COM B.V. and its contributors + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * In addition, for the avoidance of any doubt, permission is granted to + * link this program with OpenSSL and to (re)distribute the binaries + * produced as the result of such linking. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#pragma once + +#include +#include +#include + +#include +#include + +// See https://github.com/open-telemetry/opentelemetry-proto/tree/main/opentelemetry/proto + +namespace pdns::trace +{ + +struct AnyValue; +struct ArrayValue; +struct KeyValue; +struct KeyValueList; + +inline void encode(protozero::pbf_writer& writer, uint8_t field, bool value, bool always = false) +{ + if (always || value) { + writer.add_bool(field, value); + } +} + +inline void encode(protozero::pbf_writer& writer, uint8_t field, uint32_t value, bool always = false) +{ + if (always || value != 0) { + writer.add_uint32(field, value); + } +} + +inline void encodeFixed(protozero::pbf_writer& writer, uint8_t field, uint32_t value) +{ + writer.add_fixed32(field, value); +} + +inline void encode(protozero::pbf_writer& writer, uint8_t field, int64_t value, bool always = false) +{ + if (always || value != 0) { + writer.add_int64(field, value); + } +} + +inline void encode(protozero::pbf_writer& writer, uint8_t field, uint64_t value, bool always = false) +{ + if (always || value != 0) { + writer.add_uint64(field, value); + } +} + +inline void encodeFixed(protozero::pbf_writer& writer, uint8_t field, uint64_t value) +{ + writer.add_fixed64(field, value); +} + +inline void encode(protozero::pbf_writer& writer, uint8_t field, double value, bool always = false) +{ + if (always || value != 0.0) { + writer.add_double(field, value); + } +} + +inline void encode(protozero::pbf_writer& writer, uint8_t field, const std::string& value, bool always = false) +{ + if (always || !value.empty()) { + writer.add_string(field, value); + } +} + +inline void encode(protozero::pbf_writer& writer, uint8_t field, const std::vector& value, bool always = false) +{ + if (always || !value.empty()) { + writer.add_bytes(field, reinterpret_cast(value.data()), value.size()); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast) it's the API + } +} + +template +void encode(protozero::pbf_writer& writer, const std::vector& vec) +{ + for (auto const& element : vec) { + element.encode(writer); + } +} + +template +void encode(protozero::pbf_writer& writer, uint8_t field, const std::vector& vec) +{ + for (auto const& element : vec) { + protozero::pbf_writer sub{writer, field}; + element.encode(sub); + } +} + +template +T decode(protozero::pbf_reader& reader) +{ + std::vector vec; + while (reader.next()) { + if (reader.tag() == 1) { + protozero::pbf_reader sub = reader.get_message(); + vec.emplace_back(E::decode(sub)); + } + } + return {vec}; +} + +struct ArrayValue +{ + std::vector values; // = 1 + + void encode(protozero::pbf_writer& writer) const + { + pdns::trace::encode(writer, 1, values); + } + + static ArrayValue decode(protozero::pbf_reader& reader); + + bool operator==(const ArrayValue& rhs) const + { + return values == rhs.values; + } +}; + +struct KeyValueList +{ + std::vector values; // = 1 + + void encode(protozero::pbf_writer& writer) const + { + pdns::trace::encode(writer, 1, values); + } + + static KeyValueList decode(protozero::pbf_reader& reader); + + bool operator==(const KeyValueList& rhs) const + { + return values == rhs.values; + } +}; + +struct AnyValue : public std::variant> +{ + void encode(protozero::pbf_writer& writer) const; + static AnyValue decode(protozero::pbf_reader& reader); +}; + +struct EntityRef +{ + std::string schema_url; // == 1 + std::string type; // == 2 + std::vector id_keys; // == 3 + std::vector description_keys; // == 4 + + void encode(protozero::pbf_writer& writer) const; + static EntityRef decode(protozero::pbf_reader& reader); +}; + +struct KeyValue +{ + std::string key; // = 1 + AnyValue value; // = 2 + void encode(protozero::pbf_writer& writer) const; + static KeyValue decode(protozero::pbf_reader& reader); + + bool operator==(const KeyValue& rhs) const + { + return key == rhs.key && value == rhs.value; + } +}; + +struct Resource +{ + std::vector attributes; // = 1 + uint32_t dropped_attributes_count{0}; // = 2; + std::vector entity_refs; // = 3 + + void encode(protozero::pbf_writer& writer) const; + static Resource decode(protozero::pbf_reader& reader); +}; + +struct InstrumentationScope +{ + std::string name; // = 1 + std::string version; // = 2 + std::vector attributes; // = 3 + uint32_t dropped_attributes_count{0}; // = 4 + + void encode(protozero::pbf_writer& writer) const; + static InstrumentationScope decode(protozero::pbf_reader& reader); +}; + +using TraceID = std::array; +using SpanID = std::array; + +inline void encode(protozero::pbf_writer& writer, uint8_t field, const TraceID& value) +{ + writer.add_bytes(field, reinterpret_cast(value.data()), value.size()); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast) it's the API +} + +inline TraceID decodeTraceID(protozero::pbf_reader& reader) +{ + TraceID bytes; + auto [data, len] = reader.get_data(); + memcpy(bytes.data(), data, std::min(bytes.size(), static_cast(len))); + return bytes; +} + +inline void encode(protozero::pbf_writer& writer, uint8_t field, const SpanID& value) +{ + writer.add_bytes(field, reinterpret_cast(value.data()), value.size()); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast) it's the API +} + +inline SpanID decodeSpanID(protozero::pbf_reader& reader) +{ + SpanID bytes; + auto [data, len] = reader.get_data(); + memcpy(bytes.data(), data, std::min(bytes.size(), static_cast(len))); + return bytes; +} + +struct Status +{ + std::string message; // = 2; + + // For the semantics of status codes see + // https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/api.md#set-status + enum class StatusCode : uint8_t + { + STATUS_CODE_UNSET = 0, + STATUS_CODE_OK = 1, + STATUS_CODE_ERROR = 2, + }; + + // The status code. + StatusCode code{StatusCode::STATUS_CODE_UNSET}; // = 3; + + void encode(protozero::pbf_writer& writer) const; + static Status decode(protozero::pbf_reader& reader); +}; + +struct Span +{ + TraceID trace_id; // = 1 + SpanID span_id; // = 2 + std::string trace_state; // = 3 + SpanID parent_span_id; // = 4 + std::string name; // = 5 + enum class SpanKind : uint8_t + { + SPAN_KINUNSPECIFIED = 0, + SPAN_KININTERNAL = 1, + SPAN_KINSERVER = 2, + SPAN_KINCLIENT = 3, + SPAN_KINPRODUCER = 4, + SPAN_KINCONSUMER = 5, + }; + SpanKind kind{Span::SpanKind::SPAN_KINUNSPECIFIED}; // = 6 + uint64_t start_time_unix_nano{0}; // = 7 + uint64_t end_time_unix_nano{0}; // = 8 + std::vector attributes; // = 9 + uint32_t dropped_attribute_count{0}; // = 10 + struct Event + { + uint64_t time_unix_nano; // = 1 + std::string name; // = 2 + std::vector attributes; // = 3 + uint32_t dropped_attribute_count{0}; // = 4 + + void encode(protozero::pbf_writer& writer) const; + static Event decode(protozero::pbf_reader& reader); + }; + std::vector events; // = 11 + uint32_t dropped_events_count; // = 12 + struct Link + { + TraceID trace_id; // = 1 + SpanID span_id; // = 2 + std::string trace_state; // = 3 + std::vector attributes; // = 4 + uint32_t dropped_attribute_count{0}; // = 5 + uint32_t flags{0}; // = 6 + + void encode(protozero::pbf_writer& writer) const; + static Link decode(protozero::pbf_reader& reader); + }; + std::vector links; // = 13 + uint32_t dropped_links_count{0}; // = 14 + Status status; // = 15 + + void encode(protozero::pbf_writer& writer) const; + static Span decode(protozero::pbf_reader& reader); +}; + +struct ScopeSpans +{ + InstrumentationScope scope; // = 1 + std::vector spans; // = 2 + std::string schema_url; // = 3 + + void encode(protozero::pbf_writer& writer) const; + static ScopeSpans decode(protozero::pbf_reader& reader); +}; + +struct ResourceSpans +{ + Resource resource; // = 1 + std::vector scope_spans; // = 2 + std::string schema_url; // = 3 + + void encode(protozero::pbf_writer& writer) const; + static ResourceSpans decode(protozero::pbf_reader& reader); +}; + +struct TracesData +{ + std::vector resource_spans; // = 1 + + void encode(protozero::pbf_writer& writer) const; + static TracesData decode(protozero::pbf_reader& reader); +}; + +inline ArrayValue ArrayValue::decode(protozero::pbf_reader& reader) +{ + return pdns::trace::decode(reader); +} + +inline KeyValueList KeyValueList::decode(protozero::pbf_reader& reader) +{ + return pdns::trace::decode(reader); +} + +} diff --git a/pdns/recursordist/test-protozero-trace.cc b/pdns/recursordist/test-protozero-trace.cc new file mode 100644 index 0000000000..4b40374396 --- /dev/null +++ b/pdns/recursordist/test-protozero-trace.cc @@ -0,0 +1,160 @@ +#ifndef BOOST_TEST_DYN_LINK +#define BOOST_TEST_DYN_LINK +#endif + +#define BOOST_TEST_NO_MAIN + +#include "config.h" +#include +#include + +#include "protozero-trace.hh" +#include "misc.hh" + +BOOST_AUTO_TEST_SUITE(test_protobuf_trace) + +BOOST_AUTO_TEST_CASE(resource0) +{ + pdns::trace::Resource resource{}; + std::string data; + protozero::pbf_writer writer{data}; + resource.encode(writer); +#if 0 + std::ofstream x("x"); + x << data; +#endif + BOOST_CHECK_EQUAL(makeHexDump(data, " "), ""); +} + +BOOST_AUTO_TEST_CASE(resource1) +{ + pdns::trace::Resource resource{ + { + {"foo0", {"bar"}}, + {"foo1", {99.99}}, + }, + 99, + {{{"schema0", "type0", {"id00", "id01"}, {"desc00", "desc01"}}, + {"schema1", "type1", {"id10", "id11"}, {"desc10", "desc11"}}}}}; + std::string data; + protozero::pbf_writer writer{data}; + resource.encode(writer); +#if 0 + std::ofstream x("x"); + x << data; +#endif + BOOST_CHECK_EQUAL(makeHexDump(data, " "), "0a 0d 0a 04 66 6f 6f 30 12 05 0a 03 62 61 72 0a 11 0a 04 66 6f 6f 31 12 09 21 8f c2 f5 28 5c ff 58 40 10 63 1a 2c 0a 07 73 63 68 65 6d 61 30 12 05 74 79 70 65 30 1a 04 69 64 30 30 1a 04 69 64 30 31 22 06 64 65 73 63 30 30 22 06 64 65 73 63 30 31 1a 2c 0a 07 73 63 68 65 6d 61 31 12 05 74 79 70 65 31 1a 04 69 64 31 30 1a 04 69 64 31 31 22 06 64 65 73 63 31 30 22 06 64 65 73 63 31 31 "); +} + +template +static void testAny(const T& testcase) +{ + std::string data; + protozero::pbf_writer writer{data}; + pdns::trace::AnyValue wrapper{testcase}; + wrapper.encode(writer); +#if 0 + std::ofstream x("x"); + x << data; +#endif + + protozero::pbf_reader reader{data}; + pdns::trace::AnyValue value = pdns::trace::AnyValue::decode(reader); + if (!std::holds_alternative(value)) { + BOOST_CHECK(testcase == std::get(value)); + } + else { + if (std::holds_alternative(wrapper)) { + BOOST_CHECK(std::get(wrapper).values.empty()); + } + else if (std::holds_alternative(wrapper)) { + BOOST_CHECK(std::get(wrapper).values.empty()); + } + } +} + +BOOST_AUTO_TEST_CASE(any) +{ + testAny(std::string{"foo"}); + testAny(false); + testAny(true); + testAny(static_cast(0)); + testAny(static_cast(1)); + testAny(static_cast(-1)); + testAny(std::numeric_limits::min()); + testAny(std::numeric_limits::max()); + testAny(0.0); + testAny(1.0); + testAny(-1.0); + testAny(std::numeric_limits::min()); + testAny(std::numeric_limits::max()); + + pdns::trace::ArrayValue avalue; + testAny(avalue); + avalue.values.emplace_back(pdns::trace::AnyValue{"foo"}); + avalue.values.emplace_back(pdns::trace::AnyValue{1.99}); + testAny(avalue); + + pdns::trace::KeyValueList kvlist; + testAny(kvlist); + kvlist.values.emplace_back(pdns::trace::KeyValue{"foo", {"bar"}}); + kvlist.values.emplace_back(pdns::trace::KeyValue{"baz", {1.99}}); + testAny(kvlist); + + std::vector bytes; + testAny(bytes); + bytes.push_back(0); + bytes.push_back(1); + bytes.push_back(2); + testAny(bytes); +} + +BOOST_AUTO_TEST_CASE(traces) +{ + pdns::trace::Span span = { + .trace_id = {0x5B, 0x8E, 0xFF, 0xF7, 0x98, 0x03, 0x81, 0x03, 0xD2, 0x69, 0xB6, 0x33, 0x81, 0x3F, 0xC6, 0x0C}, + .span_id = {0xEE, 0xE1, 0x9B, 0x7E, 0xC3, 0xC1, 0xB1, 0x74}, + .parent_span_id = {0xEE, 0xE1, 0x9B, 0x7E, 0xC3, 0xC1, 0xB1, 0x73}, + .name = "I'm a server span", + .start_time_unix_nano = 1544712660000000000UL, + .end_time_unix_nano = 1544712661000000000UL, + .kind = pdns::trace::Span::SpanKind::SPAN_KINSERVER, + .attributes = {{"my.span.attr", {"some value"}}}}; + pdns::trace::InstrumentationScope scope = {"my.library", "1.0.0", {{"my.scope.attribute", {"some scope attribute"}}}}; + pdns::trace::ScopeSpans scopespans = {.scope = scope, .spans = {span}}; + pdns::trace::Resource res = {.attributes = {{"service.name", {"my.service"}}}}; + pdns::trace::ResourceSpans resspans = {{res}, .scope_spans = {scopespans}}; + pdns::trace::TracesData traces = {.resource_spans = {resspans}}; + + std::string data; + protozero::pbf_writer writer{data}; + traces.encode(writer); +#if 0 + std::ofstream z("z"); + z << data; +#endif + const string expected = "" + "0a d3 01 0a 1e 0a 1c 0a 0c 73 65 72 76 69 63 65 " + "2e 6e 61 6d 65 12 0c 0a 0a 6d 79 2e 73 65 72 76 " + "69 63 65 12 b0 01 0a 41 0a 0a 6d 79 2e 6c 69 62 " + "72 61 72 79 12 05 31 2e 30 2e 30 1a 2c 0a 12 6d " + "79 2e 73 63 6f 70 65 2e 61 74 74 72 69 62 75 74 " + "65 12 16 0a 14 73 6f 6d 65 20 73 63 6f 70 65 20 " + "61 74 74 72 69 62 75 74 65 12 6b 0a 10 5b 8e ff " + "f7 98 03 81 03 d2 69 b6 33 81 3f c6 0c 12 08 ee " + "e1 9b 7e c3 c1 b1 74 22 08 ee e1 9b 7e c3 c1 b1 " + "73 2a 11 49 27 6d 20 61 20 73 65 72 76 65 72 20 " + "73 70 61 6e 30 02 39 00 48 59 e3 fa eb 6f 15 41 " + "00 12 f4 1e fb eb 6f 15 4a 1c 0a 0c 6d 79 2e 73 " + "70 61 6e 2e 61 74 74 72 12 0c 0a 0a 73 6f 6d 65 " + "20 76 61 6c 75 65 "; + BOOST_CHECK_EQUAL(makeHexDump(data, " "), expected); + + protozero::pbf_reader reader{data}; + auto copy = pdns::trace::TracesData::decode(reader); + data.clear(); + protozero::pbf_writer copyWriter{data}; + copy.encode(copyWriter); + BOOST_CHECK_EQUAL(makeHexDump(data, " "), expected); +} +BOOST_AUTO_TEST_SUITE_END() diff --git a/pdns/recursordist/trace-example.proto b/pdns/recursordist/trace-example.proto new file mode 100644 index 0000000000..8927ecc257 --- /dev/null +++ b/pdns/recursordist/trace-example.proto @@ -0,0 +1,51 @@ +{ + "resourceSpans": [ + { + "resource": { + "attributes": [ + { + "key": "service.name", + "value": { + "stringValue": "my.service" + } + } + ] + }, + "scopeSpans": [ + { + "scope": { + "name": "my.library", + "version": "1.0.0", + "attributes": [ + { + "key": "my.scope.attribute", + "value": { + "stringValue": "some scope attribute" + } + } + ] + }, + "spans": [ + { + "traceId": "W47/95gDgQPSabYzgT/GDA==", + "spanId": "7uGbfsPBsXQ=", + "parentSpanId": "7uGbfsPBsXM=", + "name": "I'm a server span", + "startTimeUnixNano": 1544712660000000000, + "endTimeUnixNano": 1544712661000000000, + "kind": 2, + "attributes": [ + { + "key": "my.span.attr", + "value": { + "stringValue": "some value" + } + } + ] + } + ] + } + ] + } + ] +} \ No newline at end of file