From a85e127ca023efdbfa8ce35a18d54e763841ca6d Mon Sep 17 00:00:00 2001 From: Maksym Ochenashko Date: Sat, 27 Apr 2024 12:06:38 +0300 Subject: [PATCH] sdk-metrics: make `MetricPoints` use `NonEmptyVector` for `points` --- .../typelevel/otel4s/scalacheck/Gens.scala | 7 ++ .../otlp/metrics/MetricsProtoEncoder.scala | 6 +- .../otlp/metrics/MetricsJsonCodecs.scala | 5 +- .../sdk/metrics/aggregation/Aggregator.scala | 5 +- .../ExplicitBucketHistogramAggregator.scala | 3 +- .../aggregation/LastValueAggregator.scala | 5 +- .../metrics/aggregation/SumAggregator.scala | 5 +- .../otel4s/sdk/metrics/data/MetricData.scala | 8 -- .../sdk/metrics/data/MetricPoints.scala | 43 +++++--- .../metrics/internal/MeterSharedState.scala | 2 +- .../storage/AsynchronousStorage.scala | 30 ++--- .../internal/storage/SynchronousStorage.scala | 28 +++-- .../sdk/metrics/SdkBatchCallbackSuite.scala | 7 +- .../otel4s/sdk/metrics/SdkCounterSuite.scala | 5 +- .../sdk/metrics/SdkHistogramSuite.scala | 13 ++- .../metrics/SdkObservableCounterSuite.scala | 3 +- .../sdk/metrics/SdkObservableGaugeSuite.scala | 3 +- .../SdkObservableUpDownCounterSuite.scala | 3 +- .../sdk/metrics/SdkUpDownCounterSuite.scala | 7 +- .../metrics/aggregation/AggregatorSuite.scala | 103 ++++++++++++++---- ...plicitBucketHistogramAggregatorSuite.scala | 6 +- .../LastValueAggregatorSuite.scala | 36 +++--- .../aggregation/SumAggregatorSuite.scala | 47 ++++---- .../sdk/metrics/data/MetricPointsSuite.scala | 6 +- .../internal/MeterSharedStateSuite.scala | 15 +-- .../SdkObservableMeasurementSuite.scala | 7 +- .../storage/AsynchronousStorageSuite.scala | 19 ++-- .../storage/SynchronousStorageSuite.scala | 9 +- .../sdk/metrics/scalacheck/Cogens.scala | 6 +- .../otel4s/sdk/metrics/scalacheck/Gens.scala | 69 +++++++----- .../sdk/metrics/test/PointDataUtils.scala | 18 +-- 31 files changed, 328 insertions(+), 201 deletions(-) diff --git a/core/common/src/test/scala/org/typelevel/otel4s/scalacheck/Gens.scala b/core/common/src/test/scala/org/typelevel/otel4s/scalacheck/Gens.scala index b326aab0d..ac24cecdf 100644 --- a/core/common/src/test/scala/org/typelevel/otel4s/scalacheck/Gens.scala +++ b/core/common/src/test/scala/org/typelevel/otel4s/scalacheck/Gens.scala @@ -17,11 +17,18 @@ package org.typelevel.otel4s package scalacheck +import cats.data.NonEmptyVector import org.scalacheck.Arbitrary import org.scalacheck.Gen trait Gens { + def nonEmptyVector[A](gen: Gen[A]): Gen[NonEmptyVector[A]] = + for { + head <- gen + tail <- Gen.nonEmptyContainerOf[Vector, A](gen) + } yield NonEmptyVector(head, tail) + val nonEmptyString: Gen[String] = for { id <- Gen.identifier diff --git a/sdk-exporter/metrics/src/main/scala/org/typelevel/otel4s/sdk/exporter/otlp/metrics/MetricsProtoEncoder.scala b/sdk-exporter/metrics/src/main/scala/org/typelevel/otel4s/sdk/exporter/otlp/metrics/MetricsProtoEncoder.scala index 5e60acccb..9ed7caebc 100644 --- a/sdk-exporter/metrics/src/main/scala/org/typelevel/otel4s/sdk/exporter/otlp/metrics/MetricsProtoEncoder.scala +++ b/sdk-exporter/metrics/src/main/scala/org/typelevel/otel4s/sdk/exporter/otlp/metrics/MetricsProtoEncoder.scala @@ -151,7 +151,7 @@ private object MetricsProtoEncoder { case sum: MetricPoints.Sum => Proto.Metric.Data.Sum( Proto.Sum( - sum.points.map(ProtoEncoder.encode(_)), + sum.points.toVector.map(ProtoEncoder.encode(_)), ProtoEncoder.encode(sum.aggregationTemporality), sum.monotonic ) @@ -159,13 +159,13 @@ private object MetricsProtoEncoder { case gauge: MetricPoints.Gauge => Proto.Metric.Data.Gauge( - Proto.Gauge(gauge.points.map(ProtoEncoder.encode(_))) + Proto.Gauge(gauge.points.toVector.map(ProtoEncoder.encode(_))) ) case histogram: MetricPoints.Histogram => Proto.Metric.Data.Histogram( Proto.Histogram( - histogram.points.map(ProtoEncoder.encode(_)), + histogram.points.toVector.map(ProtoEncoder.encode(_)), ProtoEncoder.encode(histogram.aggregationTemporality) ) ) diff --git a/sdk-exporter/metrics/src/test/scala/org/typelevel/otel4s/sdk/exporter/otlp/metrics/MetricsJsonCodecs.scala b/sdk-exporter/metrics/src/test/scala/org/typelevel/otel4s/sdk/exporter/otlp/metrics/MetricsJsonCodecs.scala index 74119b8d3..46441de04 100644 --- a/sdk-exporter/metrics/src/test/scala/org/typelevel/otel4s/sdk/exporter/otlp/metrics/MetricsJsonCodecs.scala +++ b/sdk-exporter/metrics/src/test/scala/org/typelevel/otel4s/sdk/exporter/otlp/metrics/MetricsJsonCodecs.scala @@ -19,6 +19,7 @@ package sdk package exporter.otlp package metrics +import cats.data.NonEmptyVector import io.circe.Encoder import io.circe.Json import io.circe.syntax._ @@ -114,7 +115,7 @@ private object MetricsJsonCodecs extends JsonCodecs { Json .obj( - "dataPoints" := (sum.points: Vector[PointData.NumberPoint]), + "dataPoints" := (sum.points: NonEmptyVector[PointData.NumberPoint]), "aggregationTemporality" := sum.aggregationTemporality, "isMonotonic" := monotonic ) @@ -126,7 +127,7 @@ private object MetricsJsonCodecs extends JsonCodecs { Encoder.instance { gauge => Json .obj( - "dataPoints" := (gauge.points: Vector[PointData.NumberPoint]) + "dataPoints" := (gauge.points: NonEmptyVector[PointData.NumberPoint]) ) .dropEmptyValues } diff --git a/sdk/metrics/src/main/scala/org/typelevel/otel4s/sdk/metrics/aggregation/Aggregator.scala b/sdk/metrics/src/main/scala/org/typelevel/otel4s/sdk/metrics/aggregation/Aggregator.scala index d655b9411..31375adce 100644 --- a/sdk/metrics/src/main/scala/org/typelevel/otel4s/sdk/metrics/aggregation/Aggregator.scala +++ b/sdk/metrics/src/main/scala/org/typelevel/otel4s/sdk/metrics/aggregation/Aggregator.scala @@ -17,6 +17,7 @@ package org.typelevel.otel4s.sdk.metrics.aggregation import cats.Applicative +import cats.data.NonEmptyVector import cats.effect.Temporal import cats.effect.std.Random import org.typelevel.otel4s.Attributes @@ -84,7 +85,7 @@ private[metrics] object Aggregator { resource: TelemetryResource, scope: InstrumentationScope, descriptor: MetricDescriptor, - points: Vector[Point], + points: NonEmptyVector[Point], temporality: AggregationTemporality ): F[MetricData] } @@ -137,7 +138,7 @@ private[metrics] object Aggregator { resource: TelemetryResource, scope: InstrumentationScope, descriptor: MetricDescriptor, - measurements: Vector[AsynchronousMeasurement[A]], + measurements: NonEmptyVector[AsynchronousMeasurement[A]], temporality: AggregationTemporality ): F[MetricData] } diff --git a/sdk/metrics/src/main/scala/org/typelevel/otel4s/sdk/metrics/aggregation/ExplicitBucketHistogramAggregator.scala b/sdk/metrics/src/main/scala/org/typelevel/otel4s/sdk/metrics/aggregation/ExplicitBucketHistogramAggregator.scala index 4c487f514..64868b067 100644 --- a/sdk/metrics/src/main/scala/org/typelevel/otel4s/sdk/metrics/aggregation/ExplicitBucketHistogramAggregator.scala +++ b/sdk/metrics/src/main/scala/org/typelevel/otel4s/sdk/metrics/aggregation/ExplicitBucketHistogramAggregator.scala @@ -17,6 +17,7 @@ package org.typelevel.otel4s.sdk.metrics.aggregation import cats.FlatMap +import cats.data.NonEmptyVector import cats.effect.Concurrent import cats.effect.Ref import cats.effect.Temporal @@ -67,7 +68,7 @@ private final class ExplicitBucketHistogramAggregator[ resource: TelemetryResource, scope: InstrumentationScope, descriptor: MetricDescriptor, - points: Vector[PointData.Histogram], + points: NonEmptyVector[PointData.Histogram], temporality: AggregationTemporality ): F[MetricData] = Concurrent[F].pure( diff --git a/sdk/metrics/src/main/scala/org/typelevel/otel4s/sdk/metrics/aggregation/LastValueAggregator.scala b/sdk/metrics/src/main/scala/org/typelevel/otel4s/sdk/metrics/aggregation/LastValueAggregator.scala index b4a8c5cb5..9699a4e31 100644 --- a/sdk/metrics/src/main/scala/org/typelevel/otel4s/sdk/metrics/aggregation/LastValueAggregator.scala +++ b/sdk/metrics/src/main/scala/org/typelevel/otel4s/sdk/metrics/aggregation/LastValueAggregator.scala @@ -17,6 +17,7 @@ package org.typelevel.otel4s.sdk.metrics.aggregation import cats.Applicative +import cats.data.NonEmptyVector import cats.effect.Concurrent import cats.syntax.functor._ import org.typelevel.otel4s.Attributes @@ -86,7 +87,7 @@ private object LastValueAggregator { resource: TelemetryResource, scope: InstrumentationScope, descriptor: MetricDescriptor, - points: Vector[Point], + points: NonEmptyVector[Point], temporality: AggregationTemporality ): F[MetricData] = Concurrent[F].pure( @@ -142,7 +143,7 @@ private object LastValueAggregator { resource: TelemetryResource, scope: InstrumentationScope, descriptor: MetricDescriptor, - measurements: Vector[AsynchronousMeasurement[A]], + measurements: NonEmptyVector[AsynchronousMeasurement[A]], temporality: AggregationTemporality ): F[MetricData] = { val points = measurements.map { m => diff --git a/sdk/metrics/src/main/scala/org/typelevel/otel4s/sdk/metrics/aggregation/SumAggregator.scala b/sdk/metrics/src/main/scala/org/typelevel/otel4s/sdk/metrics/aggregation/SumAggregator.scala index f02fb1a3e..ffcdd527b 100644 --- a/sdk/metrics/src/main/scala/org/typelevel/otel4s/sdk/metrics/aggregation/SumAggregator.scala +++ b/sdk/metrics/src/main/scala/org/typelevel/otel4s/sdk/metrics/aggregation/SumAggregator.scala @@ -17,6 +17,7 @@ package org.typelevel.otel4s.sdk.metrics.aggregation import cats.Applicative +import cats.data.NonEmptyVector import cats.effect.Temporal import cats.effect.std.Random import cats.syntax.flatMap._ @@ -109,7 +110,7 @@ private object SumAggregator { resource: TelemetryResource, scope: InstrumentationScope, descriptor: MetricDescriptor, - points: Vector[Point], + points: NonEmptyVector[Point], temporality: AggregationTemporality ): F[MetricData] = Temporal[F].pure( @@ -184,7 +185,7 @@ private object SumAggregator { resource: TelemetryResource, scope: InstrumentationScope, descriptor: MetricDescriptor, - measurements: Vector[AsynchronousMeasurement[A]], + measurements: NonEmptyVector[AsynchronousMeasurement[A]], temporality: AggregationTemporality ): F[MetricData] = { val points = measurements.map { m => diff --git a/sdk/metrics/src/main/scala/org/typelevel/otel4s/sdk/metrics/data/MetricData.scala b/sdk/metrics/src/main/scala/org/typelevel/otel4s/sdk/metrics/data/MetricData.scala index 5870589d0..cf0117672 100644 --- a/sdk/metrics/src/main/scala/org/typelevel/otel4s/sdk/metrics/data/MetricData.scala +++ b/sdk/metrics/src/main/scala/org/typelevel/otel4s/sdk/metrics/data/MetricData.scala @@ -59,14 +59,6 @@ sealed trait MetricData { */ def resource: TelemetryResource - /** Whether the measurements are empty. - */ - final def isEmpty: Boolean = data.points.isEmpty - - /** Whether the measurements are non empty. - */ - final def nonEmpty: Boolean = !isEmpty - override final def hashCode(): Int = Hash[MetricData].hash(this) diff --git a/sdk/metrics/src/main/scala/org/typelevel/otel4s/sdk/metrics/data/MetricPoints.scala b/sdk/metrics/src/main/scala/org/typelevel/otel4s/sdk/metrics/data/MetricPoints.scala index 72779f875..517e02c94 100644 --- a/sdk/metrics/src/main/scala/org/typelevel/otel4s/sdk/metrics/data/MetricPoints.scala +++ b/sdk/metrics/src/main/scala/org/typelevel/otel4s/sdk/metrics/data/MetricPoints.scala @@ -18,6 +18,8 @@ package org.typelevel.otel4s.sdk.metrics.data import cats.Hash import cats.Show +import cats.data.NonEmptyVector +import cats.syntax.foldable._ /** A collection of metric data points. * @@ -28,7 +30,7 @@ sealed trait MetricPoints { /** The collection of the metric [[PointData]]s. */ - def points: Vector[PointData] + def points: NonEmptyVector[PointData] override final def hashCode(): Int = Hash[MetricPoints].hash(this) @@ -54,7 +56,7 @@ object MetricPoints { sealed trait Sum extends MetricPoints { type Point <: PointData.NumberPoint - def points: Vector[Point] + def points: NonEmptyVector[Point] /** Whether the points are monotonic. If true, it means the data points are * nominally increasing. @@ -74,7 +76,7 @@ object MetricPoints { sealed trait Gauge extends MetricPoints { type Point <: PointData.NumberPoint - def points: Vector[Point] + def points: NonEmptyVector[Point] } /** Histogram represents the type of a metric that is calculated by @@ -85,7 +87,7 @@ object MetricPoints { * [[https://opentelemetry.io/docs/specs/otel/metrics/data-model/#histogram]] */ sealed trait Histogram extends MetricPoints { - def points: Vector[PointData.Histogram] + def points: NonEmptyVector[PointData.Histogram] /** The aggregation temporality of this aggregation. */ @@ -95,7 +97,7 @@ object MetricPoints { /** Creates a [[Sum]] with the given values. */ def sum[A <: PointData.NumberPoint]( - points: Vector[A], + points: NonEmptyVector[A], monotonic: Boolean, aggregationTemporality: AggregationTemporality ): Sum = @@ -104,14 +106,14 @@ object MetricPoints { /** Creates a [[Gauge]] with the given values. */ def gauge[A <: PointData.NumberPoint]( - points: Vector[A] + points: NonEmptyVector[A] ): Gauge = GaugeImpl(points) /** Creates a [[Histogram]] with the given values. */ def histogram( - points: Vector[PointData.Histogram], + points: NonEmptyVector[PointData.Histogram], aggregationTemporality: AggregationTemporality ): Histogram = HistogramImpl(points, aggregationTemporality) @@ -119,14 +121,20 @@ object MetricPoints { implicit val metricPointsHash: Hash[MetricPoints] = { val sumHash: Hash[Sum] = Hash.by { s => - (s.points: Vector[PointData], s.monotonic, s.aggregationTemporality) + ( + s.points.toVector: Vector[PointData], + s.monotonic, + s.aggregationTemporality + ) } val gaugeHash: Hash[Gauge] = - Hash.by(_.points: Vector[PointData]) + Hash.by(_.points.toVector: Vector[PointData]) val histogramHash: Hash[Histogram] = - Hash.by(h => (h.points: Vector[PointData], h.aggregationTemporality)) + Hash.by(h => + (h.points.toVector: Vector[PointData], h.aggregationTemporality) + ) new Hash[MetricPoints] { def hash(x: MetricPoints): Int = @@ -154,30 +162,33 @@ object MetricPoints { Show.show { case sum: Sum => "MetricPoints.Sum{" + - s"points=${sum.points.mkString("{", ",", "}")}, " + + s"points=${(sum.points: NonEmptyVector[PointData]).mkString_("{", ",", "}")}, " + s"monotonic=${sum.monotonic}, " + s"aggregationTemporality=${sum.aggregationTemporality}}" case gauge: Gauge => - s"MetricPoints.Gauge{points=${gauge.points.mkString("{", ",", "}")}}" + "MetricPoints.Gauge{" + + s"points=${(gauge.points: NonEmptyVector[PointData]).mkString_("{", ",", "}")}}" case h: Histogram => - s"MetricPoints.Histogram{points=${h.points.mkString("{", ",", "}")}, aggregationTemporality=${h.aggregationTemporality}}" + "MetricPoints.Histogram{" + + s"points=${(h.points: NonEmptyVector[PointData]).mkString_("{", ",", "}")}, " + + s"aggregationTemporality=${h.aggregationTemporality}}" } } private final case class SumImpl[A <: PointData.NumberPoint]( - points: Vector[A], + points: NonEmptyVector[A], monotonic: Boolean, aggregationTemporality: AggregationTemporality ) extends Sum { type Point = A } private final case class GaugeImpl[A <: PointData.NumberPoint]( - points: Vector[A] + points: NonEmptyVector[A] ) extends Gauge { type Point = A } private final case class HistogramImpl( - points: Vector[PointData.Histogram], + points: NonEmptyVector[PointData.Histogram], aggregationTemporality: AggregationTemporality ) extends Histogram diff --git a/sdk/metrics/src/main/scala/org/typelevel/otel4s/sdk/metrics/internal/MeterSharedState.scala b/sdk/metrics/src/main/scala/org/typelevel/otel4s/sdk/metrics/internal/MeterSharedState.scala index af56759a1..980db1f23 100644 --- a/sdk/metrics/src/main/scala/org/typelevel/otel4s/sdk/metrics/internal/MeterSharedState.scala +++ b/sdk/metrics/src/main/scala/org/typelevel/otel4s/sdk/metrics/internal/MeterSharedState.scala @@ -226,7 +226,7 @@ private[metrics] final class MeterSharedState[ result <- storages.traverse { storage => storage.collect(resource, scope, timeWindow) } - } yield result.flatten.filter(_.nonEmpty) + } yield result.flatten } } diff --git a/sdk/metrics/src/main/scala/org/typelevel/otel4s/sdk/metrics/internal/storage/AsynchronousStorage.scala b/sdk/metrics/src/main/scala/org/typelevel/otel4s/sdk/metrics/internal/storage/AsynchronousStorage.scala index 02619cb1a..12d3b473e 100644 --- a/sdk/metrics/src/main/scala/org/typelevel/otel4s/sdk/metrics/internal/storage/AsynchronousStorage.scala +++ b/sdk/metrics/src/main/scala/org/typelevel/otel4s/sdk/metrics/internal/storage/AsynchronousStorage.scala @@ -17,6 +17,7 @@ package org.typelevel.otel4s.sdk.metrics.internal.storage import cats.Monad +import cats.data.NonEmptyVector import cats.effect.Concurrent import cats.effect.Ref import cats.effect.Temporal @@ -95,20 +96,23 @@ private final class AsynchronousStorage[ scope: InstrumentationScope, timeWindow: TimeWindow ): F[Option[MetricData]] = - collector.collectPoints.flatMap { - case measurements if measurements.nonEmpty => - aggregator - .toMetricData( - resource, - scope, - metricDescriptor, - measurements, - aggregationTemporality - ) - .map(Some(_)) + collector.collectPoints.flatMap { points => + NonEmptyVector.fromVector(points) match { + case Some(measurements) => + aggregator + .toMetricData( + resource, + scope, + metricDescriptor, + measurements, + aggregationTemporality + ) + .map(Some(_)) - case _ => - Monad[F].pure(None) + case None => + Monad[F].pure(None) + + } } private def cardinalityWarning: F[Unit] = diff --git a/sdk/metrics/src/main/scala/org/typelevel/otel4s/sdk/metrics/internal/storage/SynchronousStorage.scala b/sdk/metrics/src/main/scala/org/typelevel/otel4s/sdk/metrics/internal/storage/SynchronousStorage.scala index 6159e9459..605b6570e 100644 --- a/sdk/metrics/src/main/scala/org/typelevel/otel4s/sdk/metrics/internal/storage/SynchronousStorage.scala +++ b/sdk/metrics/src/main/scala/org/typelevel/otel4s/sdk/metrics/internal/storage/SynchronousStorage.scala @@ -17,6 +17,7 @@ package org.typelevel.otel4s.sdk.metrics.internal.storage import cats.Monad +import cats.data.NonEmptyVector import cats.effect.Temporal import cats.effect.std.AtomicCell import cats.effect.std.Console @@ -92,18 +93,21 @@ private final class SynchronousStorage[ val isDelta = aggregationTemporality == AggregationTemporality.Delta def toMetricData(points: Vector[PointData]): F[Option[MetricData]] = - if (points.isEmpty) - Monad[F].pure(Option.empty[MetricData]) - else - aggregator - .toMetricData( - resource, - scope, - metricDescriptor, - points, - aggregationTemporality - ) - .map(Some(_)) + NonEmptyVector.fromVector(points) match { + case Some(metricPoints) => + aggregator + .toMetricData( + resource, + scope, + metricDescriptor, + metricPoints, + aggregationTemporality + ) + .map(Some(_)) + + case None => + Monad[F].pure(Option.empty[MetricData]) + } for { points <- if (isDelta) collectDelta(window) else collectCumulative(window) diff --git a/sdk/metrics/src/test/scala/org/typelevel/otel4s/sdk/metrics/SdkBatchCallbackSuite.scala b/sdk/metrics/src/test/scala/org/typelevel/otel4s/sdk/metrics/SdkBatchCallbackSuite.scala index 8883efd12..afc7cb547 100644 --- a/sdk/metrics/src/test/scala/org/typelevel/otel4s/sdk/metrics/SdkBatchCallbackSuite.scala +++ b/sdk/metrics/src/test/scala/org/typelevel/otel4s/sdk/metrics/SdkBatchCallbackSuite.scala @@ -16,6 +16,7 @@ package org.typelevel.otel4s.sdk.metrics +import cats.data.NonEmptyVector import cats.effect.IO import cats.mtl.Ask import munit.CatsEffectSuite @@ -55,7 +56,7 @@ class SdkBatchCallbackSuite extends CatsEffectSuite with ScalaCheckEffectSuite { unit, MetricPoints.sum( points = PointDataUtils.toNumberPoints( - Vector(value), + NonEmptyVector.one(value), attrs, window ), @@ -72,7 +73,7 @@ class SdkBatchCallbackSuite extends CatsEffectSuite with ScalaCheckEffectSuite { unit, MetricPoints.sum( points = PointDataUtils.toNumberPoints( - Vector(value), + NonEmptyVector.one(value), attrs, window ), @@ -89,7 +90,7 @@ class SdkBatchCallbackSuite extends CatsEffectSuite with ScalaCheckEffectSuite { unit, MetricPoints.gauge( points = PointDataUtils.toNumberPoints( - Vector(value), + NonEmptyVector.one(value), attrs, window ) diff --git a/sdk/metrics/src/test/scala/org/typelevel/otel4s/sdk/metrics/SdkCounterSuite.scala b/sdk/metrics/src/test/scala/org/typelevel/otel4s/sdk/metrics/SdkCounterSuite.scala index 2da54c5c3..5c44ea137 100644 --- a/sdk/metrics/src/test/scala/org/typelevel/otel4s/sdk/metrics/SdkCounterSuite.scala +++ b/sdk/metrics/src/test/scala/org/typelevel/otel4s/sdk/metrics/SdkCounterSuite.scala @@ -16,6 +16,7 @@ package org.typelevel.otel4s.sdk.metrics +import cats.data.NonEmptyVector import cats.effect.IO import cats.mtl.Ask import cats.syntax.foldable._ @@ -106,7 +107,7 @@ class SdkCounterSuite extends CatsEffectSuite with ScalaCheckEffectSuite { unit, MetricPoints.sum( points = PointDataUtils.toNumberPoints( - Vector(Numeric[A].one), + NonEmptyVector.one(Numeric[A].one), attrs, window ), @@ -159,7 +160,7 @@ class SdkCounterSuite extends CatsEffectSuite with ScalaCheckEffectSuite { unit, MetricPoints.sum( points = PointDataUtils.toNumberPoints( - Vector(values.sum), + NonEmptyVector.one(values.sum), attrs, window ), diff --git a/sdk/metrics/src/test/scala/org/typelevel/otel4s/sdk/metrics/SdkHistogramSuite.scala b/sdk/metrics/src/test/scala/org/typelevel/otel4s/sdk/metrics/SdkHistogramSuite.scala index 9d0dfff84..d4910d586 100644 --- a/sdk/metrics/src/test/scala/org/typelevel/otel4s/sdk/metrics/SdkHistogramSuite.scala +++ b/sdk/metrics/src/test/scala/org/typelevel/otel4s/sdk/metrics/SdkHistogramSuite.scala @@ -16,6 +16,7 @@ package org.typelevel.otel4s.sdk.metrics +import cats.data.NonEmptyVector import cats.effect.IO import cats.effect.testkit.TestControl import cats.mtl.Ask @@ -110,9 +111,9 @@ class SdkHistogramSuite extends CatsEffectSuite with ScalaCheckEffectSuite { description, unit, MetricPoints.histogram( - points = Vector( + points = NonEmptyVector.one( PointDataUtils.toHistogramPoint( - Vector(Numeric[A].fromInt(duration)), + NonEmptyVector.one(Numeric[A].fromInt(duration)), attrs, window, Aggregation.Defaults.Boundaries @@ -162,7 +163,7 @@ class SdkHistogramSuite extends CatsEffectSuite with ScalaCheckEffectSuite { Gen.either(Gen.listOf(Gen.posNum[Long]), Gen.listOf(Gen.double)) ) { (resource, scope, window, attrs, name, unit, description, values) => def test[A: MeasurementValue: Numeric](values: Vector[A]): IO[Unit] = { - val expected = Option.when(values.nonEmpty)( + val expected = NonEmptyVector.fromVector(values).map { points => MetricData( resource, scope, @@ -170,9 +171,9 @@ class SdkHistogramSuite extends CatsEffectSuite with ScalaCheckEffectSuite { description, unit, MetricPoints.histogram( - points = Vector( + points = NonEmptyVector.one( PointDataUtils.toHistogramPoint( - values, + points, attrs, window, Aggregation.Defaults.Boundaries @@ -181,7 +182,7 @@ class SdkHistogramSuite extends CatsEffectSuite with ScalaCheckEffectSuite { aggregationTemporality = AggregationTemporality.Cumulative ) ) - ) + } for { state <- InMemoryMeterSharedState.create[IO]( diff --git a/sdk/metrics/src/test/scala/org/typelevel/otel4s/sdk/metrics/SdkObservableCounterSuite.scala b/sdk/metrics/src/test/scala/org/typelevel/otel4s/sdk/metrics/SdkObservableCounterSuite.scala index 7fc81e2eb..08e0bbf09 100644 --- a/sdk/metrics/src/test/scala/org/typelevel/otel4s/sdk/metrics/SdkObservableCounterSuite.scala +++ b/sdk/metrics/src/test/scala/org/typelevel/otel4s/sdk/metrics/SdkObservableCounterSuite.scala @@ -16,6 +16,7 @@ package org.typelevel.otel4s.sdk.metrics +import cats.data.NonEmptyVector import cats.effect.IO import cats.mtl.Ask import munit.CatsEffectSuite @@ -58,7 +59,7 @@ class SdkObservableCounterSuite unit, MetricPoints.sum( points = PointDataUtils.toNumberPoints( - Vector(value), + NonEmptyVector.one(value), attrs, window ), diff --git a/sdk/metrics/src/test/scala/org/typelevel/otel4s/sdk/metrics/SdkObservableGaugeSuite.scala b/sdk/metrics/src/test/scala/org/typelevel/otel4s/sdk/metrics/SdkObservableGaugeSuite.scala index 99dba9a4e..dd1bc886b 100644 --- a/sdk/metrics/src/test/scala/org/typelevel/otel4s/sdk/metrics/SdkObservableGaugeSuite.scala +++ b/sdk/metrics/src/test/scala/org/typelevel/otel4s/sdk/metrics/SdkObservableGaugeSuite.scala @@ -16,6 +16,7 @@ package org.typelevel.otel4s.sdk.metrics +import cats.data.NonEmptyVector import cats.effect.IO import cats.mtl.Ask import munit.CatsEffectSuite @@ -57,7 +58,7 @@ class SdkObservableGaugeSuite unit, MetricPoints.gauge( points = PointDataUtils.toNumberPoints( - Vector(value), + NonEmptyVector.one(value), attrs, window ) diff --git a/sdk/metrics/src/test/scala/org/typelevel/otel4s/sdk/metrics/SdkObservableUpDownCounterSuite.scala b/sdk/metrics/src/test/scala/org/typelevel/otel4s/sdk/metrics/SdkObservableUpDownCounterSuite.scala index 1b4778296..f19d9d970 100644 --- a/sdk/metrics/src/test/scala/org/typelevel/otel4s/sdk/metrics/SdkObservableUpDownCounterSuite.scala +++ b/sdk/metrics/src/test/scala/org/typelevel/otel4s/sdk/metrics/SdkObservableUpDownCounterSuite.scala @@ -16,6 +16,7 @@ package org.typelevel.otel4s.sdk.metrics +import cats.data.NonEmptyVector import cats.effect.IO import cats.mtl.Ask import munit.CatsEffectSuite @@ -58,7 +59,7 @@ class SdkObservableUpDownCounterSuite unit, MetricPoints.sum( points = PointDataUtils.toNumberPoints( - Vector(value), + NonEmptyVector.one(value), attrs, window ), diff --git a/sdk/metrics/src/test/scala/org/typelevel/otel4s/sdk/metrics/SdkUpDownCounterSuite.scala b/sdk/metrics/src/test/scala/org/typelevel/otel4s/sdk/metrics/SdkUpDownCounterSuite.scala index 005d60e34..bdc366630 100644 --- a/sdk/metrics/src/test/scala/org/typelevel/otel4s/sdk/metrics/SdkUpDownCounterSuite.scala +++ b/sdk/metrics/src/test/scala/org/typelevel/otel4s/sdk/metrics/SdkUpDownCounterSuite.scala @@ -16,6 +16,7 @@ package org.typelevel.otel4s.sdk.metrics +import cats.data.NonEmptyVector import cats.effect.IO import cats.mtl.Ask import cats.syntax.foldable._ @@ -55,7 +56,7 @@ class SdkUpDownCounterSuite extends CatsEffectSuite with ScalaCheckEffectSuite { unit, MetricPoints.sum( points = PointDataUtils.toNumberPoints( - Vector(Numeric[A].one), + NonEmptyVector.one(Numeric[A].one), attrs, window ), @@ -106,7 +107,7 @@ class SdkUpDownCounterSuite extends CatsEffectSuite with ScalaCheckEffectSuite { unit, MetricPoints.sum( points = PointDataUtils.toNumberPoints( - Vector(Numeric[A].negate(Numeric[A].one)), + NonEmptyVector.one(Numeric[A].negate(Numeric[A].one)), attrs, window ), @@ -159,7 +160,7 @@ class SdkUpDownCounterSuite extends CatsEffectSuite with ScalaCheckEffectSuite { unit, MetricPoints.sum( points = PointDataUtils.toNumberPoints( - Vector(values.sum), + NonEmptyVector.one(values.sum), attrs, window ), diff --git a/sdk/metrics/src/test/scala/org/typelevel/otel4s/sdk/metrics/aggregation/AggregatorSuite.scala b/sdk/metrics/src/test/scala/org/typelevel/otel4s/sdk/metrics/aggregation/AggregatorSuite.scala index b8d864448..e9bb43799 100644 --- a/sdk/metrics/src/test/scala/org/typelevel/otel4s/sdk/metrics/aggregation/AggregatorSuite.scala +++ b/sdk/metrics/src/test/scala/org/typelevel/otel4s/sdk/metrics/aggregation/AggregatorSuite.scala @@ -16,20 +16,28 @@ package org.typelevel.otel4s.sdk.metrics.aggregation +import cats.data.NonEmptyVector import cats.effect.IO import cats.effect.std.Random import munit.CatsEffectSuite import munit.ScalaCheckEffectSuite import org.scalacheck.Gen import org.scalacheck.effect.PropF +import org.typelevel.otel4s.Attributes +import org.typelevel.otel4s.metrics.BucketBoundaries import org.typelevel.otel4s.sdk.metrics.Aggregation import org.typelevel.otel4s.sdk.metrics.InstrumentType import org.typelevel.otel4s.sdk.metrics.data.MetricData import org.typelevel.otel4s.sdk.metrics.data.MetricPoints +import org.typelevel.otel4s.sdk.metrics.data.PointData +import org.typelevel.otel4s.sdk.metrics.data.TimeWindow import org.typelevel.otel4s.sdk.metrics.exemplar.ExemplarFilter import org.typelevel.otel4s.sdk.metrics.exemplar.TraceContextLookup import org.typelevel.otel4s.sdk.metrics.internal.MetricDescriptor import org.typelevel.otel4s.sdk.metrics.scalacheck.Gens +import org.typelevel.otel4s.sdk.metrics.test.PointDataUtils + +import scala.concurrent.duration._ class AggregatorSuite extends CatsEffectSuite with ScalaCheckEffectSuite { @@ -54,15 +62,59 @@ class AggregatorSuite extends CatsEffectSuite with ScalaCheckEffectSuite { Gens.synchronousInstrumentDescriptor, Gens.telemetryResource, Gens.instrumentationScope, - Gens.aggregationTemporality - ) { (aggregation, descriptor, resource, scope, temporality) => + Gens.aggregationTemporality, + Gens.nonEmptyVector(Gens.longNumberPointData) + ) { (aggregation, descriptor, resource, scope, temporality, values) => Random.scalaUtilRandom[IO].flatMap { implicit R: Random[IO] => - val aggregator = Aggregator.synchronous[IO, Long]( - aggregation, - descriptor, - ExemplarFilter.alwaysOn, - TraceContextLookup.noop - ) + type SynchronousAggregator[F[_], A] = + Aggregator.Synchronous[F, A] { + type Point = PointData + } + + val aggregator = Aggregator + .synchronous[IO, Long]( + aggregation, + descriptor, + ExemplarFilter.alwaysOn, + TraceContextLookup.noop + ) + .asInstanceOf[SynchronousAggregator[IO, Long]] + + val numberPoints: NonEmptyVector[PointData.LongNumber] = + values + + def histogramPoints( + boundaries: BucketBoundaries + ): NonEmptyVector[PointData.Histogram] = + NonEmptyVector.one( + PointDataUtils.toHistogramPoint( + values.map(_.value), + Attributes.empty, + TimeWindow(1.second, 10.seconds), + boundaries + ) + ) + + val points: NonEmptyVector[PointData] = { + aggregation match { + case Aggregation.Default => + descriptor.instrumentType match { + case InstrumentType.Counter => numberPoints + case InstrumentType.UpDownCounter => numberPoints + case InstrumentType.Histogram => + histogramPoints(Aggregation.Defaults.Boundaries) + } + + case Aggregation.Sum => + numberPoints + + case Aggregation.LastValue => + numberPoints + + case Aggregation.ExplicitBucketHistogram(boundaries) => + histogramPoints(boundaries) + } + } val expected = { def sum = { @@ -73,26 +125,27 @@ class AggregatorSuite extends CatsEffectSuite with ScalaCheckEffectSuite { case _ => false } - MetricPoints.sum(Vector.empty, monotonic, temporality) + MetricPoints.sum(numberPoints, monotonic, temporality) } def lastValue = - MetricPoints.gauge(Vector.empty) + MetricPoints.gauge(numberPoints) - def histogram = - MetricPoints.histogram(Vector.empty, temporality) + def histogram(boundaries: BucketBoundaries) = + MetricPoints.histogram(histogramPoints(boundaries), temporality) val metricPoints = aggregation match { case Aggregation.Default => descriptor.instrumentType match { case InstrumentType.Counter => sum case InstrumentType.UpDownCounter => sum - case InstrumentType.Histogram => histogram + case InstrumentType.Histogram => + histogram(Aggregation.Defaults.Boundaries) } case Aggregation.Sum => sum case Aggregation.LastValue => lastValue - case Aggregation.ExplicitBucketHistogram(_) => histogram + case Aggregation.ExplicitBucketHistogram(b) => histogram(b) } MetricData( @@ -110,7 +163,7 @@ class AggregatorSuite extends CatsEffectSuite with ScalaCheckEffectSuite { resource, scope, MetricDescriptor(None, descriptor), - Vector.empty, + points, temporality ) } yield assertEquals(result, expected) @@ -124,14 +177,24 @@ class AggregatorSuite extends CatsEffectSuite with ScalaCheckEffectSuite { Gens.asynchronousInstrumentDescriptor, Gens.telemetryResource, Gens.instrumentationScope, - Gens.aggregationTemporality - ) { (aggregation, descriptor, resource, scope, temporality) => + Gens.aggregationTemporality, + Gens.asynchronousMeasurement(Gen.long) + ) { (aggregation, descriptor, resource, scope, temporality, measurement) => val aggregator = Aggregator.asynchronous[IO, Long]( aggregation, descriptor ) val expected = { + val points = NonEmptyVector.one( + PointData.longNumber( + measurement.timeWindow, + measurement.attributes, + Vector.empty, + measurement.value + ) + ) + def sum = { val monotonic = descriptor.instrumentType match { @@ -139,11 +202,11 @@ class AggregatorSuite extends CatsEffectSuite with ScalaCheckEffectSuite { case _ => false } - MetricPoints.sum(Vector.empty, monotonic, temporality) + MetricPoints.sum(points, monotonic, temporality) } def lastValue = - MetricPoints.gauge(Vector.empty) + MetricPoints.gauge(points) val metricPoints = aggregation match { case Aggregation.Default => @@ -172,7 +235,7 @@ class AggregatorSuite extends CatsEffectSuite with ScalaCheckEffectSuite { resource, scope, MetricDescriptor(None, descriptor), - Vector.empty, + NonEmptyVector.one(measurement), temporality ) } yield assertEquals(result, expected) diff --git a/sdk/metrics/src/test/scala/org/typelevel/otel4s/sdk/metrics/aggregation/ExplicitBucketHistogramAggregatorSuite.scala b/sdk/metrics/src/test/scala/org/typelevel/otel4s/sdk/metrics/aggregation/ExplicitBucketHistogramAggregatorSuite.scala index 8a91eed28..b45522e23 100644 --- a/sdk/metrics/src/test/scala/org/typelevel/otel4s/sdk/metrics/aggregation/ExplicitBucketHistogramAggregatorSuite.scala +++ b/sdk/metrics/src/test/scala/org/typelevel/otel4s/sdk/metrics/aggregation/ExplicitBucketHistogramAggregatorSuite.scala @@ -200,7 +200,7 @@ class ExplicitBucketHistogramAggregatorSuite Gens.telemetryResource, Gens.instrumentationScope, Gens.instrumentDescriptor, - Gen.listOf(Gens.histogramPointData), + Gens.nonEmptyVector(Gens.histogramPointData), Gens.aggregationTemporality ) { (boundaries, resource, scope, descriptor, points, temporality) => type HistogramAggregator = Aggregator.Synchronous[IO, Double] { @@ -221,7 +221,7 @@ class ExplicitBucketHistogramAggregatorSuite name = descriptor.name.toString, description = descriptor.description, unit = descriptor.unit, - data = MetricPoints.histogram(points.toVector, temporality) + data = MetricPoints.histogram(points, temporality) ) for { @@ -229,7 +229,7 @@ class ExplicitBucketHistogramAggregatorSuite resource, scope, MetricDescriptor(None, descriptor), - points.toVector, + points, temporality ) } yield assertEquals(metricData, expected) diff --git a/sdk/metrics/src/test/scala/org/typelevel/otel4s/sdk/metrics/aggregation/LastValueAggregatorSuite.scala b/sdk/metrics/src/test/scala/org/typelevel/otel4s/sdk/metrics/aggregation/LastValueAggregatorSuite.scala index 8fe0fcabe..aca4e5d97 100644 --- a/sdk/metrics/src/test/scala/org/typelevel/otel4s/sdk/metrics/aggregation/LastValueAggregatorSuite.scala +++ b/sdk/metrics/src/test/scala/org/typelevel/otel4s/sdk/metrics/aggregation/LastValueAggregatorSuite.scala @@ -39,17 +39,19 @@ class LastValueAggregatorSuite with ScalaCheckEffectSuite { test("synchronous - aggregate with reset - return the last seen value") { - PropF.forAllF(Gen.listOf(Gen.long), Gens.attributes) { (values, attrs) => + PropF.forAllF( + Gens.nonEmptyVector(Gen.long), + Gens.attributes + ) { (values, attrs) => val aggregator = LastValueAggregator.synchronous[IO, Long] val timeWindow = TimeWindow(100.millis, 200.millis) - val expected = - values.lastOption.map { value => - PointData.longNumber(timeWindow, attrs, Vector.empty, value) - } + val expected = Some( + PointData.longNumber(timeWindow, attrs, Vector.empty, values.last) + ) for { accumulator <- aggregator.createAccumulator @@ -66,17 +68,19 @@ class LastValueAggregatorSuite } test("synchronous - aggregate without reset - return the last stored value") { - PropF.forAllF(Gen.listOf(Gen.long), Gens.attributes) { (values, attrs) => + PropF.forAllF( + Gens.nonEmptyVector(Gen.long), + Gens.attributes + ) { (values, attrs) => val aggregator = LastValueAggregator.synchronous[IO, Long] val timeWindow = TimeWindow(100.millis, 200.millis) - val expected = - values.lastOption.map { value => - PointData.longNumber(timeWindow, attrs, Vector.empty, value) - } + val expected = Some( + PointData.longNumber(timeWindow, attrs, Vector.empty, values.last) + ) for { accumulator <- aggregator.createAccumulator @@ -97,7 +101,7 @@ class LastValueAggregatorSuite Gens.telemetryResource, Gens.instrumentationScope, Gens.instrumentDescriptor, - Gen.listOf(Gens.longNumberPointData), + Gens.nonEmptyVector(Gens.longNumberPointData), Gens.aggregationTemporality ) { (resource, scope, descriptor, points, temporality) => type LongAggregator = Aggregator.Synchronous[IO, Long] { @@ -114,7 +118,7 @@ class LastValueAggregatorSuite name = descriptor.name.toString, description = descriptor.description, unit = descriptor.unit, - data = MetricPoints.gauge(points.toVector) + data = MetricPoints.gauge(points) ) for { @@ -122,7 +126,7 @@ class LastValueAggregatorSuite resource, scope, MetricDescriptor(None, descriptor), - points.toVector, + points, temporality ) } yield assertEquals(metricData, expected) @@ -145,7 +149,7 @@ class LastValueAggregatorSuite Gens.telemetryResource, Gens.instrumentationScope, Gens.instrumentDescriptor, - Gen.listOf(Gens.asynchronousMeasurement(Gen.long)), + Gens.nonEmptyVector(Gens.asynchronousMeasurement(Gen.long)), Gens.aggregationTemporality ) { (resource, scope, descriptor, measurements, temporality) => val aggregator = LastValueAggregator.asynchronous[IO, Long] @@ -161,7 +165,7 @@ class LastValueAggregatorSuite name = descriptor.name.toString, description = descriptor.description, unit = descriptor.unit, - data = MetricPoints.gauge(points.toVector) + data = MetricPoints.gauge(points) ) for { @@ -169,7 +173,7 @@ class LastValueAggregatorSuite resource, scope, MetricDescriptor(None, descriptor), - measurements.toVector, + measurements, temporality ) } yield assertEquals(metricData, expected) diff --git a/sdk/metrics/src/test/scala/org/typelevel/otel4s/sdk/metrics/aggregation/SumAggregatorSuite.scala b/sdk/metrics/src/test/scala/org/typelevel/otel4s/sdk/metrics/aggregation/SumAggregatorSuite.scala index 1409daf6e..1d8261266 100644 --- a/sdk/metrics/src/test/scala/org/typelevel/otel4s/sdk/metrics/aggregation/SumAggregatorSuite.scala +++ b/sdk/metrics/src/test/scala/org/typelevel/otel4s/sdk/metrics/aggregation/SumAggregatorSuite.scala @@ -16,6 +16,7 @@ package org.typelevel.otel4s.sdk.metrics.aggregation +import cats.data.NonEmptyVector import cats.effect.IO import cats.effect.SyncIO import cats.effect.std.Random @@ -60,7 +61,7 @@ class SumAggregatorSuite extends CatsEffectSuite with ScalaCheckEffectSuite { test("synchronous - aggregate with reset - return delta sum") { PropF.forAllF( - Gen.listOf(Gen.long), + Gens.nonEmptyVector(Gen.long), Gens.attributes, Gens.attributes, Gens.traceContext @@ -78,17 +79,16 @@ class SumAggregatorSuite extends CatsEffectSuite with ScalaCheckEffectSuite { PointData.longNumber( timeWindow, attributes, - values.lastOption.map { last => + Vector( ExemplarData.long( - exemplarAttributes.filterNot(a => - attributes.get(a.key).isDefined - ), + exemplarAttributes + .filterNot(a => attributes.get(a.key).isDefined), Duration.Zero, Some(traceContext), - last + values.last ) - }.toVector, - values.sum + ), + values.toVector.sum ) TestControl.executeEmbed { @@ -106,7 +106,7 @@ class SumAggregatorSuite extends CatsEffectSuite with ScalaCheckEffectSuite { test("synchronous - aggregate without reset - return cumulative sum") { PropF.forAllF( - Gen.listOf(Gen.long), + Gens.nonEmptyVector(Gen.long), Gens.attributes, Gens.attributes, Gens.traceContext @@ -120,21 +120,20 @@ class SumAggregatorSuite extends CatsEffectSuite with ScalaCheckEffectSuite { val timeWindow = TimeWindow(100.millis, 200.millis) - def expected(values: List[Long]): PointData.LongNumber = + def expected(values: NonEmptyVector[Long]): PointData.LongNumber = PointData.longNumber( timeWindow, attributes, - values.lastOption.map { last => + Vector( ExemplarData.long( - exemplarAttributes.filterNot(a => - attributes.get(a.key).isDefined - ), + exemplarAttributes + .filterNot(a => attributes.get(a.key).isDefined), Duration.Zero, Some(traceContext), - last + values.last ) - }.toVector, - values.sum + ), + values.toVector.sum ) TestControl.executeEmbed { @@ -152,7 +151,7 @@ class SumAggregatorSuite extends CatsEffectSuite with ScalaCheckEffectSuite { assertEquals(r1: Option[PointData], Some(expected(values))) assertEquals( r2: Option[PointData], - Some(expected(values ++ values)) + Some(expected(values.concatNev(values))) ) } } @@ -165,7 +164,7 @@ class SumAggregatorSuite extends CatsEffectSuite with ScalaCheckEffectSuite { Gens.telemetryResource, Gens.instrumentationScope, Gens.instrumentDescriptor, - Gen.listOf(Gens.longNumberPointData), + Gens.nonEmptyVector(Gens.longNumberPointData), Gens.aggregationTemporality ) { (resource, scope, descriptor, points, temporality) => Random.javaUtilRandom[IO](random).flatMap { implicit R: Random[IO] => @@ -193,7 +192,7 @@ class SumAggregatorSuite extends CatsEffectSuite with ScalaCheckEffectSuite { name = descriptor.name.toString, description = descriptor.description, unit = descriptor.unit, - data = MetricPoints.sum(points.toVector, monotonic, temporality) + data = MetricPoints.sum(points, monotonic, temporality) ) for { @@ -201,7 +200,7 @@ class SumAggregatorSuite extends CatsEffectSuite with ScalaCheckEffectSuite { resource, scope, MetricDescriptor(None, descriptor), - points.toVector, + points, temporality ) } yield assertEquals(metricData, expected) @@ -228,7 +227,7 @@ class SumAggregatorSuite extends CatsEffectSuite with ScalaCheckEffectSuite { Gens.telemetryResource, Gens.instrumentationScope, Gens.instrumentDescriptor, - Gen.listOf(Gens.asynchronousMeasurement(Gen.long)), + Gens.nonEmptyVector(Gens.asynchronousMeasurement(Gen.long)), Gens.aggregationTemporality ) { (resource, scope, descriptor, measurements, temporality) => val aggregator = SumAggregator.asynchronous[IO, Long] @@ -252,7 +251,7 @@ class SumAggregatorSuite extends CatsEffectSuite with ScalaCheckEffectSuite { name = descriptor.name.toString, description = descriptor.description, unit = descriptor.unit, - data = MetricPoints.sum(points.toVector, monotonic, temporality) + data = MetricPoints.sum(points, monotonic, temporality) ) for { @@ -260,7 +259,7 @@ class SumAggregatorSuite extends CatsEffectSuite with ScalaCheckEffectSuite { resource, scope, MetricDescriptor(None, descriptor), - measurements.toVector, + measurements, temporality ) } yield assertEquals(metricData, expected) diff --git a/sdk/metrics/src/test/scala/org/typelevel/otel4s/sdk/metrics/data/MetricPointsSuite.scala b/sdk/metrics/src/test/scala/org/typelevel/otel4s/sdk/metrics/data/MetricPointsSuite.scala index 2688f7c09..c67e0cede 100644 --- a/sdk/metrics/src/test/scala/org/typelevel/otel4s/sdk/metrics/data/MetricPointsSuite.scala +++ b/sdk/metrics/src/test/scala/org/typelevel/otel4s/sdk/metrics/data/MetricPointsSuite.scala @@ -34,16 +34,16 @@ class MetricPointsSuite extends DisciplineSuite { val expected = data match { case sum: MetricPoints.Sum => "MetricPoints.Sum{" + - s"points=${sum.points.mkString("{", ",", "}")}, " + + s"points=${sum.points.toVector.mkString("{", ",", "}")}, " + s"monotonic=${sum.monotonic}, " + s"aggregationTemporality=${sum.aggregationTemporality}}" case gauge: MetricPoints.Gauge => - s"MetricPoints.Gauge{points=${gauge.points.mkString("{", ",", "}")}}" + s"MetricPoints.Gauge{points=${gauge.points.toVector.mkString("{", ",", "}")}}" case histogram: MetricPoints.Histogram => "MetricPoints.Histogram{" + - s"points=${histogram.points.mkString("{", ",", "}")}, " + + s"points=${histogram.points.toVector.mkString("{", ",", "}")}, " + s"aggregationTemporality=${histogram.aggregationTemporality}}" } diff --git a/sdk/metrics/src/test/scala/org/typelevel/otel4s/sdk/metrics/internal/MeterSharedStateSuite.scala b/sdk/metrics/src/test/scala/org/typelevel/otel4s/sdk/metrics/internal/MeterSharedStateSuite.scala index 41d0198e1..a25039aff 100644 --- a/sdk/metrics/src/test/scala/org/typelevel/otel4s/sdk/metrics/internal/MeterSharedStateSuite.scala +++ b/sdk/metrics/src/test/scala/org/typelevel/otel4s/sdk/metrics/internal/MeterSharedStateSuite.scala @@ -17,6 +17,7 @@ package org.typelevel.otel4s.sdk.metrics.internal import cats.data.NonEmptyList +import cats.data.NonEmptyVector import cats.effect.IO import cats.effect.std.Random import cats.mtl.Ask @@ -61,7 +62,7 @@ class MeterSharedStateSuite extends CatsEffectSuite with ScalaCheckEffectSuite { case InstrumentType.Counter => MetricPoints.sum( points = PointDataUtils.toNumberPoints( - Vector(value), + NonEmptyVector.one(value), attributes, timeWindow ), @@ -72,7 +73,7 @@ class MeterSharedStateSuite extends CatsEffectSuite with ScalaCheckEffectSuite { case InstrumentType.UpDownCounter => MetricPoints.sum( PointDataUtils.toNumberPoints( - Vector(value), + NonEmptyVector.one(value), attributes, timeWindow ), @@ -82,9 +83,9 @@ class MeterSharedStateSuite extends CatsEffectSuite with ScalaCheckEffectSuite { case InstrumentType.Histogram => MetricPoints.histogram( - Vector( + NonEmptyVector.one( PointDataUtils.toHistogramPoint( - Vector(value), + NonEmptyVector.one(value), attributes, timeWindow, Aggregation.Defaults.Boundaries @@ -133,7 +134,7 @@ class MeterSharedStateSuite extends CatsEffectSuite with ScalaCheckEffectSuite { case InstrumentType.ObservableCounter => MetricPoints.sum( points = PointDataUtils.toNumberPoints( - Vector(value), + NonEmptyVector.one(value), attributes, timeWindow ), @@ -144,7 +145,7 @@ class MeterSharedStateSuite extends CatsEffectSuite with ScalaCheckEffectSuite { case InstrumentType.ObservableUpDownCounter => MetricPoints.sum( PointDataUtils.toNumberPoints( - Vector(value), + NonEmptyVector.one(value), attributes, timeWindow ), @@ -155,7 +156,7 @@ class MeterSharedStateSuite extends CatsEffectSuite with ScalaCheckEffectSuite { case InstrumentType.ObservableGauge => MetricPoints.gauge( PointDataUtils.toNumberPoints( - Vector(value), + NonEmptyVector.one(value), attributes, timeWindow ) diff --git a/sdk/metrics/src/test/scala/org/typelevel/otel4s/sdk/metrics/internal/SdkObservableMeasurementSuite.scala b/sdk/metrics/src/test/scala/org/typelevel/otel4s/sdk/metrics/internal/SdkObservableMeasurementSuite.scala index f107ce3b8..58ff8a2e9 100644 --- a/sdk/metrics/src/test/scala/org/typelevel/otel4s/sdk/metrics/internal/SdkObservableMeasurementSuite.scala +++ b/sdk/metrics/src/test/scala/org/typelevel/otel4s/sdk/metrics/internal/SdkObservableMeasurementSuite.scala @@ -16,6 +16,7 @@ package org.typelevel.otel4s.sdk.metrics.internal +import cats.data.NonEmptyVector import cats.effect.IO import cats.mtl.Ask import munit.CatsEffectSuite @@ -111,7 +112,11 @@ class SdkObservableMeasurementSuite descriptor.description, descriptor.unit, MetricPoints.gauge( - PointDataUtils.toNumberPoints(Vector(value), attributes, timeWindow) + PointDataUtils.toNumberPoints( + NonEmptyVector.one(value), + attributes, + timeWindow + ) ) ) diff --git a/sdk/metrics/src/test/scala/org/typelevel/otel4s/sdk/metrics/internal/storage/AsynchronousStorageSuite.scala b/sdk/metrics/src/test/scala/org/typelevel/otel4s/sdk/metrics/internal/storage/AsynchronousStorageSuite.scala index c9c0c4520..d07fcf0f7 100644 --- a/sdk/metrics/src/test/scala/org/typelevel/otel4s/sdk/metrics/internal/storage/AsynchronousStorageSuite.scala +++ b/sdk/metrics/src/test/scala/org/typelevel/otel4s/sdk/metrics/internal/storage/AsynchronousStorageSuite.scala @@ -16,6 +16,7 @@ package org.typelevel.otel4s.sdk.metrics.internal.storage +import cats.data.NonEmptyVector import cats.effect.IO import cats.effect.std.Console import cats.mtl.Ask @@ -63,7 +64,7 @@ class AsynchronousStorageSuite Gen.either(Gen.listOf(Gen.long), Gen.listOf(Gen.double)) ) { (descriptor, resource, scope, attributes, timeWindow, values) => def test[A: MeasurementValue: Numeric](values: Vector[A]): IO[Unit] = { - val expected = Option.when(values.nonEmpty)( + val expected = NonEmptyVector.fromVector(values).map { points => MetricData( resource, scope, @@ -72,7 +73,7 @@ class AsynchronousStorageSuite descriptor.unit, MetricPoints.sum( PointDataUtils.toNumberPoints( - values.take(1), + NonEmptyVector.one(points.head), attributes, timeWindow ), @@ -80,7 +81,7 @@ class AsynchronousStorageSuite AggregationTemporality.Cumulative ) ) - ) + } for { storage <- createStorage[A](descriptor) @@ -120,7 +121,7 @@ class AsynchronousStorageSuite descriptor.unit, MetricPoints.sum( PointDataUtils.toNumberPoints( - Vector(value), + NonEmptyVector.one(value), attributes, timeWindow ), @@ -175,7 +176,7 @@ class AsynchronousStorageSuite descriptor.unit, MetricPoints.sum( PointDataUtils.toNumberPoints( - Vector(a), + NonEmptyVector.one(a), attributes, TimeWindow(Duration.Zero, timeWindow.end) ), @@ -235,7 +236,7 @@ class AsynchronousStorageSuite descriptor.unit, MetricPoints.sum( PointDataUtils.toNumberPoints( - Vector(value), + NonEmptyVector.one(value), Attributes.empty, timeWindow ), @@ -291,7 +292,11 @@ class AsynchronousStorageSuite descriptor.description, descriptor.unit, MetricPoints.sum( - PointDataUtils.toNumberPoints(Vector(value), attrs, timeWindow), + PointDataUtils.toNumberPoints( + NonEmptyVector.one(value), + attrs, + timeWindow + ), isMonotonic(descriptor), AggregationTemporality.Cumulative ) diff --git a/sdk/metrics/src/test/scala/org/typelevel/otel4s/sdk/metrics/internal/storage/SynchronousStorageSuite.scala b/sdk/metrics/src/test/scala/org/typelevel/otel4s/sdk/metrics/internal/storage/SynchronousStorageSuite.scala index 04e92571a..8201b212f 100644 --- a/sdk/metrics/src/test/scala/org/typelevel/otel4s/sdk/metrics/internal/storage/SynchronousStorageSuite.scala +++ b/sdk/metrics/src/test/scala/org/typelevel/otel4s/sdk/metrics/internal/storage/SynchronousStorageSuite.scala @@ -16,6 +16,7 @@ package org.typelevel.otel4s.sdk.metrics.internal.storage +import cats.data.NonEmptyVector import cats.effect.IO import cats.effect.std.Console import cats.effect.std.Random @@ -87,7 +88,7 @@ class SynchronousStorageSuite descriptor.unit, MetricPoints.sum( PointDataUtils.toNumberPoints( - Vector(Vector.fill(repeat)(values).flatten.sum), + NonEmptyVector.one(Vector.fill(repeat)(values).flatten.sum), attributes, timeWindow ), @@ -140,7 +141,7 @@ class SynchronousStorageSuite descriptor.unit, MetricPoints.sum( PointDataUtils.toNumberPoints( - Vector(values.sum), + NonEmptyVector.one(values.sum), attributes, TimeWindow(Duration.Zero, timeWindow.end) ), @@ -199,7 +200,7 @@ class SynchronousStorageSuite descriptor.unit, MetricPoints.sum( PointDataUtils.toNumberPoints( - Vector(values.sum), + NonEmptyVector.one(values.sum), Attributes.empty, TimeWindow(Duration.Zero, timeWindow.end) ), @@ -266,7 +267,7 @@ class SynchronousStorageSuite descriptor.unit, MetricPoints.sum( PointDataUtils.toNumberPoints( - Vector(values.sum), + NonEmptyVector.one(values.sum), attrs, TimeWindow(Duration.Zero, timeWindow.end) ), diff --git a/sdk/metrics/src/test/scala/org/typelevel/otel4s/sdk/metrics/scalacheck/Cogens.scala b/sdk/metrics/src/test/scala/org/typelevel/otel4s/sdk/metrics/scalacheck/Cogens.scala index 5c9b6661a..2a7f4662e 100644 --- a/sdk/metrics/src/test/scala/org/typelevel/otel4s/sdk/metrics/scalacheck/Cogens.scala +++ b/sdk/metrics/src/test/scala/org/typelevel/otel4s/sdk/metrics/scalacheck/Cogens.scala @@ -146,15 +146,15 @@ trait Cogens extends org.typelevel.otel4s.sdk.scalacheck.Cogens { implicit val sumMetricPointsCogen: Cogen[MetricPoints.Sum] = Cogen[(Vector[PointData], Boolean, AggregationTemporality)].contramap { s => - (s.points, s.monotonic, s.aggregationTemporality) + (s.points.toVector, s.monotonic, s.aggregationTemporality) } implicit val gaugeMetricPointsCogen: Cogen[MetricPoints.Gauge] = - Cogen[Vector[PointData]].contramap(_.points) + Cogen[Vector[PointData]].contramap(_.points.toVector) implicit val histogramMetricPointsCogen: Cogen[MetricPoints.Histogram] = Cogen[(Vector[PointData], AggregationTemporality)].contramap { h => - (h.points, h.aggregationTemporality) + (h.points.toVector, h.aggregationTemporality) } implicit val metricPointsCogen: Cogen[MetricPoints] = diff --git a/sdk/metrics/src/test/scala/org/typelevel/otel4s/sdk/metrics/scalacheck/Gens.scala b/sdk/metrics/src/test/scala/org/typelevel/otel4s/sdk/metrics/scalacheck/Gens.scala index 2a387d85c..9b2557382 100644 --- a/sdk/metrics/src/test/scala/org/typelevel/otel4s/sdk/metrics/scalacheck/Gens.scala +++ b/sdk/metrics/src/test/scala/org/typelevel/otel4s/sdk/metrics/scalacheck/Gens.scala @@ -17,6 +17,8 @@ package org.typelevel.otel4s.sdk.metrics package scalacheck +import cats.data.NonEmptyVector +import cats.kernel.Order import org.scalacheck.Gen import org.typelevel.ci.CIString import org.typelevel.otel4s.metrics.BucketBoundaries @@ -161,20 +163,20 @@ trait Gens extends org.typelevel.otel4s.sdk.scalacheck.Gens { val exemplarData: Gen[ExemplarData] = Gen.oneOf(longExemplarData, doubleExemplarData) - val longNumberPointData: Gen[PointData.LongNumber] = + def longNumberPointDataGen(gen: Gen[Long]): Gen[PointData.LongNumber] = for { window <- Gens.timeWindow attributes <- Gens.attributes exemplars <- Gen.listOfN(1, Gens.longExemplarData) - value <- Gen.long + value <- gen } yield PointData.longNumber(window, attributes, exemplars.toVector, value) - val doubleNumberPointData: Gen[PointData.DoubleNumber] = + def doubleNumberPointDataGen(gen: Gen[Double]): Gen[PointData.DoubleNumber] = for { window <- Gens.timeWindow attributes <- Gens.attributes exemplars <- Gen.listOfN(1, Gens.doubleExemplarData) - value <- Gen.double + value <- gen } yield PointData.doubleNumber( window, attributes, @@ -182,6 +184,12 @@ trait Gens extends org.typelevel.otel4s.sdk.scalacheck.Gens { value ) + val longNumberPointData: Gen[PointData.LongNumber] = + longNumberPointDataGen(Gen.long) + + val doubleNumberPointData: Gen[PointData.DoubleNumber] = + doubleNumberPointDataGen(Gen.double) + val histogramPointData: Gen[PointData.Histogram] = { def stats(values: List[Double]): Option[PointData.Histogram.Stats] = Option.when(values.nonEmpty)( @@ -257,38 +265,43 @@ trait Gens extends org.typelevel.otel4s.sdk.scalacheck.Gens { // For delta monotonic sums, this means the reader SHOULD expect non-negative values. // For cumulative monotonic sums, this means the reader SHOULD expect values that are not less than the previous value. val sumMetricPoints: Gen[MetricPoints.Sum] = { - implicit val pointNumberOrd: Ordering[PointData.NumberPoint] = - Ordering.by { + implicit val pointNumberOrder: Order[PointData.NumberPoint] = + Order.by { case long: PointData.LongNumber => long.value.toDouble case double: PointData.DoubleNumber => double.value } for { - points <- Gen.oneOf( - Gen.listOf(longNumberPointData), - Gen.listOf(doubleNumberPointData) - ) isMonotonic <- Gen.oneOf(true, false) temporality <- Gens.aggregationTemporality + positiveOnly = isMonotonic && temporality == AggregationTemporality.Delta + points <- Gen.oneOf( + Gens.nonEmptyVector( + longNumberPointDataGen( + if (positiveOnly) Gen.posNum[Long] else Gen.long + ) + ), + Gens.nonEmptyVector( + doubleNumberPointDataGen( + if (positiveOnly) Gen.posNum[Double] else Gen.double + ) + ) + ) } yield { val values = - if (isMonotonic) { - temporality match { - case AggregationTemporality.Delta => - points.filter { - case long: PointData.LongNumber => long.value > 0 - case double: PointData.DoubleNumber => double.value > 0.0 - } - - case AggregationTemporality.Cumulative => - points.sorted[PointData.NumberPoint].distinctBy(_.value) - } + if (isMonotonic && temporality == AggregationTemporality.Cumulative) { + NonEmptyVector.fromVectorUnsafe( + points + .sorted[PointData.NumberPoint] + .toVector + .distinctBy(_.value) + ) } else { points } MetricPoints.sum( - values.toVector, + values, isMonotonic, temporality ) @@ -298,16 +311,20 @@ trait Gens extends org.typelevel.otel4s.sdk.scalacheck.Gens { val gaugeMetricPoints: Gen[MetricPoints.Gauge] = for { points <- Gen.oneOf( - Gen.listOf(longNumberPointData), - Gen.listOf(doubleNumberPointData) + Gens.nonEmptyVector(longNumberPointData), + Gens.nonEmptyVector(doubleNumberPointData) ) - } yield MetricPoints.gauge(points.toVector) + } yield MetricPoints.gauge(points) val histogramMetricPoints: Gen[MetricPoints.Histogram] = for { + point <- histogramPointData points <- Gen.listOf(histogramPointData) temporality <- Gens.aggregationTemporality - } yield MetricPoints.histogram(points.toVector, temporality) + } yield MetricPoints.histogram( + NonEmptyVector(point, points.toVector), + temporality + ) val metricPoints: Gen[MetricPoints] = Gen.oneOf(sumMetricPoints, gaugeMetricPoints, histogramMetricPoints) diff --git a/sdk/metrics/src/test/scala/org/typelevel/otel4s/sdk/metrics/test/PointDataUtils.scala b/sdk/metrics/src/test/scala/org/typelevel/otel4s/sdk/metrics/test/PointDataUtils.scala index b9ceafc6d..d4deddc2c 100644 --- a/sdk/metrics/src/test/scala/org/typelevel/otel4s/sdk/metrics/test/PointDataUtils.scala +++ b/sdk/metrics/src/test/scala/org/typelevel/otel4s/sdk/metrics/test/PointDataUtils.scala @@ -16,6 +16,8 @@ package org.typelevel.otel4s.sdk.metrics.test +import cats.data.NonEmptyVector +import cats.syntax.foldable._ import org.typelevel.otel4s.Attributes import org.typelevel.otel4s.metrics.BucketBoundaries import org.typelevel.otel4s.metrics.MeasurementValue @@ -25,10 +27,10 @@ import org.typelevel.otel4s.sdk.metrics.data.TimeWindow object PointDataUtils { def toNumberPoints[A: MeasurementValue]( - values: Vector[A], + values: NonEmptyVector[A], attributes: Attributes, timeWindow: TimeWindow - ): Vector[PointData.NumberPoint] = + ): NonEmptyVector[PointData.NumberPoint] = MeasurementValue[A] match { case MeasurementValue.LongMeasurementValue(cast) => values.map { a => @@ -52,7 +54,7 @@ object PointDataUtils { } def toHistogramPoint[A]( - values: Vector[A], + values: NonEmptyVector[A], attributes: Attributes, timeWindow: TimeWindow, boundaries: BucketBoundaries @@ -60,12 +62,12 @@ object PointDataUtils { import N.mkNumericOps val stats: Option[PointData.Histogram.Stats] = - Option.when(values.nonEmpty)( + Some( PointData.Histogram.Stats( - sum = values.sum.toDouble, - min = values.min.toDouble, - max = values.max.toDouble, - count = values.size.toLong + sum = values.toVector.sum.toDouble, + min = values.toVector.min.toDouble, + max = values.toVector.max.toDouble, + count = values.size ) )