Metrics | Cats Effect IO runtime

Available metrics

CPU Starvation

Platforms: JVM, Scala.js, Scala Native.

These metrics could help identify performance bottlenecks caused by an overloaded compute pool, excessive task scheduling, or lack of CPU resources.

Name Description Unit
cats.effect.runtime.cpu.starvation.clock.drift.current The current CPU drift in milliseconds. ms
cats.effect.runtime.cpu.starvation.clock.drift.max The max CPU drift in milliseconds. ms
cats.effect.runtime.cpu.starvation.count The number of CPU starvation events.

Work-stealing thread pool - compute

Platforms: JVM.

Built-in attributes:

These metrics provide insights about fibers and threads within the compute pool. They help diagnose load distribution, identify bottlenecks, and monitor the pool’s efficiency in handling tasks.

Name Description Unit
cats.effect.runtime.wstp.compute.fiber.enqueued.count The total number of fibers enqueued on all local queues. {fiber}
cats.effect.runtime.wstp.compute.fiber.suspended.count The number of fibers which are currently asynchronously suspended. {fiber}
cats.effect.runtime.wstp.compute.thread.active.count The number of active worker thread instances currently executing fibers on the compute thread pool. {thread}
cats.effect.runtime.wstp.compute.thread.blocked.count The number of worker thread instances that can run blocking actions on the compute thread pool. {thread}
cats.effect.runtime.wstp.compute.thread.count The number of worker thread instances backing the work-stealing thread pool (WSTP). {thread}
cats.effect.runtime.wstp.compute.thread.searching.count The number of worker thread instances currently searching for fibers to steal from other worker threads. {thread}

Work-stealing thread pool - thread

Platforms: JVM.

Built-in attributes:

These metrics provide detailed information about threads state within the compute pool.

Name Description Unit
cats.effect.runtime.wstp.worker.thread.event.count The total number of events that happened to this WorkerThread. {event}
cats.effect.runtime.wstp.worker.thread.idle.duration The total amount of time in nanoseconds that this WorkerThread has been idle. ns

Work-stealing thread pool - local queue

Platforms: JVM.

Built-in attributes:

These metrics provide a detailed view of fiber distribution within the pool. They help diagnose load imbalances and system inefficiency.

Name Description Unit
cats.effect.runtime.wstp.worker.localqueue.fiber.count The total number of fibers enqueued during the lifetime of the local queue. {fiber}
cats.effect.runtime.wstp.worker.localqueue.fiber.enqueued.count The current number of enqueued fibers. {fiber}
cats.effect.runtime.wstp.worker.localqueue.fiber.spillover.count The total number of fibers spilt over to the external queue. {fiber}
cats.effect.runtime.wstp.worker.localqueue.fiber.steal_attempt.count The total number of successful steal attempts by other worker threads. {fiber}
cats.effect.runtime.wstp.worker.localqueue.fiber.stolen.count The total number of stolen fibers by other worker threads. {fiber}

Work-stealing thread pool - timer heap

Platforms: JVM.

Built-in attributes:

These metrics provide a detailed view of timer stats within the pool.

Name Description Unit
cats.effect.runtime.wstp.worker.timerheap.next.due Returns the time in nanoseconds till the next due to fire. ns
cats.effect.runtime.wstp.worker.timerheap.outstanding.count The current number of the outstanding timers, that remain to be executed. {timer}
cats.effect.runtime.wstp.worker.timerheap.packed.count The total number of times the heap packed itself to remove canceled timers. {event}
cats.effect.runtime.wstp.worker.timerheap.timer.count The total number of the timers per state. {timer}

Work-stealing thread pool - poller

Platforms: JVM.

Built-in attributes:

These metrics provide a detailed view of poller stats within the pool.

Name Description Unit
cats.effect.runtime.wstp.worker.poller.operation.count The total number of the operations per category and outcome. {operation}
cats.effect.runtime.wstp.worker.poller.operation.outstanding.count The current number of outstanding operations per category and outcome. {operation}

Getting started

Add the following configuration to the favorite build tool:

Add settings to the build.sbt:

libraryDependencies ++= Seq(
  "org.typelevel" %%% "otel4s-instrumentation-metrics" % "0.12.0-RC2" // <1>
)

Add directives to the *.scala file:

//> using dep "org.typelevel::otel4s-instrumentation-metrics::0.12.0-RC2" // <1>
  1. Add the otel4s-instrumentation-metrics library

Registering metrics collectors

IORuntimeMetrics.register takes care of the metrics lifecycle management.

import cats.effect._
import org.typelevel.otel4s.instrumentation.ce.IORuntimeMetrics
import org.typelevel.otel4s.metrics.MeterProvider
import org.typelevel.otel4s.trace.TracerProvider
import org.typelevel.otel4s.oteljava.OtelJava

object Main extends IOApp.Simple {

