/* =================================================== * Copyright © 2013 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.newrelic import akka.actor._ import scala.collection.mutable import kamon.Kamon import kamon.trace.{ UowTrace, Trace } import kamon.newrelic.NewRelicMetric.{ MetricBatch, FlushMetrics } import scala.concurrent.duration._ class NewRelic extends ExtensionId[NewRelicExtension] { def createExtension(system: ExtendedActorSystem): NewRelicExtension = new NewRelicExtension(system) } class NewRelicExtension(system: ExtendedActorSystem) extends Kamon.Extension { val manager: ActorRef = system.actorOf(Props[NewRelicManager], "kamon-newrelic") } class NewRelicManager extends Actor with ActorLogging { log.info("Registering the Kamon(NewRelic) extension") Kamon(Trace)(context.system) ! Trace.Register val webTransactionMetrics = context.actorOf(Props[WebTransactionMetrics], "web-transaction-metrics") val agent = context.actorOf(Props[Agent], "agent") import context.dispatcher context.system.scheduler.schedule(1 minute, 1 minute) { webTransactionMetrics.tell(FlushMetrics, agent) } def receive = { case trace: UowTrace ⇒ webTransactionMetrics ! trace } } object NewRelicMetric { case class ID(name: String, scope: Option[String]) case class Data(var callCount: Long, var total: Double, var totalExclusive: Double, var min: Double, var max: Double, var sumOfSquares: Double) { def record(value: Double): Unit = { if (value > max) max = value if (value < min) min = value total += value totalExclusive += value sumOfSquares += value * value callCount += 1 } } object Data { def apply(): Data = Data(0, 0, 0, Double.MaxValue, 0, 0) } case object FlushMetrics case class MetricBatch(metrics: List[(ID, Data)]) } class WebTransactionMetrics extends Actor with ActorLogging { val apdexT = 0.5D var metrics = mutable.Map.empty[NewRelicMetric.ID, NewRelicMetric.Data] var apdex = NewRelicMetric.Data(0, 0, 0, apdexT, apdexT, 0) def receive = { case trace: UowTrace ⇒ updateStats(trace) case FlushMetrics ⇒ flush } def flush: Unit = { sender ! MetricBatch(metrics.toList :+ (NewRelicMetric.ID("Apdex", None), apdex)) apdex = NewRelicMetric.Data(0, 0, 0, apdexT, apdexT, 0) metrics = mutable.Map.empty[NewRelicMetric.ID, NewRelicMetric.Data] } def recordValue(metricID: NewRelicMetric.ID, value: Double): Unit = { metrics.getOrElseUpdate(metricID, NewRelicMetric.Data()).record(value) } def recordApdex(time: Double): Unit = { if (time <= apdexT) apdex.callCount += 1 else if (time > apdexT && time <= (4 * apdexT)) apdex.total += 1 else apdex.totalExclusive += 1 } def updateStats(trace: UowTrace): Unit = { // Basic Metrics val elapsedSeconds = trace.elapsed / 1E9D recordApdex(elapsedSeconds) recordValue(NewRelicMetric.ID("WebTransaction", None), elapsedSeconds) recordValue(NewRelicMetric.ID("HttpDispatcher", None), elapsedSeconds) recordValue(NewRelicMetric.ID("WebTransaction/Custom/" + trace.name, None), elapsedSeconds) /*println("Recorded Apdex: " + apdex) println("Current Metrics: \n" + metrics.mkString("\n"))*/ } }