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

Implement the keyed-attributes proposal #1631

Merged
merged 21 commits into from
Sep 21, 2020

Conversation

jkwatson
Copy link
Contributor

@jkwatson jkwatson commented Sep 9, 2020

This implements the proposal in #1580 .

Many apologies for the size of this PR. The only thing I can say in my defense is that it removes a net of over 1100 lines of code.
Most of the changes are to test code, where attributes are created extensively. The actual change to the API surface is remarkably small.

Resolves #1580

@jkwatson jkwatson added the API API related issues label Sep 9, 2020
@codecov
Copy link

codecov bot commented Sep 9, 2020

Codecov Report

Merging #1631 into master will increase coverage by 0.11%.
The diff coverage is 91.44%.

Impacted file tree graph

@@             Coverage Diff              @@
##             master    #1631      +/-   ##
============================================
+ Coverage     86.68%   86.79%   +0.11%     
+ Complexity     1402     1384      -18     
============================================
  Files           164      162       -2     
  Lines          5542     5340     -202     
  Branches        553      546       -7     
============================================
- Hits           4804     4635     -169     
+ Misses          542      509      -33     
  Partials        196      196              
Impacted Files Coverage Δ Complexity Δ
...ain/java/io/opentelemetry/trace/DefaultTracer.java 97.61% <ø> (ø) 5.00 <0.00> (ø)
api/src/main/java/io/opentelemetry/trace/Span.java 100.00% <ø> (ø) 0.00 <0.00> (ø)
.../opentelemetry/exporters/otlp/ResourceAdapter.java 100.00% <ø> (ø) 1.00 <0.00> (ø)
...a/io/opentelemetry/exporters/otlp/SpanAdapter.java 95.83% <ø> (ø) 19.00 <0.00> (ø)
...o/opentelemetry/extensions/trace/MessageEvent.java 0.00% <0.00%> (ø) 0.00 <0.00> (ø)
.../main/java/io/opentelemetry/sdk/trace/Sampler.java 100.00% <ø> (ø) 0.00 <0.00> (ø)
...ava/io/opentelemetry/opentracingshim/SpanShim.java 44.11% <25.00%> (+1.26%) 14.00 <0.00> (ø)
...opentelemetry/opentracingshim/SpanBuilderShim.java 53.68% <50.00%> (ø) 22.00 <0.00> (ø)
...java/io/opentelemetry/common/AttributeKeyImpl.java 57.14% <57.14%> (ø) 7.00 <7.00> (?)
...ava/io/opentelemetry/exporters/jaeger/Adapter.java 97.00% <88.88%> (-0.35%) 19.00 <1.00> (-4.00)
... and 20 more

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 c571e96...40f54ae. Read the comment docs.

Copy link
Member

@bogdandrutu bogdandrutu left a comment

Choose a reason for hiding this comment

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

Not sure how much I like it, but I can see the benefits. Can we discuss this Friday during the sig meeting?

}

