Custom metrics

The custom metrics extension provides an API whereby a developer can create one of six custom metric types for capturing arbitrary metric data. Also, the API provides the ability to create the custom metrics at both the system-level and actor-level. For example, at the actor-level, you could build and use a Counter metric to capture the number of times an actor receives a particular message.

Custom metric types

The custom metric types we currently support are as follows:

  • Counter: provides an integer to increment and decrement.
  • Gauge Double: provides a settable double.
  • Gauge Long: provides a settable long value.
  • Providing Gauge: takes a ValueProvider as the underlying metric (deprecated and replaced by Providing Gauge Long).
  • Providing Gauge Long: takes a LongValueProvider as the underlying metric.
  • Providing Gauge Double: takes a DoubleValueProvider as the underlying metric.
  • Rate: marks an occurrence for measuring the rate at which it occurs.
  • Recorder: records a long value which can be used to measure distribution.

The underlying behavior of the metric type is defined by the backend used in configuration. For example, if you use chmetrics as your backend, then the underlying metric for a Recorder is a CodaHale Histogram. For more information on backends see Backend Plugins.

Developer API

Accessing CinnamonMetrics

To start using the Cinnamon developer API you will first want to import CinnamonMetrics and the associated metric interfaces:

Scala
import com.lightbend.cinnamon.akka.CinnamonMetrics
import com.lightbend.cinnamon.metric._
Java
import com.lightbend.cinnamon.akka.CinnamonMetrics;
import com.lightbend.cinnamon.metric.*;

Creating system-level custom metrics

System level custom metrics provide an excellent way to capture metric data at the ActorSystem level. To create custom metrics at this level, you use CinnamonMetrics with the ActorSystem and call the appropriate create-method for the desired metric as follows:

Scala
val sysCounter: Counter = CinnamonMetrics(system).createCounter("sysCounter")
val sysGaugeDouble: GaugeDouble = CinnamonMetrics(system).createGaugeDouble("sysGaugeDouble")
val sysGaugeLong: GaugeLong = CinnamonMetrics(system).createGaugeLong("sysGaugeLong")
val sysProvidingGaugeLong: ProvidingGaugeLong = CinnamonMetrics(system).createProvidingGaugeLong("sysProvidingGaugeLong", provider)
val sysRate: Rate = CinnamonMetrics(system).createRate("sysRate")
val sysRecorder: Recorder = CinnamonMetrics(system).createRecorder("sysRecorder")
Java
Counter sysCounter = CinnamonMetrics.get(getSystem()).createCounter("sysCounter");
GaugeDouble sysGaugeDouble = CinnamonMetrics.get(getSystem()).createGaugeDouble("sysGaugeDouble");
GaugeLong sysGaugeLong = CinnamonMetrics.get(getSystem()).createGaugeLong("sysGaugeLong");
ProvidingGaugeLong sysProvidingGauge = CinnamonMetrics.get(getSystem()).createProvidingGaugeLong("sysProvidingGaugeLong", provider);
Rate sysRate = CinnamonMetrics.get(getSystem()).createRate("sysRate");
Recorder sysRecorder = CinnamonMetrics.get(getSystem()).createRecorder("sysRecorder");

Creating actor-level custom metrics

Actor level custom metrics provide an excellent way to capture actor-specific behavior as metric data. To create custom metrics at this level, you use CinnamonMetrics with the ActorContext and call the appropriate create-method for the desired metric as follows:

Scala
val counter: Counter = CinnamonMetrics(context).createCounter("counter")
val gaugeDouble: GaugeDouble = CinnamonMetrics(context).createGaugeDouble("gaugeDouble")
val gaugeLong: GaugeLong = CinnamonMetrics(context).createGaugeLong("gaugeLong")
val providingGaugeLong: ProvidingGaugeLong = CinnamonMetrics(context).createProvidingGaugeLong("providingGaugeLong", longValueProvider)
val providingGaugeDouble: ProvidingGaugeDouble = CinnamonMetrics(context).createProvidingGaugeDouble("providingGaugeDouble", doubleValueProvider)
val rate: Rate = CinnamonMetrics(context).createRate("rate")
val recorder: Recorder = CinnamonMetrics(context).createRecorder("recorder")
Java
Counter counter = CinnamonMetrics.get(getContext()).createCounter("counter");
GaugeDouble gaugeDouble = CinnamonMetrics.get(getContext()).createGaugeDouble("gaugeDouble");
GaugeLong gaugeLong = CinnamonMetrics.get(getContext()).createGaugeLong("gaugeLong");
ProvidingGaugeLong providingGaugeLong = CinnamonMetrics.get(getContext()).createProvidingGaugeLong("providingGaugeLong", longValueProvider);
ProvidingGaugeDouble providingGaugeDouble = CinnamonMetrics.get(getContext()).createProvidingGaugeDouble("providingGaugeDouble", doubleValueProvider);
Rate rate = CinnamonMetrics.get(getContext()).createRate("rate");
Recorder recorder = CinnamonMetrics.get(getContext()).createRecorder("recorder");

