Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Zipkin exporter #471

Merged
merged 48 commits into from
Mar 3, 2021
Merged

Zipkin exporter #471

merged 48 commits into from
Mar 3, 2021

Conversation

lalitb
Copy link
Member

@lalitb lalitb commented Dec 18, 2020

Implementation of zipkin exporter based on mapping defined here:

https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/trace/sdk_exporters/zipkin.md

The code is ready for review while I am adding below changes in this PR:

  • Adding nlohmann/json as submodule to enable cmake build.
  • Create bazel build definition

A separate PR would be raised to add an example for how to run it against zipkin server ( under examples/zipkin)

The PR is dependent on merging of below two PRs and currently includes those changes also to enable it build successfully:
1 Sending sync request through http_client: #448
2. Http client factory: #445

@lalitb lalitb requested a review from a team December 18, 2020 14:58
@codecov
Copy link

codecov bot commented Dec 18, 2020

Codecov Report

Merging #471 (7c62e73) into main (7706c6d) will increase coverage by 0.12%.
The diff coverage is 94.36%.

Impacted file tree graph

@@            Coverage Diff             @@
##             main     #471      +/-   ##
==========================================
+ Coverage   94.22%   94.34%   +0.12%     
==========================================
  Files         187      189       +2     
  Lines        8882     8949      +67     
==========================================
+ Hits         8369     8443      +74     
+ Misses        513      506       -7     
Impacted Files Coverage Δ
...nclude/opentelemetry/ext/http/client/http_client.h 93.33% <ø> (ø)
...include/opentelemetry/ext/http/common/url_parser.h 91.11% <91.11%> (ø)
...ntelemetry/ext/http/client/curl/http_client_curl.h 92.30% <100.00%> (+10.57%) ⬆️
ext/test/http/curl_http_test.cc 94.62% <100.00%> (+0.27%) ⬆️
ext/test/http/url_parser_test.cc 100.00% <100.00%> (ø)


const int kAttributeValueSize = 14;