/**
* Sets a long {@link AttributeValue} into this.
* Sets a long attribute into this.
*
* @return this Builder
*/
public Builder setAttribute(String key, long value) {
Copy link
Member

Choose a reason for hiding this comment

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

Should this change to LongKey, long value?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

My thought was, since these are already type-safe, to keep the easy option in the API, for cases where people don't want to directly use the new keys. The method that takes the generic key should cover the particular, and would end up being the same method signature as this if we change the first parameter to LongKey.

Copy link
Contributor

Choose a reason for hiding this comment

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

Yeah I don't think we need to change these methods to use the keys. We could consider removing these methods - I'm on the fence on how useful these are for users. Keys will always have a significant performance benefit compared to creating a wrapper key every time this is invoked, so removing them reduces the chances of a user not realizing that.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

At the very least, I was thinking we should add some strongly worded language to the javadoc about preferring to use the method that takes the Key.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

added some javadoc to the relevant methods.

Copy link
Contributor

@anuraaga anuraaga left a comment

Choose a reason for hiding this comment

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

Generally looks good to me


@Override
public String toString() {
return "AttributeKeyImpl{" + "key='" + key + '\'' + '}';
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
return "AttributeKeyImpl{" + "key='" + key + '\'' + '}';
return key;

I think just the key is a better toString for keys

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'm on the fence. It will certainly read better, but I wonder if it could be misleading in tests if the failure happens when you're trying to compare to a raw String, and the toStrings would look identical.

Copy link
Contributor

Choose a reason for hiding this comment

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

Admittedly I'm biased because instrumentation tests are groovy and would be able to use the key itself in assertions instead of calling a method. This is not a strong enough reason to pick one or another though so ok with either 😃

Copy link
Contributor Author

Choose a reason for hiding this comment

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

wait...spock uses the toString to do assertions??? That sounds like a terrible idea.

}

/**
* Sets a long {@link AttributeValue} into this.
* Sets a long attribute into this.
*
* @return this Builder
*/
public Builder setAttribute(String key, long value) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Yeah I don't think we need to change these methods to use the keys. We could consider removing these methods - I'm on the fence on how useful these are for users. Keys will always have a significant performance benefit compared to creating a wrapper key every time this is invoked, so removing them reduces the chances of a user not realizing that.

@carlosalberto
Copy link
Contributor

Overall good - I was worried at first at the beginning because we lost some type safety, but if the memory footprint really improves (and maybe we also help the Instrumentation?), I'm ok with it definitely.

@thisthat
Copy link
Member

@jkwatson thank you for the huge amount of work put into this PR! In the last SIG meeting, you said there are fewer allocations with this approach! Do you have the JMH number at hand?

Copy link
Contributor

@iNikem iNikem left a comment

Choose a reason for hiding this comment

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

I reviewed about half of the PR and in do agree with this change in general :)


//////////////////////////////////
// IMPORTANT: the equals/hashcode/compareTo *only* include the key, and not the type,
// so that de-duping of attributes is based on the key, and not also based on the type.
Copy link
Contributor

Choose a reason for hiding this comment

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

Why? Can there be attributes with the same key but different types?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Well... what would that mean, exactly? Certainly with the previous implementation, you get only one value per key. Having multiple values per string-key would be very odd to have to deal with in an Exporter, I think.

Copy link
Contributor

Choose a reason for hiding this comment

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

My though was that if there is NO two attributes with the same key but different type, why it is important to include only key, but not type, into equals/hashcode/compareTo?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Something has to make the replacement of one keyed-value with another (of a different type). This is the implementation that makes that happen. The de-duping relies on the equals/hashcode not including the type.

@jkwatson
Copy link
Contributor Author

jkwatson commented Sep 14, 2020

@jkwatson thank you for the huge amount of work put into this PR! In the last SIG meeting, you said there are fewer allocations with this approach! Do you have the JMH number at hand?

I do. I've been using the SpanPipelineBenchmark for this test, which does exercise attributes, although maybe or maybe not in a super realisitc way. Here is the after/befpre for that (note: don't pay much attention to the timing numbers here; only the allocation numbers should be useful, since I ran these on my laptop):

keyed_attributes:
SpanPipelineBenchmark.runThePipeline_05Threads                                   thrpt    5  3035.071 ± 187.490  ops/ms
SpanPipelineBenchmark.runThePipeline_05Threads:·gc.alloc.rate                    thrpt    5  5427.078 ± 336.116  MB/sec
SpanPipelineBenchmark.runThePipeline_05Threads:·gc.alloc.rate.norm               thrpt    5  2816.001 ±   0.001    B/op
SpanPipelineBenchmark.runThePipeline_05Threads:·gc.churn.PS_Eden_Space           thrpt    5  5408.387 ± 820.544  MB/sec
SpanPipelineBenchmark.runThePipeline_05Threads:·gc.churn.PS_Eden_Space.norm      thrpt    5  2805.580 ± 276.429    B/op
SpanPipelineBenchmark.runThePipeline_05Threads:·gc.churn.PS_Survivor_Space       thrpt    5     0.187 ±   0.114  MB/sec
SpanPipelineBenchmark.runThePipeline_05Threads:·gc.churn.PS_Survivor_Space.norm  thrpt    5     0.097 ±   0.053    B/op
SpanPipelineBenchmark.runThePipeline_05Threads:·gc.count                         thrpt    5    60.000            counts
SpanPipelineBenchmark.runThePipeline_05Threads:·gc.time                          thrpt    5    46.000                ms

