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

Prototype OTel-Go metrics APIv2 proposal #2044

Closed
wants to merge 20 commits into from

Conversation

jmacd
Copy link
Contributor

@jmacd jmacd commented Jun 29, 2021

This is a DRAFT. The goal of this API proposal is to implement open-telemetry/oteps#146 using "modern" metrics terminology in the project. The hypothesis is that the new metrics API can produced without significantly modifying or breaking the existing API, to assist with a smooth transition.

@codecov
Copy link

codecov bot commented Jun 29, 2021

Codecov Report

Merging #2044 (21c2edd) into main (12f737c) will decrease coverage by 0.3%.
The diff coverage is 0.0%.

Impacted file tree graph

@@           Coverage Diff           @@
##            main   #2044     +/-   ##
=======================================
- Coverage   72.8%   72.5%   -0.4%     
=======================================
  Files        166     171      +5     
  Lines       8168    8199     +31     
=======================================
- Hits        5953    5949      -4     
- Misses      1987    2020     +33     
- Partials     228     230      +2     
Impacted Files Coverage Δ
metric2/asyncfloat64/asyncfloat64.go 0.0% <0.0%> (ø)
metric2/asyncint64/asyncint64.go 0.0% <0.0%> (ø)
metric2/meter.go 0.0% <0.0%> (ø)
metric2/syncfloat64/syncfloat64.go 0.0% <0.0%> (ø)
metric2/syncint64/syncint64.go 0.0% <0.0%> (ø)
sdk/metric/refcount_mapped.go 77.7% <0.0%> (-22.3%) ⬇️
sdk/metric/sdk.go 80.2% <0.0%> (-1.2%) ⬇️
... and 2 more

@@ -0,0 +1,53 @@
package asyncfloat64metric
Copy link
Member

Choose a reason for hiding this comment

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

What is the reason for such fine-grained packages separation? What problem does it solve? Would it not make the API less discoverable?

Copy link
Contributor Author

@jmacd jmacd Jun 29, 2021

Choose a reason for hiding this comment

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

I was experimenting, here. @rakyll gave feedback a while back, here, which suggested focusing on the package directory structure and ensuring that the top-level package is a good entry point.

The problem I am trying to address is that we have 2 number types, and 2 calling styles. The number types create parallel interfaces that are essentially identical, and the calling styles create parallel interfaces with certain differences. If you put all four of these interfaces into one package, the godoc becomes difficult to read. My expectation is that users will not have to import these sub-packages, they would declare instruments like:

   myCounter = metricglobal.Meter("library").Int64().Counter("name1")
   myGauge = metricglobal.Meter("library").Asynchronous().Float64().Gauge("name2")

Copy link
Member

Choose a reason for hiding this comment

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

Just giving a few cents (not sure how much help it would be). e.g. github.com/prometheus/client_golang/prometheus has the following description to address the overwhelming godoc:

The number of exported identifiers in this package might appear a bit overwhelming. However, in addition to the basic plumbing shown in the example above, you only need to understand the different metric types and their vector versions for basic usage.

Not an easy problem to address...

Copy link
Contributor

Choose a reason for hiding this comment

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

Would it be prudent to remove some of the repetition?
Like:

  • metric2/asyncmetric/float64/asyncfloat64metric.go
  • metric2/syncmetric/float64/asyncfloat64metric.go

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 like this, will try it out.

@jmacd
Copy link
Contributor Author

jmacd commented Jun 29, 2021

Please note this is an incomplete proposal, a work-in-progress. I put it into a DRAFT PR because it was mentioned in today's Spec SIG meeting. I'm definitely interested in feedback, but it's not at all documented at this time.

Copy link
Contributor

@seanschade seanschade left a comment

Choose a reason for hiding this comment

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

@jmacd thanks for taking a stab at this. My main concern is that much of our instrumentation code will need to be updated to support this new API. I know it isn't stable yet, and that is part of the deal with relying on unstable packages.

Ideally the end user would only need to use the metric, and your changes seem to accomplish that goal.

type Meter struct {
}

type Callback struct {
Copy link
Contributor

Choose a reason for hiding this comment

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

Are you replacing the term Observer with Callback? Is this some sort of Handler? I think handler is a term most Go programmers are familiar with.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks for pointing this out. The Specification is moving to use "Asynchronous" to describe the instruments that use a callback. Yes, this object is a Handler, but the Spec also uses "Callback" to describe the active piece of code that makes observations. We can call it "Handler" in Go if everyone agrees.

I was trying to steer this API toward a lower-level interface than the former API. This Callback is the function-object passed to the BatchObserver instrument in the former API, here:

func (m Meter) NewBatchObserver(callback BatchObserverFunc) BatchObserver {

and the idea is to only support this batch-oriented API for async instruments. Helpers could be provided for singleton observers, and of course this would be way easier with generics.

metric2/batch/batch.go Outdated Show resolved Hide resolved
@@ -0,0 +1,4 @@
package instrument
Copy link
Contributor

Choose a reason for hiding this comment

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

Could this live in the metric package instead?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

There's a cycle if I do this. I'll try and move things around, thanks for pointing this out.

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 have restructured the draft API so that the top-level metric package contains the Instrument and Measurement types, with sub-package meter, meter/asyncint64, meter/asyncfloat64, meter/syncint64, and meter/syncfloat64. I'm using a type named Builder for the instrument constructors, to make it clear that you only need to know sync/async and int64/float64 when building instruments, not when recording measurements.

type Meter struct {
}

func (m Meter) Integer() int64metric.Meter {
Copy link
Contributor

Choose a reason for hiding this comment

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

I like these methods!

@jmacd
Copy link
Contributor Author

jmacd commented Jul 8, 2021

I will apply updates from all the standing recommendations and return with some progress in next week's SIG. This week the U.S. July 4 holiday stood in the way of more development on this PR.

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.

Some high-level comments.

metric2/async/asyncmetric.go Outdated Show resolved Hide resolved
return metric.Measurement{}
}

func (u UpDownCounter) Measure(x int64) metric.Measurement {
Copy link
Member

Choose a reason for hiding this comment

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

What does Measure do? Can it be removed? Maybe consider to be added later?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes. Measure() creates a measurement for batch recording in both synchronous and asynchronous instruments, and it can be added later. My goal in sharing this prototype was to show how it would evolve to support batch measurements since that is an important way to optimize metrics costs. The other way that we know of to optimize at the API level involves binding instruments, which I left out of this proposal (and I consider less important than batch support).

metric2/meter/meter.go Outdated Show resolved Hide resolved
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