All Downloads are FREE. Search and download functionalities are using the official Maven repository.

kamon.metric.Metric.scala Maven / Gradle / Ivy

There is a newer version: 2.7.5
Show newest version
/*
 * Copyright 2013-2021 The Kamon Project 
 *
 * 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 kamon.metric

import java.time.Duration
import java.util
import java.util.Collections
import java.util.concurrent.{ScheduledExecutorService, ScheduledFuture, TimeUnit}

import kamon.status.Status
import kamon.tag.TagSet

import scala.collection.JavaConverters.asScalaBufferConverter
import scala.collection.concurrent.TrieMap
import scala.collection.mutable


/**
  * Describes a property of a system to be measured, and contains all the necessary information to create the actual
  * instrument instances used to measure and record said property. Practically, a Metric can be seen as a group
  * instruments that measure different dimensions (via tags) of the same property of the system.
  *
  * All instruments belonging to a given metric share the same settings and only differ from each other by the unique
  * combination of tags used to create them.
  *
  */
trait Metric[Inst <: Instrument[Inst, Sett], Sett <: Metric.Settings] extends Tagging[Inst] {

  /**
    * A unique identifier for this metric. Metric names typically will be namespaced, meaning that their name has a
    * structure similar to that of a package name that describes what component is generating the metric. For example,
    * metrics related to the JVM have the "jvm." prefix while metrics related to Akka Actors have the "akka.actor."
    * prefix.
    */
  def name: String


  /**
    * Short, concise and human readable explanation of what is being measured by a metric.
    */
  def description: String


  /**
    * Configuration settings that apply to all instruments of this metric.
    */
  def settings: Sett


  /**
    * Returns an instrument without tags for this metric.
    */
  def withoutTags(): Inst


  /**
    * Removes an instrument with the provided tags from a metric, if it exists. Returns true if the instrument existed
    * and was removed or false if no instrument was found with the provided tags.
    */
  def remove(tags: TagSet): Boolean
}



object Metric {

  /**
    * User-facing API for a Counter-based metric. All Kamon APIs returning a Counter-based metric to users should always
    * return this interface rather than internal representations.
    */
  trait Counter extends Metric[kamon.metric.Counter, Settings.ForValueInstrument]

  /**
    * User-facing API for a Gauge-based metric. All Kamon APIs returning a Gauge-based metric to users should always
    * return this interface rather than internal representations.
    */
  trait Gauge extends Metric[kamon.metric.Gauge, Settings.ForValueInstrument]

  /**
    * User-facing API for a Histogram-based metric. All Kamon APIs returning a Histogram-based metric to users should
    * always return this interface rather than internal representations.
    */
  trait Histogram extends Metric[kamon.metric.Histogram, Settings.ForDistributionInstrument]

  /**
    * User-facing API for a Timer-based metric. All Kamon APIs returning a Timer-based metric to users should always
    * return this interface rather than internal representations.
    */
  trait Timer extends Metric[kamon.metric.Timer, Settings.ForDistributionInstrument]

  /**
    * User-facing API for a Range Sampler-based metric. All Kamon APIs returning a Range Sampler-based metric to users
    * should always return this interface rather than internal representations.
    */
  trait RangeSampler extends Metric[kamon.metric.RangeSampler, Settings.ForDistributionInstrument]


  /**
    * Describes the minimum settings that should be provided to all metrics.
    */
  sealed trait Settings {

    /**
      * Measurement unit of the values tracked by a metric.
      */
    def unit: MeasurementUnit


    /**
      * Interval at which auto-update actions will be scheduled.
      */
    def autoUpdateInterval: Duration
  }

  object Settings {

    /**
      * Settings that apply to all metrics backed by instruments that produce a single value (e.g. counters and gauges).
      */
    case class ForValueInstrument (
      unit: MeasurementUnit,
      autoUpdateInterval: Duration
    ) extends Metric.Settings


    /**
      * Settings that apply to all metrics backed by instruments that produce value distributions (e.g. timers, range
      * samplers and, of course, histograms).
      */
    case class ForDistributionInstrument (
      unit: MeasurementUnit,
      autoUpdateInterval: Duration,
      dynamicRange: kamon.metric.DynamicRange
    ) extends Metric.Settings
  }


  /**
    * Exposes the required API to create metric snapshots. This API is not meant to be exposed to users.
    */
  private[kamon] trait Snapshotting[Sett <: Metric.Settings, Snap] {

    /**
      * Creates a snapshot for a metric. If the resetState flag is set to true, the internal state of all instruments
      * associated with this metric will be reset, if applicable.
      */
    def snapshot(resetState: Boolean): MetricSnapshot[Sett, Snap]
  }


  /**
    * Provides basic creation, lifecycle, tagging, scheduling and snapshotting operations for Kamon metrics. This base
    * metric keeps track of all instruments created for a given metric and ensures that every time an instrument is
    * requested from it, the instrument will be either created or retrieved if it was requested before, in a thread safe
    * manner.
    *
    * Any actions scheduled on an instrument will be cancelled if that instrument is removed from the metric.
    */

  type RichInstrument[Inst <: Instrument[Inst, Sett], Sett <: Metric.Settings, Snap] = Inst
    with Instrument.Snapshotting[Snap]
    with BaseMetricAutoUpdate[Inst, Sett, Snap]

  type InstrumentBuilder[Inst <: Instrument[Inst, Sett], Sett <: Metric.Settings, Snap] =
    (BaseMetric[Inst, Sett, Snap], TagSet) => RichInstrument[Inst, Sett, Snap]


