diff --git a/include/envoy/runtime/runtime.h b/include/envoy/runtime/runtime.h index 2051897a604ee..0eec82f5830d3 100644 --- a/include/envoy/runtime/runtime.h +++ b/include/envoy/runtime/runtime.h @@ -27,6 +27,8 @@ class RandomGenerator { virtual std::string uuid() PURE; }; +typedef std::unique_ptr RandomGeneratorPtr; + /** * A snapshot of runtime data. */ diff --git a/source/common/common/hex.cc b/source/common/common/hex.cc index 0cf510c56e43c..e615f711105a7 100644 --- a/source/common/common/hex.cc +++ b/source/common/common/hex.cc @@ -1,5 +1,6 @@ #include "common/common/hex.h" +#include #include #include #include @@ -43,3 +44,19 @@ std::vector Hex::decode(const std::string& hex_string) { return segment; } + +std::string Hex::uint64ToHex(uint64_t value) { + std::array data; + + // This is explicitly done for performance reasons + data[7] = (value & 0x00000000000000FF); + data[6] = (value & 0x000000000000FF00) >> 8; + data[5] = (value & 0x0000000000FF0000) >> 16; + data[4] = (value & 0x00000000FF000000) >> 24; + data[3] = (value & 0x000000FF00000000) >> 32; + data[2] = (value & 0x0000FF0000000000) >> 40; + data[1] = (value & 0x00FF000000000000) >> 48; + data[0] = (value & 0xFF00000000000000) >> 56; + + return encode(&data[0], data.size()); +} diff --git a/source/common/common/hex.h b/source/common/common/hex.h index 84d90b02fb854..75d42ac92f99e 100644 --- a/source/common/common/hex.h +++ b/source/common/common/hex.h @@ -32,4 +32,10 @@ class Hex final { * @return binary data */ static std::vector decode(const std::string& input); + + /** + * Converts the given 64-bit integer into a hexadecimal string. + * @param value The integer to be converted. + */ + static std::string uint64ToHex(uint64_t value); }; diff --git a/source/common/tracing/zipkin/BUILD b/source/common/tracing/zipkin/BUILD new file mode 100644 index 0000000000000..d52284f57ba25 --- /dev/null +++ b/source/common/tracing/zipkin/BUILD @@ -0,0 +1,36 @@ +licenses(["notice"]) # Apache 2 + +package(default_visibility = ["//visibility:public"]) + +load("//bazel:envoy_build_system.bzl", "envoy_cc_library") + +envoy_cc_library( + name = "zipkin_lib", + srcs = [ + "span_buffer.cc", + "span_context.cc", + "tracer.cc", + "util.cc", + "zipkin_core_types.cc", + ], + hdrs = [ + "span_buffer.h", + "span_context.h", + "tracer.h", + "tracer_interface.h", + "util.h", + "zipkin_core_constants.h", + "zipkin_core_types.h", + "zipkin_json_field_names.h", + ], + external_deps = ["rapidjson"], + deps = [ + "//include/envoy/common:optional", + "//include/envoy/common:time_interface", + "//include/envoy/runtime:runtime_interface", + "//source/common/common:hex_lib", + "//source/common/common:singleton", + "//source/common/common:utility_lib", + "//source/common/network:address_lib", + ], +) diff --git a/source/common/tracing/zipkin/span_buffer.cc b/source/common/tracing/zipkin/span_buffer.cc new file mode 100644 index 0000000000000..05223c53f0d0f --- /dev/null +++ b/source/common/tracing/zipkin/span_buffer.cc @@ -0,0 +1,30 @@ +#include "common/tracing/zipkin/span_buffer.h" + +namespace Zipkin { + +bool SpanBuffer::addSpan(SpanPtr&& span) { + if (span_buffer_.size() == span_buffer_.capacity()) { + // Buffer full + return false; + } + span_buffer_.push_back(std::move(span)); + + return true; +} + +std::string SpanBuffer::toStringifiedJsonArray() { + std::string stringified_json_array = "["; + + if (pendingSpans()) { + stringified_json_array += span_buffer_[0]->toJson(); + const uint64_t size = span_buffer_.size(); + for (uint64_t i = 1; i < size; i++) { + stringified_json_array += ","; + stringified_json_array += span_buffer_[i]->toJson(); + } + } + stringified_json_array += "]"; + + return stringified_json_array; +} +} // Zipkin diff --git a/source/common/tracing/zipkin/span_buffer.h b/source/common/tracing/zipkin/span_buffer.h new file mode 100644 index 0000000000000..324965c75aeb9 --- /dev/null +++ b/source/common/tracing/zipkin/span_buffer.h @@ -0,0 +1,63 @@ +#pragma once + +#include "common/tracing/zipkin/zipkin_core_types.h" + +namespace Zipkin { + +/** + * This class implements a simple buffer to store Zipkin tracing spans + * prior to flushing them. + */ +class SpanBuffer { +public: + /** + * Constructor that creates an empty buffer. Space needs to be allocated by invoking + * the method allocateBuffer(size). + */ + SpanBuffer() {} + + /** + * Constructor that initializes a buffer with the given size. + * + * @param size The desired buffer size. + */ + SpanBuffer(uint64_t size) { allocateBuffer(size); } + + /** + * Allocates space for an empty buffer or resizes a previously-allocated one. + * + * @param size The desired buffer size. + */ + void allocateBuffer(uint64_t size) { span_buffer_.reserve(size); } + + /** + * Adds the given Zipkin span to the buffer. + * + * @param span The span to be added to the buffer. + * + * @return true if the span was successfully added, or false if the buffer was full. + */ + bool addSpan(SpanPtr&& span); + + /** + * Empties the buffer. This method is supposed to be called when all buffered spans + * have been sent to to the Zipkin service. + */ + void clear() { span_buffer_.clear(); } + + /** + * @return the number of spans currently buffered. + */ + uint64_t pendingSpans() { return span_buffer_.size(); } + + /** + * @return the contents of the buffer as a stringified array of JSONs, where + * each JSON in the array corresponds to one Zipkin span. + */ + std::string toStringifiedJsonArray(); + +private: + // We use a pre-allocated vector to improve performance + std::vector span_buffer_; +}; +} // Zipkin diff --git a/source/common/tracing/zipkin/span_context.cc b/source/common/tracing/zipkin/span_context.cc new file mode 100644 index 0000000000000..a9a2772b8a852 --- /dev/null +++ b/source/common/tracing/zipkin/span_context.cc @@ -0,0 +1,152 @@ +#include "common/tracing/zipkin/span_context.h" + +#include "common/common/utility.h" +#include "common/tracing/zipkin/zipkin_core_constants.h" + +namespace Zipkin { + +namespace { +// The functions below are needed due to the "C++ static initialization order fiasco." + +/** + * @return String that separates the span-context fields in its string-serialized form. + */ +static const std::string& fieldSeparator() { + static const std::string* field_separator = new std::string(";"); + + return *field_separator; +} + +/** + * @return String value corresponding to an empty span context. + */ +static const std::string& unitializedSpanContext() { + static const std::string* unitialized_span_context = + new std::string("0000000000000000" + fieldSeparator() + "0000000000000000" + + fieldSeparator() + "0000000000000000"); + + return *unitialized_span_context; +} + +/** + * @return String with regular expression to match a 16-digit hexadecimal number. + */ +static const std::string& hexDigitGroupRegexStr() { + static const std::string* hex_digit_group_regex_str = new std::string("([0-9,a-z]{16})"); + + return *hex_digit_group_regex_str; +} + +/** + * @return a string with a regular expression to match a valid string-serialized span context. + * + * Note that a function is needed because we cannot concatenate static strings from + * different compilation units at initialization time (the initialization order is not + * guaranteed). In this case, the compilation units are ZipkinCoreConstants and SpanContext. + */ +static const std::string& spanContextRegexStr() { + // ^([0-9,a-z]{16});([0-9,a-z]{16});([0-9,a-z]{16})((;(cs|sr|cr|ss))*)$ + static const std::string* span_context_regex_str = new std::string( + "^" + hexDigitGroupRegexStr() + fieldSeparator() + hexDigitGroupRegexStr() + + fieldSeparator() + hexDigitGroupRegexStr() + "((" + fieldSeparator() + "(" + + ZipkinCoreConstants::get().CLIENT_SEND + "|" + ZipkinCoreConstants::get().SERVER_RECV + "|" + + ZipkinCoreConstants::get().CLIENT_RECV + "|" + ZipkinCoreConstants::get().SERVER_SEND + + "))*)$"); + + return *span_context_regex_str; +} + +/** + * @return a regex to match a valid string-serialization of a span context. + * + * Note that a function is needed because the string used to build the regex + * cannot be initialized statically. + */ +static const std::regex& spanContextRegex() { + static const std::regex* span_context_regex = new std::regex(spanContextRegexStr()); + + return *span_context_regex; +} +} // namespace + +SpanContext::SpanContext(const Span& span) { + trace_id_ = span.traceId(); + id_ = span.id(); + parent_id_ = span.isSetParentId() ? span.parentId() : 0; + + for (const Annotation& annotation : span.annotations()) { + if (annotation.value() == ZipkinCoreConstants::get().CLIENT_RECV) { + annotation_values_.cr_ = true; + } else if (annotation.value() == ZipkinCoreConstants::get().CLIENT_SEND) { + annotation_values_.cs_ = true; + } else if (annotation.value() == ZipkinCoreConstants::get().SERVER_RECV) { + annotation_values_.sr_ = true; + } else if (annotation.value() == ZipkinCoreConstants::get().SERVER_SEND) { + annotation_values_.ss_ = true; + } + } + + is_initialized_ = true; +} + +const std::string SpanContext::serializeToString() { + if (!is_initialized_) { + return unitializedSpanContext(); + } + + std::string result; + result = traceIdAsHexString() + fieldSeparator() + idAsHexString() + fieldSeparator() + + parentIdAsHexString(); + + if (annotation_values_.cr_) { + result += fieldSeparator() + ZipkinCoreConstants::get().CLIENT_RECV; + } + if (annotation_values_.cs_) { + result += fieldSeparator() + ZipkinCoreConstants::get().CLIENT_SEND; + } + if (annotation_values_.sr_) { + result += fieldSeparator() + ZipkinCoreConstants::get().SERVER_RECV; + } + if (annotation_values_.ss_) { + result += fieldSeparator() + ZipkinCoreConstants::get().SERVER_SEND; + } + + return result; +} + +void SpanContext::populateFromString(const std::string& span_context_str) { + std::smatch match; + + trace_id_ = parent_id_ = id_ = 0; + annotation_values_.cs_ = annotation_values_.cr_ = annotation_values_.ss_ = + annotation_values_.sr_ = false; + + if (std::regex_search(span_context_str, match, spanContextRegex())) { + // This is a valid string encoding of the context + trace_id_ = std::stoull(match.str(1), nullptr, 16); + id_ = std::stoull(match.str(2), nullptr, 16); + parent_id_ = std::stoull(match.str(3), nullptr, 16); + + std::string matched_annotations = match.str(4); + if (matched_annotations.size() > 0) { + std::vector annotation_value_strings = + StringUtil::split(matched_annotations, fieldSeparator()); + for (const std::string& annotation_value : annotation_value_strings) { + if (annotation_value == ZipkinCoreConstants::get().CLIENT_RECV) { + annotation_values_.cr_ = true; + } else if (annotation_value == ZipkinCoreConstants::get().CLIENT_SEND) { + annotation_values_.cs_ = true; + } else if (annotation_value == ZipkinCoreConstants::get().SERVER_RECV) { + annotation_values_.sr_ = true; + } else if (annotation_value == ZipkinCoreConstants::get().SERVER_SEND) { + annotation_values_.ss_ = true; + } + } + } + + is_initialized_ = true; + } else { + is_initialized_ = false; + } +} +} // Zipkin diff --git a/source/common/tracing/zipkin/span_context.h b/source/common/tracing/zipkin/span_context.h new file mode 100644 index 0000000000000..10e5e5fdf2de6 --- /dev/null +++ b/source/common/tracing/zipkin/span_context.h @@ -0,0 +1,119 @@ +#pragma once + +#include + +#include "common/tracing/zipkin/util.h" +#include "common/tracing/zipkin/zipkin_core_types.h" + +namespace Zipkin { + +/** + * This struct identifies which Zipkin annotations are present in the + * span context (see SpanContext) + * Each member is a one-bit boolean indicating whether or not the + * corresponding annotation is present. + * + * In particular, the following annotations are tracked by this struct: + * CS: "Client Send" + * CR: "Client Receive" + * SS: "Server Send" + * SR: "Server Receive" + */ +struct AnnotationSet { + AnnotationSet() : cs_(false), cr_(false), ss_(false), sr_(false) {} + bool cs_ : 1; + bool cr_ : 1; + bool ss_ : 1; + bool sr_ : 1; +}; + +/** + * This class represents the context of a Zipkin span. It embodies the following + * span characteristics: trace id, span id, parent id, and basic annotations. + */ +class SpanContext { +public: + /** + * Default constructor. Creates an empty context. + */ + SpanContext() : trace_id_(0), id_(0), parent_id_(0), is_initialized_(false) {} + + /** + * Constructor that creates a context object from the given Zipkin span object. + * + * @param span The Zipkin span used to initialize a SpanContext object. + */ + SpanContext(const Span& span); + + /** + * Serializes the SpanContext object as a string. This encoding of a SpanContext is used + * as the contents of the x-ot-span-context HTTP header, and allows Envoy to track the + * relationships among related Zipkin spans. + * + * @return a string-encoded SpanContext in the following format: + * + * "<16-hex-string trace id>;<16-hex-string span id>;<16-hex-string parent id>; + * + * The annotation list, if present, can contain the strings "cs", "cr", "ss", "sr", depending on + * which annotations are present. The semi-colon character is used as the separator for the + * annotation list. + * + * Example of a returned string corresponding to a span with the SR annotation: + * "25c6f38dd0600e78;56707c7b3e1092af;c49193ea42335d1c;sr" + * + * Example of a returned string corresponding to a span with no annotations: + * "25c6f38dd0600e78;56707c7b3e1092af;c49193ea42335d1c" + */ + const std::string serializeToString(); + + /** + * Initializes a SpanContext object based on the given string. + * + * @param span_context_str The string-encoding of a SpanContext in the same format produced by the + * method serializeToString(). + */ + void populateFromString(const std::string& span_context_str); + + /** + * @return the span id as an integer + */ + uint64_t id() const { return id_; } + + /** + * @return the span id as a 16-character hexadecimal string. + */ + std::string idAsHexString() const { return Hex::uint64ToHex(id_); } + + /** + * @return the span's parent id as an integer. + */ + uint64_t parent_id() const { return parent_id_; } + + /** + * @return the parent id as a 16-character hexadecimal string. + */ + std::string parentIdAsHexString() const { return Hex::uint64ToHex(parent_id_); } + + /** + * @return the trace id as an integer. + */ + uint64_t trace_id() const { return trace_id_; } + + /** + * @return the trace id as a 16-character hexadecimal string. + */ + std::string traceIdAsHexString() const { return Hex::uint64ToHex(trace_id_); } + + /** + * @return a struct indicating which annotations are present in the span. + */ + AnnotationSet annotationSet() const { return annotation_values_; } + +private: + uint64_t trace_id_; + uint64_t id_; + uint64_t parent_id_; + AnnotationSet annotation_values_; + bool is_initialized_; +}; +} // Zipkin diff --git a/source/common/tracing/zipkin/tracer.cc b/source/common/tracing/zipkin/tracer.cc new file mode 100644 index 0000000000000..c6e050a6c02f3 --- /dev/null +++ b/source/common/tracing/zipkin/tracer.cc @@ -0,0 +1,121 @@ +#include "common/tracing/zipkin/tracer.h" + +#include + +#include "common/common/utility.h" +#include "common/tracing/zipkin/util.h" +#include "common/tracing/zipkin/zipkin_core_constants.h" + +namespace Zipkin { + +SpanPtr Tracer::startSpan(const std::string& span_name, MonotonicTime start_time) { + // Build the endpoint + Endpoint ep(service_name_, address_); + + // Build the CS annotation + Annotation cs; + cs.setEndpoint(std::move(ep)); + cs.setValue(ZipkinCoreConstants::get().CLIENT_SEND); + + // Create an all-new span, with no parent id + SpanPtr span_ptr(new Span()); + span_ptr->setName(span_name); + uint64_t random_number = generateRandomNumber(); + span_ptr->setId(random_number); + span_ptr->setTraceId(random_number); + int64_t start_time_micro = + std::chrono::duration_cast(start_time.time_since_epoch()).count(); + span_ptr->setStartTime(start_time_micro); + + // Set the timestamp globally for the span and also for the CS annotation + uint64_t timestamp_micro = + std::chrono::duration_cast( + ProdSystemTimeSource::instance_.currentTime().time_since_epoch()).count(); + cs.setTimestamp(timestamp_micro); + span_ptr->setTimestamp(timestamp_micro); + + // Add CS annotation to the span + span_ptr->addAnnotation(std::move(cs)); + + span_ptr->setTracer(this); + + return span_ptr; +} + +SpanPtr Tracer::startSpan(const std::string& span_name, MonotonicTime start_time, + SpanContext& previous_context) { + SpanPtr span_ptr(new Span()); + Annotation annotation; + uint64_t timestamp_micro; + + timestamp_micro = std::chrono::duration_cast( + ProdSystemTimeSource::instance_.currentTime().time_since_epoch()).count(); + + if (previous_context.annotationSet().sr_ && !previous_context.annotationSet().cs_) { + // We need to create a new span that is a child of the previous span; no shared context + + // Create a new span id + uint64_t random_number = generateRandomNumber(); + span_ptr->setId(random_number); + + span_ptr->setName(span_name); + + // Set the parent id to the id of the previous span + span_ptr->setParentId(previous_context.id()); + + // Set the CS annotation value + annotation.setValue(ZipkinCoreConstants::get().CLIENT_SEND); + + // Set the timestamp globally for the span + span_ptr->setTimestamp(timestamp_micro); + } else if (previous_context.annotationSet().cs_ && !previous_context.annotationSet().sr_) { + // We need to create a new span that will share context with the previous span + + // Initialize the shared context for the new span + span_ptr->setId(previous_context.id()); + if (previous_context.parent_id()) { + span_ptr->setParentId(previous_context.parent_id()); + } + + // Set the SR annotation value + annotation.setValue(ZipkinCoreConstants::get().SERVER_RECV); + } else { + return span_ptr; // return an empty span + } + + // Build the endpoint + Endpoint ep(service_name_, address_); + + // Add the newly-created annotation to the span + annotation.setEndpoint(std::move(ep)); + annotation.setTimestamp(timestamp_micro); + span_ptr->addAnnotation(std::move(annotation)); + + // Keep the same trace id + span_ptr->setTraceId(previous_context.trace_id()); + + int64_t start_time_micro = + std::chrono::duration_cast(start_time.time_since_epoch()).count(); + span_ptr->setStartTime(start_time_micro); + + span_ptr->setTracer(this); + + return span_ptr; +} + +void Tracer::reportSpan(Span&& span) { + if (reporter_) { + reporter_->reportSpan(std::move(span)); + } +} + +void Tracer::setReporter(ReporterPtr reporter) { reporter_ = std::move(reporter); } + +void Tracer::setRandomGenerator(Runtime::RandomGeneratorPtr random_generator) { + random_generator_ = std::move(random_generator); +} + +uint64_t Tracer::generateRandomNumber() { + return random_generator_ ? random_generator_->random() : Util::generateRandom64(); +} +} // Zipkin diff --git a/source/common/tracing/zipkin/tracer.h b/source/common/tracing/zipkin/tracer.h new file mode 100644 index 0000000000000..7cf52fdfdfcb8 --- /dev/null +++ b/source/common/tracing/zipkin/tracer.h @@ -0,0 +1,104 @@ +#pragma once + +#include "envoy/common/pure.h" +#include "envoy/common/time.h" +#include "envoy/runtime/runtime.h" + +#include "common/tracing/zipkin/span_context.h" +#include "common/tracing/zipkin/tracer_interface.h" +#include "common/tracing/zipkin/zipkin_core_types.h" + +namespace Zipkin { + +/** + * Abstract class that delegates to users of the Tracer class the responsibility + * of "reporting" a Zipkin span that has ended its life cycle. "Reporting" can mean that the + * span will be sent to out to Zipkin, or buffered so that it can be sent out later. + */ +class Reporter { +public: + /** + * Destructor. + */ + virtual ~Reporter() {} + + /** + * Method that a concrete Reporter class must implement to handle finished spans. + * For example, a span-buffer management policy could be implemented. + * + * @param span The span that needs action. + */ + virtual void reportSpan(Span&& span) PURE; +}; + +typedef std::unique_ptr ReporterPtr; + +/** + * This class implements the Zipkin tracer. It has methods to create the appropriate Zipkin span + * type, i.e., root span, child span, or shared-context span. + * + * This class allows its users to supply a concrete Reporter class whose reportSpan method + * is called by its own reportSpan method. By doing so, we have cleanly separated the logic + * of dealing with finished spans from the span-creation and tracing logic. + */ +class Tracer : public TracerInterface { +public: + /** + * Constructor. + * + * @param service_name The name of the service where the Tracer is running. This name is + * used in all annotations' endpoints of the spans created by the Tracer. + * @param address Pointer to a network-address object. The IP address and port are used + * in all annotations' endpoints of the spans created by the Tracer. + */ + Tracer(const std::string& service_name, Network::Address::InstanceConstSharedPtr address) + : service_name_(service_name), address_(address), random_generator_(nullptr) {} + + /** + * Creates a "root" Zipkin span. + * + * @param span_name Name of the new span. + * @param start_time The time indicating the beginning of the span. + */ + SpanPtr startSpan(const std::string& span_name, MonotonicTime start_time); + + /** + * Depending on the given context, creates either a "child" or a "shared-context" Zipkin span. + * + * @param span_name Name of the new span. + * @param start_time The time indicating the beginning of the span. + * @param previous_context The context of the span preceding the one to be created. + */ + SpanPtr startSpan(const std::string& span_name, MonotonicTime start_time, + SpanContext& previous_context); + + /** + * TracerInterface::reportSpan. + */ + void reportSpan(Span&& span) override; + + /** + * Associates a Reporter object with this Tracer. + */ + void setReporter(ReporterPtr reporter); + + /** + * Provides a random-number generator to be used by the Tracer. + * If this method is not used, the Tracer will use a default random-number generator. + * + * @param random_generator Random-number generator to be used. + */ + void setRandomGenerator(Runtime::RandomGeneratorPtr random_generator); + +private: + /** + * Uses the default random-number generator if one was not provided by the user + */ + uint64_t generateRandomNumber(); + + const std::string service_name_; + Network::Address::InstanceConstSharedPtr address_; + ReporterPtr reporter_; + Runtime::RandomGeneratorPtr random_generator_; +}; +} // Zipkin diff --git a/source/common/tracing/zipkin/tracer_interface.h b/source/common/tracing/zipkin/tracer_interface.h new file mode 100644 index 0000000000000..62fb779f91cd4 --- /dev/null +++ b/source/common/tracing/zipkin/tracer_interface.h @@ -0,0 +1,30 @@ +#pragma once + +#include "envoy/common/pure.h" + +namespace Zipkin { + +class Span; + +/** + * This interface must be observed by a Zipkin tracer. + */ +class TracerInterface { +public: + /** + * Destructor. + */ + virtual ~TracerInterface() {} + + /** + * A Zipkin tracer must implement this method. Its implementation must perform whatever + * actions are required when the given span is considered finished. An implementation + * will typically buffer the given span so that it can be flushed later. + * + * This method is invoked by the Span object when its finish() method is called. + * + * @param span The span that needs action. + */ + virtual void reportSpan(Span&& span) PURE; +}; +} // Zipkin diff --git a/source/common/tracing/zipkin/util.cc b/source/common/tracing/zipkin/util.cc new file mode 100644 index 0000000000000..538537212e64d --- /dev/null +++ b/source/common/tracing/zipkin/util.cc @@ -0,0 +1,55 @@ +#include "common/tracing/zipkin/util.h" + +#include +#include +#include + +#include "common/common/hex.h" +#include "common/common/utility.h" + +#include "rapidjson/document.h" +#include "rapidjson/stringbuffer.h" +#include "rapidjson/writer.h" + +// TODO(fabolive): Need to add interfaces to the JSON namespace + +namespace Zipkin { + +void Util::mergeJsons(std::string& target, const std::string& source, + const std::string& field_name) { + rapidjson::Document target_doc, source_doc; + target_doc.Parse(target.c_str()); + source_doc.Parse(source.c_str()); + + target_doc.AddMember(rapidjson::StringRef(field_name.c_str()), source_doc, + target_doc.GetAllocator()); + + rapidjson::StringBuffer sb; + rapidjson::Writer w(sb); + target_doc.Accept(w); + target = sb.GetString(); +} + +void Util::addArrayToJson(std::string& target, const std::vector& json_array, + const std::string& field_name) { + std::string stringified_json_array = "["; + + if (json_array.size() > 0) { + stringified_json_array += json_array[0]; + for (auto it = json_array.begin() + 1; it != json_array.end(); it++) { + stringified_json_array += ","; + stringified_json_array += *it; + } + } + stringified_json_array += "]"; + + mergeJsons(target, stringified_json_array, field_name); +} + +uint64_t Util::generateRandom64() { + uint64_t seed = std::chrono::duration_cast( + ProdSystemTimeSource::instance_.currentTime().time_since_epoch()).count(); + std::mt19937_64 rand_64(seed); + return rand_64(); +} +} // Zipkin diff --git a/source/common/tracing/zipkin/util.h b/source/common/tracing/zipkin/util.h new file mode 100644 index 0000000000000..919de8cc721bf --- /dev/null +++ b/source/common/tracing/zipkin/util.h @@ -0,0 +1,47 @@ +#pragma once + +#include +#include + +namespace Zipkin { + +/** + * Utility class with a few convenient methods + */ +class Util { +public: + // ==== + // Stringified-JSON manipulation + // ==== + + /** + * Merges the stringified JSONs given in target and source. + * + * @param target It will contain the resulting stringified JSON. + * @param source The stringified JSON that will be added to target. + * @param field_name The key name (added to target's JSON) whose value will be the JSON in source. + */ + static void mergeJsons(std::string& target, const std::string& source, + const std::string& field_name); + + /** + * Merges a stringified JSON and a vector of stringified JSONs. + * + * @param target It will contain the resulting stringified JSON. + * @param json_array Vector of strings, where each element is a stringified JSON. + * @param field_name The key name (added to target's JSON) whose value will be a stringified. + * JSON array derived from json_array. + */ + static void addArrayToJson(std::string& target, const std::vector& json_array, + const std::string& field_name); + + // ==== + // Miscellaneous + // ==== + + /** + * Returns a randomly-generated 64-bit integer number. + */ + static uint64_t generateRandom64(); +}; +} // Zipkin diff --git a/source/common/tracing/zipkin/zipkin_core_constants.h b/source/common/tracing/zipkin/zipkin_core_constants.h new file mode 100644 index 0000000000000..f932272779e05 --- /dev/null +++ b/source/common/tracing/zipkin/zipkin_core_constants.h @@ -0,0 +1,45 @@ +#pragma once + +#include + +#include "common/common/singleton.h" + +namespace Zipkin { + +class ZipkinCoreConstantValues { +public: + const std::string CLIENT_SEND = "cs"; + const std::string CLIENT_RECV = "cr"; + const std::string SERVER_SEND = "ss"; + const std::string SERVER_RECV = "sr"; + const std::string WIRE_SEND = "ws"; + const std::string WIRE_RECV = "wr"; + const std::string CLIENT_SEND_FRAGMENT = "csf"; + const std::string CLIENT_RECV_FRAGMENT = "crf"; + const std::string SERVER_SEND_FRAGMENT = "ssf"; + const std::string SERVER_RECV_FRAGMENT = "srf"; + + const std::string HTTP_HOST = "http.host"; + const std::string HTTP_METHOD = "http.method"; + const std::string HTTP_PATH = "http.path"; + const std::string HTTP_URL = "http.url"; + const std::string HTTP_STATUS_CODE = "http.status_code"; + const std::string HTTP_REQUEST_SIZE = "http.request.size"; + const std::string HTTP_RESPONSE_SIZE = "http.response.size"; + + const std::string LOCAL_COMPONENT = "lc"; + const std::string ERROR = "error"; + const std::string CLIENT_ADDR = "ca"; + const std::string SERVER_ADDR = "sa"; + + // Zipkin B3 headers + const std::string X_B3_TRACE_ID = "X-B3-TraceId"; + const std::string X_B3_SPAN_ID = "X-B3-SpanId"; + const std::string X_B3_PARENT_SPAN_ID = "X-B3-ParentSpanId"; + const std::string X_B3_SAMPLED = "X-B3-Sampled"; + const std::string X_B3_FLAGS = "X-B3-Flags"; +}; + +typedef ConstSingleton ZipkinCoreConstants; + +} // Zipkin diff --git a/source/common/tracing/zipkin/zipkin_core_types.cc b/source/common/tracing/zipkin/zipkin_core_types.cc new file mode 100644 index 0000000000000..b84497d1d2a39 --- /dev/null +++ b/source/common/tracing/zipkin/zipkin_core_types.cc @@ -0,0 +1,269 @@ +#include "common/tracing/zipkin/zipkin_core_types.h" + +#include "common/common/utility.h" +#include "common/tracing/zipkin/span_context.h" +#include "common/tracing/zipkin/util.h" +#include "common/tracing/zipkin/zipkin_core_constants.h" +#include "common/tracing/zipkin/zipkin_json_field_names.h" + +#include "rapidjson/stringbuffer.h" +#include "rapidjson/writer.h" + +// TODO(fabolive): Need to add interfaces to the JSON namespace + +namespace Zipkin { + +Endpoint::Endpoint(const Endpoint& ep) { + service_name_ = ep.serviceName(); + address_ = ep.address(); +} + +Endpoint& Endpoint::operator=(const Endpoint& ep) { + service_name_ = ep.serviceName(); + address_ = ep.address(); + return *this; +} + +const std::string Endpoint::toJson() { + rapidjson::StringBuffer s; + rapidjson::Writer writer(s); + writer.StartObject(); + if (!address_) { + writer.Key(ZipkinJsonFieldNames::get().ENDPOINT_IPV4.c_str()); + writer.String(""); + writer.Key(ZipkinJsonFieldNames::get().ENDPOINT_PORT.c_str()); + writer.Uint(0); + } else { + if (address_->ip()->version() == Network::Address::IpVersion::v4) { + // IPv4 + writer.Key(ZipkinJsonFieldNames::get().ENDPOINT_IPV4.c_str()); + } else { + // IPv6 + writer.Key(ZipkinJsonFieldNames::get().ENDPOINT_IPV6.c_str()); + } + writer.String(address_->ip()->addressAsString().c_str()); + writer.Key(ZipkinJsonFieldNames::get().ENDPOINT_PORT.c_str()); + writer.Uint(address_->ip()->port()); + } + writer.Key(ZipkinJsonFieldNames::get().ENDPOINT_SERVICE_NAME.c_str()); + writer.String(service_name_.c_str()); + writer.EndObject(); + std::string json_string = s.GetString(); + + return json_string; +} + +Annotation::Annotation(const Annotation& ann) { + timestamp_ = ann.timestamp(); + value_ = ann.value(); + if (ann.isSetEndpoint()) { + endpoint_ = ann.endpoint(); + } +} + +Annotation& Annotation::operator=(const Annotation& ann) { + timestamp_ = ann.timestamp(); + value_ = ann.value(); + if (ann.isSetEndpoint()) { + endpoint_ = ann.endpoint(); + } + + return *this; +} + +const std::string Annotation::toJson() { + rapidjson::StringBuffer s; + rapidjson::Writer writer(s); + writer.StartObject(); + writer.Key(ZipkinJsonFieldNames::get().ANNOTATION_TIMESTAMP.c_str()); + writer.Uint64(timestamp_); + writer.Key(ZipkinJsonFieldNames::get().ANNOTATION_VALUE.c_str()); + writer.String(value_.c_str()); + writer.EndObject(); + + std::string json_string = s.GetString(); + + if (endpoint_.valid()) { + Util::mergeJsons(json_string, static_cast(endpoint_.value()).toJson(), + ZipkinJsonFieldNames::get().ANNOTATION_ENDPOINT.c_str()); + } + + return json_string; +} + +BinaryAnnotation::BinaryAnnotation(const BinaryAnnotation& ann) { + key_ = ann.key(); + value_ = ann.value(); + annotation_type_ = ann.annotationType(); + if (ann.isSetEndpoint()) { + endpoint_ = ann.endpoint(); + } +} + +BinaryAnnotation& BinaryAnnotation::operator=(const BinaryAnnotation& ann) { + key_ = ann.key(); + value_ = ann.value(); + annotation_type_ = ann.annotationType(); + if (ann.isSetEndpoint()) { + endpoint_ = ann.endpoint(); + } + + return *this; +} + +const std::string BinaryAnnotation::toJson() { + rapidjson::StringBuffer s; + rapidjson::Writer writer(s); + writer.StartObject(); + writer.Key(ZipkinJsonFieldNames::get().BINARY_ANNOTATION_KEY.c_str()); + writer.String(key_.c_str()); + writer.Key(ZipkinJsonFieldNames::get().BINARY_ANNOTATION_VALUE.c_str()); + writer.String(value_.c_str()); + writer.EndObject(); + + std::string json_string = s.GetString(); + + if (endpoint_.valid()) { + Util::mergeJsons(json_string, static_cast(endpoint_.value()).toJson(), + ZipkinJsonFieldNames::get().BINARY_ANNOTATION_ENDPOINT.c_str()); + } + + return json_string; +} + +const std::string Span::EMPTY_HEX_STRING_ = "0000000000000000"; + +Span::Span(const Span& span) { + trace_id_ = span.traceId(); + name_ = span.name(); + id_ = span.id(); + if (span.isSetParentId()) { + parent_id_ = span.parentId(); + } + debug_ = span.debug(); + annotations_ = span.annotations(); + binary_annotations_ = span.binaryAnnotations(); + if (span.isSetTimestamp()) { + timestamp_ = span.timestamp(); + } + if (span.isSetDuration()) { + duration_ = span.duration(); + } + if (span.isSetTraceIdHigh()) { + trace_id_high_ = span.traceIdHigh(); + } + monotonic_start_time_ = span.startTime(); + tracer_ = span.tracer(); +} + +Span& Span::operator=(const Span& span) { + trace_id_ = span.traceId(); + name_ = span.name(); + id_ = span.id(); + if (span.isSetParentId()) { + parent_id_ = span.parentId(); + } + debug_ = span.debug(); + annotations_ = span.annotations(); + binary_annotations_ = span.binaryAnnotations(); + if (span.isSetTimestamp()) { + timestamp_ = span.timestamp(); + } + if (span.isSetDuration()) { + duration_ = span.duration(); + } + if (span.isSetTraceIdHigh()) { + trace_id_high_ = span.traceIdHigh(); + } + monotonic_start_time_ = span.startTime(); + tracer_ = span.tracer(); + + return *this; +} + +const std::string Span::toJson() { + rapidjson::StringBuffer s; + rapidjson::Writer writer(s); + writer.StartObject(); + writer.Key(ZipkinJsonFieldNames::get().SPAN_TRACE_ID.c_str()); + writer.String(Hex::uint64ToHex(trace_id_).c_str()); + writer.Key(ZipkinJsonFieldNames::get().SPAN_NAME.c_str()); + writer.String(name_.c_str()); + writer.Key(ZipkinJsonFieldNames::get().SPAN_ID.c_str()); + writer.String(Hex::uint64ToHex(id_).c_str()); + + if (parent_id_.valid() && parent_id_.value()) { + writer.Key(ZipkinJsonFieldNames::get().SPAN_PARENT_ID.c_str()); + writer.String(Hex::uint64ToHex(parent_id_.value()).c_str()); + } + + if (timestamp_.valid()) { + writer.Key(ZipkinJsonFieldNames::get().SPAN_TIMESTAMP.c_str()); + writer.Int64(timestamp_.value()); + } + + if (duration_.valid()) { + writer.Key(ZipkinJsonFieldNames::get().SPAN_DURATION.c_str()); + writer.Int64(duration_.value()); + } + + writer.EndObject(); + + std::string json_string = s.GetString(); + + std::vector annotation_json_vector; + + for (auto it = annotations_.begin(); it != annotations_.end(); it++) { + annotation_json_vector.push_back(it->toJson()); + } + Util::addArrayToJson(json_string, annotation_json_vector, + ZipkinJsonFieldNames::get().SPAN_ANNOTATIONS.c_str()); + + std::vector binary_annotation_json_vector; + for (auto it = binary_annotations_.begin(); it != binary_annotations_.end(); it++) { + binary_annotation_json_vector.push_back(it->toJson()); + } + Util::addArrayToJson(json_string, binary_annotation_json_vector, + ZipkinJsonFieldNames::get().SPAN_BINARY_ANNOTATIONS.c_str()); + + return json_string; +} + +void Span::finish() { + // Assumption: Span will have only one annotation when this method is called + SpanContext context(*this); + if (context.annotationSet().sr_ && !context.annotationSet().ss_) { + // Need to set the SS annotation + Annotation ss; + ss.setEndpoint(annotations_[0].endpoint()); + ss.setTimestamp(std::chrono::duration_cast( + ProdSystemTimeSource::instance_.currentTime().time_since_epoch()).count()); + ss.setValue(ZipkinCoreConstants::get().SERVER_SEND); + annotations_.push_back(std::move(ss)); + } else if (context.annotationSet().cs_ && !context.annotationSet().cr_) { + // Need to set the CR annotation + Annotation cr; + const uint64_t stop_timestamp = + std::chrono::duration_cast( + ProdSystemTimeSource::instance_.currentTime().time_since_epoch()).count(); + cr.setEndpoint(annotations_[0].endpoint()); + cr.setTimestamp(stop_timestamp); + cr.setValue(ZipkinCoreConstants::get().CLIENT_RECV); + annotations_.push_back(std::move(cr)); + const int64_t monotonic_stop_time = + std::chrono::duration_cast( + ProdMonotonicTimeSource::instance_.currentTime().time_since_epoch()).count(); + setDuration(monotonic_stop_time - monotonic_start_time_); + } + + if (auto t = tracer()) { + t->reportSpan(std::move(*this)); + } +} + +void Span::setTag(const std::string& name, const std::string& value) { + if (name.size() > 0 && value.size() > 0) { + addBinaryAnnotation(BinaryAnnotation(name, value)); + } +} +} // Zipkin diff --git a/source/common/tracing/zipkin/zipkin_core_types.h b/source/common/tracing/zipkin/zipkin_core_types.h new file mode 100644 index 0000000000000..c9cedde6d4781 --- /dev/null +++ b/source/common/tracing/zipkin/zipkin_core_types.h @@ -0,0 +1,533 @@ +#pragma once + +#include + +#include "envoy/common/optional.h" +#include "envoy/common/pure.h" +#include "envoy/network/address.h" + +#include "common/common/hex.h" +#include "common/tracing/zipkin/tracer_interface.h" +#include "common/tracing/zipkin/util.h" + +namespace Zipkin { + +/** + * Base class to be inherited by all classes that represent Zipkin-related concepts, namely: + * endpoint, annotation, binary annotation, and span. + */ +class ZipkinBase { +public: + /** + * Destructor. + */ + virtual ~ZipkinBase() {} + + /** + * All classes defining Zipkin abstractions need to implement this method to convert + * the corresponding abstraction to a Zipkin-compliant JSON. + */ + virtual const std::string toJson() PURE; +}; + +/** + * Represents a Zipkin endpoint. This class is based on Zipkin's Thrift definition of an endpoint. + * Endpoints can be added to Zipkin annotations. + */ +class Endpoint : public ZipkinBase { +public: + /** + * Copy constructor. + */ + Endpoint(const Endpoint&); + + /** + * Assignment operator. + */ + Endpoint& operator=(const Endpoint&); + + /** + * Default constructor. Creates an empty Endpoint. + */ + Endpoint() : service_name_(), address_(nullptr) {} + + /** + * Constructor that initializes an endpoint with the given attributes. + * + * @param service_name String representing the endpoint's service name + * @param address Pointer to an object representing the endpoint's network address + */ + Endpoint(const std::string& service_name, Network::Address::InstanceConstSharedPtr address) + : service_name_(service_name), address_(address) {} + + /** + * @return the endpoint's address. + */ + Network::Address::InstanceConstSharedPtr address() const { return address_; } + + /** + * Sets the endpoint's address + */ + void setAddress(Network::Address::InstanceConstSharedPtr address) { address_ = address; } + + /** + * @return the endpoint's service name attribute. + */ + const std::string& serviceName() const { return service_name_; } + + /** + * Sets the endpoint's service name attribute. + */ + void setServiceName(const std::string& service_name) { service_name_ = service_name; } + + /** + * Serializes the endpoint as a Zipkin-compliant JSON representation as a string. + * + * @return a stringified JSON. + */ + const std::string toJson() override; + +private: + std::string service_name_; + Network::Address::InstanceConstSharedPtr address_; +}; + +/** + * Represents a Zipkin basic annotation. This class is based on Zipkin's Thrift definition of + * an annotation. + */ +class Annotation : public ZipkinBase { +public: + /** + * Copy constructor. + */ + Annotation(const Annotation&); + + /** + * Assignment operator. + */ + Annotation& operator=(const Annotation&); + + /** + * Default constructor. Creates an empty annotation. + */ + Annotation() : timestamp_(0), value_() {} + + /** + * Constructor that creates an annotation based on the given parameters. + * + * @param timestamp A 64-bit integer containing the annotation timestasmp attribute. + * @param value A string containing the annotation's value attribute. Valid values + * appear on ZipkinCoreConstants. The most commonly used values are "cs", "cr", "ss" and "sr". + * @param endpoint The endpoint object representing the annotation's endpoint attribute. + */ + Annotation(uint64_t timestamp, const std::string value, Endpoint& endpoint) + : timestamp_(timestamp), value_(value), endpoint_(endpoint) {} + + /** + * @return the annotation's endpoint attribute. + */ + const Endpoint& endpoint() const { return endpoint_.value(); } + + /** + * Sets the annotation's endpoint attribute (copy semantics). + */ + void setEndpoint(const Endpoint& endpoint) { endpoint_.value(endpoint); } + + /** + * Sets the annotation's endpoint attribute (move semantics). + */ + void setEndpoint(const Endpoint&& endpoint) { endpoint_.value(endpoint); } + + /** + * @return the annotation's timestamp attribute + * (clock time for user presentation: microseconds since epoch). + */ + uint64_t timestamp() const { return timestamp_; } + + /** + * Sets the annotation's timestamp attribute. + */ + void setTimestamp(uint64_t timestamp) { timestamp_ = timestamp; } + + /** + * return the annotation's value attribute. + */ + const std::string& value() const { return value_; } + + /** + * Sets the annotation's value attribute. + */ + void setValue(const std::string& value) { value_ = value; } + + /** + * @return true if the endpoint attribute is set, or false otherwise. + */ + bool isSetEndpoint() const { return endpoint_.valid(); } + + /** + * Serializes the annotation as a Zipkin-compliant JSON representation as a string. + * + * @return a stringified JSON. + */ + const std::string toJson() override; + +private: + uint64_t timestamp_; + std::string value_; + Optional endpoint_; +}; + +/** + * Enum representing valid types of Zipkin binary annotations. + */ +enum AnnotationType { BOOL = 0, STRING = 1 }; + +/** + * Represents a Zipkin binary annotation. This class is based on Zipkin's Thrift definition of + * a binary annotation. A binary annotation allows arbitrary key-value pairs to be associated + * with a Zipkin span. + */ +class BinaryAnnotation : public ZipkinBase { +public: + /** + * Copy constructor. + */ + BinaryAnnotation(const BinaryAnnotation&); + + /** + * Assignment operator. + */ + BinaryAnnotation& operator=(const BinaryAnnotation&); + + /** + * Default constructor. Creates an empty binary annotation. + */ + BinaryAnnotation() : key_(), value_(), annotation_type_(STRING) {} + + /** + * Constructor that creates a binary annotation based on the given parameters. + * + * @param key The key name of the annotation. + * @param value The value associated with the key. + */ + BinaryAnnotation(const std::string& key, const std::string& value) + : key_(key), value_(value), annotation_type_(STRING) {} + + /** + * @return the type of the binary annotation. + */ + AnnotationType annotationType() const { return annotation_type_; } + + /** + * Sets the binary's annotation type. + */ + void setAnnotationType(AnnotationType annotationType) { annotation_type_ = annotationType; } + + /** + * @return the annotation's endpoint attribute. + */ + const Endpoint& endpoint() const { return endpoint_.value(); } + + /** + * Sets the annotation's endpoint attribute (copy semantics). + */ + void setEndpoint(const Endpoint& endpoint) { endpoint_.value(endpoint); } + + /** + * Sets the annotation's endpoint attribute (move semantics). + */ + void setEndpoint(const Endpoint&& endpoint) { endpoint_.value(endpoint); } + + /** + * @return true of the endpoint attribute has been set, or false otherwise. + */ + bool isSetEndpoint() const { return endpoint_.valid(); } + + /** + * @return the key attribute. + */ + const std::string& key() const { return key_; } + + /** + * Sets the key attribute. + */ + void setKey(const std::string& key) { key_ = key; } + + /** + * @return the value attribute. + */ + const std::string& value() const { return value_; } + + /** + * Sets the value attribute. + */ + void setValue(const std::string& value) { value_ = value; } + + /** + * Serializes the binary annotation as a Zipkin-compliant JSON representation as a string. + * + * @return a stringified JSON. + */ + const std::string toJson() override; + +private: + std::string key_; + std::string value_; + Optional endpoint_; + AnnotationType annotation_type_; +}; + +typedef std::unique_ptr SpanPtr; + +/** + * Represents a Zipkin span. This class is based on Zipkin's Thrift definition of a span. + */ +class Span : public ZipkinBase { +public: + /** + * Copy constructor. + */ + Span(const Span&); + + /** + * Assignment operator. + */ + Span& operator=(const Span&); + + /** + * Default constructor. Creates an empty span. + */ + Span() + : trace_id_(0), name_(), id_(0), debug_(false), monotonic_start_time_(0), tracer_(nullptr) {} + + /** + * Sets the span's trace id attribute. + */ + void setTraceId(const uint64_t val) { trace_id_ = val; } + + /** + * Sets the span's name attribute. + */ + void setName(const std::string& val) { name_ = val; } + + /** + * Sets the span's id. + */ + void setId(const uint64_t val) { id_ = val; } + + /** + * Sets the span's parent id. + */ + void setParentId(const uint64_t val) { parent_id_.value(val); } + + /** + * @return Whether or not the parent_id attribute is set. + */ + bool isSetParentId() const { return parent_id_.valid(); } + + /** + * @return a vector with all annotations added to the span. + */ + const std::vector& annotations() { return annotations_; } + + /** + * Sets the span's annotations all at once. + */ + void setAnnotations(const std::vector& val) { annotations_ = val; } + + /** + * Adds an annotation to the span (copy semantics). + */ + void addAnnotation(const Annotation& ann) { annotations_.push_back(ann); } + + /** + * Adds an annotation to the span (move semantics). + */ + void addAnnotation(const Annotation&& ann) { annotations_.push_back(ann); } + + /** + * Sets the span's binary annotations all at once. + */ + void setBinaryAnnotations(const std::vector& val) { binary_annotations_ = val; } + + /** + * Adds a binary annotation to the span (copy semantics). + */ + void addBinaryAnnotation(const BinaryAnnotation& bann) { binary_annotations_.push_back(bann); } + + /** + * Adds a binary annotation to the span (move semantics). + */ + void addBinaryAnnotation(const BinaryAnnotation&& bann) { binary_annotations_.push_back(bann); } + + /** + * Sets the span's debug attribute. + */ + void setDebug() { debug_ = true; } + + /** + * Sets the span's timestamp attribute. + */ + void setTimestamp(const int64_t val) { timestamp_.value(val); } + + /** + * @return Whether or not the timestamp attribute is set. + */ + bool isSetTimestamp() const { return timestamp_.valid(); } + + /** + * Sets the span's duration attribute. + */ + void setDuration(const int64_t val) { duration_.value(val); } + + /** + * @return Whether or not the duration attribute is set. + */ + bool isSetDuration() const { return duration_.valid(); } + + /** + * Sets the higher 64 bits of the span's 128-bit trace id. + * Note that this is optional, since 64-bit trace ids are valid. + */ + void setTraceIdHigh(const uint64_t val) { trace_id_high_.value(val); } + + /** + * @return whether or not the trace_id_high attribute is set. + */ + bool isSetTraceIdHigh() const { return trace_id_high_.valid(); } + + /** + * Sets the span start-time attribute (monotonic, used to calculate duration). + */ + void setStartTime(const int64_t time) { monotonic_start_time_ = time; } + + /** + * @return the span's annotations. + */ + const std::vector& annotations() const { return annotations_; } + + /** + * @return the span's binary annotations. + */ + const std::vector& binaryAnnotations() const { return binary_annotations_; } + + /** + * @return the span's duration attribute. + */ + int64_t duration() const { return duration_.value(); } + + /** + * @return the span's id as an integer. + */ + uint64_t id() const { return id_; } + + /** + * @return the span's id as a hexadecimal string. + */ + const std::string idAsHexString() const { return Hex::uint64ToHex(id_); } + + /** + * @return the span's name. + */ + const std::string& name() const { return name_; } + + /** + * @return the span's parent id as an integer. + */ + uint64_t parentId() const { return parent_id_.value(); } + + /** + * @return the span's parent id as a hexadecimal string. + */ + const std::string parentIdAsHexString() const { + return parent_id_.valid() ? Hex::uint64ToHex(parent_id_.value()) : EMPTY_HEX_STRING_; + } + + /** + * @return whether or not the debug attribute is set + */ + bool debug() const { return debug_; } + + /** + * @return the span's timestamp (clock time for user presentation: microseconds since epoch). + */ + int64_t timestamp() const { return timestamp_.value(); } + + /** + * @return the span's trace id as an integer. + */ + uint64_t traceId() const { return trace_id_; } + + /** + * @return the span's trace id as a hexadecimal string. + */ + const std::string traceIdAsHexString() const { return Hex::uint64ToHex(trace_id_); } + + /** + * @return the higher 64 bits of a 128-bit trace id. + */ + uint64_t traceIdHigh() const { return trace_id_high_.value(); } + + /** + * @return the span's start time (monotonic, used to calculate duration). + */ + int64_t startTime() const { return monotonic_start_time_; } + + /** + * Serializes the span as a Zipkin-compliant JSON representation as a string. + * The resulting JSON string can be used as part of an HTTP POST call to + * send the span to Zipkin. + * + * @return a stringified JSON. + */ + const std::string toJson() override; + + /** + * Associates a Tracer object with the span. The tracer's reportSpan() method is invoked + * by the span's finish() method so that the tracer can decide what to do with the span + * when it is finished. + * + * @param tracer Represents the Tracer object to be associated with the span. + */ + void setTracer(TracerInterface* tracer) { tracer_ = tracer; } + + /** + * @return the Tracer object associated with the span. + */ + TracerInterface* tracer() const { return tracer_; } + + /** + * Marks a successful end of the span. This method will: + * + * (1) determine if it needs to add more annotations to the span (e.g., a span containing a CS + * annotation will need to add a CR annotation) and add them; + * (2) compute and set the span's duration; and + * (3) invoke the tracer's reportSpan() method if a tracer has been associated with the span. + */ + void finish(); + + /** + * Adds a binary annotation to the span. + * + * @param name The binary annotation's key. + * @param value The binary annotation's value. + */ + void setTag(const std::string& name, const std::string& value); + +private: + static const std::string EMPTY_HEX_STRING_; + uint64_t trace_id_; + std::string name_; + uint64_t id_; + Optional parent_id_; + bool debug_; + std::vector annotations_; + std::vector binary_annotations_; + Optional timestamp_; + Optional duration_; + Optional trace_id_high_; + int64_t monotonic_start_time_; + TracerInterface* tracer_; +}; +} // Zipkin diff --git a/source/common/tracing/zipkin/zipkin_json_field_names.h b/source/common/tracing/zipkin/zipkin_json_field_names.h new file mode 100644 index 0000000000000..21d71ba313f0a --- /dev/null +++ b/source/common/tracing/zipkin/zipkin_json_field_names.h @@ -0,0 +1,36 @@ +#pragma once + +#include + +#include "common/common/singleton.h" + +namespace Zipkin { + +class ZipkinJsonFieldNameValues { +public: + const std::string SPAN_TRACE_ID = "traceId"; + const std::string SPAN_PARENT_ID = "parentId"; + const std::string SPAN_NAME = "name"; + const std::string SPAN_ID = "id"; + const std::string SPAN_TIMESTAMP = "timestamp"; + const std::string SPAN_DURATION = "duration"; + const std::string SPAN_ANNOTATIONS = "annotations"; + const std::string SPAN_BINARY_ANNOTATIONS = "binaryAnnotations"; + + const std::string ANNOTATION_ENDPOINT = "endpoint"; + const std::string ANNOTATION_TIMESTAMP = "timestamp"; + const std::string ANNOTATION_VALUE = "value"; + + const std::string BINARY_ANNOTATION_ENDPOINT = "endpoint"; + const std::string BINARY_ANNOTATION_KEY = "key"; + const std::string BINARY_ANNOTATION_VALUE = "value"; + + const std::string ENDPOINT_SERVICE_NAME = "serviceName"; + const std::string ENDPOINT_PORT = "port"; + const std::string ENDPOINT_IPV4 = "ipv4"; + const std::string ENDPOINT_IPV6 = "ipv6"; +}; + +typedef ConstSingleton ZipkinJsonFieldNames; + +} // Zipkin diff --git a/test/common/common/hex_test.cc b/test/common/common/hex_test.cc index e3f190a4fe8ae..ee1717d96f5d8 100644 --- a/test/common/common/hex_test.cc +++ b/test/common/common/hex_test.cc @@ -27,3 +27,9 @@ TEST(Hex, RoundTrip) { TEST(Hex, BadHex) { EXPECT_THROW(Hex::decode("abcde"), EnvoyException); } TEST(Hex, DecodeUppercase) { Hex::decode("ABCDEFAB"); } + +TEST(Hex, UIntToHex) { + std::string base16_string = Hex::uint64ToHex(2722130815203937912ULL); + EXPECT_EQ("25c6f38dd0600e78", base16_string); + EXPECT_EQ("0000000000000000", Hex::uint64ToHex(0ULL)); +} diff --git a/test/common/tracing/zipkin/BUILD b/test/common/tracing/zipkin/BUILD new file mode 100644 index 0000000000000..fdd3829f93895 --- /dev/null +++ b/test/common/tracing/zipkin/BUILD @@ -0,0 +1,31 @@ +licenses(["notice"]) # Apache 2 + +load( + "//bazel:envoy_build_system.bzl", + "envoy_cc_test", + "envoy_package", +) + +envoy_package() + +envoy_cc_test( + name = "zipkin_test", + srcs = [ + "span_buffer_test.cc", + "span_context_test.cc", + "tracer_test.cc", + "util_test.cc", + "zipkin_core_types_test.cc", + ], + deps = [ + "//include/envoy/common:optional", + "//include/envoy/common:time_interface", + "//include/envoy/runtime:runtime_interface", + "//source/common/common:hex_lib", + "//source/common/common:utility_lib", + "//source/common/network:address_lib", + "//source/common/tracing/zipkin:zipkin_lib", + "//test/mocks:common_lib", + "//test/mocks/runtime:runtime_mocks", + ], +) diff --git a/test/common/tracing/zipkin/span_buffer_test.cc b/test/common/tracing/zipkin/span_buffer_test.cc new file mode 100644 index 0000000000000..755b7aece2f22 --- /dev/null +++ b/test/common/tracing/zipkin/span_buffer_test.cc @@ -0,0 +1,105 @@ +#include "common/tracing/zipkin/span_buffer.h" + +#include "gtest/gtest.h" + +namespace Zipkin { + +TEST(ZipkinSpanBufferTest, defaultConstructorEndToEnd) { + SpanBuffer buffer; + SpanPtr span(new Span()); + + EXPECT_EQ(0ULL, buffer.pendingSpans()); + EXPECT_EQ("[]", buffer.toStringifiedJsonArray()); + + buffer.allocateBuffer(2); + EXPECT_EQ(0ULL, buffer.pendingSpans()); + EXPECT_EQ("[]", buffer.toStringifiedJsonArray()); + + buffer.addSpan(std::move(span)); + EXPECT_EQ(1ULL, buffer.pendingSpans()); + std::string expected_json_array_string = "[{" + R"("traceId":"0000000000000000",)" + R"("name":"",)" + R"("id":"0000000000000000",)" + R"("annotations":[],)" + R"("binaryAnnotations":[])" + "}]"; + EXPECT_EQ(expected_json_array_string, buffer.toStringifiedJsonArray()); + + buffer.clear(); + EXPECT_EQ(0ULL, buffer.pendingSpans()); + EXPECT_EQ("[]", buffer.toStringifiedJsonArray()); + + buffer.addSpan(SpanPtr(new Span())); + buffer.addSpan(SpanPtr(new Span())); + expected_json_array_string = "[" + "{" + R"("traceId":"0000000000000000",)" + R"("name":"",)" + R"("id":"0000000000000000",)" + R"("annotations":[],)" + R"("binaryAnnotations":[])" + "}," + "{" + R"("traceId":"0000000000000000",)" + R"("name":"",)" + R"("id":"0000000000000000",)" + R"("annotations":[],)" + R"("binaryAnnotations":[])" + "}" + "]"; + EXPECT_EQ(2ULL, buffer.pendingSpans()); + EXPECT_EQ(expected_json_array_string, buffer.toStringifiedJsonArray()); + + buffer.clear(); + EXPECT_EQ(0ULL, buffer.pendingSpans()); + EXPECT_EQ("[]", buffer.toStringifiedJsonArray()); +} + +TEST(ZipkinSpanBufferTest, sizeConstructorEndtoEnd) { + SpanBuffer buffer(2); + SpanPtr span(new Span()); + + EXPECT_EQ(0ULL, buffer.pendingSpans()); + EXPECT_EQ("[]", buffer.toStringifiedJsonArray()); + + buffer.addSpan(std::move(span)); + EXPECT_EQ(1ULL, buffer.pendingSpans()); + std::string expected_json_array_string = "[{" + R"("traceId":"0000000000000000",)" + R"("name":"",)" + R"("id":"0000000000000000",)" + R"("annotations":[],)" + R"("binaryAnnotations":[])" + "}]"; + EXPECT_EQ(expected_json_array_string, buffer.toStringifiedJsonArray()); + + buffer.clear(); + EXPECT_EQ(0ULL, buffer.pendingSpans()); + EXPECT_EQ("[]", buffer.toStringifiedJsonArray()); + + buffer.addSpan(SpanPtr(new Span())); + buffer.addSpan(SpanPtr(new Span())); + expected_json_array_string = "[" + "{" + R"("traceId":"0000000000000000",)" + R"("name":"",)" + R"("id":"0000000000000000",)" + R"("annotations":[],)" + R"("binaryAnnotations":[])" + "}," + "{" + R"("traceId":"0000000000000000",)" + R"("name":"",)" + R"("id":"0000000000000000",)" + R"("annotations":[],)" + R"("binaryAnnotations":[])" + "}]"; + EXPECT_EQ(2ULL, buffer.pendingSpans()); + EXPECT_EQ(expected_json_array_string, buffer.toStringifiedJsonArray()); + + buffer.clear(); + EXPECT_EQ(0ULL, buffer.pendingSpans()); + EXPECT_EQ("[]", buffer.toStringifiedJsonArray()); +} +} // Zipkin diff --git a/test/common/tracing/zipkin/span_context_test.cc b/test/common/tracing/zipkin/span_context_test.cc new file mode 100644 index 0000000000000..2d2a599978f69 --- /dev/null +++ b/test/common/tracing/zipkin/span_context_test.cc @@ -0,0 +1,235 @@ +#include "common/tracing/zipkin/span_context.h" +#include "common/tracing/zipkin/zipkin_core_constants.h" + +#include "gtest/gtest.h" + +namespace Zipkin { + +TEST(ZipkinSpanContextTest, populateFromString) { + SpanContext span_context; + + // Non-initialized span context + EXPECT_EQ(0ULL, span_context.trace_id()); + EXPECT_EQ("0000000000000000", span_context.traceIdAsHexString()); + EXPECT_EQ(0ULL, span_context.id()); + EXPECT_EQ("0000000000000000", span_context.idAsHexString()); + EXPECT_EQ(0ULL, span_context.parent_id()); + EXPECT_EQ("0000000000000000", span_context.parentIdAsHexString()); + EXPECT_FALSE(span_context.annotationSet().cr_); + EXPECT_FALSE(span_context.annotationSet().cs_); + EXPECT_FALSE(span_context.annotationSet().sr_); + EXPECT_FALSE(span_context.annotationSet().ss_); + EXPECT_EQ("0000000000000000;0000000000000000;0000000000000000", span_context.serializeToString()); + + // Span context populated with trace id, id, parent id, and no annotations + span_context.populateFromString("25c6f38dd0600e79;56707c7b3e1092af;c49193ea42335d1c"); + EXPECT_EQ(2722130815203937913ULL, span_context.trace_id()); + EXPECT_EQ("25c6f38dd0600e79", span_context.traceIdAsHexString()); + EXPECT_EQ(6228615153417491119ULL, span_context.id()); + EXPECT_EQ("56707c7b3e1092af", span_context.idAsHexString()); + EXPECT_EQ(14164264937399213340ULL, span_context.parent_id()); + EXPECT_EQ("c49193ea42335d1c", span_context.parentIdAsHexString()); + EXPECT_FALSE(span_context.annotationSet().cr_); + EXPECT_FALSE(span_context.annotationSet().cs_); + EXPECT_FALSE(span_context.annotationSet().sr_); + EXPECT_FALSE(span_context.annotationSet().ss_); + EXPECT_EQ("25c6f38dd0600e79;56707c7b3e1092af;c49193ea42335d1c", span_context.serializeToString()); + + // Span context populated with trace id, id, parent id, and one annotation + span_context.populateFromString("25c6f38dd0600e78;56707c7b3e1092af;c49193ea42335d1c;cs"); + EXPECT_EQ(2722130815203937912ULL, span_context.trace_id()); + EXPECT_EQ("25c6f38dd0600e78", span_context.traceIdAsHexString()); + EXPECT_EQ(6228615153417491119ULL, span_context.id()); + EXPECT_EQ("56707c7b3e1092af", span_context.idAsHexString()); + EXPECT_EQ(14164264937399213340ULL, span_context.parent_id()); + EXPECT_EQ("c49193ea42335d1c", span_context.parentIdAsHexString()); + EXPECT_FALSE(span_context.annotationSet().cr_); + EXPECT_TRUE(span_context.annotationSet().cs_); + EXPECT_FALSE(span_context.annotationSet().sr_); + EXPECT_FALSE(span_context.annotationSet().ss_); + EXPECT_EQ("25c6f38dd0600e78;56707c7b3e1092af;c49193ea42335d1c;cs", + span_context.serializeToString()); + + // Span context populated with trace id, id, parent id, and two annotations + span_context.populateFromString("25c6f38dd0600e78;56707c7b3e1092af;c49193ea42335d1c;cs;cr"); + EXPECT_EQ(2722130815203937912ULL, span_context.trace_id()); + EXPECT_EQ("25c6f38dd0600e78", span_context.traceIdAsHexString()); + EXPECT_EQ(6228615153417491119ULL, span_context.id()); + EXPECT_EQ("56707c7b3e1092af", span_context.idAsHexString()); + EXPECT_EQ(14164264937399213340ULL, span_context.parent_id()); + EXPECT_EQ("c49193ea42335d1c", span_context.parentIdAsHexString()); + EXPECT_TRUE(span_context.annotationSet().cr_); + EXPECT_TRUE(span_context.annotationSet().cs_); + EXPECT_FALSE(span_context.annotationSet().sr_); + EXPECT_FALSE(span_context.annotationSet().ss_); + EXPECT_EQ("25c6f38dd0600e78;56707c7b3e1092af;c49193ea42335d1c;cr;cs", + span_context.serializeToString()); + + // Span context populated with trace id, id, parent id, and three annotations + span_context.populateFromString("25c6f38dd0600e78;56707c7b3e1092af;c49193ea42335d1c;cs;cr;ss"); + EXPECT_EQ(2722130815203937912ULL, span_context.trace_id()); + EXPECT_EQ("25c6f38dd0600e78", span_context.traceIdAsHexString()); + EXPECT_EQ(6228615153417491119ULL, span_context.id()); + EXPECT_EQ("56707c7b3e1092af", span_context.idAsHexString()); + EXPECT_EQ(14164264937399213340ULL, span_context.parent_id()); + EXPECT_EQ("c49193ea42335d1c", span_context.parentIdAsHexString()); + EXPECT_TRUE(span_context.annotationSet().cr_); + EXPECT_TRUE(span_context.annotationSet().cs_); + EXPECT_FALSE(span_context.annotationSet().sr_); + EXPECT_TRUE(span_context.annotationSet().ss_); + EXPECT_EQ("25c6f38dd0600e78;56707c7b3e1092af;c49193ea42335d1c;cr;cs;ss", + span_context.serializeToString()); + + // Span context populated with trace id, id, parent id, and four annotations + span_context.populateFromString("25c6f38dd0600e78;56707c7b3e1092af;c49193ea42335d1c;cs;cr;ss;sr"); + EXPECT_EQ(2722130815203937912ULL, span_context.trace_id()); + EXPECT_EQ("25c6f38dd0600e78", span_context.traceIdAsHexString()); + EXPECT_EQ(6228615153417491119ULL, span_context.id()); + EXPECT_EQ("56707c7b3e1092af", span_context.idAsHexString()); + EXPECT_EQ(14164264937399213340ULL, span_context.parent_id()); + EXPECT_EQ("c49193ea42335d1c", span_context.parentIdAsHexString()); + EXPECT_TRUE(span_context.annotationSet().cr_); + EXPECT_TRUE(span_context.annotationSet().cs_); + EXPECT_TRUE(span_context.annotationSet().sr_); + EXPECT_TRUE(span_context.annotationSet().ss_); + EXPECT_EQ("25c6f38dd0600e78;56707c7b3e1092af;c49193ea42335d1c;cr;cs;sr;ss", + span_context.serializeToString()); + + // Span context populated with invalid string: it gets reset to its non-initialized state + span_context.populateFromString("invalid string"); + EXPECT_EQ(0ULL, span_context.trace_id()); + EXPECT_EQ("0000000000000000", span_context.traceIdAsHexString()); + EXPECT_EQ(0ULL, span_context.id()); + EXPECT_EQ("0000000000000000", span_context.idAsHexString()); + EXPECT_EQ(0ULL, span_context.parent_id()); + EXPECT_EQ("0000000000000000", span_context.parentIdAsHexString()); + EXPECT_FALSE(span_context.annotationSet().cr_); + EXPECT_FALSE(span_context.annotationSet().cs_); + EXPECT_FALSE(span_context.annotationSet().sr_); + EXPECT_FALSE(span_context.annotationSet().ss_); + EXPECT_EQ("0000000000000000;0000000000000000;0000000000000000", span_context.serializeToString()); +} + +TEST(ZipkinSpanContextTest, populateFromSpan) { + Span span; + SpanContext span_context(span); + + // Non-initialized span context + EXPECT_EQ(0ULL, span_context.trace_id()); + EXPECT_EQ("0000000000000000", span_context.traceIdAsHexString()); + EXPECT_EQ(0ULL, span_context.id()); + EXPECT_EQ("0000000000000000", span_context.idAsHexString()); + EXPECT_EQ(0ULL, span_context.parent_id()); + EXPECT_EQ("0000000000000000", span_context.parentIdAsHexString()); + EXPECT_FALSE(span_context.annotationSet().cr_); + EXPECT_FALSE(span_context.annotationSet().cs_); + EXPECT_FALSE(span_context.annotationSet().sr_); + EXPECT_FALSE(span_context.annotationSet().ss_); + EXPECT_EQ("0000000000000000;0000000000000000;0000000000000000", span_context.serializeToString()); + + // Span context populated with trace id, id, parent id, and no annotations + span.setTraceId(2722130815203937912ULL); + span.setId(6228615153417491119ULL); + span.setParentId(14164264937399213340ULL); + SpanContext span_context_2(span); + EXPECT_EQ(2722130815203937912ULL, span_context_2.trace_id()); + EXPECT_EQ("25c6f38dd0600e78", span_context_2.traceIdAsHexString()); + EXPECT_EQ(6228615153417491119ULL, span_context_2.id()); + EXPECT_EQ("56707c7b3e1092af", span_context_2.idAsHexString()); + EXPECT_EQ(14164264937399213340ULL, span_context_2.parent_id()); + EXPECT_EQ("c49193ea42335d1c", span_context_2.parentIdAsHexString()); + EXPECT_FALSE(span_context_2.annotationSet().cr_); + EXPECT_FALSE(span_context_2.annotationSet().cs_); + EXPECT_FALSE(span_context_2.annotationSet().sr_); + EXPECT_FALSE(span_context_2.annotationSet().ss_); + EXPECT_EQ("25c6f38dd0600e78;56707c7b3e1092af;c49193ea42335d1c", + span_context_2.serializeToString()); + + // Test if we can handle 128-bit trace ids + EXPECT_FALSE(span.isSetTraceIdHigh()); + span.setTraceIdHigh(9922130815203937912ULL); + EXPECT_TRUE(span.isSetTraceIdHigh()); + SpanContext span_context_high_id(span); + // We currently drop the high bits. So, we expect the same context as above + EXPECT_EQ("25c6f38dd0600e78;56707c7b3e1092af;c49193ea42335d1c", + span_context_high_id.serializeToString()); + + // Span context populated with trace id, id, parent id, and one annotation + Annotation ann; + ann.setValue(ZipkinCoreConstants::get().SERVER_RECV); + span.addAnnotation(ann); + SpanContext span_context_3(span); + EXPECT_EQ(2722130815203937912ULL, span_context_3.trace_id()); + EXPECT_EQ("25c6f38dd0600e78", span_context_3.traceIdAsHexString()); + EXPECT_EQ(6228615153417491119ULL, span_context_3.id()); + EXPECT_EQ("56707c7b3e1092af", span_context_3.idAsHexString()); + EXPECT_EQ(14164264937399213340ULL, span_context_3.parent_id()); + EXPECT_EQ("c49193ea42335d1c", span_context_3.parentIdAsHexString()); + EXPECT_FALSE(span_context_3.annotationSet().cr_); + EXPECT_FALSE(span_context_3.annotationSet().cs_); + EXPECT_TRUE(span_context_3.annotationSet().sr_); + EXPECT_FALSE(span_context_3.annotationSet().ss_); + EXPECT_EQ("25c6f38dd0600e78;56707c7b3e1092af;c49193ea42335d1c;sr", + span_context_3.serializeToString()); + + // Span context populated with trace id, id, parent id, and two annotations + ann.setValue(ZipkinCoreConstants::get().SERVER_SEND); + span.addAnnotation(ann); + SpanContext span_context_4(span); + EXPECT_EQ(2722130815203937912ULL, span_context_4.trace_id()); + EXPECT_EQ("25c6f38dd0600e78", span_context_4.traceIdAsHexString()); + EXPECT_EQ(6228615153417491119ULL, span_context_4.id()); + EXPECT_EQ("56707c7b3e1092af", span_context_4.idAsHexString()); + EXPECT_EQ(14164264937399213340ULL, span_context_4.parent_id()); + EXPECT_EQ("c49193ea42335d1c", span_context_4.parentIdAsHexString()); + EXPECT_FALSE(span_context_4.annotationSet().cr_); + EXPECT_FALSE(span_context_4.annotationSet().cs_); + EXPECT_TRUE(span_context_4.annotationSet().sr_); + EXPECT_TRUE(span_context_4.annotationSet().ss_); + EXPECT_EQ("25c6f38dd0600e78;56707c7b3e1092af;c49193ea42335d1c;sr;ss", + span_context_4.serializeToString()); + + // Span context populated with trace id, id, parent id, and three annotations + ann.setValue(ZipkinCoreConstants::get().CLIENT_SEND); + span.addAnnotation(ann); + SpanContext span_context_5(span); + EXPECT_EQ(2722130815203937912ULL, span_context_5.trace_id()); + EXPECT_EQ("25c6f38dd0600e78", span_context_5.traceIdAsHexString()); + EXPECT_EQ(6228615153417491119ULL, span_context_5.id()); + EXPECT_EQ("56707c7b3e1092af", span_context_5.idAsHexString()); + EXPECT_EQ(14164264937399213340ULL, span_context_5.parent_id()); + EXPECT_EQ("c49193ea42335d1c", span_context_5.parentIdAsHexString()); + EXPECT_FALSE(span_context_5.annotationSet().cr_); + EXPECT_TRUE(span_context_5.annotationSet().cs_); + EXPECT_TRUE(span_context_5.annotationSet().sr_); + EXPECT_TRUE(span_context_5.annotationSet().ss_); + EXPECT_EQ("25c6f38dd0600e78;56707c7b3e1092af;c49193ea42335d1c;cs;sr;ss", + span_context_5.serializeToString()); + + // Span context populated with trace id, id, parent id, and four annotations + std::vector annotations; + annotations.push_back(ann); + ann.setValue(ZipkinCoreConstants::get().SERVER_SEND); + annotations.push_back(ann); + ann.setValue(ZipkinCoreConstants::get().SERVER_RECV); + annotations.push_back(ann); + ann.setValue(ZipkinCoreConstants::get().CLIENT_RECV); + annotations.push_back(ann); + ann.setValue(ZipkinCoreConstants::get().CLIENT_SEND); + annotations.push_back(ann); + span.setAnnotations(annotations); + SpanContext span_context_6(span); + EXPECT_EQ(2722130815203937912ULL, span_context_6.trace_id()); + EXPECT_EQ("25c6f38dd0600e78", span_context_6.traceIdAsHexString()); + EXPECT_EQ(6228615153417491119ULL, span_context_6.id()); + EXPECT_EQ("56707c7b3e1092af", span_context_6.idAsHexString()); + EXPECT_EQ(14164264937399213340ULL, span_context_6.parent_id()); + EXPECT_EQ("c49193ea42335d1c", span_context_6.parentIdAsHexString()); + EXPECT_TRUE(span_context_6.annotationSet().cr_); + EXPECT_TRUE(span_context_6.annotationSet().cs_); + EXPECT_TRUE(span_context_6.annotationSet().sr_); + EXPECT_TRUE(span_context_6.annotationSet().ss_); + EXPECT_EQ("25c6f38dd0600e78;56707c7b3e1092af;c49193ea42335d1c;cr;cs;sr;ss", + span_context_6.serializeToString()); +} +} // Zipkin diff --git a/test/common/tracing/zipkin/tracer_test.cc b/test/common/tracing/zipkin/tracer_test.cc new file mode 100644 index 0000000000000..039284a351e9f --- /dev/null +++ b/test/common/tracing/zipkin/tracer_test.cc @@ -0,0 +1,218 @@ +#include "common/common/utility.h" +#include "common/network/address_impl.h" +#include "common/tracing/zipkin/tracer.h" +#include "common/tracing/zipkin/util.h" +#include "common/tracing/zipkin/zipkin_core_constants.h" + +#include "test/mocks/common.h" +#include "test/mocks/runtime/mocks.h" + +#include "gtest/gtest.h" + +namespace Zipkin { + +class TestReporterImpl : public Reporter { +public: + TestReporterImpl(int value) : value_(value) {} + void reportSpan(Span&& span) { reported_spans_.push_back(span); } + int getValue() { return value_; } + std::vector& reportedSpans() { return reported_spans_; } + +private: + int value_; + std::vector reported_spans_; +}; + +TEST(ZipkinTracerTest, spanCreation) { + Network::Address::InstanceConstSharedPtr addr = + Network::Address::parseInternetAddressAndPort("127.0.0.1:9000"); + Tracer tracer("my_service_name", addr); + MockMonotonicTimeSource mock_start_time; + MonotonicTime start_time = mock_start_time.currentTime(); + + // ============== + // Test the creation of a root span --> CS + // ============== + + SpanPtr root_span = tracer.startSpan("my_span", start_time); + + EXPECT_EQ("my_span", root_span->name()); + EXPECT_EQ( + std::chrono::duration_cast(start_time.time_since_epoch()).count(), + root_span->startTime()); + + EXPECT_NE(0ULL, root_span->traceId()); // trace id must be set + EXPECT_EQ(root_span->traceId(), root_span->id()); // span id and trace id must be the same + EXPECT_FALSE(root_span->isSetParentId()); // no parent set + EXPECT_NE(0LL, root_span->timestamp()); // span's timestamp must be set + + // A CS annotation must have been added + EXPECT_EQ(1ULL, root_span->annotations().size()); + Annotation ann = root_span->annotations()[0]; + EXPECT_EQ(ZipkinCoreConstants::get().CLIENT_SEND, ann.value()); + EXPECT_NE(0ULL, ann.timestamp()); // annotation's timestamp must be set + EXPECT_TRUE(ann.isSetEndpoint()); + Endpoint endpoint = ann.endpoint(); + EXPECT_EQ("my_service_name", endpoint.serviceName()); + + // The tracer must have been properly set + EXPECT_EQ(dynamic_cast(&tracer), root_span->tracer()); + + // Duration is not set at span-creation time + EXPECT_FALSE(root_span->isSetDuration()); + + // ============== + // Test the creation of a shared-context span --> SR + // ============== + + SpanContext root_span_context(*root_span); + SpanPtr server_side_shared_context_span = + tracer.startSpan("my_span", start_time, root_span_context); + + EXPECT_EQ( + std::chrono::duration_cast(start_time.time_since_epoch()).count(), + server_side_shared_context_span->startTime()); + + // span name should NOT be set (it was set in the CS side) + EXPECT_EQ("", server_side_shared_context_span->name()); + + // trace id must be the same in the CS and SR sides + EXPECT_EQ(root_span->traceId(), server_side_shared_context_span->traceId()); + + // span id must be the same in the CS and SR sides + EXPECT_EQ(root_span->id(), server_side_shared_context_span->id()); + + // The parent should be the same as in the CS side (none in this case) + EXPECT_FALSE(server_side_shared_context_span->isSetParentId()); + + // span timestamp should not be set (it was set in the CS side) + EXPECT_FALSE(server_side_shared_context_span->isSetTimestamp()); + + // An SR annotation must have been added + EXPECT_EQ(1ULL, server_side_shared_context_span->annotations().size()); + ann = server_side_shared_context_span->annotations()[0]; + EXPECT_EQ(ZipkinCoreConstants::get().SERVER_RECV, ann.value()); + EXPECT_NE(0ULL, ann.timestamp()); // annotation's timestamp must be set + EXPECT_TRUE(ann.isSetEndpoint()); + endpoint = ann.endpoint(); + EXPECT_EQ("my_service_name", endpoint.serviceName()); + + // The tracer must have been properly set + EXPECT_EQ(dynamic_cast(&tracer), server_side_shared_context_span->tracer()); + + // Duration is not set at span-creation time + EXPECT_FALSE(server_side_shared_context_span->isSetDuration()); + + // ============== + // Test the creation of a child span --> CS + // ============== + + SpanContext server_side_context(*server_side_shared_context_span); + SpanPtr child_span = tracer.startSpan("my_child_span", start_time, server_side_context); + + EXPECT_EQ("my_child_span", child_span->name()); + EXPECT_EQ( + std::chrono::duration_cast(start_time.time_since_epoch()).count(), + child_span->startTime()); + + // trace id must be retained + EXPECT_NE(0ULL, child_span->traceId()); + EXPECT_EQ(server_side_shared_context_span->traceId(), child_span->traceId()); + + // span id and trace id must NOT be the same + EXPECT_NE(child_span->traceId(), child_span->id()); + + // parent should be the previous span + EXPECT_TRUE(child_span->isSetParentId()); + EXPECT_EQ(server_side_shared_context_span->id(), child_span->parentId()); + + // span's timestamp must be set + EXPECT_NE(0LL, child_span->timestamp()); + + // A CS annotation must have been added + EXPECT_EQ(1ULL, child_span->annotations().size()); + ann = child_span->annotations()[0]; + EXPECT_EQ(ZipkinCoreConstants::get().CLIENT_SEND, ann.value()); + EXPECT_NE(0ULL, ann.timestamp()); // annotation's timestamp must be set + EXPECT_TRUE(ann.isSetEndpoint()); + endpoint = ann.endpoint(); + EXPECT_EQ("my_service_name", endpoint.serviceName()); + + // The tracer must have been properly set + EXPECT_EQ(dynamic_cast(&tracer), child_span->tracer()); + + // Duration is not set at span-creation time + EXPECT_FALSE(child_span->isSetDuration()); +} + +TEST(ZipkinTracerTest, finishSpan) { + Network::Address::InstanceConstSharedPtr addr = + Network::Address::parseInternetAddressAndPort("127.0.0.1:9000"); + Tracer tracer("my_service_name", addr); + tracer.setRandomGenerator(Runtime::RandomGeneratorPtr(new Runtime::MockRandomGenerator())); + MockMonotonicTimeSource mock_start_time; + MonotonicTime start_time = mock_start_time.currentTime(); + + // ============== + // Test finishing a span containing a CS annotation + // ============== + + // Creates a root-span with a CS annotation + SpanPtr span = tracer.startSpan("my_span", start_time); + + // Finishing a root span with a CS annotation must add a CR annotation + span->finish(); + EXPECT_EQ(2ULL, span->annotations().size()); + + // Check the CS annotation added at span-creation time + Annotation ann = span->annotations()[0]; + EXPECT_EQ(ZipkinCoreConstants::get().CLIENT_SEND, ann.value()); + EXPECT_NE(0ULL, ann.timestamp()); // annotation's timestamp must be set + EXPECT_TRUE(ann.isSetEndpoint()); + Endpoint endpoint = ann.endpoint(); + EXPECT_EQ("my_service_name", endpoint.serviceName()); + + // Check the CR annotation added when ending the span + ann = span->annotations()[1]; + EXPECT_EQ(ZipkinCoreConstants::get().CLIENT_RECV, ann.value()); + EXPECT_NE(0ULL, ann.timestamp()); // annotation's timestamp must be set + EXPECT_TRUE(ann.isSetEndpoint()); + endpoint = ann.endpoint(); + EXPECT_EQ("my_service_name", endpoint.serviceName()); + + // ============== + // Test finishing a span containing an SR annotation + // ============== + + SpanContext context(*span); + SpanPtr server_side = tracer.startSpan("my_span", start_time, context); + + // Associate a reporter with the tracer + TestReporterImpl* reporter_object = new TestReporterImpl(135); + ReporterPtr reporter_ptr(reporter_object); + tracer.setReporter(std::move(reporter_ptr)); + + // Finishing a server-side span with an SR annotation must add an SS annotation + server_side->finish(); + EXPECT_EQ(2ULL, server_side->annotations().size()); + + // Test if the reporter's reportSpan method was actually called upon finishing the span + EXPECT_EQ(1ULL, reporter_object->reportedSpans().size()); + + // Check the SR annotation added at span-creation time + ann = server_side->annotations()[0]; + EXPECT_EQ(ZipkinCoreConstants::get().SERVER_RECV, ann.value()); + EXPECT_NE(0ULL, ann.timestamp()); // annotation's timestamp must be set + EXPECT_TRUE(ann.isSetEndpoint()); + endpoint = ann.endpoint(); + EXPECT_EQ("my_service_name", endpoint.serviceName()); + + // Check the SS annotation added when ending the span + ann = server_side->annotations()[1]; + EXPECT_EQ(ZipkinCoreConstants::get().SERVER_SEND, ann.value()); + EXPECT_NE(0ULL, ann.timestamp()); // annotation's timestamp must be set + EXPECT_TRUE(ann.isSetEndpoint()); + endpoint = ann.endpoint(); + EXPECT_EQ("my_service_name", endpoint.serviceName()); +} +} // Zipkin diff --git a/test/common/tracing/zipkin/util_test.cc b/test/common/tracing/zipkin/util_test.cc new file mode 100644 index 0000000000000..23275edb78647 --- /dev/null +++ b/test/common/tracing/zipkin/util_test.cc @@ -0,0 +1,40 @@ +#include "common/tracing/zipkin/util.h" + +#include "gtest/gtest.h" + +namespace Zipkin { + +TEST(ZipkinUtilTest, utilTests) { + EXPECT_EQ(typeid(uint64_t).name(), typeid(Util::generateRandom64()).name()); + + // Test JSON merging + + std::string merged_json = "{}"; + std::string source_json = "{\"field1\":\"val1\"}"; + Util::mergeJsons(merged_json, source_json, "sub_json"); + std::string expected_json = "{\"sub_json\":{\"field1\":\"val1\"}}"; + EXPECT_EQ(expected_json, merged_json); + + Util::mergeJsons(merged_json, merged_json, "second_merge"); + expected_json = + "{\"sub_json\":{\"field1\":\"val1\"},\"second_merge\":{\"sub_json\":{\"field1\":\"val1\"}}}"; + EXPECT_EQ(expected_json, merged_json); + + // Test adding an array to a JSON + + std::vector json_array; + Util::addArrayToJson(merged_json, json_array, "array_field"); + expected_json = "{\"sub_json\":{\"field1\":\"val1\"},\"second_merge\":{\"sub_json\":{\"field1\":" + "\"val1\"}},\"array_field\":[]}"; + EXPECT_EQ(expected_json, merged_json); + + std::string str1 = "{\"a1\":10}"; + std::string str2 = "{\"a2\":\"10\"}"; + json_array.push_back(str1); + json_array.push_back(str2); + Util::addArrayToJson(merged_json, json_array, "second_array"); + expected_json = "{\"sub_json\":{\"field1\":\"val1\"},\"second_merge\":{\"sub_json\":{\"field1\":" + "\"val1\"}},\"array_field\":[],\"second_array\":[{\"a1\":10},{\"a2\":\"10\"}]}"; + EXPECT_EQ(expected_json, merged_json); +} +} // Zipkin diff --git a/test/common/tracing/zipkin/zipkin_core_types_test.cc b/test/common/tracing/zipkin/zipkin_core_types_test.cc new file mode 100644 index 0000000000000..01845b3fb9609 --- /dev/null +++ b/test/common/tracing/zipkin/zipkin_core_types_test.cc @@ -0,0 +1,515 @@ +#include "common/common/utility.h" +#include "common/network/address_impl.h" +#include "common/tracing/zipkin/zipkin_core_constants.h" +#include "common/tracing/zipkin/zipkin_core_types.h" + +#include "gtest/gtest.h" + +namespace Zipkin { + +TEST(ZipkinCoreTypesEndpointTest, defaultConstructor) { + Endpoint ep; + + EXPECT_EQ("", ep.serviceName()); + EXPECT_EQ(R"({"ipv4":"","port":0,"serviceName":""})", ep.toJson()); + + Network::Address::InstanceConstSharedPtr addr = + Network::Address::parseInternetAddress("127.0.0.1"); + ep.setAddress(addr); + EXPECT_EQ(R"({"ipv4":"127.0.0.1","port":0,"serviceName":""})", ep.toJson()); + + addr = Network::Address::parseInternetAddressAndPort( + "[2001:0db8:85a3:0000:0000:8a2e:0370:4444]:7334"); + ep.setAddress(addr); + EXPECT_EQ(R"({"ipv6":"2001:db8:85a3::8a2e:370:4444","port":7334,"serviceName":""})", ep.toJson()); + + ep.setServiceName("my_service"); + EXPECT_EQ("my_service", ep.serviceName()); + + EXPECT_EQ( + R"({"ipv6":"2001:db8:85a3::8a2e:370:4444","port":7334,"serviceName":"my_service"})", + ep.toJson()); +} + +TEST(ZipkinCoreTypesEndpointTest, customConstructor) { + Network::Address::InstanceConstSharedPtr addr = + Network::Address::parseInternetAddressAndPort("127.0.0.1:3306"); + Endpoint ep(std::string("my_service"), addr); + + EXPECT_EQ("my_service", ep.serviceName()); + EXPECT_EQ(R"({"ipv4":"127.0.0.1","port":3306,"serviceName":"my_service"})", ep.toJson()); + + addr = Network::Address::parseInternetAddressAndPort( + "[2001:0db8:85a3:0000:0000:8a2e:0370:4444]:7334"); + ep.setAddress(addr); + + EXPECT_EQ( + R"({"ipv6":"2001:db8:85a3::8a2e:370:4444","port":7334,"serviceName":"my_service"})", + ep.toJson()); +} + +TEST(ZipkinCoreTypesEndpointTest, copyOperator) { + Network::Address::InstanceConstSharedPtr addr = + Network::Address::parseInternetAddressAndPort("127.0.0.1:3306"); + Endpoint ep1(std::string("my_service"), addr); + Endpoint ep2(ep1); + + EXPECT_EQ("my_service", ep1.serviceName()); + EXPECT_EQ(R"({"ipv4":"127.0.0.1","port":3306,"serviceName":"my_service"})", ep1.toJson()); + + EXPECT_EQ(ep1.serviceName(), ep2.serviceName()); + EXPECT_EQ(ep1.toJson(), ep2.toJson()); +} + +TEST(ZipkinCoreTypesEndpointTest, assignmentOperator) { + Network::Address::InstanceConstSharedPtr addr = + Network::Address::parseInternetAddressAndPort("127.0.0.1:3306"); + Endpoint ep1(std::string("my_service"), addr); + Endpoint ep2 = ep1; + + EXPECT_EQ("my_service", ep1.serviceName()); + EXPECT_EQ(R"({"ipv4":"127.0.0.1","port":3306,"serviceName":"my_service"})", ep1.toJson()); + + EXPECT_EQ(ep1.serviceName(), ep2.serviceName()); + EXPECT_EQ(ep1.toJson(), ep2.toJson()); +} + +TEST(ZipkinCoreTypesAnnotationTest, defaultConstructor) { + Annotation ann; + + EXPECT_EQ(0ULL, ann.timestamp()); + EXPECT_EQ("", ann.value()); + EXPECT_FALSE(ann.isSetEndpoint()); + + uint64_t timestamp = + std::chrono::duration_cast( + ProdSystemTimeSource::instance_.currentTime().time_since_epoch()).count(); + ann.setTimestamp(timestamp); + EXPECT_EQ(timestamp, ann.timestamp()); + + ann.setValue(ZipkinCoreConstants::get().CLIENT_SEND); + EXPECT_EQ(ZipkinCoreConstants::get().CLIENT_SEND, ann.value()); + + std::string expected_json = R"({"timestamp":)" + std::to_string(timestamp) + R"(,"value":")" + + ZipkinCoreConstants::get().CLIENT_SEND + R"("})"; + EXPECT_EQ(expected_json, ann.toJson()); + + // Test the copy-semantics flavor of setEndpoint + Network::Address::InstanceConstSharedPtr addr = + Network::Address::parseInternetAddressAndPort("127.0.0.1:3306"); + Endpoint ep(std::string("my_service"), addr); + ann.setEndpoint(ep); + EXPECT_TRUE(ann.isSetEndpoint()); + EXPECT_EQ("my_service", ann.endpoint().serviceName()); + EXPECT_EQ(R"({"ipv4":"127.0.0.1","port":3306,"serviceName":"my_service"})", + (const_cast(ann.endpoint())).toJson()); + + expected_json = R"({"timestamp":)" + std::to_string(timestamp) + R"(,"value":")" + + ZipkinCoreConstants::get().CLIENT_SEND + + R"(","endpoint":{"ipv4":)" + R"("127.0.0.1","port":3306,"serviceName":"my_service"}})"; + EXPECT_EQ(expected_json, ann.toJson()); + + // Test the move-semantics flavor of setEndpoint + addr = Network::Address::parseInternetAddressAndPort("192.168.1.1:5555"); + Endpoint ep2(std::string("my_service_2"), addr); + ann.setEndpoint(std::move(ep2)); + EXPECT_TRUE(ann.isSetEndpoint()); + EXPECT_EQ("my_service_2", ann.endpoint().serviceName()); + EXPECT_EQ(R"({"ipv4":"192.168.1.1","port":5555,"serviceName":"my_service_2"})", + (const_cast(ann.endpoint())).toJson()); + + expected_json = R"({"timestamp":)" + std::to_string(timestamp) + R"(,"value":")" + + ZipkinCoreConstants::get().CLIENT_SEND + + R"(","endpoint":{"ipv4":"192.168.1.1",)" + R"("port":5555,"serviceName":"my_service_2"}})"; + EXPECT_EQ(expected_json, ann.toJson()); +} + +TEST(ZipkinCoreTypesAnnotationTest, customConstructor) { + Network::Address::InstanceConstSharedPtr addr = + Network::Address::parseInternetAddressAndPort("127.0.0.1:3306"); + Endpoint ep(std::string("my_service"), addr); + uint64_t timestamp = + std::chrono::duration_cast( + ProdSystemTimeSource::instance_.currentTime().time_since_epoch()).count(); + Annotation ann(timestamp, ZipkinCoreConstants::get().CLIENT_SEND, ep); + + EXPECT_EQ(timestamp, ann.timestamp()); + EXPECT_EQ(ZipkinCoreConstants::get().CLIENT_SEND, ann.value()); + EXPECT_TRUE(ann.isSetEndpoint()); + + EXPECT_EQ("my_service", ann.endpoint().serviceName()); + EXPECT_EQ(R"({"ipv4":"127.0.0.1","port":3306,"serviceName":"my_service"})", + (const_cast(ann.endpoint())).toJson()); + + std::string expected_json = R"({"timestamp":)" + std::to_string(timestamp) + R"(,"value":")" + + ZipkinCoreConstants::get().CLIENT_SEND + + R"(","endpoint":{"ipv4":"127.0.0.1",)" + R"("port":3306,"serviceName":"my_service"}})"; + EXPECT_EQ(expected_json, ann.toJson()); +} + +TEST(ZipkinCoreTypesAnnotationTest, copyConstructor) { + Network::Address::InstanceConstSharedPtr addr = + Network::Address::parseInternetAddressAndPort("127.0.0.1:3306"); + Endpoint ep(std::string("my_service"), addr); + uint64_t timestamp = + std::chrono::duration_cast( + ProdSystemTimeSource::instance_.currentTime().time_since_epoch()).count(); + Annotation ann(timestamp, ZipkinCoreConstants::get().CLIENT_SEND, ep); + Annotation ann2(ann); + + EXPECT_EQ(ann.value(), ann2.value()); + EXPECT_EQ(ann.timestamp(), ann2.timestamp()); + EXPECT_EQ(ann.isSetEndpoint(), ann2.isSetEndpoint()); + EXPECT_EQ(ann.toJson(), ann2.toJson()); + EXPECT_EQ(ann.endpoint().serviceName(), ann2.endpoint().serviceName()); +} + +TEST(ZipkinCoreTypesAnnotationTest, assignmentOperator) { + Network::Address::InstanceConstSharedPtr addr = + Network::Address::parseInternetAddressAndPort("127.0.0.1:3306"); + Endpoint ep(std::string("my_service"), addr); + uint64_t timestamp = + std::chrono::duration_cast( + ProdSystemTimeSource::instance_.currentTime().time_since_epoch()).count(); + Annotation ann(timestamp, ZipkinCoreConstants::get().CLIENT_SEND, ep); + Annotation ann2 = ann; + + EXPECT_EQ(ann.value(), ann2.value()); + EXPECT_EQ(ann.timestamp(), ann2.timestamp()); + EXPECT_EQ(ann.isSetEndpoint(), ann2.isSetEndpoint()); + EXPECT_EQ(ann.toJson(), ann2.toJson()); + EXPECT_EQ(ann.endpoint().serviceName(), ann2.endpoint().serviceName()); +} + +TEST(ZipkinCoreTypesBinaryAnnotationTest, defaultConstructor) { + BinaryAnnotation ann; + + EXPECT_EQ("", ann.key()); + EXPECT_EQ("", ann.value()); + EXPECT_FALSE(ann.isSetEndpoint()); + EXPECT_EQ(AnnotationType::STRING, ann.annotationType()); + + ann.setKey("key"); + EXPECT_EQ("key", ann.key()); + + ann.setValue("value"); + EXPECT_EQ("value", ann.value()); + + std::string expected_json = R"({"key":"key","value":"value"})"; + EXPECT_EQ(expected_json, ann.toJson()); + + // Test the copy-semantics flavor of setEndpoint + + Network::Address::InstanceConstSharedPtr addr = + Network::Address::parseInternetAddressAndPort("127.0.0.1:3306"); + Endpoint ep(std::string("my_service"), addr); + ann.setEndpoint(ep); + EXPECT_TRUE(ann.isSetEndpoint()); + EXPECT_EQ("my_service", ann.endpoint().serviceName()); + EXPECT_EQ(R"({"ipv4":"127.0.0.1","port":3306,"serviceName":"my_service"})", + (const_cast(ann.endpoint())).toJson()); + + expected_json = "{" + R"("key":"key","value":"value",)" + R"("endpoint":)" + R"({"ipv4":"127.0.0.1","port":3306,"serviceName":"my_service"})" + "}"; + EXPECT_EQ(expected_json, ann.toJson()); + + // Test the move-semantics flavor of setEndpoint + addr = Network::Address::parseInternetAddressAndPort("192.168.1.1:5555"); + Endpoint ep2(std::string("my_service_2"), addr); + ann.setEndpoint(std::move(ep2)); + EXPECT_TRUE(ann.isSetEndpoint()); + EXPECT_EQ("my_service_2", ann.endpoint().serviceName()); + EXPECT_EQ(R"({"ipv4":"192.168.1.1","port":5555,"serviceName":"my_service_2"})", + (const_cast(ann.endpoint())).toJson()); + expected_json = "{" + R"("key":"key","value":"value",)" + R"("endpoint":)" + R"({"ipv4":"192.168.1.1","port":5555,"serviceName":"my_service_2"})" + "}"; + EXPECT_EQ(expected_json, ann.toJson()); +} + +TEST(ZipkinCoreTypesBinaryAnnotationTest, customConstructor) { + BinaryAnnotation ann("key", "value"); + + EXPECT_EQ("key", ann.key()); + EXPECT_EQ("value", ann.value()); + EXPECT_FALSE(ann.isSetEndpoint()); + EXPECT_EQ(AnnotationType::STRING, ann.annotationType()); + std::string expected_json = R"({"key":"key","value":"value"})"; + EXPECT_EQ(expected_json, ann.toJson()); +} + +TEST(ZipkinCoreTypesBinaryAnnotationTest, copyConstructor) { + BinaryAnnotation ann("key", "value"); + BinaryAnnotation ann2(ann); + + EXPECT_EQ(ann.value(), ann2.value()); + EXPECT_EQ(ann.key(), ann2.key()); + EXPECT_EQ(ann.isSetEndpoint(), ann2.isSetEndpoint()); + EXPECT_EQ(ann.toJson(), ann2.toJson()); + EXPECT_EQ(ann.annotationType(), ann2.annotationType()); +} + +TEST(ZipkinCoreTypesBinaryAnnotationTest, assignmentOperator) { + BinaryAnnotation ann("key", "value"); + BinaryAnnotation ann2 = ann; + + EXPECT_EQ(ann.value(), ann2.value()); + EXPECT_EQ(ann.key(), ann2.key()); + EXPECT_EQ(ann.isSetEndpoint(), ann2.isSetEndpoint()); + EXPECT_EQ(ann.toJson(), ann2.toJson()); + EXPECT_EQ(ann.annotationType(), ann2.annotationType()); +} + +TEST(ZipkinCoreTypesSpanTest, defaultConstructor) { + Span span; + + EXPECT_EQ(0ULL, span.id()); + EXPECT_EQ(0ULL, span.traceId()); + EXPECT_EQ("", span.name()); + EXPECT_EQ(0ULL, span.annotations().size()); + EXPECT_EQ(0ULL, span.binaryAnnotations().size()); + EXPECT_EQ("0000000000000000", span.idAsHexString()); + EXPECT_EQ("0000000000000000", span.parentIdAsHexString()); + EXPECT_EQ("0000000000000000", span.traceIdAsHexString()); + EXPECT_EQ(0LL, span.startTime()); + EXPECT_FALSE(span.debug()); + EXPECT_FALSE(span.isSetDuration()); + EXPECT_FALSE(span.isSetParentId()); + EXPECT_FALSE(span.isSetTimestamp()); + EXPECT_FALSE(span.isSetTraceIdHigh()); + EXPECT_EQ(R"({"traceId":"0000000000000000","name":"","id":"0000000000000000",)" + R"("annotations":[],"binaryAnnotations":[]})", + span.toJson()); + + uint64_t id = Util::generateRandom64(); + std::string id_hex = Hex::uint64ToHex(id); + span.setId(id); + EXPECT_EQ(id, span.id()); + EXPECT_EQ(id_hex, span.idAsHexString()); + + id = Util::generateRandom64(); + id_hex = Hex::uint64ToHex(id); + span.setParentId(id); + EXPECT_EQ(id, span.parentId()); + EXPECT_EQ(id_hex, span.parentIdAsHexString()); + EXPECT_TRUE(span.isSetParentId()); + + id = Util::generateRandom64(); + id_hex = Hex::uint64ToHex(id); + span.setTraceId(id); + EXPECT_EQ(id, span.traceId()); + EXPECT_EQ(id_hex, span.traceIdAsHexString()); + + id = Util::generateRandom64(); + id_hex = Hex::uint64ToHex(id); + span.setTraceIdHigh(id); + EXPECT_EQ(id, span.traceIdHigh()); + EXPECT_TRUE(span.isSetTraceIdHigh()); + + int64_t timestamp = std::chrono::duration_cast( + ProdSystemTimeSource::instance_.currentTime().time_since_epoch()).count(); + span.setTimestamp(timestamp); + EXPECT_EQ(timestamp, span.timestamp()); + EXPECT_TRUE(span.isSetTimestamp()); + + int64_t start_time = + std::chrono::duration_cast( + ProdMonotonicTimeSource::instance_.currentTime().time_since_epoch()).count(); + span.setStartTime(start_time); + EXPECT_EQ(start_time, span.startTime()); + + span.setDuration(3000LL); + EXPECT_EQ(3000LL, span.duration()); + EXPECT_TRUE(span.isSetDuration()); + + span.setName("span_name"); + EXPECT_EQ("span_name", span.name()); + + span.setDebug(); + EXPECT_TRUE(span.debug()); + + Endpoint endpoint; + Annotation ann; + BinaryAnnotation bann; + std::vector annotations; + std::vector binary_annotations; + + endpoint.setServiceName("my_service_name"); + Network::Address::InstanceConstSharedPtr addr = + Network::Address::parseInternetAddressAndPort("192.168.1.2:3306"); + endpoint.setAddress(addr); + + ann.setValue(Zipkin::ZipkinCoreConstants::get().CLIENT_SEND); + ann.setTimestamp(timestamp); + ann.setEndpoint(endpoint); + + annotations.push_back(ann); + span.setAnnotations(annotations); + EXPECT_EQ(1ULL, span.annotations().size()); + + bann.setKey(Zipkin::ZipkinCoreConstants::get().LOCAL_COMPONENT); + bann.setValue("my_component_name"); + bann.setEndpoint(endpoint); + + binary_annotations.push_back(bann); + span.setBinaryAnnotations(binary_annotations); + EXPECT_EQ(1ULL, span.binaryAnnotations().size()); + + EXPECT_EQ( + R"({"traceId":")" + span.traceIdAsHexString() + R"(","name":"span_name","id":")" + + span.idAsHexString() + R"(","parentId":")" + span.parentIdAsHexString() + + R"(","timestamp":)" + std::to_string(span.timestamp()) + R"(,"duration":3000,)" + R"("annotations":[)" + R"({"timestamp":)" + + std::to_string(span.timestamp()) + + R"(,"value":"cs","endpoint":)" + R"({"ipv4":"192.168.1.2","port":3306,"serviceName":"my_service_name"}}],)" + R"("binaryAnnotations":[{"key":"lc","value":"my_component_name","endpoint":)" + R"({"ipv4":"192.168.1.2","port":3306,"serviceName":"my_service_name"}}]})", + span.toJson()); + + // Test the copy-semantics flavor of addAnnotation and addBinaryAnnotation + + ann.setValue(Zipkin::ZipkinCoreConstants::get().SERVER_SEND); + span.addAnnotation(ann); + bann.setKey("http.return_code"); + bann.setValue("200"); + span.addBinaryAnnotation(bann); + + EXPECT_EQ(2ULL, span.annotations().size()); + EXPECT_EQ(2ULL, span.binaryAnnotations().size()); + + // Test the move-semantics flavor of addAnnotation and addBinaryAnnotation + + ann.setValue(Zipkin::ZipkinCoreConstants::get().SERVER_RECV); + span.addAnnotation(std::move(ann)); + bann.setKey("http.return_code"); + bann.setValue("400"); + span.addBinaryAnnotation(std::move(bann)); + + EXPECT_EQ(3ULL, span.annotations().size()); + EXPECT_EQ(3ULL, span.binaryAnnotations().size()); + + EXPECT_EQ(R"({"traceId":")" + span.traceIdAsHexString() + R"(","name":"span_name","id":")" + + span.idAsHexString() + R"(","parentId":")" + span.parentIdAsHexString() + + R"(","timestamp":)" + std::to_string(span.timestamp()) + R"(,"duration":3000,)" + R"("annotations":[)" + R"({"timestamp":)" + + std::to_string(timestamp) + + R"(,"value":"cs","endpoint":)" + R"({"ipv4":"192.168.1.2","port":3306,"serviceName":"my_service_name"}},)" + R"({"timestamp":)" + + std::to_string(timestamp) + R"(,"value":"ss",)" + R"("endpoint":{"ipv4":"192.168.1.2","port":3306,)" + R"("serviceName":"my_service_name"}},)" + R"({"timestamp":)" + + std::to_string(timestamp) + + R"(,"value":"sr","endpoint":{"ipv4":"192.168.1.2","port":3306,)" + R"("serviceName":"my_service_name"}}],)" + R"("binaryAnnotations":[{"key":"lc","value":"my_component_name",)" + R"("endpoint":{"ipv4":"192.168.1.2","port":3306,)" + R"("serviceName":"my_service_name"}},)" + R"({"key":"http.return_code","value":"200",)" + R"("endpoint":{"ipv4":"192.168.1.2","port":3306,)" + R"("serviceName":"my_service_name"}},)" + R"({"key":"http.return_code","value":"400",)" + R"("endpoint":{"ipv4":"192.168.1.2","port":3306,)" + R"("serviceName":"my_service_name"}}]})", + span.toJson()); +} + +TEST(ZipkinCoreTypesSpanTest, copyConstructor) { + Span span; + + uint64_t id = Util::generateRandom64(); + std::string id_hex = Hex::uint64ToHex(id); + span.setId(id); + span.setParentId(id); + span.setTraceId(id); + int64_t timestamp = std::chrono::duration_cast( + ProdSystemTimeSource::instance_.currentTime().time_since_epoch()).count(); + span.setTimestamp(timestamp); + span.setDuration(3000LL); + span.setName("span_name"); + + Span span2(span); + + EXPECT_EQ(span.id(), span2.id()); + EXPECT_EQ(span.parentId(), span2.parentId()); + EXPECT_EQ(span.traceId(), span2.traceId()); + EXPECT_EQ(span.name(), span2.name()); + EXPECT_EQ(span.annotations().size(), span2.annotations().size()); + EXPECT_EQ(span.binaryAnnotations().size(), span2.binaryAnnotations().size()); + EXPECT_EQ(span.idAsHexString(), span2.idAsHexString()); + EXPECT_EQ(span.parentIdAsHexString(), span2.parentIdAsHexString()); + EXPECT_EQ(span.traceIdAsHexString(), span2.traceIdAsHexString()); + EXPECT_EQ(span.timestamp(), span2.timestamp()); + EXPECT_EQ(span.duration(), span2.duration()); + EXPECT_EQ(span.startTime(), span2.startTime()); + EXPECT_EQ(span.debug(), span2.debug()); + EXPECT_EQ(span.isSetDuration(), span2.isSetDuration()); + EXPECT_EQ(span.isSetParentId(), span2.isSetParentId()); + EXPECT_EQ(span.isSetTimestamp(), span2.isSetTimestamp()); + EXPECT_EQ(span.isSetTraceIdHigh(), span2.isSetTraceIdHigh()); +} + +TEST(ZipkinCoreTypesSpanTest, assignmentOperator) { + Span span; + + uint64_t id = Util::generateRandom64(); + std::string id_hex = Hex::uint64ToHex(id); + span.setId(id); + span.setParentId(id); + span.setTraceId(id); + int64_t timestamp = std::chrono::duration_cast( + ProdSystemTimeSource::instance_.currentTime().time_since_epoch()).count(); + span.setTimestamp(timestamp); + span.setDuration(3000LL); + span.setName("span_name"); + + Span span2 = span; + + EXPECT_EQ(span.id(), span2.id()); + EXPECT_EQ(span.parentId(), span2.parentId()); + EXPECT_EQ(span.traceId(), span2.traceId()); + EXPECT_EQ(span.name(), span2.name()); + EXPECT_EQ(span.annotations().size(), span2.annotations().size()); + EXPECT_EQ(span.binaryAnnotations().size(), span2.binaryAnnotations().size()); + EXPECT_EQ(span.idAsHexString(), span2.idAsHexString()); + EXPECT_EQ(span.parentIdAsHexString(), span2.parentIdAsHexString()); + EXPECT_EQ(span.traceIdAsHexString(), span2.traceIdAsHexString()); + EXPECT_EQ(span.timestamp(), span2.timestamp()); + EXPECT_EQ(span.duration(), span2.duration()); + EXPECT_EQ(span.startTime(), span2.startTime()); + EXPECT_EQ(span.debug(), span2.debug()); + EXPECT_EQ(span.isSetDuration(), span2.isSetDuration()); + EXPECT_EQ(span.isSetParentId(), span2.isSetParentId()); + EXPECT_EQ(span.isSetTimestamp(), span2.isSetTimestamp()); + EXPECT_EQ(span.isSetTraceIdHigh(), span2.isSetTraceIdHigh()); +} + +TEST(ZipkinCoreTypesSpanTest, setTag) { + Span span; + + span.setTag("key1", "value1"); + span.setTag("key2", "value2"); + + EXPECT_EQ(2ULL, span.binaryAnnotations().size()); + + BinaryAnnotation bann = span.binaryAnnotations()[0]; + EXPECT_EQ("key1", bann.key()); + EXPECT_EQ("value1", bann.value()); + + bann = span.binaryAnnotations()[1]; + EXPECT_EQ("key2", bann.key()); + EXPECT_EQ("value2", bann.value()); +} +} // Zipkin