From e1e7853255131f26702229735e37e160c38f2d08 Mon Sep 17 00:00:00 2001 From: Ivan Topolnjak Date: Sat, 20 May 2017 14:06:03 +0200 Subject: implement entity filters --- build.sbt | 2 + .../scala/kamon/metric/MetricsSettings.scala | 5 +- .../kamon/metric/SubscriptionsDispatcher.scala | 2 +- .../scala/kamon/util/RegexPathFilter.scala | 4 +- .../scala/kamon/util/GlobPathFilterSpec.scala | 1 + .../scala/kamon/util/RegexPathFilterSpec.scala | 7 +- .../src/main/java/kamon/util/GlobPathFilter.java | 110 --------------------- kamon-core/src/main/resources/reference.conf | 4 + kamon-core/src/main/scala/kamon/Util.scala | 2 +- .../src/main/scala/kamon/metric/EntityFilter.scala | 110 +++++++++++++++++++++ .../main/scala/kamon/metric/RecorderRegistry.scala | 22 ++--- .../metric/instrument/InstrumentFactory.scala | 14 +-- kamon-core/src/main/scala/kamon/trace/Span.scala | 6 +- .../src/main/scala/kamon/trace/SpanContext.scala | 9 +- .../src/main/scala/kamon/util/EntityFilter.scala | 7 -- .../test/scala/kamon/metric/EntityFilterSpec.scala | 69 +++++++++++++ .../scala/kamon/metric/GlobPathFilterSpec.scala | 71 +++++++++++++ .../scala/kamon/metric/RecorderRegistrySpec.scala | 43 ++++++++ .../scala/kamon/metric/RegexPathFilterSpec.scala | 60 +++++++++++ .../metric/instrument/InstrumentFactorySpec.scala | 36 +++---- .../kamon/testkit/DefaultInstrumentFactory.scala | 2 +- project/plugins.sbt | 1 + 22 files changed, 419 insertions(+), 168 deletions(-) delete mode 100644 kamon-core/src/main/java/kamon/util/GlobPathFilter.java create mode 100644 kamon-core/src/main/scala/kamon/metric/EntityFilter.scala delete mode 100644 kamon-core/src/main/scala/kamon/util/EntityFilter.scala create mode 100644 kamon-core/src/test/scala/kamon/metric/EntityFilterSpec.scala create mode 100644 kamon-core/src/test/scala/kamon/metric/GlobPathFilterSpec.scala create mode 100644 kamon-core/src/test/scala/kamon/metric/RecorderRegistrySpec.scala create mode 100644 kamon-core/src/test/scala/kamon/metric/RegexPathFilterSpec.scala 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 - * - * 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 David M. Lloyd - */ -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: - * - * In addition, any glob pattern matches all subdirectories thereof. A glob pattern ending in {@code /} is equivalent - * to a glob pattern ending in /** in that the named directory is not itself included in the glob. - *

- * See also: "Patterns" in the Ant Manual - * - * @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 + * + * 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 + * + * 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") -- cgit v1.2.3