  def run: IO[Unit] =
    OtelJava.autoConfigured[IO]().use { otel4s =>
      implicit val mp: MeterProvider[IO] = otel4s.meterProvider
      IORuntimeMetrics
        .register[IO](runtime.metrics, IORuntimeMetrics.Config.default)
        .surround {
          program(otel4s.meterProvider, otel4s.tracerProvider)
        }
    }

  def program(
      meterProvider: MeterProvider[IO],
      tracerProvider: TracerProvider[IO]
  ): IO[Unit] = {
    val _ = (meterProvider, tracerProvider)
    IO.unit
  }

}
import cats.effect._
import org.typelevel.otel4s.instrumentation.ce.IORuntimeMetrics
import org.typelevel.otel4s.metrics.MeterProvider
import org.typelevel.otel4s.trace.TracerProvider
import org.typelevel.otel4s.sdk.OpenTelemetrySdk

object Main extends IOApp.Simple {

  def run: IO[Unit] =
    OpenTelemetrySdk.autoConfigured[IO]().use { autoConfigured =>
      val sdk = autoConfigured.sdk
      implicit val mp: MeterProvider[IO] = sdk.meterProvider
      IORuntimeMetrics
        .register[IO](runtime.metrics, IORuntimeMetrics.Config.default)
        .surround {
          program(sdk.meterProvider, sdk.tracerProvider)
        }
    }

  def program(
      meterProvider: MeterProvider[IO],
      tracerProvider: TracerProvider[IO]
  ): IO[Unit] = {
    val _ = (meterProvider, tracerProvider)
    IO.unit
  }

}

Grafana dashboard

You can use a Grafana dashboard to visualize collected metrics.

Grafana Dashboard

Customization

The behavior of the IORuntimeMetrics.register can be customized via IORuntimeMetrics.Config.

CPU Starvation

To disable CPU starvation metrics:

val config: IORuntimeMetrics.Config = {
  import IORuntimeMetrics.Config._
  IORuntimeMetrics.Config(
    CpuStarvationConfig.disabled, // disable CPU starvation metrics 
    WorkStealingThreadPoolConfig.enabled
  )
}

IORuntimeMetrics.register[IO](runtime.metrics, config)

To attach attributes to CPU starvation metrics:

val config: IORuntimeMetrics.Config = {
  import IORuntimeMetrics.Config._
  IORuntimeMetrics.Config(
    CpuStarvationConfig.enabled(
      Attributes(Attribute("key", "value")) // the attributes
    ), 
    WorkStealingThreadPoolConfig.enabled
  )
}

IORuntimeMetrics.register[IO](runtime.metrics, config)

Work-stealing thread pool - compute

To disable worker metrics:

val config: IORuntimeMetrics.Config = {
  import IORuntimeMetrics.Config._
  import WorkStealingThreadPoolConfig._

  IORuntimeMetrics.Config(
    CpuStarvationConfig.enabled,
    WorkStealingThreadPoolConfig(
      ComputeConfig.disabled, // disable compute metrics
      WorkStealingThreadPoolConfig.WorkerThreadsConfig.enabled
    )
  )
}

IORuntimeMetrics.register[IO](runtime.metrics, config)

To attach attributes to compute metrics:

val config: IORuntimeMetrics.Config = {
  import IORuntimeMetrics.Config._
  import WorkStealingThreadPoolConfig._

  IORuntimeMetrics.Config(
    CpuStarvationConfig.enabled,
    WorkStealingThreadPoolConfig(
      ComputeConfig.enabled(
        Attributes(Attribute("key", "value")) // attributes
      ),
      WorkStealingThreadPoolConfig.WorkerThreadsConfig.enabled
    )
  )
}

IORuntimeMetrics.register[IO](runtime.metrics, config)

Work-stealing thread pool - thread

To disable thread metrics:

val config: IORuntimeMetrics.Config = {
  import IORuntimeMetrics.Config._
  import WorkStealingThreadPoolConfig._

  IORuntimeMetrics.Config(
    CpuStarvationConfig.enabled,
    WorkStealingThreadPoolConfig(
      ComputeConfig.enabled,
      WorkerThreadsConfig(
        WorkerThreadsConfig.ThreadConfig.disabled, // disable worker thread metrics
        WorkerThreadsConfig.LocalQueueConfig.enabled,
        WorkerThreadsConfig.TimerHeapConfig.enabled,
        WorkerThreadsConfig.PollerConfig.enabled
      )
    )
  )
}

IORuntimeMetrics.register[IO](runtime.metrics, config)

To attach attributes to thread metrics:

val config: IORuntimeMetrics.Config = {
  import IORuntimeMetrics.Config._
  import WorkStealingThreadPoolConfig._

  IORuntimeMetrics.Config(
    CpuStarvationConfig.enabled,
    WorkStealingThreadPoolConfig(
      ComputeConfig.enabled,
      WorkerThreadsConfig(
        WorkerThreadsConfig.ThreadConfig.enabled(
          Attributes(Attribute("key", "value")) // the attributes
        ),
        WorkerThreadsConfig.LocalQueueConfig.enabled,
        WorkerThreadsConfig.TimerHeapConfig.enabled,
        WorkerThreadsConfig.PollerConfig.enabled
      )
    )
  )
}

