From eafc9d454afb7f8fc601a83c75a267165137b6e0 Mon Sep 17 00:00:00 2001 From: Jason Martens Date: Wed, 5 Apr 2017 16:33:08 -0700 Subject: + MetricsModule: Add default metrics loaded from config --- kamon-core/src/main/resources/reference.conf | 11 ++++++ .../main/scala/kamon/metric/MetricsModule.scala | 42 +++++++++++++++------- kamon-core/src/test/resources/application.conf | 11 ++++++ .../kamon/metric/SubscriptionsProtocolSpec.scala | 31 ++++++++++++---- 4 files changed, 76 insertions(+), 19 deletions(-) diff --git a/kamon-core/src/main/resources/reference.conf b/kamon-core/src/main/resources/reference.conf index 69e8c54f..f687bfb8 100644 --- a/kamon-core/src/main/resources/reference.conf +++ b/kamon-core/src/main/resources/reference.conf @@ -167,4 +167,15 @@ kamon { # Just a place holder to ensure that the key is always available. Non-core Kamon modules should provide their # settings in a module-info section. } + + # Add tags to all reported metrics. Can be useful to identify the source of metrics from a particluar JVM instance. + # Example: + # + # default-tags { + # host: ${?HOSTNAME} + # container-name: ${?CONTAINER_NAME} + # } + default-tags { + + } } \ No newline at end of file diff --git a/kamon-core/src/main/scala/kamon/metric/MetricsModule.scala b/kamon-core/src/main/scala/kamon/metric/MetricsModule.scala index 864b7a0b..7c85bb02 100755 --- a/kamon-core/src/main/scala/kamon/metric/MetricsModule.scala +++ b/kamon-core/src/main/scala/kamon/metric/MetricsModule.scala @@ -16,14 +16,17 @@ package kamon.metric +import java.util.Map.Entry + import akka.actor._ -import com.typesafe.config.Config +import com.typesafe.config.{Config, ConfigValue, ConfigValueType} 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.JavaConverters._ import scala.collection.concurrent.TrieMap import scala.concurrent.duration.FiniteDuration @@ -236,6 +239,21 @@ private[kamon] class MetricsModuleImpl(config: Config) extends MetricsModule { @volatile var settings = MetricsSettings(config) + val defaultTags: Map[String, String] = if (config.hasPath("kamon.default-tags")) { + config.getConfig("kamon.default-tags").resolve().entrySet().asScala + .collect { + case e: Entry[String, ConfigValue] if e.getValue.valueType() == ConfigValueType.STRING => + (e.getKey, e.getValue.unwrapped().asInstanceOf[String]) + case e: Entry[String, ConfigValue] if e.getValue.valueType() == ConfigValueType.NUMBER => + (e.getKey, e.getValue.unwrapped().asInstanceOf[Int].toString) + case e: Entry[String, ConfigValue] if e.getValue.valueType() == ConfigValueType.BOOLEAN => + (e.getKey, e.getValue.unwrapped().asInstanceOf[Boolean].toString) + }.toMap + } + else { + Map.empty + } + def shouldTrack(entity: Entity): Boolean = settings.entityFilters.get(entity.category).map { filter ⇒ filter.accept(entity.name) @@ -245,7 +263,7 @@ private[kamon] class MetricsModuleImpl(config: Config) extends MetricsModule { def registerHistogram(name: String, tags: Map[String, String], unitOfMeasurement: Option[UnitOfMeasurement], dynamicRange: Option[DynamicRange]): Histogram = { - val histogramEntity = Entity(name, SingleInstrumentEntityRecorder.Histogram, tags) + val histogramEntity = Entity(name, SingleInstrumentEntityRecorder.Histogram, tags ++ defaultTags) val recorder = _trackedEntities.atomicGetOrElseUpdate(histogramEntity, { val factory = instrumentFactory(histogramEntity.category) HistogramRecorder( @@ -258,12 +276,12 @@ private[kamon] class MetricsModuleImpl(config: Config) extends MetricsModule { } def removeHistogram(name: String, tags: Map[String, String]): Boolean = - _trackedEntities.remove(Entity(name, SingleInstrumentEntityRecorder.Histogram, tags)).isDefined + _trackedEntities.remove(Entity(name, SingleInstrumentEntityRecorder.Histogram, tags ++ defaultTags)).isDefined 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 minMaxCounterEntity = Entity(name, SingleInstrumentEntityRecorder.MinMaxCounter, tags ++ defaultTags) val recorder = _trackedEntities.atomicGetOrElseUpdate(minMaxCounterEntity, { val factory = instrumentFactory(minMaxCounterEntity.category) MinMaxCounterRecorder( @@ -276,13 +294,13 @@ private[kamon] class MetricsModuleImpl(config: Config) extends MetricsModule { } def removeMinMaxCounter(name: String, tags: Map[String, String]): Boolean = - _trackedEntities.remove(Entity(name, SingleInstrumentEntityRecorder.MinMaxCounter, tags)).isDefined + _trackedEntities.remove(Entity(name, SingleInstrumentEntityRecorder.MinMaxCounter, tags ++ defaultTags)).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 gaugeEntity = Entity(name, SingleInstrumentEntityRecorder.Gauge, tags ++ defaultTags) val recorder = _trackedEntities.atomicGetOrElseUpdate(gaugeEntity, { val factory = instrumentFactory(gaugeEntity.category) GaugeRecorder( @@ -295,12 +313,12 @@ private[kamon] class MetricsModuleImpl(config: Config) extends MetricsModule { } def removeGauge(name: String, tags: Map[String, String]): Boolean = - _trackedEntities.remove(Entity(name, SingleInstrumentEntityRecorder.Gauge, tags)).isDefined + _trackedEntities.remove(Entity(name, SingleInstrumentEntityRecorder.Gauge, tags ++ defaultTags)).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 counterEntity = Entity(name, SingleInstrumentEntityRecorder.Counter, tags ++ defaultTags) val recorder = _trackedEntities.atomicGetOrElseUpdate(counterEntity, { val factory = instrumentFactory(counterEntity.category) CounterRecorder( @@ -313,22 +331,22 @@ private[kamon] class MetricsModuleImpl(config: Config) extends MetricsModule { } def removeCounter(name: String, tags: Map[String, String]): Boolean = - _trackedEntities.remove(Entity(name, SingleInstrumentEntityRecorder.Counter, tags)).isDefined + _trackedEntities.remove(Entity(name, SingleInstrumentEntityRecorder.Counter, tags ++ defaultTags)).isDefined def entity[T <: EntityRecorder](recorderFactory: EntityRecorderFactory[T], entity: Entity): T = { - _trackedEntities.atomicGetOrElseUpdate(entity, { + _trackedEntities.atomicGetOrElseUpdate(entity.copy(tags = entity.tags ++ defaultTags), { recorderFactory.createRecorder(instrumentFactory(recorderFactory.category)) }, _.cleanup).asInstanceOf[T] } def removeEntity(entity: Entity): Boolean = { - val removedEntity = _trackedEntities.remove(entity) + val removedEntity = _trackedEntities.remove(entity.copy(tags = entity.tags ++ defaultTags)) removedEntity.foreach(_.cleanup) removedEntity.isDefined } def find(entity: Entity): Option[EntityRecorder] = - _trackedEntities.get(entity) + _trackedEntities.get(entity.copy(tags = entity.tags ++ defaultTags)) def subscribe(filter: SubscriptionFilter, subscriber: ActorRef, permanent: Boolean): Unit = _subscriptions.tell(Subscribe(filter, subscriber, permanent)) diff --git a/kamon-core/src/test/resources/application.conf b/kamon-core/src/test/resources/application.conf index f79ab822..bf6123d9 100644 --- a/kamon-core/src/test/resources/application.conf +++ b/kamon-core/src/test/resources/application.conf @@ -12,4 +12,15 @@ kamon { level-of-detail = simple-trace sampling = all } + + default-tags = { + name = "jason" + number = 42 + username = ${USER} + list = [1, 2, 3] // lists do not make sense for a tag + object = { + nested-bool = true + nested-string = "a string" + } + } } \ No newline at end of file diff --git a/kamon-core/src/test/scala/kamon/metric/SubscriptionsProtocolSpec.scala b/kamon-core/src/test/scala/kamon/metric/SubscriptionsProtocolSpec.scala index 36cc62c3..ee34acc7 100644 --- a/kamon-core/src/test/scala/kamon/metric/SubscriptionsProtocolSpec.scala +++ b/kamon-core/src/test/scala/kamon/metric/SubscriptionsProtocolSpec.scala @@ -36,6 +36,7 @@ class SubscriptionsProtocolSpec extends BaseKamonSpec("subscriptions-protocol-sp lazy val metricsModule = Kamon.metrics import metricsModule.{entity, subscribe, unsubscribe} + val defaultTags: Map[String, String] = Kamon.metrics.defaultTags "the Subscriptions messaging protocol" should { "allow subscribing for a single tick" in { @@ -47,12 +48,28 @@ class SubscriptionsProtocolSpec extends BaseKamonSpec("subscriptions-protocol-sp val tickSnapshot = subscriber.expectMsgType[TickMetricSnapshot] tickSnapshot.metrics.size should be(1) - tickSnapshot.metrics.keys should contain(Entity("one-shot", "trace")) + tickSnapshot.metrics.keys should contain(Entity("one-shot", "trace", defaultTags)) flushSubscriptions() subscriber.expectNoMsg(1 second) } + "subscriptions should include default tags" in { + val subscriber = TestProbe() + + Kamon.metrics.histogram("histogram-with-tags").record(1) + Kamon.metrics.subscribe("**", "**", subscriber.ref, permanently = true) + flushSubscriptions() + + val tickSubscription = subscriber.expectMsgType[TickMetricSnapshot] + tickSubscription.metrics.head._1.tags.get("name") shouldBe Some("jason") + tickSubscription.metrics.head._1.tags.get("number") shouldBe Some("42") + tickSubscription.metrics.head._1.tags.get("username").isDefined shouldBe true + tickSubscription.metrics.head._1.tags.get("object.nested-bool") shouldBe Some("true") + tickSubscription.metrics.head._1.tags.get("object.nested-string") shouldBe Some("a string") + tickSubscription.metrics.head._1.tags.get("list") shouldBe None + } + "allow subscribing permanently to a metric" in { val subscriber = TestProbe() entity(TraceMetrics, "permanent") @@ -63,7 +80,7 @@ class SubscriptionsProtocolSpec extends BaseKamonSpec("subscriptions-protocol-sp val tickSnapshot = subscriber.expectMsgType[TickMetricSnapshot] tickSnapshot.metrics.size should be(1) - tickSnapshot.metrics.keys should contain(Entity("permanent", "trace")) + tickSnapshot.metrics.keys should contain(Entity("permanent", "trace", defaultTags)) } } @@ -79,8 +96,8 @@ class SubscriptionsProtocolSpec extends BaseKamonSpec("subscriptions-protocol-sp val tickSnapshot = subscriber.expectMsgType[TickMetricSnapshot] tickSnapshot.metrics.size should be(2) - tickSnapshot.metrics.keys should contain(Entity("include-one", "trace")) - tickSnapshot.metrics.keys should contain(Entity("include-three", "trace")) + tickSnapshot.metrics.keys should contain(Entity("include-one", "trace", defaultTags)) + tickSnapshot.metrics.keys should contain(Entity("include-three", "trace", defaultTags)) } } @@ -97,8 +114,8 @@ class SubscriptionsProtocolSpec extends BaseKamonSpec("subscriptions-protocol-sp val tickSnapshot = subscriber.expectMsgType[TickMetricSnapshot] tickSnapshot.metrics.size should be(2) - tickSnapshot.metrics.keys should contain(Entity("include-one", "trace")) - tickSnapshot.metrics.keys should contain(Entity("include-three", "trace")) + tickSnapshot.metrics.keys should contain(Entity("include-one", "trace", defaultTags)) + tickSnapshot.metrics.keys should contain(Entity("include-three", "trace", defaultTags)) } } @@ -110,7 +127,7 @@ class SubscriptionsProtocolSpec extends BaseKamonSpec("subscriptions-protocol-sp flushSubscriptions() val tickSnapshot = subscriber.expectMsgType[TickMetricSnapshot] tickSnapshot.metrics.size should be(1) - tickSnapshot.metrics.keys should contain(Entity("one-shot", "trace")) + tickSnapshot.metrics.keys should contain(Entity("one-shot", "trace", defaultTags)) unsubscribe(subscriber.ref) -- cgit v1.2.3