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

Observable instruments #162

Merged
merged 4 commits into from
Apr 11, 2023
Merged

Conversation

christiankjaer
Copy link

@christiankjaer christiankjaer commented Mar 30, 2023

Fixes #158
Fixes #159

@iRevive Before I work more on this, how do you think the API looks?

Copy link
Contributor

@iRevive iRevive left a comment

Choose a reason for hiding this comment

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

Hey, thanks for the contribution!

I overlooked a few details regarding the callback signature while making a draft.

There are a few comments from my side regarding the abstraction. The direction looks good and promising!


import cats.effect.Resource

trait AsyncInstrumentBuilder[F[_], Input, Instrument[_[_], _]] {
Copy link
Contributor

@iRevive iRevive Mar 30, 2023

Choose a reason for hiding this comment

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

I'm sorry, I've updated the scope of #158 recently since I spotted that we have ObservableInstrumentBuilder.

We can change it rather than adding a new AsyncInstrumentBuilder.

Copy link
Author

Choose a reason for hiding this comment

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

Of course.


def withUnit(unit: String): Self
def withDescription(description: String): Self
def withCallback(cb: Instrument[F, Input] => F[Unit]): Self
Copy link
Contributor

@iRevive iRevive Mar 30, 2023

Choose a reason for hiding this comment

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

Ah, I see. I missed the Java's signature while making a draft.

We can mimic Java API and add ObservableMeasurement:

trait ObservableMeasurement[F[_], A] {
  def record(value: A, attributes: attributes: Attribute[_]*): F[Unit]
}

Then, the bulidWithCallback can be:

def buildWithCallback(cb: ObservableMeasurement[F, Input] => F[Unit]): Resource[F, Instrument[F, Input]]


def withUnit(unit: String): Self
def withDescription(description: String): Self
def withCallback(cb: Instrument[F, Input] => F[Unit]): Self
Copy link
Contributor

@iRevive iRevive Mar 30, 2023

Choose a reason for hiding this comment

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

There should be no way to create an observable metric without a callback.

So we can merge withCallback and create:

def createWithCallback[A](cb: ObservableMeasurement[F, Input] => F[Unit]): Resource[F, Instrument[F, Input]]

Upd: already changed.

Copy link
Author

Choose a reason for hiding this comment

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

Haha. I did this before your comment :)