void Recordable::SetIds(trace::TraceId trace_id,
Copy link
Contributor

Choose a reason for hiding this comment

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

Is this function reusable by other exporters, would they use the same field names? id , parentId, traceId - should this be moved into common utility function within the SDK?

@mishal23 - you need to borrow this same code for ETW exporter, to populate the actual IDs in your PR.

Copy link
Member Author

@lalitb lalitb Dec 22, 2020

Choose a reason for hiding this comment

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

@maxgolov - Not sure if we can move it to common. As different exporters expect these fields in different formats. E.g, Zipkin (json) as hex chars of 16/32 length, Jaeger (binary thrift) as uint64_t.

Copy link
Contributor

Choose a reason for hiding this comment

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

The implementation of Recordable is exporter specific. As exporter could choose different underlying storage, agree that it is unnecessary to provide a common implementation.

lalitb and others added 2 commits December 21, 2020 22:57
Co-authored-by: Max Golovanov <max.golovanov+github@gmail.com>
/**
* Create an ZipkinExporter using the given options.
*/
ZipkinExporter(const ZipkinExporterOptions &options);
Copy link
Member

Choose a reason for hiding this comment

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

Use explicit for all 1 arguments constructor. I think it is a bad practice to rely on converting constructors (https://en.cppreference.com/w/cpp/language/converting_constructor) in general unless explicitly wanted to have this behavior like std::string_view.

/cc @maxgolov @lalitb @g-easy @jsuereth I see that in other places you do not use explicit, what is the overall rule?

Copy link
Member Author

@lalitb lalitb Jan 14, 2021

Choose a reason for hiding this comment

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

Agree. Will fix it. We follow google c++ style guide:
(https://google.github.io/styleguide/cppguide.html#Implicit_Conversions):
Do not define implicit conversions. Use the explicit keyword for conversion operators and single-argument constructors.

Also, would be good to add cpplint CI check to catch these error.

Copy link
Member Author

Choose a reason for hiding this comment

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

This is fixed for zipkin. Will have separate PR for adding explicit keyword at other places.

Comment on lines 16 to 19
find_package(CURL REQUIRED)
if(CURL_FOUND)
add_compile_definitions(WITH_CURL)
endif()
Copy link
Contributor

Choose a reason for hiding this comment

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

If this REQUIRED argument is given, we can remove the if condition, as configuration will abort anyway if CURL is not found.

Copy link
Member Author

Choose a reason for hiding this comment

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

Thank you. Have removed the REQUIRED argument, as it should be possible to run zipkin with other http client libraries.

/**
* The Zipkin exporter exports span data in JSON format as expected by Zipkin
*/
class ZipkinExporter final : public trace_sdk::SpanExporter, public http_client::EventHandler
Copy link
Contributor

Choose a reason for hiding this comment

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

Why do you derive from EventHandler here?

Copy link
Member Author

Choose a reason for hiding this comment

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

Good catch. It is not needed, and so removed.

{
return sdk::trace::ExportResult::kFailure;
}
auto session = http_session_manager_->CreateSession(url_parser_.host_, url_parser_.port_);
Copy link
Contributor

Choose a reason for hiding this comment

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

Why do we create a new session for each call to Export? I see sessions are stored in the session manager. When are those sessions cleaned up? Will this cause an implicit memory leak (all past sessions being kept in the session manager)?

Copy link
Member Author

Choose a reason for hiding this comment

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

Session object is confusing, as there is no actual session created for HTTP ( being stateless). I have simplified the http client library as part of #448 , and using that here:

  HttpClientSync httpClient;
  auto result = httpClient.Post(url, data, headers); // GET request
  if (result){
    auto response = result.GetResponse();
  } else {
    std::cout << result.GetSessionState();
  }

Base automatically changed from master to main January 26, 2021 00:46
@Muchene
Copy link
Contributor

Muchene commented Feb 12, 2021

@lalitb
`zipkin::ZipkinExporterOptions options;
options.endpoint="http://" + ip_str + ":" + std::to_string(port) + "/api/v2/spans";
std::cout << "options " << options.endpoint << std::endl;
new_trace = (struct mva_telemetry_trace *) mal_calloc(1,sizeof(*new_trace));

const size_t max_export_batch_size = 10;
const size_t max_queue_size = max_export_batch_size * 2 + 5;
const std::chrono::milliseconds schedule_delay_millis = std::chrono::milliseconds(30000);
auto exporter = std::unique_ptr<sdktrace::SpanExporter>( new zipkin::ZipkinExporter(options));
auto processor = std::shared_ptr<sdktrace::SpanProcessor>(new sdktrace::BatchSpanProcessor(
  std::move(exporter), max_queue_size, schedule_delay_millis, max_export_batch_size));
auto provider = nostd::shared_ptr<opentelemetry::trace::TracerProvider>(
    new sdktrace::TracerProvider(processor));
// Set the global trace provider
opentelemetry::trace::Provider::SetTracerProvider(provider);`

Segfault occurs when creating the exporter.

@lalitb
Copy link
Member Author

lalitb commented Feb 15, 2021

@lalitb
`zipkin::ZipkinExporterOptions options;
options.endpoint="http://" + ip_str + ":" + std::to_string(port) + "/api/v2/spans";
std::cout << "options " << options.endpoint << std::endl;
new_trace = (struct mva_telemetry_trace *) mal_calloc(1,sizeof(*new_trace));

const size_t max_export_batch_size = 10;
const size_t max_queue_size = max_export_batch_size * 2 + 5;
const std::chrono::milliseconds schedule_delay_millis = std::chrono::milliseconds(30000);
auto exporter = std::unique_ptr<sdktrace::SpanExporter>( new zipkin::ZipkinExporter(options));
auto processor = std::shared_ptr<sdktrace::SpanProcessor>(new sdktrace::BatchSpanProcessor(
  std::move(exporter), max_queue_size, schedule_delay_millis, max_export_batch_size));
auto provider = nostd::shared_ptr<opentelemetry::trace::TracerProvider>(
    new sdktrace::TracerProvider(processor));
// Set the global trace provider
opentelemetry::trace::Provider::SetTracerProvider(provider);`

Segfault occurs when creating the exporter.

Thanks for the snippet @Muchene . though I am not able to reproduce the issue, the problem could be related to order of member initialization. For now, I have changed the order of member variable definition in ZipkinExporter to be in consistent with the order of their initialization in constructor initializers. Hopefully it helps. Do let me know once you try it again.

@Muchene
Copy link
Contributor

Muchene commented Feb 16, 2021

@lalitb Will try and let you know. One more thing

void Recordable::AddEvent(nostd::string_view name, core::SystemTimestamp timestamp, const common::KeyValueIterable &attributes) noexcept { nlohmann::json annotations = nlohmann::json::object(); // empty object attributes.ForEachKeyValue([&](nostd::string_view key, common::AttributeValue value) noexcept { PopulateAttribute(annotations, key, value); return true; }); span_["annotations"][name.data()]["value"] = annotations; span_["annotations"]["timestamp"] = std::chrono::duration_cast<std::chrono::milliseconds>(timestamp.time_since_epoch()).count(); }
may be wrong as annotations are expected to be an array by zipkin.

@lalitb
Copy link
Member Author

lalitb commented Feb 25, 2021

@lalitb Will try and let you know. One more thing

void Recordable::AddEvent(nostd::string_view name, core::SystemTimestamp timestamp, const common::KeyValueIterable &attributes) noexcept { nlohmann::json annotations = nlohmann::json::object(); // empty object attributes.ForEachKeyValue([&](nostd::string_view key, common::AttributeValue value) noexcept { PopulateAttribute(annotations, key, value); return true; }); span_["annotations"][name.data()]["value"] = annotations; span_["annotations"]["timestamp"] = std::chrono::duration_cast<std::chrono::milliseconds>(timestamp.time_since_epoch()).count(); }
may be wrong as annotations are expected to be an array by zipkin.

@Muchene - Thanks for reporting the issue. Have chaged the annotations to array now as expected by Zipkin Server. Please let know if you still see any issues here.

@lalitb
Copy link
Member Author

lalitb commented Mar 2, 2021

@open-telemetry/cpp-approvers - For this PR, all the comments have been resolved, and any incremental change can be applied later. It's planned to be merged today eod unless there are any concerns raised.

@@ -1,3 +1,17 @@
# Copyright 2021, OpenTelemetry Authors
Copy link
Member

Choose a reason for hiding this comment

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

Somehow I haven't noticed this before...
Do we need to include the actual year, or we can do something simpler like https://github.com/open-telemetry/opentelemetry-dotnet/blob/381908c089aac4fa9a2056d6bbf465b9853b81b8/src/OpenTelemetry/AssemblyInfo.cs#L2?

Copy link
Member Author

Choose a reason for hiding this comment

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

Not sure where I copied from. Have fixed it now. Thanks for the comment.

attribute[key.data()].push_back(val);
}
}
else if (nostd::holds_alternative<nostd::span<const nostd::string_view>>(value))
Copy link
Member

Choose a reason for hiding this comment

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

Just curious about the perf implication of these if-else checks, not sure if a small jump table would make it faster. Not a blocker for this PR though.

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes, we are doing these variant checks at multiple places, would be better to use dispatch table throughout. Will add a ticket for same.

Copy link
Member

@reyang reyang left a comment

Choose a reason for hiding this comment

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

LGTM.


} // namespace zipkin
} // namespace exporter
OPENTELEMETRY_END_NAMESPACE
Copy link
Member

Choose a reason for hiding this comment

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

nit: I noticed some files have missed the blank line before EOF.

Copy link
Member Author

Choose a reason for hiding this comment

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

Good catch. Have added blank line at all missing places.

@lalitb lalitb merged commit 71d5b1d into open-telemetry:main Mar 3, 2021
@lalitb lalitb mentioned this pull request Apr 19, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants