Skip to content

Commit

Permalink
Merge pull request #9 from typelevel/runtime-metrics-jvm
Browse files Browse the repository at this point in the history
Implement JVM RuntimeMetrics
  • Loading branch information
iRevive authored Oct 26, 2024
2 parents a732491 + 169ee39 commit 9d635dd
Show file tree
Hide file tree
Showing 11 changed files with 822 additions and 6 deletions.
48 changes: 47 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,15 @@ jobs:
matrix:
os: [ubuntu-latest]
scala: [2.13, 3]
java: [temurin@8]
java: [temurin@8, semeru@21]
project: [rootJS, rootJVM, rootNative]
exclude:
- scala: 3
java: semeru@21
- project: rootJS
java: semeru@21
- project: rootNative
java: semeru@21
runs-on: ${{ matrix.os }}
timeout-minutes: 60
steps:
Expand All @@ -52,6 +59,19 @@ jobs:
if: matrix.java == 'temurin@8' && steps.setup-java-temurin-8.outputs.cache-hit == 'false'
run: sbt +update

- name: Setup Java (semeru@21)
id: setup-java-semeru-21
if: matrix.java == 'semeru@21'
uses: actions/setup-java@v4
with:
distribution: semeru
java-version: 21
cache: sbt

- name: sbt update
if: matrix.java == 'semeru@21' && steps.setup-java-semeru-21.outputs.cache-hit == 'false'
run: sbt +update

- name: Check that workflows are up to date
run: sbt githubWorkflowCheck

Expand Down Expand Up @@ -129,6 +149,19 @@ jobs:
if: matrix.java == 'temurin@8' && steps.setup-java-temurin-8.outputs.cache-hit == 'false'
run: sbt +update

- name: Setup Java (semeru@21)
id: setup-java-semeru-21
if: matrix.java == 'semeru@21'
uses: actions/setup-java@v4
with:
distribution: semeru
java-version: 21
cache: sbt

- name: sbt update
if: matrix.java == 'semeru@21' && steps.setup-java-semeru-21.outputs.cache-hit == 'false'
run: sbt +update

- name: Download target directories (2.13, rootJS)
uses: actions/download-artifact@v4
with:
Expand Down Expand Up @@ -240,6 +273,19 @@ jobs:
if: matrix.java == 'temurin@8' && steps.setup-java-temurin-8.outputs.cache-hit == 'false'
run: sbt +update

- name: Setup Java (semeru@21)
id: setup-java-semeru-21
if: matrix.java == 'semeru@21'
uses: actions/setup-java@v4
with:
distribution: semeru
java-version: 21
cache: sbt

- name: sbt update
if: matrix.java == 'semeru@21' && steps.setup-java-semeru-21.outputs.cache-hit == 'false'
run: sbt +update

- name: Submit Dependencies
uses: scalacenter/sbt-dependency-submission@v2
with:
Expand Down
41 changes: 41 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,46 @@ object Main extends IOApp.Simple {
}
```

### 3) `RuntimeMetrics` - the instrumentation for JVM

The provided metrics:
- Class
- `jvm.class.count`
- `jvm.class.loaded`
- `jvm.class.unloaded`
- CPU
- `jvm.cpu.count`
- `jvm.cpu.recent_utilization`
- `jvm.cpu.time`
- GC
- `jvm.gc.duration`
- Memory
- `jvm.memory.committed`
- `jvm.memory.limit`
- `jvm.memory.used`
- `jvm.memory.used_after_last_gc`
- Thread
- `jvm.thread.count`

Example:
```scala
import cats.effect.{IO, IOApp}
import org.typelevel.otel4s.experimental.metrics._
import org.typelevel.otel4s.sdk._

object Main extends IOApp.Simple {
def app: IO[Unit] = ???

def run: IO[Unit] =
OpenTelemetrySdk.autoConfigured[IO]().use { autoConfigured =>
val sdk = autoConfigured.sdk
sdk.meterProvider.get("service.meter").flatMap { implicit meter =>
RuntimeMetrics.register[IO].surround(app)
}
}
}
```

## Trace - getting started

Add the `otel4s-experimental-trace` dependency to the `build.sbt`:
Expand All @@ -90,6 +130,7 @@ libraryDependencies ++= Seq(

The body of a method annotated with `@span` will be wrapped into a span:
```scala
import cats.effect.IO
import org.typelevel.otel4s.trace.Tracer
import org.typelevel.otel4s.experimental.trace.{attribute, span}

Expand Down
20 changes: 16 additions & 4 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,17 @@ ThisBuild / githubWorkflowBuildPostamble ++= Seq(
)
)

ThisBuild / githubWorkflowJavaVersions := Seq(
JavaSpec.temurin("8"),
JavaSpec.semeru("21")
)

ThisBuild / resolvers ++= Resolver.sonatypeOssRepos("snapshots")

val Versions = new {
val Scala213 = "2.13.14"
val Scala213 = "2.13.15"
val Scala3 = "3.3.3"
val Otel4s = "0.10.0"
val Otel4s = "0.11-7d84643-SNAPSHOT"
val Munit = "1.0.0"
val MUnitScalaCheck = "1.0.0-M11" // we aren't ready for Scala Native 0.5.x
val MUnitCatsEffect = "2.0.0"
Expand All @@ -51,7 +58,11 @@ lazy val metrics = crossProject(JVMPlatform, JSPlatform, NativePlatform)
)
)
.jvmSettings(
Test / fork := true
Test / fork := true,
libraryDependencies ++= Seq(
"org.typelevel" %%% "otel4s-semconv-metrics" % Versions.Otel4s,
"org.typelevel" %%% "otel4s-semconv-metrics-experimental" % Versions.Otel4s % Test,
)
)

