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

kamon.metric.Histogram.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.nio.ByteBuffer

import kamon.metric.Metric.{BaseMetric, BaseMetricAutoUpdate}
import kamon.tag.TagSet
import org.HdrHistogram.{BaseAtomicHdrHistogram, BaseLocalHdrHistogram, HdrHistogramInternalState, ZigZag}
import org.slf4j.LoggerFactory


/**
  * Instrument that tracks the distribution of values within a configured range and precision.
  */
trait Histogram extends Instrument[Histogram, Metric.Settings.ForDistributionInstrument] {

  /**
    * Records one occurrence of the provided value. Keep in mind that the provided value will not be recorded as-is on
    * the Histogram but will be rather adjusted to a bucket within the configured precision. By default, all Kamon
    * histograms are configured to achieve up to 1% error margin across the entire range.
    */
  def record(value: Long): Histogram

  /**
    * Records several occurrences of the provided value. Keep in mind that the provided value will not be recorded as-is
    * on the Histogram but will be rather adjusted to a bucket within the configured precision. By default, all Kamon
    * histograms are configured to achieve up to 1% error margin across the entire range.
    */
  def record(value: Long, times: Long): Histogram

}


object Histogram {

  private val _logger = LoggerFactory.getLogger(classOf[Histogram])

  /**
    * Histogram implementation with thread safety guarantees. Instances of this class can be safely shared across
    * threads and updated concurrently.
    */
  class Atomic(val metric: BaseMetric[Histogram, Metric.Settings.ForDistributionInstrument, Distribution],
      val tags: TagSet, val dynamicRange: DynamicRange) extends BaseAtomicHdrHistogram(dynamicRange) with Histogram
      with DistributionSnapshotBuilder
      with BaseMetricAutoUpdate[Histogram, Metric.Settings.ForDistributionInstrument, Distribution] {

    override def record(value: Long): Histogram = {
      try {
        recordValue(value)
      } catch {
        case e: ArrayIndexOutOfBoundsException =>
          val highestTrackableValue = getHighestTrackableValue()
          recordValue(highestTrackableValue)

          _logger.warn (
            s"Failed to record value [$value] on [${metric.name},${tags}] because the value is outside of the " +
            s"configured range. The recorded value was adjusted to the highest trackable value [$highestTrackableValue]. " +
            "You might need to change your dynamic range configuration for this metric", e
          )
      }

      this
    }

    override def record(value: Long, times: Long): Histogram = {
      try {
        recordValueWithCount(value, times)
      } catch {
        case e: ArrayIndexOutOfBoundsException =>
          val highestTrackableValue = getHighestTrackableValue()
          recordValueWithCount(highestTrackableValue, times)

          _logger.warn (
            s"Failed to record value [$value] on [${metric.name},${tags}] because the value is outside of the " +
            s"configured range. The recorded value was adjusted to the highest trackable value [$highestTrackableValue]. " +
            "You might need to change your dynamic range configuration for this metric", e
          )
      }

      this
    }

    override protected def baseMetric: BaseMetric[Histogram, Metric.Settings.ForDistributionInstrument, Distribution] =
      metric
  }

  /**
    * Creates a distribution snapshot directly from a counts array while it is being actively used.
    */
  private[kamon] trait DistributionSnapshotBuilder extends Instrument.Snapshotting[Distribution] {
    self: HdrHistogramInternalState =>

    def dynamicRange: DynamicRange

    def snapshot(resetState: Boolean): Distribution = {
      val buffer = DistributionSnapshotBuilder._tempSnapshotBuffer.get()
      val countsLimit = getCountsArraySize()
      var index = 0
      buffer.clear()

      var minIndex = Int.MaxValue
      var maxIndex = 0
      var totalCount = 0L

      while(index < countsLimit) {
        val countAtIndex = if(resetState) getAndSetFromCountsArray(index, 0L) else getFromCountsArray(index)

        var zerosCount = 0L
        if(countAtIndex == 0L) {
          index += 1
          zerosCount = 1
          while(index < countsLimit && getFromCountsArray(index) == 0L) {
            index += 1
            zerosCount += 1
          }
        }

        if(zerosCount > 0) {
          if(index < countsLimit)
            ZigZag.putLong(buffer, -zerosCount)
        }
        else {
          if(minIndex > index)
            minIndex = index
          maxIndex = index

          index += 1
          totalCount += countAtIndex
          ZigZag.putLong(buffer, countAtIndex)
        }
      }

      buffer.flip()
      val zigZagCounts = Array.ofDim[Byte](buffer.limit())
      buffer.get(zigZagCounts)

      val zigZagCountsBuffer = ByteBuffer.wrap(zigZagCounts).asReadOnlyBuffer()
      val distribution = new Distribution.ZigZagCounts(totalCount, minIndex, maxIndex, zigZagCountsBuffer,
        getUnitMagnitude(), getSubBucketHalfCount(), getSubBucketHalfCountMagnitude(), dynamicRange)

      distribution
    }
  }

  private object DistributionSnapshotBuilder {
    private val _tempSnapshotBuffer = new ThreadLocal[ByteBuffer] {
      override def initialValue(): ByteBuffer = ByteBuffer.allocate(33792)
    }
  }

  /**
    * Histogram implementation that can only be used local to a thread or with external synchronization. This is only
    * used to power aggregation of distributions given that it is a lot simpler to create histograms and dump all the
    * values on them rather than trying to manually aggregate the buckets and take into account adjustments on dynamic
    * range.
    */
  private[kamon] class Local(val dynamicRange: DynamicRange) extends BaseLocalHdrHistogram(dynamicRange)
    with Instrument.Snapshotting[Distribution] with DistributionSnapshotBuilder

  private[kamon] object Local {

    /** Keeps a thread-local cache of local histograms that can be reused when aggregating distributions. */
    private val _localHistograms = new ThreadLocal[collection.mutable.Map[DynamicRange, Histogram.Local]] {
      override def initialValue(): collection.mutable.Map[DynamicRange, Histogram.Local] =
        collection.mutable.Map.empty
    }

    /**
      * Creates or retrieves a local histogram for the provided dynamic range. In theory, it is safe to do this from the
      * memory usage perspective since distribution merging or converting (the use cases for a Local Histogram) are only
      * expected to happen on reporter threads and all reporters have their own dedicated thread so we wont have many
      * instances around.
      */
    def get(dynamicRange: DynamicRange): Histogram.Local = {
      val histogram = _localHistograms.get().getOrElseUpdate(dynamicRange, new Histogram.Local(dynamicRange))
      histogram.reset()
      histogram
    }
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy