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

Data classes for exponential histogram prototype (#3550) #3637

Merged

Conversation

jamesmoessis
Copy link
Contributor

  • This addresses the first task from Exponential Histogram Prototype #3550.
  • Adds exponential histogram to sdk.metrics.data.
  • No tests becase there aren't any existing tests for classes in this package
  • This PR should allow work on an aggregator for the exponential histogram to start.

New to OTel contribution so any constructive feedback is welcomed.

@linux-foundation-easycla
Copy link

linux-foundation-easycla bot commented Sep 20, 2021

CLA Signed

The committers are authorized under a signed CLA.

@jsuereth
Copy link
Contributor

One question I have for @anuraaga and @jkwatson (and @jamesmoessis).

Given the cost of exponential histogram generation, would it make more sense for the "data" package to be an interface-only experience and we defer actual storage for an implementation? Specifically, the state of the art seems to be dynamic linked-list or bucket-expansion style classes. I think copying the data out of these representations into the data model may be expensive, looking to see if we can throw ourselves a bone by sticking to interfaces specifically.

@jkwatson
Copy link
Contributor

One question I have for @anuraaga and @jkwatson (and @jamesmoessis).

Given the cost of exponential histogram generation, would it make more sense for the "data" package to be an interface-only experience and we defer actual storage for an implementation? Specifically, the state of the art seems to be dynamic linked-list or bucket-expansion style classes. I think copying the data out of these representations into the data model may be expensive, looking to see if we can throw ourselves a bone by sticking to interfaces specifically.

Seems totally reasonable to me.

@jsuereth
Copy link
Contributor

Also @jamesmoessis Take a look at metrics-testing, where we have "assertions" that help make it easy to write unit tests. Not required in this PR, but definitely have come in handy.

@codecov
Copy link

codecov bot commented Sep 23, 2021

Codecov Report

Merging #3637 (203d87a) into main (bad62ec) will increase coverage by 88.66%.
The diff coverage is 0.00%.

❗ Current head 203d87a differs from pull request most recent head 5908235. Consider uploading reports for the commit 5908235 to get more accurate results
Impacted file tree graph

@@             Coverage Diff             @@
##             main    #3637       +/-   ##
===========================================
+ Coverage        0   88.66%   +88.66%     
- Complexity      0     3698     +3698     
===========================================
  Files           0      446      +446     
  Lines           0    11641    +11641     
  Branches        0     1115     +1115     
===========================================
+ Hits            0    10322    +10322     
- Misses          0      937      +937     
- Partials        0      382      +382     
Impacted Files Coverage Δ
...dk/metrics/data/ExponentialHistogramPointData.java 0.00% <0.00%> (ø)
...io/opentelemetry/context/StrictContextStorage.java 75.00% <0.00%> (ø)
...y/sdk/logging/export/BatchLogProcessorBuilder.java 66.66% <0.00%> (ø)
...y/sdk/metrics/internal/state/MeterSharedState.java 91.04% <0.00%> (ø)
...porter/otlp/internal/metrics/SummaryMarshaler.java 100.00% <0.00%> (ø)
...telemetry/sdk/metrics/SdkMeterProviderBuilder.java 100.00% <0.00%> (ø)
...ntelemetry/sdk/metrics/data/DoubleSummaryData.java 100.00% <0.00%> (ø)
...telemetry/extension/noopapi/NoopOpenTelemetry.java 75.00% <0.00%> (ø)
.../opentelemetry/sdk/metrics/data/DoubleSumData.java 100.00% <0.00%> (ø)
...ntelemetry/sdk/testing/assertj/SpanDataAssert.java 93.43% <0.00%> (ø)
... and 436 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 bad62ec...5908235. Read the comment docs.

@jamesmoessis
Copy link
Contributor Author

Given the cost of exponential histogram generation, would it make more sense for the "data" package to be an interface-only experience and we defer actual storage for an implementation?

I agree, and it's why I've backed off implementing any real logic in this PR. I could probably move it to a pure interface. Is something like this what you are imagining?

public interface ExponentialHistogramData extends Data<ExponentialHistogramPointData> {
  ...
}

---

public interface ExponentialHistogramPointData extends PointData {
  ...
}

Specifically, the state of the art seems to be dynamic linked-list or bucket-expansion style classes.

Which implementations are you referring to here? I would love to have a look and see the best ways of implementing.

@jsuereth
Copy link
Contributor

@jamesmoessis

Is something like this what you are imagining?

Exactly. The requirements I imagine for usefulness are:

  1. A set of interface/methods that make extracting OTLP simple
  2. A way of constructing "dummy" data classes when testing export-only paths
  3. A set of assertj classes to simplify asserting results of instruments (similar to this).

Not suggesting anything but (1) for this specific PR.

Which implementations are you referring to here? I would love to have a look and see the best ways of implementing.

To a lesser extent, you can look at DDSketch , which may share similarities in storage, but not usage.

@jsuereth
Copy link
Contributor

Also @jmacd added more links to prototypes, test code and codegen for constants here: open-telemetry/oteps#179

@jamesmoessis
Copy link
Contributor Author

jamesmoessis commented Sep 27, 2021

@jsuereth thanks for the explanations.

I've changed the abstract classes here to pure interfaces like you suggested. Essentially, a bunch of getters that map directly to the OTLP definitions.

I've left bucket mapping/indexing out because I believe these details are slightly more contentious and could be figured out in the implementations.

I think copying the data out of these representations into the data model may be expensive

Are you envisioning the implementations of these interfaces to be a mutable representation, to avoid copying?

@jsuereth
Copy link
Contributor

Are you envisioning the implementations of these interfaces to be a mutable representation, to avoid copying?

I'd like to leave that possibility open to use, yeah. For initial version feel free to copy-data and just get it working (assuming @jkwatson @anuraaga are on board with that). Using interfaces, hopefully keeps your options open for how to optimise later.

*
* @return the bucket counts.
*/
List<Long> getBucketCounts();
Copy link
Contributor

Choose a reason for hiding this comment

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

Do we think we need Long level numbers for the bucket counts? That's a lot of recordings for a single bucket.

Copy link

@jmacd jmacd Sep 27, 2021

Choose a reason for hiding this comment

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

Copy link
Contributor

Choose a reason for hiding this comment

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

Looking at the details here, I think exposing List<Long> may be... overkill for Java. (@anuraaga will prove me wrong with some fun optimisation around List), but from every example @jmacd lists, I think we want to have the flexibilty to use a primitive array and keep the primitives. That implies exposing a long getBucketCountAt(int idx) vs. an actual list (which is what the Go SDK + NR impl are doing).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It would make sense to use something similar to the WindowedCounterArray backed by the MultiTypeCounterArray from the NrSketch that @jmacd mentioned.

I can replace List<Long> getBucketCounts() with long getBucketCountAt(int idx). Though, it does raise the question in my mind of how the entire collection of counts would be gathered easily. I can't see that in the Go SDK. Am I incorrectly assuming that it's a necessary method to have for aggregation/exporting?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ok, I've changed this to long getBucketCountAt(int index) and updated relevant javadocs.

Copy link
Contributor

Choose a reason for hiding this comment

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

By the way, I expect marshaling to look like

int size = 0;
for (int i = 0; i < getNumBuckets(); i++) {
  size += CodedOutputStream.computeUint32SizeNoTag(getBucketCountAt(i));
}
writeTag(fieldNumber, size)
for (int i = 0; i < getNumBuckets(); i++) {
  writeUint32NoTag(getBucketCountAt(i);
}

Copy link
Contributor

Choose a reason for hiding this comment

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

By the way, just curious if any consideration was given to a sparse array given this comment in the proto

 This field is expected to have many buckets,
    // especially zeros, so uint64 has been selected to ensure
    // varint encoding.
repeated fixed32 bucket_indexes;
repeated fixed64 bucket_counts;

No need to write out zeros

Copy link
Contributor Author

@jamesmoessis jamesmoessis Sep 29, 2021

Choose a reason for hiding this comment

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

@anuraaga @jsuereth @jkwatson I've made a few changes:

I've removed the List<Long> and replaced it with these three methods with the aim of being flexible for implementations.

  • long getBucketCountAt(int index)
  • int getNumBuckets()
  • int getStartIndex()

@anuraaga so your for loop would look like

for (int i = getStartIndex(); i < getNumBuckets(); i++) {
  size += CodedOutputStream.computeUint32SizeNoTag(getBucketCountAt(i));
}

EDIT: going to move back to List<Long> as per below conversation.

* @param index signed int corresponding to the relevant bucket.
* @return the number of measurements in the bucket.
*/
long getBucketCountAt(int index);
Copy link
Contributor

Choose a reason for hiding this comment

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

Oh - I think we need the number of buckets (the upper limit of index) to be able to actually iterate this.

Copy link
Contributor Author

@jamesmoessis jamesmoessis Sep 28, 2021

Choose a reason for hiding this comment

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

Probably needs to be the upper_limit_index - lower_limit_index since the index can be negative and doesn't always start from 0. It might be easier to implement or expose an Iterable, I would like to hear your thoughts on 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.

As mentioned above, I've added getNumBuckets() and getStartIndex() to solve for this.

Copy link
Contributor

@anuraaga anuraaga Sep 29, 2021

Choose a reason for hiding this comment

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

Thanks for iterating on this - I'm sorry for the back and forth, but this complexity of the indexes makes me prefer the iterable a lot then. Can we go back to List<Long>? Sorry about that.

We'll just cast to an internal hypothetical LongList that stores and allows iterating on long[] in the exporters so it's still efficient.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

LongList is neat, that sounds good to me. I'll make the changes tomorrow.

@jamesmoessis
Copy link
Contributor Author

@jkwatson @anuraaga @jsuereth I've pushed the change to move back to List<Long> getBucketCounts() as discussed. As mentioned, this can still be optimised in the implementation.

If there's no other points of discussion, I think this PR is in its final form.

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.

Thanks @jamesmoessis - let me go ahead and merge this as if anything comes up we can easily follow up

@anuraaga anuraaga merged commit 694ac3f into open-telemetry:main Sep 30, 2021
This was referenced Dec 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.

5 participants