Skip to content

Commit

Permalink
Merge pull request #644 from iRevive/sdk-metrics/metric-readers-autoc…
Browse files Browse the repository at this point in the history
…onfigure

sdk-metrics: add `MetricReadersAutoConfigure`
  • Loading branch information
iRevive authored Apr 24, 2024
2 parents 3e63ed8 + 9a3849a commit 6b0a67a
Show file tree
Hide file tree
Showing 9 changed files with 324 additions and 27 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
/*
* Copyright 2024 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.sdk.metrics.autoconfigure

import cats.effect.Resource
import cats.effect.Temporal
import cats.effect.std.Console
import cats.syntax.foldable._
import cats.syntax.functor._
import cats.syntax.traverse._
import org.typelevel.otel4s.sdk.autoconfigure.AutoConfigure
import org.typelevel.otel4s.sdk.autoconfigure.Config
import org.typelevel.otel4s.sdk.metrics.exporter.MetricExporter
import org.typelevel.otel4s.sdk.metrics.exporter.MetricReader

import scala.concurrent.duration._

/** Autoconfigures
* [[org.typelevel.otel4s.sdk.metrics.exporter.MetricReader MetricReader]]s.
*
* The configuration options:
* {{{
* | System property | Environment variable | Description |
* |-----------------------------|-----------------------------|------------------------------------------------------------------------------------|
* | otel.metric.export.interval | OTEL_METRIC_EXPORT_INTERVAL | The time interval between the start of two export attempts. Default is `1 minute`. |
* | otel.metric.export.timeout | OTEL_METRIC_EXPORT_TIMEOUT | Maximum allowed time to export data. Default is `30 seconds`. |
* }}}
*
* @see
* [[https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/configuration/sdk-environment-variables.md#periodic-exporting-metricreader]]
*
* @param exporters
* the exporters to use with metric readers
*/
private final class MetricReadersAutoConfigure[F[_]: Temporal: Console](
exporters: Set[MetricExporter[F]]
) extends AutoConfigure.WithHint[F, Vector[MetricReader[F]]](
"MetricReaders",
MetricReadersAutoConfigure.ConfigKeys.All
) {

import MetricReadersAutoConfigure.ConfigKeys
import MetricReadersAutoConfigure.Defaults

protected def fromConfig(
config: Config
): Resource[F, Vector[MetricReader[F]]] = {
val (pull, push) = exporters.toVector.partitionMap {
case push: MetricExporter.Push[F] => Right(push)
case pull: MetricExporter.Pull[F] => Left(pull)
}

for {
pushReaders <- configurePush(config, push)
pullReaders <- configurePull(pull)
} yield pushReaders ++ pullReaders
}

private def configurePush(
config: Config,
exporters: Vector[MetricExporter.Push[F]]
): Resource[F, Vector[MetricReader[F]]] =
for {
interval <- Resource.eval(
Temporal[F].fromEither(
config.getOrElse(ConfigKeys.ExportInterval, Defaults.ExportInterval)
)
)

timeout <- Resource.eval(
Temporal[F].fromEither(
config.getOrElse(ConfigKeys.ExportTimeout, Defaults.ExportTimeout)
)
)

readers <- exporters.traverse { exporter =>
MetricReader.periodic(exporter, interval, timeout)
}
} yield readers

private def configurePull(
exporters: Vector[MetricExporter.Pull[F]]
): Resource[F, Vector[MetricReader[F]]] =
Resource
.eval(
exporters.traverse_ { exporter =>
Console[F].error(
s"The pull-based exporter [$exporter] is not supported yet"
)
}
)
.as(Vector.empty)

}

object MetricReadersAutoConfigure {

private object ConfigKeys {
val ExportInterval: Config.Key[FiniteDuration] =
Config.Key("otel.metric.export.interval")

val ExportTimeout: Config.Key[FiniteDuration] =
Config.Key("otel.metric.export.timeout")

val All: Set[Config.Key[_]] = Set(ExportInterval, ExportTimeout)
}

// see https://opentelemetry.io/docs/specs/otel/configuration/sdk-environment-variables/#periodic-exporting-metricreader
private object Defaults {
val ExportInterval: FiniteDuration = 1.minute
val ExportTimeout: FiniteDuration = 30.seconds
}

/** Autoconfigures
* [[org.typelevel.otel4s.sdk.metrics.exporter.MetricReader MetricReader]]s.
*
* The configuration options:
* {{{
* | System property | Environment variable | Description |
* |-----------------------------|-----------------------------|------------------------------------------------------------------------------------|
* | otel.metric.export.interval | OTEL_METRIC_EXPORT_INTERVAL | The time interval between the start of two export attempts. Default is `1 minute`. |
* | otel.metric.export.timeout | OTEL_METRIC_EXPORT_TIMEOUT | Maximum allowed time to export data. Default is `30 seconds`. |
* }}}
*
* @see
* [[https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/configuration/sdk-environment-variables.md#periodic-exporting-metricreader]]
*
* @param exporters
* the exporters to use with metric readers
*/
def apply[F[_]: Temporal: Console](
exporters: Set[MetricExporter[F]]
): AutoConfigure[F, Vector[MetricReader[F]]] =
new MetricReadersAutoConfigure[F](exporters)

}
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ import org.typelevel.otel4s.sdk.metrics.data.MetricData
*/
private final class ConsoleMetricExporter[F[_]: Monad: Console](
val aggregationTemporalitySelector: AggregationTemporalitySelector
) extends MetricExporter[F] {
) extends MetricExporter.Push[F] {

val name: String = "ConsoleMetricExporter"

Expand Down Expand Up @@ -72,7 +72,7 @@ object ConsoleMetricExporter {
* @tparam F
* the higher-kinded type of a polymorphic effect
*/
def apply[F[_]: Monad: Console]: MetricExporter[F] =
def apply[F[_]: Monad: Console]: MetricExporter.Push[F] =
apply(AggregationTemporalitySelector.alwaysCumulative)

/** Creates a metric exporter that logs every metric using
Expand All @@ -86,7 +86,7 @@ object ConsoleMetricExporter {
*/
def apply[F[_]: Monad: Console](
aggregationTemporalitySelector: AggregationTemporalitySelector
): MetricExporter[F] =
): MetricExporter.Push[F] =
new ConsoleMetricExporter[F](aggregationTemporalitySelector)

}
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,15 @@ import cats.Applicative
import cats.Foldable
import org.typelevel.otel4s.sdk.metrics.data.MetricData