IORuntimeMetrics.register[IO](runtime.metrics, config)

Work-stealing thread pool - local queue

To disable local queue metrics:

val config: IORuntimeMetrics.Config = {
  import IORuntimeMetrics.Config._
  import WorkStealingThreadPoolConfig._

  IORuntimeMetrics.Config(
    CpuStarvationConfig.enabled,
    WorkStealingThreadPoolConfig(
      ComputeConfig.enabled,
      WorkerThreadsConfig(
        WorkerThreadsConfig.ThreadConfig.enabled,
        WorkerThreadsConfig.LocalQueueConfig.disabled, // disable local queue metrics
        WorkerThreadsConfig.TimerHeapConfig.enabled,
        WorkerThreadsConfig.PollerConfig.enabled
      )
    )
  )
}

IORuntimeMetrics.register[IO](runtime.metrics, config)

To attach attributes to local queue metrics:

val config: IORuntimeMetrics.Config = {
  import IORuntimeMetrics.Config._
  import WorkStealingThreadPoolConfig._

  IORuntimeMetrics.Config(
    CpuStarvationConfig.enabled,
    WorkStealingThreadPoolConfig(
      ComputeConfig.enabled,
      WorkerThreadsConfig(
        WorkerThreadsConfig.ThreadConfig.enabled,
        WorkerThreadsConfig.LocalQueueConfig.enabled(
          Attributes(Attribute("key", "value")) // the attributes
        ),
        WorkerThreadsConfig.TimerHeapConfig.enabled,
        WorkerThreadsConfig.PollerConfig.enabled
      )
    )
  )
}

IORuntimeMetrics.register[IO](runtime.metrics, config)

Work-stealing thread pool - timer heap

To disable timer heap metrics:

val config: IORuntimeMetrics.Config = {
  import IORuntimeMetrics.Config._
  import WorkStealingThreadPoolConfig._

  IORuntimeMetrics.Config(
    CpuStarvationConfig.enabled,
    WorkStealingThreadPoolConfig(
      ComputeConfig.enabled,
      WorkerThreadsConfig(
        WorkerThreadsConfig.ThreadConfig.enabled,
        WorkerThreadsConfig.LocalQueueConfig.enabled,
        WorkerThreadsConfig.TimerHeapConfig.enabled, // disable timer heap metrics
        WorkerThreadsConfig.PollerConfig.enabled
      )
    )
  )
}

IORuntimeMetrics.register[IO](runtime.metrics, config)

To attach attributes to timer heap metrics:

val config: IORuntimeMetrics.Config = {
  import IORuntimeMetrics.Config._
  import WorkStealingThreadPoolConfig._

  IORuntimeMetrics.Config(
    CpuStarvationConfig.enabled,
    WorkStealingThreadPoolConfig(
      ComputeConfig.enabled,
      WorkerThreadsConfig(
        WorkerThreadsConfig.ThreadConfig.enabled,
        WorkerThreadsConfig.LocalQueueConfig.enabled,
        WorkerThreadsConfig.TimerHeapConfig.enabled(
          Attributes(Attribute("key", "value")) // the attributes
        ),
        WorkerThreadsConfig.PollerConfig.enabled
      )
    )
  )
}

IORuntimeMetrics.register[IO](runtime.metrics, config)

Work-stealing thread pool - poller

To disable poller metrics:

val config: IORuntimeMetrics.Config = {
  import IORuntimeMetrics.Config._
  import WorkStealingThreadPoolConfig._
  
  IORuntimeMetrics.Config(
    CpuStarvationConfig.enabled,
    WorkStealingThreadPoolConfig(
      ComputeConfig.enabled,
      WorkerThreadsConfig(
        WorkerThreadsConfig.ThreadConfig.enabled,
        WorkerThreadsConfig.LocalQueueConfig.enabled,
        WorkerThreadsConfig.TimerHeapConfig.enabled,
        WorkerThreadsConfig.PollerConfig.disabled // disable poller metrics
      )
    )
  )
}

IORuntimeMetrics.register[IO](runtime.metrics, config)

To attach attributes to poller metrics:

val config: IORuntimeMetrics.Config = {
  import IORuntimeMetrics.Config._
  import WorkStealingThreadPoolConfig._

  IORuntimeMetrics.Config(
    CpuStarvationConfig.enabled,
    WorkStealingThreadPoolConfig(
      ComputeConfig.enabled,
      WorkerThreadsConfig(
        WorkerThreadsConfig.ThreadConfig.enabled,
        WorkerThreadsConfig.LocalQueueConfig.enabled,
        WorkerThreadsConfig.TimerHeapConfig.enabled,
        WorkerThreadsConfig.PollerConfig.enabled(
          Attributes(Attribute("key", "value")) // the attributes
        )
      )
    )
  )
}

IORuntimeMetrics.register[IO](runtime.metrics, config)