aboutsummaryrefslogblamecommitdiff
path: root/kamon-log-reporter/src/main/scala/kamon/logreporter/LogReporter.scala
blob: 4fde7f92f7f18a208118e4f7bab2873ec33e7830 (plain) (tree)


















                                                                                             
                         



                                                     
                                 














                                                                                                                    


                                                          


                                                                                     





                                                                                           









                                                               

















                                                                                                                    
























































                                                                                                                


                                                                                                                       









                                                                                                             

                                                                                                    








                                                                                                              
                        











                                                                                                                                          
                            












                                                                                                                                          
                    













                                                                                                                                          
                                                                                    
                                                                           
                                          













































                                                                                                            
/*
 * =========================================================================================
 * Copyright © 2013-2014 the kamon project <http://kamon.io/>
 *
 * 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.logreporter

import akka.actor._
import akka.event.Logging
import kamon.Kamon
import kamon.metric.ActorMetrics.ActorMetricSnapshot
import kamon.metric.Subscriptions.TickMetricSnapshot
import kamon.metric.TraceMetrics.TraceMetricsSnapshot
import kamon.metric.UserMetrics._
import kamon.metric.instrument.{ Counter, Histogram }
import kamon.metric._

object LogReporter extends ExtensionId[LogReporterExtension] with ExtensionIdProvider {
  override def lookup(): ExtensionId[_ <: Extension] = LogReporter
  override def createExtension(system: ExtendedActorSystem): LogReporterExtension = new LogReporterExtension(system)

  trait MetricKeyGenerator {
    def localhostName: String
    def normalizedLocalhostName: String
    def generateKey(groupIdentity: MetricGroupIdentity, metricIdentity: MetricIdentity): String
  }
}

class LogReporterExtension(system: ExtendedActorSystem) extends Kamon.Extension {
  val log = Logging(system, classOf[LogReporterExtension])
  log.info("Starting the Kamon(LogReporter) extension")

  val subscriber = system.actorOf(Props[LogReporterSubscriber], "kamon-log-reporter")
  Kamon(Metrics)(system).subscribe(TraceMetrics, "*", subscriber, permanently = true)
  Kamon(Metrics)(system).subscribe(ActorMetrics, "*", subscriber, permanently = true)

  // Subscribe to all user metrics
  Kamon(Metrics)(system).subscribe(UserHistograms, "*", subscriber, permanently = true)
  Kamon(Metrics)(system).subscribe(UserCounters, "*", subscriber, permanently = true)
  Kamon(Metrics)(system).subscribe(UserMinMaxCounters, "*", subscriber, permanently = true)
  Kamon(Metrics)(system).subscribe(UserGauges, "*", subscriber, permanently = true)

}

class LogReporterSubscriber extends Actor with ActorLogging {
  import LogReporterSubscriber.RichHistogramSnapshot

  def receive = {
    case tick: TickMetricSnapshot  printMetricSnapshot(tick)
  }

  def printMetricSnapshot(tick: TickMetricSnapshot): Unit = {
    // Group all the user metrics together.
    val histograms = Map.newBuilder[MetricGroupIdentity, Histogram.Snapshot]
    val counters = Map.newBuilder[MetricGroupIdentity, Counter.Snapshot]
    val minMaxCounters = Map.newBuilder[MetricGroupIdentity, Histogram.Snapshot]
    val gauges = Map.newBuilder[MetricGroupIdentity, Histogram.Snapshot]

    tick.metrics foreach {
      case (identity, ams: ActorMetricSnapshot)                  logActorMetrics(identity.name, ams)
      case (identity, tms: TraceMetricsSnapshot)                 logTraceMetrics(identity.name, tms)
      case (h: UserHistogram, s: UserHistogramSnapshot)          histograms += (h -> s.histogramSnapshot)
      case (c: UserCounter, s: UserCounterSnapshot)              counters += (c -> s.counterSnapshot)
      case (m: UserMinMaxCounter, s: UserMinMaxCounterSnapshot)  minMaxCounters += (m -> s.minMaxCounterSnapshot)
      case (g: UserGauge, s: UserGaugeSnapshot)                  gauges += (g -> s.gaugeSnapshot)
      case ignoreEverythingElse                                 
    }

    logUserMetrics(histograms.result(), counters.result(), minMaxCounters.result(), gauges.result())
  }

  def logActorMetrics(name: String, ams: ActorMetricSnapshot): Unit = {
    log.info(
      """
        |+--------------------------------------------------------------------------------------------------+
        ||                                                                                                  |
        ||    Actor: %-83s    |
        ||                                                                                                  |
        ||   Processing Time (nanoseconds)      Time in Mailbox (nanoseconds)         Mailbox Size          |
        ||    Msg Count: %-12s               Msg Count: %-12s             Min: %-8s       |
        ||          Min: %-12s                     Min: %-12s            Avg.: %-8s       |
        ||    50th Perc: %-12s               50th Perc: %-12s             Max: %-8s       |
        ||    90th Perc: %-12s               90th Perc: %-12s                                 |
        ||    95th Perc: %-12s               95th Perc: %-12s                                 |
        ||    99th Perc: %-12s               99th Perc: %-12s           Error Count: %-6s   |
        ||  99.9th Perc: %-12s             99.9th Perc: %-12s                                 |
        ||          Max: %-12s                     Max: %-12s                                 |
        ||                                                                                                  |
        |+--------------------------------------------------------------------------------------------------+"""
        .stripMargin.format(
          name,
          ams.processingTime.numberOfMeasurements, ams.timeInMailbox.numberOfMeasurements, ams.mailboxSize.min,
          ams.processingTime.min, ams.timeInMailbox.min, ams.mailboxSize.average,
          ams.processingTime.percentile(0.50F), ams.timeInMailbox.percentile(0.50F), ams.mailboxSize.max,
          ams.processingTime.percentile(0.90F), ams.timeInMailbox.percentile(0.90F),
          ams.processingTime.percentile(0.95F), ams.timeInMailbox.percentile(0.95F),
          ams.processingTime.percentile(0.99F), ams.timeInMailbox.percentile(0.99F), ams.errors.count,
          ams.processingTime.percentile(0.999F), ams.timeInMailbox.percentile(0.999F),
          ams.processingTime.max, ams.timeInMailbox.max))
  }

  def logTraceMetrics(name: String, tms: TraceMetricsSnapshot): Unit = {
    val traceMetricsData = StringBuilder.newBuilder

    traceMetricsData.append(
      """
        |+--------------------------------------------------------------------------------------------------+
        ||                                                                                                  |
        ||    Trace: %-83s    |
        ||    Count: %-8s                                                                               |
        ||                                                                                                  |
        ||  Elapsed Time (nanoseconds):                                                                     |
        |"""
        .stripMargin.format(
          name, tms.elapsedTime.numberOfMeasurements))

    traceMetricsData.append(compactHistogramView(tms.elapsedTime))
    traceMetricsData.append(
      """
        ||                                                                                                  |
        |+--------------------------------------------------------------------------------------------------+"""
        .stripMargin)

    log.info(traceMetricsData.toString())
  }

  def logUserMetrics(histograms: Map[MetricGroupIdentity, Histogram.Snapshot],
    counters: Map[MetricGroupIdentity, Counter.Snapshot], minMaxCounters: Map[MetricGroupIdentity, Histogram.Snapshot],
    gauges: Map[MetricGroupIdentity, Histogram.Snapshot]): Unit = {
    val userMetricsData = StringBuilder.newBuilder

    userMetricsData.append(
      """
        |+--------------------------------------------------------------------------------------------------+
        ||                                                                                                  |
        ||                                       User Counters                                              |
        ||                                       -------------                                              |
        |""".stripMargin)

    counters.toList.sortBy(_._1.name.toLowerCase).foreach {
      case (counter, snapshot)  userMetricsData.append(userCounterString(counter.name, snapshot))
    }

    userMetricsData.append(
      """||                                                                                                  |
        ||                                                                                                  |
        ||                                      User Histograms                                             |
        ||                                      ---------------                                             |
        |""".stripMargin)

    histograms.foreach {
      case (histogram, snapshot) 
        userMetricsData.append("|  %-40s                                                        |\n".format(histogram.name))
        userMetricsData.append(compactHistogramView(snapshot))
        userMetricsData.append("\n|                                                                                                  |\n")
    }

    userMetricsData.append(
      """||                                                                                                  |
        ||                                    User MinMaxCounters                                           |
        ||                                    -------------------                                           |
        |""".stripMargin)

    minMaxCounters.foreach {
      case (minMaxCounter, snapshot) 
        userMetricsData.append("|  %-40s                                                        |\n".format(minMaxCounter.name))
        userMetricsData.append(simpleHistogramView(snapshot))
        userMetricsData.append("\n|                                                                                                  |\n")
    }

    userMetricsData.append(
      """||                                                                                                  |
        ||                                        User Gauges                                               |
        ||                                        -----------                                               |
        |"""
        .stripMargin)

    gauges.foreach {
      case (gauge, snapshot) 
        userMetricsData.append("|  %-40s                                                        |\n".format(gauge.name))
        userMetricsData.append(simpleHistogramView(snapshot))
        userMetricsData.append("\n|                                                                                                  |\n")
    }

    userMetricsData.append(
      """||                                                                                                  |
        |+--------------------------------------------------------------------------------------------------+"""
        .stripMargin)

    log.info(userMetricsData.toString())
  }

  def userCounterString(counterName: String, snapshot: Counter.Snapshot): String = {
    "|             %30s  =>  %-12s                                     |\n"
      .format(counterName, snapshot.count)
  }

  def compactHistogramView(histogram: Histogram.Snapshot): String = {
    val sb = StringBuilder.newBuilder

    sb.append("|    Min: %-11s  50th Perc: %-12s   90th Perc: %-12s   95th Perc: %-12s |\n".format(
      histogram.min, histogram.percentile(0.50F), histogram.percentile(0.90F), histogram.percentile(0.95F)))
    sb.append("|                      99th Perc: %-12s 99.9th Perc: %-12s         Max: %-12s |".format(
      histogram.percentile(0.99F), histogram.percentile(0.999F), histogram.max))

    sb.toString()
  }

  def simpleHistogramView(histogram: Histogram.Snapshot): String =
    "|          Min: %-12s           Average: %-12s                Max: %-12s      |"
      .format(histogram.min, histogram.average, histogram.max)
}

object LogReporterSubscriber {

  implicit class RichHistogramSnapshot(histogram: Histogram.Snapshot) {
    def percentile(q: Float): Long = {
      val records = histogram.recordsIterator
      val qThreshold = histogram.numberOfMeasurements * q
      var countToCurrentLevel = 0L
      var qLevel = 0L

      while (countToCurrentLevel < qThreshold && records.hasNext) {
        val record = records.next()
        countToCurrentLevel += record.count
        qLevel = record.level
      }

      qLevel
    }

    def average: Double = {
      var acc = 0L
      for (record  histogram.recordsIterator) {
        acc += record.count * record.level
      }

      return acc / histogram.numberOfMeasurements
    }
  }
}