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

Use otel4s-semconv-metrics-experimental for semantic testing #129

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -67,11 +67,12 @@ lazy val metrics = crossProject(JVMPlatform, JSPlatform, NativePlatform)
.settings(
name := s"$baseName-metrics",
libraryDependencies ++= Seq(
"org.http4s" %%% "http4s-core" % http4sV,
"org.http4s" %%% "http4s-client" % http4sV,
"org.typelevel" %%% "otel4s-core-common" % otel4sV,
"org.typelevel" %%% "otel4s-core-metrics" % otel4sV,
"org.typelevel" %%% "otel4s-semconv" % otel4sV,
"org.http4s" %%% "http4s-server" % http4sV % Test,
"org.typelevel" %%% "otel4s-semconv-metrics-experimental" % otel4sV % Test,
),
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,7 @@ object OtelMetrics {
Meter[F]
.upDownCounter[Long](s"http.$kind.active_requests")
.withUnit("{request}")
.withDescription("Number of active HTTP requests.")
.withDescription(s"Number of active HTTP $kind requests.")
.create

val abnormalTerminations: F[Histogram[F, Double]] =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,19 @@ package otel4s.middleware.metrics
import cats.data.OptionT
import cats.effect.IO
import munit.CatsEffectSuite
import org.http4s.server.middleware.Metrics
import org.http4s.client.Client
import org.http4s.client.middleware.{Metrics => ClientMetrics}
import org.http4s.server.middleware.{Metrics => ServerMetrics}
import org.typelevel.otel4s.Attributes
import org.typelevel.otel4s.metrics.Meter
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.testkit.metrics.MetricsTestkit
import org.typelevel.otel4s.semconv.MetricSpec
import org.typelevel.otel4s.semconv.Requirement
import org.typelevel.otel4s.semconv.experimental.metrics.HttpExperimentalMetrics
import org.typelevel.otel4s.semconv.metrics.HttpMetrics

class OtelMetricsTests extends CatsEffectSuite {
test("OtelMetrics") {
Expand All @@ -41,7 +48,7 @@ class OtelMetricsTests extends CatsEffectSuite {
_ <- {
val fakeServer =
HttpRoutes[IO](e => OptionT.liftF(e.body.compile.drain.as(Response[IO](Status.Ok))))
val meteredServer = Metrics[IO](metricsOps)(fakeServer)
val meteredServer = ServerMetrics[IO](metricsOps)(fakeServer)

meteredServer
.run(Request[IO](Method.GET))
Expand Down Expand Up @@ -101,4 +108,77 @@ class OtelMetricsTests extends CatsEffectSuite {
}
}
}

test("server semantic test") {
val specs = List(
HttpMetrics.ServerRequestDuration,
HttpExperimentalMetrics.ServerActiveRequests,
)

MetricsTestkit.inMemory[IO]().use { testkit =>
testkit.meterProvider.get("meter").flatMap { implicit meter =>
val fakeServer =
HttpRoutes[IO](e => OptionT.liftF(e.body.compile.drain.as(Response[IO](Status.Ok))))

for {
metricsOps <- OtelMetrics.serverMetricsOps[IO]()
server = ServerMetrics[IO](metricsOps)(fakeServer)

_ <- server.run(Request[IO](Method.GET)).semiflatMap(_.body.compile.drain).value
metrics <- testkit.collectMetrics
} yield specs.foreach(spec => specTest(metrics, spec))
}
}
}

test("client semantic test") {
val specs = List(
HttpMetrics.ClientRequestDuration,
HttpExperimentalMetrics.ClientActiveRequests,
)

MetricsTestkit.inMemory[IO]().use { testkit =>
testkit.meterProvider.get("meter").flatMap { implicit meter =>
val fakeServer =
HttpRoutes[IO](e => OptionT.liftF(e.body.compile.drain.as(Response[IO](Status.Ok))))

for {
clientOps <- OtelMetrics.clientMetricsOps[IO]()
client = ClientMetrics[IO](clientOps)(Client.fromHttpApp(fakeServer.orNotFound))

_ <- client.run(Request[IO](Method.GET)).use(_.body.compile.drain)
metrics <- testkit.collectMetrics
} yield specs.foreach(spec => specTest(metrics, spec))
}
}
}

private def specTest(metrics: List[MetricData], spec: MetricSpec): Unit = {
val metric = metrics.find(_.name == spec.name)
assert(
metric.isDefined,
s"${spec.name} metric is missing. Available [${metrics.map(_.name).mkString(", ")}]",
)

val clue = s"[${spec.name}] has a mismatched property"

metric.foreach { md =>
assertEquals(md.name, spec.name, clue)
assertEquals(md.description, Some(spec.description), clue)
assertEquals(md.unit, Some(spec.unit), clue)

val required = spec.attributeSpecs
.filter(_.requirement.level == Requirement.Level.Required)
.map(_.key)
.toSet

val current = md.data.points.toVector
.flatMap(_.attributes.map(_.key))
.filter(key => required.contains(key))
.toSet

assertEquals(current, required, clue)
}
}

}
Loading