diff --git a/exporters/trace/gcp_exporter/BUILD b/exporters/trace/gcp_exporter/BUILD index 1beea71..6013dde 100644 --- a/exporters/trace/gcp_exporter/BUILD +++ b/exporters/trace/gcp_exporter/BUILD @@ -1,9 +1,42 @@ +# Copyright 2020, OpenTelemetry Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + licenses(["notice"]) # Apache License 2.0 package(default_visibility = ["//visibility:public"]) # Libraries # ========================================================================= # + +cc_library( + name = "recordable", + srcs = [ + "internal/recordable.cc", + ], + hdrs = [ + "recordable.h", + ], + deps = [ + "@io_opentelemetry_cpp//api", + "@io_opentelemetry_cpp//sdk/src/trace", + "@com_github_grpc_grpc//:grpc++", + "@com_google_googleapis//google/devtools/cloudtrace/v2:cloudtrace_cc_proto", + "@com_google_googleapis//google/devtools/cloudtrace/v2:cloudtrace_cc_grpc" + ], +) + + cc_library( name = "gcp_exporter", srcs = ["internal/gcp_exporter.cc"], @@ -14,7 +47,7 @@ cc_library( "@io_opentelemetry_cpp//sdk/src/common:random", "@com_github_grpc_grpc//:grpc++", "@com_google_googleapis//google/devtools/cloudtrace/v2:cloudtrace_cc_grpc", - "@com_google_googleapis//google/devtools/cloudtrace/v2:cloudtrace_cc_proto", + "@com_google_googleapis//google/devtools/cloudtrace/v2:cloudtrace_cc_proto" ], ) @@ -31,3 +64,14 @@ cc_test( "@com_google_googletest//:gtest_main" ], ) + +cc_test( + name = "recordable_test", + srcs = ["internal/recordable_test.cc"], + deps = [ + ":recordable", + "@io_opentelemetry_cpp//sdk/src/trace", + "@io_opentelemetry_cpp//api", + "@com_google_googletest//:gtest_main" + ], +) diff --git a/exporters/trace/gcp_exporter/internal/recordable.cc b/exporters/trace/gcp_exporter/internal/recordable.cc new file mode 100644 index 0000000..e70d182 --- /dev/null +++ b/exporters/trace/gcp_exporter/internal/recordable.cc @@ -0,0 +1,106 @@ +#include "exporters/trace/gcp_exporter/recordable.h" +#include + + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace exporter +{ +namespace gcp +{ + +void Recordable::SetIds(trace::TraceId trace_id, + trace::SpanId span_id, + trace::SpanId parent_span_id) noexcept +{ + std::array hex_trace_buf; + trace_id.ToLowerBase16(hex_trace_buf); + const std::string hex_trace(hex_trace_buf.data(), 2*trace::TraceId::kSize); + + std::array hex_span_buf; + span_id.ToLowerBase16(hex_span_buf); + const std::string hex_span(hex_span_buf.data(), 2*trace::SpanId::kSize); + + std::array hex_parent_span_buf; + parent_span_id.ToLowerBase16(hex_parent_span_buf); + const std::string hex_parent_span(hex_parent_span_buf.data(), 2*trace::SpanId::kSize); + + // Get Project Id + const std::string project_id(getenv(kGCPEnvVar)); + + span_.set_name(kProjectsPathStr + project_id + kTracesPathStr + hex_trace + kSpansPathStr + hex_span); + span_.set_span_id(hex_span); + span_.set_parent_span_id(hex_parent_span); +} + + +void Recordable::SetAttribute(nostd::string_view key, + const common::AttributeValue &&value) noexcept +{ + // Get the protobuf span's map + auto map = span_.mutable_attributes()->mutable_attribute_map(); + + if(nostd::holds_alternative(value)) + { + (*map)[std::string(key)].set_bool_value(nostd::get(value)); + } + else if (nostd::holds_alternative(value)) + { + (*map)[std::string(key)].set_int_value(nostd::get(value)); + } + else if (nostd::holds_alternative(value)) + { + // TODO: Truncate string to 128 bytes + std::string value_str = std::string(nostd::get(value)); + (*map)[std::string(key)].mutable_string_value()->set_value(value_str); + } +} + + +void Recordable::AddEvent(nostd::string_view name, core::SystemTimestamp timestamp) noexcept +{ + (void)name; +} + + +void Recordable::SetStatus(trace::CanonicalCode code, nostd::string_view description) noexcept +{ + (void)code; + (void)description; +} + + +void Recordable::SetName(nostd::string_view name) noexcept +{ + // TODO: Truncate string to 128 bytes + span_.mutable_display_name()->set_value(std::string(name)); +} + + +void Recordable::SetStartTime(opentelemetry::core::SystemTimestamp start_time) noexcept +{ + const std::chrono::nanoseconds unix_time_nanoseconds(start_time.time_since_epoch().count()); + const auto seconds = std::chrono::duration_cast(unix_time_nanoseconds); + span_.mutable_start_time()->set_seconds(seconds.count()); + span_.mutable_start_time()->set_nanos(unix_time_nanoseconds.count()- + std::chrono::duration_cast(seconds).count()); +} + + +void Recordable::SetDuration(std::chrono::nanoseconds duration) noexcept +{ + const std::chrono::nanoseconds start_time_nanos(span_.start_time().nanos()); + const std::chrono::seconds start_time_seconds(span_.start_time().seconds()); + const std::chrono::nanoseconds unix_end_time( + std::chrono::duration_cast(start_time_seconds).count() + + start_time_nanos.count() + + duration.count()); + const auto seconds = std::chrono::duration_cast(unix_end_time); + span_.mutable_end_time()->set_seconds(seconds.count()); + span_.mutable_end_time()->set_nanos( + unix_end_time.count()- + std::chrono::duration_cast(seconds).count()); +} + +} // namespace gcp +} // namespace exporter +OPENTELEMETRY_END_NAMESPACE \ No newline at end of file diff --git a/exporters/trace/gcp_exporter/internal/recordable_test.cc b/exporters/trace/gcp_exporter/internal/recordable_test.cc new file mode 100644 index 0000000..84eac54 --- /dev/null +++ b/exporters/trace/gcp_exporter/internal/recordable_test.cc @@ -0,0 +1,119 @@ +#include "exporters/trace/gcp_exporter/recordable.h" +#include + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace exporter +{ +namespace gcp +{ + +TEST(Recordable, TestSetAttribute) +{ + Recordable rec; + + // Set 'bool' type + const nostd::string_view bool_key = "bool_key"; + const common::AttributeValue bool_value = true; + rec.SetAttribute(bool_key, std::move(bool_value)); + + // Set 'integer' type + const nostd::string_view int_key = "int_key"; + const int64_t seven = 7; + const common::AttributeValue int_value = seven; + rec.SetAttribute(int_key, std::move(int_value)); + + // Set 'string' type + const nostd::string_view string_key = "string_key"; + const common::AttributeValue string_value = "test"; + rec.SetAttribute(string_key, std::move(string_value)); + + auto attr_map = rec.span().attributes().attribute_map(); + + EXPECT_TRUE(attr_map["bool_key"].bool_value()); + EXPECT_EQ(seven, attr_map["int_key"].int_value()); + EXPECT_EQ("test", attr_map["string_key"].string_value().value()); +} + +TEST(Recordable, TestSetIds) +{ + setenv("GOOGLE_CLOUD_PROJECT_ID", "test_project", 1); + + const opentelemetry::trace::TraceId trace_id( + std::array( + {0, 1, 0, 2, 1, 3, 1, 4, 1, 5, 1, 6, 3, 7, 0, 0})); + + const opentelemetry::trace::SpanId span_id( + std::array( + {1, 2, 3, 4, 5, 6, 7, 8})); + + const opentelemetry::trace::SpanId parent_span_id( + std::array( + {4, 5, 0, 1, 1, 1, 1, 3})); + + Recordable rec; + + rec.SetIds(trace_id, span_id, parent_span_id); + + EXPECT_EQ("projects/test_project/traces/00010002010301040105010603070000/spans/0102030405060708", + rec.span().name()); + EXPECT_EQ("0102030405060708", rec.span().span_id()); + EXPECT_EQ("0405000101010103", rec.span().parent_span_id()); +} + + +TEST(Recordable, TestSetName) +{ + Recordable rec; + const nostd::string_view expected_name = "Test Span"; + rec.SetName(expected_name); + EXPECT_EQ(expected_name, rec.span().display_name().value()); +} + + +TEST(Recordable, TestSetStartTime) +{ + Recordable rec; + + const std::chrono::system_clock::time_point start_time = std::chrono::system_clock::now(); + const core::SystemTimestamp start_timestamp(start_time); + + const int64_t expected_unix_start_time = + std::chrono::duration_cast(start_time.time_since_epoch()).count(); + + rec.SetStartTime(start_timestamp); + + const std::chrono::nanoseconds start_time_nanos(rec.span().start_time().nanos()); + const std::chrono::seconds start_time_seconds(rec.span().start_time().seconds()); + const std::chrono::nanoseconds unix_start_time( + std::chrono::duration_cast(start_time_seconds).count() + + start_time_nanos.count()); + + EXPECT_EQ(expected_unix_start_time, unix_start_time.count()); +} + + +TEST(Recordable, TestSetDuration) +{ + Recordable rec; + + const std::chrono::system_clock::time_point start_time = std::chrono::system_clock::now(); + const core::SystemTimestamp start_timestamp(start_time); + const std::chrono::nanoseconds duration(10); + + const int64_t expected_unix_end_time = start_timestamp.time_since_epoch().count() + duration.count(); + + rec.SetStartTime(start_timestamp); + rec.SetDuration(duration); + + const std::chrono::nanoseconds end_time_nanos(rec.span().end_time().nanos()); + const std::chrono::seconds end_time_seconds(rec.span().end_time().seconds()); + const std::chrono::nanoseconds unix_end_time( + std::chrono::duration_cast(end_time_seconds).count() + + end_time_nanos.count()); + + EXPECT_EQ(expected_unix_end_time, unix_end_time.count()); +} + +} // namespace gcp +} // namespace exporter +OPENTELEMETRY_END_NAMESPACE \ No newline at end of file diff --git a/exporters/trace/gcp_exporter/recordable.h b/exporters/trace/gcp_exporter/recordable.h new file mode 100644 index 0000000..0b00c16 --- /dev/null +++ b/exporters/trace/gcp_exporter/recordable.h @@ -0,0 +1,48 @@ +#pragma once + +#include "google/devtools/cloudtrace/v2/tracing.grpc.pb.h" +#include "opentelemetry/sdk/trace/recordable.h" +#include "opentelemetry/version.h" +#include "opentelemetry/nostd/variant.h" + + +constexpr char kProjectsPathStr[] = "projects/"; +constexpr char kTracesPathStr[] = "/traces/"; +constexpr char kSpansPathStr[] = "/spans/"; +constexpr char kGCPEnvVar[] = "GOOGLE_CLOUD_PROJECT_ID"; + + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace exporter +{ +namespace gcp +{ +class Recordable final : public sdk::trace::Recordable +{ +public: + const google::devtools::cloudtrace::v2::Span &span() const noexcept { return span_; } + + void SetIds(trace::TraceId trace_id, + trace::SpanId span_id, + trace::SpanId parent_span_id) noexcept override; + + void SetAttribute(nostd::string_view key, + const opentelemetry::common::AttributeValue &&value) noexcept override; + + void AddEvent(nostd::string_view name, core::SystemTimestamp timestamp) noexcept override; + + void SetStatus(trace::CanonicalCode code, nostd::string_view description) noexcept override; + + void SetName(nostd::string_view name) noexcept override; + + void SetStartTime(opentelemetry::core::SystemTimestamp start_time) noexcept override; + + void SetDuration(std::chrono::nanoseconds duration) noexcept override; + +private: + google::devtools::cloudtrace::v2::Span span_; +}; + +} // gcp +} // exporter +OPENTELEMETRY_END_NAMESPACE \ No newline at end of file