trait ObservableGauge[F[_], A] {

def observe(value: A, attributes: Attribute[_]*): F[Unit]
Copy link
Contributor

@iRevive iRevive Mar 30, 2023

Choose a reason for hiding this comment

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

According to Java's API, observable Gauge cannot 'observe' value on-demand, it picks up all values exclusively from the callback:

https://github.com/open-telemetry/opentelemetry-java/blob/4a8850cc64cb407a33eb2131a2ef9cbc44567dcf/api/all/src/main/java/io/opentelemetry/api/metrics/ObservableDoubleGauge.java

So we can keep the trait empty.

Copy link
Author

Choose a reason for hiding this comment

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

Yea. I kinda misused this already existing trait such that you get the instrument in the callback which intuitively makes sense to me.

@christiankjaer
Copy link
Author

If this looks ok-ish, I will add the remaining instruments and try to test it. Also look at the macro stuff.

@iRevive
Copy link
Contributor

iRevive commented Mar 30, 2023

Looks good so far

@@ -16,4 +16,4 @@

package org.typelevel.otel4s.metrics

trait ObservableCounter[F[_], A]
trait ObservableCounter
Copy link
Author

Choose a reason for hiding this comment

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

Not sure what to do with these. It seems strange that they have these type parameters, when we really only use them in the callback. Now I tried to remove them, and that seems to aid in testing at least.

Copy link
Contributor

Choose a reason for hiding this comment

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

I've been thinking about this for a while and I tend to agree.

.observableGauge("gauge")
.withUnit("unit")
.withDescription("description")
.createWithCallback(x => x.observe(42.0))
Copy link
Contributor

Choose a reason for hiding this comment

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

It would be worth adding an Attribute to. To make sure we propagate it downstream.


import org.typelevel.otel4s.Attribute

trait Observable[F[_], A] {
Copy link
Contributor

Choose a reason for hiding this comment

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

The Observable name could be too broad. Also, it may clash with java.util.Observable.

What if we use ObservableMeasurement there?

@christiankjaer christiankjaer changed the title Prototype of observable gauge Observable instruments Apr 1, 2023
@christiankjaer christiankjaer marked this pull request as ready for review April 1, 2023 09:14
@christiankjaer
Copy link
Author

I think this is good for a round of reviews.

Copy link
Contributor

@iRevive iRevive left a comment

Choose a reason for hiding this comment

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

Looks good to me! I have a few minor suggestions regarding the documentation and that's it.

def withUnit(unit: String): Self = this
def withDescription(description: String): Self = this
def createWithCallback(
_cb: ObservableMeasurement[F, Double] => F[Unit]
Copy link
Contributor

Choose a reason for hiding this comment

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

A minor change:

Suggested change
_cb: ObservableMeasurement[F, Double] => F[Unit]
cb: ObservableMeasurement[F, Double] => F[Unit]

@@ -16,4 +16,4 @@

package org.typelevel.otel4s.metrics

trait ObservableCounter[F[_], A]
trait ObservableCounter
Copy link
Contributor

Choose a reason for hiding this comment

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

I've been thinking about this for a while and I tend to agree.

import javax.management.MBeanServer
import javax.management.ObjectName

object ObservableExample extends IOApp.Simple {
Copy link
Contributor

Choose a reason for hiding this comment

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

Nice! Thanks for the relevant example.

def createWithCallback(
cb: ObservableMeasurement[F, Long] => F[Unit]
): Resource[F, ObservableCounter] =
Dispatcher.parallel.flatMap(dispatcher =>
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm wondering which dispatcher is more suitable. The initiator of the callback evaluation is OpenTelemtry internals (it's a MetricProducer#collectAllMetrics, I believe).

Should we guarantee the order?

Copy link
Author

Choose a reason for hiding this comment

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

I'm definitely not an expert on Dispatcher semantics. I'll just change this to sequential, since these callbacks are called quite rarely, so I don't think performance should be a worry.

Copy link
Member

Choose a reason for hiding this comment

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

I don't think it's going to matter a whole lot. My gut feeling is sequential is right.

Comment on lines 48 to 49
/** Creates an instrument with the given `unit` and `description` (if any).
*/
Copy link
Contributor

Choose a reason for hiding this comment

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

It's worth mentioning how the callback should behave. (mostly taken from OpenTelemetry Java doc)

Suggested change
/** Creates an instrument with the given `unit` and `description` (if any).
*/
/** Creates an instrument with the given callback, using `unit` and
* `description` (if any).
*
* The callback will be called when the instrument is being observed.
*
* The callback is expected to abide by the following restrictions:
* - Short-living and (ideally) non-blocking
* - Run in a finite amount of time
* - Safe to call repeatedly, across multiple threads
*
* @param cb
* The callback which observes measurements when invoked
*/

import org.typelevel.otel4s.Attribute

trait ObservableMeasurement[F[_], A] {
def record(value: A, attributes: Attribute[_]*): F[Unit]
Copy link
Contributor

Choose a reason for hiding this comment

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

Scaladoc:

Suggested change
def record(value: A, attributes: Attribute[_]*): F[Unit]
/** Records a value with a set of attributes.
*
* @param value
* the value to record
*
* @param attributes
* the set of attributes to associate with the value
*/
def record(value: A, attributes: Attribute[_]*): F[Unit]

@iRevive
Copy link
Contributor

iRevive commented Apr 3, 2023

@rossabaker @zmccoy

Changing the type constraint from Sync to Async breaks binary compatibility in several places.
Since we are in 0.1 world and have already broken bincompat towards 0.2 by some other changes, should we merge it as is?

import org.typelevel.otel4s.Attribute

trait ObservableMeasurement[F[_], A] {
def record(value: A, attributes: Attribute[_]*): F[Unit]
Copy link
Contributor

Choose a reason for hiding this comment

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

@christiankjaer it would be nice to preserve laziness via macro. It should be nearly identical to HistogramMacro.
Could you take a look there too?

Copy link
Author

Choose a reason for hiding this comment

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

Sure. The reason why I didn't look at the macro is that I kinda assumed that the observable stuff is for instrumenting from the outside, and the noop is then rarely needed. I will take a look.

Copy link
Contributor

@iRevive iRevive Apr 3, 2023

Choose a reason for hiding this comment

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

observable stuff is for instrumenting from the outside

Good point. Since the callback is a function ObservableMeasurement[F, A] => F[Unit], it will never be executed if we use a noop implementation. So there's no need to worry about allocations.

So, we can keep it as-is and skip macro.

Copy link
Contributor

@iRevive iRevive left a comment

Choose a reason for hiding this comment

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

Great work, thanks!

Copy link
Member

@rossabaker rossabaker left a comment

Choose a reason for hiding this comment

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

Yes, I think we're due for an 0.2 and have no problem with the broken compatibility. This is great work.

@iRevive
Copy link
Contributor

iRevive commented Apr 10, 2023

@christiankjaer the docs/mdoc is failing since documentation uses the old methods: OtelJava.forSync instead of OtelJava.forAsync. Once the CI is green we can merge I believe.

@christiankjaer
Copy link
Author

@christiankjaer the docs/mdoc is failing since documentation uses the old methods: OtelJava.forSync instead of OtelJava.forAsync. Once the CI is green we can merge I believe.

Should work now.

@iRevive iRevive merged commit 7a95c3d into typelevel:main Apr 11, 2023
@christiankjaer christiankjaer deleted the ckjaer/observable branch April 11, 2023 07:03
chr12c pushed a commit to chr12c/otel4s that referenced this pull request Jun 26, 2023
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.

Add Gauge instrument Observable metric instruments
3 participants