/** `MetricExporter` is a push based interface for exporting `MetricData`.
/** An interface for exporting `MetricData`.
*
* @see
* [[https://opentelemetry.io/docs/specs/otel/metrics/sdk/#metricexporter]]
*
* @tparam F
* the higher-kinded type of a polymorphic effect
*/
trait MetricExporter[F[_]] {
sealed trait MetricExporter[F[_]] {

/** The name of the exporter.
*/
Expand All @@ -49,33 +52,54 @@ trait MetricExporter[F[_]] {
*/
def defaultCardinalityLimitSelector: CardinalityLimitSelector

/** Exports the sampled `MetricData`.
*
* @param metrics
* the sampled metrics to export
*/
def exportMetrics[G[_]: Foldable](metrics: G[MetricData]): F[Unit]

/** Exports the collection of sampled `MetricData` that have not yet been
* exported.
*/
def flush: F[Unit]

override def toString: String =
name

}

object MetricExporter {

/** Creates a no-op implementation of the [[MetricExporter]].
/** A push based interface for exporting `MetricData`.
*
* Implementation examples:
* - console exporter
* - in-memory exporter
* - OTLP exporter
*
* This exporter can be used with the periodic metric reader.
*
* @tparam F
* the higher-kinded type of a polymorphic effect
*/
trait Push[F[_]] extends MetricExporter[F] {

/** Exports the sampled `MetricData`.
*
* @param metrics
* the sampled metrics to export
*/
def exportMetrics[G[_]: Foldable](metrics: G[MetricData]): F[Unit]

/** Exports the collection of sampled `MetricData` that have not yet been
* exported.
*/
def flush: F[Unit]
}

/** A pull based interface for exporting `MetricData`.
*
* Implementation examples:
* - Prometheus exporter
*
* All export operations are no-op.
* @tparam F
* the higher-kinded type of a polymorphic effect
*/
trait Pull[F[_]] extends MetricExporter[F]

def noop[F[_]: Applicative]: MetricExporter[F] =
new Noop

private final class Noop[F[_]: Applicative] extends MetricExporter[F] {
private final class Noop[F[_]: Applicative] extends MetricExporter.Push[F] {
val name: String = "MetricExporter.Noop"

def aggregationTemporalitySelector: AggregationTemporalitySelector =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ object MetricReader {
* the higher-kinded type of a polymorphic effect
*/
def periodic[F[_]: Temporal: Console](
exporter: MetricExporter[F],
exporter: MetricExporter.Push[F],
interval: FiniteDuration,
timeout: FiniteDuration
): Resource[F, MetricReader[F]] =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ import scala.concurrent.duration.FiniteDuration
*/
private class PeriodicMetricReader[F[_]: Temporal: Console] private (
metricProducers: Ref[F, Vector[MetricProducer[F]]],
exporter: MetricExporter[F],
exporter: MetricExporter.Push[F],
config: PeriodicMetricReader.Config
) extends MetricReader[F] {

Expand Down Expand Up @@ -148,7 +148,7 @@ private object PeriodicMetricReader {
* the higher-kinded type of a polymorphic effect
*/
def create[F[_]: Temporal: Console](
exporter: MetricExporter[F],
exporter: MetricExporter.Push[F],
interval: FiniteDuration,
timeout: FiniteDuration
): Resource[F, MetricReader[F]] =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -126,8 +126,8 @@ class MetricExportersAutoConfigureSuite extends CatsEffectSuite {
)
}

private def customExporter(exporterName: String): MetricExporter[IO] =
new MetricExporter[IO] {
private def customExporter(exporterName: String): MetricExporter.Push[IO] =
new MetricExporter.Push[IO] {
def name: String =
exporterName

Expand Down
Loading

0 comments on commit 6b0a67a

Please sign in to comment.