Adding tags to custom metrics

Tags can be added to custom metrics by passing a map of tags to the metric create-method. For example:

Scala
val taggedRecorder: Recorder = CinnamonMetrics(context).createRecorder("recorder", tags = Map("key" -> "value"))
Java
Map<String, String> tags = ImmutableMap.of("key", "value");
Recorder taggedRecorder = CinnamonMetrics.get(getContext()).createRecorder("recorder", tags);

ProvidingGaugeLong and ProvidingGaugeDouble value providers

In the examples above you will note that the ProvidingGaugeLong takes an additional parameter, provider. The ProvidingGaugeLong is a special custom metric that takes an external provider of a Long value and reports that value when asked by the underlying metrics backend.

Scala
val longValueProvider: LongValueProvider = new LongValueProvider {
  val cnt: AtomicLong = new AtomicLong(0)
  override def currentValue(): Long = cnt.getAndIncrement()
}
Java
LongValueProvider longValueProvider = new LongValueProvider() {
    AtomicLong cnt = new AtomicLong(0);

    @Override
    public long currentValue() {
        return cnt.getAndIncrement();
    }
};

There is also a ProvidingGaugeDouble that can be used:

Scala
val doubleValueProvider: DoubleValueProvider = new DoubleValueProvider {
  val cnt: AtomicReference[Double] = new AtomicReference(0.0)
  override def currentValue(): Double = cnt.get()
}
Java
DoubleValueProvider doubleValueProvider = new DoubleValueProvider() {
    AtomicReference<Double> cnt = new AtomicReference(0.0);

    @Override
    public double currentValue() {
        return cnt.get();
    }
};

Using custom metrics

Using the custom metrics involves calling one of the methods supplied by the metric interface as follows:

Scala
counter.increment()

counter.decrement()

gaugeDouble.set(4.2)

gaugeLong.set(12L)

rate.mark()

recorder.record(42L)
Java
counter.increment();

counter.decrement();

gaugeDouble.set(4.2);

gaugeLong.set(12L);

rate.mark();

recorder.record(42L);

Destroying custom metrics

When finished with the metric(s), you should call destroy. Calling destroy will, in turn, invoke the underlying metric backend to run any cleanup tasks if required.

Scala
counter.destroy()

gaugeDouble.destroy()

gaugeLong.destroy()

providingGaugeLong.destroy()

providingGaugeDouble.destroy()

rate.destroy()

recorder.destroy()
Java
counter.destroy();

gaugeDouble.destroy();

gaugeLong.destroy();

providingGaugeLong.destroy();

providingGaugeDouble.destroy();

rate.destroy();

recorder.destroy();

Custom metric timer utility

In addition to the custom metric types, the developer API also provides a timer utility which you can use to time a block of code in nanoseconds. The elapsed time for the code block to run is captured to an underlying Recorder with the name used in the createTimer(...) method.

Scala
val timer: Timer = CinnamonMetrics(context).createTimer("timer")

timer.time { 1 to 10000 sum }
Java
Timer timer = CinnamonMetrics.get(getContext()).createTimer("timer");

timer.time(() -> IntStream.rangeClosed(1, 100000).sum());

For a timer that can be used over asynchronous flows, such as across actors, see the Stopwatch extension.

Custom metric example

Here is an example of actor-level custom metrics by using a Counter to keep track of particular messages that an actor might receive.

import com.lightbend.cinnamon.akka.CinnamonMetrics
import com.lightbend.cinnamon.metric._

case object Init
case object In
case object Out
case object Done

class CustomerCountActor extends Actor {
  val counter: Counter = CinnamonMetrics(context).createCounter("customerCounter")

  def receive: PartialFunction[Any, Unit] = {
    case Init =>
      sender ! Init
    case In =>
      counter.increment()
      sender ! In
    case Out =>
      counter.decrement()
      sender ! Out
    case Done =>
      counter.destroy()
      sender ! Done
      context stop self
  }
}