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

kamon.jmx.JMXReporterActor.scala Maven / Gradle / Ivy

/*
 * =========================================================================================
 * Copyright © 2013-2015 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.jmx

import java.lang.management.ManagementFactory
import javax.management._

import akka.actor.{ Actor, Props }
import kamon.metric.SubscriptionsDispatcher.TickMetricSnapshot
import kamon.metric.instrument.{ Counter, Histogram, InstrumentSnapshot }
import kamon.util.logger.LazyLogger

import scala.collection.concurrent.TrieMap

private object MetricMBeans {

  private implicit class RichHistogramSnapshot(histogram: Histogram.Snapshot) {
    def average: Double = {
      if (histogram.numberOfMeasurements == 0) 0D
      else histogram.sum / histogram.numberOfMeasurements
    }
  }

  private[jmx] sealed trait MetricMBean

  private[jmx] abstract class AbstractMetricMBean[T <: InstrumentSnapshot] extends MetricMBean {
    private[jmx] var snapshot: T

    private[jmx] def objectName: ObjectName
  }

  private[jmx] trait HistogramMetricMBean extends AbstractMetricMBean[Histogram.Snapshot] {
    def getNumberOfMeasurements: Long

    def getMin: Long

    def get50thPercentile: Long

    def getPercentile(n: Double): Long

    def get70thPercentile: Long

    def get90thPercentile: Long

    def get95thPercentile: Long

    def get990thPercentile: Long

    def get999thPercentile: Long

    def getAvg: Double

    def getMax: Long

    def getSum: Long
  }

  private[jmx] class HistogramMetric(@volatile private[jmx] override var snapshot: Histogram.Snapshot, private[jmx] override val objectName: ObjectName) extends HistogramMetricMBean {
    override def getNumberOfMeasurements: Long = snapshot.numberOfMeasurements

    override def getMin: Long = snapshot.min

    override def get50thPercentile: Long = snapshot.percentile(50.0D)

    override def get70thPercentile: Long = snapshot.percentile(70.0D)

    override def get90thPercentile: Long = snapshot.percentile(90.0D)

    override def get95thPercentile: Long = snapshot.percentile(95.0D)

    override def get990thPercentile: Long = snapshot.percentile(99.0D)

    override def get999thPercentile: Long = snapshot.percentile(99.9D)

    override def getPercentile(n: Double): Long = snapshot.percentile(n)

    override def getMax: Long = snapshot.max

    override def getSum: Long = snapshot.sum

    override def getAvg: Double = snapshot.average
  }

  private[jmx] trait CounterMetricMBean extends AbstractMetricMBean[Counter.Snapshot] {
    def getCount: Long
  }

  private[jmx] trait HistogramValueMetricMBean extends AbstractMetricMBean[Histogram.Snapshot] {
    def getValue: Long
  }

  private[jmx] class CounterMetric(@volatile private[jmx] override var snapshot: Counter.Snapshot, private[jmx] override val objectName: ObjectName) extends CounterMetricMBean {
    override def getCount: Long = snapshot.count
  }

  private[jmx] class HistogramValueMetric(@volatile private[jmx] override var snapshot: Histogram.Snapshot, private[jmx] override val objectName: ObjectName) extends HistogramValueMetricMBean {
    override def getValue: Long = snapshot.max
  }

  private[jmx] implicit def impCreateHistogramMetric(snapshot: Histogram.Snapshot, objectName: ObjectName) = new HistogramMetric(snapshot, objectName)

  private[jmx] implicit def impCounterMetric(snapshot: Counter.Snapshot, objectName: ObjectName) = new CounterMetric(snapshot, objectName)

}

private object MBeanManager {

  import MetricMBeans._

  private val mbs = ManagementFactory.getPlatformMBeanServer

  private val registeredMBeans = TrieMap.empty[String, AbstractMetricMBean[_]]

  private val log = LazyLogger(getClass)

  private[jmx] def createOrUpdateMBean[M <: AbstractMetricMBean[T], T <: InstrumentSnapshot](group: String, name: String, snapshot: T)(implicit buildMetricMBean: (T, ObjectName) ⇒ M): Unit = {
    registeredMBeans.get(name) match {
      case Some(mbean: M) ⇒
        mbean.snapshot = snapshot
      case None ⇒
        val objectName = new ObjectName(createMBeanName("kamon", group, name))
        val mbean = buildMetricMBean(snapshot, objectName)
        registeredMBeans += name -> mbean
        mbs.registerMBean(mbean, objectName)
      case _ ⇒ throw new IllegalStateException("Illegal metric bean type")
    }
  }

  private[jmx] def unregisterAllBeans(): Unit = {
    registeredMBeans.values.map(_.objectName).foreach(name ⇒
      try {
        mbs.unregisterMBean(name)
      } catch {
        case e: InstanceNotFoundException  ⇒ if (log.isTraceEnabled) log.trace(s"Error unregistering $name", e)
        case e: MBeanRegistrationException ⇒ if (log.isDebugEnabled) log.debug(s"Error unregistering $name", e)
      })
    registeredMBeans.clear()
  }

  private def createMBeanName(group: String, `type`: String, name: String, scope: Option[String] = None): String = {
    val nameBuilder: StringBuilder = new StringBuilder
    nameBuilder.append(group)
    nameBuilder.append(":type=")
    nameBuilder.append(`type`)
    if (scope.isDefined) {
      nameBuilder.append(",scope=")
      nameBuilder.append(scope.get)
    }
    if (name.length > 0) {
      nameBuilder.append(",name=")
      nameBuilder.append(name)
    }
    nameBuilder.toString
  }
}

private object JMXReporterActor {

  import MBeanManager._
  import MetricMBeans._

  private[jmx] def props = Props(classOf[JMXReporterActor])

  private def updateHystogramMetrics(group: String, name: String, hs: Histogram.Snapshot): Unit = {
    createOrUpdateMBean[HistogramMetric, Histogram.Snapshot](group, name, hs)
  }

  private def updateCounterMetrics(group: String, name: String, cs: Counter.Snapshot): Unit = {
    createOrUpdateMBean[CounterMetric, Counter.Snapshot](group, name, cs)
  }
}

private class JMXReporterActor extends Actor {

  import JMXReporterActor._
  import MBeanManager.unregisterAllBeans

  def receive: Receive = {
    case tick: TickMetricSnapshot ⇒
      for {
        (entity, snapshot) ← tick.metrics
        (metricKey, metricSnapshot) ← snapshot.metrics
      } {
        metricSnapshot match {
          case hs: Histogram.Snapshot ⇒
            updateHystogramMetrics(entity.category, entity.name + "." + metricKey.name, hs)
          case cs: Counter.Snapshot ⇒
            updateCounterMetrics(entity.category, entity.name + "." + metricKey.name, cs)
        }
      }
  }

  override def postStop(): Unit = {
    super.postStop()
    unregisterAllBeans()
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy