Skip to content

Commit

Permalink
Merge pull request #449 from iRevive/metrics/meter-generic-api-v1
Browse files Browse the repository at this point in the history
metrics: make `Meter` API generic
  • Loading branch information
iRevive authored Jan 28, 2024
2 parents 6638189 + 9fdd3a1 commit d55916a
Show file tree
Hide file tree
Showing 17 changed files with 545 additions and 198 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -116,4 +116,9 @@ object Counter {
}
}

private[otel4s] def fromBackend[F[_], A](b: Backend[F, A]): Counter[F, A] =
new Counter[F, A] {
def backend: Backend[F, A] = b
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -196,4 +196,9 @@ object Histogram {
List(Attribute(CauseKey, "canceled"))
}

private[otel4s] def fromBackend[F[_], A](b: Backend[F, A]): Histogram[F, A] =
new Histogram[F, A] {
def backend: Backend[F, A] = b
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
/*
* Copyright 2022 Typelevel
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.typelevel.otel4s.metrics

@annotation.implicitNotFound(
"Could not find the `MeasurementValue` for ${A}. `Long` and `Double` are available out of the box."
)
sealed trait MeasurementValue[A] {
def contramap[B](f: B => A): MeasurementValue[B]
}

object MeasurementValue {

def apply[A](implicit ev: MeasurementValue[A]): MeasurementValue[A] = ev

@annotation.implicitAmbiguous(
"""
Choose the type of an instrument explicitly, for example:
1) `.counter[Long](...)` or `.counter[Double](...)`
2) `.upDownCounter[Long](...)` or `.upDownCounter[Double](...)`
3) `.histogram[Long](...)` or `.histogram[Double](...)`
4) `.gauge[Long](...)` or `.gauge[Double](...)`
"""
)
implicit val longMeasurementValue: MeasurementValue[Long] =
LongMeasurementValue(identity)

implicit val doubleMeasurementValue: MeasurementValue[Double] =
DoubleMeasurementValue(identity)

private[otel4s] final case class LongMeasurementValue[A](
cast: A => Long
) extends MeasurementValue[A] {
def contramap[B](f: B => A): MeasurementValue[B] =
LongMeasurementValue(cast.compose(f))
}

private[otel4s] final case class DoubleMeasurementValue[A](
cast: A => Double
) extends MeasurementValue[A] {
def contramap[B](f: B => A): MeasurementValue[B] =
DoubleMeasurementValue(cast.compose(f))
}

}
120 changes: 90 additions & 30 deletions core/metrics/src/main/scala/org/typelevel/otel4s/metrics/Meter.scala
Original file line number Diff line number Diff line change
Expand Up @@ -35,46 +35,105 @@ meterProvider
""")
trait Meter[F[_]] {

/** Creates a builder of [[Counter]] instrument that records [[scala.Long]]
* values.
/** Creates a builder of [[Counter]] instrument that records values of type
* `A`.
*
* The [[Counter]] is monotonic. This means the aggregated value is nominally
* increasing.
*
* @note
* the `A` type must be provided explicitly, for example
* `meter.counter[Long]` or `meter.counter[Double]`
*
* @example
* {{{
* val meter: Meter[F] = ???
*
* val doubleCounter: F[Counter[F, Double]] =
* meter.counter[Double]("double-counter").create
*
* val longCounter: F[Counter[F, Long]] =
* meter.counter[Long]("long-counter").create
* }}}
*
* @see
* See [[upDownCounter]] for non-monotonic alternative
*
* @param name
* the name of the instrument
*
* @tparam A
* the type of the measurement. `Long` and `Double` are supported out of
* the box
*/
def counter(name: String): Counter.Builder[F, Long]
def counter[A: MeasurementValue](name: String): Counter.Builder[F, A]

/** Creates a builder of [[Histogram]] instrument that records
* [[scala.Double]] values.
/** Creates a builder of [[Histogram]] instrument that records values of type
* `A`.
*
* [[Histogram]] metric data points convey a population of recorded
* measurements in a compressed format. A histogram bundles a set of events
* into divided populations with an overall event count and aggregate sum for
* all events.
*
* @note
* the `A` type must be provided explicitly, for example
* `meter.histogram[Long]` or `meter.histogram[Double]`
*
* @example
* {{{
* val meter: Meter[F] = ???
*
* val doubleHistogram: F[Histogram[F, Double]] =
* meter.histogram[Double]("double-histogram").create
*
* val longHistogram: F[Histogram[F, Long]] =
* meter.histogram[Long]("long-histogram").create
* }}}
*
* @param name
* the name of the instrument
*
* @tparam A
* the type of the measurement. `Long` and `Double` are supported out of
* the box
*/
def histogram(name: String): Histogram.Builder[F, Double]
def histogram[A: MeasurementValue](name: String): Histogram.Builder[F, A]

/** Creates a builder of [[UpDownCounter]] instrument that records
* [[scala.Long]] values.
/** Creates a builder of [[UpDownCounter]] instrument that records values of
* type `A`.
*
* The [[UpDownCounter]] is non-monotonic. This means the aggregated value
* can increase and decrease.
*
* @note
* the `A` type must be provided explicitly, for example
* `meter.upDownCounter[Long]` or `meter.upDownCounter[Double]`
*
* @example
* {{{
* val meter: Meter[F] = ???
*
* val doubleUpDownCounter: F[UpDownCounter[F, Double]] =
* meter.upDownCounter[Double]("double-up-down-counter").create
*
* val longUpDownCounter: F[UpDownCounter[F, Long]] =
* meter.upDownCounter[Long]("long-up-down-counter").create
* }}}
*
* @see
* See [[counter]] for monotonic alternative
*
* @param name
* the name of the instrument
*
* @tparam A
* the type of the measurement. `Long` and `Double` are supported out of
* the box
*/
def upDownCounter(name: String): UpDownCounter.Builder[F, Long]
def upDownCounter[A: MeasurementValue](
name: String
): UpDownCounter.Builder[F, A]

/** Creates a builder of [[ObservableGauge]] instrument that collects
* [[scala.Double]] values from the given callback.
Expand Down Expand Up @@ -134,36 +193,37 @@ object Meter {
*/
def noop[F[_]](implicit F: Applicative[F]): Meter[F] =
new Meter[F] {
def counter(name: String): Counter.Builder[F, Long] =
new Counter.Builder[F, Long] {
def withUnit(unit: String): Counter.Builder[F, Long] = this
def withDescription(description: String): Counter.Builder[F, Long] =
this
def create: F[Counter[F, Long]] = F.pure(Counter.noop)
def counter[A: MeasurementValue](
name: String
): Counter.Builder[F, A] =
new Counter.Builder[F, A] {
def withUnit(unit: String): Counter.Builder[F, A] = this
def withDescription(description: String): Counter.Builder[F, A] = this
def create: F[Counter[F, A]] = F.pure(Counter.noop)
}

def histogram(
def histogram[A: MeasurementValue](
name: String
): Histogram.Builder[F, Double] =
new Histogram.Builder[F, Double] {
def withUnit(unit: String): Histogram.Builder[F, Double] = this
def withDescription(
description: String
): Histogram.Builder[F, Double] = this
): Histogram.Builder[F, A] =
new Histogram.Builder[F, A] {
def withUnit(unit: String): Histogram.Builder[F, A] = this
def withDescription(description: String): Histogram.Builder[F, A] =
this
def withExplicitBucketBoundaries(
boundaries: BucketBoundaries
): Histogram.Builder[F, Double] =
this
def create: F[Histogram[F, Double]] = F.pure(Histogram.noop)
): Histogram.Builder[F, A] = this
def create: F[Histogram[F, A]] = F.pure(Histogram.noop)
}

def upDownCounter(name: String): UpDownCounter.Builder[F, Long] =
new UpDownCounter.Builder[F, Long] {
def withUnit(unit: String): UpDownCounter.Builder[F, Long] = this
def upDownCounter[A: MeasurementValue](
name: String
): UpDownCounter.Builder[F, A] =
new UpDownCounter.Builder[F, A] {
def withUnit(unit: String): UpDownCounter.Builder[F, A] = this
def withDescription(
description: String
): UpDownCounter.Builder[F, Long] = this
def create: F[UpDownCounter[F, Long]] = F.pure(UpDownCounter.noop)
): UpDownCounter.Builder[F, A] = this
def create: F[UpDownCounter[F, A]] = F.pure(UpDownCounter.noop)
}

def observableGauge(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -131,4 +131,11 @@ object UpDownCounter {
}
}

private[otel4s] def fromBackend[F[_], A](
b: Backend[F, A]
): UpDownCounter[F, A] =
new UpDownCounter[F, A] {
def backend: Backend[F, A] = b
}

}
2 changes: 1 addition & 1 deletion docs/customization/histogram-custom-buckets/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ object HistogramBucketsExample extends IOApp.Simple {
.use { meter =>
for {
random <- Random.scalaUtilRandom[F]
histogram <- meter.histogram("service.work.duration").create
histogram <- meter.histogram[Double]("service.work.duration").create
_ <- work[F](histogram, random).parReplicateA_(50)
} yield ()
}
Expand Down
2 changes: 1 addition & 1 deletion docs/examples/grafana/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -218,7 +218,7 @@ object ApiService {
bananaPercentage: Int
): F[ApiService[F]] =
Meter[F]
.counter("RemoteApi.fruit.count")
.counter[Long]("RemoteApi.fruit.count")
.withDescription("Number of fruits returned by the API.")
.create
.map { remoteApiFruitCount =>
Expand Down
2 changes: 1 addition & 1 deletion docs/examples/honeycomb/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ object TracingExample extends IOApp.Simple {
.flatMap { implicit tracer: Tracer[IO] =>
for {
meter <- otel4s.meterProvider.get("com.service.runtime")
histogram <- meter.histogram("work.execution.duration").create
histogram <- meter.histogram[Double]("work.execution.duration").create
_ <- Work[IO](histogram).doWork
} yield ()
}
Expand Down
2 changes: 1 addition & 1 deletion examples/src/main/scala/HistogramBucketsExample.scala
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ object HistogramBucketsExample extends IOApp.Simple {
.use { meter =>
for {
random <- Random.scalaUtilRandom[F]
histogram <- meter.histogram("service.work.duration").create
histogram <- meter.histogram[Double]("service.work.duration").create
_ <- work[F](histogram, random).parReplicateA_(50)
} yield ()
}
Expand Down
Loading

0 comments on commit d55916a

Please sign in to comment.