aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorIvan Topolnjak <ivantopo@gmail.com>2017-05-20 14:06:03 +0200
committerIvan Topolnjak <ivantopo@gmail.com>2017-05-20 14:06:03 +0200
commite1e7853255131f26702229735e37e160c38f2d08 (patch)
tree98202bb2b62a9c0dd29d0fb7f020da232703844a
parent77f2666650726352a9e15dcf6019064d91393b2e (diff)
downloadKamon-e1e7853255131f26702229735e37e160c38f2d08.tar.gz
Kamon-e1e7853255131f26702229735e37e160c38f2d08.tar.bz2
Kamon-e1e7853255131f26702229735e37e160c38f2d08.zip
implement entity filters
-rw-r--r--build.sbt2
-rw-r--r--kamon-core/src/legacy-main/scala/kamon/metric/MetricsSettings.scala5
-rw-r--r--kamon-core/src/legacy-main/scala/kamon/metric/SubscriptionsDispatcher.scala2
-rw-r--r--kamon-core/src/legacy-main/scala/kamon/util/RegexPathFilter.scala4
-rw-r--r--kamon-core/src/legacy-test/scala/kamon/util/GlobPathFilterSpec.scala1
-rw-r--r--kamon-core/src/legacy-test/scala/kamon/util/RegexPathFilterSpec.scala7
-rw-r--r--kamon-core/src/main/java/kamon/util/GlobPathFilter.java110
-rw-r--r--kamon-core/src/main/resources/reference.conf4
-rw-r--r--kamon-core/src/main/scala/kamon/Util.scala2
-rw-r--r--kamon-core/src/main/scala/kamon/metric/EntityFilter.scala110
-rw-r--r--kamon-core/src/main/scala/kamon/metric/RecorderRegistry.scala22
-rw-r--r--kamon-core/src/main/scala/kamon/metric/instrument/InstrumentFactory.scala14
-rw-r--r--kamon-core/src/main/scala/kamon/trace/Span.scala6
-rw-r--r--kamon-core/src/main/scala/kamon/trace/SpanContext.scala9
-rw-r--r--kamon-core/src/main/scala/kamon/util/EntityFilter.scala7
-rw-r--r--kamon-core/src/test/scala/kamon/metric/EntityFilterSpec.scala69
-rw-r--r--kamon-core/src/test/scala/kamon/metric/GlobPathFilterSpec.scala71
-rw-r--r--kamon-core/src/test/scala/kamon/metric/RecorderRegistrySpec.scala43
-rw-r--r--kamon-core/src/test/scala/kamon/metric/RegexPathFilterSpec.scala60
-rw-r--r--kamon-core/src/test/scala/kamon/metric/instrument/InstrumentFactorySpec.scala36
-rw-r--r--kamon-core/src/test/scala/kamon/testkit/DefaultInstrumentFactory.scala2
-rw-r--r--project/plugins.sbt1
22 files changed, 419 insertions, 168 deletions
diff --git a/build.sbt b/build.sbt
index 99e5e8aa..ebde5423 100644
--- a/build.sbt
+++ b/build.sbt
@@ -23,6 +23,7 @@ lazy val core = (project in file("kamon-core"))
.settings(moduleName := "kamon-core")
.settings(
scalaVersion := "2.12.1",
+ coverageEnabled := true,
resolvers += Resolver.mavenLocal,
libraryDependencies ++= Seq(
"com.typesafe" % "config" % "1.3.1",
@@ -31,6 +32,7 @@ lazy val core = (project in file("kamon-core"))
"org.hdrhistogram" % "HdrHistogram" % "2.1.9",
"io.opentracing" % "opentracing-api" % "0.30.0.RC2",
"io.opentracing" % "opentracing-util" % "0.30.0.RC2",
+ "com.lihaoyi" %% "fansi" % "0.2.4",
"uk.org.lidalia" % "slf4j-test" % "1.1.0",
"org.scalatest" %% "scalatest" % "3.0.1" % "test"
diff --git a/kamon-core/src/legacy-main/scala/kamon/metric/MetricsSettings.scala b/kamon-core/src/legacy-main/scala/kamon/metric/MetricsSettings.scala
index 592e8f67..86968597 100644
--- a/kamon-core/src/legacy-main/scala/kamon/metric/MetricsSettings.scala
+++ b/kamon-core/src/legacy-main/scala/kamon/metric/MetricsSettings.scala
@@ -18,9 +18,8 @@ package kamon.metric
import com.typesafe.config.Config
import kamon.metric.instrument._
-import kamon.util.PathFilter
import kamon.util.GlobPathFilter
-import kamon.util.RegexPathFilter
+import kamon.util.RegexNameFilter
import scala.concurrent.duration.FiniteDuration
@@ -46,7 +45,7 @@ case class MetricsSettings(
/**
*
*/
-case class EntityFilter(includes: List[PathFilter], excludes: List[PathFilter]) {
+case class EntityFilter(includes: List[NameFilter], excludes: List[NameFilter]) {
def accept(name: String): Boolean =
includes.exists(_.accept(name)) && !excludes.exists(_.accept(name))
}
diff --git a/kamon-core/src/legacy-main/scala/kamon/metric/SubscriptionsDispatcher.scala b/kamon-core/src/legacy-main/scala/kamon/metric/SubscriptionsDispatcher.scala
index 09bf58ad..28bf77db 100644
--- a/kamon-core/src/legacy-main/scala/kamon/metric/SubscriptionsDispatcher.scala
+++ b/kamon-core/src/legacy-main/scala/kamon/metric/SubscriptionsDispatcher.scala
@@ -18,7 +18,7 @@ package kamon.metric
import akka.actor._
import kamon.metric.SubscriptionsDispatcher._
-import kamon.util.{MilliTimestamp, GlobPathFilter}
+import kamon.util.MilliTimestamp
import scala.concurrent.duration.FiniteDuration
/**
diff --git a/kamon-core/src/legacy-main/scala/kamon/util/RegexPathFilter.scala b/kamon-core/src/legacy-main/scala/kamon/util/RegexPathFilter.scala
index 848fca87..24e66a08 100644
--- a/kamon-core/src/legacy-main/scala/kamon/util/RegexPathFilter.scala
+++ b/kamon-core/src/legacy-main/scala/kamon/util/RegexPathFilter.scala
@@ -16,7 +16,9 @@
package kamon.util
-case class RegexPathFilter(path: String) extends PathFilter {
+import kamon.metric.NameFilter
+
+case class RegexPathFilter(path: String) extends NameFilter {
private val pathRegex = path.r
override def accept(path: String): Boolean = {
path match {
diff --git a/kamon-core/src/legacy-test/scala/kamon/util/GlobPathFilterSpec.scala b/kamon-core/src/legacy-test/scala/kamon/util/GlobPathFilterSpec.scala
index 7d585087..ee195ed2 100644
--- a/kamon-core/src/legacy-test/scala/kamon/util/GlobPathFilterSpec.scala
+++ b/kamon-core/src/legacy-test/scala/kamon/util/GlobPathFilterSpec.scala
@@ -16,6 +16,7 @@
package kamon.util
+import kamon.metric.GlobPathFilter
import org.scalatest.{Matchers, WordSpecLike}
class GlobPathFilterSpec extends WordSpecLike with Matchers {
diff --git a/kamon-core/src/legacy-test/scala/kamon/util/RegexPathFilterSpec.scala b/kamon-core/src/legacy-test/scala/kamon/util/RegexPathFilterSpec.scala
index a2cc8629..f0da5c55 100644
--- a/kamon-core/src/legacy-test/scala/kamon/util/RegexPathFilterSpec.scala
+++ b/kamon-core/src/legacy-test/scala/kamon/util/RegexPathFilterSpec.scala
@@ -16,13 +16,14 @@
package kamon.util
+import kamon.metric.RegexNameFilter
import org.scalatest.{Matchers, WordSpecLike}
class RegexPathFilterSpec extends WordSpecLike with Matchers {
"The RegexPathFilter" should {
"match a single expression" in {
- val filter = new RegexPathFilter("/user/actor")
+ val filter = new RegexNameFilter("/user/actor")
filter.accept("/user/actor") shouldBe true
@@ -31,7 +32,7 @@ class RegexPathFilterSpec extends WordSpecLike with Matchers {
}
"match arbitray expressions ending with wildcard" in {
- val filter = new RegexPathFilter("/user/.*")
+ val filter = new RegexNameFilter("/user/.*")
filter.accept("/user/actor") shouldBe true
filter.accept("/user/otherActor") shouldBe true
@@ -45,7 +46,7 @@ class RegexPathFilterSpec extends WordSpecLike with Matchers {
}
"match numbers" in {
- val filter = new RegexPathFilter("/user/actor-\\d")
+ val filter = new RegexNameFilter("/user/actor-\\d")
filter.accept("/user/actor-1") shouldBe true
filter.accept("/user/actor-2") shouldBe true
diff --git a/kamon-core/src/main/java/kamon/util/GlobPathFilter.java b/kamon-core/src/main/java/kamon/util/GlobPathFilter.java
deleted file mode 100644
index 1bfbaefc..00000000
--- a/kamon-core/src/main/java/kamon/util/GlobPathFilter.java
+++ /dev/null
@@ -1,110 +0,0 @@
-/*
- * =========================================================================================
- * Copyright 2013-2014 the kamon project <http://kamon.io/>
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
- * except in compliance with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software distributed under the
- * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
- * either express or implied. See the License for the specific language governing permissions
- * and limitations under the License.
- * =========================================================================================
- */
-
-package kamon.util;
-
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-/**
- * Default implementation of PathFilter. Uses glob based includes and excludes to determine whether to export.
- *
- * @author John E. Bailey
- * @author <a href="mailto:david.lloyd@redhat.com">David M. Lloyd</a>
- */
-public final class GlobPathFilter {
- private static final Pattern GLOB_PATTERN = Pattern.compile("(\\*\\*?)|(\\?)|(\\\\.)|(/+)|([^*?]+)");
-
- private final String glob;
- private final Pattern pattern;
-
- /**
- * Construct a new instance.
- *
- * @param glob the path glob to match
- */
- public GlobPathFilter(final String glob) {
- pattern = getGlobPattern(glob);
- this.glob = glob;
- }
-
- /**
- * Determine whether a path should be accepted.
- *
- * @param path the path to check
- * @return true if the path should be accepted, false if not
- */
- public boolean accept(final String path) {
- return pattern.matcher(path).matches();
- }
-
- /**
- * Get a regular expression pattern which accept any path names which match the given glob. The glob patterns
- * function similarly to {@code ant} file patterns. Valid metacharacters in the glob pattern include:
- * <ul>
- * <li><code>"\"</code> - escape the next character (treat it literally, even if it is itself a recognized metacharacter)</li>
- * <li><code>"?"</code> - match any non-slash character</li>
- * <li><code>"*"</code> - match zero or more non-slash characters</li>
- * <li><code>"**"</code> - match zero or more characters, including slashes</li>
- * <li><code>"/"</code> - match one or more slash characters. Consecutive {@code /} characters are collapsed down into one.</li>
- * </ul>
- * In addition, any glob pattern matches all subdirectories thereof. A glob pattern ending in {@code /} is equivalent
- * to a glob pattern ending in <code>/**</code> in that the named directory is not itself included in the glob.
- * <p/>
- * <b>See also:</b> <a href="http://ant.apache.org/manual/dirtasks.html#patterns">"Patterns" in the Ant Manual</a>
- *
- * @param glob the glob to match
- *
- * @return the pattern
- */
- private static Pattern getGlobPattern(final String glob) {
- StringBuilder patternBuilder = new StringBuilder();
- final Matcher m = GLOB_PATTERN.matcher(glob);
- boolean lastWasSlash = false;
- while (m.find()) {
- lastWasSlash = false;
- String grp;
- if ((grp = m.group(1)) != null) {
- // match a * or **
- if (grp.length() == 2) {
- // it's a *workers are able to process multiple metrics*
- patternBuilder.append(".*");
- } else {
- // it's a *
- patternBuilder.append("[^/]*");
- }
- } else if ((grp = m.group(2)) != null) {
- // match a '?' glob pattern; any non-slash character
- patternBuilder.append("[^/]");
- } else if ((grp = m.group(3)) != null) {
- // backslash-escaped value
- patternBuilder.append(Pattern.quote(m.group().substring(1)));
- } else if ((grp = m.group(4)) != null) {
- // match any number of / chars
- patternBuilder.append("/+");
- lastWasSlash = true;
- } else {
- // some other string
- patternBuilder.append(Pattern.quote(m.group()));
- }
- }
- if (lastWasSlash) {
- // ends in /, append **
- patternBuilder.append(".*");
- }
- return Pattern.compile(patternBuilder.toString());
- }
-}
diff --git a/kamon-core/src/main/resources/reference.conf b/kamon-core/src/main/resources/reference.conf
index 61072507..aac718ca 100644
--- a/kamon-core/src/main/resources/reference.conf
+++ b/kamon-core/src/main/resources/reference.conf
@@ -2,6 +2,10 @@ kamon {
metric {
tick-interval = 1 second
+ filters {
+ accept-unmatched = true
+ }
+
instrument-factory {
# Default instrument settings for histograms and min max counters. The actual settings to be used when creating
diff --git a/kamon-core/src/main/scala/kamon/Util.scala b/kamon-core/src/main/scala/kamon/Util.scala
index 04ce7a04..c8efbdc0 100644
--- a/kamon-core/src/main/scala/kamon/Util.scala
+++ b/kamon-core/src/main/scala/kamon/Util.scala
@@ -1,6 +1,6 @@
package kamon
-import kamon.util.EntityFilter
+import kamon.metric.EntityFilter
/**
* Useful classes for Kamon and submodules.
diff --git a/kamon-core/src/main/scala/kamon/metric/EntityFilter.scala b/kamon-core/src/main/scala/kamon/metric/EntityFilter.scala
new file mode 100644
index 00000000..cf203609
--- /dev/null
+++ b/kamon-core/src/main/scala/kamon/metric/EntityFilter.scala
@@ -0,0 +1,110 @@
+package kamon
+package metric
+
+import java.util.regex.Pattern
+import com.typesafe.config.Config
+
+object EntityFilter {
+ def fromConfig(config: Config): EntityFilter = {
+ val filtersConfig = config.getConfig("kamon.metric.filters")
+ val acceptUnmatched = filtersConfig.getBoolean("accept-unmatched")
+
+ val perCategoryFilters = filtersConfig.firstLevelKeys.filter(_ != "accept-unmatched") map { category: String ⇒
+ val includes = readFilters(filtersConfig, s"$category.includes")
+ val excludes = readFilters(filtersConfig, s"$category.excludes")
+
+ (category, new IncludeExcludeNameFilter(includes, excludes, acceptUnmatched))
+ } toMap
+
+ new EntityFilter(perCategoryFilters, acceptUnmatched)
+ }
+
+ private def readFilters(filtersConfig: Config, name: String): Seq[NameFilter] = {
+ import scala.collection.JavaConverters._
+ if(filtersConfig.hasPath(name))
+ filtersConfig.getStringList(name).asScala.map(readNameFilter)
+ else
+ Seq.empty
+ }
+
+ private def readNameFilter(pattern: String): NameFilter = {
+ if(pattern.startsWith("regex:"))
+ new RegexNameFilter(pattern.drop(6))
+ else if(pattern.startsWith("glob:"))
+ new GlobPathFilter(pattern.drop(5))
+ else
+ new GlobPathFilter(pattern)
+ }
+}
+
+class EntityFilter(perCategoryFilters: Map[String, NameFilter], acceptUnmatched: Boolean) {
+ def accept(entity: Entity): Boolean =
+ perCategoryFilters
+ .get(entity.category)
+ .map(_.accept(entity.name))
+ .getOrElse(acceptUnmatched)
+}
+
+trait NameFilter {
+ def accept(name: String): Boolean
+}
+
+class IncludeExcludeNameFilter(includes: Seq[NameFilter], excludes: Seq[NameFilter], acceptUnmatched: Boolean) extends NameFilter {
+ override def accept(name: String): Boolean =
+ (includes.exists(_.accept(name)) || acceptUnmatched) && !excludes.exists(_.accept(name))
+}
+
+class RegexNameFilter(path: String) extends NameFilter {
+ private val pathRegex = path.r
+
+ override def accept(path: String): Boolean = path match {
+ case pathRegex(_*) ⇒ true
+ case _ ⇒ false
+ }
+}
+
+class GlobPathFilter(glob: String) extends NameFilter {
+ private val globPattern = Pattern.compile("(\\*\\*?)|(\\?)|(\\\\.)|(/+)|([^*?]+)")
+ private val compiledPattern = getGlobPattern(glob)
+
+ override def accept(name: String): Boolean =
+ compiledPattern.matcher(name).matches()
+
+ private def getGlobPattern(glob: String) = {
+ val patternBuilder = new StringBuilder
+ val matcher = globPattern.matcher(glob)
+ while (matcher.find()) {
+ val (grp1, grp2, grp3, grp4) = (matcher.group(1), matcher.group(2), matcher.group(3), matcher.group(4))
+ if (grp1 != null) {
+ // match a * or **
+ if (grp1.length == 2) {
+ // it's a *workers are able to process multiple metrics*
+ patternBuilder.append(".*")
+ }
+ else {
+ // it's a *
+ patternBuilder.append("[^/]*")
+ }
+ }
+ else if (grp2 != null) {
+ // match a '?' glob pattern; any non-slash character
+ patternBuilder.append("[^/]")
+ }
+ else if (grp3 != null) {
+ // backslash-escaped value
+ patternBuilder.append(Pattern.quote(grp3.substring(1)))
+ }
+ else if (grp4 != null) {
+ // match any number of / chars
+ patternBuilder.append("/+")
+ }
+ else {
+ // some other string
+ patternBuilder.append(Pattern.quote(matcher.group))
+ }
+ }
+
+ Pattern.compile(patternBuilder.toString)
+ }
+}
+
diff --git a/kamon-core/src/main/scala/kamon/metric/RecorderRegistry.scala b/kamon-core/src/main/scala/kamon/metric/RecorderRegistry.scala
index a4d2f4cd..53081760 100644
--- a/kamon-core/src/main/scala/kamon/metric/RecorderRegistry.scala
+++ b/kamon-core/src/main/scala/kamon/metric/RecorderRegistry.scala
@@ -10,31 +10,31 @@ import scala.collection.concurrent.TrieMap
trait RecorderRegistry {
+ def shouldTrack(entity: Entity): Boolean
def getRecorder(entity: Entity): EntityRecorder
- def getRecorder(name: String, category: String, tags: Map[String, String]): EntityRecorder
-
def removeRecorder(entity: Entity): Boolean
- def removeRecorder(name: String, category: String, tags: Map[String, String]): Boolean
}
class RecorderRegistryImpl(initialConfig: Config) extends RecorderRegistry {
private val instrumentFactory = new AtomicReference[InstrumentFactory]()
+ private val entityFilter = new AtomicReference[EntityFilter]()
private val entities = TrieMap.empty[Entity, EntityRecorder with EntitySnapshotProducer]
reconfigure(initialConfig)
- override def getRecorder(entity: Entity): EntityRecorder =
- entities.atomicGetOrElseUpdate(entity, new DefaultEntityRecorder(entity, instrumentFactory.get()))
- override def getRecorder(name: String, category: String, tags: Map[String, String]): EntityRecorder =
- getRecorder(Entity(name, category, tags))
+ override def shouldTrack(entity: Entity): Boolean =
+ entityFilter.get().accept(entity)
- override def removeRecorder(entity: Entity): Boolean = ???
+ override def getRecorder(entity: Entity): EntityRecorder =
+ entities.atomicGetOrElseUpdate(entity, new DefaultEntityRecorder(entity, instrumentFactory.get()))
- override def removeRecorder(name: String, category: String, tags: Map[String, String]): Boolean = ???
+ override def removeRecorder(entity: Entity): Boolean =
+ entities.remove(entity).nonEmpty
- private[kamon] def reconfigure(config: Config): Unit = {
- instrumentFactory.set(InstrumentFactory(config.getConfig("kamon.metric.instrument-factory")))
+ private[kamon] def reconfigure(config: Config): Unit = synchronized {
+ instrumentFactory.set(InstrumentFactory.fromConfig(config))
+ entityFilter.set(EntityFilter.fromConfig(config))
}
private[kamon] def snapshot(): Seq[EntitySnapshot] = {
diff --git a/kamon-core/src/main/scala/kamon/metric/instrument/InstrumentFactory.scala b/kamon-core/src/main/scala/kamon/metric/instrument/InstrumentFactory.scala
index e8d4d569..33a34bdf 100644
--- a/kamon-core/src/main/scala/kamon/metric/instrument/InstrumentFactory.scala
+++ b/kamon-core/src/main/scala/kamon/metric/instrument/InstrumentFactory.scala
@@ -63,14 +63,15 @@ private[kamon] class InstrumentFactory private (
)
}
-private[kamon] object InstrumentFactory {
+object InstrumentFactory {
- private[kamon] def apply(config: Config): InstrumentFactory = {
- val histogramDynamicRange = readDynamicRange(config.getConfig("default-settings.histogram"))
- val mmCounterDynamicRange = readDynamicRange(config.getConfig("default-settings.min-max-counter"))
- val mmCounterSampleInterval = config.getDuration("default-settings.min-max-counter.sample-interval")
+ def fromConfig(config: Config): InstrumentFactory = {
+ val factoryConfig = config.getConfig("kamon.metric.instrument-factory")
+ val histogramDynamicRange = readDynamicRange(factoryConfig.getConfig("default-settings.histogram"))
+ val mmCounterDynamicRange = readDynamicRange(factoryConfig.getConfig("default-settings.min-max-counter"))
+ val mmCounterSampleInterval = factoryConfig.getDuration("default-settings.min-max-counter.sample-interval")
- val customSettings = config.getConfig("custom-settings")
+ val customSettings = factoryConfig.getConfig("custom-settings")
.configurations
.filter(nonEmptyCategories)
.flatMap(buildCustomInstrumentSettings)
@@ -96,7 +97,6 @@ private[kamon] object InstrumentFactory {
significantValueDigits = config.getInt("significant-value-digits")
)
-
private case class CustomInstrumentSettings(
lowestDiscernibleValue: Option[Long],
highestTrackableValue: Option[Long],
diff --git a/kamon-core/src/main/scala/kamon/trace/Span.scala b/kamon-core/src/main/scala/kamon/trace/Span.scala
index 87115e19..804627dc 100644
--- a/kamon-core/src/main/scala/kamon/trace/Span.scala
+++ b/kamon-core/src/main/scala/kamon/trace/Span.scala
@@ -1,7 +1,7 @@
package kamon
package trace
-import kamon.metric.RecorderRegistry
+import kamon.metric.{Entity, RecorderRegistry}
import kamon.metric.instrument.DynamicRange
import scala.collection.JavaConverters._
@@ -167,7 +167,8 @@ class Span(spanContext: SpanContext, initialOperationName: String, startTimestam
private def recordSpanMetrics(): Unit = {
val elapsedTime = endTimestampMicros - startTimestampMicros
- val recorder = recorderRegistry.getRecorder(operationName, Span.MetricCategory, metricTags)
+ val entity = Entity(operationName, Span.MetricCategory, metricTags)
+ val recorder = recorderRegistry.getRecorder(entity)
recorder
.histogram(Span.LatencyMetricName, MeasurementUnit.time.microseconds, DynamicRange.Default)
@@ -178,6 +179,5 @@ class Span(spanContext: SpanContext, initialOperationName: String, startTimestam
recorder.counter(Span.ErrorMetricName).increment()
}
}
-
}
} \ No newline at end of file
diff --git a/kamon-core/src/main/scala/kamon/trace/SpanContext.scala b/kamon-core/src/main/scala/kamon/trace/SpanContext.scala
index 7f5962e0..a3afb36d 100644
--- a/kamon-core/src/main/scala/kamon/trace/SpanContext.scala
+++ b/kamon-core/src/main/scala/kamon/trace/SpanContext.scala
@@ -1,4 +1,5 @@
package kamon.trace
+
import java.lang
import java.util.Map
import scala.collection.JavaConverters._
@@ -6,16 +7,18 @@ import scala.collection.JavaConverters._
class SpanContext(val traceID: Long, val spanID: Long, val parentID: Long) extends io.opentracing.SpanContext {
private var baggage = scala.collection.immutable.Map.empty[String, String]
- private[kamon] def addBaggageItem(key: String, value: String): Unit = {
+ private[kamon] def addBaggageItem(key: String, value: String): Unit = synchronized {
baggage = baggage + (key -> value)
}
- private[kamon] def getBaggage(key: String): String =
+ private[kamon] def getBaggage(key: String): String = synchronized {
baggage.get(key).getOrElse(null)
+ }
private[kamon] def baggageMap: scala.collection.immutable.Map[String, String] =
baggage
- override def baggageItems(): lang.Iterable[Map.Entry[String, String]] =
+ override def baggageItems(): lang.Iterable[Map.Entry[String, String]] = synchronized {
baggage.asJava.entrySet()
+ }
}
diff --git a/kamon-core/src/main/scala/kamon/util/EntityFilter.scala b/kamon-core/src/main/scala/kamon/util/EntityFilter.scala
deleted file mode 100644
index a8456689..00000000
--- a/kamon-core/src/main/scala/kamon/util/EntityFilter.scala
+++ /dev/null
@@ -1,7 +0,0 @@
-package kamon.util
-
-import kamon.metric.Entity
-
-trait EntityFilter {
- def accept(entity: Entity): Boolean
-}
diff --git a/kamon-core/src/test/scala/kamon/metric/EntityFilterSpec.scala b/kamon-core/src/test/scala/kamon/metric/EntityFilterSpec.scala
new file mode 100644
index 00000000..15dfc5ff
--- /dev/null
+++ b/kamon-core/src/test/scala/kamon/metric/EntityFilterSpec.scala
@@ -0,0 +1,69 @@
+package kamon
+package metric
+
+import com.typesafe.config.ConfigFactory
+import org.scalatest.{Matchers, WordSpec}
+
+
+class EntityFilterSpec extends WordSpec with Matchers {
+ val testConfig = ConfigFactory.parseString(
+ """
+ |kamon.metric.filters {
+ | accept-unmatched = false
+ |
+ | some-category {
+ | includes = ["**"]
+ | excludes = ["not-me"]
+ | }
+ |
+ | only-includes {
+ | includes = ["only-me"]
+ | }
+ |
+ | only-excludes {
+ | excludes = ["not-me"]
+ | }
+ |
+ | specific-rules {
+ | includes = ["glob:/user/**", "regex:test-[0-5]"]
+ | }
+ |}
+ """.stripMargin
+ )
+
+ "the entity filters" should {
+ "use the accept-unmatched setting when there is no configuration for a given category" in {
+ val acceptUnmatched = EntityFilter.fromConfig(ConfigFactory.parseString("kamon.metric.filters.accept-unmatched=true"))
+ val rejectUnmatched = EntityFilter.fromConfig(ConfigFactory.parseString("kamon.metric.filters.accept-unmatched=false"))
+
+ acceptUnmatched.accept(Entity("a", "b", Map.empty)) shouldBe true
+ rejectUnmatched.accept(Entity("a", "b", Map.empty)) shouldBe false
+ }
+
+ "accept entities that are matched by any include and none exclude filters" in {
+ val entityFilter = EntityFilter.fromConfig(testConfig)
+
+ entityFilter.accept(Entity("anything", "anything", Map.empty)) shouldBe false
+ entityFilter.accept(Entity("anything", "some-category", Map.empty)) shouldBe true
+ entityFilter.accept(Entity("not-me", "some-category", Map.empty)) shouldBe false
+ }
+
+ "allow configuring includes only or excludes only for any category" in {
+ val entityFilter = EntityFilter.fromConfig(testConfig)
+
+ entityFilter.accept(Entity("only-me", "only-includes", Map.empty)) shouldBe true
+ entityFilter.accept(Entity("anything", "only-includes", Map.empty)) shouldBe false
+ entityFilter.accept(Entity("any-other", "only-excludes", Map.empty)) shouldBe false
+ entityFilter.accept(Entity("not-me", "only-excludes", Map.empty)) shouldBe false
+ }
+
+ "allow to explicitly decide whether patterns are treated as Glob or Regex" in {
+ val entityFilter = EntityFilter.fromConfig(testConfig)
+
+ entityFilter.accept(Entity("/user/accepted", "specific-rules", Map.empty)) shouldBe true
+ entityFilter.accept(Entity("/other/rejected/", "specific-rules", Map.empty)) shouldBe false
+ entityFilter.accept(Entity("test-5", "specific-rules", Map.empty)) shouldBe true
+ entityFilter.accept(Entity("test-6", "specific-rules", Map.empty)) shouldBe false
+ }
+ }
+} \ No newline at end of file
diff --git a/kamon-core/src/test/scala/kamon/metric/GlobPathFilterSpec.scala b/kamon-core/src/test/scala/kamon/metric/GlobPathFilterSpec.scala
new file mode 100644
index 00000000..50938e12
--- /dev/null
+++ b/kamon-core/src/test/scala/kamon/metric/GlobPathFilterSpec.scala
@@ -0,0 +1,71 @@
+/*
+ * =========================================================================================
+ * Copyright © 2013-2014 the kamon project <http://kamon.io/>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+ * either express or implied. See the License for the specific language governing permissions
+ * and limitations under the License.
+ * =========================================================================================
+ */
+
+package kamon
+package metric
+
+import org.scalatest.{Matchers, WordSpecLike}
+
+class GlobPathFilterSpec extends WordSpecLike with Matchers {
+ "The GlobPathFilter" should {
+
+ "match a single expression" in {
+ val filter = new GlobPathFilter("/user/actor")
+
+ filter.accept("/user/actor") shouldBe true
+ filter.accept("/user/actor/something") shouldBe false
+ filter.accept("/user/actor/somethingElse") shouldBe false
+ }
+
+ "match all expressions in the same level" in {
+ val filter = new GlobPathFilter("/user/*")
+
+ filter.accept("/user/actor") shouldBe true
+ filter.accept("/user/otherActor") shouldBe true
+ filter.accept("/user/something/actor") shouldBe false
+ filter.accept("/user/something/otherActor") shouldBe false
+ }
+
+ "match any expressions when using double star alone (**)" in {
+ val filter = new GlobPathFilter("**")
+
+ filter.accept("GET: /ping") shouldBe true
+ filter.accept("GET: /ping/pong") shouldBe true
+ filter.accept("this-doesn't_look good but-passes") shouldBe true
+ }
+
+ "match all expressions and cross the path boundaries when using double star suffix (**)" in {
+ val filter = new GlobPathFilter("/user/actor-**")
+
+ filter.accept("/user/actor-") shouldBe true
+ filter.accept("/user/actor-one") shouldBe true
+ filter.accept("/user/actor-one/other") shouldBe true
+ filter.accept("/user/something/actor") shouldBe false
+ filter.accept("/user/something/otherActor") shouldBe false
+ }
+
+ "match exactly one character when using question mark (?)" in {
+ val filter = new GlobPathFilter("/user/actor-?")
+
+ filter.accept("/user/actor-1") shouldBe true
+ filter.accept("/user/actor-2") shouldBe true
+ filter.accept("/user/actor-3") shouldBe true
+ filter.accept("/user/actor-one") shouldBe false
+ filter.accept("/user/actor-two") shouldBe false
+ filter.accept("/user/actor-tree") shouldBe false
+ }
+ }
+}
diff --git a/kamon-core/src/test/scala/kamon/metric/RecorderRegistrySpec.scala b/kamon-core/src/test/scala/kamon/metric/RecorderRegistrySpec.scala
new file mode 100644
index 00000000..28402d7c
--- /dev/null
+++ b/kamon-core/src/test/scala/kamon/metric/RecorderRegistrySpec.scala
@@ -0,0 +1,43 @@
+package kamon.metric
+
+import com.typesafe.config.ConfigFactory
+import org.scalatest.{Matchers, WordSpec}
+
+class RecorderRegistrySpec extends WordSpec with Matchers {
+ private val testConfig = ConfigFactory.parseString(
+ """
+ |kamon.metric.filters {
+ | accept-unmatched = false
+ |
+ | my-category {
+ | includes = ["**"]
+ | excludes = ["excluded"]
+ | }
+ |}
+ """.stripMargin
+ )
+ private val recorderRegistry = new RecorderRegistryImpl(testConfig.withFallback(ConfigFactory.load()))
+
+
+ "the RecorderRegistry" should {
+ "create entity recorders as requested and always return the same instance for a given entity" in {
+ val myFirstEntityRecorder = recorderRegistry.getRecorder(Entity("my-entity", "my-category", Map.empty))
+ val mySecondEntityRecorder = recorderRegistry.getRecorder(Entity("my-entity", "my-category", Map.empty))
+ mySecondEntityRecorder shouldBe theSameInstanceAs(myFirstEntityRecorder)
+ }
+
+ "properly advice regarding entity filtering read from configuration" in {
+ recorderRegistry.shouldTrack(Entity("my-entity", "my-category", Map.empty)) shouldBe true
+ recorderRegistry.shouldTrack(Entity("other-eny", "my-category", Map.empty)) shouldBe true
+ recorderRegistry.shouldTrack(Entity("excluded", "my-category", Map.empty)) shouldBe false
+ }
+
+ "allow removing entities" in {
+ val myFirstEntityRecorder = recorderRegistry.getRecorder(Entity("my-entity", "my-category", Map.empty))
+ recorderRegistry.removeRecorder(Entity("my-entity", "my-category", Map.empty))
+
+ val mySecondEntityRecorder = recorderRegistry.getRecorder(Entity("my-entity", "my-category", Map.empty))
+ mySecondEntityRecorder shouldNot be theSameInstanceAs(myFirstEntityRecorder)
+ }
+ }
+}
diff --git a/kamon-core/src/test/scala/kamon/metric/RegexPathFilterSpec.scala b/kamon-core/src/test/scala/kamon/metric/RegexPathFilterSpec.scala
new file mode 100644
index 00000000..40fb6039
--- /dev/null
+++ b/kamon-core/src/test/scala/kamon/metric/RegexPathFilterSpec.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
+package metric
+
+import org.scalatest.{Matchers, WordSpecLike}
+
+class RegexPathFilterSpec extends WordSpecLike with Matchers {
+ "The RegexPathFilter" should {
+
+ "match a single expression" in {
+ val filter = new RegexNameFilter("/user/actor")
+
+ filter.accept("/user/actor") shouldBe true
+
+ filter.accept("/user/actor/something") shouldBe false
+ filter.accept("/user/actor/somethingElse") shouldBe false
+ }
+
+ "match arbitray expressions ending with wildcard" in {
+ val filter = new RegexNameFilter("/user/.*")
+
+ filter.accept("/user/actor") shouldBe true
+ filter.accept("/user/otherActor") shouldBe true
+ filter.accept("/user/something/actor") shouldBe true
+ filter.accept("/user/something/otherActor") shouldBe true
+
+ filter.accept("/otheruser/actor") shouldBe false
+ filter.accept("/otheruser/otherActor") shouldBe false
+ filter.accept("/otheruser/something/actor") shouldBe false
+ filter.accept("/otheruser/something/otherActor") shouldBe false
+ }
+
+ "match numbers" in {
+ val filter = new RegexNameFilter("/user/actor-\\d")
+
+ filter.accept("/user/actor-1") shouldBe true
+ filter.accept("/user/actor-2") shouldBe true
+ filter.accept("/user/actor-3") shouldBe true
+
+ filter.accept("/user/actor-one") shouldBe false
+ filter.accept("/user/actor-two") shouldBe false
+ filter.accept("/user/actor-tree") shouldBe false
+ }
+ }
+}
diff --git a/kamon-core/src/test/scala/kamon/metric/instrument/InstrumentFactorySpec.scala b/kamon-core/src/test/scala/kamon/metric/instrument/InstrumentFactorySpec.scala
index 5bf16d4c..eda838d5 100644
--- a/kamon-core/src/test/scala/kamon/metric/instrument/InstrumentFactorySpec.scala
+++ b/kamon-core/src/test/scala/kamon/metric/instrument/InstrumentFactorySpec.scala
@@ -11,23 +11,25 @@ class InstrumentFactorySpec extends WordSpec with Matchers{
val customEntity = Entity("test", "custom-category", Map.empty)
val baseConfiguration = ConfigFactory.parseString(
"""
- |default-settings {
- | histogram {
- | lowest-discernible-value = 100
- | highest-trackable-value = 5000
- | significant-value-digits = 2
- | }
+ |kamon.metric.instrument-factory {
+ | default-settings {
+ | histogram {
+ | lowest-discernible-value = 100
+ | highest-trackable-value = 5000
+ | significant-value-digits = 2
+ | }
|
- | min-max-counter {
- | lowest-discernible-value = 200
- | highest-trackable-value = 6000
- | significant-value-digits = 3
- | sample-interval = 647 millis
+ | min-max-counter {
+ | lowest-discernible-value = 200
+ | highest-trackable-value = 6000
+ | significant-value-digits = 3
+ | sample-interval = 647 millis
+ | }
| }
- |}
|
- |custom-settings {
+ | custom-settings {
|
+ | }
|}
""".stripMargin
)
@@ -35,7 +37,7 @@ class InstrumentFactorySpec extends WordSpec with Matchers{
"the metrics InstrumentFactory" should {
"create instruments using the default configuration settings" in {
- val factory = InstrumentFactory(baseConfiguration)
+ val factory = InstrumentFactory.fromConfig(baseConfiguration)
val histogram = factory.buildHistogram(testEntity, "my-histogram")
val mmCounter = factory.buildMinMaxCounter(testEntity, "my-mm-counter")
@@ -50,7 +52,7 @@ class InstrumentFactorySpec extends WordSpec with Matchers{
}
"accept custom settings when building instruments" in {
- val factory = InstrumentFactory(baseConfiguration)
+ val factory = InstrumentFactory.fromConfig(baseConfiguration)
val histogram = factory.buildHistogram(testEntity, "my-histogram", DynamicRange.Loose)
val mmCounter = factory.buildMinMaxCounter(testEntity, "my-mm-counter", DynamicRange.Fine, Duration.ofMillis(500))
@@ -63,7 +65,7 @@ class InstrumentFactorySpec extends WordSpec with Matchers{
"allow overriding any default and provided settings via the custom-settings configuration key" in {
val customConfig = ConfigFactory.parseString(
"""
- |custom-settings {
+ |kamon.metric.instrument-factory.custom-settings {
| custom-category {
| modified-histogram {
| lowest-discernible-value = 99
@@ -82,7 +84,7 @@ class InstrumentFactorySpec extends WordSpec with Matchers{
""".stripMargin
).withFallback(baseConfiguration)
- val factory = InstrumentFactory(customConfig)
+ val factory = InstrumentFactory.fromConfig(customConfig)
val defaultHistogram = factory.buildHistogram(customEntity, "default-histogram")
val modifiedHistogram = factory.buildHistogram(customEntity, "modified-histogram", DynamicRange.Loose)
diff --git a/kamon-core/src/test/scala/kamon/testkit/DefaultInstrumentFactory.scala b/kamon-core/src/test/scala/kamon/testkit/DefaultInstrumentFactory.scala
index acec5915..6fd193d6 100644
--- a/kamon-core/src/test/scala/kamon/testkit/DefaultInstrumentFactory.scala
+++ b/kamon-core/src/test/scala/kamon/testkit/DefaultInstrumentFactory.scala
@@ -6,7 +6,7 @@ import kamon.metric.instrument.InstrumentFactory
trait DefaultInstrumentFactory {
val defaultEntity = Entity("default-entity", "default-category", Map.empty)
- val instrumentFactory = InstrumentFactory(ConfigFactory.load().getConfig("kamon.metric.instrument-factory"))
+ val instrumentFactory = InstrumentFactory.fromConfig(ConfigFactory.load())
def buildCounter(name: String) = instrumentFactory.buildCounter(defaultEntity, name)
diff --git a/project/plugins.sbt b/project/plugins.sbt
index 00933fbf..59a6d802 100644
--- a/project/plugins.sbt
+++ b/project/plugins.sbt
@@ -1,3 +1,4 @@
resolvers += Resolver.bintrayIvyRepo("kamon-io", "sbt-plugins")
addSbtPlugin("io.kamon" % "kamon-sbt-umbrella" % "0.0.12")
+addSbtPlugin("org.scoverage" % "sbt-scoverage" % "1.5.0")