diff --git a/.gitignore b/.gitignore index 88d65d079..ab0902b20 100644 --- a/.gitignore +++ b/.gitignore @@ -12,5 +12,6 @@ target/ project/project project/target project/metals.sbt +.DS_Store !opentelemetry-javaagent110.jar diff --git a/build.sbt b/build.sbt index 58e6f7611..80fafa471 100644 --- a/build.sbt +++ b/build.sbt @@ -150,7 +150,23 @@ lazy val example = (project in file("example")) akkaTestkit.map(_ % "test") ++ akkaPersistance ++ logback ++ - exampleDependencies + Seq( + "io.circe" %% "circe-core" % CirceVersion, + "io.circe" %% "circe-generic" % CirceVersion, + "io.circe" %% "circe-parser" % CirceVersion, + "de.heikoseeberger" %% "akka-http-circe" % "1.39.2", + "dev.zio" %% "zio" % "2.0.0", + "org.postgresql" % "postgresql" % PostgresVersion, + "com.typesafe.slick" %% "slick" % SlickVersion, + "com.typesafe.slick" %% "slick-hikaricp" % SlickVersion, + "com.typesafe.akka" %% "akka-discovery" % AkkaVersion, + "com.lightbend.akka.management" %% "akka-management" % AkkaManagementVersion, + "com.lightbend.akka.management" %% "akka-management-cluster-http" % AkkaManagementVersion, + "com.lightbend.akka.management" %% "akka-management-cluster-bootstrap" % AkkaManagementVersion, + "io.opentelemetry" % "opentelemetry-sdk-extension-autoconfigure" % OpentelemetryAlphaVersion130, + "io.grpc" % "grpc-netty-shaded" % "1.48.0", + "org.wvlet.airframe" %% "airframe-log" % AirframeVersion + ) }, assemblyMergeStrategySettings, mainClass := Some("example.Boot"), @@ -168,7 +184,8 @@ lazy val example = (project in file("example")) keys }, commands += runExampleWithOtelAgent, - commands += runStreamExampleWithOtelAgent + commands += runStreamExampleWithOtelAgent, + commands += runZioExampleWithOtelAgent ) .dependsOn(extension) @@ -243,3 +260,21 @@ def runStreamExampleWithOtelAgent = Command.command("runStreamExampleWithOtelAge Project.extract(newState).runInputTask(Compile / runMain, " example.SimpleStreamExample", newState) s } + +def runZioExampleWithOtelAgent = Command.command("runZioExampleWithOtelAgent") { state => + val extracted = Project extract state + val newState = extracted.appendWithSession( + Seq( + run / javaOptions ++= Seq( + s"-javaagent:$projectRootDir/opentelemetry-javaagent-$OpentelemetryLatestVersion.jar", + s"-Dotel.service.name=mesmer-zio-example", + s"-Dotel.metric.export.interval=1000", + s"-Dotel.javaagent.extensions=${(otelExtension / assembly).value.absolutePath}" + ) + ), + state + ) + val (s, _) = + Project.extract(newState).runInputTask(Compile / runMain, " example.SimpleZioExample", newState) + s +} diff --git a/docs/getting-started.md b/docs/getting-started.md index ea915a0ca..02a87ef40 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -64,3 +64,14 @@ Both parts need to be included in the application for Mesmer to work. ```sh curl -i http://localhost:9464 ``` + +## How to collect JVM Metrics from ZIO (for Mesmer): + +In your ZIO Application, you will need to add the following layers: + +- `Runtime.enableRuntimeMetrics` +- `DefaultJvmMetrics.live.unit` + +For reference, please follow this ZIO 2.0 SampleApp code: + +https://github.com/zio/zio-metrics-connectors/blob/zio/series2.x/core/jvm/src/test/scala/zio/metrics/connectors/SampleApp.scala#L15-L71 diff --git a/docs/intro.md b/docs/intro.md index c79d7b275..47b8e006b 100644 --- a/docs/intro.md +++ b/docs/intro.md @@ -11,3 +11,33 @@ Auto-instrumentation means it will automatically detect the libraries you use an OpenTelemetry is a set of tools, APIs and SDKs that provide standardized approach to telemetry including: instrumenting, generating, collecting and exporting telemetry data. Mesmer is designed as an extension for the OpenTelemetry Java agent. This way you will benefit from both the general Java APIs metrics that are still useful in Scala (like JDBC or Netty) and the specific Scala instrumentation provided by Mesmer (like Akka or ZIO). + +# Links to get started: + +## ByteBuddy +This is the tool that we use to instrument java bytecode. It provides a declarative API and allows to register transformation on loaded classes. + +* [ByteBuddy Tutorial](https://bytebuddy.net/#/tutorial): provides a good overview, but we don't use many features from here (and not all features that we use are there described). + +* [Rafael Winterhalter — The definite guide to Java agents](https://www.youtube.com/watch?v=OF3YFGZcQkg): talk by the creator. + * Provides good introduction to Advice (tool that we heavily use in mesmer). + + +## OpenTelemetry +Initiative that's goal is to create a spec for observable systems and provide tools to implement it for different languages. + +* [OpenTelemetry Java SDK](https://github.com/open-telemetry/opentelemetry-java): + * tool for Manual Instrumentation +* [OpenTelemetry Java Auto-Instrumentation](https://github.com/open-telemetry/opentelemetry-java-instrumentation) + * depends on the former + * collection of auto instruments that integrates with an application seamlessly + * our goal is to make Mesmer a plugin for this + * One of the Mesmer Devs recommended that check out the repository, but it's huge and requires a lot of time to index by IDE. + +* [Conceptual Introduction to OpenTelemetry](https://www.youtube.com/watch?v=DbaO0Xxv34c) + + +## Mesmer +* [Mesmer Contributor Guide](https://github.com/ScalaConsultants/mesmer/blob/main/CONTRIBUTORS.md) +* [ScalaC Mesmer Article](https://scalac.io/blog/the-opentelemetry-mesmer-duo-state-of-the-mesmer-project/) +* [ScalaC - Mesmer homepage](https://scalac.io/mesmer-opentelemetry-extension/) diff --git a/docs/supported-metrics.md b/docs/supported-metrics.md index 15ff98b48..17c9617fa 100644 --- a/docs/supported-metrics.md +++ b/docs/supported-metrics.md @@ -54,3 +54,58 @@ In Mesmer we support 3 types of metrics: | Stream throughput | counter | | Operator throughput | counter | | Operator processing time | counter | + +## ZIO Executor Metrics + +| Metric | Type | +|---------------------------------------|-----------| +| Executor Capacity | gauge | +| Executor Concurrency | gauge | +| Executor Dequeued Count | counter | +| Executor Enqueued Count | counter | +| Executor Size | gauge | +| Executor Worker Count | counter | + +## ZIO JVM Metrics + +| Metric | Type | +|---------------------------------------|-----------| +| JVM Buffer Pool Capacity Bytes | gauge | +| JVM Buffer Pool Used Buffers | gauge | +| JVM Buffer Pool Used Bytes | gauge | +| JVM Classes Loaded | gauge | +| JVM Classes Loaded Total | gauge | +| JVM Classes Unloaded Total | gauge | +| JVM GC Collection Seconds Count | histogram | +| JVM GC Collection Seconds Sum | histogram | +| JVM Info | gauge | +| JVM Memory Bytes Committed | gauge | +| JVM Memory Bytes Init | gauge | +| JVM Memory Bytes Max | gauge | +| JVM Memory Bytes Used | gauge | +| JVM Memory Pool Allocated Bytes Total | counter | +| JVM Memory Pool Bytes Committed | gauge | +| JVM Memory Pool Bytes Init | gauge | +| JVM Memory Pool Bytes Max | gauge | +| JVM Memory Pool Bytes Used | gauge | +| JVM Threads Current | gauge | +| JVM Threads Daemon | gauge | +| JVM Threads Deadlocked | gauge | +| JVM Threads Deadlocked Monitor | gauge | +| JVM Threads Peak | gauge | +| JVM Threads Started Total | gauge | + +## ZIO Runtime Metrics + +| Metric | Type | +|---------------------------------------|-----------| +| Forwarded Null | counter | +| Process CPU Seconds Total | gauge | +| Process Max FDS | gauge | +| Process Open FDS | gauge | +| Process Resident Memory Bytes | gauge | +| Process Start Time Seconds | gauge | +| Process Virtual Memory Bytes | gauge | +| ZIO Fiber Failures | counter | +| ZIO Fiber Started | counter | +| ZIO Fiber Successes | counter | diff --git a/example/README.md b/example/README.md index 297208573..afa1c1f63 100644 --- a/example/README.md +++ b/example/README.md @@ -13,7 +13,7 @@ This will set up everything needed by the application: data (Averages, Requests per second etc). It shows graphs based both on Mesmer-provided (Akka Persistence + Actor Metrics) and OpenTelemetry-provided metrics (Akka Http). -## Run the application +## Run the application (both Akka and ZIO examples) ``` sbt "project example" runExampleWithOtelAgent @@ -25,6 +25,12 @@ or (for Akka Streaming example) sbt "project example" runStreamExampleWithOtelAgent ``` +or (for ZIO 2.0.0 example) + +``` +sbt "project example" runZioExampleWithOtelAgent +``` + ## In case you are running the non-streaming example: call the endpoints You can now interact with the application to generate some traffic and metrics: diff --git a/example/src/main/scala/example/SimpleStreamExample.scala b/example/src/main/scala/example/SimpleStreamExample.scala index 46ea19209..77d1bf934 100644 --- a/example/src/main/scala/example/SimpleStreamExample.scala +++ b/example/src/main/scala/example/SimpleStreamExample.scala @@ -14,8 +14,9 @@ import scala.concurrent.duration._ /** * Another example useful to testing if stream instrumentation work. It's a simple example that prints amount of process - * elements roughly in 5 seconds intervals and after shutdown. Data should be compared with information that can be - * found in prometheus. + * elements roughly in 5 seconds intervals and after shutdown. + * + * Data should be compared with information that can be found in prometheus. */ object SimpleStreamExample extends App { diff --git a/example/src/main/scala/example/SimpleZioExample.scala b/example/src/main/scala/example/SimpleZioExample.scala new file mode 100644 index 000000000..048c39412 --- /dev/null +++ b/example/src/main/scala/example/SimpleZioExample.scala @@ -0,0 +1,16 @@ +package example + +import zio._ +import zio.metrics.jvm.DefaultJvmMetrics + +object SimpleZioExample extends ZIOAppDefault { + + override def run: ZIO[Any, Throwable, Boolean] = + ZioProgram + .findTheMeaningOfLife(3, Int.MinValue, Int.MaxValue) + .provide( + Runtime.enableRuntimeMetrics, // NOTE: refactored by following this zio-metrics-connectors example ( https://github.com/zio/zio-metrics-connectors/blob/zio/series2.x/core/jvm/src/test/scala/zio/metrics/connectors/SampleApp.scala#L15-L71 ) + DefaultJvmMetrics.live.unit // NOTE: DefaultJvmMetrics.live collects the same JVM metrics as the Prometheus Java client's default exporters + ) + +} diff --git a/example/src/main/scala/example/ZioProgram.scala b/example/src/main/scala/example/ZioProgram.scala new file mode 100644 index 000000000..e1f290d72 --- /dev/null +++ b/example/src/main/scala/example/ZioProgram.scala @@ -0,0 +1,29 @@ +package example + +import zio.Console +import zio.Random +import zio.Schedule +import zio.ZIO +import zio.durationInt + +object ZioProgram { + + def findTheMeaningOfLife(parallelism: Int, lowerBound: Int, upperBound: Int): ZIO[Any, Nothing, Boolean] = { + val numberToGuess = 42 + + val task: ZIO[Any, Nothing, Boolean] = { + val recurringTask = (for { + _ <- Console.printLine("Looking for the meaning of life...") + guessedNumber <- Random.nextIntBetween(lowerBound, upperBound) + _ <- Console.printLine(s"Found some number: $guessedNumber") *> ZIO.sleep(100.milliseconds) + result = guessedNumber == numberToGuess + } yield result).orDie + + recurringTask.repeat(Schedule.recurUntilEquals(true)) + } + + val tasks = (0 until parallelism).map(_ => task).toList + ZIO.raceAll(tasks.head, tasks.tail) + } + +} diff --git a/project/Dependencies.scala b/project/Dependencies.scala index f4b8129df..f8bd642e3 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -65,7 +65,7 @@ object Dependencies { ) val openTelemetryMuzzle = Seq( - "io.opentelemetry.javaagent" % "opentelemetry-muzzle" % OpentelemetryAlphaVersion131, + "io.opentelemetry.javaagent" % "opentelemetry-muzzle" % OpentelemetryAlphaVersion131, "io.opentelemetry.javaagent" % "opentelemetry-javaagent-bootstrap" % OpentelemetryAlphaVersion131 ) @@ -83,21 +83,4 @@ object Dependencies { val scalatest = Seq("org.scalatest" %% "scalatest" % ScalatestVersion) val akkaMultiNodeTestKit = Seq("com.typesafe.akka" %% "akka-multi-node-testkit" % AkkaVersion) - - val exampleDependencies = Seq( - "io.circe" %% "circe-core" % CirceVersion, - "io.circe" %% "circe-generic" % CirceVersion, - "io.circe" %% "circe-parser" % CirceVersion, - "de.heikoseeberger" %% "akka-http-circe" % "1.39.2", - "org.postgresql" % "postgresql" % PostgresVersion, - "com.typesafe.slick" %% "slick" % SlickVersion, - "com.typesafe.slick" %% "slick-hikaricp" % SlickVersion, - "com.typesafe.akka" %% "akka-discovery" % AkkaVersion, - "com.lightbend.akka.management" %% "akka-management" % AkkaManagementVersion, - "com.lightbend.akka.management" %% "akka-management-cluster-http" % AkkaManagementVersion, - "com.lightbend.akka.management" %% "akka-management-cluster-bootstrap" % AkkaManagementVersion, - "io.opentelemetry" % "opentelemetry-sdk-extension-autoconfigure" % OpentelemetryAlphaVersion130, - "io.grpc" % "grpc-netty-shaded" % "1.48.0", - "org.wvlet.airframe" %% "airframe-log" % AirframeVersion - ) }