diff --git a/src/datadog/datadog_agent.cpp b/src/datadog/datadog_agent.cpp index 1d68a241..ddbc102b 100644 --- a/src/datadog/datadog_agent.cpp +++ b/src/datadog/datadog_agent.cpp @@ -2,7 +2,6 @@ #include #include -#include #include #include #include @@ -31,37 +30,21 @@ HTTPClient::URL traces_endpoint(const HTTPClient::URL& agent_url) { Expected msgpack_encode( std::string& destination, - const std::vector>& spans) try { - msgpack::pack_array(destination, spans.size()); - - for (const auto& span_ptr : spans) { - assert(span_ptr); - auto result = msgpack_encode(destination, *span_ptr); - if (auto* error = result.if_error()) { - return std::move(*error); - } - } - - return std::nullopt; -} catch (const std::exception& error) { - return Error{Error::MESSAGEPACK_ENCODE_FAILURE, error.what()}; + const std::vector>& spans) { + return msgpack::pack_array(destination, spans, + [](auto& destination, const auto& span_ptr) { + assert(span_ptr); + return msgpack_encode(destination, *span_ptr); + }); } Expected msgpack_encode( std::string& destination, - const std::vector& trace_chunks) try { - msgpack::pack_array(destination, trace_chunks.size()); - - for (const auto& chunk : trace_chunks) { - auto result = msgpack_encode(destination, chunk.spans); - if (auto* error = result.if_error()) { - return std::move(*error); - } - } - - return std::nullopt; -} catch (const std::exception& error) { - return Error{Error::MESSAGEPACK_ENCODE_FAILURE, error.what()}; + const std::vector& trace_chunks) { + return msgpack::pack_array(destination, trace_chunks, + [](auto& destination, const auto& chunk) { + return msgpack_encode(destination, chunk.spans); + }); } std::variant parse_agent_traces_response( diff --git a/src/datadog/msgpack.cpp b/src/datadog/msgpack.cpp index 661f76cf..6ea5fc7a 100644 --- a/src/datadog/msgpack.cpp +++ b/src/datadog/msgpack.cpp @@ -1 +1,133 @@ #include "msgpack.h" + +#include +#include +#include + +#include "error.h" + +namespace datadog { +namespace tracing { +namespace msgpack { +namespace { +// MessagePack values are prefixed by a byte naming their type. +namespace types { +constexpr auto ARRAY32 = std::byte(0xDD); +constexpr auto DOUBLE = std::byte(0xCB); +constexpr auto INT64 = std::byte(0xD3); +constexpr auto MAP32 = std::byte(0xDF); +constexpr auto STR32 = std::byte(0xDB); +constexpr auto UINT64 = std::byte(0xCF); +} // namespace types + +std::string make_overflow_message(std::string_view type, std::size_t actual, + std::size_t max) { + std::string message; + message += "Cannot msgpack encode "; + message += type; + message += " of size "; + message += std::to_string(actual); + message += ", which exceeds the protocol maximum of "; + message += std::to_string(max); + message += '.'; + return message; +} + +template +void push_number_big_endian(std::string& buffer, Integer integer) { + // Assume two's complement. + const std::make_unsigned_t value = integer; + + // The loop below is more likely to unroll if we don't call any functions + // within it. + char buf[sizeof value]; + + // The most significant byte of `value` goes to the front of `buf`, and the + // least significant byte of `value` goes + // to the back of `buf`, and so on in between. + // On a big endian architecture, this is just a complicated way to copy + // `value`. On a little endian architecture, which is much more common, this + // effectively copies the bytes of `value` backwards. + const int size = sizeof value; + for (int i = 0; i < size; ++i) { + const char byte = (value >> (8 * ((size - 1) - i))) & 0xFF; + buf[i] = byte; + } + + buffer.append(buf, sizeof buf); +} + +} // namespace + +void pack_integer(std::string& buffer, std::int64_t value) { + buffer.push_back(static_cast(types::INT64)); + push_number_big_endian(buffer, static_cast(value)); +} + +void pack_integer(std::string& buffer, std::uint64_t value) { + buffer.push_back(static_cast(types::UINT64)); + push_number_big_endian(buffer, static_cast(value)); +} + +void pack_double(std::string& buffer, double value) { + buffer.push_back(static_cast(types::DOUBLE)); + + // The following is lifted from the "msgpack-c" project. + // See "pack_double" in + // + + union { + double as_double; + uint64_t as_integer; + } memory; + memory.as_double = value; + +#if defined(TARGET_OS_IPHONE) + // ok +#elif defined(__arm__) && !(__ARM_EABI__) // arm-oabi + // https://github.com/msgpack/msgpack-perl/pull/1 + memory.as_integer = + (memory.as_integer & 0xFFFFFFFFUL) << 32UL | (memory.as_integer >> 32UL); +#endif + + push_number_big_endian(buffer, memory.as_integer); +} + +Expected pack_string(std::string& buffer, std::string_view value) { + const auto size = value.size(); + const auto max = std::numeric_limits::max(); + if (size > max) { + return Error{Error::MESSAGEPACK_ENCODE_FAILURE, + make_overflow_message("string", size, max)}; + } + buffer.push_back(static_cast(types::STR32)); + push_number_big_endian(buffer, static_cast(size)); + buffer.append(value.begin(), value.end()); + return {}; +} + +Expected pack_array(std::string& buffer, size_t size) { + const auto max = std::numeric_limits::max(); + if (size > max) { + return Error{Error::MESSAGEPACK_ENCODE_FAILURE, + make_overflow_message("array", size, max)}; + } + buffer.push_back(static_cast(types::ARRAY32)); + push_number_big_endian(buffer, static_cast(size)); + return {}; +} + +Expected pack_map(std::string& buffer, size_t size) { + const auto max = std::numeric_limits::max(); + if (size > max) { + return Error{Error::MESSAGEPACK_ENCODE_FAILURE, + make_overflow_message("map", size, max)}; + } + buffer.push_back(static_cast(types::MAP32)); + push_number_big_endian(buffer, static_cast(size)); + return {}; +} + +} // namespace msgpack +} // namespace tracing +} // namespace datadog diff --git a/src/datadog/msgpack.h b/src/datadog/msgpack.h index 06fe03af..212350cf 100644 --- a/src/datadog/msgpack.h +++ b/src/datadog/msgpack.h @@ -1,177 +1,159 @@ #pragma once +// This component provides encoding routines for [MessagePack][1]. +// +// Each function is in `namespace msgpack` and appends a specified value to a +// `std::string`. For example, `msgpack::pack_integer(destination, -42)` +// MessagePack encodes the number `-42` and appends the result to `destination`. +// +// Only encoding is provided, and only for the types required by `SpanData` and +// `DatadogAgent`. +// +// [1]: https://msgpack.org/index.html + #include #include -#include -#include #include #include -#include +#include + +#include "expected.h" namespace datadog { namespace tracing { namespace msgpack { -// First, declare all functions, so that I don't have to topologically sort -// their inline definitions. - -template -void push_number_big_endian(std::string& buffer, Integer integer); - -template -void push(std::string& buffer, const Range& range); - -void pack_negative(std::string& buffer, std::int64_t value); - -void pack_nonnegative(std::string& buffer, std::uint64_t value); - -template -void pack_integer(std::string& buffer, Integer value); +void pack_integer(std::string& buffer, std::int64_t value); +void pack_integer(std::string& buffer, std::uint64_t value); +void pack_integer(std::string& buffer, std::int32_t value); void pack_double(std::string& buffer, double value); -void pack_str(std::string& buffer, const char* cstr); - -template -void pack_str(std::string& buffer, const Range& range); - -void pack_array(std::string& buffer, size_t size); - -void pack_map(std::string& buffer, size_t size); - -std::string make_overflow_message(std::string_view type, std::size_t actual, - std::size_t max); - -// MessagePack values are prefixed by a byte naming their type. -namespace types { -constexpr auto DOUBLE = std::byte(0xCB); -constexpr auto UINT64 = std::byte(0xCF); -constexpr auto INT64 = std::byte(0xD3); -constexpr auto STR32 = std::byte(0xDB); -constexpr auto ARRAY32 = std::byte(0xDD); -constexpr auto MAP32 = std::byte(0xDF); -} // namespace types - -// Here are the inline definitions of all of the functions declared above. - -inline std::string make_overflow_message(std::string_view type, - std::size_t actual, std::size_t max) { - std::string message; - message += "Cannot msgpack encode "; - message += type; - message += " of size "; - message += std::to_string(actual); - message += ", which exceeds the protocol maximum of "; - message += std::to_string(max); - message += '.'; - return message; -} - -template -void push_number_big_endian(std::string& buffer, Integer integer) { - // Assume two's complement. - const std::make_unsigned_t value = integer; - - // The loop below is more likely to unroll if we don't call any functions - // within it. - char buf[sizeof value]; - - // The most significant byte of `value` goes to the front of `buf`, and the - // least significant byte of `value` goes - // to the back of `buf`, and so on in between. - // On a big endian architecture, this is just a complicated way to copy - // `value`. On a little endian architecture, which is much more common, this - // effectively copies the bytes of `value` backwards. - const int size = sizeof value; - for (int i = 0; i < size; ++i) { - const char byte = (value >> (8 * ((size - 1) - i))) & 0xFF; - buf[i] = byte; +Expected pack_string(std::string& buffer, std::string_view value); + +Expected pack_array(std::string& buffer, std::size_t size); + +// Append to the specified `buffer` a MessagePack encoded array having the +// specified `values`, where for each element of `values` the specified +// `pack_value` function appends the value. `pack_value` is invoked with two +// arguments: the first is a reference to `buffer`, and the second is a +// reference to the current value. `pack_value` returns an `Expected`. If +// the return value is an error, then iteration is halted and the error is +// returned. If some other error occurs, then an error is returned. Otherwise, +// the non-error value is returned. +template +Expected pack_array(std::string& buffer, Iterable&& values, + PackValue&& pack_value); + +Expected pack_map(std::string& buffer, std::size_t size); + +// Append to the specified `buffer` a MessagePack encoded map consisting of the +// specified `pairs`, where the first element of each pair is the name of the +// map element, and the second element of each pair is some value that is +// MessagePack encoded by the specified `pack_value` function. `pack_value` is +// invoked with two arguments: the first is a reference to `buffer`, and the +// second is a reference to the current value. `pack_value` returns an +// `Expected`. If the return value is an error, then iteration is halted +// and the error is returned. If some other error occurs, then an error is +// returned. Otherwise, the non-error value is returned. +template +Expected pack_map(std::string& buffer, const PairIterable& pairs, + PackValue&& pack_value); + +// Append to the specified `buffer` a MessagePack encoded map consisting of the +// specified key value pairs. After the `buffer` argument, `pack_map` accepts +// an even number of arguments. First in each pair of arguments is `key`, the +// key name of the corresponding map item. Second in each pair of arguments is +// `pack_value`, a function that encodes the corresponding value. `pack_value` +// is invoked with two arguments: the first is a reference to `buffer`, and the +// second is a reference to the current value. `pack_value` returns an +// `Expected`. If the return value is an error, then iteration is halted +// and the error is returned. If some other error occurs, then an error is +// returned. Otherwise, the non-error value is returned. +template +Expected pack_map(std::string& buffer, Key&& key, PackValue&& pack_value, + Rest&&... rest); + +template +Expected pack_map_suffix(std::string& buffer, Key&& key, + PackValue&& pack_value, Rest&&... rest); +Expected pack_map_suffix(std::string& buffer); + +template +Expected pack_array(std::string& buffer, Iterable&& values, + PackValue&& pack_value) { + Expected result; + result = pack_array(buffer, std::size(values)); + if (!result) { + return result; } - - buffer.append(buf, sizeof buf); -} - -template -void push(std::string& buffer, const Range& range) { - buffer.insert(buffer.end(), std::begin(range), std::end(range)); -} - -inline void pack_negative(std::string& buffer, std::int64_t value) { - buffer.push_back(static_cast(types::INT64)); - push_number_big_endian(buffer, static_cast(value)); -} - -inline void pack_nonnegative(std::string& buffer, std::uint64_t value) { - buffer.push_back(static_cast(types::UINT64)); - push_number_big_endian(buffer, static_cast(value)); -} - -template -void pack_integer(std::string& buffer, Integer value) { - if (value < 0) { - return pack_negative(buffer, value); - } else { - return pack_nonnegative(buffer, value); + for (const auto& value : values) { + result = pack_value(buffer, value); + if (!result) { + break; + } } + return result; } -inline void pack_double(std::string& buffer, double value) { - buffer.push_back(static_cast(types::DOUBLE)); - - // The following is lifted from the "msgpack-c" project. - // See "pack_double" in - // - - union { - double as_double; - uint64_t as_integer; - } memory; - memory.as_double = value; - -#if defined(TARGET_OS_IPHONE) - // ok -#elif defined(__arm__) && !(__ARM_EABI__) // arm-oabi - // https://github.com/msgpack/msgpack-perl/pull/1 - memory.as_integer = - (memory.as_integer & 0xFFFFFFFFUL) << 32UL | (memory.as_integer >> 32UL); -#endif - - push_number_big_endian(buffer, memory.as_integer); +template +Expected pack_map(std::string& buffer, const PairIterable& pairs, + PackValue&& pack_value) { + Expected result; + result = pack_map(buffer, std::size(pairs)); + if (!result) { + return result; + } + for (const auto& [key, value] : pairs) { + result = pack_string(buffer, key); + if (!result) { + break; + } + result = pack_value(buffer, value); + if (!result) { + break; + } + } + return result; } -inline void pack_str(std::string& buffer, const char* cstr) { - return pack_str(buffer, std::string_view(cstr)); +template +Expected pack_map(std::string& buffer, Key&& key, PackValue&& pack_value, + Rest&&... rest) { + Expected result; + result = pack_map(buffer, 1 + sizeof...(rest)); + if (!result) { + return result; + } + result = pack_map_suffix(buffer, std::forward(key), + std::forward(pack_value), + std::forward(rest)...); + return result; } -template -void pack_str(std::string& buffer, const Range& range) { - auto size = - static_cast(std::distance(std::begin(range), std::end(range))); - if (size > std::numeric_limits::max()) { - throw std::out_of_range(make_overflow_message( - "string", size, std::numeric_limits::max())); +template +Expected pack_map_suffix(std::string& buffer, Key&& key, + PackValue&& pack_value, Rest&&... rest) { + Expected result; + result = pack_string(buffer, key); + if (!result) { + return result; } - buffer.push_back(static_cast(types::STR32)); - push_number_big_endian(buffer, static_cast(size)); - push(buffer, range); + result = pack_value(buffer); + if (!result) { + return result; + } + result = pack_map_suffix(buffer, std::forward(rest)...); + return result; } -inline void pack_array(std::string& buffer, size_t size) { - if (size > std::numeric_limits::max()) { - throw std::out_of_range(make_overflow_message( - "array", size, std::numeric_limits::max())); - } - buffer.push_back(static_cast(types::ARRAY32)); - push_number_big_endian(buffer, static_cast(size)); +inline Expected pack_map_suffix(std::string&) { + // base case does nothing + return {}; } -inline void pack_map(std::string& buffer, size_t size) { - if (size > std::numeric_limits::max()) { - throw std::out_of_range(make_overflow_message( - "map", size, std::numeric_limits::max())); - } - buffer.push_back(static_cast(types::MAP32)); - push_number_big_endian(buffer, static_cast(size)); +inline void pack_integer(std::string& buffer, std::int32_t value) { + pack_integer(buffer, std::int64_t(value)); } } // namespace msgpack diff --git a/src/datadog/span_data.cpp b/src/datadog/span_data.cpp index ee962c0d..2b701223 100644 --- a/src/datadog/span_data.cpp +++ b/src/datadog/span_data.cpp @@ -1,6 +1,6 @@ #include "span_data.h" -#include +#include #include #include "error.h" @@ -62,66 +62,67 @@ void SpanData::apply_config(const SpanDefaults& defaults, } } -Expected msgpack_encode(std::string& destination, - const SpanData& span) try { - // Be sure to update `num_fields` when adding fields. - const int num_fields = 12; - msgpack::pack_map(destination, num_fields); - - msgpack::pack_str(destination, "service"); - msgpack::pack_str(destination, span.service); - - msgpack::pack_str(destination, "name"); - msgpack::pack_str(destination, span.name); - - msgpack::pack_str(destination, "resource"); - msgpack::pack_str(destination, span.resource); - - msgpack::pack_str(destination, "trace_id"); - msgpack::pack_integer(destination, span.trace_id); - - msgpack::pack_str(destination, "span_id"); - msgpack::pack_integer(destination, span.span_id); - - msgpack::pack_str(destination, "parent_id"); - msgpack::pack_integer(destination, span.parent_id); - - msgpack::pack_str(destination, "start"); - msgpack::pack_integer(destination, - std::chrono::duration_cast( - span.start.wall.time_since_epoch()) - .count()); - - msgpack::pack_str(destination, "duration"); - msgpack::pack_integer( +Expected msgpack_encode(std::string& destination, const SpanData& span) { + // clang-format off + msgpack::pack_map( destination, - std::chrono::duration_cast(span.duration) - .count()); - - msgpack::pack_str(destination, "error"); - msgpack::pack_integer(destination, std::int32_t(span.error)); - - msgpack::pack_str(destination, "meta"); - msgpack::pack_map(destination, span.tags.size()); - for (const auto& [key, value] : span.tags) { - msgpack::pack_str(destination, key); - msgpack::pack_str(destination, value); - } - - msgpack::pack_str(destination, "metrics"); - msgpack::pack_map(destination, span.numeric_tags.size()); - for (const auto& [key, value] : span.numeric_tags) { - msgpack::pack_str(destination, key); - msgpack::pack_double(destination, value); - } - - msgpack::pack_str(destination, "type"); - msgpack::pack_str(destination, span.service_type); + "service", [&](auto& destination) { + return msgpack::pack_string(destination, span.service); + }, + "name", [&](auto& destination) { + return msgpack::pack_string(destination, span.name); + }, + "resource", [&](auto& destination) { + return msgpack::pack_string(destination, span.resource); + }, + "trace_id", [&](auto& destination) { + msgpack::pack_integer(destination, span.trace_id); + return Expected{}; + }, + "span_id", [&](auto& destination) { + msgpack::pack_integer(destination, span.span_id); + return Expected{}; + }, + "parent_id", [&](auto& destination) { + msgpack::pack_integer(destination, span.parent_id); + return Expected{}; + }, + "start", [&](auto& destination) { + msgpack::pack_integer( + destination, std::chrono::duration_cast( + span.start.wall.time_since_epoch()) + .count()); + return Expected{}; + }, + "duration", [&](auto& destination) { + msgpack::pack_integer( + destination, + std::chrono::duration_cast(span.duration) + .count()); + return Expected{}; + }, + "error", [&](auto& destination) { + msgpack::pack_integer(destination, std::int32_t(span.error)); + return Expected{}; + }, + "meta", [&](auto& destination) { + return msgpack::pack_map(destination, span.tags, + [](std::string& destination, const auto& value) { + return msgpack::pack_string(destination, value); + }); + }, "metrics", + [&](auto& destination) { + return msgpack::pack_map(destination, span.numeric_tags, + [](std::string& destination, const auto& value) { + msgpack::pack_double(destination, value); + return Expected{}; + }); + }, "type", [&](auto& destination) { + return msgpack::pack_string(destination, span.service_type); + }); + // clang-format on - // Be sure to update `num_fields` when adding fields. return std::nullopt; -} catch (const std::exception& error) { - return Error{Error::MESSAGEPACK_ENCODE_FAILURE, error.what()}; } } // namespace tracing