From f5e70695ad0124cd5cd648d186d5174c7b121266 Mon Sep 17 00:00:00 2001 From: Ivan Topolnjak Date: Thu, 27 Apr 2017 23:48:39 +0200 Subject: implement HdrHistogram and Distribution snapshots --- build.sbt | 20 ++- kamon-core/src/main/scala/kamon/Kamon.scala | 7 +- .../src/main/scala/kamon/metric/Entity.scala | 8 +- .../scala/kamon/metric/instrument/Counter.scala | 31 +++- .../kamon/metric/instrument/DynamicRange.scala | 6 +- .../main/scala/kamon/metric/instrument/Gauge.scala | 3 + .../scala/kamon/metric/instrument/Histogram.scala | 184 ++++++++++++++++++++- .../metric/instrument/HistogramExtension.scala | 40 +++++ .../metric/instrument/InstrumentFactory.scala | 29 +++- .../metric/instrument/InstrumentSnapshot.scala | 44 +++++ .../kamon/metric/instrument/MinMaxCounter.scala | 5 + .../scala/kamon/metric/snapshot/Distribution.scala | 24 --- .../kamon/metric/snapshot/EntitySnapshot.scala | 1 + .../kamon/metric/snapshot/InstrumentSnapshot.scala | 16 -- .../scala/kamon/metric/snapshot/SingleValue.scala | 5 - kamon-core/src/main/scala/kamon/package.scala | 2 +- .../src/test/scala/kamon/LogInterceptor.scala | 14 ++ .../kamon/metric/instrument/CounterSpec.scala | 46 ++++++ .../metric/instrument/InstrumentFactorySpec.scala | 4 +- .../kamon/testkit/DefaultInstrumentFactory.scala | 13 ++ 20 files changed, 427 insertions(+), 75 deletions(-) create mode 100644 kamon-core/src/main/scala/kamon/metric/instrument/HistogramExtension.scala create mode 100644 kamon-core/src/main/scala/kamon/metric/instrument/InstrumentSnapshot.scala delete mode 100644 kamon-core/src/main/scala/kamon/metric/snapshot/Distribution.scala delete mode 100644 kamon-core/src/main/scala/kamon/metric/snapshot/InstrumentSnapshot.scala delete mode 100644 kamon-core/src/main/scala/kamon/metric/snapshot/SingleValue.scala create mode 100644 kamon-core/src/test/scala/kamon/LogInterceptor.scala create mode 100644 kamon-core/src/test/scala/kamon/metric/instrument/CounterSpec.scala create mode 100644 kamon-core/src/test/scala/kamon/testkit/DefaultInstrumentFactory.scala diff --git a/build.sbt b/build.sbt index 65fc7811..dceeb55e 100644 --- a/build.sbt +++ b/build.sbt @@ -27,19 +27,31 @@ lazy val core = (project in file("kamon-core")) libraryDependencies ++= Seq( "com.typesafe" % "config" % "1.3.1", "org.slf4j" % "slf4j-api" % "1.7.7", + "com.typesafe.scala-logging" %% "scala-logging" % "3.5.0", "org.hdrhistogram" % "HdrHistogram" % "2.1.9", "io.opentracing" % "opentracing-api" % "0.21.1-SNAPSHOT", + "uk.org.lidalia" % "slf4j-test" % "1.1.0", "org.scalatest" %% "scalatest" % "3.0.1" % "test" ) -) - + ) lazy val testkit = (project in file("kamon-testkit")) - .dependsOn(core) .settings(moduleName := "kamon-testkit", resolvers += Resolver.mavenLocal) .settings( libraryDependencies ++= compileScope(akkaDependency("actor").value, akkaDependency("testkit").value) ++ providedScope(aspectJ) ++ - testScope(slf4jApi, slf4jnop)) \ No newline at end of file + testScope(slf4jApi, slf4jnop) + ).dependsOn(core) + +// +//lazy val coreTests = (project in file("kamon-core-tests")) +// .settings(moduleName := "kamon-core-tests", resolvers += Resolver.mavenLocal) +// .settings(noPublishing: _*) +// .settings( +// libraryDependencies ++= +// compileScope(akkaDependency("actor").value, akkaDependency("testkit").value) ++ +// providedScope(aspectJ) ++ +// testScope(slf4jApi, slf4jnop) +// ).dependsOn(testkit ) diff --git a/kamon-core/src/main/scala/kamon/Kamon.scala b/kamon-core/src/main/scala/kamon/Kamon.scala index 1dd72ab6..75ad81d5 100644 --- a/kamon-core/src/main/scala/kamon/Kamon.scala +++ b/kamon-core/src/main/scala/kamon/Kamon.scala @@ -1,7 +1,8 @@ package kamon import com.typesafe.config.Config -import kamon.metric.Metrics +import kamon.metric.instrument.Histogram +import kamon.metric.{Entity, EntityRecorder, Metrics} import kamon.trace.Tracer /** @@ -26,6 +27,10 @@ trait Kamon { } +object Kamon { + def getHistogram: Histogram = ??? +} + /* diff --git a/kamon-core/src/main/scala/kamon/metric/Entity.scala b/kamon-core/src/main/scala/kamon/metric/Entity.scala index 5e2b8b46..a38a7f46 100644 --- a/kamon-core/src/main/scala/kamon/metric/Entity.scala +++ b/kamon-core/src/main/scala/kamon/metric/Entity.scala @@ -2,4 +2,10 @@ package kamon.metric -case class Entity(name: String, category: String, tags: Map[String, String]) +case class Entity(name: String, category: String, tags: Map[String, String]) { + + override def toString: String = { + val tagString = tags.map { case (k, v) => k + "=>" + v }.mkString(",") + "Entity(name=\"" + name + "\", category=\"" + category + "\", tags=\"" + tagString + "\"" + } +} diff --git a/kamon-core/src/main/scala/kamon/metric/instrument/Counter.scala b/kamon-core/src/main/scala/kamon/metric/instrument/Counter.scala index 4a9edd77..10b9c3a6 100644 --- a/kamon-core/src/main/scala/kamon/metric/instrument/Counter.scala +++ b/kamon-core/src/main/scala/kamon/metric/instrument/Counter.scala @@ -1,11 +1,34 @@ -package kamon.metric.instrument +package kamon +package metric +package instrument -import kamon.metric.Entity +import java.util.concurrent.atomic.LongAdder + +import com.typesafe.scalalogging.StrictLogging +import kamon.util.MeasurementUnit trait Counter { + def measurementUnit: MeasurementUnit + def increment(): Unit + def increment(times: Long): Unit } -object Counter { - def apply(entity: Entity, name: String): Counter = ??? +class LongAdderCounter(entity: Entity, name: String, val measurementUnit: MeasurementUnit) + extends Counter with SingleValueSnapshotInstrument with StrictLogging { + + private val adder = new LongAdder() + + def increment(): Unit = + adder.increment() + + def increment(times: Long): Unit = { + if (times >= 0) + adder.add(times) + else + logger.warn(s"Ignored attempt to decrement counter [$name] on entity [$entity]") + } + + def snapshot(): SingleValueSnapshot = + SingleValueSnapshot(name, measurementUnit, adder.sumThenReset()) } diff --git a/kamon-core/src/main/scala/kamon/metric/instrument/DynamicRange.scala b/kamon-core/src/main/scala/kamon/metric/instrument/DynamicRange.scala index 628439c2..226f5450 100644 --- a/kamon-core/src/main/scala/kamon/metric/instrument/DynamicRange.scala +++ b/kamon-core/src/main/scala/kamon/metric/instrument/DynamicRange.scala @@ -17,17 +17,17 @@ object DynamicRange { * 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) + val Loose = DynamicRange(1L, 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) + val Default = DynamicRange(1L, 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) + val Fine = DynamicRange(1L, 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 index 43c71206..bb31e30a 100644 --- a/kamon-core/src/main/scala/kamon/metric/instrument/Gauge.scala +++ b/kamon-core/src/main/scala/kamon/metric/instrument/Gauge.scala @@ -1,8 +1,11 @@ package kamon.metric.instrument import kamon.metric.Entity +import kamon.util.MeasurementUnit trait Gauge { + def measurementUnit: MeasurementUnit + def increment(): Unit def increment(times: Long): Unit def decrement(): Unit diff --git a/kamon-core/src/main/scala/kamon/metric/instrument/Histogram.scala b/kamon-core/src/main/scala/kamon/metric/instrument/Histogram.scala index c43c9dbc..76d4ab65 100644 --- a/kamon-core/src/main/scala/kamon/metric/instrument/Histogram.scala +++ b/kamon-core/src/main/scala/kamon/metric/instrument/Histogram.scala @@ -1,18 +1,192 @@ package kamon.metric.instrument +import java.nio.ByteBuffer + +import com.typesafe.scalalogging.StrictLogging import kamon.metric.Entity +import kamon.util.MeasurementUnit +import org.HdrHistogram.{AtomicHistogramExtension, ZigZag} trait Histogram { def dynamicRange: DynamicRange + def measurementUnit: MeasurementUnit 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 = ??? + +class HdrHistogram(entity: Entity, name: String, val measurementUnit: MeasurementUnit, val dynamicRange: DynamicRange) + extends AtomicHistogramExtension(dynamicRange) with Histogram with DistributionSnapshotInstrument with StrictLogging { + + def record(value: Long): Unit = + tryRecord(value, 1) + + def record(value: Long, count: Long): Unit = + tryRecord(value, count) + + private def tryRecord(value: Long, count: Long): Unit = { + try { + recordValueWithCount(value, count) + } catch { + case anyException: Throwable ⇒ + logger.warn(s"Failed to store value [$value] in histogram [$name] of entity [$entity]. You might need to change " + + "your dynamic range configuration for this instrument.", anyException) + } + } + + override def snapshot(): DistributionSnapshot = { + val buffer = HdrHistogram.tempSnapshotBuffer.get() + val counts = countsArray() + val countsLimit = counts.length() + var index = 0 + buffer.clear() + + var minIndex = Int.MaxValue + var maxIndex = 0 + var totalCount = 0L + + while(index < countsLimit) { + val countAtIndex = counts.getAndSet(index, 0L) + + var zerosCount = 0L + if(countAtIndex == 0L) { + index += 1 + zerosCount = 1 + while(index < countsLimit && counts.get(index) == 0L) { + index += 1 + zerosCount += 1 + } + } + + if(zerosCount > 0) { + if(index < countsLimit) + ZigZag.putLong(buffer, -zerosCount) + } + else { + if(minIndex > index) + minIndex = index + maxIndex = index + + index += 1 + totalCount += countAtIndex + ZigZag.putLong(buffer, countAtIndex) + } + } + + buffer.flip() + val zigZagCounts = Array.ofDim[Byte](buffer.limit()) + buffer.get(zigZagCounts) + + val distribution = new ZigZagCountsDistribution(totalCount, minIndex, maxIndex, ByteBuffer.wrap(zigZagCounts), + protectedUnitMagnitude(), protectedSubBucketHalfCount(), protectedSubBucketHalfCountMagnitude()) + + DistributionSnapshot(name, measurementUnit, dynamicRange, distribution) } + + private class ZigZagCountsDistribution(val count: Long, minIndex: Int, maxIndex: Int, zigZagCounts: ByteBuffer, + unitMagnitude: Int, subBucketHalfCount: Int, subBucketHalfCountMagnitude: Int) extends Distribution { + + val min: Long = if(count == 0) 0 else bucketValueAtIndex(minIndex) + val max: Long = bucketValueAtIndex(maxIndex) + def sum: Long = bucketsIterator.foldLeft(0L)((a, b) => a + (b.value * b.frequency)) + + def buckets: Seq[Bucket] = { + val builder = Vector.newBuilder[Bucket] + bucketsIterator.foreach { b => + builder += DefaultBucket(b.value, b.frequency) + } + + builder.result() + } + + def bucketsIterator: Iterator[Bucket] = new Iterator[Bucket] { + val buffer = zigZagCounts.duplicate() + val bucket = MutableBucket(0, 0) + var countsArrayIndex = 0 + + def hasNext: Boolean = + buffer.remaining() > 0 + + def next(): Bucket = { + val readLong = ZigZag.getLong(buffer) + val frequency = if(readLong > 0) { + readLong + } else { + countsArrayIndex += (-readLong.toInt) + ZigZag.getLong(buffer) + } + + bucket.value = bucketValueAtIndex(countsArrayIndex) + bucket.frequency = frequency + countsArrayIndex += 1 + bucket + } + } + + def percentilesIterator: Iterator[Percentile] = new Iterator[Percentile]{ + val buckets = bucketsIterator + val percentile = MutablePercentile(0D, 0, 0) + var countUnderQuantile = 0L + + def hasNext: Boolean = + buckets.hasNext + + def next(): Percentile = { + val bucket = buckets.next() + countUnderQuantile += bucket.frequency + percentile.quantile = (countUnderQuantile * 100D) / ZigZagCountsDistribution.this.count + percentile.countUnderQuantile = countUnderQuantile + percentile.value = bucket.value + percentile + } + } + + def percentile(p: Double): Percentile = { + val percentiles = percentilesIterator + if(percentiles.hasNext) { + var currentPercentile = percentiles.next() + while(percentiles.hasNext && currentPercentile.quantile < p) { + currentPercentile = percentiles.next() + } + + currentPercentile + + } else DefaultPercentile(p, 0, 0) + } + + + def percentiles: Seq[Percentile] = { + val builder = Vector.newBuilder[Percentile] + percentilesIterator.foreach { p => + builder += DefaultPercentile(p.quantile, p.value, p.countUnderQuantile) + } + + builder.result() + } + + @inline private def bucketValueAtIndex(index: Int): Long = { + var bucketIndex: Int = (index >> subBucketHalfCountMagnitude) - 1 + var subBucketIndex: Int = (index & (subBucketHalfCount - 1)) + subBucketHalfCount + if (bucketIndex < 0) { + subBucketIndex -= subBucketHalfCount + bucketIndex = 0 + } + + subBucketIndex.toLong << (bucketIndex + unitMagnitude) + } + } + + case class DefaultBucket(value: Long, frequency: Long) extends Bucket + case class MutableBucket(var value: Long, var frequency: Long) extends Bucket + + case class DefaultPercentile(quantile: Double, value: Long, countUnderQuantile: Long) extends Percentile + case class MutablePercentile(var quantile: Double, var value: Long, var countUnderQuantile: Long) extends Percentile } + +object HdrHistogram { + // TODO: move this to some object pool might be better, or at + private val tempSnapshotBuffer = new ThreadLocal[ByteBuffer] { + override def initialValue(): ByteBuffer = ByteBuffer.allocate(33792) + } +} \ No newline at end of file diff --git a/kamon-core/src/main/scala/kamon/metric/instrument/HistogramExtension.scala b/kamon-core/src/main/scala/kamon/metric/instrument/HistogramExtension.scala new file mode 100644 index 00000000..ebb82040 --- /dev/null +++ b/kamon-core/src/main/scala/kamon/metric/instrument/HistogramExtension.scala @@ -0,0 +1,40 @@ +package org.HdrHistogram + +import java.nio.ByteBuffer +import java.util.concurrent.atomic.AtomicLongArray + +import kamon.metric.instrument.DynamicRange + +/** + * This class exposes package-private members of the [[AtomicHistogram]] class that are required to properly generate + * snapshots of our HdrHistogram implementation. + */ +abstract class AtomicHistogramExtension(dr: DynamicRange) + extends AtomicHistogram(dr.lowestDiscernibleValue, dr.highestTrackableValue, dr.significantValueDigits) { + + override def incrementTotalCount(): Unit = {} + override def addToTotalCount(value: Long): Unit = {} + + def countsArray(): AtomicLongArray = counts + def protectedUnitMagnitude(): Int = unitMagnitude + def protectedSubBucketHalfCount(): Int = subBucketHalfCount + def protectedSubBucketHalfCountMagnitude(): Int = subBucketHalfCountMagnitude +} + +/** + * Exposes the package-private members of [[ZigZagEncoding]]. + */ +object ZigZag { + def putLong(buffer: ByteBuffer, value: Long): Unit = + ZigZagEncoding.putLong(buffer, value) + + def getLong(buffer: ByteBuffer): Long = + ZigZagEncoding.getLong(buffer) + + def putInt(buffer: ByteBuffer, value: Int): Unit = + ZigZagEncoding.putInt(buffer, value) + + def getInt(buffer: ByteBuffer): Int = + ZigZagEncoding.getInt(buffer) +} + 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 820c05b5..1ccd5899 100644 --- a/kamon-core/src/main/scala/kamon/metric/instrument/InstrumentFactory.scala +++ b/kamon-core/src/main/scala/kamon/metric/instrument/InstrumentFactory.scala @@ -6,31 +6,42 @@ import java.time.Duration import com.typesafe.config.Config import kamon.metric.instrument.InstrumentFactory.CustomInstrumentSettings +import kamon.util.MeasurementUnit -private[kamon] class InstrumentFactory private ( +private[metric] 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 buildHistogram(entity: Entity, name: String, dynamicRange: DynamicRange = defaultHistogramDynamicRange, + measurementUnit: MeasurementUnit = MeasurementUnit.none): Histogram = { + + new HdrHistogram( + entity, + name, + measurementUnit, + instrumentDynamicRange(entity, name, dynamicRange) + ) + } def buildMinMaxCounter(entity: Entity, name: String, dynamicRange: DynamicRange = defaultMMCounterDynamicRange, - sampleInterval: Duration = defaultMMCounterSampleRate): MinMaxCounter = + sampleInterval: Duration = defaultMMCounterSampleRate, measurementUnit: MeasurementUnit = MeasurementUnit.none): MinMaxCounter = { + MinMaxCounter( entity, name, instrumentDynamicRange(entity, name, dynamicRange), instrumentSampleInterval(entity, name, sampleInterval) ) + } - def buildGauge(entity: Entity, name: String): Gauge = + def buildGauge(entity: Entity, name: String, measurementUnit: MeasurementUnit = MeasurementUnit.none): Gauge = Gauge(entity, name) - def buildCounter(entity: Entity, name: String): Counter = - Counter(entity, name) + def buildCounter(entity: Entity, name: String, measurementUnit: MeasurementUnit = MeasurementUnit.none): Counter with SingleValueSnapshotInstrument = + new LongAdderCounter(entity, name, measurementUnit) private def instrumentDynamicRange(entity: Entity, instrumentName: String, dynamicRange: DynamicRange): DynamicRange = @@ -51,9 +62,9 @@ private[kamon] class InstrumentFactory private ( ) } -object InstrumentFactory { +private[kamon] object InstrumentFactory { - def apply(config: Config): 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") diff --git a/kamon-core/src/main/scala/kamon/metric/instrument/InstrumentSnapshot.scala b/kamon-core/src/main/scala/kamon/metric/instrument/InstrumentSnapshot.scala new file mode 100644 index 00000000..58e10c54 --- /dev/null +++ b/kamon-core/src/main/scala/kamon/metric/instrument/InstrumentSnapshot.scala @@ -0,0 +1,44 @@ +package kamon.metric.instrument + +import kamon.util.MeasurementUnit + + +case class SingleValueSnapshot(name: String, measurementUnit: MeasurementUnit, value: Long) + +case class DistributionSnapshot(name: String, measurementUnit: MeasurementUnit, dynamicRange: DynamicRange, distribution: Distribution) + +trait DistributionSnapshotInstrument { + def snapshot(): DistributionSnapshot +} + +trait SingleValueSnapshotInstrument { + def snapshot(): SingleValueSnapshot +} + + + + +trait Distribution { + def buckets: Seq[Bucket] + def bucketsIterator: Iterator[Bucket] + + def min: Long + def max: Long + def sum: Long + def count: 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 quantile: Double + def value: Long + def countUnderQuantile: Long +} diff --git a/kamon-core/src/main/scala/kamon/metric/instrument/MinMaxCounter.scala b/kamon-core/src/main/scala/kamon/metric/instrument/MinMaxCounter.scala index 34a983a9..8a43865f 100644 --- a/kamon-core/src/main/scala/kamon/metric/instrument/MinMaxCounter.scala +++ b/kamon-core/src/main/scala/kamon/metric/instrument/MinMaxCounter.scala @@ -3,10 +3,12 @@ package kamon.metric.instrument import java.time.Duration import kamon.metric.Entity +import kamon.util.MeasurementUnit trait MinMaxCounter { def dynamicRange: DynamicRange def sampleInterval: Duration + def measurementUnit: MeasurementUnit def increment(): Unit def increment(times: Long): Unit @@ -16,6 +18,9 @@ trait MinMaxCounter { object MinMaxCounter { def apply(entity: Entity, name: String, dynamicRange2: DynamicRange, sampleInterval2: Duration): MinMaxCounter = new MinMaxCounter { + + override def measurementUnit: MeasurementUnit = ??? + override def sampleInterval: Duration = sampleInterval2 override def increment(): Unit = ??? override def increment(times: Long): Unit = ??? diff --git a/kamon-core/src/main/scala/kamon/metric/snapshot/Distribution.scala b/kamon-core/src/main/scala/kamon/metric/snapshot/Distribution.scala deleted file mode 100644 index 99e6b24e..00000000 --- a/kamon-core/src/main/scala/kamon/metric/snapshot/Distribution.scala +++ /dev/null @@ -1,24 +0,0 @@ -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 index a2b7b606..41fde2c0 100644 --- a/kamon-core/src/main/scala/kamon/metric/snapshot/EntitySnapshot.scala +++ b/kamon-core/src/main/scala/kamon/metric/snapshot/EntitySnapshot.scala @@ -1,6 +1,7 @@ package kamon.metric.snapshot import kamon.metric.Entity +import kamon.metric.instrument.{DistributionSnapshot, SingleValueSnapshot} trait EntitySnapshot { def entity: Entity diff --git a/kamon-core/src/main/scala/kamon/metric/snapshot/InstrumentSnapshot.scala b/kamon-core/src/main/scala/kamon/metric/snapshot/InstrumentSnapshot.scala deleted file mode 100644 index 624121c0..00000000 --- a/kamon-core/src/main/scala/kamon/metric/snapshot/InstrumentSnapshot.scala +++ /dev/null @@ -1,16 +0,0 @@ -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 deleted file mode 100644 index ac67620c..00000000 --- a/kamon-core/src/main/scala/kamon/metric/snapshot/SingleValue.scala +++ /dev/null @@ -1,5 +0,0 @@ -package kamon.metric.snapshot - -trait SingleValue { - def value: Long -} diff --git a/kamon-core/src/main/scala/kamon/package.scala b/kamon-core/src/main/scala/kamon/package.scala index 3d8c3515..92c48017 100644 --- a/kamon-core/src/main/scala/kamon/package.scala +++ b/kamon-core/src/main/scala/kamon/package.scala @@ -7,7 +7,7 @@ package object kamon { /** - * Workaround to the non thread-safe [[scala.collection.concurrent.TrieMap#getOrElseUpdate]] method. More details on + * 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 { diff --git a/kamon-core/src/test/scala/kamon/LogInterceptor.scala b/kamon-core/src/test/scala/kamon/LogInterceptor.scala new file mode 100644 index 00000000..b5b8a79c --- /dev/null +++ b/kamon-core/src/test/scala/kamon/LogInterceptor.scala @@ -0,0 +1,14 @@ +package kamon + +import uk.org.lidalia.slf4jext.Level +import uk.org.lidalia.slf4jtest.{LoggingEvent, TestLogger} + +trait LogInterceptor { + + def interceptLog[T](level: Level)(code: => T)(implicit tl: TestLogger): Seq[LoggingEvent] = { + import scala.collection.JavaConverters._ + tl.clear() + val run = code + tl.getLoggingEvents().asScala.filter(_.getLevel == level) + } +} diff --git a/kamon-core/src/test/scala/kamon/metric/instrument/CounterSpec.scala b/kamon-core/src/test/scala/kamon/metric/instrument/CounterSpec.scala new file mode 100644 index 00000000..26b3456d --- /dev/null +++ b/kamon-core/src/test/scala/kamon/metric/instrument/CounterSpec.scala @@ -0,0 +1,46 @@ +package kamon.metric.instrument + +import kamon.LogInterceptor +import kamon.metric.Entity +import kamon.testkit.DefaultInstrumentFactory +import org.scalatest.{Matchers, WordSpec} +import uk.org.lidalia.slf4jext.Level +import uk.org.lidalia.slf4jtest.TestLoggerFactory + +class CounterSpec extends WordSpec with Matchers with LogInterceptor with DefaultInstrumentFactory { + implicit val testLogger = TestLoggerFactory.getTestLogger(classOf[LongAdderCounter]) + + "a Counter" should { + + "allow unit and bundled increments" in { + val counter = buildCounter("unit-increments") + counter.increment() + counter.increment() + counter.increment(40) + + counter.snapshot().value shouldBe(42) + } + + "warn the user and ignore attempts to decrement the counter" in { + val counter = buildCounter("attempt-to-decrement") + counter.increment(100) + counter.increment(100) + counter.increment(100) + + interceptLog(Level.WARN) { + counter.increment(-10L) + }.head.getMessage() shouldBe(s"Ignored attempt to decrement counter [attempt-to-decrement] on entity [$defaultEntity]") + + counter.snapshot().value shouldBe(300) + } + + "reset the internal state to zero after taking snapshots" in { + val counter = buildCounter("reset-after-snapshot") + counter.increment() + counter.increment(10) + + counter.snapshot().value shouldBe(11) + counter.snapshot().value shouldBe(0) + } + } +} 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 fc82ddcd..5bf16d4c 100644 --- a/kamon-core/src/test/scala/kamon/metric/instrument/InstrumentFactorySpec.scala +++ b/kamon-core/src/test/scala/kamon/metric/instrument/InstrumentFactorySpec.scala @@ -68,7 +68,7 @@ class InstrumentFactorySpec extends WordSpec with Matchers{ | modified-histogram { | lowest-discernible-value = 99 | highest-trackable-value = 999 - | significant-value-digits = 7 + | significant-value-digits = 4 | } | | modified-mm-counter { @@ -92,7 +92,7 @@ class InstrumentFactorySpec extends WordSpec with Matchers{ modifiedHistogram.dynamicRange.lowestDiscernibleValue shouldBe(99) modifiedHistogram.dynamicRange.highestTrackableValue shouldBe(999) - modifiedHistogram.dynamicRange.significantValueDigits shouldBe(7) + modifiedHistogram.dynamicRange.significantValueDigits shouldBe(4) val defaultMMCounter = factory.buildMinMaxCounter(customEntity, "default-mm-counter") diff --git a/kamon-core/src/test/scala/kamon/testkit/DefaultInstrumentFactory.scala b/kamon-core/src/test/scala/kamon/testkit/DefaultInstrumentFactory.scala new file mode 100644 index 00000000..acec5915 --- /dev/null +++ b/kamon-core/src/test/scala/kamon/testkit/DefaultInstrumentFactory.scala @@ -0,0 +1,13 @@ +package kamon.testkit + +import com.typesafe.config.ConfigFactory +import kamon.metric.Entity +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")) + + def buildCounter(name: String) = instrumentFactory.buildCounter(defaultEntity, name) + +} -- cgit v1.2.3