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

Distributed tracing - implement traceparent from w3c Trace Context #168

Merged
merged 27 commits into from
Apr 9, 2019
Merged

Distributed tracing - implement traceparent from w3c Trace Context #168

merged 27 commits into from
Apr 9, 2019

Conversation

gregkalapos
Copy link
Contributor

  • Implements traceparent from the Trace Context draft
  • Two differences to the draft:
    • The name of the traceparent header is elastic-apm-traceparent and not traceparent. This is what other agents do. Reason: the w3c document is a draft, and it can change. To avoid breaking changes and unwanted behaviour we go with a different header name. Once the draft becomes a standard we can rename the header.
    • We don't implement the tracestate header yet.
  • Adds a WebApi sample, which is called from the SampleAspNetCoreApp via Http.
  • Includes integration test based on the 2 sample apps.

@discostu105
Copy link

To verify compliance, you could run the test-suite that we provided here: https://github.com/w3c/trace-context/tree/master/test

There is a sample app for C#: https://github.com/distributed-tracing/samples/tree/master/csharp

Comments are not part of JSON, so removed those.
@gregkalapos
Copy link
Contributor Author

To verify compliance, you could run the test-suite that we provided here: https://github.com/w3c/trace-context/tree/master/test

There is a sample app for C#: https://github.com/distributed-tracing/samples/tree/master/csharp

Woo, haven't seen that yet. I'll test with it! Thanks!

@SergeyKleyman
Copy link
Contributor

@gregkalapos When you say

We don't implement the tracestate header yet

Do you mean all Elastic APM agents don't implement it or is it just .NET Agent?

@gregkalapos
Copy link
Contributor Author

@gregkalapos When you say

We don't implement the tracestate header yet

Do you mean all Elastic APM agents don't implement it or is it just .NET Agent?

That applies to all APM Agents, none of them implement tracestate currently - we discussed this with @watson, I follow the team here. It wouldn't really help anyway, since the traceparent header name is not standard, so it'd not work with other tools currently anyway. It'll be relevant once we rename the traceparent header to what the standard uses.

@codecov-io
Copy link

codecov-io commented Apr 6, 2019

Codecov Report

Merging #168 into master will increase coverage by 0.49%.
The diff coverage is n/a.

Impacted file tree graph

@@            Coverage Diff             @@
##           master     #168      +/-   ##
==========================================
+ Coverage   84.72%   85.21%   +0.49%     
==========================================
  Files          47       47              
  Lines        1460     1468       +8     
  Branches      263      276      +13     
==========================================
+ Hits         1237     1251      +14     
+ Misses        134      133       -1     
+ Partials       89       84       -5
Impacted Files Coverage Δ
....Apm/DiagnosticListeners/HttpDiagnosticListener.cs 82.47% <0%> (+8.31%) ⬆️

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update bf20fa2...8aa2aa9. Read the comment docs.

Found this during testing with DT - ids should be lowercase (see trace context spec)
Copy link
Contributor

@SergeyKleyman SergeyKleyman left a comment

Choose a reason for hiding this comment

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

I've added a few comments - please take a look.

sample/WebApiSample/Startup.cs Outdated Show resolved Hide resolved
src/Elastic.Apm/DistributedTracing/TraceParent.cs Outdated Show resolved Hide resolved
src/Elastic.Apm/Api/Tracer.cs Show resolved Hide resolved
src/Elastic.Apm/Model/Payload/Transaction.cs Show resolved Hide resolved
src/Elastic.Apm.AspNetCore/ApmMiddleware.cs Outdated Show resolved Hide resolved
src/Elastic.Apm.AspNetCore/ApmMiddleware.cs Outdated Show resolved Hide resolved
src/Elastic.Apm.AspNetCore/ApmMiddleware.cs Outdated Show resolved Hide resolved
test/Elastic.Apm.Tests/DistributedTracingTests.cs Outdated Show resolved Hide resolved
@gregkalapos
Copy link
Contributor Author

Adapted the validation logic based on the W3C Distributed Tracing Validation Service. Thanks for pointing that our @discostu105 - very useful stuff!

Obviously tracestate related tests are failing, everything else is ok. This is fine for us currently. We started discussing to rename the traceparent header and also implementing tracestate, but that'll be a future PR.

I wanna do some perf work on this, and I'll also go through @SergeyKleyman review. Once that's done I think it's ready to merge.

