diff --git a/sdk/include/opentelemetry/sdk/trace/exporter.h b/sdk/include/opentelemetry/sdk/trace/exporter.h new file mode 100644 index 0000000000..d517c572b2 --- /dev/null +++ b/sdk/include/opentelemetry/sdk/trace/exporter.h @@ -0,0 +1,63 @@ +#pragma once + +#include +#include "opentelemetry/nostd/span.h" +#include "opentelemetry/sdk/trace/recordable.h" + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace sdk +{ +namespace trace +{ +/** + * ExportResult is returned as result of exporting a span batch. + */ +enum class ExportResult +{ + /** + * Batch was successfully exported. + */ + kSuccess = 0, + /** + * Exporting failed. The caller must not retry exporting the same batch; the + * batch must be dropped. + */ + kFailure +}; +/** + * SpanExporter defines the interface that protocol-specific span exporters must + * implement. + */ +class SpanExporter +{ +public: + virtual ~SpanExporter() = default; + + /** + * Create a span recordable. This object will be used to record span data and + * will subsequently be passed to SpanExporter::Export. Vendors can implement + * custom recordables or use the default SpanData recordable provided by the + * SDK. + * @return a newly initialized Recordable object + */ + virtual std::unique_ptr MakeRecordable() noexcept = 0; + + /** + * Exports a batch of span recordables. This method must not be called + * concurrently for the same exporter instance. + * @param spans a span of unique pointers to span recordables + */ + virtual ExportResult Export( + nostd::span> &spans) noexcept = 0; + + /** + * Shut down the exporter. + * @param timeout an optional timeout, the default timeout of 0 means that no + * timeout is applied. + */ + virtual void Shutdown( + std::chrono::microseconds timeout = std::chrono::microseconds(0)) noexcept = 0; +}; +} // namespace trace +} // namespace sdk +OPENTELEMETRY_END_NAMESPACE diff --git a/sdk/include/opentelemetry/sdk/trace/processor.h b/sdk/include/opentelemetry/sdk/trace/processor.h new file mode 100644 index 0000000000..04f9f8ab9d --- /dev/null +++ b/sdk/include/opentelemetry/sdk/trace/processor.h @@ -0,0 +1,63 @@ +#pragma once + +#include +#include +#include "opentelemetry/sdk/trace/recordable.h" + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace sdk +{ +namespace trace +{ +/** + * Span processor allow hooks for span start and end method invocations. + * + * Built-in span processors are responsible for batching and conversion of + * spans to exportable representation and passing batches to exporters. + */ +class SpanProcessor +{ +public: + virtual ~SpanProcessor() = default; + + /** + * Create a span recordable. This requests a new span recordable from the + * associated exporter. + * @return a newly initialized recordable + */ + virtual std::unique_ptr MakeRecordable() noexcept = 0; + + /** + * OnStart is called when a span is started. + * @param span a recordable for a span that was just started + */ + virtual void OnStart(Recordable &span) noexcept = 0; + + /** + * OnEnd is called when a span is ended. + * @param span a recordable for a span that was ended + */ + virtual void OnEnd(std::unique_ptr &&span) noexcept = 0; + + /** + * Export all ended spans that have not yet been exported. + * @param timeout an optional timeout, the default timeout of 0 means that no + * timeout is applied. + */ + virtual void ForceFlush( + std::chrono::microseconds timeout = std::chrono::microseconds(0)) noexcept = 0; + + /** + * Shut down the processor and do any cleanup required. Ended spans are + * exported before shutdown. After the call to Shutdown, subsequent calls to + * OnStart, OnEnd, ForceFlush or Shutdown will return immediately without + * doing anything. + * @param timeout an optional timeout, the default timeout of 0 means that no + * timeout is applied. + */ + virtual void Shutdown( + std::chrono::microseconds timeout = std::chrono::microseconds(0)) noexcept = 0; +}; +} // namespace trace +} // namespace sdk +OPENTELEMETRY_END_NAMESPACE diff --git a/sdk/include/opentelemetry/sdk/trace/recordable.h b/sdk/include/opentelemetry/sdk/trace/recordable.h index 32646a3bbe..c102958fe3 100644 --- a/sdk/include/opentelemetry/sdk/trace/recordable.h +++ b/sdk/include/opentelemetry/sdk/trace/recordable.h @@ -3,6 +3,8 @@ #include "opentelemetry/core/timestamp.h" #include "opentelemetry/nostd/string_view.h" #include "opentelemetry/trace/canonical_code.h" +#include "opentelemetry/trace/span_id.h" +#include "opentelemetry/trace/trace_id.h" #include "opentelemetry/version.h" OPENTELEMETRY_BEGIN_NAMESPACE @@ -21,6 +23,16 @@ class Recordable public: virtual ~Recordable() = default; + /** + * Set a trace id, span id and parent span id for this span. + * @param trace_id the trace id to set + * @param span_id the span id to set + * @param parent_span_id the parent span id to set + */ + virtual void SetIds(opentelemetry::trace::TraceId trace_id, + opentelemetry::trace::SpanId span_id, + opentelemetry::trace::SpanId parent_span_id) noexcept = 0; + /** * Add an event to a span. * @param name the name of the event @@ -41,6 +53,18 @@ class Recordable * @param name the name to set */ virtual void SetName(nostd::string_view name) noexcept = 0; + + /** + * Set the start time of the span. + * @param start_time the start time to set + */ + virtual void SetStartTime(opentelemetry::core::SystemTimestamp start_time) noexcept = 0; + + /** + * Set the duration of the span. + * @param duration the duration to set + */ + virtual void SetDuration(std::chrono::nanoseconds duration) noexcept = 0; }; } // namespace trace } // namespace sdk diff --git a/sdk/include/opentelemetry/sdk/trace/span_data.h b/sdk/include/opentelemetry/sdk/trace/span_data.h new file mode 100644 index 0000000000..8ac620ad1b --- /dev/null +++ b/sdk/include/opentelemetry/sdk/trace/span_data.h @@ -0,0 +1,112 @@ +#pragma once + +#include +#include "opentelemetry/core/timestamp.h" +#include "opentelemetry/nostd/string_view.h" +#include "opentelemetry/sdk/trace/recordable.h" +#include "opentelemetry/trace/canonical_code.h" +#include "opentelemetry/trace/span_id.h" +#include "opentelemetry/trace/trace_id.h" + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace sdk +{ +namespace trace +{ +/** + * SpanData is a representation of all data collected by a span. + */ +class SpanData final : public Recordable +{ +public: + /** + * Get the trace id for this span + * @return the trace id for this span + */ + opentelemetry::trace::TraceId GetTraceId() const noexcept { return trace_id_; } + + /** + * Get the span id for this span + * @return the span id for this span + */ + opentelemetry::trace::SpanId GetSpanId() const noexcept { return span_id_; } + + /** + * Get the parent span id for this span + * @return the span id for this span's parent + */ + opentelemetry::trace::SpanId GetParentSpanId() const noexcept { return parent_span_id_; } + + /** + * Get the name for this span + * @return the name for this span + */ + opentelemetry::nostd::string_view GetName() const noexcept { return name_; } + + /** + * Get the status for this span + * @return the status for this span + */ + opentelemetry::trace::CanonicalCode GetStatus() const noexcept { return status_code_; } + + /** + * Get the status description for this span + * @return the description of the the status of this span + */ + opentelemetry::nostd::string_view GetDescription() const noexcept { return status_desc_; } + + /** + * Get the start time for this span + * @return the start time for this span + */ + opentelemetry::core::SystemTimestamp GetStartTime() const noexcept { return start_time_; } + + /** + * Get the duration for this span + * @return the duration for this span + */ + std::chrono::nanoseconds GetDuration() const noexcept { return duration_; } + + void SetIds(opentelemetry::trace::TraceId trace_id, + opentelemetry::trace::SpanId span_id, + opentelemetry::trace::SpanId parent_span_id) noexcept override + { + trace_id_ = trace_id; + span_id_ = span_id; + parent_span_id_ = parent_span_id; + } + + void AddEvent(nostd::string_view name, core::SystemTimestamp timestamp) noexcept override + { + (void)name; + (void)timestamp; + } + + void SetStatus(trace_api::CanonicalCode code, nostd::string_view description) noexcept override + { + status_code_ = code; + status_desc_ = std::string(description); + } + + void SetName(nostd::string_view name) noexcept override { name_ = std::string(name); } + + void SetStartTime(opentelemetry::core::SystemTimestamp start_time) noexcept override + { + start_time_ = start_time; + } + + void SetDuration(std::chrono::nanoseconds duration) noexcept override { duration_ = duration; } + +private: + opentelemetry::trace::TraceId trace_id_; + opentelemetry::trace::SpanId span_id_; + opentelemetry::trace::SpanId parent_span_id_; + core::SystemTimestamp start_time_; + std::chrono::nanoseconds duration_{0}; + std::string name_; + opentelemetry::trace::CanonicalCode status_code_{opentelemetry::trace::CanonicalCode::OK}; + std::string status_desc_; +}; +} // namespace trace +} // namespace sdk +OPENTELEMETRY_END_NAMESPACE diff --git a/sdk/src/trace/simple_processor.h b/sdk/src/trace/simple_processor.h new file mode 100644 index 0000000000..c9cc617fa6 --- /dev/null +++ b/sdk/src/trace/simple_processor.h @@ -0,0 +1,59 @@ +#pragma once + +#include "opentelemetry/sdk/trace/exporter.h" +#include "opentelemetry/sdk/trace/processor.h" + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace sdk +{ +namespace trace +{ +/** + * The simple span processor passes finished recordables to the configured + * SpanExporter, as soon as they are finished. + * + * OnEnd and ForceFlush are no-ops. + */ +class SimpleSpanProcessor : public SpanProcessor +{ +public: + /** + * Initialize a simple span processor. + * @param exporter the exporter used by the span processor + */ + explicit SimpleSpanProcessor(std::unique_ptr &&exporter) noexcept + : exporter_(std::move(exporter)) + {} + + std::unique_ptr MakeRecordable() noexcept override + { + return exporter_->MakeRecordable(); + } + + void OnStart(Recordable &span) noexcept override {} + + void OnEnd(std::unique_ptr &&span) noexcept override + { + nostd::span> batch(&span, 1); + if (exporter_->Export(batch) == ExportResult::kFailure) + { + /* Once it is defined how the SDK does logging, an error should be + * logged in this case. */ + } + } + + void ForceFlush( + std::chrono::microseconds timeout = std::chrono::microseconds(0)) noexcept override + {} + + void Shutdown(std::chrono::microseconds timeout = std::chrono::microseconds(0)) noexcept override + { + exporter_->Shutdown(timeout); + } + +private: + std::unique_ptr exporter_; +}; +} // namespace trace +} // namespace sdk +OPENTELEMETRY_END_NAMESPACE diff --git a/sdk/test/trace/BUILD b/sdk/test/trace/BUILD index 858ff28601..7ba548fd53 100644 --- a/sdk/test/trace/BUILD +++ b/sdk/test/trace/BUILD @@ -1,5 +1,5 @@ cc_test( - name = "default_tracer_provider", + name = "default_tracer_provider_test", srcs = [ "default_tracer_provider_test.cc", ], @@ -8,3 +8,25 @@ cc_test( "@com_google_googletest//:gtest_main", ], ) + +cc_test( + name = "span_data_test", + srcs = [ + "span_data_test.cc", + ], + deps = [ + "//sdk/src/trace", + "@com_google_googletest//:gtest_main", + ], +) + +cc_test( + name = "simple_processor_test", + srcs = [ + "simple_processor_test.cc", + ], + deps = [ + "//sdk/src/trace", + "@com_google_googletest//:gtest_main", + ], +) diff --git a/sdk/test/trace/CMakeLists.txt b/sdk/test/trace/CMakeLists.txt index b12d584eed..ec14273050 100644 --- a/sdk/test/trace/CMakeLists.txt +++ b/sdk/test/trace/CMakeLists.txt @@ -1,4 +1,5 @@ -foreach(testname default_tracer_provider_test) +foreach(testname default_tracer_provider_test span_data_test + simple_processor_test) add_executable(${testname} "${testname}.cc") target_link_libraries(${testname} ${GTEST_BOTH_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT} opentelemetry_trace) diff --git a/sdk/test/trace/simple_processor_test.cc b/sdk/test/trace/simple_processor_test.cc new file mode 100644 index 0000000000..ddeba25fe6 --- /dev/null +++ b/sdk/test/trace/simple_processor_test.cc @@ -0,0 +1,66 @@ +#include "src/trace/simple_processor.h" +#include "opentelemetry/nostd/span.h" +#include "opentelemetry/sdk/trace/span_data.h" + +#include + +using namespace opentelemetry::sdk::trace; + +/** + * A mock exporter that switches a flag once a valid recordable was received. + */ +class MockSpanExporter final : public SpanExporter +{ +public: + MockSpanExporter(std::shared_ptr span_received, + std::shared_ptr shutdown_called) noexcept + : span_received_(span_received), shutdown_called_(shutdown_called) + {} + + std::unique_ptr MakeRecordable() noexcept override + { + return std::unique_ptr(new SpanData); + } + + ExportResult Export( + opentelemetry::nostd::span> &spans) noexcept override + { + for (auto &span : spans) + { + if (span != nullptr) + { + *span_received_ = true; + } + } + + return ExportResult::kSuccess; + } + + void Shutdown(std::chrono::microseconds timeout = std::chrono::microseconds(0)) noexcept override + { + *shutdown_called_ = true; + } + +private: + std::shared_ptr span_received_; + std::shared_ptr shutdown_called_; +}; + +TEST(SimpleSpanProcessor, ToMockSpanExporter) +{ + std::shared_ptr span_received(new bool(false)); + std::shared_ptr shutdown_called(new bool(false)); + std::unique_ptr exporter(new MockSpanExporter(span_received, shutdown_called)); + SimpleSpanProcessor processor(std::move(exporter)); + + auto recordable = processor.MakeRecordable(); + + processor.OnStart(*recordable); + ASSERT_FALSE(*span_received); + + processor.OnEnd(std::move(recordable)); + ASSERT_TRUE(*span_received); + + processor.Shutdown(); + ASSERT_TRUE(*shutdown_called); +} diff --git a/sdk/test/trace/span_data_test.cc b/sdk/test/trace/span_data_test.cc new file mode 100644 index 0000000000..ea8aab446c --- /dev/null +++ b/sdk/test/trace/span_data_test.cc @@ -0,0 +1,48 @@ +#include "opentelemetry/sdk/trace/span_data.h" +#include "opentelemetry/trace/span_id.h" +#include "opentelemetry/trace/trace_id.h" + +#include + +using opentelemetry::sdk::trace::SpanData; + +TEST(SpanData, DefaultValues) +{ + opentelemetry::trace::TraceId zero_trace_id; + opentelemetry::trace::SpanId zero_span_id; + SpanData data; + + ASSERT_EQ(data.GetTraceId(), zero_trace_id); + ASSERT_EQ(data.GetSpanId(), zero_span_id); + ASSERT_EQ(data.GetParentSpanId(), zero_span_id); + ASSERT_EQ(data.GetName(), ""); + ASSERT_EQ(data.GetStatus(), opentelemetry::trace::CanonicalCode::OK); + ASSERT_EQ(data.GetDescription(), ""); + ASSERT_EQ(data.GetStartTime().time_since_epoch(), std::chrono::nanoseconds(0)); + ASSERT_EQ(data.GetDuration(), std::chrono::nanoseconds(0)); +} + +TEST(SpanData, Set) +{ + opentelemetry::trace::TraceId trace_id; + opentelemetry::trace::SpanId span_id; + opentelemetry::trace::SpanId parent_span_id; + opentelemetry::core::SystemTimestamp now(std::chrono::system_clock::now()); + + SpanData data; + data.SetIds(trace_id, span_id, parent_span_id); + data.SetName("span name"); + data.SetStatus(opentelemetry::trace::CanonicalCode::UNKNOWN, "description"); + data.SetStartTime(now); + data.SetDuration(std::chrono::nanoseconds(1000000)); + data.AddEvent("event1", now); + + ASSERT_EQ(data.GetTraceId(), trace_id); + ASSERT_EQ(data.GetSpanId(), span_id); + ASSERT_EQ(data.GetParentSpanId(), parent_span_id); + ASSERT_EQ(data.GetName(), "span name"); + ASSERT_EQ(data.GetStatus(), opentelemetry::trace::CanonicalCode::UNKNOWN); + ASSERT_EQ(data.GetDescription(), "description"); + ASSERT_EQ(data.GetStartTime().time_since_epoch(), now.time_since_epoch()); + ASSERT_EQ(data.GetDuration(), std::chrono::nanoseconds(1000000)); +}