diff options
Diffstat (limited to 'mocktracer/src')
-rw-r--r-- | mocktracer/src/base64.cpp | 174 | ||||
-rw-r--r-- | mocktracer/src/base64.h | 48 | ||||
-rw-r--r-- | mocktracer/src/dynamic_load.cpp | 39 | ||||
-rw-r--r-- | mocktracer/src/in_memory_recorder.cpp | 33 | ||||
-rw-r--r-- | mocktracer/src/json.cpp | 285 | ||||
-rw-r--r-- | mocktracer/src/json_recorder.cpp | 30 | ||||
-rw-r--r-- | mocktracer/src/mock_span.cpp | 206 | ||||
-rw-r--r-- | mocktracer/src/mock_span.h | 66 | ||||
-rw-r--r-- | mocktracer/src/mock_span_context.cpp | 40 | ||||
-rw-r--r-- | mocktracer/src/mock_span_context.h | 79 | ||||
-rw-r--r-- | mocktracer/src/propagation.cpp | 214 | ||||
-rw-r--r-- | mocktracer/src/propagation.h | 39 | ||||
-rw-r--r-- | mocktracer/src/tracer.cpp | 109 | ||||
-rw-r--r-- | mocktracer/src/tracer_factory.cpp | 139 | ||||
-rw-r--r-- | mocktracer/src/utility.cpp | 57 | ||||
-rw-r--r-- | mocktracer/src/utility.h | 17 |
16 files changed, 1575 insertions, 0 deletions
diff --git a/mocktracer/src/base64.cpp b/mocktracer/src/base64.cpp new file mode 100644 index 0000000..d039dcf --- /dev/null +++ b/mocktracer/src/base64.cpp @@ -0,0 +1,174 @@ +/* + * Envoy + * Copyright 2016-2017 Lyft Inc. + * + * Licensed under Apache License 2.0. See LICENSE.apache for terms. + */ + +#include "base64.h" + +#include <cstdint> +#include <string> + +namespace opentracing { +BEGIN_OPENTRACING_ABI_NAMESPACE +namespace mocktracer { +static constexpr char CHAR_TABLE[] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + +// Conversion table is taken from +// https://opensource.apple.com/source/QuickTimeStreamingServer/QuickTimeStreamingServer-452/CommonUtilitiesLib/base64.c +static const unsigned char REVERSE_LOOKUP_TABLE[256] = { + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 62, 64, 64, 64, 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, + 61, 64, 64, 64, 64, 64, 64, 64, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, + 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 64, 64, 64, 64, + 64, 64, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, + 43, 44, 45, 46, 47, 48, 49, 50, 51, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64}; + +std::string Base64::decode(const char* input, size_t length) { + if (length % 4 || length == 0) { + return {}; + } + + // First position of "valid" padding character. + uint64_t first_padding_index = length; + int max_length = static_cast<int>(length) / 4 * 3; + // At most last two chars can be '='. + if (input[length - 1] == '=') { + max_length--; + first_padding_index = length - 1; + if (input[length - 2] == '=') { + max_length--; + first_padding_index = length - 2; + } + } + std::string result; + result.reserve(static_cast<size_t>(max_length)); + + uint64_t bytes_left = length; + uint64_t cur_read = 0; + + // Read input string by group of 4 chars, length of input string must be + // divided evenly by 4. Decode 4 chars 6 bits each into 3 chars 8 bits each. + while (bytes_left > 0) { + // Take first 6 bits from 1st converted char and first 2 bits from 2nd + // converted char, make 8 bits char from it. Use conversion table to map + // char to decoded value (value is between 0 and 63 inclusive for a valid + // character). + const unsigned char a = + REVERSE_LOOKUP_TABLE[static_cast<uint32_t>(input[cur_read])]; + const unsigned char b = + REVERSE_LOOKUP_TABLE[static_cast<uint32_t>(input[cur_read + 1])]; + if (a == 64 || b == 64) { + // Input contains an invalid character. + return {}; + } + result.push_back(static_cast<char>(a << 2 | b >> 4)); + const unsigned char c = + REVERSE_LOOKUP_TABLE[static_cast<uint32_t>(input[cur_read + 2])]; + + // Decoded value 64 means invalid character unless we already know it is a + // valid padding. If so, following characters are all padding. Also we + // should check there are no unused bits. + if (c == 64) { + if (first_padding_index != cur_read + 2) { + // Input contains an invalid character. + return {}; + } else if (b & 15) { + // There are unused bits at tail. + return {}; + } else { + return result; + } + } + // Take last 4 bits from 2nd converted char and 4 first bits from 3rd + // converted char. + result.push_back(static_cast<char>(b << 4 | c >> 2)); + + const unsigned char d = + REVERSE_LOOKUP_TABLE[static_cast<uint32_t>(input[cur_read + 3])]; + if (d == 64) { + if (first_padding_index != cur_read + 3) { + // Input contains an invalid character. + return {}; + } else if (c & 3) { + // There are unused bits at tail. + return {}; + } else { + return result; + } + } + // Take last 2 bits from 3rd converted char and all(6) bits from 4th + // converted char. + result.push_back(static_cast<char>(c << 6 | d)); + + cur_read += 4; + bytes_left -= 4; + } + + return result; +} + +void Base64::encodeBase(const uint8_t cur_char, uint64_t pos, uint8_t& next_c, + std::string& ret) { + switch (pos % 3) { + case 0: + ret.push_back(CHAR_TABLE[cur_char >> 2]); + next_c = static_cast<uint8_t>((cur_char & 0x03) << 4); + break; + case 1: + ret.push_back(CHAR_TABLE[next_c | (cur_char >> 4)]); + next_c = static_cast<uint8_t>((cur_char & 0x0f) << 2); + break; + case 2: + ret.push_back(CHAR_TABLE[next_c | (cur_char >> 6)]); + ret.push_back(CHAR_TABLE[cur_char & 0x3f]); + next_c = 0; + break; + } +} + +void Base64::encodeLast(uint64_t pos, uint8_t last_char, std::string& ret) { + switch (pos % 3) { + case 1: + ret.push_back(CHAR_TABLE[last_char]); + ret.push_back('='); + ret.push_back('='); + break; + case 2: + ret.push_back(CHAR_TABLE[last_char]); + ret.push_back('='); + break; + default: + break; + } +} + +std::string Base64::encode(const char* input, uint64_t length) { + uint64_t output_length = (length + 2) / 3 * 4; + std::string ret; + ret.reserve(output_length); + + uint64_t pos = 0; + uint8_t next_c = 0; + + for (uint64_t i = 0; i < length; ++i) { + encodeBase(static_cast<uint8_t>(input[i]), pos++, next_c, ret); + } + + encodeLast(pos, next_c, ret); + + return ret; +} +} // namespace mocktracer +END_OPENTRACING_ABI_NAMESPACE +} // namespace opentracing diff --git a/mocktracer/src/base64.h b/mocktracer/src/base64.h new file mode 100644 index 0000000..438bbda --- /dev/null +++ b/mocktracer/src/base64.h @@ -0,0 +1,48 @@ +#ifndef OPENTRACING_MOCKTRACER_BASE64_H +#define OPENTRACING_MOCKTRACER_BASE64_H + +#include <opentracing/version.h> +#include <cstdint> +#include <string> + +namespace opentracing { +BEGIN_OPENTRACING_ABI_NAMESPACE +namespace mocktracer { +class Base64 { + public: + /** + * Base64 encode an input char buffer with a given length. + * @param input char array to encode. + * @param length of the input array. + */ + static std::string encode(const char* input, uint64_t length); + + /** + * Base64 decode an input string. + * @param input char array to decode. + * @param length of the input array. + * + * Note, decoded string may contain '\0' at any position, it should be treated + * as a sequence of bytes. + */ + static std::string decode(const char* input, size_t length); + + private: + /** + * Helper method for encoding. This is used to encode all of the characters + * from the input string. + */ + static void encodeBase(const uint8_t cur_char, uint64_t pos, uint8_t& next_c, + std::string& ret); + + /** + * Encode last characters. It appends '=' chars to the ret if input + * string length is not divisible by 3. + */ + static void encodeLast(uint64_t pos, uint8_t last_char, std::string& ret); +}; +} // namespace mocktracer +END_OPENTRACING_ABI_NAMESPACE +} // namespace opentracing + +#endif // OPENTRACING_MOCKTRACER_BASE64_H diff --git a/mocktracer/src/dynamic_load.cpp b/mocktracer/src/dynamic_load.cpp new file mode 100644 index 0000000..0fc7371 --- /dev/null +++ b/mocktracer/src/dynamic_load.cpp @@ -0,0 +1,39 @@ +#include <opentracing/dynamic_load.h> +#include <opentracing/mocktracer/tracer_factory.h> +#include <cstdio> +#include <cstring> +#include <exception> + +static int OpenTracingMakeTracerFactoryFct(const char* opentracing_version, + const char* opentracing_abi_version, + const void** error_category, + void* error_message, + void** tracer_factory) try { + if (opentracing_version == nullptr || opentracing_abi_version == nullptr || + error_category == nullptr || tracer_factory == nullptr) { + fprintf(stderr, + "`opentracing_version`, `opentracing_abi_version`, " + "`error_category`, and `tracer_factory` must be non-null.\n"); + std::terminate(); + } + + if (std::strcmp(opentracing_abi_version, OPENTRACING_ABI_VERSION) != 0) { + *error_category = + static_cast<const void*>(&opentracing::dynamic_load_error_category()); + auto& message = *static_cast<std::string*>(error_message); + message = + "incompatible OpenTracing ABI versions; " + "expected " OPENTRACING_ABI_VERSION " but got "; + message.append(opentracing_abi_version); + return opentracing::incompatible_library_versions_error.value(); + } + + *tracer_factory = new opentracing::mocktracer::MockTracerFactory{}; + + return 0; +} catch (const std::bad_alloc&) { + *error_category = static_cast<const void*>(&std::generic_category()); + return static_cast<int>(std::errc::not_enough_memory); +} + +OPENTRACING_DECLARE_IMPL_FACTORY(OpenTracingMakeTracerFactoryFct); diff --git a/mocktracer/src/in_memory_recorder.cpp b/mocktracer/src/in_memory_recorder.cpp new file mode 100644 index 0000000..818df3c --- /dev/null +++ b/mocktracer/src/in_memory_recorder.cpp @@ -0,0 +1,33 @@ +#include <opentracing/mocktracer/in_memory_recorder.h> +#include <stdexcept> + +namespace opentracing { +BEGIN_OPENTRACING_ABI_NAMESPACE +namespace mocktracer { +void InMemoryRecorder::RecordSpan(SpanData&& span_data) noexcept try { + std::lock_guard<std::mutex> lock_guard{mutex_}; + spans_.emplace_back(std::move(span_data)); +} catch (const std::exception&) { + // Drop span. +} + +std::vector<SpanData> InMemoryRecorder::spans() const { + std::lock_guard<std::mutex> lock_guard{mutex_}; + return spans_; +} + +size_t InMemoryRecorder::size() const { + std::lock_guard<std::mutex> lock_guard{mutex_}; + return spans_.size(); +} + +SpanData InMemoryRecorder::top() const { + std::lock_guard<std::mutex> lock_guard{mutex_}; + if (spans_.empty()) { + throw std::runtime_error{"no spans"}; + } + return spans_.back(); +} +} // namespace mocktracer +END_OPENTRACING_ABI_NAMESPACE +} // namespace opentracing diff --git a/mocktracer/src/json.cpp b/mocktracer/src/json.cpp new file mode 100644 index 0000000..10c46d3 --- /dev/null +++ b/mocktracer/src/json.cpp @@ -0,0 +1,285 @@ +#include <opentracing/mocktracer/json.h> +#include <opentracing/mocktracer/recorder.h> +#include <opentracing/string_view.h> +#include <cmath> +#include <iomanip> +#include <sstream> + +namespace opentracing { +BEGIN_OPENTRACING_ABI_NAMESPACE +namespace mocktracer { +// The implementation is based off of this answer from StackOverflow: +// https://stackoverflow.com/a/33799784 +static void WriteEscapedString(std::ostream& writer, + opentracing::string_view s) { + writer << '"'; + for (char c : s) { + switch (c) { + case '"': + writer << R"(\")"; + break; + case '\\': + writer << R"(\\)"; + break; + case '\b': + writer << R"(\b)"; + break; + case '\n': + writer << R"(\n)"; + break; + case '\r': + writer << R"(\r)"; + break; + case '\t': + writer << R"(\t)"; + break; + default: + if ('\x00' <= c && c <= '\x1f') { + writer << R"(\u)"; + writer << std::hex << std::setw(4) << std::setfill('0') + << static_cast<int>(c); + } else { + writer << c; + } + } + } + writer << '"'; +} + +static void WriteId(std::ostream& writer, uint64_t id) { + std::ostringstream oss; + oss << std::setfill('0') << std::setw(16) << std::hex << id; + if (!oss.good()) { + writer.setstate(std::ios::failbit); + return; + } + writer << '"' << oss.str() << '"'; +} + +static void ToJson(std::ostream& writer, + const SpanContextData& span_context_data) { + writer << '{'; + writer << R"("trace_id":)"; + WriteId(writer, span_context_data.trace_id); + writer << ','; + + writer << R"("span_id":)"; + WriteId(writer, span_context_data.span_id); + writer << ','; + + writer << R"("baggage":{)"; + auto num_baggage = span_context_data.baggage.size(); + size_t baggage_index = 0; + for (auto& baggage_item : span_context_data.baggage) { + WriteEscapedString(writer, baggage_item.first); + writer << ':'; + WriteEscapedString(writer, baggage_item.second); + if (++baggage_index < num_baggage) { + writer << ','; + } + } + writer << '}'; + writer << '}'; +} + +static void ToJson(std::ostream& writer, + const SpanReferenceData& span_reference_data) { + writer << '{'; + writer << R"("reference_type":)"; + if (span_reference_data.reference_type == SpanReferenceType::ChildOfRef) { + writer << R"("CHILD_OF")"; + } else { + writer << R"("FOLLOWS_FROM")"; + } + writer << ','; + + writer << R"("trace_id":)"; + WriteId(writer, span_reference_data.trace_id); + writer << ','; + writer << R"("span_id":)"; + WriteId(writer, span_reference_data.span_id); + + writer << '}'; +} + +static void ToJson(std::ostream& writer, const Value& value); + +namespace { +struct ValueVisitor { + std::ostream& writer; + + void operator()(bool value) { + if (value) { + writer << "true"; + } else { + writer << "false"; + } + } + + void operator()(double value) { + if (std::isnan(value)) { + writer << R"("NaN")"; + } else if (std::isinf(value)) { + if (std::signbit(value)) { + writer << R"("-Inf")"; + } else { + writer << R"("+Inf")"; + } + } else { + writer << value; + } + } + + void operator()(int64_t value) { writer << value; } + + void operator()(uint64_t value) { writer << value; } + + void operator()(const std::string& s) { WriteEscapedString(writer, s); } + + void operator()(std::nullptr_t) { writer << "null"; } + + void operator()(const char* s) { WriteEscapedString(writer, s); } + + void operator()(const Values& values) { + writer << '['; + size_t i = 0; + for (const auto& value : values) { + ToJson(writer, value); + if (++i < values.size()) { + writer << ','; + } + } + writer << ']'; + } + + void operator()(const Dictionary& dictionary) { + writer << '{'; + size_t i = 0; + for (const auto& key_value : dictionary) { + WriteEscapedString(writer, key_value.first); + writer << ':'; + ToJson(writer, key_value.second); + if (++i < dictionary.size()) { + writer << ','; + } + } + writer << '}'; + } +}; +} // anonymous namespace + +void ToJson(std::ostream& writer, const Value& value) { + ValueVisitor value_visitor{writer}; + apply_visitor(value_visitor, value); +} + +template <class Rep, class Period> +static void ToJson(std::ostream& writer, + const std::chrono::duration<Rep, Period>& duration) { + auto count = + std::chrono::duration_cast<std::chrono::microseconds>(duration).count(); + writer << count; +} + +static void ToJson(std::ostream& writer, const LogRecord& log_record) { + writer << '{'; + writer << R"("timestamp":)"; + ToJson(writer, log_record.timestamp.time_since_epoch()); + writer << ','; + writer << R"("fields":)"; + writer << '['; + auto num_fields = log_record.fields.size(); + size_t field_index = 0; + for (auto& field : log_record.fields) { + writer << '{'; + writer << R"("key":)"; + WriteEscapedString(writer, field.first); + writer << ','; + writer << R"("value":)"; + ToJson(writer, field.second); + writer << '}'; + if (++field_index < num_fields) { + writer << ','; + } + } + writer << ']'; + writer << '}'; +} + +static void ToJson(std::ostream& writer, const SpanData& span_data) { + writer << '{'; + + writer << R"("span_context":)"; + ToJson(writer, span_data.span_context); + writer << ','; + + writer << R"("references":)"; + writer << '['; + auto num_references = span_data.references.size(); + size_t reference_index = 0; + for (auto& reference : span_data.references) { + ToJson(writer, reference); + if (++reference_index < num_references) { + writer << ','; + } + } + writer << ']'; + writer << ','; + + writer << R"("operation_name":)"; + WriteEscapedString(writer, span_data.operation_name); + writer << ','; + + writer << R"("start_timestamp":)"; + ToJson(writer, span_data.start_timestamp.time_since_epoch()); + writer << ','; + + writer << R"("duration":)"; + ToJson(writer, span_data.duration); + writer << ','; + + writer << R"("tags":)"; + writer << '{'; + auto num_tags = span_data.tags.size(); + size_t tag_index = 0; + for (auto& tag : span_data.tags) { + WriteEscapedString(writer, tag.first); + writer << ':'; + ToJson(writer, tag.second); + if (++tag_index < num_tags) { + writer << ','; + } + } + writer << '}'; + writer << ','; + + writer << R"("logs":)"; + writer << '['; + auto num_logs = span_data.logs.size(); + size_t log_index = 0; + for (auto& log : span_data.logs) { + ToJson(writer, log); + if (++log_index < num_logs) { + writer << ','; + } + } + writer << ']'; + + writer << '}'; +} + +void ToJson(std::ostream& writer, const std::vector<SpanData>& spans) { + writer << '['; + auto num_spans = spans.size(); + size_t span_index = 0; + for (auto& span_data : spans) { + ToJson(writer, span_data); + if (++span_index < num_spans) { + writer << ','; + } + } + writer << ']'; +} +} // namespace mocktracer +END_OPENTRACING_ABI_NAMESPACE +} // namespace opentracing diff --git a/mocktracer/src/json_recorder.cpp b/mocktracer/src/json_recorder.cpp new file mode 100644 index 0000000..5d30d9a --- /dev/null +++ b/mocktracer/src/json_recorder.cpp @@ -0,0 +1,30 @@ +#include <opentracing/mocktracer/json.h> +#include <opentracing/mocktracer/json_recorder.h> + +namespace opentracing { +BEGIN_OPENTRACING_ABI_NAMESPACE +namespace mocktracer { +JsonRecorder::JsonRecorder(std::unique_ptr<std::ostream>&& out) + : out_{std::move(out)} {} + +void JsonRecorder::RecordSpan(SpanData&& span_data) noexcept try { + std::lock_guard<std::mutex> lock_guard{mutex_}; + spans_.emplace_back(std::move(span_data)); +} catch (const std::exception&) { + // Drop span. +} + +void JsonRecorder::Close() noexcept try { + if (out_ == nullptr) { + return; + } + std::lock_guard<std::mutex> lock_guard{mutex_}; + ToJson(*out_, spans_); + out_->flush(); + spans_.clear(); +} catch (const std::exception&) { + // Ignore errors. +} +} // namespace mocktracer +END_OPENTRACING_ABI_NAMESPACE +} // namespace opentracing diff --git a/mocktracer/src/mock_span.cpp b/mocktracer/src/mock_span.cpp new file mode 100644 index 0000000..b312bd8 --- /dev/null +++ b/mocktracer/src/mock_span.cpp @@ -0,0 +1,206 @@ +#include "mock_span.h" +#include <cstdio> +#include <random> + +namespace opentracing { +BEGIN_OPENTRACING_ABI_NAMESPACE +namespace mocktracer { + +static uint64_t GenerateId() { + static thread_local std::mt19937_64 rand_source{std::random_device()()}; + return static_cast<uint64_t>(rand_source()); +} + +static std::tuple<SystemTime, SteadyTime> ComputeStartTimestamps( + const SystemTime& start_system_timestamp, + const SteadyTime& start_steady_timestamp) { + // If neither the system nor steady timestamps are set, get the tme from the + // respective clocks; otherwise, use the set timestamp to initialize the + // other. + if (start_system_timestamp == SystemTime() && + start_steady_timestamp == SteadyTime()) { + return std::tuple<SystemTime, SteadyTime>{SystemClock::now(), + SteadyClock::now()}; + } + if (start_system_timestamp == SystemTime()) { + return std::tuple<SystemTime, SteadyTime>{ + opentracing::convert_time_point<SystemClock>(start_steady_timestamp), + start_steady_timestamp}; + } + if (start_steady_timestamp == SteadyTime()) { + return std::tuple<SystemTime, SteadyTime>{ + start_system_timestamp, + opentracing::convert_time_point<SteadyClock>(start_system_timestamp)}; + } + return std::tuple<SystemTime, SteadyTime>{start_system_timestamp, + start_steady_timestamp}; +} + +static bool SetSpanReference( + const std::pair<SpanReferenceType, const SpanContext*>& reference, + std::map<std::string, std::string>& baggage, + SpanReferenceData& reference_data) { + reference_data.reference_type = reference.first; + if (reference.second == nullptr) { + return false; + } + auto referenced_context = + dynamic_cast<const MockSpanContext*>(reference.second); + if (referenced_context == nullptr) { + return false; + } + reference_data.trace_id = referenced_context->trace_id(); + reference_data.span_id = referenced_context->span_id(); + + referenced_context->ForeachBaggageItem( + [&baggage](const std::string& key, const std::string& value) { + baggage[key] = value; + return true; + }); + + return true; +} + +MockSpan::MockSpan(std::shared_ptr<const Tracer>&& tracer, Recorder* recorder, + string_view operation_name, const StartSpanOptions& options) + : tracer_{std::move(tracer)}, recorder_{recorder} { + data_.operation_name = operation_name; + + // Set start timestamps + std::tie(data_.start_timestamp, start_steady_) = ComputeStartTimestamps( + options.start_system_timestamp, options.start_steady_timestamp); + + // Set references + SpanContextData span_context_data; + for (auto& reference : options.references) { + SpanReferenceData reference_data; + if (!SetSpanReference(reference, span_context_data.baggage, + reference_data)) { + continue; + } + data_.references.push_back(reference_data); + } + + // Set tags + for (auto& tag : options.tags) { + data_.tags[tag.first] = tag.second; + } + + // Set span context + span_context_data.trace_id = + data_.references.empty() ? GenerateId() : data_.references[0].trace_id; + span_context_data.span_id = GenerateId(); + span_context_ = MockSpanContext{std::move(span_context_data)}; +} + +MockSpan::~MockSpan() { + if (!is_finished_) { + Finish(); + } +} + +void MockSpan::FinishWithOptions(const FinishSpanOptions& options) noexcept { + // Ensure the span is only finished once. + if (is_finished_.exchange(true)) { + return; + } + + data_.logs.reserve(data_.logs.size() + options.log_records.size()); + for (auto& log_record : options.log_records) { + data_.logs.push_back(log_record); + } + + auto finish_timestamp = options.finish_steady_timestamp; + if (finish_timestamp == SteadyTime{}) { + finish_timestamp = SteadyClock::now(); + } + + data_.duration = finish_timestamp - start_steady_; + + span_context_.CopyData(data_.span_context); + + if (recorder_ != nullptr) { + recorder_->RecordSpan(std::move(data_)); + } +} + +void MockSpan::SetOperationName(string_view name) noexcept try { + std::lock_guard<std::mutex> lock_guard{mutex_}; + data_.operation_name = name; +} catch (const std::exception& e) { + // Ignore operation + fprintf(stderr, "Failed to set operation name: %s\n", e.what()); +} + +void MockSpan::SetTag(string_view key, + const opentracing::Value& value) noexcept try { + std::lock_guard<std::mutex> lock_guard{mutex_}; + data_.tags[key] = value; +} catch (const std::exception& e) { + // Ignore upon error. + fprintf(stderr, "Failed to set tag: %s\n", e.what()); +} + +void MockSpan::Log( + std::initializer_list<std::pair<string_view, Value>> fields) noexcept { + Log(SystemClock::now(), fields); +} + +void MockSpan::Log( + SystemTime timestamp, + std::initializer_list<std::pair<string_view, Value>> fields) noexcept try { + LogRecord log_record; + log_record.timestamp = timestamp; + log_record.fields.reserve(fields.size()); + for (auto& field : fields) { + log_record.fields.emplace_back(field.first, field.second); + } + std::lock_guard<std::mutex> lock_guard{mutex_}; + data_.logs.emplace_back(std::move(log_record)); +} catch (const std::exception& e) { + // Drop log record upon error. + fprintf(stderr, "Failed to log: %s\n", e.what()); +} + +void MockSpan::Log( + SystemTime timestamp, + const std::vector<std::pair<string_view, Value>>& fields) noexcept try { + LogRecord log_record; + log_record.timestamp = timestamp; + log_record.fields.reserve(fields.size()); + for (auto& field : fields) { + log_record.fields.emplace_back(field.first, field.second); + } + std::lock_guard<std::mutex> lock_guard{mutex_}; + data_.logs.emplace_back(std::move(log_record)); +} catch (const std::exception& e) { + // Drop log record upon error. + fprintf(stderr, "Failed to log: %s\n", e.what()); +} + +void MockSpan::SetBaggageItem(string_view restricted_key, + string_view value) noexcept try { + std::lock_guard<std::mutex> lock_guard{span_context_.baggage_mutex_}; + span_context_.data_.baggage.emplace(restricted_key, value); +} catch (const std::exception& e) { + // Drop baggage item upon error. + fprintf(stderr, "Failed to set baggage item: %s\n", e.what()); +} + +std::string MockSpan::BaggageItem(string_view restricted_key) const + noexcept try { + std::lock_guard<std::mutex> lock_guard{span_context_.baggage_mutex_}; + auto lookup = span_context_.data_.baggage.find(restricted_key); + if (lookup != span_context_.data_.baggage.end()) { + return lookup->second; + } + return {}; +} catch (const std::exception& e) { + // Return empty string upon error. + fprintf(stderr, "Failed to retrieve baggage item: %s\n", e.what()); + return {}; +} + +} // namespace mocktracer +END_OPENTRACING_ABI_NAMESPACE +} // namespace opentracing diff --git a/mocktracer/src/mock_span.h b/mocktracer/src/mock_span.h new file mode 100644 index 0000000..bf8ba05 --- /dev/null +++ b/mocktracer/src/mock_span.h @@ -0,0 +1,66 @@ +#ifndef OPENTRACING_MOCKTRACER_SPAN_H +#define OPENTRACING_MOCKTRACER_SPAN_H + +#include <opentracing/mocktracer/tracer.h> +#include <atomic> +#include <mutex> +#include "mock_span_context.h" + +namespace opentracing { +BEGIN_OPENTRACING_ABI_NAMESPACE +namespace mocktracer { + +class MockSpan : public Span { + public: + MockSpan(std::shared_ptr<const Tracer>&& tracer, Recorder* recorder, + string_view operation_name, const StartSpanOptions& options); + + ~MockSpan() override; + + void FinishWithOptions(const FinishSpanOptions& options) noexcept override; + + void SetOperationName(string_view name) noexcept override; + + void SetTag(string_view key, + const opentracing::Value& value) noexcept override; + + void Log(std::initializer_list<std::pair<string_view, Value>> + fields) noexcept override; + + void Log(SystemTime timestamp, + std::initializer_list<std::pair<string_view, Value>> + fields) noexcept override; + + void Log(SystemTime timestamp, + const std::vector<std::pair<string_view, Value>>& + fields) noexcept override; + + void SetBaggageItem(string_view restricted_key, + string_view value) noexcept override; + + std::string BaggageItem(string_view restricted_key) const noexcept override; + + const SpanContext& context() const noexcept override { return span_context_; } + + const opentracing::Tracer& tracer() const noexcept override { + return *tracer_; + } + + private: + std::shared_ptr<const Tracer> tracer_; + Recorder* recorder_; + MockSpanContext span_context_; + SteadyTime start_steady_; + + std::atomic<bool> is_finished_{false}; + + // Mutex protects data_ + std::mutex mutex_; + SpanData data_; +}; + +} // namespace mocktracer +END_OPENTRACING_ABI_NAMESPACE +} // namespace opentracing + +#endif // OPENTRACING_MOCKTRACER_SPAN_H diff --git a/mocktracer/src/mock_span_context.cpp b/mocktracer/src/mock_span_context.cpp new file mode 100644 index 0000000..ac6eb2b --- /dev/null +++ b/mocktracer/src/mock_span_context.cpp @@ -0,0 +1,40 @@ +#include "mock_span_context.h" + +namespace opentracing { +BEGIN_OPENTRACING_ABI_NAMESPACE +namespace mocktracer { +MockSpanContext& MockSpanContext::operator=(MockSpanContext&& other) noexcept { + data_.trace_id = other.data_.trace_id; + data_.span_id = other.data_.span_id; + data_.baggage = std::move(other.data_.baggage); + return *this; +} + +void MockSpanContext::ForeachBaggageItem( + std::function<bool(const std::string& key, const std::string& value)> f) + const { + std::lock_guard<std::mutex> lock_guard{baggage_mutex_}; + for (const auto& baggage_item : data_.baggage) { + if (!f(baggage_item.first, baggage_item.second)) { + return; + } + } +} + +void MockSpanContext::CopyData(SpanContextData& data) const { + data.trace_id = data_.trace_id; + data.span_id = data_.span_id; + std::lock_guard<std::mutex> lock_guard{baggage_mutex_}; + data.baggage = data_.baggage; +} + +std::unique_ptr<SpanContext> MockSpanContext::Clone() const noexcept try { + auto result = std::unique_ptr<MockSpanContext>{new MockSpanContext{}}; + CopyData(result->data_); + return std::unique_ptr<SpanContext>{result.release()}; +} catch (const std::exception& /*e*/) { + return nullptr; +} +} // namespace mocktracer +END_OPENTRACING_ABI_NAMESPACE +} // namespace opentracing diff --git a/mocktracer/src/mock_span_context.h b/mocktracer/src/mock_span_context.h new file mode 100644 index 0000000..ed156da --- /dev/null +++ b/mocktracer/src/mock_span_context.h @@ -0,0 +1,79 @@ +#ifndef OPENTRACING_MOCKTRACER_SPAN_CONTEXT_H +#define OPENTRACING_MOCKTRACER_SPAN_CONTEXT_H + +#include <opentracing/mocktracer/tracer.h> +#include <exception> +#include <mutex> +#include <string> +#include "propagation.h" + +namespace opentracing { +BEGIN_OPENTRACING_ABI_NAMESPACE +namespace mocktracer { + +class MockSpan; + +class MockSpanContext : public SpanContext { + public: + MockSpanContext() = default; + + MockSpanContext(SpanContextData&& data) noexcept : data_(std::move(data)) {} + + MockSpanContext(const MockSpanContext&) = delete; + MockSpanContext(MockSpanContext&&) = delete; + + ~MockSpanContext() override = default; + + MockSpanContext& operator=(const MockSpanContext&) = delete; + MockSpanContext& operator=(MockSpanContext&& other) noexcept; + + void ForeachBaggageItem( + std::function<bool(const std::string& key, const std::string& value)> f) + const override; + + std::string ToTraceID() const noexcept override try { + return std::to_string(data_.trace_id); + } catch (const std::exception& /*e*/) { + return {}; + } + + std::string ToSpanID() const noexcept override try { + return std::to_string(data_.span_id); + } catch (const std::exception& /*e*/) { + return {}; + } + + uint64_t trace_id() const noexcept { return data_.trace_id; } + + uint64_t span_id() const noexcept { return data_.span_id; } + + void CopyData(SpanContextData& data) const; + + template <class Carrier> + expected<void> Inject(const PropagationOptions& propagation_options, + Carrier& writer) const { + std::lock_guard<std::mutex> lock_guard{baggage_mutex_}; + return InjectSpanContext(propagation_options, writer, data_); + } + + template <class Carrier> + expected<bool> Extract(const PropagationOptions& propagation_options, + Carrier& reader) { + std::lock_guard<std::mutex> lock_guard{baggage_mutex_}; + return ExtractSpanContext(propagation_options, reader, data_); + } + + std::unique_ptr<SpanContext> Clone() const noexcept override; + + private: + friend MockSpan; + + mutable std::mutex baggage_mutex_; + SpanContextData data_; +}; + +} // namespace mocktracer +END_OPENTRACING_ABI_NAMESPACE +} // namespace opentracing + +#endif // OPENTRACING_MOCKTRACER_SPAN_CONTEXT_H diff --git a/mocktracer/src/propagation.cpp b/mocktracer/src/propagation.cpp new file mode 100644 index 0000000..d546877 --- /dev/null +++ b/mocktracer/src/propagation.cpp @@ -0,0 +1,214 @@ +#include "propagation.h" +#include <algorithm> +#include <cctype> +#include <functional> +#include <iostream> +#include <sstream> +#include "base64.h" +#include "utility.h" + +namespace opentracing { +BEGIN_OPENTRACING_ABI_NAMESPACE +namespace mocktracer { +static void WriteString(std::ostream& ostream, const std::string& s) { + const uint32_t size = static_cast<uint32_t>(s.size()); + ostream.write(reinterpret_cast<const char*>(&size), sizeof(size)); + ostream.write(s.data(), size); +} + +static void ReadString(std::istream& istream, std::string& s) { + uint32_t size = 0; + istream.read(reinterpret_cast<char*>(&size), sizeof(size)); + s.resize(size); + istream.read(&s[0], size); +} + +expected<void> InjectSpanContext( + const PropagationOptions& /*propagation_options*/, std::ostream& carrier, + const SpanContextData& span_context_data) { + auto trace_id = SwapEndianIfBig(span_context_data.trace_id); + carrier.write(reinterpret_cast<const char*>(&trace_id), sizeof(trace_id)); + auto span_id = SwapEndianIfBig(span_context_data.span_id); + carrier.write(reinterpret_cast<const char*>(&span_id), sizeof(span_id)); + + const uint32_t num_baggage = + SwapEndianIfBig(static_cast<uint32_t>(span_context_data.baggage.size())); + carrier.write(reinterpret_cast<const char*>(&num_baggage), + sizeof(num_baggage)); + for (auto& baggage_item : span_context_data.baggage) { + WriteString(carrier, baggage_item.first); + WriteString(carrier, baggage_item.second); + } + + // Flush so that when we call carrier.good(), we'll get an accurate view of + // the error state. + carrier.flush(); + if (!carrier.good()) { + return opentracing::make_unexpected( + std::make_error_code(std::errc::io_error)); + } + + return {}; +} + +expected<bool> ExtractSpanContext( + const PropagationOptions& /*propagation_options*/, std::istream& carrier, + SpanContextData& span_context_data) try { + // istream::peek returns EOF if it's in an error state, so check for an error + // state first before checking for an empty stream. + if (!carrier.good()) { + return opentracing::make_unexpected( + std::make_error_code(std::errc::io_error)); + } + + // Check for the case when no span is encoded. + if (carrier.peek() == EOF) { + return false; + } + carrier.read(reinterpret_cast<char*>(&span_context_data.trace_id), + sizeof(span_context_data.trace_id)); + span_context_data.trace_id = SwapEndianIfBig(span_context_data.trace_id); + carrier.read(reinterpret_cast<char*>(&span_context_data.span_id), + sizeof(span_context_data.span_id)); + span_context_data.span_id = SwapEndianIfBig(span_context_data.span_id); + uint32_t num_baggage = 0; + carrier.read(reinterpret_cast<char*>(&num_baggage), sizeof(num_baggage)); + num_baggage = SwapEndianIfBig(num_baggage); + std::string baggage_key, baggage_value; + for (int i = 0; i < static_cast<int>(num_baggage); ++i) { + ReadString(carrier, baggage_key); + ReadString(carrier, baggage_value); + span_context_data.baggage[baggage_key] = baggage_value; + if (!carrier.good()) { + return opentracing::make_unexpected( + std::make_error_code(std::errc::io_error)); + } + } + + return true; +} catch (const std::bad_alloc&) { + return opentracing::make_unexpected( + std::make_error_code(std::errc::not_enough_memory)); +} + +expected<void> InjectSpanContext(const PropagationOptions& propagation_options, + const TextMapWriter& carrier, + const SpanContextData& span_context_data) { + std::ostringstream ostream; + auto result = + InjectSpanContext(propagation_options, ostream, span_context_data); + if (!result) { + return result; + } + std::string context_value; + try { + auto binary_encoding = ostream.str(); + context_value = + Base64::encode(binary_encoding.data(), binary_encoding.size()); + } catch (const std::bad_alloc&) { + return opentracing::make_unexpected( + std::make_error_code(std::errc::not_enough_memory)); + } + + result = carrier.Set(propagation_options.propagation_key, context_value); + if (!result) { + return result; + } + return {}; +} + +template <class KeyCompare> +static opentracing::expected<opentracing::string_view> LookupKey( + const opentracing::TextMapReader& carrier, opentracing::string_view key, + KeyCompare key_compare) { + // First try carrier.LookupKey since that can potentially be the fastest + // approach. + auto result = carrier.LookupKey(key); + if (result || + !are_errors_equal(result.error(), + opentracing::lookup_key_not_supported_error)) { + return result; + } + + // Fall back to iterating through all of the keys. + result = opentracing::make_unexpected(opentracing::key_not_found_error); + auto was_successful = carrier.ForeachKey( + [&](opentracing::string_view carrier_key, + opentracing::string_view value) -> opentracing::expected<void> { + if (!key_compare(carrier_key, key)) { + return {}; + } + result = value; + + // Found key, so bail out of the loop with a success error code. + return opentracing::make_unexpected(std::error_code{}); + }); + if (!was_successful && was_successful.error() != std::error_code{}) { + return opentracing::make_unexpected(was_successful.error()); + } + return result; +} + +template <class KeyCompare> +static opentracing::expected<bool> ExtractSpanContext( + const PropagationOptions& propagation_options, + const opentracing::TextMapReader& carrier, + SpanContextData& span_context_data, KeyCompare key_compare) { + auto value_maybe = + LookupKey(carrier, propagation_options.propagation_key, key_compare); + if (!value_maybe) { + if (are_errors_equal(value_maybe.error(), + opentracing::key_not_found_error)) { + return false; + } else { + return opentracing::make_unexpected(value_maybe.error()); + } + } + auto value = *value_maybe; + std::string base64_decoding; + try { + base64_decoding = Base64::decode(value.data(), value.size()); + } catch (const std::bad_alloc&) { + return opentracing::make_unexpected( + std::make_error_code(std::errc::not_enough_memory)); + } + if (base64_decoding.empty()) { + return opentracing::make_unexpected( + opentracing::span_context_corrupted_error); + } + std::istringstream istream{base64_decoding}; + return ExtractSpanContext(propagation_options, istream, span_context_data); +} + +expected<bool> ExtractSpanContext(const PropagationOptions& propagation_options, + const TextMapReader& carrier, + SpanContextData& span_context_data) { + return ExtractSpanContext(propagation_options, carrier, span_context_data, + std::equal_to<string_view>{}); +} + +expected<void> InjectSpanContext(const PropagationOptions& propagation_options, + const HTTPHeadersWriter& carrier, + const SpanContextData& span_context_data) { + return InjectSpanContext(propagation_options, + static_cast<const TextMapWriter&>(carrier), + span_context_data); +} + +expected<bool> ExtractSpanContext(const PropagationOptions& propagation_options, + const HTTPHeadersReader& carrier, + SpanContextData& span_context_data) { + auto iequals = [](opentracing::string_view lhs, + opentracing::string_view rhs) { + return lhs.length() == rhs.length() && + std::equal(std::begin(lhs), std::end(lhs), std::begin(rhs), + [](char a, char b) { + return std::tolower(a) == std::tolower(b); + }); + }; + return ExtractSpanContext(propagation_options, carrier, span_context_data, + iequals); +} +} // namespace mocktracer +END_OPENTRACING_ABI_NAMESPACE +} // namespace opentracing diff --git a/mocktracer/src/propagation.h b/mocktracer/src/propagation.h new file mode 100644 index 0000000..6dced93 --- /dev/null +++ b/mocktracer/src/propagation.h @@ -0,0 +1,39 @@ +#ifndef OPENTRACING_MOCKTRACER_PROPAGATION_H +#define OPENTRACING_MOCKTRACER_PROPAGATION_H + +#include <opentracing/mocktracer/recorder.h> +#include <opentracing/mocktracer/tracer.h> +#include <opentracing/propagation.h> + +namespace opentracing { +BEGIN_OPENTRACING_ABI_NAMESPACE +namespace mocktracer { + +expected<void> InjectSpanContext(const PropagationOptions& propagation_options, + std::ostream& carrier, + const SpanContextData& span_context_data); + +expected<bool> ExtractSpanContext(const PropagationOptions& propagation_options, + std::istream& carrier, + SpanContextData& span_context_data); + +expected<void> InjectSpanContext(const PropagationOptions& propagation_options, + const TextMapWriter& carrier, + const SpanContextData& span_context_data); + +expected<bool> ExtractSpanContext(const PropagationOptions& propagation_options, + const TextMapReader& carrier, + SpanContextData& span_context_data); + +expected<void> InjectSpanContext(const PropagationOptions& propagation_options, + const HTTPHeadersWriter& carrier, + const SpanContextData& span_context_data); + +expected<bool> ExtractSpanContext(const PropagationOptions& propagation_options, + const HTTPHeadersReader& carrier, + SpanContextData& span_context_data); +} // namespace mocktracer +END_OPENTRACING_ABI_NAMESPACE +} // namespace opentracing + +#endif // OPENTRACING_MOCKTRACER_PROPAGATION_H diff --git a/mocktracer/src/tracer.cpp b/mocktracer/src/tracer.cpp new file mode 100644 index 0000000..e5f9127 --- /dev/null +++ b/mocktracer/src/tracer.cpp @@ -0,0 +1,109 @@ +#include <opentracing/mocktracer/tracer.h> +#include <cstdio> +#include <exception> + +#include "mock_span.h" +#include "mock_span_context.h" +#include "propagation.h" + +#include <opentracing/dynamic_load.h> +#include <opentracing/mocktracer/tracer_factory.h> +#include <cstdio> +#include <cstring> +#include <exception> + +namespace opentracing { +BEGIN_OPENTRACING_ABI_NAMESPACE +namespace mocktracer { + +template <class Carrier> +static expected<void> InjectImpl(const PropagationOptions& propagation_options, + const opentracing::SpanContext& span_context, + Carrier& writer) { + if (propagation_options.inject_error_code.value() != 0) { + return opentracing::make_unexpected(propagation_options.inject_error_code); + } + auto mock_span_context = dynamic_cast<const MockSpanContext*>(&span_context); + if (mock_span_context == nullptr) { + return opentracing::make_unexpected( + opentracing::invalid_span_context_error); + } + return mock_span_context->Inject(propagation_options, writer); +} + +template <class Carrier> +opentracing::expected<std::unique_ptr<opentracing::SpanContext>> ExtractImpl( + const PropagationOptions& propagation_options, Carrier& reader) { + if (propagation_options.extract_error_code.value() != 0) { + return opentracing::make_unexpected(propagation_options.extract_error_code); + } + MockSpanContext* mock_span_context; + try { + mock_span_context = new MockSpanContext{}; + } catch (const std::bad_alloc&) { + return opentracing::make_unexpected( + make_error_code(std::errc::not_enough_memory)); + } + std::unique_ptr<opentracing::SpanContext> span_context(mock_span_context); + auto result = mock_span_context->Extract(propagation_options, reader); + if (!result) { + return opentracing::make_unexpected(result.error()); + } + if (!*result) { + span_context.reset(); + } + return std::move(span_context); +} + +MockTracer::MockTracer(MockTracerOptions&& options) + : recorder_{std::move(options.recorder)}, + propagation_options_{std::move(options.propagation_options)} {} + +std::unique_ptr<Span> MockTracer::StartSpanWithOptions( + string_view operation_name, const StartSpanOptions& options) const + noexcept try { + return std::unique_ptr<Span>{new MockSpan{shared_from_this(), recorder_.get(), + operation_name, options}}; +} catch (const std::exception& e) { + fprintf(stderr, "Failed to start span: %s\n", e.what()); + return nullptr; +} + +void MockTracer::Close() noexcept { + if (recorder_ != nullptr) { + recorder_->Close(); + } +} + +expected<void> MockTracer::Inject(const SpanContext& sc, + std::ostream& writer) const { + return InjectImpl(propagation_options_, sc, writer); +} + +expected<void> MockTracer::Inject(const SpanContext& sc, + const TextMapWriter& writer) const { + return InjectImpl(propagation_options_, sc, writer); +} + +expected<void> MockTracer::Inject(const SpanContext& sc, + const HTTPHeadersWriter& writer) const { + return InjectImpl(propagation_options_, sc, writer); +} + +expected<std::unique_ptr<SpanContext>> MockTracer::Extract( + std::istream& reader) const { + return ExtractImpl(propagation_options_, reader); +} + +expected<std::unique_ptr<SpanContext>> MockTracer::Extract( + const TextMapReader& reader) const { + return ExtractImpl(propagation_options_, reader); +} + +expected<std::unique_ptr<SpanContext>> MockTracer::Extract( + const HTTPHeadersReader& reader) const { + return ExtractImpl(propagation_options_, reader); +} +} // namespace mocktracer +END_OPENTRACING_ABI_NAMESPACE +} // namespace opentracing diff --git a/mocktracer/src/tracer_factory.cpp b/mocktracer/src/tracer_factory.cpp new file mode 100644 index 0000000..ebfbe60 --- /dev/null +++ b/mocktracer/src/tracer_factory.cpp @@ -0,0 +1,139 @@ +#include <opentracing/mocktracer/json_recorder.h> +#include <opentracing/mocktracer/tracer.h> +#include <opentracing/mocktracer/tracer_factory.h> +#include <cctype> +#include <cerrno> +#include <cstring> +#include <fstream> +#include <stdexcept> +#include <string> + +namespace opentracing { +BEGIN_OPENTRACING_ABI_NAMESPACE +namespace mocktracer { + +namespace { +struct InvalidConfigurationError : public std::exception { + public: + InvalidConfigurationError(const char* position, std::string&& message) + : position_{position}, message_{std::move(message)} {} + + const char* what() const noexcept override { return message_.c_str(); } + + const char* position() const { return position_; } + + private: + const char* position_; + std::string message_; +}; +} // namespace + +static void Consume(const char*& i, const char* last, string_view s) { + if (static_cast<std::size_t>(std::distance(i, last)) < s.size()) { + throw InvalidConfigurationError{i, + std::string{"expected "} + std::string{s}}; + } + + for (size_t index = 0; index < s.size(); ++index) { + if (*i++ != s[index]) { + throw InvalidConfigurationError{ + i, std::string{"expected "} + + std::string{s.data() + index, s.data() + s.size()}}; + } + } +} + +static void ConsumeWhitespace(const char*& i, const char* last) { + for (; i != last; ++i) { + if (!std::isspace(*i)) { + return; + } + } +} + +static void ConsumeToken(const char*& i, const char* last, string_view token) { + ConsumeWhitespace(i, last); + Consume(i, last, token); +} + +static std::string ParseFilename(const char*& i, const char* last) { + ConsumeToken(i, last, "\""); + std::string result; + while (i != last) { + if (*i == '\"') { + ++i; + return result; + } + if (*i == '\\') { + throw InvalidConfigurationError{ + i, "escaped characters are not supported in filename"}; + } + if (std::isprint(*i)) { + result.push_back(*i); + } else { + throw InvalidConfigurationError{i, "invalid character"}; + } + ++i; + } + + throw InvalidConfigurationError{i, R"(no matching ")"}; +} + +static std::string ParseConfiguration(const char* i, const char* last) { + ConsumeToken(i, last, "{"); + ConsumeToken(i, last, R"("output_file")"); + ConsumeToken(i, last, ":"); + auto filename = ParseFilename(i, last); + ConsumeToken(i, last, "}"); + ConsumeWhitespace(i, last); + if (i != last) { + throw InvalidConfigurationError{i, "expected EOF"}; + } + + return filename; +} + +struct MockTracerConfiguration { + std::string output_file; +}; + +expected<std::shared_ptr<Tracer>> MockTracerFactory::MakeTracer( + const char* configuration, std::string& error_message) const noexcept try { + MockTracerConfiguration tracer_configuration; + if (configuration == nullptr) { + error_message = "configuration must not be null"; + return make_unexpected(invalid_configuration_error); + } + try { + tracer_configuration.output_file = ParseConfiguration( + configuration, configuration + std::strlen(configuration)); + } catch (const InvalidConfigurationError& e) { + error_message = std::string{"Error parsing configuration at position "} + + std::to_string(std::distance(configuration, e.position())) + + ": " + e.what(); + return make_unexpected(invalid_configuration_error); + } + + errno = 0; + std::unique_ptr<std::ostream> ostream{ + new std::ofstream{tracer_configuration.output_file}}; + if (!ostream->good()) { + error_message = "failed to open file `"; + error_message += tracer_configuration.output_file + "` ("; + error_message += std::strerror(errno); + error_message += ")"; + return make_unexpected(invalid_configuration_error); + } + + MockTracerOptions tracer_options; + tracer_options.recorder = + std::unique_ptr<Recorder>{new JsonRecorder{std::move(ostream)}}; + + return std::shared_ptr<Tracer>{new MockTracer{std::move(tracer_options)}}; +} catch (const std::bad_alloc&) { + return make_unexpected(std::make_error_code(std::errc::not_enough_memory)); +} + +} // namespace mocktracer +END_OPENTRACING_ABI_NAMESPACE +} // namespace opentracing diff --git a/mocktracer/src/utility.cpp b/mocktracer/src/utility.cpp new file mode 100644 index 0000000..4a08d82 --- /dev/null +++ b/mocktracer/src/utility.cpp @@ -0,0 +1,57 @@ +#include "utility.h" +#include <climits> + +namespace opentracing { +BEGIN_OPENTRACING_ABI_NAMESPACE +namespace mocktracer { + +// Converts swaps the endianness of a number. +// +// Taken from https://stackoverflow.com/a/105339/4447365 +template <typename T> +static T SwapEndian(T u) { + static_assert(CHAR_BIT == 8, "CHAR_BIT != 8"); + + union { + T u; + unsigned char u8[sizeof(T)]; + } source, dest; + + source.u = u; + + for (size_t k = 0; k < sizeof(T); k++) + dest.u8[k] = source.u8[sizeof(T) - k - 1]; + + return dest.u; +} + +// Determines whether the architecture is big endian. +// +// Taken from https://stackoverflow.com/a/1001373/4447365 +static bool IsBigEndian() { + union { + uint32_t i; + char c[4]; + } bint = {0x01020304}; + + return bint.c[0] == 1; +} + +uint64_t SwapEndianIfBig(uint64_t x) { + if (IsBigEndian()) { + return SwapEndian(x); + } else { + return x; + } +} + +uint32_t SwapEndianIfBig(uint32_t x) { + if (IsBigEndian()) { + return SwapEndian(x); + } else { + return x; + } +} +} // namespace mocktracer +END_OPENTRACING_ABI_NAMESPACE +} // namespace opentracing diff --git a/mocktracer/src/utility.h b/mocktracer/src/utility.h new file mode 100644 index 0000000..f7cff91 --- /dev/null +++ b/mocktracer/src/utility.h @@ -0,0 +1,17 @@ +#ifndef OPENTRACING_MOCKTRACER_UTILITY_H +#define OPENTRACING_MOCKTRACER_UTILITY_H + +#include <opentracing/mocktracer/tracer.h> +#include <cstdint> + +namespace opentracing { +BEGIN_OPENTRACING_ABI_NAMESPACE +namespace mocktracer { +// If the native architecture is big endian, swaps the endianness of x +uint64_t SwapEndianIfBig(uint64_t x); +uint32_t SwapEndianIfBig(uint32_t x); +} // namespace mocktracer +END_OPENTRACING_ABI_NAMESPACE +} // namespace opentracing + +#endif // OPENTRACING_MOCKTRACER_UTILITY_H |