harness listening on http://127.0.0.1:7777
test_multiple_requests_with_illegal_traceparent (__main__.AdvancedTest) ... ok
test_multiple_requests_with_valid_traceparent (__main__.AdvancedTest) ... ok
test_multiple_requests_without_traceparent (__main__.AdvancedTest) ... ok
test_both_traceparent_and_tracestate_missing (__main__.TraceContextTest) ... ok
test_traceparent_duplicated (__main__.TraceContextTest) ... ok
test_traceparent_header_name (__main__.TraceContextTest) ... ok
test_traceparent_header_name_valid_casing (__main__.TraceContextTest) ... ok
test_traceparent_included_tracestate_missing (__main__.TraceContextTest) ... ok
test_traceparent_ows_handling (__main__.TraceContextTest) ... ok
test_traceparent_parent_id_all_zero (__main__.TraceContextTest) ... ok
test_traceparent_parent_id_illegal_characters (__main__.TraceContextTest) ... ok
test_traceparent_parent_id_too_long (__main__.TraceContextTest) ... ok
test_traceparent_parent_id_too_short (__main__.TraceContextTest) ... ok
test_traceparent_trace_flags_illegal_characters (__main__.TraceContextTest) ... ok
test_traceparent_trace_flags_too_long (__main__.TraceContextTest) ... ok
test_traceparent_trace_flags_too_short (__main__.TraceContextTest) ... ok
test_traceparent_trace_id_all_zero (__main__.TraceContextTest) ... ok
test_traceparent_trace_id_illegal_characters (__main__.TraceContextTest) ... ok
test_traceparent_trace_id_too_long (__main__.TraceContextTest) ... ok
test_traceparent_trace_id_too_short (__main__.TraceContextTest) ... ok
test_traceparent_version_0x00 (__main__.TraceContextTest) ... ok
test_traceparent_version_0xcc (__main__.TraceContextTest) ... ok
test_traceparent_version_0xff (__main__.TraceContextTest) ... ok
test_traceparent_version_illegal_characters (__main__.TraceContextTest) ... ok
test_traceparent_version_too_long (__main__.TraceContextTest) ... ok
test_traceparent_version_too_short (__main__.TraceContextTest) ... ok
test_tracestate_all_allowed_characters (__main__.TraceContextTest) ... ERROR
test_tracestate_duplicated_keys (__main__.TraceContextTest) ... ok
test_tracestate_empty_header (__main__.TraceContextTest) ... ERROR
test_tracestate_header_name (__main__.TraceContextTest) ... ok
test_tracestate_header_name_valid_casing (__main__.TraceContextTest) ... ERROR
test_tracestate_included_traceparent_included (__main__.TraceContextTest) ... ERROR
test_tracestate_included_traceparent_missing (__main__.TraceContextTest) ... ok
test_tracestate_key_illegal_characters (__main__.TraceContextTest) ... ok
test_tracestate_key_illegal_vendor_format (__main__.TraceContextTest) ... ok
test_tracestate_key_length_limit (__main__.TraceContextTest) ... ERROR
test_tracestate_member_count_limit (__main__.TraceContextTest) ... ERROR
test_tracestate_multiple_headers_different_keys (__main__.TraceContextTest) ... ERROR
test_tracestate_ows_handling (__main__.TraceContextTest) ... ERROR

@gregkalapos
Copy link
Contributor Author

gregkalapos commented Apr 8, 2019

Got rid of the string -> byte[] -> string conversion, which was basically only used to validate if traceId and parentId only contains hex values.

Perf before:

|                 Method |     Mean |    Error |   StdDev | Gen 0/1k Op | Gen 1/1k Op | Gen 2/1k Op | Allocated Memory/Op |
|----------------------- |---------:|---------:|---------:|------------:|------------:|------------:|--------------------:|
| ParseTraceparentHeader | 804.5 ns | 14.16 ns | 13.24 ns |      0.2279 |           - |           - |             1.41 KB |

Perf after:

|                 Method |     Mean |    Error |   StdDev | Gen 0/1k Op | Gen 1/1k Op | Gen 2/1k Op | Allocated Memory/Op |
|----------------------- |---------:|---------:|---------:|------------:|------------:|------------:|--------------------:|
| ParseTraceparentHeader | 386.2 ns | 7.538 ns | 7.051 ns |      0.0453 |           - |           - |               288 B |

Perf. test scenario:

const string traceParent = "00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01";
var res = TraceParent.TryExtractTraceparent(traceParent, out var traceId, out var parentId, out var traceOptions);

Probably we could still gain a lot by using ReadonlySpan<char>, but I think atm it's not worth optimizing this further, 386.2ns for parsing the header sounds acceptable for now.

@@ -112,7 +106,10 @@ internal static bool TryExtractTraceparent(string traceParentValue, out string t

try
{
traceOptions = StringToByteArray(traceParentValue, VersionAndTraceIdAndSpanIdLength, OptionsLength);
var fields = StringToByteArray(traceParentValue, VersionAndTraceIdAndSpanIdLength, OptionsLength);
Copy link
Contributor

Choose a reason for hiding this comment

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

You don't really need to allocate fields array, right? You can just have (Hex)StringToByte which reads 2 chars from start index in src string.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I did this, but the perf. difference is almost nothing - I measured it. This got it to 383.2 ns... and reference less. I leave it like that, since it's still a plus.

Copy link
Contributor

Choose a reason for hiding this comment

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

Could you please clarify what do you mean by?

since it's still a plus

If you would like to keep (Hex)StringToByteArray for future use you can have it call (Hex)StringToByte to parse each byte.

It's not a big issue - I am just curious what pros you see in leaving the array.
I personally see only cons - it makes the code less readable since the code does something that the spec doesn't require and it has performance impact.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

By "it's a plus" I meant that it's positive for performance, so it's better to keep it that way. So I guess we are on the same page :)

I agree, there is no reason to keep the array, and the current version already got rid of that, so I guess we are ok here. In case we need it later we can add it, but atm we don't need it, so I also think it's better to avoid that unnecessary allocation.

Also added IDisposable to the DistributedTracingAspNetCoreTests, because the DiagnosticListeners did not unsubscribe.
Copy link
Contributor

@SergeyKleyman SergeyKleyman left a comment

Choose a reason for hiding this comment

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

LGTM

@gregkalapos gregkalapos merged commit 04b7e83 into elastic:master Apr 9, 2019
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.

None yet

6 participants