lazy val trace = crossProject(JVMPlatform, JSPlatform, NativePlatform)
Expand Down Expand Up @@ -98,7 +109,8 @@ lazy val docs = project
scalacOptions += "-Ymacro-annotations",
tlFatalWarnings := false,
libraryDependencies ++= Seq(
"org.typelevel" %% "otel4s-oteljava" % Versions.Otel4s
"org.typelevel" %% "otel4s-oteljava" % Versions.Otel4s,
"org.typelevel" %% "otel4s-sdk" % Versions.Otel4s
)
)

Expand Down
43 changes: 42 additions & 1 deletion docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,46 @@ object Main extends IOApp.Simple {
}
```

### 3) `RuntimeMetrics` - the instrumentation for JVM

The provided metrics:
- Class
- `jvm.class.count`
- `jvm.class.loaded`
- `jvm.class.unloaded`
- CPU
- `jvm.cpu.count`
- `jvm.cpu.recent_utilization`
- `jvm.cpu.time`
- GC
- `jvm.gc.duration`
- Memory
- `jvm.memory.committed`
- `jvm.memory.limit`
- `jvm.memory.used`
- `jvm.memory.used_after_last_gc`
- Thread
- `jvm.thread.count`

Example:
```scala mdoc:reset:silent
import cats.effect.{IO, IOApp}
import org.typelevel.otel4s.experimental.metrics._
import org.typelevel.otel4s.sdk._

object Main extends IOApp.Simple {
def app: IO[Unit] = ???

def run: IO[Unit] =
OpenTelemetrySdk.autoConfigured[IO]().use { autoConfigured =>
val sdk = autoConfigured.sdk
sdk.meterProvider.get("service.meter").flatMap { implicit meter =>
RuntimeMetrics.register[IO].surround(app)
}
}
}
```

## Trace - getting started

Add the `otel4s-experimental-trace` dependency to the `build.sbt`:
Expand All @@ -89,7 +129,8 @@ libraryDependencies ++= Seq(
### 1) `@span` annotation

The body of a method annotated with `@span` will be wrapped into a span:
```scala mdoc:silent
```scala mdoc:reset:silent
import cats.effect.IO
import org.typelevel.otel4s.trace.Tracer
import org.typelevel.otel4s.experimental.trace.{attribute, span}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/*
* 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.experimental.metrics

import cats.effect.Async
import cats.effect.Resource
import cats.effect.std.Console
import org.typelevel.otel4s.experimental.metrics.jvm._
import org.typelevel.otel4s.metrics.Meter

object RuntimeMetrics {

/** Registers the JVM runtime metrics:
* - Class
* - `jvm.class.count`
* - `jvm.class.loaded`
* - `jvm.class.unloaded`
* - CPU
* - `jvm.cpu.count`
* - `jvm.cpu.recent_utilization`
* - `jvm.cpu.time`
* - GC
* - `jvm.gc.duration`
* - Memory
* - `jvm.memory.committed`
* - `jvm.memory.limit`
* - `jvm.memory.used`
* - `jvm.memory.used_after_last_gc`
* - Thread
* - `jvm.thread.count`
*/
def register[F[_]: Async: Meter: Console]: Resource[F, Unit] =
for {
_ <- ClassMetrics.register
_ <- CpuMetrics.register
_ <- GarbageCollectorMetrics.register
_ <- MemoryPoolMetrics.register
_ <- ThreadMetrics.register
} yield ()

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/*
* 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.experimental.metrics.jvm

import cats.effect.Resource
import cats.effect.Sync
import cats.effect.syntax.resource._
import cats.syntax.flatMap._
import cats.syntax.functor._
import org.typelevel.otel4s.metrics.Meter
import org.typelevel.otel4s.semconv.metrics.JvmMetrics

import java.lang.management.ManagementFactory

/** @see
* [[https://opentelemetry.io/docs/specs/semconv/runtime/jvm-metrics/#jvm-classes]]
*/
object ClassMetrics {

def register[F[_]: Sync: Meter]: Resource[F, Unit] =
Sync[F].delay(ManagementFactory.getClassLoadingMXBean).toResource.flatMap { bean =>
Meter[F].batchCallback.of(
JvmMetrics.ClassCount.createObserver[F, Long],
JvmMetrics.ClassLoaded.createObserver[F, Long],
JvmMetrics.ClassUnloaded.createObserver[F, Long]
) { (classCount, classLoaded, classUnloaded) =>
for {
count <- Sync[F].delay(bean.getTotalLoadedClassCount)
loaded <- Sync[F].delay(bean.getLoadedClassCount)
unloaded <- Sync[F].delay(bean.getUnloadedClassCount)
_ <- classCount.record(count)
_ <- classLoaded.record(loaded.toLong)
_ <- classUnloaded.record(unloaded)
} yield ()
}
}

}
Loading

0 comments on commit 9d635dd

Please sign in to comment.