Main : 9/11/2020:
SpanPipelineBenchmark.runThePipeline_05Threads                                   thrpt    5  2946.739 ± 165.970  ops/ms
SpanPipelineBenchmark.runThePipeline_05Threads:·gc.alloc.rate                    thrpt    5  5396.947 ± 277.886  MB/sec
SpanPipelineBenchmark.runThePipeline_05Threads:·gc.alloc.rate.norm               thrpt    5  2888.001 ±   0.001    B/op
SpanPipelineBenchmark.runThePipeline_05Threads:·gc.churn.PS_Eden_Space           thrpt    5  5431.879 ± 795.503  MB/sec
SpanPipelineBenchmark.runThePipeline_05Threads:·gc.churn.PS_Eden_Space.norm      thrpt    5  2907.242 ± 467.967    B/op
SpanPipelineBenchmark.runThePipeline_05Threads:·gc.churn.PS_Survivor_Space       thrpt    5     0.245 ±   0.290  MB/sec
SpanPipelineBenchmark.runThePipeline_05Threads:·gc.churn.PS_Survivor_Space.norm  thrpt    5     0.131 ±   0.157    B/op
SpanPipelineBenchmark.runThePipeline_05Threads:·gc.count                         thrpt    5    63.000            counts
SpanPipelineBenchmark.runThePipeline_05Threads:·gc.time                          thrpt    5    48.000                ms

@iNikem
Copy link
Contributor

iNikem commented Sep 15, 2020

Am I reading it right, that this PR reduces allocation rate by 3%? Somehow I expected more :(


AttributeKeyImpl<?> that = (AttributeKeyImpl<?>) o;

return getKey() != null ? getKey().equals(that.getKey()) : that.getKey() == null;
Copy link
Member

Choose a reason for hiding this comment

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

Use https://docs.oracle.com/javase/7/docs/api/java/util/Objects.html#equals(java.lang.Object,%20java.lang.Object)?

Suggested change
return getKey() != null ? getKey().equals(that.getKey()) : that.getKey() == null;
return Objects.equals(getKey(), that.getKey());

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We can't use that in the API, due to java 7/android

Copy link
Member

Choose a reason for hiding this comment

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

This API is definititely in Java 7, check the link I posted 😃


@Override
public int compareTo(AttributeKey o) {
return getKey().compareTo(o.getKey());
Copy link
Member

Choose a reason for hiding this comment

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

Why no null check here? Use Objects.compare?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

For some reason I thought we couldn't use that with java 7/android. Let me take a look.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

updated to be null-safe

@carlosalberto
Copy link
Contributor

Somehow I expected more :(

If this helps Auto-Instrumentation, that's a big win already ;)

@jkwatson
Copy link
Contributor Author

Am I reading it right, that this PR reduces allocation rate by 3%? Somehow I expected more :(

Recall that the benchmark I'm running is creating one of each type of attribute. Only the String attribute has an allocation improvement in this scheme. I would guess that, in real instrumentation, most of the attributes are String-typed, and hence the allocation rate gain would be proportionally much better.

@jkwatson
Copy link
Contributor Author

@carlosalberto @bogdandrutu This is good to go. Any last thoughts/concerns before we merge it?

@Oberon00
Copy link
Member

Oberon00 commented Sep 19, 2020

Hi, it would be nice if you could merge this PR mostly as-is without relying on Java 8 features (features for newer android API levels are fine), as we would like to include this (hopefully last) major change to the tracing API in our Dynatrace fork.

@jkwatson
Copy link
Contributor Author

Hi, it would be nice if you could merge this PR mostly as-is without relying on Java 8 features (features for newer android API levels are fine), as we would like to include this (hopefully last) major change to the tracing API in our Dynatrace fork.

I'm planning on merging first thing on Monday.

@jkwatson
Copy link
Contributor Author

Merging!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
API API related issues
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Proposal: Remove AttributeValue class and implement attribute value type-safety with a type-indicating Key.
8 participants