diff options
99 files changed, 2954 insertions, 1097 deletions
diff --git a/.travis.yml b/.travis.yml index f49aa1a1..1285f35d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,7 +5,7 @@ script: scala: - 2.11.5 jdk: - - oraclejdk8 + - oraclejdk8 before_script: - mkdir $TRAVIS_BUILD_DIR/tmp - export SBT_OPTS="-Djava.io.tmpdir=$TRAVIS_BUILD_DIR/tmp" @@ -3,7 +3,7 @@ Kamon [![Build Status](https://api.travis-ci.org/kamon-io/Kamon.png)](https: Kamon is a set of tools that will help you monitor your reactive applications. - +[![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/kamon-io/Kamon?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) ![First Implementation of Kamon in Action!!!](kamon-dashboard/kamon-dashboard-screenshot.png) diff --git a/kamon-akka/src/main/resources/reference.conf b/kamon-akka/src/main/resources/reference.conf index cc2b6060..c1e59e63 100644 --- a/kamon-akka/src/main/resources/reference.conf +++ b/kamon-akka/src/main/resources/reference.conf @@ -15,10 +15,21 @@ kamon { } metric.filters { - actor.includes = [] - actor.excludes = [ "", "user", "system**", "user/IO-**" ] - } + akka-actor { + includes = [] + excludes = [ "*/system/**", "*/user/IO-**" ] + } + + akka-router { + includes = [] + excludes = [] + } + akka-dispatcher { + includes = [] + excludes = [] + } + } modules { kamon-akka { diff --git a/kamon-akka/src/main/scala/kamon/akka/DispatcherMetrics.scala b/kamon-akka/src/main/scala/kamon/akka/DispatcherMetrics.scala index acf92e70..23ecde79 100644 --- a/kamon-akka/src/main/scala/kamon/akka/DispatcherMetrics.scala +++ b/kamon-akka/src/main/scala/kamon/akka/DispatcherMetrics.scala @@ -26,21 +26,10 @@ class ForkJoinPoolDispatcherMetrics(fjp: AkkaForkJoinPool, instrumentFactory: In val paralellism = minMaxCounter("parallelism") paralellism.increment(fjp.getParallelism) // Steady value. - val poolSize = gauge("pool-size", () ⇒ { - fjp.getPoolSize.toLong - }) - - val activeThreads = gauge("active-threads", () ⇒ { - fjp.getActiveThreadCount.toLong - }) - - val runningThreads = gauge("running-threads", () ⇒ { - fjp.getRunningThreadCount.toLong - }) - - val queuedTaskCount = gauge("queued-task-count", () ⇒ { - fjp.getQueuedTaskCount - }) + val poolSize = gauge("pool-size", fjp.getPoolSize.toLong) + val activeThreads = gauge("active-threads", fjp.getActiveThreadCount.toLong) + val runningThreads = gauge("running-threads", fjp.getRunningThreadCount.toLong) + val queuedTaskCount = gauge("queued-task-count", fjp.getQueuedTaskCount) } object ForkJoinPoolDispatcherMetrics { @@ -52,29 +41,16 @@ object ForkJoinPoolDispatcherMetrics { } class ThreadPoolExecutorDispatcherMetrics(tpe: ThreadPoolExecutor, instrumentFactory: InstrumentFactory) extends GenericEntityRecorder(instrumentFactory) { - val corePoolSize = gauge("core-pool-size", () ⇒ { - tpe.getCorePoolSize.toLong - }) - - val maxPoolSize = gauge("max-pool-size", () ⇒ { - tpe.getMaximumPoolSize.toLong - }) - - val poolSize = gauge("pool-size", () ⇒ { - tpe.getPoolSize.toLong - }) - - val activeThreads = gauge("active-threads", () ⇒ { - tpe.getActiveCount.toLong - }) - + val corePoolSize = gauge("core-pool-size", tpe.getCorePoolSize.toLong) + val maxPoolSize = gauge("max-pool-size", tpe.getMaximumPoolSize.toLong) + val poolSize = gauge("pool-size", tpe.getPoolSize.toLong) + val activeThreads = gauge("active-threads", tpe.getActiveCount.toLong) val processedTasks = gauge("processed-tasks", DifferentialValueCollector(() ⇒ { tpe.getTaskCount })) } object ThreadPoolExecutorDispatcherMetrics { - def factory(tpe: ThreadPoolExecutor) = new EntityRecorderFactory[ThreadPoolExecutorDispatcherMetrics] { def category: String = AkkaDispatcherMetrics.Category def createRecorder(instrumentFactory: InstrumentFactory) = new ThreadPoolExecutorDispatcherMetrics(tpe, instrumentFactory) diff --git a/kamon-akka/src/main/scala/kamon/akka/instrumentation/ActorCellInstrumentation.scala b/kamon-akka/src/main/scala/kamon/akka/instrumentation/ActorCellInstrumentation.scala index 4783484f..deb43499 100644 --- a/kamon-akka/src/main/scala/kamon/akka/instrumentation/ActorCellInstrumentation.scala +++ b/kamon-akka/src/main/scala/kamon/akka/instrumentation/ActorCellInstrumentation.scala @@ -34,13 +34,18 @@ class ActorCellInstrumentation { @After("actorCellCreation(cell, system, ref, props, dispatcher, parent)") def afterCreation(cell: ActorCell, system: ActorSystem, ref: ActorRef, props: Props, dispatcher: MessageDispatcher, parent: ActorRef): Unit = { - Kamon.metrics.register(ActorMetrics, ref.path.elements.mkString("/")).map { registration ⇒ + def isRootSupervisor(path: String): Boolean = path.length == 0 || path == "user" || path == "system" + + val pathString = ref.path.elements.mkString("/") + val actorEntity = Entity(system.name + "/" + pathString, ActorMetrics.category) + + if (!isRootSupervisor(pathString) && Kamon.metrics.shouldTrack(actorEntity)) { + val actorMetricsRecorder = Kamon.metrics.entity(ActorMetrics, actorEntity) val cellMetrics = cell.asInstanceOf[ActorCellMetrics] - cellMetrics.entity = registration.entity - cellMetrics.recorder = Some(registration.recorder) + cellMetrics.entity = actorEntity + cellMetrics.recorder = Some(actorMetricsRecorder) } - } @Pointcut("execution(* akka.actor.ActorCell.invoke(*)) && this(cell) && args(envelope)") @@ -57,19 +62,19 @@ class ActorCellInstrumentation { pjp.proceed() } } finally { - cellMetrics.recorder.map { am ⇒ - val processingTime = System.nanoTime() - timestampBeforeProcessing - val timeInMailbox = timestampBeforeProcessing - contextAndTimestamp.captureNanoTime + val processingTime = System.nanoTime() - timestampBeforeProcessing + val timeInMailbox = timestampBeforeProcessing - contextAndTimestamp.captureNanoTime + cellMetrics.recorder.map { am ⇒ am.processingTime.record(processingTime) am.timeInMailbox.record(timeInMailbox) am.mailboxSize.decrement() + } - // In case that this actor is behind a router, record the metrics for the router. - envelope.asInstanceOf[RouterAwareEnvelope].routerMetricsRecorder.map { rm ⇒ - rm.processingTime.record(processingTime) - rm.timeInMailbox.record(timeInMailbox) - } + // In case that this actor is behind a router, record the metrics for the router. + envelope.asInstanceOf[RouterAwareEnvelope].routerMetricsRecorder.map { rm ⇒ + rm.processingTime.record(processingTime) + rm.timeInMailbox.record(timeInMailbox) } } } @@ -90,14 +95,14 @@ class ActorCellInstrumentation { def afterStop(cell: ActorCell): Unit = { val cellMetrics = cell.asInstanceOf[ActorCellMetrics] cellMetrics.recorder.map { _ ⇒ - Kamon.metrics.unregister(cellMetrics.entity) + Kamon.metrics.removeEntity(cellMetrics.entity) } // The Stop can't be captured from the RoutedActorCell so we need to put this piece of cleanup here. if (cell.isInstanceOf[RoutedActorCell]) { val routedCellMetrics = cell.asInstanceOf[RoutedActorCellMetrics] routedCellMetrics.routerRecorder.map { _ ⇒ - Kamon.metrics.unregister(routedCellMetrics.routerEntity) + Kamon.metrics.removeEntity(routedCellMetrics.routerEntity) } } } @@ -124,11 +129,13 @@ class RoutedActorCellInstrumentation { @After("routedActorCellCreation(cell, system, ref, props, dispatcher, routeeProps, supervisor)") def afterRoutedActorCellCreation(cell: RoutedActorCell, system: ActorSystem, ref: ActorRef, props: Props, dispatcher: MessageDispatcher, routeeProps: Props, supervisor: ActorRef): Unit = { - Kamon.metrics.register(RouterMetrics, ref.path.elements.mkString("/")).map { registration ⇒ + val routerEntity = Entity(system.name + "/" + ref.path.elements.mkString("/"), RouterMetrics.category) + + if (Kamon.metrics.shouldTrack(routerEntity)) { val cellMetrics = cell.asInstanceOf[RoutedActorCellMetrics] - cellMetrics.routerEntity = registration.entity - cellMetrics.routerRecorder = Some(registration.recorder) + cellMetrics.routerEntity = routerEntity + cellMetrics.routerRecorder = Some(Kamon.metrics.entity(RouterMetrics, routerEntity)) } } diff --git a/kamon-akka/src/main/scala/kamon/akka/instrumentation/DispatcherInstrumentation.scala b/kamon-akka/src/main/scala/kamon/akka/instrumentation/DispatcherInstrumentation.scala index 7b15c443..931d8a45 100644 --- a/kamon-akka/src/main/scala/kamon/akka/instrumentation/DispatcherInstrumentation.scala +++ b/kamon-akka/src/main/scala/kamon/akka/instrumentation/DispatcherInstrumentation.scala @@ -59,10 +59,16 @@ class DispatcherInstrumentation { private def registerDispatcher(dispatcherName: String, executorService: ExecutorService, system: ActorSystem): Unit = executorService match { case fjp: AkkaForkJoinPool ⇒ - Kamon.metrics.register(ForkJoinPoolDispatcherMetrics.factory(fjp), dispatcherName) + val dispatcherEntity = Entity(system.name + "/" + dispatcherName, AkkaDispatcherMetrics.Category, tags = Map("dispatcher-type" -> "fork-join-pool")) + + if (Kamon.metrics.shouldTrack(dispatcherEntity)) + Kamon.metrics.entity(ForkJoinPoolDispatcherMetrics.factory(fjp), dispatcherEntity) case tpe: ThreadPoolExecutor ⇒ - Kamon.metrics.register(ThreadPoolExecutorDispatcherMetrics.factory(tpe), dispatcherName) + val dispatcherEntity = Entity(system.name + "/" + dispatcherName, AkkaDispatcherMetrics.Category, tags = Map("dispatcher-type" -> "thread-pool-executor")) + + if (Kamon.metrics.shouldTrack(dispatcherEntity)) + Kamon.metrics.entity(ThreadPoolExecutorDispatcherMetrics.factory(tpe), dispatcherEntity) case others ⇒ // Currently not interested in other kinds of dispatchers. } @@ -120,7 +126,17 @@ class DispatcherInstrumentation { import lazyExecutor.lookupData if (lookupData.actorSystem != null) - Kamon.metrics.unregister(Entity(lookupData.dispatcherName, AkkaDispatcherMetrics.Category)) + lazyExecutor.asInstanceOf[ExecutorServiceDelegate].executor match { + case fjp: AkkaForkJoinPool ⇒ + Kamon.metrics.removeEntity(Entity(lookupData.actorSystem.name + "/" + lookupData.dispatcherName, + AkkaDispatcherMetrics.Category, tags = Map("dispatcher-type" -> "fork-join-pool"))) + + case tpe: ThreadPoolExecutor ⇒ + Kamon.metrics.removeEntity(Entity(lookupData.actorSystem.name + "/" + lookupData.dispatcherName, + AkkaDispatcherMetrics.Category, tags = Map("dispatcher-type" -> "thread-pool-executor"))) + + case other ⇒ // nothing to remove. + } } } diff --git a/kamon-akka/src/test/scala/kamon/akka/ActorMetricsSpec.scala b/kamon-akka/src/test/scala/kamon/akka/ActorMetricsSpec.scala index 19a71053..4647abc0 100644 --- a/kamon-akka/src/test/scala/kamon/akka/ActorMetricsSpec.scala +++ b/kamon-akka/src/test/scala/kamon/akka/ActorMetricsSpec.scala @@ -38,8 +38,8 @@ class ActorMetricsSpec extends BaseKamonSpec("actor-metrics-spec") { | | filters { | akka-actor { - | includes = [ "user/tracked-*", "user/measuring-*", "user/clean-after-collect", "user/stop" ] - | excludes = [ "user/tracked-explicitly-excluded", "user/non-tracked-actor" ] + | includes = [ "*/user/tracked-*", "*/user/measuring-*", "*/user/clean-after-collect", "*/user/stop", "*/" ] + | excludes = [ "*/user/tracked-explicitly-excluded", "*/user/non-tracked-actor" ] | } | } | @@ -64,6 +64,10 @@ class ActorMetricsSpec extends BaseKamonSpec("actor-metrics-spec") { actorMetricsRecorderOf(trackedButExplicitlyExcluded) shouldBe empty } + "not pick up the root supervisor" in { + Kamon.metrics.find("actor-metrics-spec/", ActorMetrics.category) shouldBe empty + } + "reset all recording instruments after taking a snapshot" in new ActorMetricsFixtures { val trackedActor = createTestActor("clean-after-collect") @@ -151,7 +155,7 @@ class ActorMetricsSpec extends BaseKamonSpec("actor-metrics-spec") { trackedActor ! PoisonPill deathWatcher.expectTerminated(trackedActor) - actorMetricsRecorderOf(trackedActor).get shouldNot be theSameInstanceAs (firstRecorder) + actorMetricsRecorderOf(trackedActor) shouldBe empty } } @@ -162,10 +166,10 @@ class ActorMetricsSpec extends BaseKamonSpec("actor-metrics-spec") { val buffer: LongBuffer = LongBuffer.allocate(10000) } - def actorRecorderName(ref: ActorRef): String = ref.path.elements.mkString("/") + def actorRecorderName(ref: ActorRef): String = system.name + "/" + ref.path.elements.mkString("/") def actorMetricsRecorderOf(ref: ActorRef): Option[ActorMetrics] = - Kamon.metrics.register(ActorMetrics, actorRecorderName(ref)).map(_.recorder) + Kamon.metrics.find(actorRecorderName(ref), ActorMetrics.category).map(_.asInstanceOf[ActorMetrics]) def collectMetricsOf(ref: ActorRef): Option[EntitySnapshot] = { Thread.sleep(5) // Just in case the test advances a bit faster than the actor being tested. diff --git a/kamon-akka/src/test/scala/kamon/akka/DispatcherMetricsSpec.scala b/kamon-akka/src/test/scala/kamon/akka/DispatcherMetricsSpec.scala index 2379bff5..02a5a0d4 100644 --- a/kamon-akka/src/test/scala/kamon/akka/DispatcherMetricsSpec.scala +++ b/kamon-akka/src/test/scala/kamon/akka/DispatcherMetricsSpec.scala @@ -15,7 +15,6 @@ package kamon.akka - import akka.actor.ActorRef import akka.dispatch.MessageDispatcher import akka.testkit.TestProbe @@ -37,8 +36,8 @@ class DispatcherMetricsSpec extends BaseKamonSpec("dispatcher-metrics-spec") { | | filters = { | akka-dispatcher { - | includes = [ "*" ] - | excludes = [ "explicitly-excluded" ] + | includes = [ "**" ] + | excludes = [ "*/explicitly-excluded" ] | } | } | @@ -85,16 +84,16 @@ class DispatcherMetricsSpec extends BaseKamonSpec("dispatcher-metrics-spec") { val tpeDispatcher = forceInit(system.dispatchers.lookup("tracked-tpe")) val excludedDispatcher = forceInit(system.dispatchers.lookup("explicitly-excluded")) - findDispatcherRecorder(defaultDispatcher) shouldNot be(empty) - findDispatcherRecorder(fjpDispatcher) shouldNot be(empty) - findDispatcherRecorder(tpeDispatcher) shouldNot be(empty) - findDispatcherRecorder(excludedDispatcher) should be(empty) + findDispatcherRecorder(defaultDispatcher, "fork-join-pool") shouldNot be(empty) + findDispatcherRecorder(fjpDispatcher, "fork-join-pool") shouldNot be(empty) + findDispatcherRecorder(tpeDispatcher, "thread-pool-executor") shouldNot be(empty) + findDispatcherRecorder(excludedDispatcher, "fork-join-pool") should be(empty) } "record metrics for a dispatcher with thread-pool-executor" in { implicit val tpeDispatcher = system.dispatchers.lookup("tracked-tpe") - refreshDispatcherInstruments(tpeDispatcher) - collectDispatcherMetrics(tpeDispatcher) + refreshDispatcherInstruments(tpeDispatcher, "thread-pool-executor") + collectDispatcherMetrics(tpeDispatcher, "thread-pool-executor") Await.result({ Future.sequence { @@ -102,8 +101,8 @@ class DispatcherMetricsSpec extends BaseKamonSpec("dispatcher-metrics-spec") { } }, 5 seconds) - refreshDispatcherInstruments(tpeDispatcher) - val snapshot = collectDispatcherMetrics(tpeDispatcher) + refreshDispatcherInstruments(tpeDispatcher, "thread-pool-executor") + val snapshot = collectDispatcherMetrics(tpeDispatcher, "thread-pool-executor") snapshot.gauge("active-threads") should not be empty snapshot.gauge("pool-size").get.min should be >= 7L @@ -113,13 +112,13 @@ class DispatcherMetricsSpec extends BaseKamonSpec("dispatcher-metrics-spec") { snapshot.gauge("processed-tasks").get.max should be(102L +- 5L) // The processed tasks should be reset to 0 if no more tasks are submitted. - val secondSnapshot = collectDispatcherMetrics(tpeDispatcher) + val secondSnapshot = collectDispatcherMetrics(tpeDispatcher, "thread-pool-executor") secondSnapshot.gauge("processed-tasks").get.max should be(0) } "record metrics for a dispatcher with fork-join-executor" in { implicit val fjpDispatcher = system.dispatchers.lookup("tracked-fjp") - collectDispatcherMetrics(fjpDispatcher) + collectDispatcherMetrics(fjpDispatcher, "fork-join-pool") Await.result({ Future.sequence { @@ -127,8 +126,8 @@ class DispatcherMetricsSpec extends BaseKamonSpec("dispatcher-metrics-spec") { } }, 5 seconds) - refreshDispatcherInstruments(fjpDispatcher) - val snapshot = collectDispatcherMetrics(fjpDispatcher) + refreshDispatcherInstruments(fjpDispatcher, "fork-join-pool") + val snapshot = collectDispatcherMetrics(fjpDispatcher, "fork-join-pool") snapshot.minMaxCounter("parallelism").get.max should be(22) snapshot.gauge("pool-size").get.min should be >= 0L @@ -143,28 +142,28 @@ class DispatcherMetricsSpec extends BaseKamonSpec("dispatcher-metrics-spec") { implicit val tpeDispatcher = system.dispatchers.lookup("tracked-tpe") implicit val fjpDispatcher = system.dispatchers.lookup("tracked-fjp") - findDispatcherRecorder(fjpDispatcher) shouldNot be(empty) - findDispatcherRecorder(tpeDispatcher) shouldNot be(empty) + findDispatcherRecorder(fjpDispatcher, "fork-join-pool") shouldNot be(empty) + findDispatcherRecorder(tpeDispatcher, "thread-pool-executor") shouldNot be(empty) shutdownDispatcher(tpeDispatcher) shutdownDispatcher(fjpDispatcher) - findDispatcherRecorder(fjpDispatcher) should be(empty) - findDispatcherRecorder(tpeDispatcher) should be(empty) + findDispatcherRecorder(fjpDispatcher, "fork-join-pool") should be(empty) + findDispatcherRecorder(tpeDispatcher, "thread-pool-executor") should be(empty) } } def actorRecorderName(ref: ActorRef): String = ref.path.elements.mkString("/") - def findDispatcherRecorder(dispatcher: MessageDispatcher): Option[EntityRecorder] = - Kamon.metrics.find(dispatcher.id, "akka-dispatcher") + def findDispatcherRecorder(dispatcher: MessageDispatcher, dispatcherType: String): Option[EntityRecorder] = + Kamon.metrics.find(system.name + "/" + dispatcher.id, "akka-dispatcher", tags = Map("dispatcher-type" -> dispatcherType)) - def collectDispatcherMetrics(dispatcher: MessageDispatcher): EntitySnapshot = - findDispatcherRecorder(dispatcher).map(_.collect(collectionContext)).get + def collectDispatcherMetrics(dispatcher: MessageDispatcher, dispatcherType: String): EntitySnapshot = + findDispatcherRecorder(dispatcher, dispatcherType).map(_.collect(collectionContext)).get - def refreshDispatcherInstruments(dispatcher: MessageDispatcher): Unit = { - findDispatcherRecorder(dispatcher) match { + def refreshDispatcherInstruments(dispatcher: MessageDispatcher, dispatcherType: String): Unit = { + findDispatcherRecorder(dispatcher, dispatcherType) match { case Some(tpe: ThreadPoolExecutorDispatcherMetrics) ⇒ tpe.processedTasks.refreshValue() tpe.activeThreads.refreshValue() diff --git a/kamon-akka/src/test/scala/kamon/akka/RouterMetricsSpec.scala b/kamon-akka/src/test/scala/kamon/akka/RouterMetricsSpec.scala index ec55648b..4128c9ef 100644 --- a/kamon-akka/src/test/scala/kamon/akka/RouterMetricsSpec.scala +++ b/kamon-akka/src/test/scala/kamon/akka/RouterMetricsSpec.scala @@ -39,8 +39,8 @@ class RouterMetricsSpec extends BaseKamonSpec("router-metrics-spec") { | | filters = { | akka-router { - | includes = [ "user/tracked-*", "user/measuring-*", "user/stop-*" ] - | excludes = [ "user/tracked-explicitly-excluded-*"] + | includes = [ "*/user/tracked-*", "*/user/measuring-*", "*/user/stop-*" ] + | excludes = [ "*/user/tracked-explicitly-excluded-*"] | } | } |} @@ -180,7 +180,7 @@ class RouterMetricsSpec extends BaseKamonSpec("router-metrics-spec") { trackedRouter ! PoisonPill deathWatcher.expectTerminated(trackedRouter) - routerMetricsRecorderOf("user/stop-in-pool-router").get shouldNot be theSameInstanceAs (firstRecorder) + routerMetricsRecorderOf("user/stop-in-pool-router") shouldBe empty } "clean up the associated recorder when the group router is stopped" in new RouterMetricsFixtures { @@ -193,7 +193,7 @@ class RouterMetricsSpec extends BaseKamonSpec("router-metrics-spec") { trackedRouter ! PoisonPill deathWatcher.expectTerminated(trackedRouter) - routerMetricsRecorderOf("user/stop-in-group-router").get shouldNot be theSameInstanceAs (firstRecorder) + routerMetricsRecorderOf("user/stop-in-group-router") shouldBe empty } } @@ -205,7 +205,7 @@ class RouterMetricsSpec extends BaseKamonSpec("router-metrics-spec") { } def routerMetricsRecorderOf(routerName: String): Option[RouterMetrics] = - Kamon.metrics.register(RouterMetrics, routerName).map(_.recorder) + Kamon.metrics.find(system.name + "/" + routerName, RouterMetrics.category).map(_.asInstanceOf[RouterMetrics]) def collectMetricsOf(routerName: String): Option[EntitySnapshot] = { Thread.sleep(5) // Just in case the test advances a bit faster than the actor being tested. diff --git a/kamon-akka/src/test/scala/kamon/akka/instrumentation/ActorLoggingInstrumentationSpec.scala b/kamon-akka/src/test/scala/kamon/akka/instrumentation/ActorLoggingInstrumentationSpec.scala index 42a26cdd..85f41795 100644 --- a/kamon-akka/src/test/scala/kamon/akka/instrumentation/ActorLoggingInstrumentationSpec.scala +++ b/kamon-akka/src/test/scala/kamon/akka/instrumentation/ActorLoggingInstrumentationSpec.scala @@ -21,7 +21,7 @@ import com.typesafe.config.ConfigFactory import kamon.testkit.BaseKamonSpec import kamon.trace.TraceLocal.AvailableToMdc import kamon.trace.logging.MdcKeysSupport -import kamon.trace.{Tracer, TraceContextAware, TraceLocal} +import kamon.trace.{ Tracer, TraceContextAware, TraceLocal } import org.scalatest.Inspectors import org.slf4j.MDC diff --git a/kamon-akka/src/test/scala/kamon/akka/instrumentation/ActorSystemMessageInstrumentationSpec.scala b/kamon-akka/src/test/scala/kamon/akka/instrumentation/ActorSystemMessageInstrumentationSpec.scala index fd9f58d0..1635fadc 100644 --- a/kamon-akka/src/test/scala/kamon/akka/instrumentation/ActorSystemMessageInstrumentationSpec.scala +++ b/kamon-akka/src/test/scala/kamon/akka/instrumentation/ActorSystemMessageInstrumentationSpec.scala @@ -21,7 +21,7 @@ import akka.actor._ import akka.testkit.ImplicitSender import com.typesafe.config.ConfigFactory import kamon.testkit.BaseKamonSpec -import kamon.trace.{Tracer, EmptyTraceContext} +import kamon.trace.{ Tracer, EmptyTraceContext } import org.scalatest.WordSpecLike import scala.concurrent.duration._ diff --git a/kamon-akka/src/test/scala/kamon/akka/instrumentation/AskPatternInstrumentationSpec.scala b/kamon-akka/src/test/scala/kamon/akka/instrumentation/AskPatternInstrumentationSpec.scala index a44945ea..4268e53d 100644 --- a/kamon-akka/src/test/scala/kamon/akka/instrumentation/AskPatternInstrumentationSpec.scala +++ b/kamon-akka/src/test/scala/kamon/akka/instrumentation/AskPatternInstrumentationSpec.scala @@ -27,7 +27,7 @@ import com.typesafe.config.ConfigFactory import kamon.Kamon import kamon.akka.Akka import kamon.testkit.BaseKamonSpec -import kamon.trace.{Tracer, TraceContext, TraceContextAware} +import kamon.trace.{ Tracer, TraceContext, TraceContextAware } import scala.concurrent.duration._ diff --git a/kamon-annotation/src/main/java/kamon/annotation/Count.java b/kamon-annotation/src/main/java/kamon/annotation/Count.java new file mode 100644 index 00000000..09bea4ec --- /dev/null +++ b/kamon-annotation/src/main/java/kamon/annotation/Count.java @@ -0,0 +1,69 @@ +/* + * ========================================================================================= + * Copyright © 2013-2015 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.annotation; + +import java.lang.annotation.*; + +/** + * A marker annotation to define a method as a Counter. + * <p> + * <p> + * Given a method like this: + * <pre><code> + * {@literal @}Count(name = "coolName", tags="${'my-cool-tag':'my-cool-value'}") + * public String coolName(String name) { + * return "Hello " + name; + * } + * </code></pre> + * <p> + * <p> + * A {@link kamon.metric.instrument.Counter Counter} for the defining method with the name {@code coolName} will be created and each time the + * {@code #coolName(String)} method is invoked, the counter will be incremented. + */ +@Documented +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +public @interface Count { + + /** + * @return The counter's name. + * <p> + * Also, the Metric name can be resolved with an EL expression that evaluates to a String: + * <p> + * <pre> + * {@code + * class Counted { + * private long id; + * + * public long getId() { return id; } + * + * {@literal @}Count (name = "${'counterID:' += this.id}") + * void countedMethod() {} // create a counter with name => counterID:[id] + * } + * } + * </pre> + */ + String name(); + + /** + * Tags are a way of adding dimensions to metrics, + * these are constructed using EL syntax e.g. "${'algorithm':'1','env':'production'}" + * + * @return the tags associated to the counter + */ + String tags() default ""; +} diff --git a/kamon-annotation/src/main/java/kamon/annotation/EnableKamon.java b/kamon-annotation/src/main/java/kamon/annotation/EnableKamon.java new file mode 100644 index 00000000..97ed9375 --- /dev/null +++ b/kamon-annotation/src/main/java/kamon/annotation/EnableKamon.java @@ -0,0 +1,27 @@ +/* + * ========================================================================================= + * Copyright © 2013-2015 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.annotation; + +import java.lang.annotation.*; + +/** + * A marker annotation for enable the Kamon instrumentation. + */ +@Documented +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.TYPE}) +public @interface EnableKamon {} diff --git a/kamon-annotation/src/main/java/kamon/annotation/Histogram.java b/kamon-annotation/src/main/java/kamon/annotation/Histogram.java new file mode 100644 index 00000000..7c163104 --- /dev/null +++ b/kamon-annotation/src/main/java/kamon/annotation/Histogram.java @@ -0,0 +1,89 @@ +/* + * ========================================================================================= + * Copyright © 2013-2015 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.annotation; + +import java.lang.annotation.*; + +/** + * A marker annotation to define a method as a Histogram. + * <p> + * <p> + * Given a method like this: + * <pre><code> + * {@literal @}0Histogram(name = "coolName", tags="${'my-cool-tag':'my-cool-value'}") + * public (Long|Double|Float|Integer) coolName() { + * return someComputation(); + * } + * </code></pre> + * <p> + * <p> + * A {@link kamon.metric.instrument.Histogram Histogram} for the defining method with the name {@code coolName} will be created which uses the + * annotated method's return as its value. + */ +@Documented +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +public @interface Histogram { + + /** + * @return The histogram's name. + * <p> + * Also, the Metric name can be resolved with an EL expression that evaluates to a String: + * <p> + * <pre> + * {@code + * class ClassWithHistogram { + * private long id; + * + * public long getId() { return id; } + * + * {@literal @}Histogram (name = "${'histoID:' += this.id}") + * void countedMethod() {} // create a histogram with name => histoID:[id] + * } + * } + * </pre> + */ + String name(); + + /** + * The lowest value that can be discerned (distinguished from 0) by the histogram.Must be a positive integer that + * is >= 1. May be internally rounded down to nearest power of 2. + */ + long lowestDiscernibleValue() default 1; + + + /** + * The highest value to be tracked by the histogram. Must be a positive integer that is >= (2 * lowestDiscernibleValue). + * Must not be larger than (Long.MAX_VALUE/2). + */ + long highestTrackableValue() default 3600000000000L; + + /** + * The number of significant decimal digits to which the histogram will maintain value resolution and separation. + * Must be a non-negative integer between 1 and 3. + */ + int precision() default 2; + + + /** + * Tags are a way of adding dimensions to metrics, + * these are constructed using EL syntax e.g. "${'algorithm':'1','env':'production'}" + * + * @return the tags associated to the histogram + */ + String tags() default ""; +}
\ No newline at end of file diff --git a/kamon-annotation/src/main/java/kamon/annotation/MinMaxCount.java b/kamon-annotation/src/main/java/kamon/annotation/MinMaxCount.java new file mode 100644 index 00000000..ed2a6f6e --- /dev/null +++ b/kamon-annotation/src/main/java/kamon/annotation/MinMaxCount.java @@ -0,0 +1,71 @@ +/* + * ========================================================================================= + * Copyright © 2013-2015 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.annotation; + +import java.lang.annotation.*; + +/** + * A marker annotation to define a method as a MinMaxCounter. + * <p> + * <p> + * Given a method like this: + * <pre><code> + * {@literal @}MinMaxCount(name = "coolName", tags="${'my-cool-tag':'my-cool-value'}") + * public String coolName(String name) { + * return "Hello " + name; + * } + * </code></pre> + * <p> + * <p> + * A {@link kamon.metric.instrument.MinMaxCounter MinMaxCounter} for the defining method with the name {@code coolName} will be created and each time the + * {@code #coolName(String)} method is invoked the counter is decremented when the method returns, + * counting current invocations of the annotated method. + */ +@Documented +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +public @interface MinMaxCount { + + /** + * @return The MinMaxCounter's name. + * <p> + * Also, the Metric name can be resolved with an EL expression that evaluates to a String: + * <p> + * <pre> + * {@code + * class MinMaxCounted { + * private long id; + * + * public long getId() { return id; } + * + * {@literal @}MinMaxCount (name = "${'counterID:' += this.id}") + * void countedMethod() {} // create a counter with name => counterID:[id] + * } + * } + * </pre> + */ + + String name(); + + /** + * Tags are a way of adding dimensions to metrics, + * these are constructed using EL syntax e.g. "${'algorithm':'1','env':'production'}" + * + * @return the tags associated to the counter + */ + String tags() default ""; +} diff --git a/kamon-annotation/src/main/java/kamon/annotation/Segment.java b/kamon-annotation/src/main/java/kamon/annotation/Segment.java new file mode 100644 index 00000000..8d69e84a --- /dev/null +++ b/kamon-annotation/src/main/java/kamon/annotation/Segment.java @@ -0,0 +1,78 @@ +/* + * ========================================================================================= + * Copyright © 2013-2015 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.annotation; + +import java.lang.annotation.*; + +/** + * A marker annotation to start a new Segment. + * <p> + * <p> + * Given a method like this: + * <pre><code> + * {@literal @}Segment("coolSegmentName", tags="${'my-cool-tag':'my-cool-value'}") + * public String coolName(String name) { + * return "Hello " + name; + * } + * </code></pre> + * <p> + * <p> + * A new Segment will be created only if in the moment of the method execution exist a TraceContext. + */ +@Documented +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +public @interface Segment { + + /** + * @return The Segment's name. + * <p> + * Also, the Segment name can be resolved with an EL expression that evaluates to a String: + * <p> + * <pre> + * {@code + * class Segment { + * private long id; + * + * public long getId() { return id; } + * + * {@literal @}Segment (name = "${'segmentID:' += this.id}") + * void segment() {} // create a Segment with name => segmentID:[id] + * } + * } + * </pre> + */ + String name(); + + /** + * @return The Segment's category. + */ + String category(); + + /** + * @return The Segment's library. + */ + String library(); + + /** + * Tags are a way of adding dimensions to metrics, + * these are constructed using EL syntax e.g. "${'algorithm':'1','env':'production'}" + * + * @return the tags associated to the segment + */ + String tags() default ""; +} diff --git a/kamon-annotation/src/main/java/kamon/annotation/Time.java b/kamon-annotation/src/main/java/kamon/annotation/Time.java new file mode 100644 index 00000000..a8e3a62c --- /dev/null +++ b/kamon-annotation/src/main/java/kamon/annotation/Time.java @@ -0,0 +1,57 @@ +/* + * ========================================================================================= + * Copyright © 2013-2015 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.annotation; + + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + + +/** + * A marker annotation to define a method as a timed. + * <p> + * <p> + * Given a method like this: + * <pre><code> + * {@literal @}Timed(name = "coolName", tags="""${{'my-cool-tag':'my-cool-value'}}""") + * public String coolName(String name) { + * return "Hello " + name; + * } + * </code></pre> + * <p> + * <p> + * A histogram for the defining method with the name {@code coolName} will be created and each time the + * {@code #coolName(String)} method is invoked, the latency of execution will be recorded. + */ +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +public @interface Time { + /** + * @return The histogram's name. + */ + String name(); + + /** + * Tags are a way of adding dimensions to metrics, + * these are constructed using EL syntax e.g. """${{'algorithm':'1','env':'production'}}""" + * + * @return the tags associated to the histogram + */ + String tags() default ""; +} diff --git a/kamon-annotation/src/main/java/kamon/annotation/Trace.java b/kamon-annotation/src/main/java/kamon/annotation/Trace.java new file mode 100644 index 00000000..1f373fa2 --- /dev/null +++ b/kamon-annotation/src/main/java/kamon/annotation/Trace.java @@ -0,0 +1,69 @@ +/* + * ========================================================================================= + * Copyright © 2013-2015 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.annotation; + +import java.lang.annotation.*; + +/** + * A marker annotation to start a new Trace. + * <p> + * <p> + * Given a method like this: + * <pre><code> + * {@literal @}Trace("coolTraceName", tags="${'my-cool-tag':'my-cool-value'}") + * public String coolName(String name) { + * return "Hello " + name; + * } + * </code></pre> + * <p> + * <p> + * A new Trace will be created for the defining method with the name each time the + * {@code #coolName(String)} method is invoked. + */ + +@Documented +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +public @interface Trace { + /** + * @return The Trace's name. + * <p> + * Also, the Trace name can be resolved with an EL expression that evaluates to a String: + * <p> + * <pre> + * {@code + * class Traced { + * private long id; + * + * public long getId() { return id; } + * + * {@literal @}Trace (name = "${'traceID:' += this.id}") + * void countedMethod() {} // create a Trace with name => traceID:[id] + * } + * } + * </pre> + */ + String value(); + + /** + * Tags are a way of adding dimensions to metrics, + * these are constructed using EL syntax e.g. "${'algorithm':'1','env':'production'}" + * + * @return the tags associated to the trace + */ + String tags() default ""; +} diff --git a/kamon-annotation/src/main/java/kamon/annotation/el/resolver/PrivateFieldELResolver.java b/kamon-annotation/src/main/java/kamon/annotation/el/resolver/PrivateFieldELResolver.java new file mode 100644 index 00000000..df646942 --- /dev/null +++ b/kamon-annotation/src/main/java/kamon/annotation/el/resolver/PrivateFieldELResolver.java @@ -0,0 +1,117 @@ +/* + * ========================================================================================= + * Copyright © 2013-2015 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.annotation.el.resolver; + +import java.beans.FeatureDescriptor; +import java.lang.reflect.Field; +import java.util.Iterator; +import javax.el.ELContext; +import javax.el.ELException; +import javax.el.ELResolver; + +/** + * A custom {@link ELResolver} for mapping properties to public/private fields of the base object. + */ +public class PrivateFieldELResolver extends ELResolver { + + @Override + public Object getValue(ELContext context, Object base, Object property) { + if (base == null) { + return null; + } + try { + Field field = getField(base, (String) property); + context.setPropertyResolved(true); + field.setAccessible(true); + return field.get(base); + } catch (NoSuchFieldException exc) { + context.setPropertyResolved(false); + return null; + } catch (Exception exc) { + throw new ELException(exc); + } + } + + private Field getField(Object base, String property) throws NoSuchFieldException { + try { + return base.getClass().getDeclaredField(property); + } catch (SecurityException exc) { + throw new ELException(exc); + } + } + + @Override + public Class<?> getType(ELContext context, Object base, Object property) { + if (base == null) { + return null; + } + try { + Field field = getField(base, (String) property); + if (field != null) { + context.setPropertyResolved(true); + return field.getType(); + } else { + return null; + } + } catch (NoSuchFieldException exc) { + context.setPropertyResolved(false); + return null; + } + } + + @Override + public void setValue(ELContext context, Object base, Object property, Object value) { + if (base == null) { + return; + } + try { + context.setPropertyResolved(true); + getField(base, (String) property).set(base, value); + } catch (NoSuchFieldException exc) { + context.setPropertyResolved(false); + } catch (Exception exc) { + throw new ELException(exc); + } + } + + @Override + public boolean isReadOnly(ELContext context, Object base, Object property) { + if (base == null) { + return true; + } + try { + Field field = getField(base, (String) property); + if (field != null) { + context.setPropertyResolved(true); + return !field.isAccessible(); + } + } catch (NoSuchFieldException exc) { + context.setPropertyResolved(false); + } + return false; + } + + @Override + public Iterator<FeatureDescriptor> getFeatureDescriptors(ELContext context, Object base) { + return null; + } + + @Override + public Class<?> getCommonPropertyType(ELContext context, Object base) { + return String.class; + } +}
\ No newline at end of file diff --git a/kamon-annotation/src/main/resources/META-INF/aop.xml b/kamon-annotation/src/main/resources/META-INF/aop.xml new file mode 100644 index 00000000..b017e60e --- /dev/null +++ b/kamon-annotation/src/main/resources/META-INF/aop.xml @@ -0,0 +1,18 @@ +<!DOCTYPE aspectj PUBLIC "-//AspectJ//DTD//EN" "http://www.eclipse.org/aspectj/dtd/aspectj.dtd"> + +<aspectj> + <aspects> + <aspect name="kamon.annotation.instrumentation.ClassToAnnotationInstrumentsMixin"/> + <aspect name="kamon.annotation.instrumentation.AnnotationInstrumentation"/> + <aspect name="kamon.annotation.instrumentation.StaticAnnotationInstrumentation"/> + </aspects> + <weaver options="-warn:none"> + <!-- exclude some commons packages --> + <exclude within="org.apache.commons..*"/> + <exclude within="org.apache.log4j..*"/> + <exclude within="org.hibernate..*"/> + <exclude within="org.springframework..*"/> + <exclude within="com.google..*"/> + <exclude within="*..*CGLIB*" /> + </weaver> +</aspectj> diff --git a/kamon-annotation/src/main/resources/reference.conf b/kamon-annotation/src/main/resources/reference.conf new file mode 100644 index 00000000..b7680022 --- /dev/null +++ b/kamon-annotation/src/main/resources/reference.conf @@ -0,0 +1,19 @@ +# ========================================= # +# Kamon-Annotation Reference Configuration # +# ========================================= # + +kamon { + annotation { + # We use two arrays to store the kamon instruments in order to do fast array lookups. + # These lookups are done using the getId() method on the JoinPoint.StaticPart object. + # The ids for all affected join points within a target type are unique (and start from 0). + instruments-array-size = 32 + } + modules { + kamon-annotation { + auto-start = yes + requires-aspectj = yes + extension-id = "kamon.annotation.Annotation" + } + } +} diff --git a/kamon-annotation/src/main/scala/kamon/annotation/Annotation.scala b/kamon-annotation/src/main/scala/kamon/annotation/Annotation.scala new file mode 100644 index 00000000..6ddf57cf --- /dev/null +++ b/kamon-annotation/src/main/scala/kamon/annotation/Annotation.scala @@ -0,0 +1,34 @@ +/* ========================================================================================= + * Copyright © 2013-2015 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.annotation + +import akka.actor.{ ExtendedActorSystem, Extension, ExtensionId, ExtensionIdProvider } +import akka.event.Logging +import kamon.Kamon + +object Annotation extends ExtensionId[AnnotationExtension] with ExtensionIdProvider { + override def lookup(): ExtensionId[_ <: Extension] = Annotation + override def createExtension(system: ExtendedActorSystem): AnnotationExtension = new AnnotationExtension(system) +} + +class AnnotationExtension(system: ExtendedActorSystem) extends Kamon.Extension { + val log = Logging(system, classOf[AnnotationExtension]) + log.info(s"Starting the Kamon(Annotation) extension") + + val config = system.settings.config.getConfig("kamon.annotation") + val arraySize = config.getInt("instruments-array-size") +} + diff --git a/kamon-core/src/main/scala/kamon/instrumentation/AspectJWeaverMissingWarning.scala b/kamon-annotation/src/main/scala/kamon/annotation/el/ELProcessorFactory.scala index 4dc5ff41..e80c61d8 100644 --- a/kamon-core/src/main/scala/kamon/instrumentation/AspectJWeaverMissingWarning.scala +++ b/kamon-annotation/src/main/scala/kamon/annotation/el/ELProcessorFactory.scala @@ -14,20 +14,27 @@ * ========================================================================================= */ -package kamon.instrumentation +package kamon.annotation.el -import _root_.akka.event.EventStream -import org.aspectj.lang.ProceedingJoinPoint -import org.aspectj.lang.annotation.{ Around, Pointcut, Aspect } +import javax.el.ELProcessor +import kamon.annotation.el.resolver.PrivateFieldELResolver -@Aspect -class AspectJWeaverMissingWarning { +object ELProcessorFactory { + def withClass(clazz: Class[_]): ELProcessor = { + val processor = create() + processor.getELManager.importClass(clazz.getName) + processor + } - @Pointcut("execution(* kamon.metric.MetricsExtension.printInitializationMessage(..)) && args(eventStream, *)") - def printInitializationMessage(eventStream: EventStream): Unit = {} + def withObject(obj: AnyRef): ELProcessor = { + val processor = withClass(obj.getClass) + processor.defineBean("this", obj) + processor + } - @Around("printInitializationMessage(eventStream)") - def aroundPrintInitializationMessage(pjp: ProceedingJoinPoint, eventStream: EventStream): Unit = { - pjp.proceed(Array[AnyRef](eventStream, Boolean.box(true))) + private def create(): ELProcessor = { + val processor = new ELProcessor() + processor.getELManager.addELResolver(new PrivateFieldELResolver()) + processor } } diff --git a/kamon-annotation/src/main/scala/kamon/annotation/el/EnhancedELProcessor.scala b/kamon-annotation/src/main/scala/kamon/annotation/el/EnhancedELProcessor.scala new file mode 100644 index 00000000..f407930b --- /dev/null +++ b/kamon-annotation/src/main/scala/kamon/annotation/el/EnhancedELProcessor.scala @@ -0,0 +1,60 @@ +/* + * ========================================================================================= + * Copyright © 2013-2015 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.annotation.el + +import javax.el.ELProcessor + +import kamon.Kamon +import kamon.annotation.Annotation + +import scala.util.{ Failure, Success, Try } + +/** + * Pimp ELProcessor injecting some useful methods. + */ +object EnhancedELProcessor { + private val Pattern = """[#|$]\{(.*)\}""".r + + implicit class Syntax(val processor: ELProcessor) extends AnyVal { + import scala.collection.JavaConverters._ + + def evalToString(expression: String): String = extract(expression).map { str ⇒ + eval[String](str) match { + case Success(value) ⇒ value + case Failure(cause) ⇒ + Kamon(Annotation).log.error(s"${cause.getMessage} -> we will complete the operation with 'unknown' string") + "unknown" + } + } getOrElse expression + + def evalToMap(expression: String): Map[String, String] = extract(expression).map { str ⇒ + eval[Map[String, String]](s"{$str}") match { + case Success(value) ⇒ value.asInstanceOf[java.util.HashMap[String, String]].asScala.toMap + case Failure(cause) ⇒ + Kamon(Annotation).log.error(s"${cause.getMessage} -> we will complete the operation with an empty map") + Map.empty[String, String] + } + } getOrElse Map.empty[String, String] + + private def eval[A](str: String): Try[A] = Try(processor.eval(str).asInstanceOf[A]) + + private def extract(expression: String): Option[String] = expression match { + case Pattern(ex) ⇒ Some(ex) + case _ ⇒ None + } + } +} diff --git a/kamon-annotation/src/main/scala/kamon/annotation/instrumentation/AnnotationInstrumentation.scala b/kamon-annotation/src/main/scala/kamon/annotation/instrumentation/AnnotationInstrumentation.scala new file mode 100644 index 00000000..381aeb72 --- /dev/null +++ b/kamon-annotation/src/main/scala/kamon/annotation/instrumentation/AnnotationInstrumentation.scala @@ -0,0 +1,81 @@ +/* + * ========================================================================================= + * Copyright © 2013-2015 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.annotation.instrumentation + +import kamon.Kamon +import kamon.annotation._ +import kamon.metric.instrument +import kamon.metric.instrument.{ MinMaxCounter, Counter } +import org.aspectj.lang.annotation._ +import org.aspectj.lang.{ JoinPoint, ProceedingJoinPoint } +import java.util.concurrent.atomic.AtomicReferenceArray + +@Aspect +class AnnotationInstrumentation extends BaseAnnotationInstrumentation { + + @After("execution((@kamon.annotation.EnableKamon AnnotationInstruments+).new(..)) && this(obj)") + def creation(jps: JoinPoint.StaticPart, obj: AnnotationInstruments): Unit = { + val size = Kamon(Annotation).arraySize + obj.traces = new AtomicReferenceArray[TraceContextInfo](size) + obj.segments = new AtomicReferenceArray[SegmentInfo](size) + obj.counters = new AtomicReferenceArray[Counter](size) + obj.minMaxCounters = new AtomicReferenceArray[MinMaxCounter](size) + obj.histograms = new AtomicReferenceArray[instrument.Histogram](size) + } + + @Around("execution(@kamon.annotation.Trace !static * (@kamon.annotation.EnableKamon AnnotationInstruments+).*(..)) && this(obj)") + def trace(pjp: ProceedingJoinPoint, obj: AnnotationInstruments): AnyRef = { + var traceInfo = obj.traces.get(pjp.getStaticPart.getId) + if (traceInfo == null) traceInfo = registerTrace(pjp.getStaticPart, obj.traces, StringEvaluator(obj), TagsEvaluator(obj)) + processTrace(traceInfo, pjp) + } + + @Around("execution(@kamon.annotation.Segment !static * (@kamon.annotation.EnableKamon AnnotationInstruments+).*(..)) && this(obj)") + def segment(pjp: ProceedingJoinPoint, obj: AnnotationInstruments): AnyRef = { + var segmentInfo = obj.segments.get(pjp.getStaticPart.getId) + if (segmentInfo == null) segmentInfo = registerSegment(pjp.getStaticPart, obj.segments, StringEvaluator(obj), TagsEvaluator(obj)) + processSegment(segmentInfo, pjp) + } + + @Around("execution(@kamon.annotation.Time !static * (@kamon.annotation.EnableKamon AnnotationInstruments+).*(..)) && this(obj)") + def time(pjp: ProceedingJoinPoint, obj: AnnotationInstruments): AnyRef = { + var histogram = obj.histograms.get(pjp.getStaticPart.getId) + if (histogram == null) histogram = registerTime(pjp.getStaticPart, obj.histograms, StringEvaluator(obj), TagsEvaluator(obj)) + processTime(histogram, pjp) + } + + @Around("execution(@kamon.annotation.Count !static * (@kamon.annotation.EnableKamon AnnotationInstruments+).*(..)) && this(obj)") + def count(pjp: ProceedingJoinPoint, obj: AnnotationInstruments): AnyRef = { + var counter = obj.counters.get(pjp.getStaticPart.getId) + if (counter == null) counter = registerCounter(pjp.getStaticPart, obj.counters, StringEvaluator(obj), TagsEvaluator(obj)) + processCount(counter, pjp) + } + + @Around("execution(@kamon.annotation.MinMaxCount !static * (@kamon.annotation.EnableKamon AnnotationInstruments+).*(..)) && this(obj)") + def minMax(pjp: ProceedingJoinPoint, obj: AnnotationInstruments): AnyRef = { + var minMax = obj.minMaxCounters.get(pjp.getStaticPart.getId) + if (minMax == null) minMax = registerMinMaxCounter(pjp.getStaticPart, obj.minMaxCounters, StringEvaluator(obj), TagsEvaluator(obj)) + processMinMax(minMax, pjp) + } + + @AfterReturning(pointcut = "execution(@kamon.annotation.Histogram !static (int || long || double || float) (@kamon.annotation.EnableKamon AnnotationInstruments+).*(..)) && this(obj)", returning = "result") + def histogram(jps: JoinPoint.StaticPart, obj: AnnotationInstruments, result: AnyRef): Unit = { + var histogram = obj.histograms.get(jps.getId) + if (histogram == null) histogram = registerHistogram(jps, obj.histograms, StringEvaluator(obj), TagsEvaluator(obj)) + processHistogram(histogram, result, jps) + } +} diff --git a/kamon-annotation/src/main/scala/kamon/annotation/instrumentation/BaseAnnotationInstrumentation.scala b/kamon-annotation/src/main/scala/kamon/annotation/instrumentation/BaseAnnotationInstrumentation.scala new file mode 100644 index 00000000..2074e237 --- /dev/null +++ b/kamon-annotation/src/main/scala/kamon/annotation/instrumentation/BaseAnnotationInstrumentation.scala @@ -0,0 +1,178 @@ +/* + * ========================================================================================= + * Copyright © 2013-2015 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.annotation.instrumentation + +import java.util.concurrent.atomic.AtomicReferenceArray +import javax.el.ELProcessor + +import kamon.Kamon +import kamon.annotation.el.{ EnhancedELProcessor, ELProcessorFactory } +import kamon.annotation.{ Histogram, _ } +import kamon.metric.instrument.Histogram.DynamicRange +import kamon.metric.instrument.{ Counter, MinMaxCounter } +import kamon.metric._ +import kamon.trace.Tracer +import kamon.util.Latency +import org.aspectj.lang.{ JoinPoint, ProceedingJoinPoint } +import org.aspectj.lang.annotation.{ Aspect, DeclareMixin } +import org.aspectj.lang.reflect.MethodSignature +import EnhancedELProcessor.Syntax + +class BaseAnnotationInstrumentation { + + @inline final def registerTime(jps: JoinPoint.StaticPart, histograms: AtomicReferenceArray[instrument.Histogram], evalString: StringEvaluator, evalTags: TagsEvaluator): instrument.Histogram = { + val method = jps.getSignature.asInstanceOf[MethodSignature].getMethod + val time = method.getAnnotation(classOf[Time]) + val name = evalString(time.name()) + val tags = evalTags(time.tags()) + val currentHistogram = Kamon.metrics.histogram(name, tags) + histograms.set(jps.getId, currentHistogram) + currentHistogram + } + + @inline final def registerHistogram(jps: JoinPoint.StaticPart, histograms: AtomicReferenceArray[instrument.Histogram], evalString: StringEvaluator, evalTags: TagsEvaluator): instrument.Histogram = { + val method = jps.getSignature.asInstanceOf[MethodSignature].getMethod + val histogram = method.getAnnotation(classOf[Histogram]) + val name = evalString(histogram.name()) + val tags = evalTags(histogram.tags()) + val dynamicRange = DynamicRange(histogram.lowestDiscernibleValue(), histogram.highestTrackableValue(), histogram.precision()) + val currentHistogram = Kamon.metrics.histogram(name, tags, dynamicRange) + histograms.set(jps.getId, currentHistogram) + currentHistogram + } + + @inline final def registerMinMaxCounter(jps: JoinPoint.StaticPart, minMaxCounters: AtomicReferenceArray[MinMaxCounter], evalString: StringEvaluator, evalTags: TagsEvaluator): instrument.MinMaxCounter = { + val method = jps.getSignature.asInstanceOf[MethodSignature].getMethod + val minMaxCount = method.getAnnotation(classOf[MinMaxCount]) + val name = evalString(minMaxCount.name()) + val tags = evalTags(minMaxCount.tags()) + val minMaxCounter = Kamon.metrics.minMaxCounter(name, tags) + minMaxCounters.set(jps.getId, minMaxCounter) + minMaxCounter + } + + @inline final def registerCounter(jps: JoinPoint.StaticPart, counters: AtomicReferenceArray[Counter], evalString: StringEvaluator, evalTags: TagsEvaluator): instrument.Counter = { + val method = jps.getSignature.asInstanceOf[MethodSignature].getMethod + val count = method.getAnnotation(classOf[Count]) + val name = evalString(count.name()) + val tags = evalTags(count.tags()) + val counter = Kamon.metrics.counter(name, tags) + counters.set(jps.getId, counter) + counter + } + + @inline final def registerTrace(jps: JoinPoint.StaticPart, traces: AtomicReferenceArray[TraceContextInfo], evalString: StringEvaluator, evalTags: TagsEvaluator): TraceContextInfo = { + val method = jps.getSignature.asInstanceOf[MethodSignature].getMethod + val trace = method.getAnnotation(classOf[Trace]) + val name = evalString(trace.value()) + val tags = evalTags(trace.tags()) + val traceContextInfo = TraceContextInfo(name, tags) + traces.set(jps.getId, traceContextInfo) + traceContextInfo + } + + @inline final def registerSegment(jps: JoinPoint.StaticPart, segments: AtomicReferenceArray[SegmentInfo], evalString: StringEvaluator, evalTags: TagsEvaluator): SegmentInfo = { + val method = jps.getSignature.asInstanceOf[MethodSignature].getMethod + val segment = method.getAnnotation(classOf[Segment]) + val name = evalString(segment.name()) + val category = evalString(segment.category()) + val library = evalString(segment.library()) + val tags = evalTags(segment.tags()) + val segmentInfo = SegmentInfo(name, category, library, tags) + segments.set(jps.getId, segmentInfo) + segmentInfo + } + + @inline final def processTrace(traceInfo: TraceContextInfo, pjp: ProceedingJoinPoint): AnyRef = { + Tracer.withContext(Kamon.tracer.newContext(traceInfo.name)) { + traceInfo.tags.foreach { case (key, value) ⇒ Tracer.currentContext.addMetadata(key, value) } + val result = pjp.proceed() + Tracer.currentContext.finish() + result + } + } + + @inline final def processSegment(segmentInfo: SegmentInfo, pjp: ProceedingJoinPoint): AnyRef = { + Tracer.currentContext.collect { ctx ⇒ + val currentSegment = ctx.startSegment(segmentInfo.name, segmentInfo.category, segmentInfo.library) + segmentInfo.tags.foreach { case (key, value) ⇒ currentSegment.addMetadata(key, value) } + val result = pjp.proceed() + currentSegment.finish() + result + } getOrElse pjp.proceed() + } + + @inline final def processTime(histogram: instrument.Histogram, pjp: ProceedingJoinPoint): AnyRef = { + Latency.measure(histogram)(pjp.proceed) + } + + @inline final def processHistogram(histogram: instrument.Histogram, result: AnyRef, jps: JoinPoint.StaticPart): Unit = { + histogram.record(result.asInstanceOf[Number].longValue()) + } + + final def processCount(counter: instrument.Counter, pjp: ProceedingJoinPoint): AnyRef = { + try pjp.proceed() finally counter.increment() + } + + final def processMinMax(minMaxCounter: instrument.MinMaxCounter, pjp: ProceedingJoinPoint): AnyRef = { + minMaxCounter.increment() + try pjp.proceed() finally minMaxCounter.decrement() + } + + private[annotation] def declaringType(signature: org.aspectj.lang.Signature) = signature.getDeclaringType +} + +@Aspect +class ClassToAnnotationInstrumentsMixin { + @DeclareMixin("(@kamon.annotation.EnableKamon *)") + def mixinClassToAnnotationInstruments: AnnotationInstruments = new AnnotationInstruments {} +} + +trait AnnotationInstruments { + var traces: AtomicReferenceArray[TraceContextInfo] = _ + var segments: AtomicReferenceArray[SegmentInfo] = _ + var histograms: AtomicReferenceArray[instrument.Histogram] = _ + var counters: AtomicReferenceArray[Counter] = _ + var minMaxCounters: AtomicReferenceArray[MinMaxCounter] = _ +} + +case class SegmentInfo(name: String, category: String, library: String, tags: Map[String, String]) +case class TraceContextInfo(name: String, tags: Map[String, String]) + +abstract class StringEvaluator(val processor: ELProcessor) extends (String ⇒ String) + +object StringEvaluator { + def apply(obj: AnyRef) = new StringEvaluator(ELProcessorFactory.withObject(obj)) { + def apply(str: String) = processor.evalToString(str) + } + + def apply(clazz: Class[_]) = new StringEvaluator(ELProcessorFactory.withClass(clazz)) { + def apply(str: String) = processor.evalToString(str) + } +} + +abstract class TagsEvaluator(val processor: ELProcessor) extends (String ⇒ Map[String, String]) + +object TagsEvaluator { + def apply(obj: AnyRef) = new TagsEvaluator(ELProcessorFactory.withObject(obj)) { + def apply(str: String) = processor.evalToMap(str) + } + + def apply(clazz: Class[_]) = new TagsEvaluator(ELProcessorFactory.withClass(clazz)) { + def apply(str: String) = processor.evalToMap(str) + } +}
\ No newline at end of file diff --git a/kamon-annotation/src/main/scala/kamon/annotation/instrumentation/StaticAnnotationInstrumentation.scala b/kamon-annotation/src/main/scala/kamon/annotation/instrumentation/StaticAnnotationInstrumentation.scala new file mode 100644 index 00000000..24ce75fd --- /dev/null +++ b/kamon-annotation/src/main/scala/kamon/annotation/instrumentation/StaticAnnotationInstrumentation.scala @@ -0,0 +1,100 @@ +/* + * ========================================================================================= + * Copyright © 2013-2015 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.annotation.instrumentation + +import java.util.concurrent.atomic.AtomicReferenceArray + +import kamon.Kamon +import kamon.annotation.Annotation +import kamon.metric.instrument +import kamon.metric.instrument.{ Counter, MinMaxCounter } +import org.aspectj.lang.annotation.{ After, AfterReturning, Around, Aspect } +import org.aspectj.lang.{ JoinPoint, ProceedingJoinPoint } + +@Aspect("pertypewithin(kamon.annotation.instrumentation.AnnotationInstruments+ && !kamon.annotation.instrumentation.*)") +class StaticAnnotationInstrumentation extends BaseAnnotationInstrumentation with AnnotationInstruments { + + @After("staticinitialization(*) && !within(kamon.annotation.instrumentation.*)") + def creation(jps: JoinPoint.StaticPart): Unit = { + val size = Kamon(Annotation).arraySize + traces = new AtomicReferenceArray[TraceContextInfo](size) + segments = new AtomicReferenceArray[SegmentInfo](size) + counters = new AtomicReferenceArray[Counter](size) + minMaxCounters = new AtomicReferenceArray[MinMaxCounter](size) + histograms = new AtomicReferenceArray[instrument.Histogram](size) + } + + @Around("execution(@kamon.annotation.Trace static * (@kamon.annotation.EnableKamon *).*(..))") + def trace(pjp: ProceedingJoinPoint): AnyRef = { + var traceInfo = traces.get(pjp.getStaticPart.getId) + if (traceInfo == null) { + val clazz = declaringType(pjp.getSignature) + traceInfo = registerTrace(pjp.getStaticPart, traces, StringEvaluator(clazz), TagsEvaluator(clazz)) + } + processTrace(traceInfo, pjp) + } + + @Around("execution(@kamon.annotation.Segment static * (@kamon.annotation.EnableKamon *).*(..))") + def segment(pjp: ProceedingJoinPoint): AnyRef = { + var segmentInfo = segments.get(pjp.getStaticPart.getId) + if (segmentInfo == null) { + val clazz = declaringType(pjp.getSignature) + segmentInfo = registerSegment(pjp.getStaticPart, segments, StringEvaluator(clazz), TagsEvaluator(clazz)) + } + processSegment(segmentInfo, pjp) + } + + @Around("execution(@kamon.annotation.Time static * (@kamon.annotation.EnableKamon *).*(..))") + def time(pjp: ProceedingJoinPoint): AnyRef = { + var histogram = histograms.get(pjp.getStaticPart.getId) + if (histogram == null) { + val clazz = declaringType(pjp.getSignature) + histogram = registerTime(pjp.getStaticPart, histograms, StringEvaluator(clazz), TagsEvaluator(clazz)) + } + processTime(histogram, pjp) + } + + @Around("execution(@kamon.annotation.Count static * (@kamon.annotation.EnableKamon *).*(..))") + def count(pjp: ProceedingJoinPoint): AnyRef = { + var counter = counters.get(pjp.getStaticPart.getId) + if (counter == null) { + val clazz = declaringType(pjp.getSignature) + counter = registerCounter(pjp.getStaticPart, counters, StringEvaluator(clazz), TagsEvaluator(clazz)) + } + processCount(counter, pjp) + } + + @Around("execution(@kamon.annotation.MinMaxCount static * (@kamon.annotation.EnableKamon *).*(..))") + def minMax(pjp: ProceedingJoinPoint): AnyRef = { + var minMax = minMaxCounters.get(pjp.getStaticPart.getId) + if (minMax == null) { + val clazz = declaringType(pjp.getSignature) + minMax = registerMinMaxCounter(pjp.getStaticPart, minMaxCounters, StringEvaluator(clazz), TagsEvaluator(clazz)) + } + processMinMax(minMax, pjp) + } + + @AfterReturning(pointcut = "execution(@kamon.annotation.Histogram static (int || long || double || float) (@kamon.annotation.EnableKamon *).*(..))", returning = "result") + def histogram(jps: JoinPoint.StaticPart, result: AnyRef): Unit = { + var histogram = histograms.get(jps.getId) + if (histogram == null) { + val clazz = declaringType(jps.getSignature) + histogram = registerHistogram(jps, histograms, StringEvaluator(clazz), TagsEvaluator(clazz)) + } + processHistogram(histogram, result, jps) + } +} diff --git a/kamon-annotation/src/test/java/kamon/annotation/AnnotatedJavaClass.java b/kamon-annotation/src/test/java/kamon/annotation/AnnotatedJavaClass.java new file mode 100644 index 00000000..285ccc74 --- /dev/null +++ b/kamon-annotation/src/test/java/kamon/annotation/AnnotatedJavaClass.java @@ -0,0 +1,66 @@ +/* + * ========================================================================================= + * Copyright © 2013-2015 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.annotation; + +@EnableKamon +public class AnnotatedJavaClass { + + public static String ID = "10"; + + @Trace("trace") + public static void trace() {} + + @Trace("trace-with-segment") + public static void segment() { + inner(); // method annotated with @Segment + } + + @Trace("trace-with-segment-el") + public static void segmentWithEL() { + innerWithEL(); // method annotated with @Segment + } + + @Count(name = "count") + public static void count() {} + + @Count(name = "${'count:' += AnnotatedJavaClass.ID}", tags = "${'counter':'1', 'env':'prod'}") + public static void countWithEL() {} + + @MinMaxCount(name = "minMax") + public static void countMinMax() {} + + @MinMaxCount(name = "#{'minMax:' += AnnotatedJavaClass.ID}", tags = "#{'minMax':'1', 'env':'dev'}") + public static void countMinMaxWithEL() {} + + @Time(name = "time") + public static void time() {} + + @Time(name = "${'time:' += AnnotatedJavaClass.ID}", tags = "${'slow-service':'service', 'env':'prod'}") + public static void timeWithEL() {} + + @Histogram(name = "histogram") + public static long histogram(Long value) { return value; } + + @Histogram(name = "#{'histogram:' += AnnotatedJavaClass.ID}", tags = "${'histogram':'hdr', 'env':'prod'}") + public static long histogramWithEL(Long value) { return value;} + + @Segment(name = "inner-segment", category = "inner", library = "segment") + private static void inner() {} + + @Segment(name = "#{'inner-segment:' += AnnotatedJavaClass.ID}", category = "segments", library = "segment") + private static void innerWithEL() {} +}
\ No newline at end of file diff --git a/kamon-annotation/src/test/resources/logback.xml b/kamon-annotation/src/test/resources/logback.xml new file mode 100644 index 00000000..c336bbfe --- /dev/null +++ b/kamon-annotation/src/test/resources/logback.xml @@ -0,0 +1,12 @@ +<configuration> + <statusListener class="ch.qos.logback.core.status.NopStatusListener"/> + <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> + <encoder> + <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern> + </encoder> + </appender> + + <root level="OFF"> + <appender-ref ref="STDOUT"/> + </root> +</configuration>
\ No newline at end of file diff --git a/kamon-annotation/src/test/scala/kamon/annotation/AnnotationInstrumentationSpec.scala b/kamon-annotation/src/test/scala/kamon/annotation/AnnotationInstrumentationSpec.scala new file mode 100644 index 00000000..3d94d246 --- /dev/null +++ b/kamon-annotation/src/test/scala/kamon/annotation/AnnotationInstrumentationSpec.scala @@ -0,0 +1,199 @@ +/* + * ========================================================================================= + * Copyright © 2013-2015 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.annotation + +import com.typesafe.config.ConfigFactory +import kamon.metric._ +import kamon.testkit.BaseKamonSpec +import kamon.trace.SegmentCategory + +class AnnotationInstrumentationSpec extends BaseKamonSpec("annotation-instrumentation-spec") { + override lazy val config = + ConfigFactory.parseString( + """ + |kamon.metric { + | tick-interval = 1 hour + | default-collection-context-buffer-size = 100 + |} + """.stripMargin) + + "the Kamon Annotation module" should { + "create a new trace when is invoked a method annotated with @Trace" in { + for (id ← 1 to 10) Annotated(id).trace() + + val snapshot = takeSnapshotOf("trace", "trace") + snapshot.histogram("elapsed-time").get.numberOfMeasurements should be(10) + } + + "create a segment when is invoked a method annotated with @Segment" in { + for (id ← 1 to 10) Annotated().segment() + + val snapshot = takeSnapshotOf("trace-with-segment", "trace") + snapshot.histogram("elapsed-time").get.numberOfMeasurements should be(10) + + val segmentMetricsSnapshot = takeSnapshotOf("inner-segment", "trace-segment", + tags = Map( + "trace" -> "trace-with-segment", + "category" -> "inner", + "library" -> "segment")) + + segmentMetricsSnapshot.histogram("elapsed-time").get.numberOfMeasurements should be(10) + } + + "create a segment when is invoked a method annotated with @Segment and evaluate EL expressions" in { + for (id ← 1 to 10) Annotated(id).segmentWithEL() + + val snapshot = takeSnapshotOf("trace-with-segment-el", "trace") + snapshot.histogram("elapsed-time").get.numberOfMeasurements should be(10) + + val segmentMetricsSnapshot = takeSnapshotOf("inner-segment:1", "trace-segment", + tags = Map( + "trace" -> "trace-with-segment-el", + "category" -> "inner", + "library" -> "segment")) + + segmentMetricsSnapshot.histogram("elapsed-time").get.numberOfMeasurements should be(1) + } + + "count the invocations of a method annotated with @Count" in { + for (id ← 1 to 10) Annotated(id).count() + + val snapshot = takeSnapshotOf("count", "counter") + snapshot.counter("counter").get.count should be(10) + } + + "count the invocations of a method annotated with @Count and evaluate EL expressions" in { + for (id ← 1 to 2) Annotated(id).countWithEL() + + val counter1Snapshot = takeSnapshotOf("count:1", "counter", Map("counter" -> "1", "env" -> "prod")) + counter1Snapshot.counter("counter").get.count should be(1) + + val counter2Snapshot = takeSnapshotOf("count:2", "counter", Map("counter" -> "1", "env" -> "prod")) + counter2Snapshot.counter("counter").get.count should be(1) + } + + "count the current invocations of a method annotated with @MinMaxCount" in { + for (id ← 1 to 10) { + Annotated(id).countMinMax() + } + + val snapshot = takeSnapshotOf("minMax", "min-max-counter") + snapshot.minMaxCounter("min-max-counter").get.max should be(1) + } + + "count the current invocations of a method annotated with @MinMaxCount and evaluate EL expressions" in { + for (id ← 1 to 10) Annotated(id).countMinMaxWithEL() + + val minMaxCounter1Snapshot = takeSnapshotOf("minMax:1", "min-max-counter", tags = Map("minMax" -> "1", "env" -> "dev")) + minMaxCounter1Snapshot.minMaxCounter("min-max-counter").get.sum should be(1) + + val minMaxCounter2Snapshot = takeSnapshotOf("minMax:2", "min-max-counter", tags = Map("minMax" -> "1", "env" -> "dev")) + minMaxCounter2Snapshot.minMaxCounter("min-max-counter").get.sum should be(1) + } + + "measure the time spent in the execution of a method annotated with @Time" in { + for (id ← 1 to 1) Annotated(id).time() + + val snapshot = takeSnapshotOf("time", "histogram") + snapshot.histogram("histogram").get.numberOfMeasurements should be(1) + } + + "measure the time spent in the execution of a method annotated with @Time and evaluate EL expressions" in { + for (id ← 1 to 1) Annotated(id).timeWithEL() + + val snapshot = takeSnapshotOf("time:1", "histogram", tags = Map("slow-service" -> "service", "env" -> "prod")) + snapshot.histogram("histogram").get.numberOfMeasurements should be(1) + } + + "record the value returned by a method annotated with @Histogram" in { + for (value ← 1 to 5) Annotated().histogram(value) + + val snapshot = takeSnapshotOf("histogram", "histogram") + snapshot.histogram("histogram").get.numberOfMeasurements should be(5) + snapshot.histogram("histogram").get.min should be(1) + snapshot.histogram("histogram").get.max should be(5) + snapshot.histogram("histogram").get.sum should be(15) + } + + "record the value returned by a method annotated with @Histogram and evaluate EL expressions" in { + for (value ← 1 to 2) Annotated(value).histogramWithEL(value) + + val snapshot1 = takeSnapshotOf("histogram:1", "histogram", tags = Map("histogram" -> "hdr", "env" -> "prod")) + snapshot1.histogram("histogram").get.numberOfMeasurements should be(1) + snapshot1.histogram("histogram").get.min should be(1) + snapshot1.histogram("histogram").get.max should be(1) + snapshot1.histogram("histogram").get.sum should be(1) + + val snapshot2 = takeSnapshotOf("histogram:2", "histogram", tags = Map("histogram" -> "hdr", "env" -> "prod")) + snapshot2.histogram("histogram").get.numberOfMeasurements should be(1) + snapshot2.histogram("histogram").get.min should be(2) + snapshot2.histogram("histogram").get.max should be(2) + snapshot2.histogram("histogram").get.sum should be(2) + } + } +} + +@EnableKamon +case class Annotated(id: Long) { + + @Trace("trace") + def trace(): Unit = {} + + @Trace("trace-with-segment") + def segment(): Unit = { + inner() // method annotated with @Segment + } + + @Trace("trace-with-segment-el") + def segmentWithEL(): Unit = { + innerWithEL() // method annotated with @Segment + } + + @Count(name = "count") + def count(): Unit = {} + + @Count(name = "${'count:' += this.id}", tags = "${'counter':'1', 'env':'prod'}") + def countWithEL(): Unit = {} + + @MinMaxCount(name = "minMax") + def countMinMax(): Unit = {} + + @MinMaxCount(name = "#{'minMax:' += this.id}", tags = "#{'minMax':'1', 'env':'dev'}") + def countMinMaxWithEL(): Unit = {} + + @Time(name = "time") + def time(): Unit = {} + + @Time(name = "${'time:' += this.id}", tags = "${'slow-service':'service', 'env':'prod'}") + def timeWithEL(): Unit = {} + + @Histogram(name = "histogram") + def histogram(value: Long): Long = value + + @Histogram(name = "#{'histogram:' += this.id}", tags = "${'histogram':'hdr', 'env':'prod'}") + def histogramWithEL(value: Long): Long = value + + @Segment(name = "inner-segment", category = "inner", library = "segment") + private def inner(): Unit = {} + + @Segment(name = "#{'inner-segment:' += this.id}", category = "inner", library = "segment") + private def innerWithEL(): Unit = {} +} + +object Annotated { + def apply(): Annotated = new Annotated(0L) +}
\ No newline at end of file diff --git a/kamon-annotation/src/test/scala/kamon/annotation/StaticAnnotationInstrumentationJavaSpec.scala b/kamon-annotation/src/test/scala/kamon/annotation/StaticAnnotationInstrumentationJavaSpec.scala new file mode 100644 index 00000000..6e0fbd02 --- /dev/null +++ b/kamon-annotation/src/test/scala/kamon/annotation/StaticAnnotationInstrumentationJavaSpec.scala @@ -0,0 +1,130 @@ +/* + * ========================================================================================= + * Copyright © 2013-2015 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.annotation + +import com.typesafe.config.ConfigFactory +import kamon.metric.{ HistogramKey, MinMaxCounterKey, CounterKey } +import kamon.testkit.BaseKamonSpec + +class StaticAnnotationInstrumentationJavaSpec extends BaseKamonSpec("static-annotation-instrumentation-java-spec") { + override lazy val config = + ConfigFactory.parseString( + """ + |kamon.metric { + | tick-interval = 1 hour + | default-collection-context-buffer-size = 100 + |} + """.stripMargin) + + "the Kamon Annotation module" should { + "create a new trace when is invoked a static method annotated with @Trace" in { + for (id ← 1 to 10) AnnotatedJavaClass.trace() + + val snapshot = takeSnapshotOf("trace", "trace") + snapshot.histogram("elapsed-time").get.numberOfMeasurements should be(10) + } + "create a segment when is invoked a static method annotated with @Segment" in { + for (id ← 1 to 7) AnnotatedJavaClass.segment() + + val segmentMetricsSnapshot = takeSnapshotOf("inner-segment", "trace-segment", + tags = Map( + "trace" -> "trace-with-segment", + "category" -> "inner", + "library" -> "segment")) + + segmentMetricsSnapshot.histogram("elapsed-time").get.numberOfMeasurements should be(7) + } + + "create a segment when is invoked a static method annotated with @Segment and evaluate EL expressions" in { + for (id ← 1 to 10) AnnotatedJavaClass.segmentWithEL() + + val snapshot = takeSnapshotOf("trace-with-segment-el", "trace") + snapshot.histogram("elapsed-time").get.numberOfMeasurements should be(10) + + val segmentMetricsSnapshot = takeSnapshotOf("inner-segment:10", "trace-segment", + tags = Map( + "trace" -> "trace-with-segment-el", + "category" -> "segments", + "library" -> "segment")) + + segmentMetricsSnapshot.histogram("elapsed-time").get.numberOfMeasurements should be(10) + } + + "count the invocations of a static method annotated with @Count" in { + for (id ← 1 to 10) AnnotatedJavaClass.count() + + val snapshot = takeSnapshotOf("count", "counter") + snapshot.counter("counter").get.count should be(10) + } + + "count the invocations of a static method annotated with @Count and evaluate EL expressions" in { + for (id ← 1 to 2) AnnotatedJavaClass.countWithEL() + + val snapshot = takeSnapshotOf("count:10", "counter", tags = Map("counter" -> "1", "env" -> "prod")) + snapshot.counter("counter").get.count should be(2) + } + + "count the current invocations of a static method annotated with @MinMaxCount" in { + for (id ← 1 to 10) { + AnnotatedJavaClass.countMinMax() + } + + val snapshot = takeSnapshotOf("minMax", "min-max-counter") + snapshot.minMaxCounter("min-max-counter").get.max should be(1) + } + + "count the current invocations of a static method annotated with @MinMaxCount and evaluate EL expressions" in { + for (id ← 1 to 10) AnnotatedJavaClass.countMinMaxWithEL() + + val snapshot = takeSnapshotOf("minMax:10", "min-max-counter", tags = Map("minMax" -> "1", "env" -> "dev")) + snapshot.minMaxCounter("min-max-counter").get.max should be(1) + } + + "measure the time spent in the execution of a static method annotated with @Time" in { + for (id ← 1 to 1) AnnotatedJavaClass.time() + + val snapshot = takeSnapshotOf("time", "histogram") + snapshot.histogram("histogram").get.numberOfMeasurements should be(1) + } + + "measure the time spent in the execution of a static method annotated with @Time and evaluate EL expressions" in { + for (id ← 1 to 1) AnnotatedJavaClass.timeWithEL() + + val snapshot = takeSnapshotOf("time:10", "histogram", tags = Map("slow-service" -> "service", "env" -> "prod")) + snapshot.histogram("histogram").get.numberOfMeasurements should be(1) + } + + "record the value returned by a static method annotated with @Histogram" in { + for (value ← 1 to 5) AnnotatedJavaClass.histogram(value.toLong) + + val snapshot = takeSnapshotOf("histogram", "histogram") + snapshot.histogram("histogram").get.numberOfMeasurements should be(5) + snapshot.histogram("histogram").get.min should be(1) + snapshot.histogram("histogram").get.max should be(5) + snapshot.histogram("histogram").get.sum should be(15) + } + + "record the value returned by a static method annotated with @Histogram and evaluate EL expressions" in { + for (value ← 1 to 2) AnnotatedJavaClass.histogramWithEL(value.toLong) + + val snapshot = takeSnapshotOf("histogram:10", "histogram", tags = Map("histogram" -> "hdr", "env" -> "prod")) + snapshot.histogram("histogram").get.numberOfMeasurements should be(2) + snapshot.histogram("histogram").get.min should be(1) + snapshot.histogram("histogram").get.max should be(2) + } + } +}
\ No newline at end of file diff --git a/kamon-annotation/src/test/scala/kamon/annotation/StaticAnnotationInstrumentationSpec.scala b/kamon-annotation/src/test/scala/kamon/annotation/StaticAnnotationInstrumentationSpec.scala new file mode 100644 index 00000000..a8e68009 --- /dev/null +++ b/kamon-annotation/src/test/scala/kamon/annotation/StaticAnnotationInstrumentationSpec.scala @@ -0,0 +1,188 @@ +/* + * ========================================================================================= + * Copyright © 2013-2015 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.annotation + +import com.typesafe.config.ConfigFactory +import kamon.metric.{ HistogramKey, MinMaxCounterKey, CounterKey } +import kamon.testkit.BaseKamonSpec + +class StaticAnnotationInstrumentationSpec extends BaseKamonSpec("static-annotation-instrumentation-spec") { + override lazy val config = + ConfigFactory.parseString( + """ + |kamon.metric { + | tick-interval = 1 hour + | default-collection-context-buffer-size = 100 + |} + """.stripMargin) + "the Kamon Annotation module" should { + "create a new trace when is invoked a method annotated with @Trace in a Scala Object" in { + for (id ← 1 to 42) AnnotatedObject.trace() + + val snapshot = takeSnapshotOf("trace", "trace") + snapshot.histogram("elapsed-time").get.numberOfMeasurements should be(42) + } + + "create a segment when is invoked a method annotated with @Trace and @Segment in a Scala Object" in { + for (id ← 1 to 15) AnnotatedObject.segment() + + val segmentMetricsSnapshot = takeSnapshotOf("segment", "trace-segment", + tags = Map( + "trace" -> "trace-with-segment", + "category" -> "segments", + "library" -> "segment")) + + segmentMetricsSnapshot.histogram("elapsed-time").get.numberOfMeasurements should be(15) + } + + "create a segment when is invoked a method annotated with @Trace and @Segment and evaluate EL expressions in a Scala Object" in { + for (id ← 1 to 18) AnnotatedObject.segmentWithEL() + + val snapshot = takeSnapshotOf("trace-with-segment-el", "trace") + snapshot.histogram("elapsed-time").get.numberOfMeasurements should be(18) + + val segment10Snapshot = takeSnapshotOf("segment:10", "trace-segment", + tags = Map( + "trace" -> "trace-with-segment-el", + "category" -> "segments", + "library" -> "segment")) + + segment10Snapshot.histogram("elapsed-time").get.numberOfMeasurements should be(18) + + val innerSegmentSnapshot = takeSnapshotOf("inner-segment", "trace-segment", + tags = Map( + "trace" -> "trace-with-segment-el", + "category" -> "inner", + "library" -> "segment")) + + innerSegmentSnapshot.histogram("elapsed-time").get.numberOfMeasurements should be(18) + } + + "count the invocations of a method annotated with @Count in a Scala Object" in { + for (id ← 1 to 10) AnnotatedObject.count() + + val snapshot = takeSnapshotOf("count", "counter") + snapshot.counter("counter").get.count should be(10) + } + + "count the invocations of a method annotated with @Count and evaluate EL expressions in a Scala Object" in { + for (id ← 1 to 2) AnnotatedObject.countWithEL() + + val snapshot = takeSnapshotOf("count:10", "counter", tags = Map("counter" -> "1", "env" -> "prod")) + snapshot.counter("counter").get.count should be(2) + + } + + "count the current invocations of a method annotated with @MinMaxCount in a Scala Object" in { + for (id ← 1 to 10) { + AnnotatedObject.countMinMax() + } + + val snapshot = takeSnapshotOf("minMax", "min-max-counter") + snapshot.minMaxCounter("min-max-counter").get.max should be(1) + } + + "count the current invocations of a method annotated with @MinMaxCount and evaluate EL expressions in a Scala Object" in { + for (id ← 1 to 10) AnnotatedObject.countMinMaxWithEL() + + val snapshot = takeSnapshotOf("minMax:10", "min-max-counter", tags = Map("minMax" -> "1", "env" -> "dev")) + snapshot.minMaxCounter("min-max-counter").get.max should be(1) + + } + + "measure the time spent in the execution of a method annotated with @Time in a Scala Object" in { + for (id ← 1 to 1) AnnotatedObject.time() + + val snapshot = takeSnapshotOf("time", "histogram") + snapshot.histogram("histogram").get.numberOfMeasurements should be(1) + } + + "measure the time spent in the execution of a method annotated with @Time and evaluate EL expressions in a Scala Object" in { + for (id ← 1 to 1) AnnotatedObject.timeWithEL() + + val snapshot = takeSnapshotOf("time:10", "histogram", tags = Map("slow-service" -> "service", "env" -> "prod")) + snapshot.histogram("histogram").get.numberOfMeasurements should be(1) + } + + "record the value returned by a method annotated with @Histogram in a Scala Object" in { + for (value ← 1 to 5) AnnotatedObject.histogram(value) + + val snapshot = takeSnapshotOf("histogram", "histogram") + snapshot.histogram("histogram").get.numberOfMeasurements should be(5) + snapshot.histogram("histogram").get.min should be(1) + snapshot.histogram("histogram").get.max should be(5) + snapshot.histogram("histogram").get.sum should be(15) + } + + "record the value returned by a method annotated with @Histogram and evaluate EL expressions in a Scala Object" in { + for (value ← 1 to 2) AnnotatedObject.histogramWithEL(value) + + val snapshot = takeSnapshotOf("histogram:10", "histogram", tags = Map("histogram" -> "hdr", "env" -> "prod")) + snapshot.histogram("histogram").get.numberOfMeasurements should be(2) + snapshot.histogram("histogram").get.min should be(1) + snapshot.histogram("histogram").get.max should be(2) + } + } +} + +@EnableKamon +object AnnotatedObject { + + val Id = "10" + + @Trace("trace") + def trace(): Unit = {} + + @Trace("trace-with-segment") + @Segment(name = "segment", category = "segments", library = "segment") + def segment(): Unit = { + inner() // method annotated with @Segment + } + + @Trace("trace-with-segment-el") + @Segment(name = "#{'segment:' += AnnotatedObject$.MODULE$.Id}", category = "segments", library = "segment") + def segmentWithEL(): Unit = { + inner() // method annotated with @Segment + } + + @Count(name = "count") + def count(): Unit = {} + + @Count(name = "${'count:' += AnnotatedObject$.MODULE$.Id}", tags = "${'counter':'1', 'env':'prod'}") + def countWithEL(): Unit = {} + + @MinMaxCount(name = "minMax") + def countMinMax(): Unit = {} + + @MinMaxCount(name = "#{'minMax:' += AnnotatedObject$.MODULE$.Id}", tags = "#{'minMax':'1', 'env':'dev'}") + def countMinMaxWithEL(): Unit = {} + + @Time(name = "time") + def time(): Unit = {} + + @Time(name = "${'time:' += AnnotatedObject$.MODULE$.Id}", tags = "${'slow-service':'service', 'env':'prod'}") + def timeWithEL(): Unit = {} + + @Histogram(name = "histogram") + def histogram(value: Long): Long = value + + @Histogram(name = "#{'histogram:' += AnnotatedObject$.MODULE$.Id}", tags = "${'histogram':'hdr', 'env':'prod'}") + def histogramWithEL(value: Long): Long = value + + @Segment(name = "inner-segment", category = "inner", library = "segment") + private def inner(): Unit = {} +}
\ No newline at end of file diff --git a/kamon-core/src/main/resources/META-INF/aop.xml b/kamon-core/src/main/resources/META-INF/aop.xml index 2ffb8b09..b13f9aac 100644 --- a/kamon-core/src/main/resources/META-INF/aop.xml +++ b/kamon-core/src/main/resources/META-INF/aop.xml @@ -4,7 +4,7 @@ <aspects> <!-- Notify that AspectJ is present --> - <aspect name="kamon.supervisor.AspectJPresent"/> + <aspect name="kamon.AspectJPresent"/> </aspects> diff --git a/kamon-core/src/main/scala/kamon/Kamon.scala b/kamon-core/src/main/scala/kamon/Kamon.scala index a2a24f49..f8253875 100644 --- a/kamon-core/src/main/scala/kamon/Kamon.scala +++ b/kamon-core/src/main/scala/kamon/Kamon.scala @@ -18,15 +18,13 @@ import _root_.akka.actor import _root_.akka.actor._ import com.typesafe.config.{ ConfigFactory, Config } import kamon.metric._ +import kamon.metric.instrument.Gauge import kamon.trace.{ TracerImpl, Tracer } object Kamon { trait Extension extends actor.Extension - private case class KamonCoreComponents( - metrics: Metrics, - tracer: Tracer, - simpleMetrics: SimpleMetrics) + private case class KamonCoreComponents(metrics: Metrics, tracer: Tracer) @volatile private var _system: ActorSystem = _ @volatile private var _coreComponents: Option[KamonCoreComponents] = None @@ -43,14 +41,14 @@ object Kamon { if (_coreComponents.isEmpty) { val metrics = MetricsImpl(config) - val simpleMetrics = SimpleMetricsImpl(metrics) val tracer = TracerImpl(metrics, config) - _coreComponents = Some(KamonCoreComponents(metrics, tracer, simpleMetrics)) + _coreComponents = Some(KamonCoreComponents(metrics, tracer)) _system = ActorSystem("kamon", resolveInternalConfig) metrics.start(_system) tracer.start(_system) + _system.registerExtension(ModuleLoader) } else sys.error("Kamon has already been started.") } @@ -70,9 +68,6 @@ object Kamon { def tracer: Tracer = ifStarted(_.tracer) - def simpleMetrics: SimpleMetrics = - ifStarted(_.simpleMetrics) - def apply[T <: Kamon.Extension](key: ExtensionId[T]): T = ifStarted { _ ⇒ if (_system ne null) diff --git a/kamon-core/src/main/scala/kamon/ModuleLoader.scala b/kamon-core/src/main/scala/kamon/ModuleLoader.scala new file mode 100644 index 00000000..d1b7466e --- /dev/null +++ b/kamon-core/src/main/scala/kamon/ModuleLoader.scala @@ -0,0 +1,123 @@ +/* + * ========================================================================================= + * Copyright © 2013-2015 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 + +import _root_.akka.actor +import _root_.akka.actor._ +import _root_.akka.event.Logging +import org.aspectj.lang.ProceedingJoinPoint +import org.aspectj.lang.annotation.{ Around, Pointcut, Aspect } + +private[kamon] object ModuleLoader extends ExtensionId[ModuleLoaderExtension] with ExtensionIdProvider { + def lookup(): ExtensionId[_ <: actor.Extension] = ModuleLoader + def createExtension(system: ExtendedActorSystem): ModuleLoaderExtension = new ModuleLoaderExtension(system) +} + +private[kamon] class ModuleLoaderExtension(system: ExtendedActorSystem) extends Kamon.Extension { + val log = Logging(system, "ModuleLoader") + val settings = ModuleLoaderSettings(system) + + if (settings.modulesRequiringAspectJ.nonEmpty && !isAspectJPresent && settings.showAspectJMissingWarning) + logAspectJWeaverMissing(settings.modulesRequiringAspectJ) + + // Force initialization of all modules marked with auto-start. + settings.availableModules.filter(_.autoStart).foreach { module ⇒ + if (module.extensionClass == "none") + log.debug("Ignoring auto start of the [{}] module with no extension class.") + else + system.dynamicAccess.getObjectFor[ExtensionId[Kamon.Extension]](module.extensionClass).map { moduleID ⇒ + log.debug("Auto starting the [{}] module.", module.name) + moduleID.get(system) + + } recover { + case th: Throwable ⇒ log.error(th, "Failed to auto start the [{}] module.", module.name) + } + + } + + // When AspectJ is present the kamon.supervisor.AspectJPresent aspect will make this return true. + def isAspectJPresent: Boolean = false + + def logAspectJWeaverMissing(modulesRequiringAspectJ: List[AvailableModuleInfo]): Unit = { + val moduleNames = modulesRequiringAspectJ.map(_.name).mkString(", ") + val weaverMissingMessage = + """ + | + | ___ _ ___ _ _ ___ ___ _ _ + | / _ \ | | |_ | | | | | | \/ |(_) (_) + |/ /_\ \ ___ _ __ ___ ___ | |_ | | | | | | ___ __ _ __ __ ___ _ __ | . . | _ ___ ___ _ _ __ __ _ + || _ |/ __|| '_ \ / _ \ / __|| __| | | | |/\| | / _ \ / _` |\ \ / // _ \| '__| | |\/| || |/ __|/ __|| || '_ \ / _` | + || | | |\__ \| |_) || __/| (__ | |_ /\__/ / \ /\ /| __/| (_| | \ V /| __/| | | | | || |\__ \\__ \| || | | || (_| | + |\_| |_/|___/| .__/ \___| \___| \__|\____/ \/ \/ \___| \__,_| \_/ \___||_| \_| |_/|_||___/|___/|_||_| |_| \__, | + | | | __/ | + | |_| |___/ + | + | It seems like your application was not started with the -javaagent:/path-to-aspectj-weaver.jar option but Kamon detected + | the following modules which require AspecJ to work properly: + | + """.stripMargin + moduleNames + + """ + | + | If you need help on setting up the aspectj weaver go to http://kamon.io/introduction/get-started/ for more info. On the + | other hand, if you are sure that you do not need or do not want to use the weaver then you can disable this error message + | by changing the kamon.show-aspectj-missing-warning setting in your configuration file. + | + """.stripMargin + + log.error(weaverMissingMessage) + } +} + +private[kamon] case class AvailableModuleInfo(name: String, extensionClass: String, requiresAspectJ: Boolean, autoStart: Boolean) +private[kamon] case class ModuleLoaderSettings(showAspectJMissingWarning: Boolean, availableModules: List[AvailableModuleInfo]) { + val modulesRequiringAspectJ = availableModules.filter(_.requiresAspectJ) +} + +private[kamon] object ModuleLoaderSettings { + + def apply(system: ActorSystem): ModuleLoaderSettings = { + import kamon.util.ConfigTools.Syntax + + val config = system.settings.config.getConfig("kamon.modules") + val showAspectJMissingWarning = system.settings.config.getBoolean("kamon.show-aspectj-missing-warning") + + val modules = config.firstLevelKeys + val availableModules = modules.map { moduleName ⇒ + val moduleConfig = config.getConfig(moduleName) + + AvailableModuleInfo( + moduleName, + moduleConfig.getString("extension-id"), + moduleConfig.getBoolean("requires-aspectj"), + moduleConfig.getBoolean("auto-start")) + + } toList + + ModuleLoaderSettings(showAspectJMissingWarning, availableModules) + } +} + +@Aspect +private[kamon] class AspectJPresent { + + @Pointcut("execution(* kamon.ModuleLoaderExtension.isAspectJPresent())") + def isAspectJPresentAtModuleSupervisor(): Unit = {} + + @Around("isAspectJPresentAtModuleSupervisor()") + def aroundIsAspectJPresentAtModuleSupervisor(pjp: ProceedingJoinPoint): Boolean = true + +} diff --git a/kamon-core/src/main/scala/kamon/metric/Entity.scala b/kamon-core/src/main/scala/kamon/metric/Entity.scala index 8d328f83..91249af0 100644 --- a/kamon-core/src/main/scala/kamon/metric/Entity.scala +++ b/kamon-core/src/main/scala/kamon/metric/Entity.scala @@ -23,36 +23,15 @@ package kamon.metric * * // TODO: Find a better word for `thing`. */ -class Entity(val name: String, val category: String, val metadata: Map[String, String]) { - - override def equals(o: Any): Boolean = { - if (this eq o.asInstanceOf[AnyRef]) - true - else if ((o.asInstanceOf[AnyRef] eq null) || !o.isInstanceOf[Entity]) - false - else { - val thatAsEntity = o.asInstanceOf[Entity] - category == thatAsEntity.category && name == thatAsEntity.name - } - } - - override def hashCode: Int = { - var result: Int = name.hashCode - result = 31 * result + category.hashCode - return result - } -} +case class Entity(name: String, category: String, tags: Map[String, String]) object Entity { def apply(name: String, category: String): Entity = apply(name, category, Map.empty) - def apply(name: String, category: String, metadata: Map[String, String]): Entity = - new Entity(name, category, metadata) - def create(name: String, category: String): Entity = apply(name, category, Map.empty) - def create(name: String, category: String, metadata: Map[String, String]): Entity = - new Entity(name, category, metadata) + def create(name: String, category: String, tags: Map[String, String]): Entity = + new Entity(name, category, tags) }
\ No newline at end of file diff --git a/kamon-core/src/main/scala/kamon/metric/EntityRecorder.scala b/kamon-core/src/main/scala/kamon/metric/EntityRecorder.scala index 6e0a4248..65dafa9a 100644 --- a/kamon-core/src/main/scala/kamon/metric/EntityRecorder.scala +++ b/kamon-core/src/main/scala/kamon/metric/EntityRecorder.scala @@ -19,6 +19,7 @@ package kamon.metric import kamon.metric.instrument.Gauge.CurrentValueCollector import kamon.metric.instrument.Histogram.DynamicRange import kamon.metric.instrument._ +import kamon.util.Function import scala.collection.concurrent.TrieMap import scala.concurrent.duration.FiniteDuration @@ -33,6 +34,64 @@ trait EntityRecorderFactory[T <: EntityRecorder] { def createRecorder(instrumentFactory: InstrumentFactory): T } +object EntityRecorderFactory { + def apply[T <: EntityRecorder](entityCategory: String, factory: InstrumentFactory ⇒ T): EntityRecorderFactory[T] = + new EntityRecorderFactory[T] { + def category: String = entityCategory + def createRecorder(instrumentFactory: InstrumentFactory): T = factory(instrumentFactory) + } + + def create[T <: EntityRecorder](entityCategory: String, factory: Function[InstrumentFactory, T]): EntityRecorderFactory[T] = + new EntityRecorderFactory[T] { + def category: String = entityCategory + def createRecorder(instrumentFactory: InstrumentFactory): T = factory(instrumentFactory) + } +} + +private[kamon] sealed trait SingleInstrumentEntityRecorder extends EntityRecorder { + def key: MetricKey + def instrument: Instrument + + def collect(collectionContext: CollectionContext): EntitySnapshot = + new DefaultEntitySnapshot(Map(key -> instrument.collect(collectionContext))) + + def cleanup: Unit = instrument.cleanup +} + +object SingleInstrumentEntityRecorder { + val Histogram = "histogram" + val MinMaxCounter = "min-max-counter" + val Gauge = "gauge" + val Counter = "counter" + + val AllCategories = List("histogram", "gauge", "counter", "min-max-counter") +} + +/** + * Entity recorder for a single Counter instrument. + */ +case class CounterRecorder(key: MetricKey, instrument: Counter) extends SingleInstrumentEntityRecorder + +/** + * Entity recorder for a single Histogram instrument. + */ +case class HistogramRecorder(key: MetricKey, instrument: Histogram) extends SingleInstrumentEntityRecorder + +/** + * Entity recorder for a single MinMaxCounter instrument. + */ +case class MinMaxCounterRecorder(key: MetricKey, instrument: MinMaxCounter) extends SingleInstrumentEntityRecorder + +/** + * Entity recorder for a single Gauge instrument. + */ +case class GaugeRecorder(key: MetricKey, instrument: Gauge) extends SingleInstrumentEntityRecorder + +/** + * Base class with plenty of utility methods to facilitate the creation of [[EntityRecorder]] implementations. + * It is not required to use this base class for defining custom a custom [[EntityRecorder]], but it is certainly + * the most convenient way to do it and the preferred approach throughout the Kamon codebase. + */ abstract class GenericEntityRecorder(instrumentFactory: InstrumentFactory) extends EntityRecorder { import kamon.util.TriemapAtomicGetOrElseUpdate.Syntax @@ -41,10 +100,10 @@ abstract class GenericEntityRecorder(instrumentFactory: InstrumentFactory) exten _instruments.atomicGetOrElseUpdate(key, instrument, _.cleanup).asInstanceOf[T] protected def histogram(name: String): Histogram = - register(HistogramKey(name), instrumentFactory.createHistogram(name)) + register(HistogramKey(name, UnitOfMeasurement.Unknown), instrumentFactory.createHistogram(name)) protected def histogram(name: String, dynamicRange: DynamicRange): Histogram = - register(HistogramKey(name), instrumentFactory.createHistogram(name, Some(dynamicRange))) + register(HistogramKey(name, UnitOfMeasurement.Unknown), instrumentFactory.createHistogram(name, Some(dynamicRange))) protected def histogram(name: String, unitOfMeasurement: UnitOfMeasurement): Histogram = register(HistogramKey(name, unitOfMeasurement), instrumentFactory.createHistogram(name)) @@ -52,32 +111,26 @@ abstract class GenericEntityRecorder(instrumentFactory: InstrumentFactory) exten protected def histogram(name: String, dynamicRange: DynamicRange, unitOfMeasurement: UnitOfMeasurement): Histogram = register(HistogramKey(name, unitOfMeasurement), instrumentFactory.createHistogram(name, Some(dynamicRange))) - protected def histogram(key: HistogramKey): Histogram = - register(key, instrumentFactory.createHistogram(key.name)) - - protected def histogram(key: HistogramKey, dynamicRange: DynamicRange): Histogram = - register(key, instrumentFactory.createHistogram(key.name, Some(dynamicRange))) - protected def removeHistogram(name: String): Unit = - _instruments.remove(HistogramKey(name)) + _instruments.remove(HistogramKey(name, UnitOfMeasurement.Unknown)) - protected def removeHistogram(key: HistogramKey): Unit = - _instruments.remove(key) + protected def removeHistogram(name: String, unitOfMeasurement: UnitOfMeasurement): Unit = + _instruments.remove(HistogramKey(name, unitOfMeasurement)) protected def minMaxCounter(name: String): MinMaxCounter = - register(MinMaxCounterKey(name), instrumentFactory.createMinMaxCounter(name)) + register(MinMaxCounterKey(name, UnitOfMeasurement.Unknown), instrumentFactory.createMinMaxCounter(name)) protected def minMaxCounter(name: String, dynamicRange: DynamicRange): MinMaxCounter = - register(MinMaxCounterKey(name), instrumentFactory.createMinMaxCounter(name, Some(dynamicRange))) + register(MinMaxCounterKey(name, UnitOfMeasurement.Unknown), instrumentFactory.createMinMaxCounter(name, Some(dynamicRange))) protected def minMaxCounter(name: String, refreshInterval: FiniteDuration): MinMaxCounter = - register(MinMaxCounterKey(name), instrumentFactory.createMinMaxCounter(name, refreshInterval = Some(refreshInterval))) + register(MinMaxCounterKey(name, UnitOfMeasurement.Unknown), instrumentFactory.createMinMaxCounter(name, refreshInterval = Some(refreshInterval))) protected def minMaxCounter(name: String, unitOfMeasurement: UnitOfMeasurement): MinMaxCounter = register(MinMaxCounterKey(name, unitOfMeasurement), instrumentFactory.createMinMaxCounter(name)) protected def minMaxCounter(name: String, dynamicRange: DynamicRange, refreshInterval: FiniteDuration): MinMaxCounter = - register(MinMaxCounterKey(name), instrumentFactory.createMinMaxCounter(name, Some(dynamicRange), Some(refreshInterval))) + register(MinMaxCounterKey(name, UnitOfMeasurement.Unknown), instrumentFactory.createMinMaxCounter(name, Some(dynamicRange), Some(refreshInterval))) protected def minMaxCounter(name: String, dynamicRange: DynamicRange, unitOfMeasurement: UnitOfMeasurement): MinMaxCounter = register(MinMaxCounterKey(name, unitOfMeasurement), instrumentFactory.createMinMaxCounter(name, Some(dynamicRange))) @@ -86,7 +139,7 @@ abstract class GenericEntityRecorder(instrumentFactory: InstrumentFactory) exten register(MinMaxCounterKey(name, unitOfMeasurement), instrumentFactory.createMinMaxCounter(name, refreshInterval = Some(refreshInterval))) protected def minMaxCounter(name: String, dynamicRange: DynamicRange, refreshInterval: FiniteDuration, unitOfMeasurement: UnitOfMeasurement): MinMaxCounter = - register(MinMaxCounterKey(name), instrumentFactory.createMinMaxCounter(name, Some(dynamicRange), Some(refreshInterval))) + register(MinMaxCounterKey(name, UnitOfMeasurement.Unknown), instrumentFactory.createMinMaxCounter(name, Some(dynamicRange), Some(refreshInterval))) protected def minMaxCounter(key: MinMaxCounterKey): MinMaxCounter = register(key, instrumentFactory.createMinMaxCounter(key.name)) @@ -101,31 +154,31 @@ abstract class GenericEntityRecorder(instrumentFactory: InstrumentFactory) exten register(key, instrumentFactory.createMinMaxCounter(key.name, Some(dynamicRange), Some(refreshInterval))) protected def removeMinMaxCounter(name: String): Unit = - _instruments.remove(MinMaxCounterKey(name)) + _instruments.remove(MinMaxCounterKey(name, UnitOfMeasurement.Unknown)) protected def removeMinMaxCounter(key: MinMaxCounterKey): Unit = _instruments.remove(key) protected def gauge(name: String, valueCollector: CurrentValueCollector): Gauge = - register(GaugeKey(name), instrumentFactory.createGauge(name, valueCollector = valueCollector)) + register(GaugeKey(name, UnitOfMeasurement.Unknown), instrumentFactory.createGauge(name, valueCollector = valueCollector)) protected def gauge(name: String, dynamicRange: DynamicRange, valueCollector: CurrentValueCollector): Gauge = - register(GaugeKey(name), instrumentFactory.createGauge(name, Some(dynamicRange), valueCollector = valueCollector)) + register(GaugeKey(name, UnitOfMeasurement.Unknown), instrumentFactory.createGauge(name, Some(dynamicRange), valueCollector = valueCollector)) protected def gauge(name: String, refreshInterval: FiniteDuration, valueCollector: CurrentValueCollector): Gauge = - register(GaugeKey(name), instrumentFactory.createGauge(name, refreshInterval = Some(refreshInterval), valueCollector = valueCollector)) + register(GaugeKey(name, UnitOfMeasurement.Unknown), instrumentFactory.createGauge(name, refreshInterval = Some(refreshInterval), valueCollector = valueCollector)) protected def gauge(name: String, unitOfMeasurement: UnitOfMeasurement, valueCollector: CurrentValueCollector): Gauge = register(GaugeKey(name, unitOfMeasurement), instrumentFactory.createGauge(name, valueCollector = valueCollector)) protected def gauge(name: String, dynamicRange: DynamicRange, refreshInterval: FiniteDuration, valueCollector: CurrentValueCollector): Gauge = - register(GaugeKey(name), instrumentFactory.createGauge(name, Some(dynamicRange), Some(refreshInterval), valueCollector = valueCollector)) + register(GaugeKey(name, UnitOfMeasurement.Unknown), instrumentFactory.createGauge(name, Some(dynamicRange), Some(refreshInterval), valueCollector = valueCollector)) protected def gauge(name: String, dynamicRange: DynamicRange, unitOfMeasurement: UnitOfMeasurement, valueCollector: CurrentValueCollector): Gauge = register(GaugeKey(name, unitOfMeasurement), instrumentFactory.createGauge(name, Some(dynamicRange), valueCollector = valueCollector)) protected def gauge(name: String, refreshInterval: FiniteDuration, unitOfMeasurement: UnitOfMeasurement, valueCollector: CurrentValueCollector): Gauge = - register(GaugeKey(name), instrumentFactory.createGauge(name, refreshInterval = Some(refreshInterval), valueCollector = valueCollector)) + register(GaugeKey(name, UnitOfMeasurement.Unknown), instrumentFactory.createGauge(name, refreshInterval = Some(refreshInterval), valueCollector = valueCollector)) protected def gauge(name: String, dynamicRange: DynamicRange, refreshInterval: FiniteDuration, unitOfMeasurement: UnitOfMeasurement, valueCollector: CurrentValueCollector): Gauge = register(GaugeKey(name, unitOfMeasurement), instrumentFactory.createGauge(name, Some(dynamicRange), Some(refreshInterval), valueCollector)) @@ -143,19 +196,19 @@ abstract class GenericEntityRecorder(instrumentFactory: InstrumentFactory) exten register(key, instrumentFactory.createGauge(key.name, Some(dynamicRange), Some(refreshInterval), valueCollector = valueCollector)) protected def removeGauge(name: String): Unit = - _instruments.remove(GaugeKey(name)) + _instruments.remove(GaugeKey(name, UnitOfMeasurement.Unknown)) protected def removeGauge(key: GaugeKey): Unit = _instruments.remove(key) protected def counter(name: String): Counter = - register(CounterKey(name), instrumentFactory.createCounter()) + register(CounterKey(name, UnitOfMeasurement.Unknown), instrumentFactory.createCounter()) protected def counter(key: CounterKey): Counter = register(key, instrumentFactory.createCounter()) protected def removeCounter(name: String): Unit = - _instruments.remove(CounterKey(name)) + _instruments.remove(CounterKey(name, UnitOfMeasurement.Unknown)) protected def removeCounter(key: CounterKey): Unit = _instruments.remove(key) diff --git a/kamon-core/src/main/scala/kamon/metric/MetricKey.scala b/kamon-core/src/main/scala/kamon/metric/MetricKey.scala index a5d30c81..0d4e0163 100644 --- a/kamon-core/src/main/scala/kamon/metric/MetricKey.scala +++ b/kamon-core/src/main/scala/kamon/metric/MetricKey.scala @@ -16,154 +16,32 @@ package kamon.metric -import kamon.metric.instrument.{ InstrumentTypes, InstrumentType, UnitOfMeasurement } +import kamon.metric.instrument.UnitOfMeasurement /** - * MetricKeys are used to identify a given metric in entity recorders and snapshots. MetricKeys can be used to encode - * additional metadata for a metric being recorded, as well as the unit of measurement of the data being recorder. + * MetricKeys are used to identify a given metric in entity recorders and snapshots. */ sealed trait MetricKey { def name: String def unitOfMeasurement: UnitOfMeasurement - def instrumentType: InstrumentType - def metadata: Map[String, String] } -// Wish that there was a shorter way to describe the operations bellow, but apparently there is no way to generalize all -// the apply/create versions that would produce the desired return types when used from Java. - /** * MetricKey for all Histogram-based metrics. */ -case class HistogramKey(name: String, unitOfMeasurement: UnitOfMeasurement, metadata: Map[String, String]) extends MetricKey { - val instrumentType = InstrumentTypes.Histogram -} - -object HistogramKey { - def apply(name: String): HistogramKey = - apply(name, UnitOfMeasurement.Unknown) - - def apply(name: String, unitOfMeasurement: UnitOfMeasurement): HistogramKey = - apply(name, unitOfMeasurement, Map.empty) - - def apply(name: String, metadata: Map[String, String]): HistogramKey = - apply(name, UnitOfMeasurement.Unknown, Map.empty) - - /** - * Java friendly versions: - */ - - def create(name: String): HistogramKey = - apply(name, UnitOfMeasurement.Unknown) - - def create(name: String, unitOfMeasurement: UnitOfMeasurement): HistogramKey = - apply(name, unitOfMeasurement) - - def create(name: String, metadata: Map[String, String]): HistogramKey = - apply(name, metadata) - - def create(name: String, unitOfMeasurement: UnitOfMeasurement, metadata: Map[String, String]): HistogramKey = - apply(name, unitOfMeasurement, metadata) -} +private[kamon] case class HistogramKey(name: String, unitOfMeasurement: UnitOfMeasurement) extends MetricKey /** * MetricKey for all MinMaxCounter-based metrics. */ -case class MinMaxCounterKey(name: String, unitOfMeasurement: UnitOfMeasurement, metadata: Map[String, String]) extends MetricKey { - val instrumentType = InstrumentTypes.MinMaxCounter -} - -object MinMaxCounterKey { - def apply(name: String): MinMaxCounterKey = - apply(name, UnitOfMeasurement.Unknown) - - def apply(name: String, unitOfMeasurement: UnitOfMeasurement): MinMaxCounterKey = - apply(name, unitOfMeasurement, Map.empty) - - def apply(name: String, metadata: Map[String, String]): MinMaxCounterKey = - apply(name, UnitOfMeasurement.Unknown, Map.empty) - - /** - * Java friendly versions: - */ - - def create(name: String): MinMaxCounterKey = - apply(name, UnitOfMeasurement.Unknown) - - def create(name: String, unitOfMeasurement: UnitOfMeasurement): MinMaxCounterKey = - apply(name, unitOfMeasurement) - - def create(name: String, metadata: Map[String, String]): MinMaxCounterKey = - apply(name, metadata) - - def create(name: String, unitOfMeasurement: UnitOfMeasurement, metadata: Map[String, String]): MinMaxCounterKey = - apply(name, unitOfMeasurement, metadata) -} +private[kamon] case class MinMaxCounterKey(name: String, unitOfMeasurement: UnitOfMeasurement) extends MetricKey /** * MetricKey for all Gauge-based metrics. */ -case class GaugeKey(name: String, unitOfMeasurement: UnitOfMeasurement, metadata: Map[String, String]) extends MetricKey { - val instrumentType = InstrumentTypes.Gauge -} - -object GaugeKey { - def apply(name: String): GaugeKey = - apply(name, UnitOfMeasurement.Unknown) - - def apply(name: String, unitOfMeasurement: UnitOfMeasurement): GaugeKey = - apply(name, unitOfMeasurement, Map.empty) - - def apply(name: String, metadata: Map[String, String]): GaugeKey = - apply(name, UnitOfMeasurement.Unknown, Map.empty) - - /** - * Java friendly versions: - */ - - def create(name: String): GaugeKey = - apply(name, UnitOfMeasurement.Unknown) - - def create(name: String, unitOfMeasurement: UnitOfMeasurement): GaugeKey = - apply(name, unitOfMeasurement) - - def create(name: String, metadata: Map[String, String]): GaugeKey = - apply(name, metadata) - - def create(name: String, unitOfMeasurement: UnitOfMeasurement, metadata: Map[String, String]): GaugeKey = - apply(name, unitOfMeasurement, metadata) -} +private[kamon] case class GaugeKey(name: String, unitOfMeasurement: UnitOfMeasurement) extends MetricKey /** * MetricKey for all Counter-based metrics. */ -case class CounterKey(name: String, unitOfMeasurement: UnitOfMeasurement, metadata: Map[String, String]) extends MetricKey { - val instrumentType = InstrumentTypes.Counter -} - -object CounterKey { - def apply(name: String): CounterKey = - apply(name, UnitOfMeasurement.Unknown) - - def apply(name: String, unitOfMeasurement: UnitOfMeasurement): CounterKey = - apply(name, unitOfMeasurement, Map.empty) - - def apply(name: String, metadata: Map[String, String]): CounterKey = - apply(name, UnitOfMeasurement.Unknown, Map.empty) - - /** - * Java friendly versions: - */ - - def create(name: String): CounterKey = - apply(name, UnitOfMeasurement.Unknown) - - def create(name: String, unitOfMeasurement: UnitOfMeasurement): CounterKey = - apply(name, unitOfMeasurement) - - def create(name: String, metadata: Map[String, String]): CounterKey = - apply(name, metadata) - - def create(name: String, unitOfMeasurement: UnitOfMeasurement, metadata: Map[String, String]): CounterKey = - apply(name, unitOfMeasurement, metadata) -}
\ No newline at end of file +private[kamon] case class CounterKey(name: String, unitOfMeasurement: UnitOfMeasurement) extends MetricKey diff --git a/kamon-core/src/main/scala/kamon/metric/Metrics.scala b/kamon-core/src/main/scala/kamon/metric/Metrics.scala index d79b1de3..9fd9e771 100644 --- a/kamon-core/src/main/scala/kamon/metric/Metrics.scala +++ b/kamon-core/src/main/scala/kamon/metric/Metrics.scala @@ -16,46 +16,221 @@ package kamon.metric +import akka.actor._ import com.typesafe.config.Config -import kamon.metric.SubscriptionsDispatcher.{ Unsubscribe, Subscribe } -import kamon.metric.instrument.{ DefaultRefreshScheduler, InstrumentFactory, CollectionContext } +import kamon.metric.SubscriptionsDispatcher.{ Subscribe, Unsubscribe } +import kamon.metric.instrument.Gauge.CurrentValueCollector +import kamon.metric.instrument.Histogram.DynamicRange +import kamon.metric.instrument._ +import kamon.util.LazyActorRef import scala.collection.concurrent.TrieMap -import akka.actor._ -import kamon.util.{ LazyActorRef, TriemapAtomicGetOrElseUpdate } +import scala.concurrent.duration.FiniteDuration case class EntityRegistration[T <: EntityRecorder](entity: Entity, recorder: T) trait Metrics { def settings: MetricsSettings + def shouldTrack(entity: Entity): Boolean + def shouldTrack(entityName: String, category: String): Boolean = shouldTrack(Entity(entityName, category)) - def register[T <: EntityRecorder](recorderFactory: EntityRecorderFactory[T], entityName: String): Option[EntityRegistration[T]] - def register[T <: EntityRecorder](entity: Entity, recorder: T): EntityRegistration[T] - def unregister(entity: Entity): Unit + // + // Histograms registration and removal + // + + def histogram(name: String): Histogram = + registerHistogram(name) + + def histogram(name: String, unitOfMeasurement: UnitOfMeasurement): Histogram = + registerHistogram(name, unitOfMeasurement = Some(unitOfMeasurement)) + + def histogram(name: String, dynamicRange: DynamicRange): Histogram = + registerHistogram(name, dynamicRange = Some(dynamicRange)) + + def histogram(name: String, unitOfMeasurement: UnitOfMeasurement, dynamicRange: DynamicRange): Histogram = + registerHistogram(name, unitOfMeasurement = Some(unitOfMeasurement), dynamicRange = Some(dynamicRange)) + + def histogram(name: String, tags: Map[String, String]): Histogram = + registerHistogram(name, tags) + + def histogram(name: String, tags: Map[String, String], unitOfMeasurement: UnitOfMeasurement): Histogram = + registerHistogram(name, tags, Some(unitOfMeasurement)) + + def histogram(name: String, tags: Map[String, String], dynamicRange: DynamicRange): Histogram = + registerHistogram(name, tags, dynamicRange = Some(dynamicRange)) + + def histogram(name: String, tags: Map[String, String], unitOfMeasurement: UnitOfMeasurement, dynamicRange: DynamicRange): Histogram = + registerHistogram(name, tags, Some(unitOfMeasurement), Some(dynamicRange)) + + def removeHistogram(name: String): Boolean = + removeHistogram(name, Map.empty) + + def registerHistogram(name: String, tags: Map[String, String] = Map.empty, unitOfMeasurement: Option[UnitOfMeasurement] = None, + dynamicRange: Option[DynamicRange] = None): Histogram + + def removeHistogram(name: String, tags: Map[String, String]): Boolean + + // + // MinMaxCounter registration and removal + // + + def minMaxCounter(name: String): MinMaxCounter = + registerMinMaxCounter(name) + + def minMaxCounter(name: String, unitOfMeasurement: UnitOfMeasurement): MinMaxCounter = + registerMinMaxCounter(name, unitOfMeasurement = Some(unitOfMeasurement)) + + def minMaxCounter(name: String, dynamicRange: DynamicRange): MinMaxCounter = + registerMinMaxCounter(name, dynamicRange = Some(dynamicRange)) + + def minMaxCounter(name: String, refreshInterval: FiniteDuration): MinMaxCounter = + registerMinMaxCounter(name, refreshInterval = Some(refreshInterval)) + + def minMaxCounter(name: String, dynamicRange: DynamicRange, refreshInterval: FiniteDuration): MinMaxCounter = + registerMinMaxCounter(name, dynamicRange = Some(dynamicRange), refreshInterval = Some(refreshInterval)) + + def minMaxCounter(name: String, unitOfMeasurement: UnitOfMeasurement, dynamicRange: DynamicRange): MinMaxCounter = + registerMinMaxCounter(name, unitOfMeasurement = Some(unitOfMeasurement), dynamicRange = Some(dynamicRange)) + + def minMaxCounter(name: String, tags: Map[String, String]): MinMaxCounter = + registerMinMaxCounter(name, tags) + + def minMaxCounter(name: String, tags: Map[String, String], unitOfMeasurement: UnitOfMeasurement): MinMaxCounter = + registerMinMaxCounter(name, tags, Some(unitOfMeasurement)) + + def minMaxCounter(name: String, tags: Map[String, String], dynamicRange: DynamicRange): MinMaxCounter = + registerMinMaxCounter(name, tags, dynamicRange = Some(dynamicRange)) + + def minMaxCounter(name: String, tags: Map[String, String], unitOfMeasurement: UnitOfMeasurement, dynamicRange: DynamicRange): MinMaxCounter = + registerMinMaxCounter(name, tags, Some(unitOfMeasurement), Some(dynamicRange)) + + def removeMinMaxCounter(name: String): Boolean = + removeMinMaxCounter(name, Map.empty) + + def removeMinMaxCounter(name: String, tags: Map[String, String]): Boolean + + def registerMinMaxCounter(name: String, tags: Map[String, String] = Map.empty, unitOfMeasurement: Option[UnitOfMeasurement] = None, + dynamicRange: Option[DynamicRange] = None, refreshInterval: Option[FiniteDuration] = None): MinMaxCounter + + // + // Gauge registration and removal + // + + def gauge(name: String)(valueCollector: CurrentValueCollector): Gauge = + registerGauge(name, valueCollector) + + def gauge(name: String, unitOfMeasurement: UnitOfMeasurement)(valueCollector: CurrentValueCollector): Gauge = + registerGauge(name, valueCollector, unitOfMeasurement = Some(unitOfMeasurement)) + + def gauge(name: String, dynamicRange: DynamicRange)(valueCollector: CurrentValueCollector): Gauge = + registerGauge(name, valueCollector, dynamicRange = Some(dynamicRange)) + + def gauge(name: String, refreshInterval: FiniteDuration)(valueCollector: CurrentValueCollector): Gauge = + registerGauge(name, valueCollector, refreshInterval = Some(refreshInterval)) + + def gauge(name: String, dynamicRange: DynamicRange, refreshInterval: FiniteDuration)(valueCollector: CurrentValueCollector): Gauge = + registerGauge(name, valueCollector, dynamicRange = Some(dynamicRange), refreshInterval = Some(refreshInterval)) + + def gauge(name: String, unitOfMeasurement: UnitOfMeasurement, dynamicRange: DynamicRange)(valueCollector: CurrentValueCollector): Gauge = + registerGauge(name, valueCollector, unitOfMeasurement = Some(unitOfMeasurement), dynamicRange = Some(dynamicRange)) + + def gauge(name: String, tags: Map[String, String])(valueCollector: CurrentValueCollector): Gauge = + registerGauge(name, valueCollector, tags) + + def gauge(name: String, tags: Map[String, String], unitOfMeasurement: UnitOfMeasurement)(valueCollector: CurrentValueCollector): Gauge = + registerGauge(name, valueCollector, tags, Some(unitOfMeasurement)) + + def gauge(name: String, tags: Map[String, String], dynamicRange: DynamicRange)(valueCollector: CurrentValueCollector): Gauge = + registerGauge(name, valueCollector, tags, dynamicRange = Some(dynamicRange)) + + def gauge(name: String, tags: Map[String, String], unitOfMeasurement: UnitOfMeasurement, dynamicRange: DynamicRange)(valueCollector: CurrentValueCollector): Gauge = + registerGauge(name, valueCollector, tags, Some(unitOfMeasurement), Some(dynamicRange)) + + def removeGauge(name: String): Boolean = + removeGauge(name, Map.empty) + + def removeGauge(name: String, tags: Map[String, String]): Boolean + + def registerGauge(name: String, valueCollector: CurrentValueCollector, tags: Map[String, String] = Map.empty, + unitOfMeasurement: Option[UnitOfMeasurement] = None, dynamicRange: Option[DynamicRange] = None, + refreshInterval: Option[FiniteDuration] = None): Gauge + + // + // Counters registration and removal + // + + def counter(name: String): Counter = + registerCounter(name) + + def counter(name: String, unitOfMeasurement: UnitOfMeasurement): Counter = + registerCounter(name, unitOfMeasurement = Some(unitOfMeasurement)) + + def counter(name: String, tags: Map[String, String]): Counter = + registerCounter(name, tags) + + def counter(name: String, tags: Map[String, String], unitOfMeasurement: UnitOfMeasurement): Counter = + registerCounter(name, tags, Some(unitOfMeasurement)) + + def removeCounter(name: String): Boolean = + removeCounter(name, Map.empty) + + def removeCounter(name: String, tags: Map[String, String]): Boolean + + def registerCounter(name: String, tags: Map[String, String] = Map.empty, unitOfMeasurement: Option[UnitOfMeasurement] = None, + dynamicRange: Option[DynamicRange] = None): Counter + + // + // Entities registration and removal + // + + def entity[T <: EntityRecorder](recorderFactory: EntityRecorderFactory[T], name: String): T = + entity(recorderFactory, Entity(name, recorderFactory.category)) + + def entity[T <: EntityRecorder](recorderFactory: EntityRecorderFactory[T], name: String, tags: Map[String, String]): T = + entity(recorderFactory, Entity(name, recorderFactory.category, tags)) + + def removeEntity(name: String, category: String): Boolean = + removeEntity(Entity(name, category, Map.empty)) + + def removeEntity(name: String, category: String, tags: Map[String, String]): Boolean = + removeEntity(Entity(name, category, tags)) + + def removeEntity(entity: Entity): Boolean + + def entity[T <: EntityRecorder](recorderFactory: EntityRecorderFactory[T], entity: Entity): T + + def find(name: String, category: String): Option[EntityRecorder] = + find(Entity(name, category)) + + def find(name: String, category: String, tags: Map[String, String]): Option[EntityRecorder] = + find(Entity(name, category, tags)) def find(entity: Entity): Option[EntityRecorder] - def find(name: String, category: String): Option[EntityRecorder] def subscribe(filter: SubscriptionFilter, subscriber: ActorRef): Unit = - subscribe(filter, subscriber, permanently = false) + subscribe(filter, subscriber, permanently = true) def subscribe(category: String, selection: String, subscriber: ActorRef, permanently: Boolean): Unit = subscribe(SubscriptionFilter(category, selection), subscriber, permanently) def subscribe(category: String, selection: String, subscriber: ActorRef): Unit = - subscribe(SubscriptionFilter(category, selection), subscriber, permanently = false) + subscribe(SubscriptionFilter(category, selection), subscriber, permanently = true) def subscribe(filter: SubscriptionFilter, subscriber: ActorRef, permanently: Boolean): Unit def unsubscribe(subscriber: ActorRef): Unit + def buildDefaultCollectionContext: CollectionContext + def instrumentFactory(category: String): InstrumentFactory } private[kamon] class MetricsImpl(config: Config) extends Metrics { + import kamon.util.TriemapAtomicGetOrElseUpdate.Syntax + private val _trackedEntities = TrieMap.empty[Entity, EntityRecorder] private val _subscriptions = new LazyActorRef @@ -67,35 +242,83 @@ private[kamon] class MetricsImpl(config: Config) extends Metrics { } getOrElse (settings.trackUnmatchedEntities) - def register[T <: EntityRecorder](recorderFactory: EntityRecorderFactory[T], entityName: String): Option[EntityRegistration[T]] = { - import TriemapAtomicGetOrElseUpdate.Syntax - val entity = Entity(entityName, recorderFactory.category) + def registerHistogram(name: String, tags: Map[String, String], unitOfMeasurement: Option[UnitOfMeasurement], + dynamicRange: Option[DynamicRange]): Histogram = { - if (shouldTrack(entity)) { - val instrumentFactory = settings.instrumentFactories.get(recorderFactory.category).getOrElse(settings.defaultInstrumentFactory) - val recorder = _trackedEntities.atomicGetOrElseUpdate(entity, recorderFactory.createRecorder(instrumentFactory), _.cleanup).asInstanceOf[T] + val histogramEntity = Entity(name, SingleInstrumentEntityRecorder.Histogram, tags) + val recorder = _trackedEntities.atomicGetOrElseUpdate(histogramEntity, { + val factory = instrumentFactory(histogramEntity.category) + HistogramRecorder(HistogramKey(histogramEntity.category, unitOfMeasurement.getOrElse(UnitOfMeasurement.Unknown)), + factory.createHistogram(name, dynamicRange)) + }, _.cleanup) - Some(EntityRegistration(entity, recorder)) - } else None + recorder.asInstanceOf[HistogramRecorder].instrument } - def register[T <: EntityRecorder](entity: Entity, recorder: T): EntityRegistration[T] = { - _trackedEntities.put(entity, recorder).map { oldRecorder ⇒ - oldRecorder.cleanup - } + def removeHistogram(name: String, tags: Map[String, String]): Boolean = + _trackedEntities.remove(Entity(name, SingleInstrumentEntityRecorder.Histogram, tags)).isDefined - EntityRegistration(entity, recorder) + def registerMinMaxCounter(name: String, tags: Map[String, String], unitOfMeasurement: Option[UnitOfMeasurement], dynamicRange: Option[DynamicRange], + refreshInterval: Option[FiniteDuration]): MinMaxCounter = { + + val minMaxCounterEntity = Entity(name, SingleInstrumentEntityRecorder.MinMaxCounter, tags) + val recorder = _trackedEntities.atomicGetOrElseUpdate(minMaxCounterEntity, { + val factory = instrumentFactory(minMaxCounterEntity.category) + MinMaxCounterRecorder(MinMaxCounterKey(minMaxCounterEntity.category, unitOfMeasurement.getOrElse(UnitOfMeasurement.Unknown)), + factory.createMinMaxCounter(name, dynamicRange, refreshInterval)) + }, _.cleanup) + + recorder.asInstanceOf[MinMaxCounterRecorder].instrument } - def unregister(entity: Entity): Unit = - _trackedEntities.remove(entity).map(_.cleanup) + def removeMinMaxCounter(name: String, tags: Map[String, String]): Boolean = + _trackedEntities.remove(Entity(name, SingleInstrumentEntityRecorder.MinMaxCounter, tags)).isDefined + + def registerGauge(name: String, valueCollector: CurrentValueCollector, tags: Map[String, String] = Map.empty, + unitOfMeasurement: Option[UnitOfMeasurement] = None, dynamicRange: Option[DynamicRange] = None, + refreshInterval: Option[FiniteDuration] = None): Gauge = { + + val gaugeEntity = Entity(name, SingleInstrumentEntityRecorder.Gauge, tags) + val recorder = _trackedEntities.atomicGetOrElseUpdate(gaugeEntity, { + val factory = instrumentFactory(gaugeEntity.category) + GaugeRecorder(MinMaxCounterKey(gaugeEntity.category, unitOfMeasurement.getOrElse(UnitOfMeasurement.Unknown)), + factory.createGauge(name, dynamicRange, refreshInterval, valueCollector)) + }, _.cleanup) + + recorder.asInstanceOf[GaugeRecorder].instrument + } + + def removeGauge(name: String, tags: Map[String, String]): Boolean = + _trackedEntities.remove(Entity(name, SingleInstrumentEntityRecorder.Gauge, tags)).isDefined + + def registerCounter(name: String, tags: Map[String, String] = Map.empty, unitOfMeasurement: Option[UnitOfMeasurement] = None, + dynamicRange: Option[DynamicRange] = None): Counter = { + + val counterEntity = Entity(name, SingleInstrumentEntityRecorder.Counter, tags) + val recorder = _trackedEntities.atomicGetOrElseUpdate(counterEntity, { + val factory = instrumentFactory(counterEntity.category) + CounterRecorder(CounterKey(counterEntity.category, unitOfMeasurement.getOrElse(UnitOfMeasurement.Unknown)), + factory.createCounter()) + }, _.cleanup) + + recorder.asInstanceOf[CounterRecorder].instrument + } + + def removeCounter(name: String, tags: Map[String, String]): Boolean = + _trackedEntities.remove(Entity(name, SingleInstrumentEntityRecorder.Counter, tags)).isDefined + + def entity[T <: EntityRecorder](recorderFactory: EntityRecorderFactory[T], entity: Entity): T = { + _trackedEntities.atomicGetOrElseUpdate(entity, { + recorderFactory.createRecorder(instrumentFactory(recorderFactory.category)) + }, _.cleanup).asInstanceOf[T] + } + + def removeEntity(entity: Entity): Boolean = + _trackedEntities.remove(entity).isDefined def find(entity: Entity): Option[EntityRecorder] = _trackedEntities.get(entity) - def find(name: String, category: String): Option[EntityRecorder] = - find(Entity(name, category)) - def subscribe(filter: SubscriptionFilter, subscriber: ActorRef, permanent: Boolean): Unit = _subscriptions.tell(Subscribe(filter, subscriber, permanent)) diff --git a/kamon-core/src/main/scala/kamon/metric/SimpleMetrics.scala b/kamon-core/src/main/scala/kamon/metric/SimpleMetrics.scala deleted file mode 100644 index 6324c320..00000000 --- a/kamon-core/src/main/scala/kamon/metric/SimpleMetrics.scala +++ /dev/null @@ -1,204 +0,0 @@ -/* - * ========================================================================================= - * Copyright © 2013-2015 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.metric - -import kamon.metric.instrument.Gauge.CurrentValueCollector -import kamon.metric.instrument.Histogram.DynamicRange -import kamon.metric.instrument._ - -import scala.concurrent.duration.FiniteDuration - -trait SimpleMetrics { - def histogram(name: String): Histogram - def histogram(name: String, dynamicRange: DynamicRange): Histogram - def histogram(name: String, unitOfMeasurement: UnitOfMeasurement): Histogram - def histogram(name: String, dynamicRange: DynamicRange, unitOfMeasurement: UnitOfMeasurement): Histogram - def histogram(key: HistogramKey): Histogram - def histogram(key: HistogramKey, dynamicRange: DynamicRange): Histogram - def removeHistogram(name: String): Unit - def removeHistogram(key: HistogramKey): Unit - - def minMaxCounter(name: String): MinMaxCounter - def minMaxCounter(name: String, dynamicRange: DynamicRange): MinMaxCounter - def minMaxCounter(name: String, refreshInterval: FiniteDuration): MinMaxCounter - def minMaxCounter(name: String, unitOfMeasurement: UnitOfMeasurement): MinMaxCounter - def minMaxCounter(name: String, dynamicRange: DynamicRange, refreshInterval: FiniteDuration): MinMaxCounter - def minMaxCounter(name: String, dynamicRange: DynamicRange, unitOfMeasurement: UnitOfMeasurement): MinMaxCounter - def minMaxCounter(name: String, refreshInterval: FiniteDuration, unitOfMeasurement: UnitOfMeasurement): MinMaxCounter - def minMaxCounter(name: String, dynamicRange: DynamicRange, refreshInterval: FiniteDuration, unitOfMeasurement: UnitOfMeasurement): MinMaxCounter - def minMaxCounter(key: MinMaxCounterKey): MinMaxCounter - def minMaxCounter(key: MinMaxCounterKey, dynamicRange: DynamicRange): MinMaxCounter - def minMaxCounter(key: MinMaxCounterKey, refreshInterval: FiniteDuration): MinMaxCounter - def minMaxCounter(key: MinMaxCounterKey, dynamicRange: DynamicRange, refreshInterval: FiniteDuration): MinMaxCounter - def removeMinMaxCounter(name: String): Unit - def removeMinMaxCounter(key: MinMaxCounterKey): Unit - - def gauge(name: String, valueCollector: CurrentValueCollector): Gauge - def gauge(name: String, dynamicRange: DynamicRange, valueCollector: CurrentValueCollector): Gauge - def gauge(name: String, refreshInterval: FiniteDuration, valueCollector: CurrentValueCollector): Gauge - def gauge(name: String, unitOfMeasurement: UnitOfMeasurement, valueCollector: CurrentValueCollector): Gauge - def gauge(name: String, dynamicRange: DynamicRange, refreshInterval: FiniteDuration, valueCollector: CurrentValueCollector): Gauge - def gauge(name: String, dynamicRange: DynamicRange, unitOfMeasurement: UnitOfMeasurement, valueCollector: CurrentValueCollector): Gauge - def gauge(name: String, refreshInterval: FiniteDuration, unitOfMeasurement: UnitOfMeasurement, valueCollector: CurrentValueCollector): Gauge - def gauge(name: String, dynamicRange: DynamicRange, refreshInterval: FiniteDuration, unitOfMeasurement: UnitOfMeasurement, valueCollector: CurrentValueCollector): Gauge - def gauge(key: GaugeKey, valueCollector: CurrentValueCollector): Gauge - def gauge(key: GaugeKey, dynamicRange: DynamicRange, valueCollector: CurrentValueCollector): Gauge - def gauge(key: GaugeKey, refreshInterval: FiniteDuration, valueCollector: CurrentValueCollector): Gauge - def gauge(key: GaugeKey, dynamicRange: DynamicRange, refreshInterval: FiniteDuration, valueCollector: CurrentValueCollector): Gauge - def removeGauge(name: String): Unit - def removeGauge(key: GaugeKey): Unit - - def counter(name: String): Counter - def counter(key: CounterKey): Counter - def removeCounter(name: String): Unit - def removeCounter(key: CounterKey): Unit - -} - -private[kamon] class SimpleMetricsImpl(instrumentFactory: InstrumentFactory) extends GenericEntityRecorder(instrumentFactory) with SimpleMetrics { - override def histogram(name: String): Histogram = - super.histogram(name) - - override def histogram(name: String, dynamicRange: DynamicRange): Histogram = - super.histogram(name, dynamicRange) - - override def histogram(name: String, unitOfMeasurement: UnitOfMeasurement): Histogram = - super.histogram(name, unitOfMeasurement) - - override def histogram(name: String, dynamicRange: DynamicRange, unitOfMeasurement: UnitOfMeasurement): Histogram = - super.histogram(name, dynamicRange, unitOfMeasurement) - - override def histogram(key: HistogramKey): Histogram = - super.histogram(key) - - override def histogram(key: HistogramKey, dynamicRange: DynamicRange): Histogram = - super.histogram(key, dynamicRange) - - override def removeHistogram(name: String): Unit = - super.removeHistogram(name) - - override def removeHistogram(key: HistogramKey): Unit = - super.removeHistogram(key) - - override def minMaxCounter(name: String): MinMaxCounter = - super.minMaxCounter(name) - - override def minMaxCounter(name: String, dynamicRange: DynamicRange): MinMaxCounter = - super.minMaxCounter(name, dynamicRange) - - override def minMaxCounter(name: String, refreshInterval: FiniteDuration): MinMaxCounter = - super.minMaxCounter(name, refreshInterval) - - override def minMaxCounter(name: String, unitOfMeasurement: UnitOfMeasurement): MinMaxCounter = - super.minMaxCounter(name, unitOfMeasurement) - - override def minMaxCounter(name: String, dynamicRange: DynamicRange, refreshInterval: FiniteDuration): MinMaxCounter = - super.minMaxCounter(name, dynamicRange, refreshInterval) - - override def minMaxCounter(name: String, dynamicRange: DynamicRange, unitOfMeasurement: UnitOfMeasurement): MinMaxCounter = - super.minMaxCounter(name, dynamicRange, unitOfMeasurement) - - override def minMaxCounter(name: String, refreshInterval: FiniteDuration, unitOfMeasurement: UnitOfMeasurement): MinMaxCounter = - super.minMaxCounter(name, refreshInterval, unitOfMeasurement) - - override def minMaxCounter(name: String, dynamicRange: DynamicRange, refreshInterval: FiniteDuration, unitOfMeasurement: UnitOfMeasurement): MinMaxCounter = - super.minMaxCounter(name, dynamicRange, refreshInterval, unitOfMeasurement) - - override def minMaxCounter(key: MinMaxCounterKey): MinMaxCounter = - super.minMaxCounter(key) - - override def minMaxCounter(key: MinMaxCounterKey, dynamicRange: DynamicRange): MinMaxCounter = - super.minMaxCounter(key, dynamicRange) - - override def minMaxCounter(key: MinMaxCounterKey, refreshInterval: FiniteDuration): MinMaxCounter = - super.minMaxCounter(key, refreshInterval) - - override def minMaxCounter(key: MinMaxCounterKey, dynamicRange: DynamicRange, refreshInterval: FiniteDuration): MinMaxCounter = - super.minMaxCounter(key, dynamicRange, refreshInterval) - - override def removeMinMaxCounter(name: String): Unit = - super.removeMinMaxCounter(name) - - override def removeMinMaxCounter(key: MinMaxCounterKey): Unit = - super.removeMinMaxCounter(key) - - override def gauge(name: String, valueCollector: CurrentValueCollector): Gauge = - super.gauge(name, valueCollector) - - override def gauge(name: String, dynamicRange: DynamicRange, valueCollector: CurrentValueCollector): Gauge = - super.gauge(name, dynamicRange, valueCollector) - - override def gauge(name: String, refreshInterval: FiniteDuration, valueCollector: CurrentValueCollector): Gauge = - super.gauge(name, refreshInterval, valueCollector) - - override def gauge(name: String, unitOfMeasurement: UnitOfMeasurement, valueCollector: CurrentValueCollector): Gauge = - super.gauge(name, unitOfMeasurement, valueCollector) - - override def gauge(name: String, dynamicRange: DynamicRange, refreshInterval: FiniteDuration, valueCollector: CurrentValueCollector): Gauge = - super.gauge(name, dynamicRange, refreshInterval, valueCollector) - - override def gauge(name: String, dynamicRange: DynamicRange, unitOfMeasurement: UnitOfMeasurement, valueCollector: CurrentValueCollector): Gauge = - super.gauge(name, dynamicRange, unitOfMeasurement, valueCollector) - - override def gauge(name: String, refreshInterval: FiniteDuration, unitOfMeasurement: UnitOfMeasurement, valueCollector: CurrentValueCollector): Gauge = - super.gauge(name, refreshInterval, unitOfMeasurement, valueCollector) - - override def gauge(name: String, dynamicRange: DynamicRange, refreshInterval: FiniteDuration, unitOfMeasurement: UnitOfMeasurement, valueCollector: CurrentValueCollector): Gauge = - super.gauge(name, dynamicRange, refreshInterval, unitOfMeasurement, valueCollector) - - override def gauge(key: GaugeKey, valueCollector: CurrentValueCollector): Gauge = - super.gauge(key, valueCollector) - - override def gauge(key: GaugeKey, dynamicRange: DynamicRange, valueCollector: CurrentValueCollector): Gauge = - super.gauge(key, dynamicRange, valueCollector) - - override def gauge(key: GaugeKey, refreshInterval: FiniteDuration, valueCollector: CurrentValueCollector): Gauge = - super.gauge(key, refreshInterval, valueCollector) - - override def gauge(key: GaugeKey, dynamicRange: DynamicRange, refreshInterval: FiniteDuration, valueCollector: CurrentValueCollector): Gauge = - super.gauge(key, dynamicRange, refreshInterval, valueCollector) - - override def removeGauge(name: String): Unit = - super.removeGauge(name) - - override def removeGauge(key: GaugeKey): Unit = - super.removeGauge(key) - - override def counter(name: String): Counter = - super.counter(name) - - override def counter(key: CounterKey): Counter = - super.counter(key) - - override def removeCounter(name: String): Unit = - super.removeCounter(name) - - override def removeCounter(key: CounterKey): Unit = - super.removeCounter(key) -} - -private[kamon] object SimpleMetricsImpl { - val SimpleMetricsEntity = Entity("simple-metric", "simple-metric") - - def apply(metricsExtension: Metrics): SimpleMetricsImpl = { - val instrumentFactory = metricsExtension.instrumentFactory(SimpleMetricsEntity.category) - val simpleMetricsExtension = new SimpleMetricsImpl(instrumentFactory) - - metricsExtension.register(SimpleMetricsEntity, simpleMetricsExtension).recorder - } - -}
\ No newline at end of file diff --git a/kamon-core/src/main/scala/kamon/metric/TraceMetrics.scala b/kamon-core/src/main/scala/kamon/metric/TraceMetrics.scala index 3da9c1d4..eb4f327a 100644 --- a/kamon-core/src/main/scala/kamon/metric/TraceMetrics.scala +++ b/kamon-core/src/main/scala/kamon/metric/TraceMetrics.scala @@ -16,29 +16,36 @@ package kamon.metric -import kamon.metric.instrument.{ Time, InstrumentFactory, Histogram } +import kamon.metric.instrument.{ Time, InstrumentFactory } class TraceMetrics(instrumentFactory: InstrumentFactory) extends GenericEntityRecorder(instrumentFactory) { - import TraceMetrics.segmentKey /** * Records blah blah */ - val ElapsedTime = histogram("elapsed-time", unitOfMeasurement = Time.Nanoseconds) - - /** - * Records Blah Blah. - * - */ - def segment(name: String, category: String, library: String): Histogram = - histogram(segmentKey(name, category, library)) - + val elapsedTime = histogram("elapsed-time", unitOfMeasurement = Time.Nanoseconds) } object TraceMetrics extends EntityRecorderFactory[TraceMetrics] { def category: String = "trace" def createRecorder(instrumentFactory: InstrumentFactory): TraceMetrics = new TraceMetrics(instrumentFactory) - def segmentKey(name: String, category: String, library: String): HistogramKey = - HistogramKey(name, Time.Nanoseconds, Map("category" -> category, "library" -> library)) + // Java API. + def factory: EntityRecorderFactory[TraceMetrics] = this +} + +class SegmentMetrics(instrumentFactory: InstrumentFactory) extends GenericEntityRecorder(instrumentFactory) { + + /** + * Records blah blah + */ + val elapsedTime = histogram("elapsed-time", unitOfMeasurement = Time.Nanoseconds) +} + +object SegmentMetrics extends EntityRecorderFactory[SegmentMetrics] { + def category: String = "trace-segment" + def createRecorder(instrumentFactory: InstrumentFactory): SegmentMetrics = new SegmentMetrics(instrumentFactory) + + // Java API. + def factory: EntityRecorderFactory[SegmentMetrics] = this }
\ No newline at end of file diff --git a/kamon-core/src/main/scala/kamon/metric/instrument/Gauge.scala b/kamon-core/src/main/scala/kamon/metric/instrument/Gauge.scala index 80214510..61b53df2 100644 --- a/kamon-core/src/main/scala/kamon/metric/instrument/Gauge.scala +++ b/kamon-core/src/main/scala/kamon/metric/instrument/Gauge.scala @@ -55,6 +55,10 @@ object Gauge { implicit def functionZeroAsCurrentValueCollector(f: () ⇒ Long): CurrentValueCollector = new CurrentValueCollector { def currentValue: Long = f.apply() } + + implicit def callByNameLongAsCurrentValueCollector(f: ⇒ Long): CurrentValueCollector = new CurrentValueCollector { + def currentValue: Long = f + } } /** diff --git a/kamon-core/src/main/scala/kamon/metric/instrument/Instrument.scala b/kamon-core/src/main/scala/kamon/metric/instrument/Instrument.scala index 59b4b443..089dbeec 100644 --- a/kamon-core/src/main/scala/kamon/metric/instrument/Instrument.scala +++ b/kamon-core/src/main/scala/kamon/metric/instrument/Instrument.scala @@ -33,14 +33,6 @@ trait InstrumentSnapshot { def merge(that: InstrumentSnapshot, context: CollectionContext): InstrumentSnapshot } -class InstrumentType private[kamon] (val id: Int) extends AnyVal -object InstrumentTypes { - val Histogram = new InstrumentType(1) - val MinMaxCounter = new InstrumentType(2) - val Gauge = new InstrumentType(3) - val Counter = new InstrumentType(4) -} - trait CollectionContext { def buffer: LongBuffer } @@ -51,3 +43,11 @@ object CollectionContext { } } +sealed trait InstrumentType + +object InstrumentTypes { + case object Histogram extends InstrumentType + case object MinMaxCounter extends InstrumentType + case object Gauge extends InstrumentType + case object Counter extends InstrumentType +} diff --git a/kamon-core/src/main/scala/kamon/metric/instrument/RefreshScheduler.scala b/kamon-core/src/main/scala/kamon/metric/instrument/RefreshScheduler.scala index adb08713..4809ac0d 100644 --- a/kamon-core/src/main/scala/kamon/metric/instrument/RefreshScheduler.scala +++ b/kamon-core/src/main/scala/kamon/metric/instrument/RefreshScheduler.scala @@ -1,3 +1,19 @@ +/* + * ========================================================================================= + * Copyright © 2013-2015 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.metric.instrument import akka.actor.{ Scheduler, Cancellable } diff --git a/kamon-core/src/main/scala/kamon/metric/instrument/UnitOfMeasurement.scala b/kamon-core/src/main/scala/kamon/metric/instrument/UnitOfMeasurement.scala index f2a061d1..c5a1b81a 100644 --- a/kamon-core/src/main/scala/kamon/metric/instrument/UnitOfMeasurement.scala +++ b/kamon-core/src/main/scala/kamon/metric/instrument/UnitOfMeasurement.scala @@ -16,17 +16,20 @@ package kamon.metric.instrument +/** + * A UnitOfMeasurement implementation describes the magnitude of a quantity being measured, such as Time and computer + * Memory space. Kamon uses UnitOfMeasurement implementations just as a informative companion to metrics inside entity + * recorders and might be used to scale certain kinds of measurements in metric backends. + */ trait UnitOfMeasurement { def name: String def label: String - def factor: Double } object UnitOfMeasurement { case object Unknown extends UnitOfMeasurement { val name = "unknown" val label = "unknown" - val factor = 1D } def isUnknown(uom: UnitOfMeasurement): Boolean = @@ -35,19 +38,18 @@ object UnitOfMeasurement { def isTime(uom: UnitOfMeasurement): Boolean = uom.isInstanceOf[Time] + def isMemory(uom: UnitOfMeasurement): Boolean = + uom.isInstanceOf[Memory] + } +/** + * UnitOfMeasurement representing time. + */ case class Time(factor: Double, label: String) extends UnitOfMeasurement { val name = "time" - /** - * Scale a value from this scale factor to a different scale factor. - * - * @param toUnit Time unit of the expected result. - * @param value Value to scale. - * @return Equivalent of value on the target time unit. - */ - def scale(toUnit: Time)(value: Long): Double = + def scale(toUnit: Time)(value: Double): Double = (value * factor) / toUnit.factor } @@ -58,8 +60,14 @@ object Time { val Seconds = Time(1, "s") } +/** + * UnitOfMeasurement representing computer memory space. + */ case class Memory(factor: Double, label: String) extends UnitOfMeasurement { val name = "bytes" + + def scale(toUnit: Memory)(value: Double): Double = + (value * factor) / toUnit.factor } object Memory { diff --git a/kamon-core/src/main/scala/kamon/supervisor/ModuleSupervisorExtension.scala b/kamon-core/src/main/scala/kamon/supervisor/ModuleSupervisorExtension.scala deleted file mode 100644 index ddce63fb..00000000 --- a/kamon-core/src/main/scala/kamon/supervisor/ModuleSupervisorExtension.scala +++ /dev/null @@ -1,125 +0,0 @@ -/* - * ========================================================================================= - * Copyright © 2013-2015 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.supervisor - -import akka.actor -import akka.actor._ -import kamon.Kamon -import kamon.supervisor.KamonSupervisor.CreateModule - -import scala.concurrent.{ Promise, Future } -import scala.util.Success - -object ModuleSupervisor extends ExtensionId[ModuleSupervisorExtension] with ExtensionIdProvider { - def lookup(): ExtensionId[_ <: actor.Extension] = ModuleSupervisor - def createExtension(system: ExtendedActorSystem): ModuleSupervisorExtension = new ModuleSupervisorExtensionImpl(system) -} - -trait ModuleSupervisorExtension extends actor.Extension { - def createModule(name: String, props: Props): Future[ActorRef] -} - -class ModuleSupervisorExtensionImpl(system: ExtendedActorSystem) extends ModuleSupervisorExtension { - import system.dispatcher - - private val _settings = ModuleSupervisorSettings(system) - private val _supervisor = system.actorOf(KamonSupervisor.props(_settings, system.dynamicAccess), "kamon") - - def createModule(name: String, props: Props): Future[ActorRef] = Future {} flatMap { _: Unit ⇒ - val modulePromise = Promise[ActorRef]() - _supervisor ! CreateModule(name, props, modulePromise) - modulePromise.future - } -} - -class KamonSupervisor(settings: ModuleSupervisorSettings, dynamicAccess: DynamicAccess) extends Actor with ActorLogging { - - init() - - def receive = { - case CreateModule(name, props, childPromise) ⇒ createChildModule(name, props, childPromise) - } - - def createChildModule(name: String, props: Props, childPromise: Promise[ActorRef]): Unit = - context.child(name).map { alreadyAvailableModule ⇒ - log.warning("Received a request to create module [{}] but the module is already available, returning the existent instance.") - childPromise.complete(Success(alreadyAvailableModule)) - - } getOrElse (childPromise.complete(Success(context.actorOf(props, name)))) - - def init(): Unit = { - if (settings.modulesRequiringAspectJ.nonEmpty && !isAspectJPresent && settings.showAspectJMissingWarning) - logAspectJWeaverMissing(settings.modulesRequiringAspectJ) - - // Force initialization of all modules marked with auto-start. - settings.availableModules.filter(_.autoStart).foreach { module ⇒ - if (module.extensionClass == "none") - log.debug("Ignoring auto start of the [{}] module with no extension class.") - else - dynamicAccess.getObjectFor[ExtensionId[Kamon.Extension]](module.extensionClass).map { moduleID ⇒ - moduleID.get(context.system) - log.debug("Auto starting the [{}] module.", module.name) - - } recover { - case th: Throwable ⇒ log.error(th, "Failed to auto start the [{}] module.", module.name) - } - - } - } - - // When AspectJ is present the kamon.supervisor.AspectJPresent aspect will make this return true. - def isAspectJPresent: Boolean = false - - def logAspectJWeaverMissing(modulesRequiringAspectJ: List[AvailableModuleInfo]): Unit = { - val moduleNames = modulesRequiringAspectJ.map(_.name).mkString(", ") - val weaverMissingMessage = - """ - | - | ___ _ ___ _ _ ___ ___ _ _ - | / _ \ | | |_ | | | | | | \/ |(_) (_) - |/ /_\ \ ___ _ __ ___ ___ | |_ | | | | | | ___ __ _ __ __ ___ _ __ | . . | _ ___ ___ _ _ __ __ _ - || _ |/ __|| '_ \ / _ \ / __|| __| | | | |/\| | / _ \ / _` |\ \ / // _ \| '__| | |\/| || |/ __|/ __|| || '_ \ / _` | - || | | |\__ \| |_) || __/| (__ | |_ /\__/ / \ /\ /| __/| (_| | \ V /| __/| | | | | || |\__ \\__ \| || | | || (_| | - |\_| |_/|___/| .__/ \___| \___| \__|\____/ \/ \/ \___| \__,_| \_/ \___||_| \_| |_/|_||___/|___/|_||_| |_| \__, | - | | | __/ | - | |_| |___/ - | - | It seems like your application was not started with the -javaagent:/path-to-aspectj-weaver.jar option but Kamon detected - | the following modules which require AspecJ to work properly: - | - """.stripMargin + moduleNames + - """ - | - | If you need help on setting up the aspectj weaver go to http://kamon.io/introduction/get-started/ for more info. On the - | other hand, if you are sure that you do not need or do not want to use the weaver then you can disable this error message - | by changing the kamon.show-aspectj-missing-warning setting in your configuration file. - | - """.stripMargin - - log.error(weaverMissingMessage) - } - -} - -object KamonSupervisor { - case class CreateModule(name: String, props: Props, childPromise: Promise[ActorRef]) - - def props(settings: ModuleSupervisorSettings, dynamicAccess: DynamicAccess): Props = - Props(new KamonSupervisor(settings, dynamicAccess)) - -} - diff --git a/kamon-core/src/main/scala/kamon/supervisor/ModuleSupervisorSettings.scala b/kamon-core/src/main/scala/kamon/supervisor/ModuleSupervisorSettings.scala deleted file mode 100644 index c04157aa..00000000 --- a/kamon-core/src/main/scala/kamon/supervisor/ModuleSupervisorSettings.scala +++ /dev/null @@ -1,49 +0,0 @@ -/* - * ========================================================================================= - * Copyright © 2013-2015 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.supervisor - -import akka.actor.ActorSystem - -case class AvailableModuleInfo(name: String, extensionClass: String, requiresAspectJ: Boolean, autoStart: Boolean) -case class ModuleSupervisorSettings(showAspectJMissingWarning: Boolean, availableModules: List[AvailableModuleInfo]) { - val modulesRequiringAspectJ = availableModules.filter(_.requiresAspectJ) -} - -object ModuleSupervisorSettings { - - def apply(system: ActorSystem): ModuleSupervisorSettings = { - import kamon.util.ConfigTools.Syntax - - val config = system.settings.config.getConfig("kamon.modules") - val showAspectJMissingWarning = system.settings.config.getBoolean("kamon.show-aspectj-missing-warning") - - val modules = config.firstLevelKeys - val availableModules = modules.map { moduleName ⇒ - val moduleConfig = config.getConfig(moduleName) - - AvailableModuleInfo( - moduleName, - moduleConfig.getString("extension-id"), - moduleConfig.getBoolean("requires-aspectj"), - moduleConfig.getBoolean("auto-start")) - - } toList - - ModuleSupervisorSettings(showAspectJMissingWarning, availableModules) - } - -} diff --git a/kamon-core/src/main/scala/kamon/trace/MetricsOnlyContext.scala b/kamon-core/src/main/scala/kamon/trace/MetricsOnlyContext.scala index eb4bad6f..0f09b4be 100644 --- a/kamon-core/src/main/scala/kamon/trace/MetricsOnlyContext.scala +++ b/kamon-core/src/main/scala/kamon/trace/MetricsOnlyContext.scala @@ -19,13 +19,14 @@ package kamon.trace import java.util.concurrent.ConcurrentLinkedQueue import akka.event.LoggingAdapter -import kamon.metric.{ Metrics, TraceMetrics } +import kamon.Kamon +import kamon.metric.{ SegmentMetrics, Metrics, TraceMetrics } import kamon.util.{ NanoInterval, RelativeNanoTimestamp } import scala.annotation.tailrec private[kamon] class MetricsOnlyContext(traceName: String, val token: String, izOpen: Boolean, val levelOfDetail: LevelOfDetail, - val startTimestamp: RelativeNanoTimestamp, log: LoggingAdapter, metricsExtension: Metrics) + val startTimestamp: RelativeNanoTimestamp, log: LoggingAdapter) extends TraceContext { @volatile private var _name = traceName @@ -51,20 +52,23 @@ private[kamon] class MetricsOnlyContext(traceName: String, val token: String, iz val traceElapsedTime = NanoInterval.since(startTimestamp) _elapsedTime = traceElapsedTime - metricsExtension.register(TraceMetrics, name).map { registration ⇒ - registration.recorder.ElapsedTime.record(traceElapsedTime.nanos) - drainFinishedSegments(registration.recorder) - } + Kamon.metrics.entity(TraceMetrics, name).elapsedTime.record(traceElapsedTime.nanos) + drainFinishedSegments() } def startSegment(segmentName: String, category: String, library: String): Segment = new MetricsOnlySegment(segmentName, category, library) - @tailrec private def drainFinishedSegments(recorder: TraceMetrics): Unit = { + @tailrec private def drainFinishedSegments(): Unit = { val segment = _finishedSegments.poll() if (segment != null) { - recorder.segment(segment.name, segment.category, segment.library).record(segment.duration.nanos) - drainFinishedSegments(recorder) + val segmentTags = Map( + "trace" -> name, + "category" -> segment.category, + "library" -> segment.library) + + Kamon.metrics.entity(SegmentMetrics, segment.name, segmentTags).elapsedTime.record(segment.duration.nanos) + drainFinishedSegments() } } @@ -72,9 +76,7 @@ private[kamon] class MetricsOnlyContext(traceName: String, val token: String, iz _finishedSegments.add(SegmentLatencyData(segmentName, category, library, duration)) if (isClosed) { - metricsExtension.register(TraceMetrics, name).map { registration ⇒ - drainFinishedSegments(registration.recorder) - } + drainFinishedSegments() } } diff --git a/kamon-core/src/main/scala/kamon/trace/Tracer.scala b/kamon-core/src/main/scala/kamon/trace/Tracer.scala index cf83b36b..472c1d65 100644 --- a/kamon-core/src/main/scala/kamon/trace/Tracer.scala +++ b/kamon-core/src/main/scala/kamon/trace/Tracer.scala @@ -80,7 +80,7 @@ private[kamon] class TracerImpl(metricsExtension: Metrics, config: Config) exten private def createTraceContext(traceName: String, token: String = newToken, startTimestamp: RelativeNanoTimestamp = RelativeNanoTimestamp.now, isOpen: Boolean = true, isLocal: Boolean = true): TraceContext = { - def newMetricsOnlyContext = new MetricsOnlyContext(traceName, token, isOpen, _settings.levelOfDetail, startTimestamp, null, metricsExtension) + def newMetricsOnlyContext = new MetricsOnlyContext(traceName, token, isOpen, _settings.levelOfDetail, startTimestamp, null) if (_settings.levelOfDetail == LevelOfDetail.MetricsOnly || !isLocal) newMetricsOnlyContext @@ -88,7 +88,7 @@ private[kamon] class TracerImpl(metricsExtension: Metrics, config: Config) exten if (!_settings.sampler.shouldTrace) newMetricsOnlyContext else - new TracingContext(traceName, token, true, _settings.levelOfDetail, isLocal, startTimestamp, null, metricsExtension, this, dispatchTracingContext) + new TracingContext(traceName, token, true, _settings.levelOfDetail, isLocal, startTimestamp, null, dispatchTracingContext) } } diff --git a/kamon-core/src/main/scala/kamon/trace/TracingContext.scala b/kamon-core/src/main/scala/kamon/trace/TracingContext.scala index d34526f7..9708d25f 100644 --- a/kamon-core/src/main/scala/kamon/trace/TracingContext.scala +++ b/kamon-core/src/main/scala/kamon/trace/TracingContext.scala @@ -26,9 +26,8 @@ import kamon.metric.Metrics import scala.collection.concurrent.TrieMap private[trace] class TracingContext(traceName: String, token: String, izOpen: Boolean, levelOfDetail: LevelOfDetail, - isLocal: Boolean, startTimeztamp: RelativeNanoTimestamp, log: LoggingAdapter, metricsExtension: Metrics, - traceExtension: TracerImpl, traceInfoSink: TracingContext ⇒ Unit) - extends MetricsOnlyContext(traceName, token, izOpen, levelOfDetail, startTimeztamp, log, metricsExtension) { + isLocal: Boolean, startTimeztamp: RelativeNanoTimestamp, log: LoggingAdapter, traceInfoSink: TracingContext ⇒ Unit) + extends MetricsOnlyContext(traceName, token, izOpen, levelOfDetail, startTimeztamp, log) { private val _openSegments = new AtomicInteger(0) private val _startTimestamp = NanoTimestamp.now diff --git a/kamon-core/src/main/scala/kamon/supervisor/AspectJPresent.scala b/kamon-core/src/main/scala/kamon/util/FunctionalInterfaces.scala index 0df9539f..8dab6519 100644 --- a/kamon-core/src/main/scala/kamon/supervisor/AspectJPresent.scala +++ b/kamon-core/src/main/scala/kamon/util/FunctionalInterfaces.scala @@ -14,18 +14,12 @@ * ========================================================================================= */ -package kamon.supervisor - -import org.aspectj.lang.ProceedingJoinPoint -import org.aspectj.lang.annotation.{ Around, Aspect, Pointcut } - -@Aspect -class AspectJPresent { - - @Pointcut("execution(* kamon.supervisor.KamonSupervisor.isAspectJPresent())") - def isAspectJPresentAtModuleSupervisor(): Unit = {} - - @Around("isAspectJPresentAtModuleSupervisor()") - def aroundIsAspectJPresentAtModuleSupervisor(pjp: ProceedingJoinPoint): Boolean = true +package kamon.util +trait Supplier[T] { + def get: T } + +trait Function[T, R] { + def apply(t: T): R +}
\ No newline at end of file diff --git a/kamon-core/src/main/scala/kamon/util/JavaTags.scala b/kamon-core/src/main/scala/kamon/util/JavaTags.scala new file mode 100644 index 00000000..90bece5c --- /dev/null +++ b/kamon-core/src/main/scala/kamon/util/JavaTags.scala @@ -0,0 +1,14 @@ +package kamon.util + +object JavaTags { + + /** + * Helper method to transform Java maps into Scala maps. Typically this will be used as a static import from + * Java code that wants to use tags, as tags are defined as scala.collection.mutable.Map[String, String] and + * creating them directly from Java is quite verbose. + */ + def tagsFromMap(tags: java.util.Map[String, String]): Map[String, String] = { + import scala.collection.JavaConversions._ + tags.toMap + } +} diff --git a/kamon-core/src/main/scala/kamon/util/Latency.scala b/kamon-core/src/main/scala/kamon/util/Latency.scala new file mode 100644 index 00000000..52e044f8 --- /dev/null +++ b/kamon-core/src/main/scala/kamon/util/Latency.scala @@ -0,0 +1,29 @@ +/* + * ========================================================================================= + * Copyright © 2013-2015 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.util + +import kamon.metric.instrument.Histogram + +object Latency { + def measure[A](histogram: Histogram)(thunk: ⇒ A): A = { + val start = RelativeNanoTimestamp.now + try thunk finally { + val latency = NanoInterval.since(start).nanos + histogram.record(latency) + } + } +} diff --git a/kamon-core/src/main/scala/kamon/util/LazyActorRef.scala b/kamon-core/src/main/scala/kamon/util/LazyActorRef.scala index 855bf1fc..e2fb747a 100644 --- a/kamon-core/src/main/scala/kamon/util/LazyActorRef.scala +++ b/kamon-core/src/main/scala/kamon/util/LazyActorRef.scala @@ -1,3 +1,19 @@ +/* + * ========================================================================================= + * Copyright © 2013-2015 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.util import java.util diff --git a/kamon-core/src/main/scala/kamon/util/MapMerge.scala b/kamon-core/src/main/scala/kamon/util/MapMerge.scala index 64b4f7ae..8573358b 100644 --- a/kamon-core/src/main/scala/kamon/util/MapMerge.scala +++ b/kamon-core/src/main/scala/kamon/util/MapMerge.scala @@ -19,7 +19,7 @@ package kamon.util object MapMerge { /** - * Merge to immutable maps with the same key and value types, using the provided valueMerge function. + * Merge two immutable maps with the same key and value types, using the provided valueMerge function. */ implicit class Syntax[K, V](val map: Map[K, V]) extends AnyVal { def merge(that: Map[K, V], valueMerge: (V, V) ⇒ V): Map[K, V] = { diff --git a/kamon-core/src/main/scala/kamon/util/SameThreadExecutionContext.scala b/kamon-core/src/main/scala/kamon/util/SameThreadExecutionContext.scala new file mode 100644 index 00000000..2aae526f --- /dev/null +++ b/kamon-core/src/main/scala/kamon/util/SameThreadExecutionContext.scala @@ -0,0 +1,30 @@ +/* + * ========================================================================================= + * Copyright © 2013-2015 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.util + +import scala.concurrent.ExecutionContext +import org.slf4j.LoggerFactory + +/** + * For small code blocks that don't need to be run on a separate thread. + */ +object SameThreadExecutionContext extends ExecutionContext { + val logger = LoggerFactory.getLogger("SameThreadExecutionContext") + + override def execute(runnable: Runnable): Unit = runnable.run + override def reportFailure(t: Throwable): Unit = logger.error(t.getMessage, t) +} diff --git a/kamon-core/src/test/scala/kamon/metric/SimpleMetricsSpec.scala b/kamon-core/src/test/scala/kamon/metric/SimpleMetricsSpec.scala index 60ba9a0e..0180e980 100644 --- a/kamon-core/src/test/scala/kamon/metric/SimpleMetricsSpec.scala +++ b/kamon-core/src/test/scala/kamon/metric/SimpleMetricsSpec.scala @@ -35,93 +35,81 @@ class SimpleMetricsSpec extends BaseKamonSpec("simple-metrics-spec") { "the SimpleMetrics extension" should { "allow registering a fully configured Histogram and get the same Histogram if registering again" in { - val histogramA = Kamon.simpleMetrics.histogram("histogram-with-settings", DynamicRange(1, 10000, 2)) - val histogramB = Kamon.simpleMetrics.histogram("histogram-with-settings", DynamicRange(1, 10000, 2)) + val histogramA = Kamon.metrics.histogram("histogram-with-settings", DynamicRange(1, 10000, 2)) + val histogramB = Kamon.metrics.histogram("histogram-with-settings", DynamicRange(1, 10000, 2)) histogramA shouldBe theSameInstanceAs(histogramB) } "return the original Histogram when registering a fully configured Histogram for second time but with different settings" in { - val histogramA = Kamon.simpleMetrics.histogram("histogram-with-settings", DynamicRange(1, 10000, 2)) - val histogramB = Kamon.simpleMetrics.histogram("histogram-with-settings", DynamicRange(1, 50000, 2)) + val histogramA = Kamon.metrics.histogram("histogram-with-settings", DynamicRange(1, 10000, 2)) + val histogramB = Kamon.metrics.histogram("histogram-with-settings", DynamicRange(1, 50000, 2)) histogramA shouldBe theSameInstanceAs(histogramB) } "allow registering a Histogram that takes the default configuration from the kamon.metrics.precision settings" in { - Kamon.simpleMetrics.histogram("histogram-with-default-configuration") + Kamon.metrics.histogram("histogram-with-default-configuration") } "allow registering a Counter and get the same Counter if registering again" in { - val counterA = Kamon.simpleMetrics.counter("counter") - val counterB = Kamon.simpleMetrics.counter("counter") + val counterA = Kamon.metrics.counter("counter") + val counterB = Kamon.metrics.counter("counter") counterA shouldBe theSameInstanceAs(counterB) } "allow registering a fully configured MinMaxCounter and get the same MinMaxCounter if registering again" in { - val minMaxCounterA = Kamon.simpleMetrics.minMaxCounter("min-max-counter-with-settings", DynamicRange(1, 10000, 2), 1 second) - val minMaxCounterB = Kamon.simpleMetrics.minMaxCounter("min-max-counter-with-settings", DynamicRange(1, 10000, 2), 1 second) + val minMaxCounterA = Kamon.metrics.minMaxCounter("min-max-counter-with-settings", DynamicRange(1, 10000, 2), 1 second) + val minMaxCounterB = Kamon.metrics.minMaxCounter("min-max-counter-with-settings", DynamicRange(1, 10000, 2), 1 second) minMaxCounterA shouldBe theSameInstanceAs(minMaxCounterB) } "return the original MinMaxCounter when registering a fully configured MinMaxCounter for second time but with different settings" in { - val minMaxCounterA = Kamon.simpleMetrics.minMaxCounter("min-max-counter-with-settings", DynamicRange(1, 10000, 2), 1 second) - val minMaxCounterB = Kamon.simpleMetrics.minMaxCounter("min-max-counter-with-settings", DynamicRange(1, 50000, 2), 1 second) + val minMaxCounterA = Kamon.metrics.minMaxCounter("min-max-counter-with-settings", DynamicRange(1, 10000, 2), 1 second) + val minMaxCounterB = Kamon.metrics.minMaxCounter("min-max-counter-with-settings", DynamicRange(1, 50000, 2), 1 second) minMaxCounterA shouldBe theSameInstanceAs(minMaxCounterB) } "allow registering a MinMaxCounter that takes the default configuration from the kamon.metrics.precision settings" in { - Kamon.simpleMetrics.minMaxCounter("min-max-counter-with-default-configuration") + Kamon.metrics.minMaxCounter("min-max-counter-with-default-configuration") } "allow registering a fully configured Gauge and get the same Gauge if registering again" in { - val gaugeA = Kamon.simpleMetrics.gauge("gauge-with-settings", DynamicRange(1, 10000, 2), 1 second, { - () ⇒ 1L - }) - - val gaugeB = Kamon.simpleMetrics.gauge("gauge-with-settings", DynamicRange(1, 10000, 2), 1 second, { - () ⇒ 1L - }) + val gaugeA = Kamon.metrics.gauge("gauge-with-settings", DynamicRange(1, 10000, 2), 1 second)(1L) + val gaugeB = Kamon.metrics.gauge("gauge-with-settings", DynamicRange(1, 10000, 2), 1 second)(1L) gaugeA shouldBe theSameInstanceAs(gaugeB) } "return the original Gauge when registering a fully configured Gauge for second time but with different settings" in { - val gaugeA = Kamon.simpleMetrics.gauge("gauge-with-settings", DynamicRange(1, 10000, 2), 1 second, { - () ⇒ 1L - }) - - val gaugeB = Kamon.simpleMetrics.gauge("gauge-with-settings", DynamicRange(1, 10000, 2), 1 second, { - () ⇒ 1L - }) + val gaugeA = Kamon.metrics.gauge("gauge-with-settings", DynamicRange(1, 10000, 2), 1 second)(1L) + val gaugeB = Kamon.metrics.gauge("gauge-with-settings", DynamicRange(1, 10000, 2), 1 second)(1L) gaugeA shouldBe theSameInstanceAs(gaugeB) } "allow registering a Gauge that takes the default configuration from the kamon.metrics.precision settings" in { - Kamon.simpleMetrics.gauge("gauge-with-default-configuration", { - () ⇒ 2L - }) + Kamon.metrics.gauge("gauge-with-default-configuration")(2L) } "allow un-registering user metrics" in { - val counter = Kamon.simpleMetrics.counter("counter-for-remove") - val histogram = Kamon.simpleMetrics.histogram("histogram-for-remove") - val minMaxCounter = Kamon.simpleMetrics.minMaxCounter("min-max-counter-for-remove") - val gauge = Kamon.simpleMetrics.gauge("gauge-for-remove", { () ⇒ 2L }) - - Kamon.simpleMetrics.removeCounter("counter-for-remove") - Kamon.simpleMetrics.removeHistogram("histogram-for-remove") - Kamon.simpleMetrics.removeMinMaxCounter("min-max-counter-for-remove") - Kamon.simpleMetrics.removeGauge("gauge-for-remove") - - counter should not be (theSameInstanceAs(Kamon.simpleMetrics.counter("counter-for-remove"))) - histogram should not be (theSameInstanceAs(Kamon.simpleMetrics.histogram("histogram-for-remove"))) - minMaxCounter should not be (theSameInstanceAs(Kamon.simpleMetrics.minMaxCounter("min-max-counter-for-remove"))) - gauge should not be (theSameInstanceAs(Kamon.simpleMetrics.gauge("gauge-for-remove", { () ⇒ 2L }))) + val counter = Kamon.metrics.counter("counter-for-remove") + val histogram = Kamon.metrics.histogram("histogram-for-remove") + val minMaxCounter = Kamon.metrics.minMaxCounter("min-max-counter-for-remove") + val gauge = Kamon.metrics.gauge("gauge-for-remove")(2L) + + Kamon.metrics.removeCounter("counter-for-remove") + Kamon.metrics.removeHistogram("histogram-for-remove") + Kamon.metrics.removeMinMaxCounter("min-max-counter-for-remove") + Kamon.metrics.removeGauge("gauge-for-remove") + + counter should not be (theSameInstanceAs(Kamon.metrics.counter("counter-for-remove"))) + histogram should not be (theSameInstanceAs(Kamon.metrics.histogram("histogram-for-remove"))) + minMaxCounter should not be (theSameInstanceAs(Kamon.metrics.minMaxCounter("min-max-counter-for-remove"))) + gauge should not be (theSameInstanceAs(Kamon.metrics.gauge("gauge-for-remove")(2L))) } } } diff --git a/kamon-core/src/test/scala/kamon/metric/SubscriptionsProtocolSpec.scala b/kamon-core/src/test/scala/kamon/metric/SubscriptionsProtocolSpec.scala index 53ae5273..b8131f6e 100644 --- a/kamon-core/src/test/scala/kamon/metric/SubscriptionsProtocolSpec.scala +++ b/kamon-core/src/test/scala/kamon/metric/SubscriptionsProtocolSpec.scala @@ -34,12 +34,12 @@ class SubscriptionsProtocolSpec extends BaseKamonSpec("subscriptions-protocol-sp """.stripMargin) lazy val metricsModule = Kamon.metrics - import metricsModule.{ register, subscribe, unsubscribe } + import metricsModule.{ entity, subscribe, unsubscribe } "the Subscriptions messaging protocol" should { "allow subscribing for a single tick" in { val subscriber = TestProbe() - register(TraceMetrics, "one-shot") + entity(TraceMetrics, "one-shot") subscribe("trace", "one-shot", subscriber.ref, permanently = false) flushSubscriptions() @@ -54,7 +54,7 @@ class SubscriptionsProtocolSpec extends BaseKamonSpec("subscriptions-protocol-sp "allow subscribing permanently to a metric" in { val subscriber = TestProbe() - register(TraceMetrics, "permanent") + entity(TraceMetrics, "permanent") subscribe("trace", "permanent", subscriber.ref, permanently = true) for (repetition ← 1 to 5) { @@ -68,9 +68,9 @@ class SubscriptionsProtocolSpec extends BaseKamonSpec("subscriptions-protocol-sp "allow subscribing to metrics matching a glob pattern" in { val subscriber = TestProbe() - register(TraceMetrics, "include-one") - register(TraceMetrics, "exclude-two") - register(TraceMetrics, "include-three") + entity(TraceMetrics, "include-one") + entity(TraceMetrics, "exclude-two") + entity(TraceMetrics, "include-three") subscribe("trace", "include-*", subscriber.ref, permanently = true) for (repetition ← 1 to 5) { @@ -85,9 +85,9 @@ class SubscriptionsProtocolSpec extends BaseKamonSpec("subscriptions-protocol-sp "send a single TickMetricSnapshot to each subscriber, even if subscribed multiple times" in { val subscriber = TestProbe() - register(TraceMetrics, "include-one") - register(TraceMetrics, "exclude-two") - register(TraceMetrics, "include-three") + entity(TraceMetrics, "include-one") + entity(TraceMetrics, "exclude-two") + entity(TraceMetrics, "include-three") subscribe("trace", "include-one", subscriber.ref, permanently = true) subscribe("trace", "include-three", subscriber.ref, permanently = true) @@ -103,7 +103,7 @@ class SubscriptionsProtocolSpec extends BaseKamonSpec("subscriptions-protocol-sp "allow un-subscribing a subscriber" in { val subscriber = TestProbe() - register(TraceMetrics, "one-shot") + entity(TraceMetrics, "one-shot") subscribe("trace", "one-shot", subscriber.ref, permanently = true) flushSubscriptions() diff --git a/kamon-core/src/test/scala/kamon/metric/TickMetricSnapshotBufferSpec.scala b/kamon-core/src/test/scala/kamon/metric/TickMetricSnapshotBufferSpec.scala index 0c9ced32..ac35cf58 100644 --- a/kamon-core/src/test/scala/kamon/metric/TickMetricSnapshotBufferSpec.scala +++ b/kamon-core/src/test/scala/kamon/metric/TickMetricSnapshotBufferSpec.scala @@ -88,21 +88,21 @@ class TickMetricSnapshotBufferSpec extends BaseKamonSpec("trace-metrics-spec") w trait SnapshotFixtures { val collectionContext = Kamon.metrics.buildDefaultCollectionContext val testTraceIdentity = Entity("buffer-spec-test-trace", "trace") - val traceRecorder = Kamon.metrics.register(TraceMetrics, "buffer-spec-test-trace").get.recorder + val traceRecorder = Kamon.metrics.entity(TraceMetrics, "buffer-spec-test-trace") val firstEmpty = TickMetricSnapshot(new MilliTimestamp(1000), new MilliTimestamp(2000), Map.empty) val secondEmpty = TickMetricSnapshot(new MilliTimestamp(2000), new MilliTimestamp(3000), Map.empty) val thirdEmpty = TickMetricSnapshot(new MilliTimestamp(3000), new MilliTimestamp(4000), Map.empty) - traceRecorder.ElapsedTime.record(10L) - traceRecorder.ElapsedTime.record(20L) - traceRecorder.ElapsedTime.record(30L) + traceRecorder.elapsedTime.record(10L) + traceRecorder.elapsedTime.record(20L) + traceRecorder.elapsedTime.record(30L) val firstNonEmpty = TickMetricSnapshot(new MilliTimestamp(1000), new MilliTimestamp(2000), Map( (testTraceIdentity -> traceRecorder.collect(collectionContext)))) - traceRecorder.ElapsedTime.record(10L) - traceRecorder.ElapsedTime.record(10L) - traceRecorder.ElapsedTime.record(300L) + traceRecorder.elapsedTime.record(10L) + traceRecorder.elapsedTime.record(10L) + traceRecorder.elapsedTime.record(300L) val secondNonEmpty = TickMetricSnapshot(new MilliTimestamp(1000), new MilliTimestamp(2000), Map( (testTraceIdentity -> traceRecorder.collect(collectionContext)))) } diff --git a/kamon-core/src/test/scala/kamon/metric/TraceMetricsSpec.scala b/kamon-core/src/test/scala/kamon/metric/TraceMetricsSpec.scala index 03a09b7f..efdcb79b 100644 --- a/kamon-core/src/test/scala/kamon/metric/TraceMetricsSpec.scala +++ b/kamon-core/src/test/scala/kamon/metric/TraceMetricsSpec.scala @@ -23,8 +23,6 @@ import kamon.trace.Tracer import kamon.metric.instrument.Histogram class TraceMetricsSpec extends BaseKamonSpec("trace-metrics-spec") with ImplicitSender { - import TraceMetricsSpec.SegmentSyntax - override lazy val config = ConfigFactory.parseString( """ @@ -60,10 +58,13 @@ class TraceMetricsSpec extends BaseKamonSpec("trace-metrics-spec") with Implicit Tracer.currentContext.finish() } - val snapshot = takeSnapshotOf("trace-with-segments", "trace") + val snapshot = takeSnapshotOf("test-segment", "trace-segment", + tags = Map( + "trace" -> "trace-with-segments", + "category" -> "test-category", + "library" -> "test-library")) + snapshot.histogram("elapsed-time").get.numberOfMeasurements should be(1) - snapshot.segments.size should be(1) - snapshot.segment("test-segment", "test-category", "test-library").numberOfMeasurements should be(1) } "record the elapsed time for segments that finish after their correspondent trace has finished" in { @@ -75,25 +76,26 @@ class TraceMetricsSpec extends BaseKamonSpec("trace-metrics-spec") with Implicit val beforeFinishSegmentSnapshot = takeSnapshotOf("closing-segment-after-trace", "trace") beforeFinishSegmentSnapshot.histogram("elapsed-time").get.numberOfMeasurements should be(1) - beforeFinishSegmentSnapshot.segments.size should be(0) + + intercept[NoSuchElementException] { + // The segment metric should not exist before we it has finished. + + takeSnapshotOf("test-segment", "trace-segment", + tags = Map( + "trace" -> "closing-segment-after-trace", + "category" -> "test-category", + "library" -> "test-library")) + } segment.finish() - val afterFinishSegmentSnapshot = takeSnapshotOf("closing-segment-after-trace", "trace") - afterFinishSegmentSnapshot.histogram("elapsed-time").get.numberOfMeasurements should be(0) - afterFinishSegmentSnapshot.segments.size should be(1) - afterFinishSegmentSnapshot.segment("test-segment", "test-category", "test-library").numberOfMeasurements should be(1) - } - } -} + val afterFinishSegmentSnapshot = takeSnapshotOf("test-segment", "trace-segment", + tags = Map( + "trace" -> "closing-segment-after-trace", + "category" -> "test-category", + "library" -> "test-library")) -object TraceMetricsSpec { - implicit class SegmentSyntax(val entitySnapshot: EntitySnapshot) extends AnyVal { - def segments: Map[HistogramKey, Histogram.Snapshot] = { - entitySnapshot.histograms.filterKeys(_.metadata.contains("category")) + afterFinishSegmentSnapshot.histogram("elapsed-time").get.numberOfMeasurements should be(1) } - - def segment(name: String, category: String, library: String): Histogram.Snapshot = - segments(TraceMetrics.segmentKey(name, category, library)) } } diff --git a/kamon-core/src/test/scala/kamon/testkit/BaseKamonSpec.scala b/kamon-core/src/test/scala/kamon/testkit/BaseKamonSpec.scala index f3d809bf..e7b18770 100644 --- a/kamon-core/src/test/scala/kamon/testkit/BaseKamonSpec.scala +++ b/kamon-core/src/test/scala/kamon/testkit/BaseKamonSpec.scala @@ -20,7 +20,7 @@ import akka.testkit.{ ImplicitSender, TestKitBase } import akka.actor.ActorSystem import com.typesafe.config.{ Config, ConfigFactory } import kamon.Kamon -import kamon.metric.{ SubscriptionsDispatcher, EntitySnapshot, MetricsImpl } +import kamon.metric.{ Entity, SubscriptionsDispatcher, EntitySnapshot, MetricsImpl } import kamon.trace.TraceContext import kamon.util.LazyActorRef import org.scalatest.{ BeforeAndAfterAll, Matchers, WordSpecLike } @@ -48,6 +48,11 @@ abstract class BaseKamonSpec(actorSystemName: String) extends TestKitBase with W recorder.collect(collectionContext) } + def takeSnapshotOf(name: String, category: String, tags: Map[String, String]): EntitySnapshot = { + val recorder = Kamon.metrics.find(Entity(name, category, tags)).get + recorder.collect(collectionContext) + } + def flushSubscriptions(): Unit = { val subscriptionsField = Kamon.metrics.getClass.getDeclaredField("_subscriptions") subscriptionsField.setAccessible(true) diff --git a/kamon-datadog/src/main/scala/kamon/datadog/DatadogMetricsSender.scala b/kamon-datadog/src/main/scala/kamon/datadog/DatadogMetricsSender.scala index 80d4f098..4a73f5aa 100644 --- a/kamon-datadog/src/main/scala/kamon/datadog/DatadogMetricsSender.scala +++ b/kamon-datadog/src/main/scala/kamon/datadog/DatadogMetricsSender.scala @@ -23,7 +23,7 @@ import akka.util.ByteString import kamon.metric.SubscriptionsDispatcher.TickMetricSnapshot import java.text.{ DecimalFormatSymbols, DecimalFormat } import kamon.metric.instrument.{ Counter, Histogram } -import kamon.metric.{ MetricKey, Entity } +import kamon.metric.{ SingleInstrumentEntityRecorder, MetricKey, Entity } import java.util.Locale class DatadogMetricsSender(remote: InetSocketAddress, maxPacketSizeInBytes: Long) extends Actor with UdpExtensionProvider { @@ -90,11 +90,18 @@ class DatadogMetricsSender(remote: InetSocketAddress, maxPacketSizeInBytes: Long def encodeDatadogCounter(count: Long): String = count.toString + "|c" def buildMetricName(entity: Entity, metricKey: MetricKey): String = - s"$appName.${entity.category}.${metricKey.name}" + if (SingleInstrumentEntityRecorder.AllCategories.contains(entity.category)) + s"$appName.${entity.category}" + else + s"$appName.${entity.category}.${metricKey.name}" def buildIdentificationTag(entity: Entity, metricKey: MetricKey): String = { - val normalizedEntityName = entity.name.replace(": ", ":") - s"|#${entity.category}:${normalizedEntityName}" + val normalizedEntityName = entity.name.replace(" ", "") + if (entity.tags.nonEmpty) { + val tagsString = entity.tags.map { case (k, v) ⇒ k + ":" + v } mkString "," + s"|#${entity.category}:${normalizedEntityName},$tagsString" + } else + s"|#${entity.category}:${normalizedEntityName}" } } diff --git a/kamon-datadog/src/test/scala/kamon/datadog/DatadogMetricSenderSpec.scala b/kamon-datadog/src/test/scala/kamon/datadog/DatadogMetricSenderSpec.scala index b35902f9..22ce1f8e 100644 --- a/kamon-datadog/src/test/scala/kamon/datadog/DatadogMetricSenderSpec.scala +++ b/kamon-datadog/src/test/scala/kamon/datadog/DatadogMetricSenderSpec.scala @@ -96,7 +96,20 @@ class DatadogMetricSenderSpec extends BaseKamonSpec("datadog-metric-sender-spec" val udp = setup(Map(entity -> testRecorder.collect(collectionContext))) val Udp.Send(data, _, _) = udp.expectMsgType[Udp.Send] - data.utf8String should be("kamon.category.metric-one:10|ms|@0.5|#category:datadog\nkamon.category.counter:4|c|#category:datadog\nkamon.category.metric-two:21|ms|#category:datadog") + data.utf8String.split("\n") should contain allOf ( + "kamon.category.metric-one:10|ms|@0.5|#category:datadog", + "kamon.category.metric-two:21|ms|#category:datadog", + "kamon.category.counter:4|c|#category:datadog") + } + + "include all entity tags, if available in the metric packet" in new UdpListenerFixture { + val (entity, testRecorder) = buildRecorder("datadog", tags = Map("my-cool-tag" -> "some-value")) + testRecorder.metricTwo.record(10L, 2) + + val udp = setup(Map(entity -> testRecorder.collect(collectionContext))) + val Udp.Send(data, _, _) = udp.expectMsgType[Udp.Send] + + data.utf8String should be(s"kamon.category.metric-two:10|ms|@0.5|#category:datadog,my-cool-tag:some-value") } } @@ -105,9 +118,10 @@ class DatadogMetricSenderSpec extends BaseKamonSpec("datadog-metric-sender-spec" val localhostName = ManagementFactory.getRuntimeMXBean.getName.split('@')(1) val testMaxPacketSize = system.settings.config.getBytes("kamon.datadog.max-packet-size") - def buildRecorder(name: String): (Entity, TestEntityRecorder) = { - val registration = Kamon.metrics.register(TestEntityRecorder, name).get - (registration.entity, registration.recorder) + def buildRecorder(name: String, tags: Map[String, String] = Map.empty): (Entity, TestEntityRecorder) = { + val entity = Entity(name, TestEntityRecorder.category, tags) + val recorder = Kamon.metrics.entity(TestEntityRecorder, entity) + (entity, recorder) } def setup(metrics: Map[Entity, EntitySnapshot]): TestProbe = { diff --git a/kamon-examples/kamon-play-example/app/Global.scala b/kamon-examples/kamon-play-example/app/Global.scala index 535ea308..8d43fbdc 100644 --- a/kamon-examples/kamon-play-example/app/Global.scala +++ b/kamon-examples/kamon-play-example/app/Global.scala @@ -16,5 +16,11 @@ import filters.TraceLocalFilter import play.api.mvc.WithFilters +import play.api.Application +import kamon.Kamon -object Global extends WithFilters(TraceLocalFilter)
\ No newline at end of file +object Global extends WithFilters(TraceLocalFilter) { + override def onStart(app: Application) { + Kamon.start() + } +} diff --git a/kamon-examples/kamon-play-example/app/controllers/KamonPlayExample.scala b/kamon-examples/kamon-play-example/app/controllers/KamonPlayExample.scala index b3a8c11f..dea2b879 100644 --- a/kamon-examples/kamon-play-example/app/controllers/KamonPlayExample.scala +++ b/kamon-examples/kamon-play-example/app/controllers/KamonPlayExample.scala @@ -17,7 +17,6 @@ package controllers import filters.{TraceLocalContainer, TraceLocalKey} import kamon.Kamon -import kamon.metric.UserMetrics import kamon.play.action.TraceName import kamon.trace.TraceLocal import play.api.Logger @@ -56,7 +55,7 @@ import scala.concurrent._ object KamonPlayExample extends Controller { val logger = Logger(this.getClass) - val counter = Kamon(UserMetrics)(Akka.system()).registerCounter("my-counter") + val counter = Kamon.metrics.counter("my-counter") def sayHello = Action.async { Future { diff --git a/kamon-examples/kamon-play-example/app/filters/TraceLocalFilter.scala b/kamon-examples/kamon-play-example/app/filters/TraceLocalFilter.scala index c1d5b92e..8a92aa66 100644 --- a/kamon-examples/kamon-play-example/app/filters/TraceLocalFilter.scala +++ b/kamon-examples/kamon-play-example/app/filters/TraceLocalFilter.scala @@ -16,7 +16,7 @@ package filters -import kamon.trace.{TraceRecorder, TraceLocal} +import kamon.trace.TraceLocal import play.api.Logger import play.api.mvc.{Result, RequestHeader, Filter} import play.api.libs.concurrent.Execution.Implicits.defaultContext diff --git a/kamon-examples/kamon-play-example/conf/application.conf b/kamon-examples/kamon-play-example/conf/application.conf index 7d9fba80..e1b4d770 100644 --- a/kamon-examples/kamon-play-example/conf/application.conf +++ b/kamon-examples/kamon-play-example/conf/application.conf @@ -5,7 +5,7 @@ akka { kamon { - metrics { + metric { tick-interval = 1 second } diff --git a/kamon-examples/kamon-play-example/project/Build.scala b/kamon-examples/kamon-play-example/project/Build.scala index 080ef4a5..1896c932 100644 --- a/kamon-examples/kamon-play-example/project/Build.scala +++ b/kamon-examples/kamon-play-example/project/Build.scala @@ -20,7 +20,7 @@ object ApplicationBuild extends Build { ) val defaultSettings = Seq( - scalaVersion := "2.10.4", + scalaVersion := "2.11.5", resolvers ++= resolutionRepos, scalacOptions := Seq( "-encoding", @@ -35,7 +35,7 @@ object ApplicationBuild extends Build { "-Xlog-reflective-calls" )) - val kamonVersion = "0.3.4" + val kamonVersion = "0.3.6-125bee567e0b18ea5ec10f3a1fe76409673011d9" val dependencies = Seq( "io.kamon" %% "kamon-core" % kamonVersion, diff --git a/kamon-examples/kamon-play-example/project/plugins.sbt b/kamon-examples/kamon-play-example/project/plugins.sbt index 6f7c1c8b..b4f51541 100644 --- a/kamon-examples/kamon-play-example/project/plugins.sbt +++ b/kamon-examples/kamon-play-example/project/plugins.sbt @@ -5,5 +5,5 @@ logLevel := Level.Warn resolvers += "Typesafe repository" at "http://repo.typesafe.com/typesafe/releases/" // Use the Play sbt plugin for Play projects -addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.3.4") +addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.3.8") diff --git a/kamon-jdbc/src/main/scala/kamon/jdbc/instrumentation/StatementInstrumentation.scala b/kamon-jdbc/src/main/scala/kamon/jdbc/instrumentation/StatementInstrumentation.scala index aa9295db..bef20667 100644 --- a/kamon-jdbc/src/main/scala/kamon/jdbc/instrumentation/StatementInstrumentation.scala +++ b/kamon-jdbc/src/main/scala/kamon/jdbc/instrumentation/StatementInstrumentation.scala @@ -44,9 +44,8 @@ class StatementInstrumentation { @Around("onExecuteStatement(sql) || onExecutePreparedStatement(sql) || onExecutePreparedCall(sql)") def aroundExecuteStatement(pjp: ProceedingJoinPoint, sql: String): Any = { Tracer.currentContext.collect { ctx ⇒ - val metricsExtension = Kamon.metrics val jdbcExtension = Kamon(Jdbc) - implicit val statementRecorder = metricsExtension.register(StatementsMetrics, "jdbc-statements").map(_.recorder) + implicit val statementRecorder = Kamon.metrics.entity(StatementsMetrics, "jdbc-statements") sql.replaceAll(CommentPattern, Empty) match { case SelectStatement(_) ⇒ withSegment(ctx, Select, jdbcExtension)(recordRead(pjp, sql, jdbcExtension)) @@ -71,22 +70,22 @@ class StatementInstrumentation { try thunk finally segment.finish() } - def recordRead(pjp: ProceedingJoinPoint, sql: String, jdbcExtension: JdbcExtension)(implicit statementRecorder: Option[StatementsMetrics]): Any = { + def recordRead(pjp: ProceedingJoinPoint, sql: String, jdbcExtension: JdbcExtension)(implicit statementRecorder: StatementsMetrics): Any = { withTimeSpent(pjp.proceedWithErrorHandler(sql, jdbcExtension)) { timeSpent ⇒ - statementRecorder.map(stmr ⇒ stmr.reads.record(timeSpent)) + statementRecorder.reads.record(timeSpent) val timeSpentInMillis = nanos.toMillis(timeSpent) if (timeSpentInMillis >= jdbcExtension.slowQueryThreshold) { - statementRecorder.map(stmr ⇒ stmr.slows.increment()) + statementRecorder.slows.increment() jdbcExtension.processSlowQuery(sql, timeSpentInMillis) } } } - def recordWrite(pjp: ProceedingJoinPoint, sql: String, jdbcExtension: JdbcExtension)(implicit statementRecorder: Option[StatementsMetrics]): Any = { + def recordWrite(pjp: ProceedingJoinPoint, sql: String, jdbcExtension: JdbcExtension)(implicit statementRecorder: StatementsMetrics): Any = { withTimeSpent(pjp.proceedWithErrorHandler(sql, jdbcExtension)) { timeSpent ⇒ - statementRecorder.map(stmr ⇒ stmr.writes.record(timeSpent)) + statementRecorder.writes.record(timeSpent) } } } @@ -107,13 +106,13 @@ object StatementInstrumentation { val Delete = "Delete" implicit class PimpedProceedingJoinPoint(pjp: ProceedingJoinPoint) { - def proceedWithErrorHandler(sql: String, jdbcExtension: JdbcExtension)(implicit statementRecorder: Option[StatementsMetrics]): Any = { + def proceedWithErrorHandler(sql: String, jdbcExtension: JdbcExtension)(implicit statementRecorder: StatementsMetrics): Any = { try { pjp.proceed() } catch { case NonFatal(cause) ⇒ jdbcExtension.processSqlError(sql, cause) - statementRecorder.map(stmr ⇒ stmr.errors.increment()) + statementRecorder.errors.increment() throw cause } } diff --git a/kamon-jdbc/src/test/scala/kamon/jdbc/instrumentation/StatementInstrumentationSpec.scala b/kamon-jdbc/src/test/scala/kamon/jdbc/instrumentation/StatementInstrumentationSpec.scala index 8ad5faa8..80107dff 100644 --- a/kamon-jdbc/src/test/scala/kamon/jdbc/instrumentation/StatementInstrumentationSpec.scala +++ b/kamon-jdbc/src/test/scala/kamon/jdbc/instrumentation/StatementInstrumentationSpec.scala @@ -24,8 +24,6 @@ import kamon.testkit.BaseKamonSpec import kamon.trace.{ Tracer, SegmentCategory } class StatementInstrumentationSpec extends BaseKamonSpec("jdbc-spec") { - import TraceMetricsSpec.SegmentSyntax - override lazy val config = ConfigFactory.parseString( """ @@ -76,8 +74,14 @@ class StatementInstrumentationSpec extends BaseKamonSpec("jdbc-spec") { val traceSnapshot = takeSnapshotOf("jdbc-trace-insert", "trace") traceSnapshot.histogram("elapsed-time").get.numberOfMeasurements should be(1) - traceSnapshot.segments.size should be(1) - traceSnapshot.segment("Jdbc[Insert]", SegmentCategory.Database, Jdbc.SegmentLibraryName).numberOfMeasurements should be(100) + + val segmentSnapshot = takeSnapshotOf("Jdbc[Insert]", "trace-segment", + tags = Map( + "trace" -> "jdbc-trace-insert", + "category" -> SegmentCategory.Database, + "library" -> Jdbc.SegmentLibraryName)) + + segmentSnapshot.histogram("elapsed-time").get.numberOfMeasurements should be(100) } "record the execution time of SELECT operation" in { @@ -96,8 +100,14 @@ class StatementInstrumentationSpec extends BaseKamonSpec("jdbc-spec") { val traceSnapshot = takeSnapshotOf("jdbc-trace-select", "trace") traceSnapshot.histogram("elapsed-time").get.numberOfMeasurements should be(1) - traceSnapshot.segments.size should be(1) - traceSnapshot.segment("Jdbc[Select]", SegmentCategory.Database, Jdbc.SegmentLibraryName).numberOfMeasurements should be(100) + + val segmentSnapshot = takeSnapshotOf("Jdbc[Select]", "trace-segment", + tags = Map( + "trace" -> "jdbc-trace-select", + "category" -> SegmentCategory.Database, + "library" -> Jdbc.SegmentLibraryName)) + + segmentSnapshot.histogram("elapsed-time").get.numberOfMeasurements should be(100) } "record the execution time of UPDATE operation" in { @@ -116,8 +126,14 @@ class StatementInstrumentationSpec extends BaseKamonSpec("jdbc-spec") { val traceSnapshot = takeSnapshotOf("jdbc-trace-update", "trace") traceSnapshot.histogram("elapsed-time").get.numberOfMeasurements should be(1) - traceSnapshot.segments.size should be(1) - traceSnapshot.segment("Jdbc[Update]", SegmentCategory.Database, Jdbc.SegmentLibraryName).numberOfMeasurements should be(100) + + val segmentSnapshot = takeSnapshotOf("Jdbc[Update]", "trace-segment", + tags = Map( + "trace" -> "jdbc-trace-update", + "category" -> SegmentCategory.Database, + "library" -> Jdbc.SegmentLibraryName)) + + segmentSnapshot.histogram("elapsed-time").get.numberOfMeasurements should be(100) } "record the execution time of DELETE operation" in { @@ -136,8 +152,14 @@ class StatementInstrumentationSpec extends BaseKamonSpec("jdbc-spec") { val traceSnapshot = takeSnapshotOf("jdbc-trace-delete", "trace") traceSnapshot.histogram("elapsed-time").get.numberOfMeasurements should be(1) - traceSnapshot.segments.size should be(1) - traceSnapshot.segment("Jdbc[Delete]", SegmentCategory.Database, Jdbc.SegmentLibraryName).numberOfMeasurements should be(100) + + val segmentSnapshot = takeSnapshotOf("Jdbc[Delete]", "trace-segment", + tags = Map( + "trace" -> "jdbc-trace-delete", + "category" -> SegmentCategory.Database, + "library" -> Jdbc.SegmentLibraryName)) + + segmentSnapshot.histogram("elapsed-time").get.numberOfMeasurements should be(100) } diff --git a/kamon-log-reporter/src/main/scala/kamon/logreporter/LogReporter.scala b/kamon-log-reporter/src/main/scala/kamon/logreporter/LogReporter.scala index fc1f0faf..27424e39 100644 --- a/kamon-log-reporter/src/main/scala/kamon/logreporter/LogReporter.scala +++ b/kamon-log-reporter/src/main/scala/kamon/logreporter/LogReporter.scala @@ -1,6 +1,6 @@ /* * ========================================================================================= - * Copyright © 2013-2014 the kamon project <http://kamon.io/> + * Copyright © 2013-2015 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 @@ -36,8 +36,12 @@ class LogReporterExtension(system: ExtendedActorSystem) extends Kamon.Extension val subscriber = system.actorOf(Props[LogReporterSubscriber], "kamon-log-reporter") Kamon.metrics.subscribe("trace", "**", subscriber, permanently = true) - Kamon.metrics.subscribe("actor", "**", subscriber, permanently = true) - Kamon.metrics.subscribe("user-metrics", "**", subscriber, permanently = true) + Kamon.metrics.subscribe("akka-actor", "**", subscriber, permanently = true) + Kamon.metrics.subscribe("akka-dispatcher", "**", subscriber, permanently = true) + Kamon.metrics.subscribe("counter", "**", subscriber, permanently = true) + Kamon.metrics.subscribe("histogram", "**", subscriber, permanently = true) + Kamon.metrics.subscribe("min-max-counter", "**", subscriber, permanently = true) + Kamon.metrics.subscribe("gauge", "**", subscriber, permanently = true) val includeSystemMetrics = logReporterConfig.getBoolean("report-system-metrics") if (includeSystemMetrics) { @@ -47,6 +51,7 @@ class LogReporterExtension(system: ExtendedActorSystem) extends Kamon.Extension } class LogReporterSubscriber extends Actor with ActorLogging { + import kamon.logreporter.LogReporterSubscriber.RichHistogramSnapshot def receive = { @@ -54,13 +59,25 @@ class LogReporterSubscriber extends Actor with ActorLogging { } def printMetricSnapshot(tick: TickMetricSnapshot): Unit = { + // Group all the user metrics together. + val histograms = Map.newBuilder[String, Option[Histogram.Snapshot]] + val counters = Map.newBuilder[String, Option[Counter.Snapshot]] + val minMaxCounters = Map.newBuilder[String, Option[Histogram.Snapshot]] + val gauges = Map.newBuilder[String, Option[Histogram.Snapshot]] + tick.metrics foreach { - case (entity, snapshot) if entity.category == "actor" ⇒ logActorMetrics(entity.name, snapshot) - case (entity, snapshot) if entity.category == "trace" ⇒ logTraceMetrics(entity.name, snapshot) - case (entity, snapshot) if entity.category == "simple-metric" ⇒ logSimpleMetrics(snapshot) - case (entity, snapshot) if entity.category == "system-metric" ⇒ logSystemMetrics(entity.name, snapshot) - case ignoreEverythingElse ⇒ + case (entity, snapshot) if entity.category == "akka-actor" ⇒ logActorMetrics(entity.name, snapshot) + case (entity, snapshot) if entity.category == "akka-dispatcher" ⇒ logDispatcherMetrics(entity, snapshot) + case (entity, snapshot) if entity.category == "trace" ⇒ logTraceMetrics(entity.name, snapshot) + case (entity, snapshot) if entity.category == "histogram" ⇒ histograms += (entity.name -> snapshot.histogram("histogram")) + case (entity, snapshot) if entity.category == "counter" ⇒ counters += (entity.name -> snapshot.counter("counter")) + case (entity, snapshot) if entity.category == "min-max-counter" ⇒ minMaxCounters += (entity.name -> snapshot.minMaxCounter("min-max-counter")) + case (entity, snapshot) if entity.category == "gauge" ⇒ gauges += (entity.name -> snapshot.gauge("gauge")) + case (entity, snapshot) if entity.category == "system-metric" ⇒ logSystemMetrics(entity.name, snapshot) + case ignoreEverythingElse ⇒ } + + logMetrics(histograms.result(), counters.result(), minMaxCounters.result(), gauges.result()) } def logActorMetrics(name: String, actorSnapshot: EntitySnapshot): Unit = { @@ -102,6 +119,79 @@ class LogReporterSubscriber extends Actor with ActorLogging { } + def logDispatcherMetrics(entity: Entity, snapshot: EntitySnapshot): Unit = entity.tags.get("dispatcher-type") match { + case Some("fork-join-pool") ⇒ logForkJoinPool(entity.name, snapshot) + case Some("thread-pool-executor") ⇒ logThreadPoolExecutor(entity.name, snapshot) + case ignoreOthers ⇒ + } + + def logForkJoinPool(name: String, forkJoinMetrics: EntitySnapshot): Unit = { + for { + paralellism ← forkJoinMetrics.minMaxCounter("parallelism") + poolSize ← forkJoinMetrics.gauge("pool-size") + activeThreads ← forkJoinMetrics.gauge("active-threads") + runningThreads ← forkJoinMetrics.gauge("running-threads") + queuedTaskCount ← forkJoinMetrics.gauge("queued-task-count") + + } { + log.info( + """ + |+--------------------------------------------------------------------------------------------------+ + || Fork-Join-Pool | + || | + || Dispatcher: %-83s | + || | + || Paralellism: %-4s | + || | + || Pool Size Active Threads Running Threads Queue Task Count | + || Min %-4s %-4s %-4s %-4s | + || Avg %-4s %-4s %-4s %-4s | + || Max %-4s %-4s %-4s %-4s | + || | + |+--------------------------------------------------------------------------------------------------+""" + .stripMargin.format(name, + paralellism.max, poolSize.min, activeThreads.min, runningThreads.min, queuedTaskCount.min, + poolSize.average, activeThreads.average, runningThreads.average, queuedTaskCount.average, + poolSize.max, activeThreads.max, runningThreads.max, queuedTaskCount.max)) + } + } + + def logThreadPoolExecutor(name: String, threadPoolMetrics: EntitySnapshot): Unit = { + for { + corePoolSize ← threadPoolMetrics.gauge("core-pool-size") + maxPoolSize ← threadPoolMetrics.gauge("max-pool-size") + poolSize ← threadPoolMetrics.gauge("pool-size") + activeThreads ← threadPoolMetrics.gauge("active-threads") + processedTasks ← threadPoolMetrics.gauge("processed-tasks") + } { + + log.info( + """ + |+--------------------------------------------------------------------------------------------------+ + || Thread-Pool-Executor | + || | + || Dispatcher: %-83s | + || | + || Core Pool Size: %-4s | + || Max Pool Size: %-4s | + || | + || | + || Pool Size Active Threads Processed Task | + || Min %-4s %-4s %-4s | + || Avg %-4s %-4s %-4s | + || Max %-4s %-4s %-4s | + || | + |+--------------------------------------------------------------------------------------------------+""" + .stripMargin.format(name, + corePoolSize.max, + maxPoolSize.max, + poolSize.min, activeThreads.min, processedTasks.min, + poolSize.average, activeThreads.average, processedTasks.average, + poolSize.max, activeThreads.max, processedTasks.max)) + } + + } + def logSystemMetrics(metric: String, snapshot: EntitySnapshot): Unit = metric match { case "cpu" ⇒ logCpuMetrics(snapshot) case "network" ⇒ logNetworkMetrics(snapshot) @@ -185,11 +275,10 @@ class LogReporterSubscriber extends Actor with ActorLogging { || Max: %-12s Max: %-12s | || | |+--------------------------------------------------------------------------------------------------+""" - .stripMargin. - format( - (user.min, total.min, - user.average, total.average, - user.max, total.max))) + .stripMargin.format( + user.min, total.min, + user.average, total.average, + user.max, total.max)) } } @@ -253,78 +342,74 @@ class LogReporterSubscriber extends Actor with ActorLogging { } } - def logSimpleMetrics(simpleMetrics: EntitySnapshot): Unit = { - val histograms = simpleMetrics.histograms - val minMaxCounters = simpleMetrics.minMaxCounters - val gauges = simpleMetrics.gauges - val counters = simpleMetrics.counters + def logMetrics(histograms: Map[String, Option[Histogram.Snapshot]], + counters: Map[String, Option[Counter.Snapshot]], minMaxCounters: Map[String, Option[Histogram.Snapshot]], + gauges: Map[String, Option[Histogram.Snapshot]]): Unit = { if (histograms.isEmpty && counters.isEmpty && minMaxCounters.isEmpty && gauges.isEmpty) { - log.info("No user metrics reported") + log.info("No metrics reported") return } - val simpleMetricsData = StringBuilder.newBuilder + val metricsData = StringBuilder.newBuilder - simpleMetricsData.append( + metricsData.append( """ |+--------------------------------------------------------------------------------------------------+ || | - || Simple Counters | + || Counters | || ------------- | |""".stripMargin) - counters.toList.sortBy(_._1.name.toLowerCase).foreach { - case (counter, snapshot) ⇒ simpleMetricsData.append(userCounterString(counter.name, snapshot)) - } + counters.foreach { case (name, snapshot) ⇒ metricsData.append(userCounterString(name, snapshot.get)) } - simpleMetricsData.append( + metricsData.append( """|| | || | - || Simple Histograms | - || --------------- | + || Histograms | + || -------------- | |""".stripMargin) histograms.foreach { - case (histogram, snapshot) ⇒ - simpleMetricsData.append("| %-40s |\n".format(histogram.name)) - simpleMetricsData.append(compactHistogramView(snapshot)) - simpleMetricsData.append("\n| |\n") + case (name, snapshot) ⇒ + metricsData.append("| %-40s |\n".format(name)) + metricsData.append(compactHistogramView(snapshot.get)) + metricsData.append("\n| |\n") } - simpleMetricsData.append( + metricsData.append( """|| | - || Simple MinMaxCounters | - || ------------------- | + || MinMaxCounters | + || ----------------- | |""".stripMargin) minMaxCounters.foreach { - case (minMaxCounter, snapshot) ⇒ - simpleMetricsData.append("| %-40s |\n".format(minMaxCounter.name)) - simpleMetricsData.append(simpleHistogramView(snapshot)) - simpleMetricsData.append("\n| |\n") + case (name, snapshot) ⇒ + metricsData.append("| %-40s |\n".format(name)) + metricsData.append(histogramView(snapshot.get)) + metricsData.append("\n| |\n") } - simpleMetricsData.append( + metricsData.append( """|| | - || User Gauges | - || ----------- | + || Gauges | + || ---------- | |""" .stripMargin) gauges.foreach { - case (gauge, snapshot) ⇒ - simpleMetricsData.append("| %-40s |\n".format(gauge.name)) - simpleMetricsData.append(simpleHistogramView(snapshot)) - simpleMetricsData.append("\n| |\n") + case (name, snapshot) ⇒ + metricsData.append("| %-40s |\n".format(name)) + metricsData.append(histogramView(snapshot.get)) + metricsData.append("\n| |\n") } - simpleMetricsData.append( + metricsData.append( """|| | |+--------------------------------------------------------------------------------------------------+""" .stripMargin) - log.info(simpleMetricsData.toString()) + log.info(metricsData.toString()) } def userCounterString(counterName: String, snapshot: Counter.Snapshot): String = { @@ -343,7 +428,7 @@ class LogReporterSubscriber extends Actor with ActorLogging { sb.toString() } - def simpleHistogramView(histogram: Histogram.Snapshot): String = + def histogramView(histogram: Histogram.Snapshot): String = "| Min: %-12s Average: %-12s Max: %-12s |" .format(histogram.min, histogram.average, histogram.max) } diff --git a/kamon-newrelic/src/main/scala/kamon/newrelic/CustomMetricExtractor.scala b/kamon-newrelic/src/main/scala/kamon/newrelic/CustomMetricExtractor.scala index 41ed9661..6919a967 100644 --- a/kamon-newrelic/src/main/scala/kamon/newrelic/CustomMetricExtractor.scala +++ b/kamon-newrelic/src/main/scala/kamon/newrelic/CustomMetricExtractor.scala @@ -16,17 +16,22 @@ package kamon.newrelic -import kamon.metric.{ SimpleMetricsImpl, EntitySnapshot, Entity } +import kamon.metric.{ EntitySnapshot, Entity } import kamon.metric.instrument.CollectionContext object CustomMetricExtractor extends MetricExtractor { def extract(settings: AgentSettings, collectionContext: CollectionContext, metrics: Map[Entity, EntitySnapshot]): Map[MetricID, MetricData] = { - metrics.get(SimpleMetricsImpl.SimpleMetricsEntity).map { allSimpleMetrics ⇒ - allSimpleMetrics.metrics.map { - case (key, snapshot) ⇒ Metric(snapshot, key.unitOfMeasurement, s"Custom/${key.name}", None) - } + def onlySimpleMetrics(kv: (Entity, EntitySnapshot)): Boolean = + kamon.metric.SingleInstrumentEntityRecorder.AllCategories.contains(kv._1.category) - } getOrElse (Map.empty) + def toNewRelicMetric(kv: (Entity, EntitySnapshot)): (MetricID, MetricData) = { + val (entity, entitySnapshot) = kv + val (metricKey, instrumentSnapshot) = entitySnapshot.metrics.head + + Metric(instrumentSnapshot, metricKey.unitOfMeasurement, s"Custom/${entity.name}", None) + } + + metrics.filter(onlySimpleMetrics).map(toNewRelicMetric) } } diff --git a/kamon-newrelic/src/main/scala/kamon/newrelic/Metric.scala b/kamon-newrelic/src/main/scala/kamon/newrelic/Metric.scala index 20204b79..6fb645f5 100644 --- a/kamon-newrelic/src/main/scala/kamon/newrelic/Metric.scala +++ b/kamon-newrelic/src/main/scala/kamon/newrelic/Metric.scala @@ -18,9 +18,9 @@ case class MetricData(callCount: Long, total: Double, totalExclusive: Double, mi object Metric { - def scaleFunction(uom: UnitOfMeasurement): Long ⇒ Double = uom match { + def scaleFunction(uom: UnitOfMeasurement): Double ⇒ Double = uom match { case time: Time ⇒ time.scale(Time.Seconds) - case other ⇒ _.toDouble + case other ⇒ a ⇒ a } def apply(snapshot: InstrumentSnapshot, snapshotUnit: UnitOfMeasurement, name: String, scope: Option[String]): Metric = { diff --git a/kamon-newrelic/src/main/scala/kamon/newrelic/WebTransactionMetricExtractor.scala b/kamon-newrelic/src/main/scala/kamon/newrelic/WebTransactionMetricExtractor.scala index d0144f4b..76cf0757 100644 --- a/kamon-newrelic/src/main/scala/kamon/newrelic/WebTransactionMetricExtractor.scala +++ b/kamon-newrelic/src/main/scala/kamon/newrelic/WebTransactionMetricExtractor.scala @@ -17,6 +17,7 @@ package kamon.newrelic import kamon.metric.{ EntitySnapshot, Entity } +import kamon.trace.SegmentCategory import scala.collection.mutable import kamon.metric.instrument.{ Time, CollectionContext, Histogram } @@ -35,38 +36,36 @@ object WebTransactionMetricExtractor extends MetricExtractor { val externalScopedByHostAndLibrarySnapshots = mutable.Map.empty[(String, String, String), List[Histogram.Snapshot]] val transactionMetrics = metrics.filterKeys(_.category == "trace").map { - case (entity: Entity, es: EntitySnapshot) ⇒ - // Trace metrics only have elapsed-time and segments and all of them are Histograms. - es.histograms.foreach { - case (key, segmentSnapshot) if key.metadata.get("category").filter(_ == "http-client").nonEmpty ⇒ - val library = key.metadata("library") - accumulatedExternalServices = accumulatedExternalServices.merge(segmentSnapshot, collectionContext) - - // Accumulate externals by host - externalByHostSnapshots.update(key.name, segmentSnapshot :: externalByHostSnapshots.getOrElse(key.name, Nil)) + case (entity, entitySnapshot) ⇒ + val elapsedTime = entitySnapshot.histogram("elapsed-time").get + accumulatedHttpDispatcher = accumulatedHttpDispatcher.merge(elapsedTime, collectionContext) + elapsedTime.recordsIterator.foreach { record ⇒ + apdexBuilder.record(Time.Nanoseconds.scale(Time.Seconds)(record.level), record.count) + } - // Accumulate externals by host and library - externalByHostAndLibrarySnapshots.update((key.name, library), - segmentSnapshot :: externalByHostAndLibrarySnapshots.getOrElse((key.name, library), Nil)) + Metric(elapsedTime, Time.Nanoseconds, "WebTransaction/Custom/" + entity.name, None) + } - // Accumulate externals by host and library, including the transaction as scope. - externalScopedByHostAndLibrarySnapshots.update((key.name, library, entity.name), - segmentSnapshot :: externalScopedByHostAndLibrarySnapshots.getOrElse((key.name, library, entity.name), Nil)) + // Accumulate all segment metrics + metrics.filterKeys(_.category == "trace-segment").map { + case (entity, entitySnapshot) if entity.tags("category") == SegmentCategory.HttpClient ⇒ + val library = entity.tags("library") + val trace = entity.tags("trace") + val elapsedTime = entitySnapshot.histogram("elapsed-time").get - case otherSegments ⇒ + accumulatedExternalServices = accumulatedExternalServices.merge(elapsedTime, collectionContext) - } + // Accumulate externals by host + externalByHostSnapshots.update(entity.name, elapsedTime :: externalByHostSnapshots.getOrElse(entity.name, Nil)) - es.histograms.collect { - case (key, elapsedTime) if key.name == "elapsed-time" ⇒ - accumulatedHttpDispatcher = accumulatedHttpDispatcher.merge(elapsedTime, collectionContext) - elapsedTime.recordsIterator.foreach { record ⇒ - apdexBuilder.record(Time.Nanoseconds.scale(Time.Seconds)(record.level), record.count) - } + // Accumulate externals by host and library + externalByHostAndLibrarySnapshots.update((entity.name, library), + elapsedTime :: externalByHostAndLibrarySnapshots.getOrElse((entity.name, library), Nil)) - Metric(elapsedTime, key.unitOfMeasurement, "WebTransaction/Custom/" + entity.name, None) - } - } flatten + // Accumulate externals by host and library, including the transaction as scope. + externalScopedByHostAndLibrarySnapshots.update((entity.name, library, trace), + elapsedTime :: externalScopedByHostAndLibrarySnapshots.getOrElse((entity.name, library, trace), Nil)) + } val httpDispatcher = Metric(accumulatedHttpDispatcher, Time.Seconds, "HttpDispatcher", None) val webTransaction = httpDispatcher.copy(MetricID("WebTransaction", None)) diff --git a/kamon-newrelic/src/test/scala/kamon/newrelic/MetricReporterSpec.scala b/kamon-newrelic/src/test/scala/kamon/newrelic/MetricReporterSpec.scala index 04380677..d4e815e5 100644 --- a/kamon-newrelic/src/test/scala/kamon/newrelic/MetricReporterSpec.scala +++ b/kamon-newrelic/src/test/scala/kamon/newrelic/MetricReporterSpec.scala @@ -139,19 +139,19 @@ class MetricReporterSpec extends BaseKamonSpec("metric-reporter-spec") with Spra trait FakeTickSnapshotsFixture { val testTraceID = Entity("example-trace", "trace") - val recorder = Kamon.metrics.register(TraceMetrics, testTraceID.name).get.recorder + val recorder = Kamon.metrics.entity(TraceMetrics, testTraceID.name) val collectionContext = Kamon.metrics.buildDefaultCollectionContext def collectRecorder = recorder.collect(collectionContext) - recorder.ElapsedTime.record(1000000) - recorder.ElapsedTime.record(2000000) - recorder.ElapsedTime.record(3000000) + recorder.elapsedTime.record(1000000) + recorder.elapsedTime.record(2000000) + recorder.elapsedTime.record(3000000) val firstSnapshot = TickMetricSnapshot(new MilliTimestamp(1415587618000L), new MilliTimestamp(1415587678000L), Map(testTraceID -> collectRecorder)) - recorder.ElapsedTime.record(6000000) - recorder.ElapsedTime.record(5000000) - recorder.ElapsedTime.record(4000000) + recorder.elapsedTime.record(6000000) + recorder.elapsedTime.record(5000000) + recorder.elapsedTime.record(4000000) val secondSnapshot = TickMetricSnapshot(new MilliTimestamp(1415587678000L), new MilliTimestamp(1415587738000L), Map(testTraceID -> collectRecorder)) } }
\ No newline at end of file diff --git a/kamon-play/src/main/scala/kamon/play/Play.scala b/kamon-play/src/main/scala/kamon/play/Play.scala index 9e160d58..270d244f 100644 --- a/kamon-play/src/main/scala/kamon/play/Play.scala +++ b/kamon-play/src/main/scala/kamon/play/Play.scala @@ -36,13 +36,7 @@ class PlayExtension(private val system: ExtendedActorSystem) extends Kamon.Exten log.info(s"Starting the Kamon(Play) extension") private val config = system.settings.config.getConfig("kamon.play") - val httpServerMetrics = { - val metricsExtension = Kamon.metrics - val factory = metricsExtension.instrumentFactory(HttpServerMetrics.category) - val entity = Entity("play-server", HttpServerMetrics.category) - - metricsExtension.register(entity, new HttpServerMetrics(factory)).recorder - } + val httpServerMetrics = Kamon.metrics.entity(HttpServerMetrics, "play-server") val defaultDispatcher = system.dispatcher val includeTraceToken: Boolean = config.getBoolean("automatic-trace-token-propagation") diff --git a/kamon-play/src/test/scala/kamon/play/WSInstrumentationSpec.scala b/kamon-play/src/test/scala/kamon/play/WSInstrumentationSpec.scala index d639fd9f..fe16ccbf 100644 --- a/kamon-play/src/test/scala/kamon/play/WSInstrumentationSpec.scala +++ b/kamon-play/src/test/scala/kamon/play/WSInstrumentationSpec.scala @@ -17,7 +17,7 @@ package kamon.play import kamon.Kamon -import kamon.metric.{ EntitySnapshot, TraceMetrics } +import kamon.metric.{ Entity, EntitySnapshot, TraceMetrics } import kamon.trace.{ Tracer, TraceContext, SegmentCategory } import org.scalatest.{ Matchers, WordSpecLike } import org.scalatestplus.play.OneServerPerSuite @@ -32,7 +32,7 @@ import scala.concurrent.duration._ class WSInstrumentationSpec extends WordSpecLike with Matchers with OneServerPerSuite { Kamon.start() - import kamon.metric.TraceMetricsSpec.SegmentSyntax + System.setProperty("config.file", "./kamon-play/src/test/resources/conf/application.conf") override lazy val port: Port = 19003 @@ -46,10 +46,16 @@ class WSInstrumentationSpec extends WordSpecLike with Matchers with OneServerPer "propagate the TraceContext inside an Action and complete the WS request" in { Await.result(route(FakeRequest(GET, "/inside")).get, 10 seconds) - val snapshot = takeSnapshotOf("GET: /inside") + val snapshot = takeSnapshotOf("GET: /inside", "trace") snapshot.histogram("elapsed-time").get.numberOfMeasurements should be(1) - snapshot.segments.size should be(1) - snapshot.segment(s"http://localhost:$port/async", SegmentCategory.HttpClient, Play.SegmentLibraryName).numberOfMeasurements should be(1) + + val segmentMetricsSnapshot = takeSnapshotOf(s"http://localhost:$port/async", "trace-segment", + tags = Map( + "trace" -> "GET: /inside", + "category" -> SegmentCategory.HttpClient, + "library" -> Play.SegmentLibraryName)) + + segmentMetricsSnapshot.histogram("elapsed-time").get.numberOfMeasurements should be(1) } "propagate the TraceContext outside an Action and complete the WS request" in { @@ -58,22 +64,26 @@ class WSInstrumentationSpec extends WordSpecLike with Matchers with OneServerPer Tracer.currentContext.finish() } - val snapshot = takeSnapshotOf("trace-outside-action") + val snapshot = takeSnapshotOf("trace-outside-action", "trace") snapshot.histogram("elapsed-time").get.numberOfMeasurements should be(1) - snapshot.segments.size should be(1) - snapshot.segment(s"http://localhost:$port/outside", SegmentCategory.HttpClient, Play.SegmentLibraryName).numberOfMeasurements should be(1) - } + val segmentMetricsSnapshot = takeSnapshotOf(s"http://localhost:$port/outside", "trace-segment", + tags = Map( + "trace" -> "trace-outside-action", + "category" -> SegmentCategory.HttpClient, + "library" -> Play.SegmentLibraryName)) + + segmentMetricsSnapshot.histogram("elapsed-time").get.numberOfMeasurements should be(1) + } } + lazy val collectionContext = Kamon.metrics.buildDefaultCollectionContext + def newContext(name: String): TraceContext = Kamon.tracer.newContext(name) - def takeSnapshotOf(traceName: String): EntitySnapshot = { - // Give some time for async segments to finish. - Thread.sleep(300) - val recorder = Kamon.metrics.register(TraceMetrics, traceName).get.recorder - val collectionContext = Kamon.metrics.buildDefaultCollectionContext + def takeSnapshotOf(name: String, category: String, tags: Map[String, String] = Map.empty): EntitySnapshot = { + val recorder = Kamon.metrics.find(Entity(name, category, tags)).get recorder.collect(collectionContext) } diff --git a/kamon-playground/src/main/resources/application.conf b/kamon-playground/src/main/resources/application.conf index 74e710bc..463894b2 100644 --- a/kamon-playground/src/main/resources/application.conf +++ b/kamon-playground/src/main/resources/application.conf @@ -22,7 +22,8 @@ kamon { metric { filters { trace.includes = [ "**" ] - actor.includes = [ "**" ] + akka-actor.includes = [ "**" ] + akka-dispatcher.includes = [ "**" ] } } @@ -31,10 +32,14 @@ kamon { license-key = e7d350b14228f3d28f35bc3140df2c3e565ea5d5 } + internal-config { + akka.loglevel = DEBUG + } + modules { kamon-newrelic.auto-start = no kamon-datadog.auto-start = no - kamon-log-reporter.auto-start = no + kamon-log-reporter.auto-start = yes kamon-system-metrics.auto-start = no } } diff --git a/kamon-playground/src/main/scala/test/SimpleRequestProcessor.scala b/kamon-playground/src/main/scala/test/SimpleRequestProcessor.scala index 141c8768..280158c0 100644 --- a/kamon-playground/src/main/scala/test/SimpleRequestProcessor.scala +++ b/kamon-playground/src/main/scala/test/SimpleRequestProcessor.scala @@ -37,8 +37,8 @@ object SimpleRequestProcessor extends App with SimpleRoutingApp with RequestBuil import scala.concurrent.duration._ - implicit val system = ActorSystem("test") Kamon.start() + implicit val system = ActorSystem("test") import test.SimpleRequestProcessor.system.dispatcher val printer = system.actorOf(Props[PrintWhatever]) @@ -49,7 +49,7 @@ object SimpleRequestProcessor extends App with SimpleRoutingApp with RequestBuil implicit val timeout = Timeout(30 seconds) - val counter = Kamon.simpleMetrics.counter("requests") + val counter = Kamon.metrics.counter("requests") val pipeline = sendReceive val replier = system.actorOf(Props[Replier].withRouter(RoundRobinPool(nrOfInstances = 4)), "replier") diff --git a/kamon-spray/src/main/scala/kamon/spray/SprayExtension.scala b/kamon-spray/src/main/scala/kamon/spray/SprayExtension.scala index ab0fe50b..a5aa33cb 100644 --- a/kamon-spray/src/main/scala/kamon/spray/SprayExtension.scala +++ b/kamon-spray/src/main/scala/kamon/spray/SprayExtension.scala @@ -46,11 +46,8 @@ class SprayExtensionImpl(system: ExtendedActorSystem) extends SprayExtension { val log = Logging(system, "SprayExtension") val httpServerMetrics = { - val metricsExtension = Kamon.metrics - val factory = metricsExtension.instrumentFactory(HttpServerMetrics.category) val entity = Entity("spray-server", HttpServerMetrics.category) - - metricsExtension.register(entity, new HttpServerMetrics(factory)).recorder + Kamon.metrics.entity(HttpServerMetrics, entity) } def generateTraceName(request: HttpRequest): String = diff --git a/kamon-spray/src/test/scala/kamon/spray/ClientRequestInstrumentationSpec.scala b/kamon-spray/src/test/scala/kamon/spray/ClientRequestInstrumentationSpec.scala index 67e6725f..4b99022e 100644 --- a/kamon-spray/src/test/scala/kamon/spray/ClientRequestInstrumentationSpec.scala +++ b/kamon-spray/src/test/scala/kamon/spray/ClientRequestInstrumentationSpec.scala @@ -34,8 +34,6 @@ import scala.concurrent.duration._ class ClientRequestInstrumentationSpec extends BaseKamonSpec("client-request-instrumentation-spec") with ScalaFutures with RequestBuilding with TestServer { - import TraceMetricsSpec.SegmentSyntax - override lazy val config = ConfigFactory.parseString( """ @@ -130,8 +128,14 @@ class ClientRequestInstrumentationSpec extends BaseKamonSpec("client-request-ins val traceMetricsSnapshot = takeSnapshotOf("assign-name-to-segment-with-request-level-api", "trace") traceMetricsSnapshot.histogram("elapsed-time").get.numberOfMeasurements should be(1) - traceMetricsSnapshot.segment("request-level /request-level-api-segment", SegmentCategory.HttpClient, Spray.SegmentLibraryName) - .numberOfMeasurements should be(1) + + val segmentMetricsSnapshot = takeSnapshotOf("request-level /request-level-api-segment", "trace-segment", + tags = Map( + "trace" -> "assign-name-to-segment-with-request-level-api", + "category" -> SegmentCategory.HttpClient, + "library" -> Spray.SegmentLibraryName)) + + segmentMetricsSnapshot.histogram("elapsed-time").get.numberOfMeasurements should be(1) } "rename a request level api segment once it reaches the relevant host connector" in { @@ -161,8 +165,14 @@ class ClientRequestInstrumentationSpec extends BaseKamonSpec("client-request-ins val traceMetricsSnapshot = takeSnapshotOf("rename-segment-with-request-level-api", "trace") traceMetricsSnapshot.histogram("elapsed-time").get.numberOfMeasurements should be(1) - traceMetricsSnapshot.segment("host-level /request-level-api-segment", SegmentCategory.HttpClient, Spray.SegmentLibraryName) - .numberOfMeasurements should be(1) + + val segmentMetricsSnapshot = takeSnapshotOf("host-level /request-level-api-segment", "trace-segment", + tags = Map( + "trace" -> "rename-segment-with-request-level-api", + "category" -> SegmentCategory.HttpClient, + "library" -> Spray.SegmentLibraryName)) + + segmentMetricsSnapshot.histogram("elapsed-time").get.numberOfMeasurements should be(1) } } @@ -249,8 +259,14 @@ class ClientRequestInstrumentationSpec extends BaseKamonSpec("client-request-ins val traceMetricsSnapshot = takeSnapshotOf("create-segment-with-host-level-api", "trace") traceMetricsSnapshot.histogram("elapsed-time").get.numberOfMeasurements should be(1) - traceMetricsSnapshot.segment("host-level /host-level-api-segment", SegmentCategory.HttpClient, Spray.SegmentLibraryName) - .numberOfMeasurements should be(1) + + val segmentMetricsSnapshot = takeSnapshotOf("host-level /host-level-api-segment", "trace-segment", + tags = Map( + "trace" -> "create-segment-with-host-level-api", + "category" -> SegmentCategory.HttpClient, + "library" -> Spray.SegmentLibraryName)) + + segmentMetricsSnapshot.histogram("elapsed-time").get.numberOfMeasurements should be(1) } } } diff --git a/kamon-statsd/src/main/resources/reference.conf b/kamon-statsd/src/main/resources/reference.conf index f26ce98b..63a784a4 100644 --- a/kamon-statsd/src/main/resources/reference.conf +++ b/kamon-statsd/src/main/resources/reference.conf @@ -20,11 +20,16 @@ kamon { # Subscription patterns used to select which metrics will be pushed to StatsD. Note that first, metrics # collection for your desired entities must be activated under the kamon.metrics.filters settings. subscriptions { - trace = [ "**" ] - actor = [ "**" ] - dispatcher = [ "**" ] - user-metric = [ "**" ] - system-metric = [ "**" ] + histogram = [ "**" ] + min-max-counter = [ "**" ] + gauge = [ "**" ] + counter = [ "**" ] + trace = [ "**" ] + trace-segment = [ "**" ] + akka-actor = [ "**" ] + akka-dispatcher = [ "**" ] + akka-router = [ "**" ] + system-metric = [ "**" ] } # Enable system metrics diff --git a/kamon-statsd/src/main/scala/kamon/statsd/SimpleMetricKeyGenerator.scala b/kamon-statsd/src/main/scala/kamon/statsd/SimpleMetricKeyGenerator.scala index 0fce855c..97a27ff3 100644 --- a/kamon-statsd/src/main/scala/kamon/statsd/SimpleMetricKeyGenerator.scala +++ b/kamon-statsd/src/main/scala/kamon/statsd/SimpleMetricKeyGenerator.scala @@ -3,7 +3,7 @@ package kamon.statsd import java.lang.management.ManagementFactory import com.typesafe.config.Config -import kamon.metric.{ MetricKey, Entity } +import kamon.metric.{ SingleInstrumentEntityRecorder, MetricKey, Entity } trait MetricKeyGenerator { def generateKey(entity: Entity, metricKey: MetricKey): String @@ -26,9 +26,16 @@ class SimpleMetricKeyGenerator(config: Config) extends MetricKeyGenerator { if (includeHostname) s"$application.$normalizedHostname" else application - def generateKey(entity: Entity, metricKey: MetricKey): String = { - val normalizedGroupName = normalizer(entity.name) - s"${baseName}.${entity.category}.${normalizedGroupName}.${metricKey.name}" + def generateKey(entity: Entity, metricKey: MetricKey): String = entity.category match { + case "trace-segment" ⇒ + s"${baseName}.trace.${normalizer(entity.tags("trace"))}.segments.${normalizer(entity.name)}.${metricKey.name}" + + case _ if SingleInstrumentEntityRecorder.AllCategories.contains(entity.category) ⇒ + s"${baseName}.${entity.category}.${normalizer(entity.name)}" + + case _ ⇒ + s"${baseName}.${entity.category}.${normalizer(entity.name)}.${metricKey.name}" + } def hostName: String = ManagementFactory.getRuntimeMXBean.getName.split('@')(1) diff --git a/kamon-statsd/src/main/scala/kamon/statsd/StatsD.scala b/kamon-statsd/src/main/scala/kamon/statsd/StatsD.scala index d406faf6..91d05510 100644 --- a/kamon-statsd/src/main/scala/kamon/statsd/StatsD.scala +++ b/kamon-statsd/src/main/scala/kamon/statsd/StatsD.scala @@ -43,7 +43,6 @@ class StatsDExtension(system: ExtendedActorSystem) extends Kamon.Extension { val metricsExtension = Kamon.metrics val tickInterval = metricsExtension.settings.tickInterval - val statsDHost = new InetSocketAddress(statsDConfig.getString("hostname"), statsDConfig.getInt("port")) val flushInterval = statsDConfig.getFiniteDuration("flush-interval") val maxPacketSizeInBytes = statsDConfig.getBytes("max-packet-size") val keyGeneratorFQCN = statsDConfig.getString("metric-key-generator") @@ -62,7 +61,8 @@ class StatsDExtension(system: ExtendedActorSystem) extends Kamon.Extension { val keyGenerator = system.dynamicAccess.createInstanceFor[MetricKeyGenerator](keyGeneratorFQCN, (classOf[Config], config) :: Nil).get val metricsSender = system.actorOf(StatsDMetricsSender.props( - statsDHost, + statsDConfig.getString("hostname"), + statsDConfig.getInt("port"), maxPacketSizeInBytes, keyGenerator), "statsd-metrics-sender") diff --git a/kamon-statsd/src/main/scala/kamon/statsd/StatsDMetricsSender.scala b/kamon-statsd/src/main/scala/kamon/statsd/StatsDMetricsSender.scala index 3241e1f3..70ff1b45 100644 --- a/kamon-statsd/src/main/scala/kamon/statsd/StatsDMetricsSender.scala +++ b/kamon-statsd/src/main/scala/kamon/statsd/StatsDMetricsSender.scala @@ -26,7 +26,7 @@ import java.util.Locale import kamon.metric.instrument.{ Counter, Histogram } -class StatsDMetricsSender(remote: InetSocketAddress, maxPacketSizeInBytes: Long, metricKeyGenerator: MetricKeyGenerator) +class StatsDMetricsSender(statsDHost: String, statsDPort: Int, maxPacketSizeInBytes: Long, metricKeyGenerator: MetricKeyGenerator) extends Actor with UdpExtensionProvider { import context.system @@ -38,6 +38,8 @@ class StatsDMetricsSender(remote: InetSocketAddress, maxPacketSizeInBytes: Long, udpExtension ! Udp.SimpleSender + def newSocketAddress = new InetSocketAddress(statsDHost, statsDPort) + def receive = { case Udp.SimpleSenderReady ⇒ context.become(ready(sender)) @@ -48,7 +50,7 @@ class StatsDMetricsSender(remote: InetSocketAddress, maxPacketSizeInBytes: Long, } def writeMetricsToRemote(tick: TickMetricSnapshot, udpSender: ActorRef): Unit = { - val packetBuilder = new MetricDataPacketBuilder(maxPacketSizeInBytes, udpSender, remote) + val packetBuilder = new MetricDataPacketBuilder(maxPacketSizeInBytes, udpSender, newSocketAddress) for ( (entity, snapshot) ← tick.metrics; @@ -80,8 +82,8 @@ class StatsDMetricsSender(remote: InetSocketAddress, maxPacketSizeInBytes: Long, } object StatsDMetricsSender { - def props(remote: InetSocketAddress, maxPacketSize: Long, metricKeyGenerator: MetricKeyGenerator): Props = - Props(new StatsDMetricsSender(remote, maxPacketSize, metricKeyGenerator)) + def props(statsDHost: String, statsDPort: Int, maxPacketSize: Long, metricKeyGenerator: MetricKeyGenerator): Props = + Props(new StatsDMetricsSender(statsDHost, statsDPort, maxPacketSize, metricKeyGenerator)) } trait UdpExtensionProvider { diff --git a/kamon-statsd/src/test/scala/kamon/statsd/SimpleMetricKeyGeneratorSpec.scala b/kamon-statsd/src/test/scala/kamon/statsd/SimpleMetricKeyGeneratorSpec.scala index 0edeb3df..2e03a59d 100644 --- a/kamon-statsd/src/test/scala/kamon/statsd/SimpleMetricKeyGeneratorSpec.scala +++ b/kamon-statsd/src/test/scala/kamon/statsd/SimpleMetricKeyGeneratorSpec.scala @@ -69,7 +69,7 @@ class SimpleMetricKeyGeneratorSpec extends WordSpec with Matchers { } def buildMetricKey(categoryName: String, entityName: String, metricName: String)(implicit metricKeyGenerator: SimpleMetricKeyGenerator): String = { - val metric = HistogramKey(metricName, UnitOfMeasurement.Unknown, Map.empty) + val metric = HistogramKey(metricName, UnitOfMeasurement.Unknown) val entity = Entity(entityName, categoryName) metricKeyGenerator.generateKey(entity, metric) } diff --git a/kamon-statsd/src/test/scala/kamon/statsd/StatsDMetricSenderSpec.scala b/kamon-statsd/src/test/scala/kamon/statsd/StatsDMetricSenderSpec.scala index 0211ac0f..51e7cf19 100644 --- a/kamon-statsd/src/test/scala/kamon/statsd/StatsDMetricSenderSpec.scala +++ b/kamon-statsd/src/test/scala/kamon/statsd/StatsDMetricSenderSpec.scala @@ -128,17 +128,17 @@ class StatsDMetricSenderSpec extends BaseKamonSpec("statsd-metric-sender-spec") val testEntity = Entity("user/kamon", "test") def buildMetricKey(entity: Entity, metricName: String)(implicit metricKeyGenerator: SimpleMetricKeyGenerator): String = { - val metricKey = HistogramKey(metricName, UnitOfMeasurement.Unknown, Map.empty) + val metricKey = HistogramKey(metricName, UnitOfMeasurement.Unknown) metricKeyGenerator.generateKey(entity, metricKey) } def buildRecorder(name: String): TestEntityRecorder = { - Kamon.metrics.register(TestEntityRecorder, name).get.recorder + Kamon.metrics.entity(TestEntityRecorder, name) } def setup(metrics: Map[Entity, EntitySnapshot]): TestProbe = { val udp = TestProbe() - val metricsSender = system.actorOf(Props(new StatsDMetricsSender(new InetSocketAddress("127.0.0.1", 0), testMaxPacketSize, metricKeyGenerator) { + val metricsSender = system.actorOf(Props(new StatsDMetricsSender("127.0.0.1", 0, testMaxPacketSize, metricKeyGenerator) { override def udpExtension(implicit system: ActorSystem): ActorRef = udp.ref })) diff --git a/kamon-system-metrics/src/main/scala/kamon/system/SystemMetricsExtension.scala b/kamon-system-metrics/src/main/scala/kamon/system/SystemMetricsExtension.scala index ebdaf01f..94c6fefa 100644 --- a/kamon-system-metrics/src/main/scala/kamon/system/SystemMetricsExtension.scala +++ b/kamon-system-metrics/src/main/scala/kamon/system/SystemMetricsExtension.scala @@ -16,18 +16,19 @@ package kamon.system import java.io.File +import akka.actor.SupervisorStrategy.Restart import akka.actor._ import akka.event.Logging -import kamon.supervisor.ModuleSupervisor import kamon.system.custom.{ ContextSwitchesUpdater, ContextSwitchesMetrics } import kamon.system.jmx._ import kamon.Kamon -import kamon.metric._ import kamon.sigar.SigarProvisioner import kamon.system.sigar.SigarMetricsUpdater import kamon.util.ConfigTools.Syntax +import scala.concurrent.duration.FiniteDuration + object SystemMetrics extends ExtensionId[SystemMetricsExtension] with ExtensionIdProvider { override def lookup(): ExtensionId[_ <: Extension] = SystemMetrics override def createExtension(system: ExtendedActorSystem): SystemMetricsExtension = new SystemMetricsExtension(system) @@ -44,10 +45,9 @@ class SystemMetricsExtension(system: ExtendedActorSystem) extends Kamon.Extensio val contextSwitchesRefreshInterval = config.getFiniteDuration("context-switches-refresh-interval") val metricsExtension = Kamon.metrics - // Sigar-based metrics SigarProvisioner.provision(new File(sigarFolder)) - val sigarMetricsRecorder = ModuleSupervisor.get(system).createModule("sigar-metrics-recorder", - SigarMetricsUpdater.props(sigarRefreshInterval).withDispatcher("kamon.system-metrics.sigar-dispatcher")) + + val supervisor = system.actorOf(SystemMetricsSupervisor.props(sigarRefreshInterval, contextSwitchesRefreshInterval), "kamon-system-metrics") // JMX Metrics ClassLoadingMetrics.register(metricsExtension) @@ -55,17 +55,32 @@ class SystemMetricsExtension(system: ExtendedActorSystem) extends Kamon.Extensio HeapMemoryMetrics.register(metricsExtension) NonHeapMemoryMetrics.register(metricsExtension) ThreadsMetrics.register(metricsExtension) +} + +class SystemMetricsSupervisor(sigarRefreshInterval: FiniteDuration, contextSwitchesRefreshInterval: FiniteDuration) extends Actor { + + // Sigar metrics recorder + context.actorOf(SigarMetricsUpdater.props(sigarRefreshInterval) + .withDispatcher("kamon.system-metrics.sigar-dispatcher"), "sigar-metrics-recorder") // If we are in Linux, add ContextSwitchesMetrics as well. if (isLinux) { - val contextSwitchesRecorder = ContextSwitchesMetrics.register(system, contextSwitchesRefreshInterval) - - ModuleSupervisor.get(system).createModule("context-switches-metrics-recorder", - ContextSwitchesUpdater.props(contextSwitchesRecorder, sigarRefreshInterval) - .withDispatcher("kamon.system-metrics.context-switches-dispatcher")) + val contextSwitchesRecorder = ContextSwitchesMetrics.register(context.system, contextSwitchesRefreshInterval) + context.actorOf(ContextSwitchesUpdater.props(contextSwitchesRecorder, sigarRefreshInterval) + .withDispatcher("kamon.system-metrics.context-switches-dispatcher"), "context-switches-metrics-recorder") } def isLinux: Boolean = System.getProperty("os.name").indexOf("Linux") != -1 + def receive = Actor.emptyBehavior + + override def supervisorStrategy: SupervisorStrategy = OneForOneStrategy() { + case anyException ⇒ Restart + } +} + +object SystemMetricsSupervisor { + def props(sigarRefreshInterval: FiniteDuration, contextSwitchesRefreshInterval: FiniteDuration): Props = + Props(new SystemMetricsSupervisor(sigarRefreshInterval, contextSwitchesRefreshInterval)) }
\ No newline at end of file diff --git a/kamon-system-metrics/src/main/scala/kamon/system/custom/ContextSwitchesMetrics.scala b/kamon-system-metrics/src/main/scala/kamon/system/custom/ContextSwitchesMetrics.scala index 384c89f1..5090f3dd 100644 --- a/kamon-system-metrics/src/main/scala/kamon/system/custom/ContextSwitchesMetrics.scala +++ b/kamon-system-metrics/src/main/scala/kamon/system/custom/ContextSwitchesMetrics.scala @@ -88,12 +88,10 @@ class ContextSwitchesMetrics(pid: Long, log: LoggingAdapter, instrumentFactory: object ContextSwitchesMetrics { def register(system: ActorSystem, refreshInterval: FiniteDuration): ContextSwitchesMetrics = { - val metricsExtension = Kamon.metrics val log = Logging(system, "ContextSwitchesMetrics") val pid = (new Sigar).getPid - val instrumentFactory = metricsExtension.instrumentFactory("system-metric") - metricsExtension.register(Entity("context-switches", "system-metric"), new ContextSwitchesMetrics(pid, log, instrumentFactory)).recorder + Kamon.metrics.entity(EntityRecorderFactory("system-metric", new ContextSwitchesMetrics(pid, log, _)), "context-switches") } } diff --git a/kamon-system-metrics/src/main/scala/kamon/system/jmx/GarbageCollectionMetrics.scala b/kamon-system-metrics/src/main/scala/kamon/system/jmx/GarbageCollectionMetrics.scala index 59d4050c..7a5770d8 100644 --- a/kamon-system-metrics/src/main/scala/kamon/system/jmx/GarbageCollectionMetrics.scala +++ b/kamon-system-metrics/src/main/scala/kamon/system/jmx/GarbageCollectionMetrics.scala @@ -18,7 +18,8 @@ package kamon.system.jmx import java.lang.management.{ GarbageCollectorMXBean, ManagementFactory } -import kamon.metric.{ Entity, Metrics, GenericEntityRecorder } +import kamon.Kamon +import kamon.metric.{ EntityRecorderFactory, Entity, Metrics, GenericEntityRecorder } import kamon.metric.instrument.{ DifferentialValueCollector, Time, InstrumentFactory } import scala.collection.JavaConverters._ @@ -44,11 +45,9 @@ object GarbageCollectionMetrics { name.replaceAll("""[^\w]""", "-").toLowerCase def register(metricsExtension: Metrics): Unit = { - - val instrumentFactory = metricsExtension.instrumentFactory("system-metric") ManagementFactory.getGarbageCollectorMXBeans.asScala.filter(_.isValid) map { gc ⇒ val gcName = sanitizeCollectorName(gc.getName) - metricsExtension.register(Entity(s"$gcName-garbage-collector", "system-metric"), new GarbageCollectionMetrics(gc, instrumentFactory)) + Kamon.metrics.entity(EntityRecorderFactory("system-metric", new GarbageCollectionMetrics(gc, _)), s"$gcName-garbage-collector") } } } diff --git a/kamon-system-metrics/src/main/scala/kamon/system/jmx/JmxSystemMetricRecorderCompanion.scala b/kamon-system-metrics/src/main/scala/kamon/system/jmx/JmxSystemMetricRecorderCompanion.scala index 8837aec0..15bd399e 100644 --- a/kamon-system-metrics/src/main/scala/kamon/system/jmx/JmxSystemMetricRecorderCompanion.scala +++ b/kamon-system-metrics/src/main/scala/kamon/system/jmx/JmxSystemMetricRecorderCompanion.scala @@ -17,13 +17,11 @@ package kamon.system.jmx import kamon.metric.instrument.InstrumentFactory -import kamon.metric.{ Entity, EntityRecorder, Metrics } +import kamon.metric.{ EntityRecorderFactory, EntityRecorder, Metrics } abstract class JmxSystemMetricRecorderCompanion(metricName: String) { - def register(metricsExtension: Metrics): EntityRecorder = { - val instrumentFactory = metricsExtension.instrumentFactory("system-metric") - metricsExtension.register(Entity(metricName, "system-metric"), apply(instrumentFactory)).recorder - } + def register(metricsExtension: Metrics): EntityRecorder = + metricsExtension.entity(EntityRecorderFactory("system-metric", apply(_)), metricName) def apply(instrumentFactory: InstrumentFactory): EntityRecorder }
\ No newline at end of file diff --git a/kamon-system-metrics/src/main/scala/kamon/system/sigar/SigarMetricsUpdater.scala b/kamon-system-metrics/src/main/scala/kamon/system/sigar/SigarMetricsUpdater.scala index 68b133b0..a1bd3e01 100644 --- a/kamon-system-metrics/src/main/scala/kamon/system/sigar/SigarMetricsUpdater.scala +++ b/kamon-system-metrics/src/main/scala/kamon/system/sigar/SigarMetricsUpdater.scala @@ -19,7 +19,7 @@ package kamon.system.sigar import akka.actor.{ Props, Actor } import kamon.Kamon import kamon.metric.instrument.InstrumentFactory -import kamon.metric.{ Entity, EntityRecorder, Metrics } +import kamon.metric.{ EntityRecorderFactory, Entity, EntityRecorder, Metrics } import kamon.system.sigar.SigarMetricsUpdater.UpdateSigarMetrics import org.hyperic.sigar.Sigar @@ -65,10 +65,8 @@ trait SigarMetric extends EntityRecorder { } abstract class SigarMetricRecorderCompanion(metricName: String) { - def register(sigar: Sigar, metricsExtension: Metrics): SigarMetric = { - val instrumentFactory = metricsExtension.instrumentFactory("system-metric") - metricsExtension.register(Entity(metricName, "system-metric"), apply(sigar, instrumentFactory)).recorder - } + def register(sigar: Sigar, metricsExtension: Metrics): SigarMetric = + metricsExtension.entity(EntityRecorderFactory("system-metric", apply(sigar, _)), metricName) def apply(sigar: Sigar, instrumentFactory: InstrumentFactory): SigarMetric } diff --git a/project/Dependencies.scala b/project/Dependencies.scala index b9bd89e7..8d3e57fb 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -22,40 +22,40 @@ object Dependencies { "typesafe repo" at "http://repo.typesafe.com/typesafe/releases/" ) - val sprayVersion = "1.3.2" - val akkaVersion = "2.3.9" - val aspectjVersion = "1.8.4" - val slf4jVersion = "1.7.7" - val playVersion = "2.3.5" - val sigarVersion = "1.6.5.132" + val sprayVersion = "1.3.2" + val akkaVersion = "2.3.9" + val aspectjVersion = "1.8.5" + val slf4jVersion = "1.7.7" + val playVersion = "2.3.8" + val sigarVersion = "1.6.5.132" - val sprayJson = "io.spray" %% "spray-json" % "1.3.1" - val sprayJsonLenses = "net.virtual-void" %% "json-lenses" % "0.6.0" - val scalatest = "org.scalatest" %% "scalatest" % "2.2.1" - val logback = "ch.qos.logback" % "logback-classic" % "1.0.13" - val aspectJ = "org.aspectj" % "aspectjweaver" % aspectjVersion - val newrelic = "com.newrelic.agent.java" % "newrelic-api" % "3.11.0" - val hdrHistogram = "org.hdrhistogram" % "HdrHistogram" % "2.1.3" - val sprayCan = "io.spray" %% "spray-can" % sprayVersion - val sprayRouting = "io.spray" %% "spray-routing" % sprayVersion - val sprayTestkit = "io.spray" %% "spray-testkit" % sprayVersion - val sprayClient = "io.spray" %% "spray-client" % sprayVersion - val akkaActor = "com.typesafe.akka" %% "akka-actor" % akkaVersion - val akkaSlf4j = "com.typesafe.akka" %% "akka-slf4j" % akkaVersion - val akkaTestKit = "com.typesafe.akka" %% "akka-testkit" % akkaVersion - val akkaRemote = "com.typesafe.akka" %% "akka-remote" % akkaVersion - val akkaCluster = "com.typesafe.akka" %% "akka-cluster" % akkaVersion - val play = "com.typesafe.play" %% "play" % playVersion - val playWS = "com.typesafe.play" %% "play-ws" % playVersion - val playTest = "org.scalatestplus" %% "play" % "1.2.0" - val slf4Api = "org.slf4j" % "slf4j-api" % slf4jVersion - val slf4nop = "org.slf4j" % "slf4j-nop" % slf4jVersion - val slf4Jul = "org.slf4j" % "jul-to-slf4j" % slf4jVersion - val slf4Log4j = "org.slf4j" % "log4j-over-slf4j" % slf4jVersion - val scalaCompiler = "org.scala-lang" % "scala-compiler" % Settings.ScalaVersion - val scalazConcurrent = "org.scalaz" %% "scalaz-concurrent" % "7.1.0" - val sigarLoader = "io.kamon" % "sigar-loader" % "1.6.5-rev001" - val h2 = "com.h2database" % "h2" % "1.4.182" + val sprayJson = "io.spray" %% "spray-json" % "1.3.1" + val sprayJsonLenses = "net.virtual-void" %% "json-lenses" % "0.6.0" + val scalatest = "org.scalatest" %% "scalatest" % "2.2.1" + val logback = "ch.qos.logback" % "logback-classic" % "1.0.13" + val aspectJ = "org.aspectj" % "aspectjweaver" % aspectjVersion + val newrelic = "com.newrelic.agent.java" % "newrelic-api" % "3.11.0" + val hdrHistogram = "org.hdrhistogram" % "HdrHistogram" % "2.1.4" + val sprayCan = "io.spray" %% "spray-can" % sprayVersion + val sprayRouting = "io.spray" %% "spray-routing" % sprayVersion + val sprayTestkit = "io.spray" %% "spray-testkit" % sprayVersion + val sprayClient = "io.spray" %% "spray-client" % sprayVersion + val akkaActor = "com.typesafe.akka" %% "akka-actor" % akkaVersion + val akkaSlf4j = "com.typesafe.akka" %% "akka-slf4j" % akkaVersion + val akkaTestKit = "com.typesafe.akka" %% "akka-testkit" % akkaVersion + val akkaRemote = "com.typesafe.akka" %% "akka-remote" % akkaVersion + val akkaCluster = "com.typesafe.akka" %% "akka-cluster" % akkaVersion + val play = "com.typesafe.play" %% "play" % playVersion + val playWS = "com.typesafe.play" %% "play-ws" % playVersion + val playTest = "org.scalatestplus" %% "play" % "1.2.0" + val slf4Api = "org.slf4j" % "slf4j-api" % slf4jVersion + val slf4nop = "org.slf4j" % "slf4j-nop" % slf4jVersion + val slf4Jul = "org.slf4j" % "jul-to-slf4j" % slf4jVersion + val slf4Log4j = "org.slf4j" % "log4j-over-slf4j" % slf4jVersion + val scalazConcurrent = "org.scalaz" %% "scalaz-concurrent" % "7.1.0" + val sigarLoader = "io.kamon" % "sigar-loader" % "1.6.5-rev001" + val h2 = "com.h2database" % "h2" % "1.4.182" + val el = "org.glassfish" % "javax.el" % "3.0.0" def compile (deps: ModuleID*): Seq[ModuleID] = deps map (_ % "compile") def provided (deps: ModuleID*): Seq[ModuleID] = deps map (_ % "provided") diff --git a/project/Projects.scala b/project/Projects.scala index d52a7764..96cc239e 100644 --- a/project/Projects.scala +++ b/project/Projects.scala @@ -22,8 +22,8 @@ object Projects extends Build { import Dependencies._ lazy val kamon = Project("kamon", file(".")) - .aggregate(kamonCore, kamonSpray, kamonNewrelic, kamonPlayground, kamonDashboard, kamonTestkit, kamonPlay, kamonStatsD, - kamonDatadog, kamonSystemMetrics, kamonLogReporter, kamonAkkaRemote, kamonJdbc) + .aggregate(kamonCore, kamonScala, kamonAkka, kamonSpray, kamonNewrelic, kamonPlayground, kamonDashboard, kamonTestkit, + kamonPlay, kamonStatsD, kamonDatadog, kamonSystemMetrics, kamonLogReporter, kamonAkkaRemote, kamonJdbc, kamonAnnotation) .settings(basicSettings: _*) .settings(formatSettings: _*) .settings(noPublishing: _*) @@ -193,5 +193,16 @@ object Projects extends Build { test(h2,scalatest, akkaTestKit, slf4Api) ++ provided(aspectJ)) + lazy val kamonAnnotation = Project("kamon-annotation", file("kamon-annotation")) + .dependsOn(kamonCore % "compile->compile;test->test") + .settings(basicSettings: _*) + .settings(formatSettings: _*) + .settings(aspectJSettings: _*) + .settings( + libraryDependencies ++= + compile(el) ++ + test(scalatest, akkaTestKit, slf4Api) ++ + provided(aspectJ)) + val noPublishing = Seq(publish := (), publishLocal := (), publishArtifact := false) -} +}
\ No newline at end of file diff --git a/project/Publish.scala b/project/Publish.scala index 025e258e..ddb64d2d 100644 --- a/project/Publish.scala +++ b/project/Publish.scala @@ -19,7 +19,7 @@ import sbt.Keys._ object Publish { lazy val settings = Seq( - crossPaths := false, + crossPaths := true, pomExtra := kamonPomExtra, publishTo := kamonRepo, organization := "io.kamon", diff --git a/project/Settings.scala b/project/Settings.scala index 3758fb15..675d99f2 100644 --- a/project/Settings.scala +++ b/project/Settings.scala @@ -27,19 +27,16 @@ object Settings { val JavaVersion = "1.6" val ScalaVersion = "2.11.5" - + lazy val basicSettings = Seq( - scalaVersion := ScalaVersion, + crossScalaVersions := Seq("2.10.5", "2.11.6"), resolvers ++= Dependencies.resolutionRepos, fork in run := true, + parallelExecution in Test := false, testGrouping in Test := singleTestPerJvm((definedTests in Test).value, (javaOptions in Test).value), - javacOptions in compile := Seq( + javacOptions := Seq( "-Xlint:-options", - "-source", JavaVersion, "-target", JavaVersion - ), - javacOptions in doc := Seq( - "-source", JavaVersion - ), + "-source", JavaVersion, "-target", JavaVersion), scalacOptions := Seq( "-encoding", "utf8", @@ -75,4 +72,4 @@ object Settings { .setPreference(AlignParameters, false) .setPreference(AlignSingleLineCaseStatements, true) .setPreference(DoubleIndentClassDeclaration, true) -}
\ No newline at end of file +} diff --git a/project/plugins.sbt b/project/plugins.sbt index abd5e2a8..0b0fb5e7 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -4,7 +4,7 @@ resolvers += "Kamon Releases" at "http://repo.kamon.io" addSbtPlugin("com.github.mpeltonen" % "sbt-idea" % "1.6.0") -addSbtPlugin("com.typesafe.sbt" % "sbt-aspectj" % "0.10.0") +addSbtPlugin("com.typesafe.sbt" % "sbt-aspectj" % "0.10.1") addSbtPlugin("com.github.gseitz" % "sbt-release" % "0.8.2") |