  abstract class BaseMetric[Inst <: Instrument[Inst, Sett], Sett <: Metric.Settings, Snap] (
      val name: String,
      val description: String,
      val settings: Sett,
      instrumentBuilder: InstrumentBuilder[Inst, Sett, Snap],
      scheduler: Option[ScheduledExecutorService])
    extends Metric[Inst, Sett] with Metric.Snapshotting[Sett, Snap] {

    @volatile private var _scheduler: Option[ScheduledExecutorService] = scheduler
    private val _instruments = TrieMap.empty[TagSet, InstrumentEntry]

    override def withTag(key: String, value: String): Inst =
      lookupInstrument(TagSet.of(key, value))

    override def withTag(key: String, value: Boolean): Inst =
      lookupInstrument(TagSet.of(key, value))

    override def withTag(key: String, value: Long): Inst =
      lookupInstrument(TagSet.of(key, value))

    override def withTags(tags: TagSet): Inst =
      lookupInstrument(tags)

    override def withoutTags(): Inst =
      lookupInstrument(TagSet.Empty)

    override def remove(tags: TagSet): Boolean = synchronized {
      _instruments.get(tags).map(entry => {
        entry.removeOnNextSnapshot = true
        entry.scheduledActions.dropWhile(sa => {
          sa.cancel(false)
          true
        })
      }).nonEmpty
    }

    override def snapshot(resetState: Boolean): MetricSnapshot[Sett, Snap] = synchronized {
      var instrumentSnapshots = List.empty[Instrument.Snapshot[Snap]]
      _instruments.foreach {
        case (tags, entry) =>
          val instrumentSnapshot = entry.instrument.snapshot(resetState)
          if(entry.removeOnNextSnapshot && resetState)
            _instruments.remove(tags)

          instrumentSnapshots = Instrument.Snapshot(tags, instrumentSnapshot) :: instrumentSnapshots
      }

      buildMetricSnapshot(this, instrumentSnapshots)
    }

    def bindScheduler(scheduler: ScheduledExecutorService): Unit = synchronized {
      _scheduler = Some(scheduler)
      _instruments.foreach {
        case (_, entry) =>
          entry.actions.foreach(action => {
            val (runnable, interval) = action
            entry.scheduledActions += scheduler.scheduleAtFixedRate(runnable, interval.toNanos, interval.toNanos, TimeUnit.NANOSECONDS)
          })
      }
    }

    def shutdown(): Unit = synchronized {
      _scheduler = None
      _instruments.foreach {
        case (_, entry) =>
          entry.scheduledActions.dropWhile(sa => {
            sa.cancel(false)
            true
          })
      }

      _instruments.clear()
    }

    def schedule(instrument: Inst, action: Runnable, interval: Duration): Any = synchronized {
      _instruments.get(instrument.tags).map { entry =>
        _scheduler match {
          case Some(scheduler) =>
            val scheduledAction = scheduler.scheduleAtFixedRate(action, interval.toNanos, interval.toNanos, TimeUnit.NANOSECONDS)
            entry.scheduledActions += (scheduledAction)
            entry.actions += ((action, interval))

          case None =>
            entry.actions += ((action, interval))
        }
      }
    }

    def status(): Status.Metric =
      Status.Metric (
        name,
        description,
        settings.unit,
        instrumentType,
        _instruments.keys.map(t => Status.Instrument(t)).toSeq
      )

    /** Used by the Status API only */
    protected def instrumentType: Instrument.Type

    protected def buildMetricSnapshot(metric: Metric[Inst, Sett], instruments: Seq[Instrument.Snapshot[Snap]]): MetricSnapshot[Sett, Snap]

    private def lookupInstrument(tags: TagSet): Inst = {
      val entry = _instruments.atomicGetOrElseUpdate(tags, newInstrumentEntry(tags), cleanupStaleEntry, triggerDefaultSchedule)
      entry.removeOnNextSnapshot = false
      entry.instrument
    }

    private def newInstrumentEntry(tags: TagSet): InstrumentEntry = {
      val actions = Collections.synchronizedList(new util.ArrayList[(Runnable, Duration)]()).asScala
      val scheduledActions = Collections.synchronizedList(new util.ArrayList[ScheduledFuture[_]]()).asScala
      val instrument = instrumentBuilder(this, tags)

      new InstrumentEntry(instrument, actions, scheduledActions, false)
    }

    private def triggerDefaultSchedule(entry: InstrumentEntry): Unit =
      entry.instrument.defaultSchedule()

    private def cleanupStaleEntry(entry: InstrumentEntry): Unit =
      entry.scheduledActions.foreach(sa => sa.cancel(false))

    private class InstrumentEntry (
      val instrument: RichInstrument[Inst, Sett, Snap],
      val actions: mutable.Buffer[(Runnable, Duration)],
      val scheduledActions: mutable.Buffer[ScheduledFuture[_]],
      @volatile var removeOnNextSnapshot: Boolean
    )
  }

  /**
    * Handles registration of auto-update actions on a base metric.
    */
  trait BaseMetricAutoUpdate[Inst <: Instrument[Inst, Sett], Sett <: Metric.Settings, Snap] {
      self: Inst =>

    protected def baseMetric: BaseMetric[Inst, Sett, Snap]

    def defaultSchedule(): Unit = ()

    def autoUpdate(consumer: Inst => Unit, interval: Duration): Inst = {
      val instrument = this
      val action = new Runnable {
        override def run(): Unit = consumer(instrument)
      }

      baseMetric.schedule(instrument, action, interval)
      this
    }
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy