-
Notifications
You must be signed in to change notification settings - Fork 12
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #678 from ptrdom/item-667
Instrument `http4s`
- Loading branch information
Showing
7 changed files
with
296 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
142 changes: 142 additions & 0 deletions
142
...lac/mesmer/otelextension/instrumentations/http4s/ember/server/Http4sEmberServerTest.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,142 @@ | ||
package io.scalac.mesmer.otelextension.instrumentations.http4s.ember.server | ||
|
||
import cats.effect._ | ||
import cats.effect.unsafe.implicits.global | ||
import cats.syntax.all._ | ||
import com.comcast.ip4s._ | ||
import io.opentelemetry.api.common.Attributes | ||
import io.scalac.mesmer.agent.utils.OtelAgentTest | ||
import io.scalac.mesmer.core.config.MesmerPatienceConfig | ||
import org.http4s.HttpApp | ||
import org.http4s.HttpRoutes | ||
import org.http4s.Uri | ||
import org.http4s.ember.client.EmberClientBuilder | ||
import org.http4s.ember.server.EmberServerBuilder | ||
import org.scalatest.BeforeAndAfterEach | ||
import org.scalatest.Inside | ||
import org.scalatest.freespec.AnyFreeSpec | ||
import org.scalatest.matchers.should.Matchers | ||
|
||
import scala.jdk.CollectionConverters._ | ||
|
||
class Http4sEmberServerTest | ||
extends AnyFreeSpec | ||
with OtelAgentTest | ||
with Matchers | ||
with MesmerPatienceConfig | ||
with BeforeAndAfterEach | ||
with Inside { | ||
import Http4sEmberServerTest._ | ||
|
||
private def service(block: () => Unit): HttpApp[IO] = { | ||
import org.http4s.dsl.io._ | ||
|
||
HttpRoutes | ||
.of[IO] { case GET -> Root => | ||
block() | ||
Ok("") | ||
} | ||
.orNotFound | ||
} | ||
|
||
private def url(address: SocketAddress[Host], path: String = ""): Uri = | ||
Uri.unsafeFromString( | ||
s"http://${Uri.Host.fromIp4sHost(address.host).renderString}:${address.port.value}$path" | ||
) | ||
|
||
private def server(block: () => Unit) = EmberServerBuilder | ||
.default[IO] | ||
.withHttpApp(service(block)) | ||
.withPort(port"0") | ||
.build | ||
|
||
private val client = EmberClientBuilder.default[IO].build | ||
|
||
private def doGetRootCall(block: () => Unit = () => ()) = { | ||
server(block) | ||
.use(server => | ||
client.use(client => | ||
client | ||
.get(url(server.addressIp4s))(_.status.pure[IO]) | ||
) | ||
) | ||
.unsafeRunSync() | ||
() | ||
} | ||
|
||
"http4s ember server" - { | ||
"should record" - { | ||
"requests" - { | ||
"total counter" in { | ||
doGetRootCall() | ||
|
||
assertMetric("mesmer_http4s_ember_server_requests") { data => | ||
inside(data.getLongSumData.getPoints.asScala.toList) { case List(point) => | ||
point.getValue shouldEqual 1 | ||
point.getAttributes.asScalaMap() should contain theSameElementsAs Map( | ||
"method" -> "GET", | ||
"path" -> "/", | ||
"status" -> "200" | ||
) | ||
} | ||
} | ||
} | ||
|
||
"duration histogram" in { | ||
doGetRootCall() | ||
|
||
assertMetric("mesmer_http4s_ember_server_request_duration_seconds") { data => | ||
inside(data.getHistogramData.getPoints.asScala.toList) { case List(point) => | ||
point.getAttributes.asScalaMap() should contain theSameElementsAs Map( | ||
"method" -> "GET", | ||
"path" -> "/", | ||
"status" -> "200" | ||
) | ||
} | ||
} | ||
} | ||
|
||
"concurrent counter" in { | ||
val expectedAttributes = Map( | ||
"method" -> "GET", | ||
"path" -> "/" | ||
) | ||
|
||
val assertZeroConcurrentRequests = () => | ||
assertMetric("mesmer_http4s_ember_server_concurrent_requests") { data => | ||
inside(data.getLongSumData.getPoints.asScala.toList) { case List(point) => | ||
point.getValue shouldEqual 0 | ||
point.getAttributes.asScalaMap() should contain theSameElementsAs expectedAttributes | ||
} | ||
} | ||
|
||
assertZeroConcurrentRequests() | ||
|
||
doGetRootCall { () => | ||
assertMetric("mesmer_http4s_ember_server_concurrent_requests") { data => | ||
inside(data.getLongSumData.getPoints.asScala.toList) { case List(point) => | ||
point.getValue shouldEqual 1 | ||
point.getAttributes.asScalaMap() should contain theSameElementsAs expectedAttributes | ||
} | ||
} | ||
} | ||
|
||
assertZeroConcurrentRequests() | ||
} | ||
} | ||
} | ||
} | ||
} | ||
|
||
object Http4sEmberServerTest { | ||
implicit class AttributesAsMap(attributes: Attributes) { | ||
def asScalaMap(): Map[String, AnyRef] = | ||
attributes | ||
.asMap() | ||
.asScala | ||
.map { case (k, v) => | ||
(k.getKey, v) | ||
} | ||
.toMap | ||
} | ||
} |
46 changes: 46 additions & 0 deletions
46
...esmer/otelextension/http4s/ember/server/MesmerHttp4sEmberServerInstrumentationModule.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
package io.scalac.mesmer.otelextension.http4s.ember.server; | ||
|
||
import com.google.auto.service.AutoService; | ||
import io.opentelemetry.javaagent.extension.instrumentation.InstrumentationModule; | ||
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation; | ||
import io.opentelemetry.javaagent.tooling.muzzle.InstrumentationModuleMuzzle; | ||
import io.opentelemetry.javaagent.tooling.muzzle.VirtualFieldMappingsBuilder; | ||
import io.opentelemetry.javaagent.tooling.muzzle.references.ClassRef; | ||
import io.scalac.mesmer.otelextension.instrumentations.http4s.ember.server.Http4sEmberServerInstrumentations; | ||
|
||
import java.util.Collections; | ||
import java.util.List; | ||
import java.util.Map; | ||
|
||
@AutoService(InstrumentationModule.class) | ||
public class MesmerHttp4sEmberServerInstrumentationModule extends InstrumentationModule | ||
implements InstrumentationModuleMuzzle { | ||
public MesmerHttp4sEmberServerInstrumentationModule() { | ||
super("mesmer-http4s-ember-server"); | ||
} | ||
|
||
@Override | ||
public List<TypeInstrumentation> typeInstrumentations() { | ||
return Collections.singletonList(Http4sEmberServerInstrumentations.serverHelpersRunApp()); | ||
} | ||
|
||
@Override | ||
public List<String> getAdditionalHelperClassNames() { | ||
return List.of( | ||
"io.scalac.mesmer.otelextension.instrumentations.http4s.ember.server.advice.ServerHelpersRunAppAdviceHelper$" | ||
); | ||
} | ||
|
||
@Override | ||
public Map<String, ClassRef> getMuzzleReferences() { | ||
return Collections.emptyMap(); | ||
} | ||
|
||
@Override | ||
public void registerMuzzleVirtualFields(VirtualFieldMappingsBuilder builder) {} | ||
|
||
@Override | ||
public List<String> getMuzzleHelperClassNames() { | ||
return Collections.emptyList(); | ||
} | ||
} |
19 changes: 19 additions & 0 deletions
19
...telextension/instrumentations/http4s/ember/server/Http4sEmberServerInstrumentations.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
package io.scalac.mesmer.otelextension.instrumentations.http4s.ember.server | ||
|
||
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation | ||
|
||
import io.scalac.mesmer.agent.util.dsl.matchers.named | ||
import io.scalac.mesmer.agent.util.i13n.Advice | ||
import io.scalac.mesmer.agent.util.i13n.Instrumentation | ||
|
||
object Http4sEmberServerInstrumentations { | ||
|
||
val serverHelpersRunApp: TypeInstrumentation = | ||
Instrumentation(named("org.http4s.ember.server.internal.ServerHelpers$")) | ||
.`with`( | ||
Advice( | ||
named("runApp"), | ||
"io.scalac.mesmer.otelextension.instrumentations.http4s.ember.server.advice.ServerHelpersRunAppAdvice" | ||
) | ||
) | ||
} |
12 changes: 12 additions & 0 deletions
12
.../otelextension/instrumentations/http4s/ember/server/advice/ServerHelpersRunAppAdvice.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
package io.scalac.mesmer.otelextension.instrumentations.http4s.ember.server.advice; | ||
|
||
import cats.data.Kleisli; | ||
import net.bytebuddy.asm.Advice; | ||
|
||
public class ServerHelpersRunAppAdvice { | ||
|
||
@Advice.OnMethodEnter | ||
public static void runAppEnter(@Advice.Argument(value = 4, readOnly = false) Kleisli<?, ?, ?> httpApp) { | ||
httpApp = ServerHelpersRunAppAdviceHelper.withMetrics(httpApp); | ||
} | ||
} |
64 changes: 64 additions & 0 deletions
64
...tension/instrumentations/http4s/ember/server/advice/ServerHelpersRunAppAdviceHelper.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
package io.scalac.mesmer.otelextension.instrumentations.http4s.ember.server.advice | ||
|
||
import cats.data.Kleisli | ||
import cats.effect.IO | ||
import cats.implicits._ | ||
import io.opentelemetry.api.GlobalOpenTelemetry | ||
import io.opentelemetry.api.common.Attributes | ||
import org.http4s.HttpApp | ||
import org.http4s.Request | ||
import org.http4s.Response | ||
|
||
import scala.util.Try | ||
|
||
object ServerHelpersRunAppAdviceHelper { | ||
|
||
private val meter = GlobalOpenTelemetry.getMeter("mesmer") | ||
|
||
private val requestsTotal = meter | ||
.counterBuilder("mesmer_http4s_ember_server_requests") | ||
.build() | ||
|
||
private val concurrentRequests = meter | ||
.upDownCounterBuilder("mesmer_http4s_ember_server_concurrent_requests") | ||
.build() | ||
|
||
private val requestDuration = meter | ||
.histogramBuilder("mesmer_http4s_ember_server_request_duration_seconds") | ||
.build() | ||
|
||
private def attributesForRequest(request: Request[IO]) = | ||
Attributes.builder().put("method", request.method.name).put("path", request.pathInfo.renderString) | ||
|
||
private def attributesForResponse(response: Response[IO]) = | ||
Attributes.builder().put("status", response.status.code.toString) | ||
|
||
def withMetrics(httpApp: Any): Kleisli[IO, Request[IO], Response[IO]] = | ||
Kleisli[IO, Request[IO], Response[IO]] { request => | ||
val requestAttributes = attributesForRequest(request).build() | ||
val startTime = System.nanoTime() | ||
|
||
concurrentRequests.add(1, requestAttributes) | ||
|
||
httpApp | ||
.asInstanceOf[HttpApp[IO]] | ||
.run(request) | ||
.attemptTap { response => | ||
IO.fromTry(Try { | ||
val allAttributes = requestAttributes.toBuilder | ||
.putAll( | ||
response | ||
.map(attributesForResponse(_).build()) | ||
.getOrElse(Attributes.empty()) | ||
) | ||
.build() | ||
|
||
requestsTotal.add(1, allAttributes) | ||
|
||
requestDuration.record((System.nanoTime() - startTime) / 1e9d, allAttributes) | ||
|
||
concurrentRequests.add(-1, requestAttributes) | ||
}) | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters