Skip to content
63 changes: 63 additions & 0 deletions sdk/include/opentelemetry/sdk/trace/exporter.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
#pragma once

#include <memory>
#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<Recordable> MakeRecordable() noexcept = 0;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this in the interface? Do I need to implement this function in order to implement an exporter?

Copy link
Copy Markdown
Contributor

@rnburn rnburn Apr 14, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's to allow for more efficient implementations.

It's expensive to build up a SpanData-like object that provides an accessor interface. Having this as a customization point, allows for direct serialization approaches where memory is allocated in larger blocks and the serialization is built up eagerly as methods on the tracer are called (I outlined the approach in #2)

See, for example, this benchmark from lightstep's cpp tracer where we compare a custom eager serialization approach (manual) to a SpanData-like appraoch that uses protobuf-generated objects (legacy_manual). The results show that the cost can be substantially less (ranges from 2x-5x in that run). If you look at the profiles (manual vs legacy_manual), you'll see the eager serialization approach saves a lot from avoiding small memory allocations.

But there's a SpanData Recordable in the SDK, so if an implementer doesn't want to provide their own Recordable, they can write MakeRecordable as a stub that returns a SpanData.


/**
* 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<std::unique_ptr<opentelemetry::sdk::trace::Recordable>> &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
63 changes: 63 additions & 0 deletions sdk/include/opentelemetry/sdk/trace/processor.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
#pragma once

#include <chrono>
#include <memory>
#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<Recordable> MakeRecordable() noexcept = 0;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this in the interface? Do I need to implement this function in order to implement an exporter?

Copy link
Copy Markdown
Contributor Author

@pyohannes pyohannes Apr 13, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. For non-optimized cases, the exporter can return a SpanData instance, which derives from Recordable.

There are good descriptions and discussions about this approach in this PR. It's shown in action here.


/**
* 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<Recordable> &&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
24 changes: 24 additions & 0 deletions sdk/include/opentelemetry/sdk/trace/recordable.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -21,6 +23,16 @@ class Recordable
public:
virtual ~Recordable() = default;

/**
Comment on lines 24 to +26
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In other languages Recordable is not an interface but a concrete immutable class.

* 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
Expand All @@ -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
Expand Down
112 changes: 112 additions & 0 deletions sdk/include/opentelemetry/sdk/trace/span_data.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
#pragma once

#include <chrono>
#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_;
};
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Events? Is that TODO?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, for now I left events (as well as attributes, links and resources) out on purpose. I'd like to handle that in a separate PR.

} // namespace trace
} // namespace sdk
OPENTELEMETRY_END_NAMESPACE
59 changes: 59 additions & 0 deletions sdk/src/trace/simple_processor.h
Original file line number Diff line number Diff line change
@@ -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<SpanExporter> &&exporter) noexcept
: exporter_(std::move(exporter))
{}

std::unique_ptr<Recordable> MakeRecordable() noexcept override
{
return exporter_->MakeRecordable();
}

void OnStart(Recordable &span) noexcept override {}

void OnEnd(std::unique_ptr<Recordable> &&span) noexcept override
{
nostd::span<std::unique_ptr<Recordable>> 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<SpanExporter> exporter_;
};
} // namespace trace
} // namespace sdk
OPENTELEMETRY_END_NAMESPACE
24 changes: 23 additions & 1 deletion sdk/test/trace/BUILD
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
cc_test(
name = "default_tracer_provider",
name = "default_tracer_provider_test",
srcs = [
"default_tracer_provider_test.cc",
],
Expand All @@ -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",
],
)
3 changes: 2 additions & 1 deletion sdk/test/trace/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -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)
Expand Down
Loading