From bbd52dcca66caa3cbd78478a1d075ff54da4f65a Mon Sep 17 00:00:00 2001 From: Ivan Topolnjak Date: Mon, 24 Apr 2017 14:10:46 +0200 Subject: bring the new skeleton into place --- build.sbt | 21 ++-- kamon-core/src/main/resources/reference.conf | 54 ++++++++++ kamon-core/src/main/scala/kamon/Diagnostic.scala | 13 +++ kamon-core/src/main/scala/kamon/Environment.scala | 10 ++ kamon-core/src/main/scala/kamon/Kamon.scala | 64 ++++++++++++ .../src/main/scala/kamon/Subscriptions.scala | 12 +++ kamon-core/src/main/scala/kamon/Util.scala | 19 ++++ .../src/main/scala/kamon/metric/Entity.scala | 5 + .../main/scala/kamon/metric/EntityRecorder.scala | 23 +++++ .../src/main/scala/kamon/metric/Metrics.scala | 34 +++++++ .../scala/kamon/metric/MetricsSubscriber.scala | 12 +++ .../scala/kamon/metric/instrument/Counter.scala | 11 ++ .../kamon/metric/instrument/DynamicRange.scala | 33 ++++++ .../main/scala/kamon/metric/instrument/Gauge.scala | 15 +++ .../scala/kamon/metric/instrument/Histogram.scala | 18 ++++ .../metric/instrument/InstrumentFactory.scala | 102 +++++++++++++++++++ .../kamon/metric/instrument/MinMaxCounter.scala | 26 +++++ .../scala/kamon/metric/snapshot/Distribution.scala | 24 +++++ .../kamon/metric/snapshot/EntitySnapshot.scala | 11 ++ .../kamon/metric/snapshot/InstrumentSnapshot.scala | 16 +++ .../scala/kamon/metric/snapshot/SingleValue.scala | 5 + .../scala/kamon/metric/snapshot/TickSnapshot.scala | 17 ++++ kamon-core/src/main/scala/kamon/package.scala | 49 +++++++++ .../src/main/scala/kamon/trace/Sampler.scala | 5 + kamon-core/src/main/scala/kamon/trace/Tracer.scala | 5 + kamon-core/src/main/scala/kamon/util/Clock.scala | 9 ++ .../src/main/scala/kamon/util/EntityFilter.scala | 7 ++ .../main/scala/kamon/util/MeasurementUnit.scala | 15 +++ .../metric/instrument/InstrumentFactorySpec.scala | 112 +++++++++++++++++++++ 29 files changed, 738 insertions(+), 9 deletions(-) create mode 100644 kamon-core/src/main/resources/reference.conf create mode 100644 kamon-core/src/main/scala/kamon/Diagnostic.scala create mode 100644 kamon-core/src/main/scala/kamon/Environment.scala create mode 100644 kamon-core/src/main/scala/kamon/Kamon.scala create mode 100644 kamon-core/src/main/scala/kamon/Subscriptions.scala create mode 100644 kamon-core/src/main/scala/kamon/Util.scala create mode 100644 kamon-core/src/main/scala/kamon/metric/Entity.scala create mode 100644 kamon-core/src/main/scala/kamon/metric/EntityRecorder.scala create mode 100644 kamon-core/src/main/scala/kamon/metric/Metrics.scala create mode 100644 kamon-core/src/main/scala/kamon/metric/MetricsSubscriber.scala create mode 100644 kamon-core/src/main/scala/kamon/metric/instrument/Counter.scala create mode 100644 kamon-core/src/main/scala/kamon/metric/instrument/DynamicRange.scala create mode 100644 kamon-core/src/main/scala/kamon/metric/instrument/Gauge.scala create mode 100644 kamon-core/src/main/scala/kamon/metric/instrument/Histogram.scala create mode 100644 kamon-core/src/main/scala/kamon/metric/instrument/InstrumentFactory.scala create mode 100644 kamon-core/src/main/scala/kamon/metric/instrument/MinMaxCounter.scala create mode 100644 kamon-core/src/main/scala/kamon/metric/snapshot/Distribution.scala create mode 100644 kamon-core/src/main/scala/kamon/metric/snapshot/EntitySnapshot.scala create mode 100644 kamon-core/src/main/scala/kamon/metric/snapshot/InstrumentSnapshot.scala create mode 100644 kamon-core/src/main/scala/kamon/metric/snapshot/SingleValue.scala create mode 100644 kamon-core/src/main/scala/kamon/metric/snapshot/TickSnapshot.scala create mode 100644 kamon-core/src/main/scala/kamon/package.scala create mode 100644 kamon-core/src/main/scala/kamon/trace/Sampler.scala create mode 100644 kamon-core/src/main/scala/kamon/trace/Tracer.scala create mode 100644 kamon-core/src/main/scala/kamon/util/Clock.scala create mode 100644 kamon-core/src/main/scala/kamon/util/EntityFilter.scala create mode 100644 kamon-core/src/main/scala/kamon/util/MeasurementUnit.scala create mode 100644 kamon-core/src/test/scala/kamon/metric/instrument/InstrumentFactorySpec.scala diff --git a/build.sbt b/build.sbt index 60d4ad43..65fc7811 100644 --- a/build.sbt +++ b/build.sbt @@ -13,8 +13,6 @@ * ========================================================================================= */ -parallelExecution in Test in Global := false - lazy val kamon = (project in file(".")) .settings(moduleName := "kamon") .settings(noPublishing: _*) @@ -24,19 +22,24 @@ lazy val kamon = (project in file(".")) lazy val core = (project in file("kamon-core")) .settings(moduleName := "kamon-core") .settings( - libraryDependencies ++= - compileScope(akkaDependency("actor").value, hdrHistogram, slf4jApi) ++ - providedScope(aspectJ) ++ - optionalScope(logbackClassic) ++ - testScope(scalatest, akkaDependency("testkit").value, akkaDependency("slf4j").value, logbackClassic)) + scalaVersion := "2.12.1", + resolvers += Resolver.mavenLocal, + libraryDependencies ++= Seq( + "com.typesafe" % "config" % "1.3.1", + "org.slf4j" % "slf4j-api" % "1.7.7", + "org.hdrhistogram" % "HdrHistogram" % "2.1.9", + "io.opentracing" % "opentracing-api" % "0.21.1-SNAPSHOT", + "org.scalatest" %% "scalatest" % "3.0.1" % "test" + ) +) lazy val testkit = (project in file("kamon-testkit")) .dependsOn(core) - .settings(moduleName := "kamon-testkit") + .settings(moduleName := "kamon-testkit", resolvers += Resolver.mavenLocal) .settings( libraryDependencies ++= compileScope(akkaDependency("actor").value, akkaDependency("testkit").value) ++ providedScope(aspectJ) ++ - testScope(slf4jApi, slf4jnop)) + testScope(slf4jApi, slf4jnop)) \ No newline at end of file diff --git a/kamon-core/src/main/resources/reference.conf b/kamon-core/src/main/resources/reference.conf new file mode 100644 index 00000000..9de2247b --- /dev/null +++ b/kamon-core/src/main/resources/reference.conf @@ -0,0 +1,54 @@ +kamon { + metric { + + + instrument-factory { + + # Default instrument settings for histograms and min max counters. The actual settings to be used when creating + # instruments is determined by merging the default settings, code settings and custom-settings using the following + # priorities (top wins): + # + # - any setting in the `custom-settings` section for the given category/instrument. + # - code settings provided when creating the instrument. + # - `default-settings` bellow. + # + default-settings { + histogram { + lowest-discernible-value = 0 + highest-trackable-value = 3600000000000 + significant-value-digits = 2 + } + + min-max-counter { + lowest-discernible-value = 0 + highest-trackable-value = 3600000000000 + significant-value-digits = 2 + sample-interval = 200 millis + } + } + + # Custom settings for instruments of a given category. The settings provided in this section override the default + # and manually provided settings when creating instruments. All settings are optional in this section and default + # values from the `kamon.metric.instrument-factory.default-settings` will be used in case of any setting being + # missing. + # + # Example: + # If you wish to change the highest trackable value setting of the `elapsed-time` instrument in the `span` + # category, you should include the following configuration in your application.conf file: + # + # kamon.metric.instrument-factory.custom-settings.span { + # elapsed-time { + # highest-trackable-value = 5000 + # } + # } + # + # After including that configuration, every time a new histogram called `elapsed-time` for a entity with category + # `span`, the highest-trackable-value will be 5000, but lowest-discernible-value and significant-value-digits will + # remain with default values. + # + custom-settings { + + } + } + } +} \ No newline at end of file diff --git a/kamon-core/src/main/scala/kamon/Diagnostic.scala b/kamon-core/src/main/scala/kamon/Diagnostic.scala new file mode 100644 index 00000000..87784a72 --- /dev/null +++ b/kamon-core/src/main/scala/kamon/Diagnostic.scala @@ -0,0 +1,13 @@ +package kamon + +// The types are just an idea, they will need further refinement. +trait Diagnostic { + def isAspectJWorking: Boolean + def detectedModules: Seq[String] + def entityFilterPatterns: Seq[String] + def metricSubscribers: Seq[String] + def traceSubscribers: Seq[String] + + // Category Name => Count + def entityCount: Map[String, Long] +} diff --git a/kamon-core/src/main/scala/kamon/Environment.scala b/kamon-core/src/main/scala/kamon/Environment.scala new file mode 100644 index 00000000..3184184a --- /dev/null +++ b/kamon-core/src/main/scala/kamon/Environment.scala @@ -0,0 +1,10 @@ +package kamon + +import com.typesafe.config.Config + +trait Environment { + def instance: String + def host: String + def application: String + def config: Config +} diff --git a/kamon-core/src/main/scala/kamon/Kamon.scala b/kamon-core/src/main/scala/kamon/Kamon.scala new file mode 100644 index 00000000..1dd72ab6 --- /dev/null +++ b/kamon-core/src/main/scala/kamon/Kamon.scala @@ -0,0 +1,64 @@ +package kamon + +import com.typesafe.config.Config +import kamon.metric.Metrics +import kamon.trace.Tracer + +/** + * The main entry point to all Kamon functionality. + * + * + * + * + */ +trait Kamon { + def metrics: Metrics + def tracer: Tracer + + def subscriptions: Subscriptions + def util: Util + + def environment: Environment + def diagnose: Diagnostic + + def reconfigure(config: Config): Unit + + +} + + + +/* + +Kamon.metrics.getRecorder("app-metrics") +Kamon.metrics.getRecorder("akka-actor", "test") + +Kamon.entities.get("akka-actor", "test") +Kamon.entities.remove(entity) + +Kamon.util.entityFilters.accept(entity) +Kamon.util.clock. + +Kamon.entities.new(). + +Kamon.subscriptions.loadFromConfig() +Kamon.subscriptions.subscribe(StatsD, Filters.IncludeAll) +Kamon.subscriptions.subscribe(NewRelic, Filters.Empty().includeCategory("span").withTag("span.kind", "server")) + + +Things that you need to do with Kamon: +Global: + - Reconfigure + - Get Diagnostic Data +Metrics: + - create entities + - subscribe to metrics data + +Tracer: + - Build Spans / Use ActiveSpanSource + - subscribe to tracing data + + */ + + + diff --git a/kamon-core/src/main/scala/kamon/Subscriptions.scala b/kamon-core/src/main/scala/kamon/Subscriptions.scala new file mode 100644 index 00000000..ff5dda4c --- /dev/null +++ b/kamon-core/src/main/scala/kamon/Subscriptions.scala @@ -0,0 +1,12 @@ +package kamon + +import kamon.metric.MetricsSubscriber + +trait Subscriptions { + def loadFromConfig() + def subscribeToMetrics(subscriber: MetricsSubscriber): Subscription +} + +trait Subscription { + def cancel(): Unit +} diff --git a/kamon-core/src/main/scala/kamon/Util.scala b/kamon-core/src/main/scala/kamon/Util.scala new file mode 100644 index 00000000..51282afc --- /dev/null +++ b/kamon-core/src/main/scala/kamon/Util.scala @@ -0,0 +1,19 @@ +package kamon + +import kamon.util.{Clock, EntityFilter} + +/** + * Useful classes for Kamon and submodules. + * + */ +trait Util { + /** + * @return The Clock instance used by Kamon for timestamps and latency measurements. + */ + def clock: Clock + + /** + * @return Currently configured entity filters. + */ + def entityFilter: EntityFilter +} diff --git a/kamon-core/src/main/scala/kamon/metric/Entity.scala b/kamon-core/src/main/scala/kamon/metric/Entity.scala new file mode 100644 index 00000000..5e2b8b46 --- /dev/null +++ b/kamon-core/src/main/scala/kamon/metric/Entity.scala @@ -0,0 +1,5 @@ +package kamon.metric + + + +case class Entity(name: String, category: String, tags: Map[String, String]) diff --git a/kamon-core/src/main/scala/kamon/metric/EntityRecorder.scala b/kamon-core/src/main/scala/kamon/metric/EntityRecorder.scala new file mode 100644 index 00000000..a94881d2 --- /dev/null +++ b/kamon-core/src/main/scala/kamon/metric/EntityRecorder.scala @@ -0,0 +1,23 @@ +package kamon.metric + +import java.time.Duration + +import kamon.metric.instrument._ +import kamon.util.MeasurementUnit + +trait EntityRecorder { + def histogram(name: String): Histogram + def histogram(name: String, measurementUnit: MeasurementUnit, dynamicRange: DynamicRange): Histogram + + def minMaxCounter(name: String): MinMaxCounter + def minMaxCounter(name: String, measurementUnit: MeasurementUnit, dynamicRange: DynamicRange, sampleFrequency: Duration): MinMaxCounter + + def gauge(name: String): Gauge + + def counter(name: String): Counter +} + + +class EntityRecorderImpl { + +} \ No newline at end of file diff --git a/kamon-core/src/main/scala/kamon/metric/Metrics.scala b/kamon-core/src/main/scala/kamon/metric/Metrics.scala new file mode 100644 index 00000000..f312c5b7 --- /dev/null +++ b/kamon-core/src/main/scala/kamon/metric/Metrics.scala @@ -0,0 +1,34 @@ +package kamon +package metric + +import scala.collection.concurrent.TrieMap + + +trait Metrics { + 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 MetricsImpl extends Metrics{ + private val entities = TrieMap.empty[Entity, EntityRecorder] + + override def getRecorder(entity: Entity): EntityRecorder = { + ??? + } + + override def getRecorder(name: String, category: String, tags: Map[String, String]): EntityRecorder = ??? + + override def removeRecorder(entity: Entity): Boolean = ??? + + override def removeRecorder(name: String, category: String, tags: Map[String, String]): Boolean = ??? +} + + + + + + + diff --git a/kamon-core/src/main/scala/kamon/metric/MetricsSubscriber.scala b/kamon-core/src/main/scala/kamon/metric/MetricsSubscriber.scala new file mode 100644 index 00000000..dbdfde9d --- /dev/null +++ b/kamon-core/src/main/scala/kamon/metric/MetricsSubscriber.scala @@ -0,0 +1,12 @@ +package kamon.metric + +import com.typesafe.config.Config + +trait MetricsSubscriber { + def reconfigure(config: Config): Unit + + def start(config: Config): Unit + def shutdown(): Unit + + def processTick(snapshot: String) +} diff --git a/kamon-core/src/main/scala/kamon/metric/instrument/Counter.scala b/kamon-core/src/main/scala/kamon/metric/instrument/Counter.scala new file mode 100644 index 00000000..4a9edd77 --- /dev/null +++ b/kamon-core/src/main/scala/kamon/metric/instrument/Counter.scala @@ -0,0 +1,11 @@ +package kamon.metric.instrument + +import kamon.metric.Entity + +trait Counter { + def increment(): Unit +} + +object Counter { + def apply(entity: Entity, name: String): Counter = ??? +} diff --git a/kamon-core/src/main/scala/kamon/metric/instrument/DynamicRange.scala b/kamon-core/src/main/scala/kamon/metric/instrument/DynamicRange.scala new file mode 100644 index 00000000..628439c2 --- /dev/null +++ b/kamon-core/src/main/scala/kamon/metric/instrument/DynamicRange.scala @@ -0,0 +1,33 @@ +package kamon.metric.instrument + +import java.util.concurrent.TimeUnit + +case class DynamicRange(lowestDiscernibleValue: Long, highestTrackableValue: Long, significantValueDigits: Int) { + def upTo(highestTrackableValue: Long): DynamicRange = + copy(highestTrackableValue = highestTrackableValue) + + def startingFrom(lowestDiscernibleValue: Long): DynamicRange = + copy(lowestDiscernibleValue = lowestDiscernibleValue) +} + +object DynamicRange { + private val oneHourInNanoseconds = TimeUnit.HOURS.toNanos(1) + + /** + * Provides a range from 0 to 3.6e+12 (one hour in nanoseconds) with a value precision of 1 significant digit (10%) + * across that range. + */ + val Loose = DynamicRange(0L, oneHourInNanoseconds, 1) + + /** + * Provides a range from 0 to 3.6e+12 (one hour in nanoseconds) with a value precision of 2 significant digit (1%) + * across that range. + */ + val Default = DynamicRange(0L, oneHourInNanoseconds, 2) + + /** + * Provides a range from 0 to 3.6e+12 (one hour in nanoseconds) with a value precision of 3 significant digit (0.1%) + * across that range. + */ + val Fine = DynamicRange(0L, oneHourInNanoseconds, 3) +} diff --git a/kamon-core/src/main/scala/kamon/metric/instrument/Gauge.scala b/kamon-core/src/main/scala/kamon/metric/instrument/Gauge.scala new file mode 100644 index 00000000..43c71206 --- /dev/null +++ b/kamon-core/src/main/scala/kamon/metric/instrument/Gauge.scala @@ -0,0 +1,15 @@ +package kamon.metric.instrument + +import kamon.metric.Entity + +trait Gauge { + def increment(): Unit + def increment(times: Long): Unit + def decrement(): Unit + def decrement(times: Long): Unit + def set(value: Long): Unit +} + +object Gauge { + def apply(entity: Entity, name: String): Gauge = ??? +} diff --git a/kamon-core/src/main/scala/kamon/metric/instrument/Histogram.scala b/kamon-core/src/main/scala/kamon/metric/instrument/Histogram.scala new file mode 100644 index 00000000..c43c9dbc --- /dev/null +++ b/kamon-core/src/main/scala/kamon/metric/instrument/Histogram.scala @@ -0,0 +1,18 @@ +package kamon.metric.instrument + +import kamon.metric.Entity + +trait Histogram { + def dynamicRange: DynamicRange + + def record(value: Long): Unit + def record(value: Long, times: Long): Unit +} + +object Histogram { + def apply(entity: Entity, name: String, dynamicRange2: DynamicRange): Histogram = new Histogram { + override def dynamicRange: DynamicRange = dynamicRange2 + override def record(value: Long): Unit = ??? + override def record(value: Long, times: Long): Unit = ??? + } +} diff --git a/kamon-core/src/main/scala/kamon/metric/instrument/InstrumentFactory.scala b/kamon-core/src/main/scala/kamon/metric/instrument/InstrumentFactory.scala new file mode 100644 index 00000000..820c05b5 --- /dev/null +++ b/kamon-core/src/main/scala/kamon/metric/instrument/InstrumentFactory.scala @@ -0,0 +1,102 @@ +package kamon +package metric +package instrument + +import java.time.Duration + +import com.typesafe.config.Config +import kamon.metric.instrument.InstrumentFactory.CustomInstrumentSettings + + +private[kamon] class InstrumentFactory private ( + defaultHistogramDynamicRange: DynamicRange, + defaultMMCounterDynamicRange: DynamicRange, + defaultMMCounterSampleRate: Duration, + customSettings: Map[(String, String), CustomInstrumentSettings]) { + + def buildHistogram(entity: Entity, name: String, dynamicRange: DynamicRange = defaultHistogramDynamicRange): Histogram = + Histogram(entity, name, instrumentDynamicRange(entity, name, dynamicRange)) + + def buildMinMaxCounter(entity: Entity, name: String, dynamicRange: DynamicRange = defaultMMCounterDynamicRange, + sampleInterval: Duration = defaultMMCounterSampleRate): MinMaxCounter = + MinMaxCounter( + entity, + name, + instrumentDynamicRange(entity, name, dynamicRange), + instrumentSampleInterval(entity, name, sampleInterval) + ) + + def buildGauge(entity: Entity, name: String): Gauge = + Gauge(entity, name) + + def buildCounter(entity: Entity, name: String): Counter = + Counter(entity, name) + + + private def instrumentDynamicRange(entity: Entity, instrumentName: String, dynamicRange: DynamicRange): DynamicRange = + customSettings.get((entity.category, instrumentName)).fold(dynamicRange) { cs => + overrideDynamicRange(dynamicRange, cs) + } + + private def instrumentSampleInterval(entity: Entity, instrumentName: String, sampleInterval: Duration): Duration = + customSettings.get((entity.category, instrumentName)).fold(sampleInterval) { cs => + cs.sampleInterval.getOrElse(sampleInterval) + } + + private def overrideDynamicRange(defaultDynamicRange: DynamicRange, customSettings: CustomInstrumentSettings): DynamicRange = + DynamicRange( + customSettings.lowestDiscernibleValue.getOrElse(defaultDynamicRange.lowestDiscernibleValue), + customSettings.highestTrackableValue.getOrElse(defaultDynamicRange.highestTrackableValue), + customSettings.significantValueDigits.getOrElse(defaultDynamicRange.significantValueDigits) + ) +} + +object InstrumentFactory { + + 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") + + val customSettings = config.getConfig("custom-settings") + .configurations + .filter(nonEmptyCategories) + .flatMap(buildCustomInstrumentSettings) + + new InstrumentFactory(histogramDynamicRange, mmCounterDynamicRange, mmCounterSampleInterval, customSettings) + } + + private def nonEmptyCategories(entry: (String, Config)): Boolean = entry match { + case (_, config) => config.firstLevelKeys.nonEmpty + } + + private def buildCustomInstrumentSettings(entry: (String, Config)): Map[(String, String), CustomInstrumentSettings] = { + val (category, categoryConfig) = entry + categoryConfig.configurations.map { + case (instrumentName, instrumentConfig) => (category, instrumentName) -> readCustomSettings(instrumentConfig) + } + } + + private def readDynamicRange(config: Config): DynamicRange = + DynamicRange( + lowestDiscernibleValue = config.getLong("lowest-discernible-value"), + highestTrackableValue = config.getLong("highest-trackable-value"), + significantValueDigits = config.getInt("significant-value-digits") + ) + + + private case class CustomInstrumentSettings( + lowestDiscernibleValue: Option[Long], + highestTrackableValue: Option[Long], + significantValueDigits: Option[Int], + sampleInterval: Option[Duration] + ) + + private def readCustomSettings(config: Config): CustomInstrumentSettings = + CustomInstrumentSettings( + if (config.hasPath("lowest-discernible-value")) Some(config.getLong("lowest-discernible-value")) else None, + if (config.hasPath("highest-trackable-value")) Some(config.getLong("highest-trackable-value")) else None, + if (config.hasPath("significant-value-digits")) Some(config.getInt("significant-value-digits")) else None, + if (config.hasPath("sample-interval")) Some(config.getDuration("sample-interval")) else None + ) +} \ No newline at end of file diff --git a/kamon-core/src/main/scala/kamon/metric/instrument/MinMaxCounter.scala b/kamon-core/src/main/scala/kamon/metric/instrument/MinMaxCounter.scala new file mode 100644 index 00000000..34a983a9 --- /dev/null +++ b/kamon-core/src/main/scala/kamon/metric/instrument/MinMaxCounter.scala @@ -0,0 +1,26 @@ +package kamon.metric.instrument + +import java.time.Duration + +import kamon.metric.Entity + +trait MinMaxCounter { + def dynamicRange: DynamicRange + def sampleInterval: Duration + + def increment(): Unit + def increment(times: Long): Unit + def decrement(): Unit + def decrement(times: Long): Unit +} + +object MinMaxCounter { + def apply(entity: Entity, name: String, dynamicRange2: DynamicRange, sampleInterval2: Duration): MinMaxCounter = new MinMaxCounter { + override def sampleInterval: Duration = sampleInterval2 + override def increment(): Unit = ??? + override def increment(times: Long): Unit = ??? + override def decrement(): Unit = ??? + override def decrement(times: Long): Unit = ??? + override def dynamicRange: DynamicRange = dynamicRange2 + } +} diff --git a/kamon-core/src/main/scala/kamon/metric/snapshot/Distribution.scala b/kamon-core/src/main/scala/kamon/metric/snapshot/Distribution.scala new file mode 100644 index 00000000..99e6b24e --- /dev/null +++ b/kamon-core/src/main/scala/kamon/metric/snapshot/Distribution.scala @@ -0,0 +1,24 @@ +package kamon.metric.snapshot + +trait Distribution { + def buckets: Seq[Bucket] + def bucketsIterator: Iterator[Bucket] + + def min: Long + def max: Long + def sum: Long + def percentile(p: Double): Percentile + + def percentiles: Seq[Percentile] + def percentilesIterator: Iterator[Percentile] +} + +trait Bucket { + def value: Long + def frequency: Long +} + +trait Percentile { + def value: Long + def count: Long +} diff --git a/kamon-core/src/main/scala/kamon/metric/snapshot/EntitySnapshot.scala b/kamon-core/src/main/scala/kamon/metric/snapshot/EntitySnapshot.scala new file mode 100644 index 00000000..a2b7b606 --- /dev/null +++ b/kamon-core/src/main/scala/kamon/metric/snapshot/EntitySnapshot.scala @@ -0,0 +1,11 @@ +package kamon.metric.snapshot + +import kamon.metric.Entity + +trait EntitySnapshot { + def entity: Entity + def histograms: Seq[DistributionSnapshot] + def minMaxCounters: Seq[DistributionSnapshot] + def gauges: Seq[SingleValueSnapshot] + def counters: Seq[SingleValueSnapshot] +} \ No newline at end of file diff --git a/kamon-core/src/main/scala/kamon/metric/snapshot/InstrumentSnapshot.scala b/kamon-core/src/main/scala/kamon/metric/snapshot/InstrumentSnapshot.scala new file mode 100644 index 00000000..624121c0 --- /dev/null +++ b/kamon-core/src/main/scala/kamon/metric/snapshot/InstrumentSnapshot.scala @@ -0,0 +1,16 @@ +package kamon.metric.snapshot + +import kamon.util.MeasurementUnit + +sealed trait InstrumentSnapshot { + def name: String + def measurementUnit: MeasurementUnit +} + +trait DistributionSnapshot extends InstrumentSnapshot { + def distribution: Distribution +} + +trait SingleValueSnapshot extends InstrumentSnapshot { + def value: Long +} \ No newline at end of file diff --git a/kamon-core/src/main/scala/kamon/metric/snapshot/SingleValue.scala b/kamon-core/src/main/scala/kamon/metric/snapshot/SingleValue.scala new file mode 100644 index 00000000..ac67620c --- /dev/null +++ b/kamon-core/src/main/scala/kamon/metric/snapshot/SingleValue.scala @@ -0,0 +1,5 @@ +package kamon.metric.snapshot + +trait SingleValue { + def value: Long +} diff --git a/kamon-core/src/main/scala/kamon/metric/snapshot/TickSnapshot.scala b/kamon-core/src/main/scala/kamon/metric/snapshot/TickSnapshot.scala new file mode 100644 index 00000000..a1bdd6b1 --- /dev/null +++ b/kamon-core/src/main/scala/kamon/metric/snapshot/TickSnapshot.scala @@ -0,0 +1,17 @@ +package kamon.metric.snapshot + +import java.time.Instant + + +trait TickSnapshot { + def interval: Interval + def entities: Seq[EntitySnapshot] +} + +trait Interval { + def from: Instant + def to: Instant +} + + + diff --git a/kamon-core/src/main/scala/kamon/package.scala b/kamon-core/src/main/scala/kamon/package.scala new file mode 100644 index 00000000..3d8c3515 --- /dev/null +++ b/kamon-core/src/main/scala/kamon/package.scala @@ -0,0 +1,49 @@ +import com.typesafe.config.Config + +import scala.collection.concurrent.TrieMap + +package object kamon { + + + + /** + * Workaround to the non thread-safe [[scala.collection.concurrent.TrieMap#getOrElseUpdate]] method. More details on + * why this is necessary can be found at [[https://issues.scala-lang.org/browse/SI-7943]]. + */ + implicit class AtomicGetOrElseUpdateOnTrieMap[K, V](val trieMap: TrieMap[K, V]) extends AnyVal { + + def atomicGetOrElseUpdate(key: K, op: ⇒ V): V = + atomicGetOrElseUpdate(key, op, { v: V ⇒ Unit }) + + def atomicGetOrElseUpdate(key: K, op: ⇒ V, cleanup: V ⇒ Unit): V = + trieMap.get(key) match { + case Some(v) ⇒ v + case None ⇒ + val d = op + trieMap.putIfAbsent(key, d).map { oldValue ⇒ + // If there was an old value then `d` was never added + // and thus need to be cleanup. + cleanup(d) + oldValue + + } getOrElse (d) + } + } + + + implicit class UtilsOnConfig(val config: Config) extends AnyVal { + import scala.collection.JavaConverters._ + + def firstLevelKeys: Set[String] = { + config.entrySet().asScala.map { + case entry ⇒ entry.getKey.takeWhile(_ != '.') + } toSet + } + + def configurations: Map[String, Config] = { + firstLevelKeys + .map(entry => (entry, config.getConfig(entry))) + .toMap + } + } +} diff --git a/kamon-core/src/main/scala/kamon/trace/Sampler.scala b/kamon-core/src/main/scala/kamon/trace/Sampler.scala new file mode 100644 index 00000000..a26e61be --- /dev/null +++ b/kamon-core/src/main/scala/kamon/trace/Sampler.scala @@ -0,0 +1,5 @@ +package kamon.trace + +trait Sampler { + +} diff --git a/kamon-core/src/main/scala/kamon/trace/Tracer.scala b/kamon-core/src/main/scala/kamon/trace/Tracer.scala new file mode 100644 index 00000000..802d95ec --- /dev/null +++ b/kamon-core/src/main/scala/kamon/trace/Tracer.scala @@ -0,0 +1,5 @@ +package kamon.trace + +trait Tracer extends io.opentracing.Tracer { + def sampler: Sampler +} diff --git a/kamon-core/src/main/scala/kamon/util/Clock.scala b/kamon-core/src/main/scala/kamon/util/Clock.scala new file mode 100644 index 00000000..55bb529a --- /dev/null +++ b/kamon-core/src/main/scala/kamon/util/Clock.scala @@ -0,0 +1,9 @@ +package kamon.util + +trait Clock { + def nanoTimestamp(): Long + def microTimestamp(): Long + def milliTimestamp(): Long + + def relativeNanoTimestamp(): Long +} diff --git a/kamon-core/src/main/scala/kamon/util/EntityFilter.scala b/kamon-core/src/main/scala/kamon/util/EntityFilter.scala new file mode 100644 index 00000000..a8456689 --- /dev/null +++ b/kamon-core/src/main/scala/kamon/util/EntityFilter.scala @@ -0,0 +1,7 @@ +package kamon.util + +import kamon.metric.Entity + +trait EntityFilter { + def accept(entity: Entity): Boolean +} diff --git a/kamon-core/src/main/scala/kamon/util/MeasurementUnit.scala b/kamon-core/src/main/scala/kamon/util/MeasurementUnit.scala new file mode 100644 index 00000000..4130fa50 --- /dev/null +++ b/kamon-core/src/main/scala/kamon/util/MeasurementUnit.scala @@ -0,0 +1,15 @@ +package kamon.util + +trait MeasurementUnit { + def dimension: Dimension + def magnitude: Magnitude +} + +trait Magnitude { + def name: String +} + +trait Dimension { + def name: String + def scale(value: Long, from: Magnitude, to: Magnitude): Double +} diff --git a/kamon-core/src/test/scala/kamon/metric/instrument/InstrumentFactorySpec.scala b/kamon-core/src/test/scala/kamon/metric/instrument/InstrumentFactorySpec.scala new file mode 100644 index 00000000..fc82ddcd --- /dev/null +++ b/kamon-core/src/test/scala/kamon/metric/instrument/InstrumentFactorySpec.scala @@ -0,0 +1,112 @@ +package kamon.metric.instrument + +import java.time.Duration + +import com.typesafe.config.ConfigFactory +import kamon.metric.Entity +import org.scalatest.{Matchers, WordSpec} + +class InstrumentFactorySpec extends WordSpec with Matchers{ + val testEntity = Entity("test", "test-category", Map.empty) + 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 + | } + | + | min-max-counter { + | lowest-discernible-value = 200 + | highest-trackable-value = 6000 + | significant-value-digits = 3 + | sample-interval = 647 millis + | } + |} + | + |custom-settings { + | + |} + """.stripMargin + ) + + + "the metrics InstrumentFactory" should { + "create instruments using the default configuration settings" in { + val factory = InstrumentFactory(baseConfiguration) + val histogram = factory.buildHistogram(testEntity, "my-histogram") + val mmCounter = factory.buildMinMaxCounter(testEntity, "my-mm-counter") + + histogram.dynamicRange.lowestDiscernibleValue shouldBe(100) + histogram.dynamicRange.highestTrackableValue shouldBe(5000) + histogram.dynamicRange.significantValueDigits shouldBe(2) + + mmCounter.dynamicRange.lowestDiscernibleValue shouldBe(200) + mmCounter.dynamicRange.highestTrackableValue shouldBe(6000) + mmCounter.dynamicRange.significantValueDigits shouldBe(3) + mmCounter.sampleInterval shouldBe(Duration.ofMillis(647)) + } + + "accept custom settings when building instruments" in { + val factory = InstrumentFactory(baseConfiguration) + val histogram = factory.buildHistogram(testEntity, "my-histogram", DynamicRange.Loose) + val mmCounter = factory.buildMinMaxCounter(testEntity, "my-mm-counter", DynamicRange.Fine, Duration.ofMillis(500)) + + histogram.dynamicRange shouldBe(DynamicRange.Loose) + + mmCounter.dynamicRange shouldBe(DynamicRange.Fine) + mmCounter.sampleInterval shouldBe(Duration.ofMillis(500)) + } + + "allow overriding any default and provided settings via the custom-settings configuration key" in { + val customConfig = ConfigFactory.parseString( + """ + |custom-settings { + | custom-category { + | modified-histogram { + | lowest-discernible-value = 99 + | highest-trackable-value = 999 + | significant-value-digits = 7 + | } + | + | modified-mm-counter { + | lowest-discernible-value = 784 + | highest-trackable-value = 14785 + | significant-value-digits = 1 + | sample-interval = 3 seconds + | } + | } + |} + """.stripMargin + ).withFallback(baseConfiguration) + + val factory = InstrumentFactory(customConfig) + val defaultHistogram = factory.buildHistogram(customEntity, "default-histogram") + val modifiedHistogram = factory.buildHistogram(customEntity, "modified-histogram", DynamicRange.Loose) + + defaultHistogram.dynamicRange.lowestDiscernibleValue shouldBe(100) + defaultHistogram.dynamicRange.highestTrackableValue shouldBe(5000) + defaultHistogram.dynamicRange.significantValueDigits shouldBe(2) + + modifiedHistogram.dynamicRange.lowestDiscernibleValue shouldBe(99) + modifiedHistogram.dynamicRange.highestTrackableValue shouldBe(999) + modifiedHistogram.dynamicRange.significantValueDigits shouldBe(7) + + + val defaultMMCounter = factory.buildMinMaxCounter(customEntity, "default-mm-counter") + val modifiedMMCounter = factory.buildMinMaxCounter(customEntity, "modified-mm-counter", DynamicRange.Loose) + + defaultMMCounter.dynamicRange.lowestDiscernibleValue shouldBe(200) + defaultMMCounter.dynamicRange.highestTrackableValue shouldBe(6000) + defaultMMCounter.dynamicRange.significantValueDigits shouldBe(3) + defaultMMCounter.sampleInterval shouldBe(Duration.ofMillis(647)) + + modifiedMMCounter.dynamicRange.lowestDiscernibleValue shouldBe(784) + modifiedMMCounter.dynamicRange.highestTrackableValue shouldBe(14785) + modifiedMMCounter.dynamicRange.significantValueDigits shouldBe(1) + modifiedMMCounter.sampleInterval shouldBe(Duration.ofSeconds(3)) + } + } +} -- cgit v1.2.3