diff options
Diffstat (limited to 'kamon-core/src/test')
20 files changed, 1295 insertions, 486 deletions
diff --git a/kamon-core/src/test/scala/kamon/trace/instrumentation/ActorMessagePassingTracingSpec.scala b/kamon-core/src/test/scala/kamon/instrumentation/akka/ActorCellInstrumentationSpec.scala index acc939fb..ee3857c3 100644 --- a/kamon-core/src/test/scala/kamon/trace/instrumentation/ActorMessagePassingTracingSpec.scala +++ b/kamon-core/src/test/scala/kamon/instrumentation/akka/ActorCellInstrumentationSpec.scala @@ -23,9 +23,11 @@ import kamon.trace.TraceRecorder import akka.pattern.{ pipe, ask } import akka.util.Timeout import scala.concurrent.duration._ -import akka.routing.RoundRobinRouter +import akka.routing.{ RoundRobinRouter } + +class ActorCellInstrumentationSpec extends TestKit(ActorSystem("actor-cell-instrumentation-spec")) with WordSpecLike + with ImplicitSender { -class ActorMessagePassingTracingSpec extends TestKit(ActorSystem("actor-message-passing-tracing-spec")) with WordSpecLike with ImplicitSender { implicit val executionContext = system.dispatcher "the message passing instrumentation" should { diff --git a/kamon-core/src/test/scala/kamon/trace/instrumentation/ActorLoggingSpec.scala b/kamon-core/src/test/scala/kamon/instrumentation/akka/ActorLoggingInstrumentationSpec.scala index 81fd9cbc..3dab44bc 100644 --- a/kamon-core/src/test/scala/kamon/trace/instrumentation/ActorLoggingSpec.scala +++ b/kamon-core/src/test/scala/kamon/instrumentation/akka/ActorLoggingInstrumentationSpec.scala @@ -13,15 +13,16 @@ * See the License for the specific language governing permissions and * limitations under the License. * ========================================================== */ -package kamon.trace.instrumentation +package kamon.instrumentation.akka -import akka.testkit.TestKit -import org.scalatest.{ Inspectors, Matchers, WordSpecLike } -import akka.actor.{ Props, ActorLogging, Actor, ActorSystem } +import akka.actor.{ Actor, ActorLogging, ActorSystem, Props } import akka.event.Logging.LogEvent +import akka.testkit.TestKit import kamon.trace.{ TraceContextAware, TraceRecorder } +import org.scalatest.{ Inspectors, Matchers, WordSpecLike } -class ActorLoggingSpec extends TestKit(ActorSystem("actor-logging-spec")) with WordSpecLike with Matchers with Inspectors { +class ActorLoggingInstrumentationSpec extends TestKit(ActorSystem("actor-logging-instrumentation-spec")) with WordSpecLike + with Matchers with Inspectors { "the ActorLogging instrumentation" should { "attach the TraceContext (if available) to log events" in { diff --git a/kamon-core/src/test/scala/kamon/trace/instrumentation/ActorSystemMessagePassingInstrumentationSpec.scala b/kamon-core/src/test/scala/kamon/instrumentation/akka/ActorSystemMessageInstrumentationSpec.scala index ed239b38..47867c55 100644 --- a/kamon-core/src/test/scala/kamon/trace/instrumentation/ActorSystemMessagePassingInstrumentationSpec.scala +++ b/kamon-core/src/test/scala/kamon/instrumentation/akka/ActorSystemMessageInstrumentationSpec.scala @@ -1,14 +1,17 @@ -package kamon.trace.instrumentation +package kamon.instrumentation.akka -import akka.testkit.{ ImplicitSender, TestKit } +import akka.actor.SupervisorStrategy.{ Escalate, Restart, Resume, Stop } import akka.actor._ -import org.scalatest.WordSpecLike +import akka.testkit.{ ImplicitSender, TestKit } import kamon.trace.TraceRecorder -import scala.util.control.NonFatal -import akka.actor.SupervisorStrategy.{ Escalate, Stop, Restart, Resume } +import org.scalatest.WordSpecLike + import scala.concurrent.duration._ +import scala.util.control.NonFatal + +class ActorSystemMessageInstrumentationSpec extends TestKit(ActorSystem("actor-system-message-instrumentation-spec")) + with WordSpecLike with ImplicitSender { -class ActorSystemMessagePassingInstrumentationSpec extends TestKit(ActorSystem("actor-message-passing-tracing-spec")) with WordSpecLike with ImplicitSender { implicit val executionContext = system.dispatcher "the system message passing instrumentation" should { @@ -107,7 +110,7 @@ class ActorSystemMessagePassingInstrumentationSpec extends TestKit(ActorSystem(" } def supervisorWithDirective(directive: SupervisorStrategy.Directive, sendPreRestart: Boolean = false, sendPostRestart: Boolean = false, - sendPostStop: Boolean = false, sendPreStart: Boolean = false): ActorRef = { + sendPostStop: Boolean = false, sendPreStart: Boolean = false): ActorRef = { class GrandParent extends Actor { val child = context.actorOf(Props(new Parent)) diff --git a/kamon-core/src/test/scala/kamon/trace/instrumentation/AskPatternTracingSpec.scala b/kamon-core/src/test/scala/kamon/instrumentation/akka/AskPatternInstrumentationSpec.scala index fb886de6..d914ffe8 100644 --- a/kamon-core/src/test/scala/kamon/trace/instrumentation/AskPatternTracingSpec.scala +++ b/kamon-core/src/test/scala/kamon/instrumentation/akka/AskPatternInstrumentationSpec.scala @@ -14,19 +14,20 @@ * ========================================================================================= */ -package kamon.trace.instrumentation +package kamon.instrumentation.akka -import akka.testkit.TestKitBase -import akka.actor.{ Props, Actor, ActorSystem } -import org.scalatest.{ Matchers, WordSpecLike } +import akka.actor.{ Actor, ActorSystem, Props } import akka.event.Logging.Warning -import scala.concurrent.duration._ import akka.pattern.ask +import akka.testkit.TestKitBase import akka.util.Timeout -import kamon.trace.{ TraceContextAware, TraceRecorder } import com.typesafe.config.ConfigFactory +import kamon.trace.{ TraceContextAware, TraceRecorder } +import org.scalatest.{ Matchers, WordSpecLike } + +import scala.concurrent.duration._ -class AskPatternTracingSpec extends TestKitBase with WordSpecLike with Matchers { +class AskPatternInstrumentationSpec extends TestKitBase with WordSpecLike with Matchers { implicit lazy val system: ActorSystem = ActorSystem("ask-pattern-tracing-spec", ConfigFactory.parseString( """ |kamon { diff --git a/kamon-core/src/test/scala/kamon/trace/instrumentation/FutureTracingSpec.scala b/kamon-core/src/test/scala/kamon/instrumentation/scala/FutureInstrumentationSpec.scala index b1765fd8..31afd3ff 100644 --- a/kamon-core/src/test/scala/kamon/trace/instrumentation/FutureTracingSpec.scala +++ b/kamon-core/src/test/scala/kamon/instrumentation/scala/FutureInstrumentationSpec.scala @@ -13,16 +13,17 @@ * See the License for the specific language governing permissions and * limitations under the License. * ========================================================== */ -package kamon.trace.instrumentation +package kamon.instrumentation.scala -import scala.concurrent.{ ExecutionContext, Future } -import org.scalatest.{ Matchers, OptionValues, WordSpecLike } -import org.scalatest.concurrent.{ ScalaFutures, PatienceConfiguration } -import kamon.trace.TraceRecorder -import akka.testkit.TestKit import akka.actor.ActorSystem +import akka.testkit.TestKit +import kamon.trace.TraceRecorder +import org.scalatest.concurrent.{ PatienceConfiguration, ScalaFutures } +import org.scalatest.{ Matchers, OptionValues, WordSpecLike } + +import scala.concurrent.Future -class FutureTracingSpec extends TestKit(ActorSystem("actor-message-passing-tracing-spec")) with WordSpecLike with Matchers +class FutureInstrumentationSpec extends TestKit(ActorSystem("future-instrumentation-spec")) with WordSpecLike with Matchers with ScalaFutures with PatienceConfiguration with OptionValues { implicit val execContext = system.dispatcher diff --git a/kamon-core/src/test/scala/kamon/metric/ActorMetricsSpec.scala b/kamon-core/src/test/scala/kamon/metric/ActorMetricsSpec.scala new file mode 100644 index 00000000..0029fd7c --- /dev/null +++ b/kamon-core/src/test/scala/kamon/metric/ActorMetricsSpec.scala @@ -0,0 +1,204 @@ +/* ========================================================================================= + * Copyright © 2013 the kamon project <http://kamon.io/> + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the + * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions + * and limitations under the License. + * ========================================================================================= + */ + +package kamon.metric + +import java.nio.LongBuffer + +import akka.instrumentation.ActorCellMetrics +import kamon.metric.ActorMetricsTestActor._ +import org.scalatest.{ WordSpecLike, Matchers } +import akka.testkit.{ ImplicitSender, TestProbe, TestKitBase } +import akka.actor._ +import com.typesafe.config.ConfigFactory +import scala.concurrent.duration._ +import kamon.metric.ActorMetrics.{ ActorMetricsRecorder, ActorMetricSnapshot } + +class ActorMetricsSpec extends TestKitBase with WordSpecLike with Matchers { + implicit def self = testActor + implicit lazy val system: ActorSystem = ActorSystem("actor-metrics-spec", ConfigFactory.parseString( + """ + |kamon.metrics { + | tick-interval = 1 hour + | default-collection-context-buffer-size = 10 + | + | filters = [ + | { + | actor { + | includes = [ "user/tracked-*", "user/measuring-*", "user/clean-after-collect" ] + | excludes = [ "user/tracked-explicitly-excluded"] + | } + | } + | ] + | precision { + | default-histogram-precision { + | highest-trackable-value = 3600000000000 + | significant-value-digits = 2 + | } + | + | default-min-max-counter-precision { + | refresh-interval = 1 second + | highest-trackable-value = 999999999 + | significant-value-digits = 2 + | } + | } + |} + """.stripMargin)) + + "the Kamon actor metrics" should { + "respect the configured include and exclude filters" in new ActorMetricsFixtures { + val trackedActor = createTestActor("tracked-actor") + actorMetricsRecorderOf(trackedActor) should not be empty + + val nonTrackedActor = createTestActor("non-tracked-actor") + actorMetricsRecorderOf(nonTrackedActor) shouldBe empty + + val trackedButExplicitlyExcluded = createTestActor("tracked-explicitly-excluded") + actorMetricsRecorderOf(trackedButExplicitlyExcluded) shouldBe empty + } + + "reset all recording instruments after taking a snapshot" in new ActorMetricsFixtures { + val trackedActor = createTestActor("clean-after-collect") + val trackedActorMetrics = actorMetricsRecorderOf(trackedActor).get + for (i ← 1 to 100) { + trackedActor ! Discard + } + trackedActor ! Fail + trackedActor ! TrackTimings(sleep = Some(1 second)) + expectMsgType[TrackedTimings] + + val firstSnapshot = takeSnapshotOf(trackedActorMetrics) + firstSnapshot.errors.count should be(1L) + firstSnapshot.mailboxSize.numberOfMeasurements should be > 0L + firstSnapshot.processingTime.numberOfMeasurements should be(103L) // 102 examples + Initialize message + firstSnapshot.timeInMailbox.numberOfMeasurements should be(103L) // 102 examples + Initialize message + + val secondSnapshot = takeSnapshotOf(trackedActorMetrics) // Ensure that the recorders are clean + secondSnapshot.errors.count should be(0L) + secondSnapshot.mailboxSize.numberOfMeasurements should be <= 3L + secondSnapshot.processingTime.numberOfMeasurements should be(0L) // 102 examples + Initialize message + secondSnapshot.timeInMailbox.numberOfMeasurements should be(0L) // 102 examples + Initialize message + } + + "record the processing-time of the receive function" in new ActorMetricsFixtures { + val trackedActor = createTestActor("measuring-processing-time") + val trackedActorMetrics = actorMetricsRecorderOf(trackedActor).get + takeSnapshotOf(trackedActorMetrics) // Ensure that the recorders are clean + + trackedActor ! TrackTimings(sleep = Some(1 second)) + val timings = expectMsgType[TrackedTimings] + val snapshot = takeSnapshotOf(trackedActorMetrics) + + snapshot.processingTime.numberOfMeasurements should be(1L) + snapshot.processingTime.recordsIterator.next().count should be(1L) + snapshot.processingTime.recordsIterator.next().level should be(timings.approximateProcessingTime +- 10.millis.toNanos) + } + + "record the number of errors" in new ActorMetricsFixtures { + val trackedActor = createTestActor("measuring-errors") + val trackedActorMetrics = actorMetricsRecorderOf(trackedActor).get + takeSnapshotOf(trackedActorMetrics) // Ensure that the recorders are clean + + for (i ← 1 to 10) { trackedActor ! Fail } + trackedActor ! Ping + expectMsg(Pong) + val snapshot = takeSnapshotOf(trackedActorMetrics) + + snapshot.errors.count should be(10) + } + + "record the mailbox-size" in new ActorMetricsFixtures { + val trackedActor = createTestActor("measuring-mailbox-size") + val trackedActorMetrics = actorMetricsRecorderOf(trackedActor).get + takeSnapshotOf(trackedActorMetrics) // Ensure that the recorders are clean + + trackedActor ! TrackTimings(sleep = Some(1 second)) + for (i ← 1 to 10) { + trackedActor ! Discard + } + trackedActor ! Ping + + val timings = expectMsgType[TrackedTimings] + expectMsg(Pong) + val snapshot = takeSnapshotOf(trackedActorMetrics) + + snapshot.mailboxSize.min should be(0L) + snapshot.mailboxSize.max should be(11L +- 1L) + } + + "record the time-in-mailbox" in new ActorMetricsFixtures { + val trackedActor = createTestActor("measuring-time-in-mailbox") + val trackedActorMetrics = actorMetricsRecorderOf(trackedActor).get + takeSnapshotOf(trackedActorMetrics) // Ensure that the recorders are clean + + trackedActor ! TrackTimings(sleep = Some(1 second)) + val timings = expectMsgType[TrackedTimings] + val snapshot = takeSnapshotOf(trackedActorMetrics) + + snapshot.timeInMailbox.numberOfMeasurements should be(1L) + snapshot.timeInMailbox.recordsIterator.next().count should be(1L) + snapshot.timeInMailbox.recordsIterator.next().level should be(timings.approximateTimeInMailbox +- 10.millis.toNanos) + } + } + + trait ActorMetricsFixtures { + val collectionContext = new CollectionContext { + val buffer: LongBuffer = LongBuffer.allocate(10000) + } + + def actorMetricsRecorderOf(ref: ActorRef): Option[ActorMetricsRecorder] = { + val initialisationListener = TestProbe() + ref.tell(Ping, initialisationListener.ref) + initialisationListener.expectMsg(Pong) + + val underlyingCellField = ref.getClass.getDeclaredMethod("underlying") + val cell = underlyingCellField.invoke(ref).asInstanceOf[ActorCellMetrics] + + cell.actorMetricsRecorder + } + + def createTestActor(name: String): ActorRef = system.actorOf(Props[ActorMetricsTestActor], name) + + def takeSnapshotOf(amr: ActorMetricsRecorder): ActorMetricSnapshot = amr.collect(collectionContext) + } +} + +class ActorMetricsTestActor extends Actor { + def receive = { + case Discard ⇒ + case Fail ⇒ 1 / 0 + case Ping ⇒ sender ! Pong + case TrackTimings(sendTimestamp, sleep) ⇒ { + val dequeueTimestamp = System.nanoTime() + sleep.map(s ⇒ Thread.sleep(s.toMillis)) + val afterReceiveTimestamp = System.nanoTime() + + sender ! TrackedTimings(sendTimestamp, dequeueTimestamp, afterReceiveTimestamp) + } + } +} + +object ActorMetricsTestActor { + case object Ping + case object Pong + case object Fail + case object Discard + + case class TrackTimings(sendTimestamp: Long = System.nanoTime(), sleep: Option[Duration] = None) + case class TrackedTimings(sendTimestamp: Long, dequeueTimestamp: Long, afterReceiveTimestamp: Long) { + def approximateTimeInMailbox: Long = dequeueTimestamp - sendTimestamp + def approximateProcessingTime: Long = afterReceiveTimestamp - dequeueTimestamp + } +} diff --git a/kamon-core/src/test/scala/kamon/metrics/DispatcherMetricsSpec.scala b/kamon-core/src/test/scala/kamon/metric/DispatcherMetricsSpec.scala index 2a9cb6b4..ae324b73 100644 --- a/kamon-core/src/test/scala/kamon/metrics/DispatcherMetricsSpec.scala +++ b/kamon-core/src/test/scala/kamon/metric/DispatcherMetricsSpec.scala @@ -13,7 +13,7 @@ * ========================================================================================= */ -package kamon.metrics +package kamon.metric import org.scalatest.{ WordSpecLike, Matchers } import akka.testkit.{ TestProbe, TestKitBase } @@ -21,13 +21,16 @@ import akka.actor.{ ActorRef, Props, ActorSystem } import com.typesafe.config.ConfigFactory import scala.concurrent.duration._ import kamon.Kamon -import kamon.metrics.Subscriptions.TickMetricSnapshot -import kamon.metrics.DispatcherMetrics.DispatcherMetricSnapshot +import kamon.metric.Subscriptions.TickMetricSnapshot +import kamon.metric.DispatcherMetrics.DispatcherMetricSnapshot class DispatcherMetricsSpec extends TestKitBase with WordSpecLike with Matchers { implicit lazy val system: ActorSystem = ActorSystem("dispatcher-metrics-spec", ConfigFactory.parseString( """ |kamon.metrics { + | tick-interval = 1 second + | default-collection-context-buffer-size = 10 + | | filters = [ | { | dispatcher { @@ -52,8 +55,8 @@ class DispatcherMetricsSpec extends TestKitBase with WordSpecLike with Matchers "the Kamon dispatcher metrics" should { "respect the configured include and exclude filters" in { - system.actorOf(Props[DelayableActor].withDispatcher("tracked-dispatcher"), "actor-with-tracked-dispatcher") - system.actorOf(Props[DelayableActor].withDispatcher("dispatcher-explicitly-excluded"), "actor-with-excluded-dispatcher") + system.actorOf(Props[ActorMetricsTestActor].withDispatcher("tracked-dispatcher"), "actor-with-tracked-dispatcher") + system.actorOf(Props[ActorMetricsTestActor].withDispatcher("dispatcher-explicitly-excluded"), "actor-with-excluded-dispatcher") Kamon(Metrics).subscribe(DispatcherMetrics, "*", testActor, permanently = true) expectMsgType[TickMetricSnapshot] @@ -69,7 +72,7 @@ class DispatcherMetricsSpec extends TestKitBase with WordSpecLike with Matchers val (delayable, metricsListener) = delayableActor("worker-actor", "tracked-dispatcher") for (_ ← 1 to 100) { - delayable ! Discard + //delayable ! Discard } val dispatcherMetrics = expectDispatcherMetrics("tracked-dispatcher", metricsListener, 3 seconds) @@ -92,7 +95,7 @@ class DispatcherMetricsSpec extends TestKitBase with WordSpecLike with Matchers trait DelayableActorFixture { def delayableActor(name: String, dispatcher: String): (ActorRef, TestProbe) = { - val actor = system.actorOf(Props[DelayableActor].withDispatcher(dispatcher), name) + val actor = system.actorOf(Props[ActorMetricsTestActor].withDispatcher(dispatcher), name) val metricsListener = TestProbe() Kamon(Metrics).subscribe(DispatcherMetrics, "*", metricsListener.ref, permanently = true) diff --git a/kamon-core/src/test/scala/kamon/metric/SubscriptionsProtocolSpec.scala b/kamon-core/src/test/scala/kamon/metric/SubscriptionsProtocolSpec.scala new file mode 100644 index 00000000..60923a2b --- /dev/null +++ b/kamon-core/src/test/scala/kamon/metric/SubscriptionsProtocolSpec.scala @@ -0,0 +1,134 @@ +package kamon.metric + +import akka.actor._ +import akka.testkit.{ TestProbe, ImplicitSender, TestKitBase } +import com.typesafe.config.ConfigFactory +import kamon.Kamon +import kamon.metric.Subscriptions.TickMetricSnapshot +import org.scalatest.{ Matchers, WordSpecLike } +import scala.concurrent.duration._ + +class SubscriptionsProtocolSpec extends TestKitBase with WordSpecLike with Matchers { + implicit def self = testActor + implicit lazy val system: ActorSystem = ActorSystem("subscriptions-protocol-spec", ConfigFactory.parseString( + """ + |kamon.metrics { + | tick-interval = 1 hour + |} + """.stripMargin)) + + val metricsExtension = Kamon(Metrics)(system) + import metricsExtension.{ register, subscribe, unsubscribe } + + "the Subscriptions messaging protocol" should { + "allow subscribing for a single tick" in { + val subscriber = TestProbe() + register(TraceMetrics("one-shot"), TraceMetrics.Factory) + subscribe(TraceMetrics, "one-shot", subscriber.ref, permanently = false) + + metricsExtension.subscriptions ! Subscriptions.FlushMetrics + val tickSnapshot = subscriber.expectMsgType[TickMetricSnapshot] + + tickSnapshot.metrics.size should be(1) + tickSnapshot.metrics.keys should contain(TraceMetrics("one-shot")) + + metricsExtension.subscriptions ! Subscriptions.FlushMetrics + subscriber.expectNoMsg(1 second) + } + + "allow subscribing permanently to a metric" in { + val subscriber = TestProbe() + register(TraceMetrics("permanent"), TraceMetrics.Factory) + subscribe(TraceMetrics, "permanent", subscriber.ref, permanently = true) + + for (repetition ← 1 to 5) { + metricsExtension.subscriptions ! Subscriptions.FlushMetrics + val tickSnapshot = subscriber.expectMsgType[TickMetricSnapshot] + + tickSnapshot.metrics.size should be(1) + tickSnapshot.metrics.keys should contain(TraceMetrics("permanent")) + subscriber.expectNoMsg(1 second) + } + } + + "allow subscribing to metrics matching a glob pattern" in { + val subscriber = TestProbe() + register(TraceMetrics("include-one"), TraceMetrics.Factory) + register(TraceMetrics("exclude-two"), TraceMetrics.Factory) + register(TraceMetrics("include-three"), TraceMetrics.Factory) + subscribe(TraceMetrics, "include-*", subscriber.ref, permanently = true) + + for (repetition ← 1 to 5) { + metricsExtension.subscriptions ! Subscriptions.FlushMetrics + val tickSnapshot = subscriber.expectMsgType[TickMetricSnapshot] + + tickSnapshot.metrics.size should be(2) + tickSnapshot.metrics.keys should contain(TraceMetrics("include-one")) + tickSnapshot.metrics.keys should contain(TraceMetrics("include-three")) + subscriber.expectNoMsg(1 second) + } + } + + "send a single TickMetricSnapshot to each subscriber, even if subscribed multiple times" in { + val subscriber = TestProbe() + register(TraceMetrics("include-one"), TraceMetrics.Factory) + register(TraceMetrics("exclude-two"), TraceMetrics.Factory) + register(TraceMetrics("include-three"), TraceMetrics.Factory) + subscribe(TraceMetrics, "include-one", subscriber.ref, permanently = true) + subscribe(TraceMetrics, "include-three", subscriber.ref, permanently = true) + + for (repetition ← 1 to 5) { + metricsExtension.subscriptions ! Subscriptions.FlushMetrics + val tickSnapshot = subscriber.expectMsgType[TickMetricSnapshot] + + tickSnapshot.metrics.size should be(2) + tickSnapshot.metrics.keys should contain(TraceMetrics("include-one")) + tickSnapshot.metrics.keys should contain(TraceMetrics("include-three")) + } + } + + "allow un-subscribing a subscriber" in { + val subscriber = TestProbe() + register(TraceMetrics("one-shot"), TraceMetrics.Factory) + subscribe(TraceMetrics, "one-shot", subscriber.ref, permanently = true) + + metricsExtension.subscriptions ! Subscriptions.FlushMetrics + val tickSnapshot = subscriber.expectMsgType[TickMetricSnapshot] + tickSnapshot.metrics.size should be(1) + tickSnapshot.metrics.keys should contain(TraceMetrics("one-shot")) + + unsubscribe(subscriber.ref) + + metricsExtension.subscriptions ! Subscriptions.FlushMetrics + subscriber.expectNoMsg(1 second) + } + + "watch all subscribers and un-subscribe them if they die" in { + val subscriber = TestProbe() + val forwarderSubscriber = system.actorOf(Props(new ForwarderSubscriber(subscriber.ref))) + watch(forwarderSubscriber) + register(TraceMetrics("one-shot"), TraceMetrics.Factory) + subscribe(TraceMetrics, "one-shot", forwarderSubscriber, permanently = true) + + metricsExtension.subscriptions ! Subscriptions.FlushMetrics + val tickSnapshot = subscriber.expectMsgType[TickMetricSnapshot] + tickSnapshot.metrics.size should be(1) + tickSnapshot.metrics.keys should contain(TraceMetrics("one-shot")) + + forwarderSubscriber ! PoisonPill + expectTerminated(forwarderSubscriber) + + metricsExtension.subscriptions ! Subscriptions.FlushMetrics + metricsExtension.subscriptions ! Subscriptions.FlushMetrics + metricsExtension.subscriptions ! Subscriptions.FlushMetrics + metricsExtension.subscriptions ! Subscriptions.FlushMetrics + subscriber.expectNoMsg(2 seconds) + } + } +} + +class ForwarderSubscriber(target: ActorRef) extends Actor { + def receive = { + case anything ⇒ target.forward(anything) + } +} diff --git a/kamon-core/src/test/scala/kamon/metrics/TickMetricSnapshotBufferSpec.scala b/kamon-core/src/test/scala/kamon/metric/TickMetricSnapshotBufferSpec.scala index d0a0c707..27eb64e2 100644 --- a/kamon-core/src/test/scala/kamon/metrics/TickMetricSnapshotBufferSpec.scala +++ b/kamon-core/src/test/scala/kamon/metric/TickMetricSnapshotBufferSpec.scala @@ -14,16 +14,36 @@ * ========================================================================================= */ -package kamon.metrics +package kamon.metric +import com.typesafe.config.ConfigFactory +import kamon.Kamon +import kamon.metric.instrument.Histogram +import kamon.metric.instrument.Histogram.MutableRecord import org.scalatest.{ Matchers, WordSpecLike } -import akka.testkit.TestKit +import akka.testkit.{ ImplicitSender, TestKitBase } import akka.actor.ActorSystem import scala.concurrent.duration._ -import kamon.metrics.Subscriptions.TickMetricSnapshot -import kamon.metrics.MetricSnapshot.Measurement - -class TickMetricSnapshotBufferSpec extends TestKit(ActorSystem("tick-metric-snapshot-buffer")) with WordSpecLike with Matchers { +import kamon.metric.Subscriptions.TickMetricSnapshot + +class TickMetricSnapshotBufferSpec extends TestKitBase with WordSpecLike with Matchers { + implicit def self = testActor + implicit lazy val system: ActorSystem = ActorSystem("trace-metrics-spec", ConfigFactory.parseString( + """ + |kamon.metrics { + | tick-interval = 1 hour + | default-collection-context-buffer-size = 10 + | + | filters = [ + | { + | trace { + | includes = [ "*" ] + | excludes = [ "non-tracked-trace"] + | } + | } + | ] + |} + """.stripMargin)) "the TickMetricSnapshotBuffer" should { "merge TickMetricSnapshots received until the flush timeout is reached and fix the from/to fields" in new SnapshotFixtures { @@ -55,27 +75,38 @@ class TickMetricSnapshotBufferSpec extends TestKit(ActorSystem("tick-metric-snap mergedSnapshot.to should equal(4000) mergedSnapshot.metrics should not be ('empty) - val testMetricSnapshot = mergedSnapshot.metrics(CustomMetric("test-metric")).metrics(CustomMetric.RecordedValues) - testMetricSnapshot.min should equal(1) - testMetricSnapshot.max should equal(10) - testMetricSnapshot.numberOfMeasurements should equal(35) - testMetricSnapshot.measurements should contain allOf (Measurement(1, 10), Measurement(4, 9), Measurement(10, 16)) + val testMetricSnapshot = mergedSnapshot.metrics(testTraceIdentity).metrics(TraceMetrics.ElapsedTime).asInstanceOf[Histogram.Snapshot] + testMetricSnapshot.min should equal(10) + testMetricSnapshot.max should equal(300) + testMetricSnapshot.numberOfMeasurements should equal(6) + testMetricSnapshot.recordsIterator.toStream should contain allOf ( + MutableRecord(10, 3), + MutableRecord(20, 1), + MutableRecord(30, 1), + MutableRecord(300, 1)) } } trait SnapshotFixtures { + val collectionContext = Kamon(Metrics).buildDefaultCollectionContext + val testTraceIdentity = TraceMetrics("buffer-spec-test-trace") + val traceRecorder = Kamon(Metrics).register(testTraceIdentity, TraceMetrics.Factory).get + val firstEmpty = TickMetricSnapshot(1000, 2000, Map.empty) val secondEmpty = TickMetricSnapshot(2000, 3000, Map.empty) val thirdEmpty = TickMetricSnapshot(3000, 4000, Map.empty) - val firstNonEmpty = TickMetricSnapshot(1000, 2000, - Map((CustomMetric("test-metric") -> SimpleGroupSnapshot(Map(CustomMetric.RecordedValues -> MetricSnapshot(InstrumentTypes.Histogram, 20, Scale.Unit, Vector(Measurement(1, 10), Measurement(10, 10)))))))) - - val secondNonEmpty = TickMetricSnapshot(1000, 2000, - Map((CustomMetric("test-metric") -> SimpleGroupSnapshot(Map(CustomMetric.RecordedValues -> MetricSnapshot(InstrumentTypes.Histogram, 15, Scale.Unit, Vector(Measurement(4, 9), Measurement(10, 6)))))))) - + traceRecorder.elapsedTime.record(10L) + traceRecorder.elapsedTime.record(20L) + traceRecorder.elapsedTime.record(30L) + val firstNonEmpty = TickMetricSnapshot(1000, 2000, Map( + (testTraceIdentity -> traceRecorder.collect(collectionContext)))) + + traceRecorder.elapsedTime.record(10L) + traceRecorder.elapsedTime.record(10L) + traceRecorder.elapsedTime.record(300L) + val secondNonEmpty = TickMetricSnapshot(1000, 2000, Map( + (testTraceIdentity -> traceRecorder.collect(collectionContext)))) } - - case class SimpleGroupSnapshot(metrics: Map[MetricIdentity, MetricSnapshotLike]) extends MetricGroupSnapshot } diff --git a/kamon-core/src/test/scala/kamon/metric/TraceMetricsSpec.scala b/kamon-core/src/test/scala/kamon/metric/TraceMetricsSpec.scala new file mode 100644 index 00000000..23977971 --- /dev/null +++ b/kamon-core/src/test/scala/kamon/metric/TraceMetricsSpec.scala @@ -0,0 +1,96 @@ +package kamon.metric + +import akka.actor.ActorSystem +import akka.testkit.{ ImplicitSender, TestKitBase } +import com.typesafe.config.ConfigFactory +import kamon.Kamon +import kamon.metric.TraceMetrics.TraceMetricsSnapshot +import kamon.trace.TraceContext.SegmentIdentity +import kamon.trace.TraceRecorder +import org.scalatest.{ Matchers, WordSpecLike } + +class TraceMetricsSpec extends TestKitBase with WordSpecLike with Matchers { + implicit def self = testActor + implicit lazy val system: ActorSystem = ActorSystem("trace-metrics-spec", ConfigFactory.parseString( + """ + |kamon.metrics { + | tick-interval = 1 hour + | default-collection-context-buffer-size = 10 + | + | filters = [ + | { + | trace { + | includes = [ "*" ] + | excludes = [ "non-tracked-trace"] + | } + | } + | ] + | precision { + | default-histogram-precision { + | highest-trackable-value = 3600000000000 + | significant-value-digits = 2 + | } + | + | default-min-max-counter-precision { + | refresh-interval = 1 second + | highest-trackable-value = 999999999 + | significant-value-digits = 2 + | } + | } + |} + """.stripMargin)) + + "the TraceMetrics" should { + "record the elapsed time between a trace creation and finish" in { + for (repetitions ← 1 to 10) { + TraceRecorder.withNewTraceContext("record-elapsed-time") { + TraceRecorder.finish() + } + } + + val snapshot = takeSnapshotOf("record-elapsed-time") + snapshot.elapsedTime.numberOfMeasurements should be(10) + snapshot.segments shouldBe empty + } + + "record the elapsed time for segments that occur inside a given trace" in { + TraceRecorder.withNewTraceContext("trace-with-segments") { + val segmentHandle = TraceRecorder.startSegment(TraceMetricsTestSegment("test-segment")) + segmentHandle.get.finish() + TraceRecorder.finish() + } + + val snapshot = takeSnapshotOf("trace-with-segments") + snapshot.elapsedTime.numberOfMeasurements should be(1) + snapshot.segments.size should be(1) + snapshot.segments(TraceMetricsTestSegment("test-segment")).numberOfMeasurements should be(1) + } + + "record the elapsed time for segments that finish after their correspondent trace has finished" in { + val segmentHandle = TraceRecorder.withNewTraceContext("closing-segment-after-trace") { + val sh = TraceRecorder.startSegment(TraceMetricsTestSegment("test-segment")) + TraceRecorder.finish() + sh + } + + val beforeFinishSegmentSnapshot = takeSnapshotOf("closing-segment-after-trace") + beforeFinishSegmentSnapshot.elapsedTime.numberOfMeasurements should be(1) + beforeFinishSegmentSnapshot.segments.size should be(0) + + segmentHandle.get.finish() + + val afterFinishSegmentSnapshot = takeSnapshotOf("closing-segment-after-trace") + afterFinishSegmentSnapshot.elapsedTime.numberOfMeasurements should be(0) + afterFinishSegmentSnapshot.segments.size should be(1) + afterFinishSegmentSnapshot.segments(TraceMetricsTestSegment("test-segment")).numberOfMeasurements should be(1) + } + } + + case class TraceMetricsTestSegment(name: String) extends SegmentIdentity + + def takeSnapshotOf(traceName: String): TraceMetricsSnapshot = { + val recorder = Kamon(Metrics).register(TraceMetrics(traceName), TraceMetrics.Factory) + val collectionContext = Kamon(Metrics).buildDefaultCollectionContext + recorder.get.collect(collectionContext) + } +} diff --git a/kamon-core/src/test/scala/kamon/metric/UserMetricsSpec.scala b/kamon-core/src/test/scala/kamon/metric/UserMetricsSpec.scala new file mode 100644 index 00000000..66ee49ea --- /dev/null +++ b/kamon-core/src/test/scala/kamon/metric/UserMetricsSpec.scala @@ -0,0 +1,304 @@ +package kamon.metric + +import akka.actor.ActorSystem +import akka.testkit.{ ImplicitSender, TestKitBase } +import com.typesafe.config.ConfigFactory +import kamon.Kamon +import kamon.metric.UserMetrics.{ UserGauge, UserMinMaxCounter, UserCounter, UserHistogram } +import kamon.metric.instrument.Histogram +import kamon.metric.instrument.Histogram.MutableRecord +import org.scalatest.{ Matchers, WordSpecLike } +import scala.concurrent.duration._ + +class UserMetricsSpec extends TestKitBase with WordSpecLike with Matchers { + implicit def self = testActor + implicit lazy val system: ActorSystem = ActorSystem("actor-metrics-spec", ConfigFactory.parseString( + """ + |kamon.metrics { + | tick-interval = 1 hour + | default-collection-context-buffer-size = 10 + | + | precision { + | default-histogram-precision { + | highest-trackable-value = 10000 + | significant-value-digits = 2 + | } + | + | default-min-max-counter-precision { + | refresh-interval = 1 hour + | highest-trackable-value = 1000 + | significant-value-digits = 2 + | } + | + | default-gauge-precision { + | refresh-interval = 1 hour + | highest-trackable-value = 999999999 + | significant-value-digits = 2 + | } + | } + |} + """.stripMargin)) + + "the UserMetrics extension" should { + "allow registering a fully configured Histogram and get the same Histogram if registering again" in { + val histogramA = Kamon(UserMetrics).registerHistogram("histogram-with-settings", Histogram.Precision.Normal, 10000L) + val histogramB = Kamon(UserMetrics).registerHistogram("histogram-with-settings", Histogram.Precision.Normal, 10000L) + + histogramA shouldBe theSameInstanceAs(histogramB) + } + + "return the original Histogram when registering a fully configured Histogram for second time but with different settings" in { + val histogramA = Kamon(UserMetrics).registerHistogram("histogram-with-settings", Histogram.Precision.Normal, 10000L) + val histogramB = Kamon(UserMetrics).registerHistogram("histogram-with-settings", Histogram.Precision.Fine, 50000L) + + histogramA shouldBe theSameInstanceAs(histogramB) + } + + "allow registering a Histogram that takes the default configuration from the kamon.metrics.precision settings" in { + Kamon(UserMetrics).registerHistogram("histogram-with-default-configuration") + } + + "allow registering a Counter and get the same Counter if registering again" in { + val counterA = Kamon(UserMetrics).registerCounter("counter") + val counterB = Kamon(UserMetrics).registerCounter("counter") + + counterA shouldBe theSameInstanceAs(counterB) + } + + "allow registering a fully configured MinMaxCounter and get the same MinMaxCounter if registering again" in { + val minMaxCounterA = Kamon(UserMetrics).registerMinMaxCounter("min-max-counter-with-settings", Histogram.Precision.Normal, 1000L, 1 second) + val minMaxCounterB = Kamon(UserMetrics).registerMinMaxCounter("min-max-counter-with-settings", Histogram.Precision.Normal, 1000L, 1 second) + + minMaxCounterA shouldBe theSameInstanceAs(minMaxCounterB) + } + + "return the original MinMaxCounter when registering a fully configured MinMaxCounter for second time but with different settings" in { + val minMaxCounterA = Kamon(UserMetrics).registerMinMaxCounter("min-max-counter-with-settings", Histogram.Precision.Normal, 1000L, 1 second) + val minMaxCounterB = Kamon(UserMetrics).registerMinMaxCounter("min-max-counter-with-settings", Histogram.Precision.Fine, 5000L, 1 second) + + minMaxCounterA shouldBe theSameInstanceAs(minMaxCounterB) + } + + "allow registering a MinMaxCounter that takes the default configuration from the kamon.metrics.precision settings" in { + Kamon(UserMetrics).registerMinMaxCounter("min-max-counter-with-default-configuration") + } + + "allow registering a fully configured Gauge and get the same Gauge if registering again" in { + val gaugeA = Kamon(UserMetrics).registerGauge("gauge-with-settings", Histogram.Precision.Normal, 1000L, 1 second) { + () ⇒ 1L + } + + val gaugeB = Kamon(UserMetrics).registerGauge("gauge-with-settings", Histogram.Precision.Normal, 1000L, 1 second) { + () ⇒ 1L + } + + gaugeA shouldBe theSameInstanceAs(gaugeB) + } + + "return the original Gauge when registering a fully configured Gauge for second time but with different settings" in { + val gaugeA = Kamon(UserMetrics).registerGauge("gauge-with-settings", Histogram.Precision.Normal, 1000L, 1 second) { + () ⇒ 1L + } + + val gaugeB = Kamon(UserMetrics).registerGauge("gauge-with-settings", Histogram.Precision.Fine, 5000L, 1 second) { + () ⇒ 1L + } + + gaugeA shouldBe theSameInstanceAs(gaugeB) + } + + "allow registering a Gauge that takes the default configuration from the kamon.metrics.precision settings" in { + Kamon(UserMetrics).registerGauge("gauge-with-default-configuration") { + () ⇒ 2L + } + } + + "allow unregistering metrics from the extension" in { + val userMetricsRecorder = Kamon(Metrics).register(UserMetrics, UserMetrics.Factory).get + val counter = Kamon(UserMetrics).registerCounter("counter-for-remove") + val histogram = Kamon(UserMetrics).registerHistogram("histogram-for-remove") + val minMaxCounter = Kamon(UserMetrics).registerMinMaxCounter("min-max-counter-for-remove") + val gauge = Kamon(UserMetrics).registerGauge("gauge-for-remove") { () ⇒ 2L } + + userMetricsRecorder.counters.keys should contain("counter-for-remove") + userMetricsRecorder.histograms.keys should contain("histogram-for-remove") + userMetricsRecorder.minMaxCounters.keys should contain("min-max-counter-for-remove") + userMetricsRecorder.gauges.keys should contain("gauge-for-remove") + + Kamon(UserMetrics).removeCounter("counter-for-remove") + Kamon(UserMetrics).removeHistogram("histogram-for-remove") + Kamon(UserMetrics).removeMinMaxCounter("min-max-counter-for-remove") + Kamon(UserMetrics).removeGauge("gauge-for-remove") + + userMetricsRecorder.counters.keys should not contain ("counter-for-remove") + userMetricsRecorder.histograms.keys should not contain ("histogram-for-remove") + userMetricsRecorder.minMaxCounters.keys should not contain ("min-max-counter-for-remove") + userMetricsRecorder.gauges.keys should not contain ("gauge-for-remove") + } + + "generate a snapshot containing all the registered user metrics and reset all instruments" in { + val context = Kamon(Metrics).buildDefaultCollectionContext + val userMetricsRecorder = Kamon(Metrics).register(UserMetrics, UserMetrics.Factory).get + + val histogramWithSettings = Kamon(UserMetrics).registerHistogram("histogram-with-settings", Histogram.Precision.Normal, 10000L) + val histogramWithDefaultConfiguration = Kamon(UserMetrics).registerHistogram("histogram-with-default-configuration") + val counter = Kamon(UserMetrics).registerCounter("counter") + val minMaxCounterWithSettings = Kamon(UserMetrics).registerMinMaxCounter("min-max-counter-with-settings", Histogram.Precision.Normal, 1000L, 1 second) + val gauge = Kamon(UserMetrics).registerGauge("gauge-with-default-configuration") { () ⇒ 2L } + + // lets put some values on those metrics + histogramWithSettings.record(10) + histogramWithSettings.record(20, 100) + histogramWithDefaultConfiguration.record(40) + + counter.increment() + counter.increment(16) + + minMaxCounterWithSettings.increment(43) + minMaxCounterWithSettings.decrement() + + gauge.record(15) + + val firstSnapshot = userMetricsRecorder.collect(context) + + firstSnapshot.histograms.size should be(2) + firstSnapshot.histograms.keys should contain allOf ( + UserHistogram("histogram-with-settings"), + UserHistogram("histogram-with-default-configuration")) + + firstSnapshot.histograms(UserHistogram("histogram-with-settings")).min shouldBe (10) + firstSnapshot.histograms(UserHistogram("histogram-with-settings")).max shouldBe (20) + firstSnapshot.histograms(UserHistogram("histogram-with-settings")).numberOfMeasurements should be(101) + firstSnapshot.histograms(UserHistogram("histogram-with-settings")).recordsIterator.toStream should contain allOf ( + MutableRecord(10, 1), + MutableRecord(20, 100)) + + firstSnapshot.histograms(UserHistogram("histogram-with-default-configuration")).min shouldBe (40) + firstSnapshot.histograms(UserHistogram("histogram-with-default-configuration")).max shouldBe (40) + firstSnapshot.histograms(UserHistogram("histogram-with-default-configuration")).numberOfMeasurements should be(1) + firstSnapshot.histograms(UserHistogram("histogram-with-default-configuration")).recordsIterator.toStream should contain only ( + MutableRecord(40, 1)) + + firstSnapshot.counters(UserCounter("counter")).count should be(17) + + firstSnapshot.minMaxCounters(UserMinMaxCounter("min-max-counter-with-settings")).min shouldBe (0) + firstSnapshot.minMaxCounters(UserMinMaxCounter("min-max-counter-with-settings")).max shouldBe (43) + firstSnapshot.minMaxCounters(UserMinMaxCounter("min-max-counter-with-settings")).numberOfMeasurements should be(3) + firstSnapshot.minMaxCounters(UserMinMaxCounter("min-max-counter-with-settings")).recordsIterator.toStream should contain allOf ( + MutableRecord(0, 1), // min + MutableRecord(42, 1), // current + MutableRecord(43, 1)) // max + + firstSnapshot.minMaxCounters(UserMinMaxCounter("min-max-counter-with-default-configuration")).min shouldBe (0) + firstSnapshot.minMaxCounters(UserMinMaxCounter("min-max-counter-with-default-configuration")).max shouldBe (0) + firstSnapshot.minMaxCounters(UserMinMaxCounter("min-max-counter-with-default-configuration")).numberOfMeasurements should be(3) + firstSnapshot.minMaxCounters(UserMinMaxCounter("min-max-counter-with-default-configuration")).recordsIterator.toStream should contain only ( + MutableRecord(0, 3)) // min, max and current + + firstSnapshot.gauges(UserGauge("gauge-with-default-configuration")).min shouldBe (15) + firstSnapshot.gauges(UserGauge("gauge-with-default-configuration")).max shouldBe (15) + firstSnapshot.gauges(UserGauge("gauge-with-default-configuration")).numberOfMeasurements should be(1) + firstSnapshot.gauges(UserGauge("gauge-with-default-configuration")).recordsIterator.toStream should contain only ( + MutableRecord(15, 1)) // only the manually recorded value + + val secondSnapshot = userMetricsRecorder.collect(context) + + secondSnapshot.histograms.size should be(2) + secondSnapshot.histograms.keys should contain allOf ( + UserHistogram("histogram-with-settings"), + UserHistogram("histogram-with-default-configuration")) + + secondSnapshot.histograms(UserHistogram("histogram-with-settings")).min shouldBe (0) + secondSnapshot.histograms(UserHistogram("histogram-with-settings")).max shouldBe (0) + secondSnapshot.histograms(UserHistogram("histogram-with-settings")).numberOfMeasurements should be(0) + secondSnapshot.histograms(UserHistogram("histogram-with-settings")).recordsIterator.toStream shouldBe empty + + secondSnapshot.histograms(UserHistogram("histogram-with-default-configuration")).min shouldBe (0) + secondSnapshot.histograms(UserHistogram("histogram-with-default-configuration")).max shouldBe (0) + secondSnapshot.histograms(UserHistogram("histogram-with-default-configuration")).numberOfMeasurements should be(0) + secondSnapshot.histograms(UserHistogram("histogram-with-default-configuration")).recordsIterator.toStream shouldBe empty + + secondSnapshot.counters(UserCounter("counter")).count should be(0) + + secondSnapshot.minMaxCounters.size should be(2) + secondSnapshot.minMaxCounters.keys should contain allOf ( + UserMinMaxCounter("min-max-counter-with-settings"), + UserMinMaxCounter("min-max-counter-with-default-configuration")) + + secondSnapshot.minMaxCounters(UserMinMaxCounter("min-max-counter-with-settings")).min shouldBe (42) + secondSnapshot.minMaxCounters(UserMinMaxCounter("min-max-counter-with-settings")).max shouldBe (42) + secondSnapshot.minMaxCounters(UserMinMaxCounter("min-max-counter-with-settings")).numberOfMeasurements should be(3) + secondSnapshot.minMaxCounters(UserMinMaxCounter("min-max-counter-with-settings")).recordsIterator.toStream should contain only ( + MutableRecord(42, 3)) // min, max and current + + secondSnapshot.minMaxCounters(UserMinMaxCounter("min-max-counter-with-default-configuration")).min shouldBe (0) + secondSnapshot.minMaxCounters(UserMinMaxCounter("min-max-counter-with-default-configuration")).max shouldBe (0) + secondSnapshot.minMaxCounters(UserMinMaxCounter("min-max-counter-with-default-configuration")).numberOfMeasurements should be(3) + secondSnapshot.minMaxCounters(UserMinMaxCounter("min-max-counter-with-default-configuration")).recordsIterator.toStream should contain only ( + MutableRecord(0, 3)) // min, max and current + + secondSnapshot.gauges(UserGauge("gauge-with-default-configuration")).min shouldBe (0) + secondSnapshot.gauges(UserGauge("gauge-with-default-configuration")).max shouldBe (0) + secondSnapshot.gauges(UserGauge("gauge-with-default-configuration")).numberOfMeasurements should be(0) + secondSnapshot.gauges(UserGauge("gauge-with-default-configuration")).recordsIterator shouldBe empty + + } + + "generate a snapshot that can be merged with another" in { + val context = Kamon(Metrics).buildDefaultCollectionContext + val userMetricsRecorder = Kamon(Metrics).register(UserMetrics, UserMetrics.Factory).get + + val histogram = Kamon(UserMetrics).registerHistogram("histogram-for-merge") + val counter = Kamon(UserMetrics).registerCounter("counter-for-merge") + val minMaxCounter = Kamon(UserMetrics).registerMinMaxCounter("min-max-counter-for-merge") + val gauge = Kamon(UserMetrics).registerGauge("gauge-for-merge") { () ⇒ 10L } + + histogram.record(100) + counter.increment(10) + minMaxCounter.increment(50) + minMaxCounter.decrement(10) + gauge.record(50) + + val firstSnapshot = userMetricsRecorder.collect(context) + + val extraCounter = Kamon(UserMetrics).registerCounter("extra-counter") + histogram.record(200) + extraCounter.increment(20) + minMaxCounter.increment(40) + minMaxCounter.decrement(50) + gauge.record(70) + + val secondSnapshot = userMetricsRecorder.collect(context) + val mergedSnapshot = firstSnapshot.merge(secondSnapshot, context) + + mergedSnapshot.histograms.keys should contain(UserHistogram("histogram-for-merge")) + + mergedSnapshot.histograms(UserHistogram("histogram-for-merge")).min shouldBe (100) + mergedSnapshot.histograms(UserHistogram("histogram-for-merge")).max shouldBe (200) + mergedSnapshot.histograms(UserHistogram("histogram-for-merge")).numberOfMeasurements should be(2) + mergedSnapshot.histograms(UserHistogram("histogram-for-merge")).recordsIterator.toStream should contain allOf ( + MutableRecord(100, 1), + MutableRecord(200, 1)) + + mergedSnapshot.counters(UserCounter("counter-for-merge")).count should be(10) + mergedSnapshot.counters(UserCounter("extra-counter")).count should be(20) + + mergedSnapshot.minMaxCounters(UserMinMaxCounter("min-max-counter-for-merge")).min shouldBe (0) + mergedSnapshot.minMaxCounters(UserMinMaxCounter("min-max-counter-for-merge")).max shouldBe (80) + mergedSnapshot.minMaxCounters(UserMinMaxCounter("min-max-counter-for-merge")).numberOfMeasurements should be(6) + mergedSnapshot.minMaxCounters(UserMinMaxCounter("min-max-counter-for-merge")).recordsIterator.toStream should contain allOf ( + MutableRecord(0, 1), // min in first snapshot + MutableRecord(30, 2), // min and current in second snapshot + MutableRecord(40, 1), // current in first snapshot + MutableRecord(50, 1), // max in first snapshot + MutableRecord(80, 1)) // max in second snapshot + + mergedSnapshot.gauges(UserGauge("gauge-for-merge")).min shouldBe (50) + mergedSnapshot.gauges(UserGauge("gauge-for-merge")).max shouldBe (70) + mergedSnapshot.gauges(UserGauge("gauge-for-merge")).numberOfMeasurements should be(2) + mergedSnapshot.gauges(UserGauge("gauge-for-merge")).recordsIterator.toStream should contain allOf ( + MutableRecord(50, 1), + MutableRecord(70, 1)) + } + } +} 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..1a93e1f6 --- /dev/null +++ b/kamon-core/src/test/scala/kamon/metric/instrument/CounterSpec.scala @@ -0,0 +1,55 @@ +package kamon.metric.instrument + +import java.nio.LongBuffer + +import kamon.metric.CollectionContext +import org.scalatest.{ Matchers, WordSpec } + +class CounterSpec extends WordSpec with Matchers { + + "a Counter" should { + "allow increment only operations" in new CounterFixture { + counter.increment() + counter.increment(10) + + intercept[UnsupportedOperationException] { + counter.increment(-10) + } + } + + "reset to zero when a snapshot is taken" in new CounterFixture { + counter.increment(100) + takeSnapshotFrom(counter).count should be(100) + takeSnapshotFrom(counter).count should be(0) + takeSnapshotFrom(counter).count should be(0) + + counter.increment(50) + takeSnapshotFrom(counter).count should be(50) + takeSnapshotFrom(counter).count should be(0) + } + + "produce a snapshot that can be merged with others" in new CounterFixture { + val counterA = Counter() + val counterB = Counter() + counterA.increment(100) + counterB.increment(200) + + val counterASnapshot = takeSnapshotFrom(counterA) + val counterBSnapshot = takeSnapshotFrom(counterB) + + counterASnapshot.merge(counterBSnapshot, collectionContext).count should be(300) + counterBSnapshot.merge(counterASnapshot, collectionContext).count should be(300) + } + + } + + trait CounterFixture { + val counter = Counter() + + val collectionContext = new CollectionContext { + val buffer: LongBuffer = LongBuffer.allocate(1) + } + + def takeSnapshotFrom(counter: Counter): Counter.Snapshot = counter.collect(collectionContext) + } +} diff --git a/kamon-core/src/test/scala/kamon/metric/instrument/GaugeSpec.scala b/kamon-core/src/test/scala/kamon/metric/instrument/GaugeSpec.scala new file mode 100644 index 00000000..9192d999 --- /dev/null +++ b/kamon-core/src/test/scala/kamon/metric/instrument/GaugeSpec.scala @@ -0,0 +1,72 @@ +package kamon.metric.instrument + +import java.util.concurrent.atomic.AtomicLong + +import akka.actor.ActorSystem +import com.typesafe.config.ConfigFactory +import kamon.Kamon +import kamon.metric.{ Metrics, Scale, CollectionContext } +import org.scalatest.{ Matchers, WordSpecLike } +import scala.concurrent.duration._ + +class GaugeSpec extends WordSpecLike with Matchers { + implicit val system = ActorSystem("gauge-spec", ConfigFactory.parseString( + """ + |kamon.metrics { + | flush-interval = 1 hour + | default-collection-context-buffer-size = 10 + | precision { + | default-gauge-precision { + | refresh-interval = 100 milliseconds + | highest-trackable-value = 999999999 + | significant-value-digits = 2 + | } + | } + |} + """.stripMargin)) + + "a Gauge" should { + "automatically record the current value using the configured refresh-interval" in { + val numberOfValuesRecorded = new AtomicLong(0) + val gauge = Gauge.fromDefaultConfig(system) { () ⇒ numberOfValuesRecorded.addAndGet(1) } + + Thread.sleep(1.second.toMillis) + numberOfValuesRecorded.get() should be(10L +- 1L) + gauge.cleanup + } + + "stop automatically recording after a call to cleanup" in { + val numberOfValuesRecorded = new AtomicLong(0) + val gauge = Gauge.fromDefaultConfig(system) { () ⇒ numberOfValuesRecorded.addAndGet(1) } + + Thread.sleep(1.second.toMillis) + gauge.cleanup + numberOfValuesRecorded.get() should be(10L +- 1L) + Thread.sleep(1.second.toMillis) + numberOfValuesRecorded.get() should be(10L +- 1L) + } + + "produce a Histogram snapshot including all the recorded values" in { + val numberOfValuesRecorded = new AtomicLong(0) + val gauge = Gauge.fromDefaultConfig(system) { () ⇒ numberOfValuesRecorded.addAndGet(1) } + + Thread.sleep(1.second.toMillis) + gauge.cleanup + val snapshot = gauge.collect(Kamon(Metrics).buildDefaultCollectionContext) + + snapshot.numberOfMeasurements should be(10L +- 1L) + snapshot.min should be(1) + snapshot.max should be(10L +- 1L) + } + + "not record the current value when doing a collection" in { + val numberOfValuesRecorded = new AtomicLong(0) + val gauge = Gauge(Histogram.Precision.Normal, 10000L, Scale.Unit, 1 hour, system)(() ⇒ numberOfValuesRecorded.addAndGet(1)) + + val snapshot = gauge.collect(Kamon(Metrics).buildDefaultCollectionContext) + + snapshot.numberOfMeasurements should be(0) + numberOfValuesRecorded.get() should be(0) + } + } +} diff --git a/kamon-core/src/test/scala/kamon/metric/instrument/HistogramSpec.scala b/kamon-core/src/test/scala/kamon/metric/instrument/HistogramSpec.scala new file mode 100644 index 00000000..cefdf0f4 --- /dev/null +++ b/kamon-core/src/test/scala/kamon/metric/instrument/HistogramSpec.scala @@ -0,0 +1,130 @@ +/* + * ========================================================================================= + * Copyright © 2013 the kamon project <http://kamon.io/> + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the + * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions + * and limitations under the License. + * ========================================================================================= + */ + +package kamon.metric.instrument + +import java.nio.LongBuffer + +import com.typesafe.config.ConfigFactory +import kamon.metric.CollectionContext +import org.scalatest.{ Matchers, WordSpec } + +import scala.util.Random + +class HistogramSpec extends WordSpec with Matchers { + + val histogramConfig = ConfigFactory.parseString( + """ + | + |highest-trackable-value = 100000 + |significant-value-digits = 2 + | + """.stripMargin) + + "a Histogram" should { + "allow record values within the configured range" in new HistogramFixture { + histogram.record(1000) + histogram.record(5000, count = 100) + histogram.record(10000) + } + + "fail when recording values higher than the highest trackable value" in new HistogramFixture { + intercept[IndexOutOfBoundsException] { + histogram.record(1000000) + } + } + + "reset all recorded levels to zero after a snapshot collection" in new HistogramFixture { + histogram.record(100) + histogram.record(200) + histogram.record(300) + + takeSnapshot().numberOfMeasurements should be(3) + takeSnapshot().numberOfMeasurements should be(0) + } + + "produce a snapshot" which { + "supports min, max and numberOfMeasurements operations" in new HistogramFixture { + histogram.record(100) + histogram.record(200, count = 200) + histogram.record(300) + histogram.record(900) + + val snapshot = takeSnapshot() + + snapshot.min should equal(100L +- 1L) + snapshot.max should equal(900L +- 9L) + snapshot.numberOfMeasurements should be(203) + } + + "can be merged with another snapshot" in new MultipleHistogramFixture { + val random = new Random(System.nanoTime()) + + for (repetitions ← 1 to 1000) { + // Put some values on A and Control + for (_ ← 1 to 1000) { + val newRecording = random.nextInt(100000) + controlHistogram.record(newRecording) + histogramA.record(newRecording) + } + + // Put some values on B and Control + for (_ ← 1 to 2000) { + val newRecording = random.nextInt(100000) + controlHistogram.record(newRecording) + histogramB.record(newRecording) + } + + val controlSnapshot = takeSnapshotFrom(controlHistogram) + val histogramASnapshot = takeSnapshotFrom(histogramA) + val histogramBSnapshot = takeSnapshotFrom(histogramB) + + assertEquals(controlSnapshot, histogramASnapshot.merge(histogramBSnapshot, collectionContext)) + assertEquals(controlSnapshot, histogramBSnapshot.merge(histogramASnapshot, collectionContext)) + } + } + } + } + + trait HistogramFixture { + val collectionContext = new CollectionContext { + val buffer: LongBuffer = LongBuffer.allocate(10000) + } + + val histogram = Histogram.fromConfig(histogramConfig) + + def takeSnapshot(): Histogram.Snapshot = histogram.collect(collectionContext) + } + + trait MultipleHistogramFixture { + val collectionContext = new CollectionContext { + val buffer: LongBuffer = LongBuffer.allocate(10000) + } + + val controlHistogram = Histogram.fromConfig(histogramConfig) + val histogramA = Histogram.fromConfig(histogramConfig) + val histogramB = Histogram.fromConfig(histogramConfig) + + def takeSnapshotFrom(histogram: Histogram): Histogram.Snapshot = histogram.collect(collectionContext) + + def assertEquals(left: Histogram.Snapshot, right: Histogram.Snapshot): Unit = { + left.numberOfMeasurements should equal(right.numberOfMeasurements) + left.min should equal(right.min) + left.max should equal(right.max) + left.recordsIterator.toStream should contain theSameElementsAs (right.recordsIterator.toStream) + } + } +} diff --git a/kamon-core/src/test/scala/kamon/metric/instrument/MinMaxCounterSpec.scala b/kamon-core/src/test/scala/kamon/metric/instrument/MinMaxCounterSpec.scala new file mode 100644 index 00000000..cb03664c --- /dev/null +++ b/kamon-core/src/test/scala/kamon/metric/instrument/MinMaxCounterSpec.scala @@ -0,0 +1,108 @@ +/* ========================================================================================= + * Copyright © 2013-2014 the kamon project <http://kamon.io/> + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the + * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions + * and limitations under the License. + * ========================================================================================= + */ + +package kamon.metric.instrument + +import java.nio.LongBuffer + +import akka.actor.ActorSystem +import com.typesafe.config.ConfigFactory +import kamon.metric.CollectionContext +import kamon.metric.instrument.Histogram.MutableRecord +import org.scalatest.{ Matchers, WordSpecLike } + +class MinMaxCounterSpec extends WordSpecLike with Matchers { + val system = ActorSystem("min-max-counter-spec") + val minMaxCounterConfig = ConfigFactory.parseString( + """ + |refresh-interval = 1 hour + |highest-trackable-value = 1000 + |significant-value-digits = 2 + """.stripMargin) + + "the MinMaxCounter" should { + "track ascending tendencies" in new MinMaxCounterFixture { + mmCounter.increment() + mmCounter.increment(3) + mmCounter.increment() + + val snapshot = collectCounterSnapshot() + + snapshot.min should be(0) + snapshot.max should be(5) + snapshot.recordsIterator.toStream should contain allOf ( + MutableRecord(0, 1), // min + MutableRecord(5, 2)) // max and current + } + + "track descending tendencies" in new MinMaxCounterFixture { + mmCounter.increment(5) + mmCounter.decrement() + mmCounter.decrement(3) + mmCounter.decrement() + + val snapshot = collectCounterSnapshot() + + snapshot.min should be(0) + snapshot.max should be(5) + snapshot.recordsIterator.toStream should contain allOf ( + MutableRecord(0, 2), // min and current + MutableRecord(5, 1)) // max + } + + "reset the min and max to the current value after taking a snapshot" in new MinMaxCounterFixture { + mmCounter.increment(5) + mmCounter.decrement(3) + + val firstSnapshot = collectCounterSnapshot() + + firstSnapshot.min should be(0) + firstSnapshot.max should be(5) + firstSnapshot.recordsIterator.toStream should contain allOf ( + MutableRecord(0, 1), // min + MutableRecord(2, 1), // current + MutableRecord(5, 1)) // max + + val secondSnapshot = collectCounterSnapshot() + + secondSnapshot.min should be(2) + secondSnapshot.max should be(2) + secondSnapshot.recordsIterator.toStream should contain( + MutableRecord(2, 3)) // min, max and current + } + + "report zero as the min and current values if they current value fell bellow zero" in new MinMaxCounterFixture { + mmCounter.decrement(3) + + val snapshot = collectCounterSnapshot() + + snapshot.min should be(0) + snapshot.max should be(0) + snapshot.recordsIterator.toStream should contain( + MutableRecord(0, 3)) // min, max and current (even while current really is -3 + } + } + + trait MinMaxCounterFixture { + val collectionContext = new CollectionContext { + val buffer: LongBuffer = LongBuffer.allocate(64) + } + + val mmCounter = MinMaxCounter.fromConfig(minMaxCounterConfig, system).asInstanceOf[PaddedMinMaxCounter] + mmCounter.cleanup // cancel the refresh schedule + + def collectCounterSnapshot(): Histogram.Snapshot = mmCounter.collect(collectionContext) + } +} diff --git a/kamon-core/src/test/scala/kamon/metrics/ActorMetricsSpec.scala b/kamon-core/src/test/scala/kamon/metrics/ActorMetricsSpec.scala deleted file mode 100644 index 645ca96a..00000000 --- a/kamon-core/src/test/scala/kamon/metrics/ActorMetricsSpec.scala +++ /dev/null @@ -1,172 +0,0 @@ -/* ========================================================================================= - * Copyright © 2013 the kamon project <http://kamon.io/> - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file - * except in compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the - * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, - * either express or implied. See the License for the specific language governing permissions - * and limitations under the License. - * ========================================================================================= - */ - -package kamon.metrics - -import org.scalatest.{ WordSpecLike, Matchers } -import akka.testkit.{ TestProbe, TestKitBase } -import akka.actor.{ ActorRef, Actor, Props, ActorSystem } -import com.typesafe.config.ConfigFactory -import scala.concurrent.duration._ -import kamon.Kamon -import kamon.metrics.Subscriptions.TickMetricSnapshot -import kamon.metrics.ActorMetrics.ActorMetricSnapshot -import kamon.metrics.MetricSnapshot.Measurement - -class ActorMetricsSpec extends TestKitBase with WordSpecLike with Matchers { - implicit lazy val system: ActorSystem = ActorSystem("actor-metrics-spec", ConfigFactory.parseString( - """ - |kamon.metrics { - | filters = [ - | { - | actor { - | includes = [ "user/tracked-*" ] - | excludes = [ "user/tracked-explicitly-excluded"] - | } - | } - | ] - |} - """.stripMargin)) - - "the Kamon actor metrics" should { - "respect the configured include and exclude filters" in new DelayableActorFixture { - val tracked = system.actorOf(Props[DelayableActor], "tracked-actor") - val nonTracked = system.actorOf(Props[DelayableActor], "non-tracked-actor") - val trackedExplicitlyExcluded = system.actorOf(Props[DelayableActor], "tracked-explicitly-excluded") - - Kamon(Metrics).subscribe(ActorMetrics, "*", testActor, permanently = true) - expectMsgType[TickMetricSnapshot] - - tracked ! Discard - nonTracked ! Discard - trackedExplicitlyExcluded ! Discard - - within(2 seconds) { - val tickSnapshot = expectMsgType[TickMetricSnapshot] - tickSnapshot.metrics.keys should contain(ActorMetrics("user/tracked-actor")) - tickSnapshot.metrics.keys should not contain (ActorMetrics("user/non-tracked-actor")) - tickSnapshot.metrics.keys should not contain (ActorMetrics("user/tracked-explicitly-excluded")) - } - } - - "record mailbox-size, processing-time and time-in-mailbox metrics under regular conditions" in new DelayableActorFixture { - val (delayable, metricsListener) = delayableActor("tracked-normal-conditions") - - for (_ ← 1 to 10) { - delayable ! Discard - } - - val actorMetrics = expectActorMetrics("user/tracked-normal-conditions", metricsListener, 3 seconds) - actorMetrics.mailboxSize.max should be <= 10L - actorMetrics.processingTime.numberOfMeasurements should be(10L) - actorMetrics.timeInMailbox.numberOfMeasurements should be(10L) - } - - "keep a correct mailbox-size even if the actor is blocked processing a message" in new DelayableActorFixture { - val (delayable, metricsListener) = delayableActor("tracked-mailbox-size-queueing-up") - - delayable ! Delay(2500 milliseconds) - for (_ ← 1 to 9) { - delayable ! Discard - } - - // let the first snapshot pass - metricsListener.expectMsgType[TickMetricSnapshot] - - // process the tick in which the actor is stalled. - val stalledTickMetrics = expectActorMetrics("user/tracked-mailbox-size-queueing-up", metricsListener, 2 seconds) - stalledTickMetrics.mailboxSize.numberOfMeasurements should equal(30) - // only the automatic last-value recording should be taken, and includes the message being currently processed. - stalledTickMetrics.mailboxSize.measurements should contain only (Measurement(10, 30)) - stalledTickMetrics.mailboxSize.min should equal(10) - stalledTickMetrics.mailboxSize.max should equal(10) - stalledTickMetrics.processingTime.numberOfMeasurements should be(0L) - stalledTickMetrics.timeInMailbox.numberOfMeasurements should be(0L) - - // process the tick after the actor is unblocked. - val afterStallTickMetrics = expectActorMetrics("user/tracked-mailbox-size-queueing-up", metricsListener, 2 seconds) - afterStallTickMetrics.processingTime.numberOfMeasurements should be(10L) - afterStallTickMetrics.timeInMailbox.numberOfMeasurements should be(10L) - afterStallTickMetrics.processingTime.max should be(2500.milliseconds.toNanos +- 100.milliseconds.toNanos) - afterStallTickMetrics.timeInMailbox.max should be(2500.milliseconds.toNanos +- 100.milliseconds.toNanos) - } - - "track the number of errors" in new ErrorActorFixture { - val (error, metricsListener) = failedActor("tracked-errors") - - for (_ ← 1 to 5) { - error ! Error - } - - val actorMetrics = expectActorMetrics("user/tracked-errors", metricsListener, 3 seconds) - actorMetrics.errorCounter.numberOfMeasurements should be(5L) - } - } - - def expectActorMetrics(actorPath: String, listener: TestProbe, waitTime: FiniteDuration): ActorMetricSnapshot = { - val tickSnapshot = within(waitTime) { - listener.expectMsgType[TickMetricSnapshot] - } - val actorMetricsOption = tickSnapshot.metrics.get(ActorMetrics(actorPath)) - actorMetricsOption should not be empty - actorMetricsOption.get.asInstanceOf[ActorMetricSnapshot] - } - - trait DelayableActorFixture { - def delayableActor(name: String): (ActorRef, TestProbe) = { - val actor = system.actorOf(Props[DelayableActor], name) - val metricsListener = TestProbe() - - Kamon(Metrics).subscribe(ActorMetrics, "user/" + name, metricsListener.ref, permanently = true) - // Wait for one empty snapshot before proceeding to the test. - metricsListener.expectMsgType[TickMetricSnapshot] - - (actor, metricsListener) - } - } - - trait ErrorActorFixture { - def failedActor(name: String): (ActorRef, TestProbe) = { - val actor = system.actorOf(Props[FailedActor], name) - val metricsListener = TestProbe() - - Kamon(Metrics).subscribe(ActorMetrics, "user/" + name, metricsListener.ref, permanently = true) - // Wait for one empty snapshot before proceeding to the test. - metricsListener.expectMsgType[TickMetricSnapshot] - - (actor, metricsListener) - } - } -} - -class DelayableActor extends Actor { - def receive = { - case Delay(time) ⇒ Thread.sleep(time.toMillis) - case Discard ⇒ - } -} - -class FailedActor extends Actor { - def receive = { - case Error ⇒ 1 / 0 - case Discard ⇒ - } -} - -case object Discard - -case class Delay(time: FiniteDuration) - -case class Error() diff --git a/kamon-core/src/test/scala/kamon/metrics/CustomMetricSpec.scala b/kamon-core/src/test/scala/kamon/metrics/CustomMetricSpec.scala deleted file mode 100644 index 1e072f71..00000000 --- a/kamon-core/src/test/scala/kamon/metrics/CustomMetricSpec.scala +++ /dev/null @@ -1,78 +0,0 @@ -/* - * ========================================================================================= - * Copyright © 2013 the kamon project <http://kamon.io/> - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file - * except in compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the - * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, - * either express or implied. See the License for the specific language governing permissions - * and limitations under the License. - * ========================================================================================= - */ - -package kamon.metrics - -import akka.testkit.TestKitBase -import org.scalatest.{ Matchers, WordSpecLike } -import akka.actor.ActorSystem -import scala.concurrent.duration._ -import com.typesafe.config.ConfigFactory -import kamon.Kamon -import kamon.metrics.Subscriptions.TickMetricSnapshot -import kamon.metrics.MetricSnapshot.Measurement - -class CustomMetricSpec extends TestKitBase with WordSpecLike with Matchers { - implicit lazy val system: ActorSystem = ActorSystem("actor-metrics-spec", ConfigFactory.parseString( - """ - |kamon.metrics { - | filters = [ - | { - | custom-metric { - | includes = [ "test/*" ] - | excludes = [ ] - | } - | } - | ] - |} - """.stripMargin)) - - "the Kamon custom metrics support" should { - "allow registering a custom metric with the Metrics extension" in { - val recorder = Kamon(Metrics).register(CustomMetric("test/sample-counter"), CustomMetric.histogram(100, 2, Scale.Unit)) - - recorder should be('defined) - } - - "allow subscriptions to custom metrics using the default subscription protocol" in { - val recorder = Kamon(Metrics).register(CustomMetric("test/sample-counter"), CustomMetric.histogram(100, 2, Scale.Unit)) - - recorder.map { r ⇒ - r.record(100) - r.record(15) - r.record(0) - r.record(50) - } - - Kamon(Metrics).subscribe(CustomMetric, "test/sample-counter", testActor) - - val recordedValues = within(5 seconds) { - val snapshot = expectMsgType[TickMetricSnapshot] - snapshot.metrics(CustomMetric("test/sample-counter")).metrics(CustomMetric.RecordedValues) - } - - recordedValues.min should equal(0) - recordedValues.max should equal(100) - recordedValues.numberOfMeasurements should equal(4) - recordedValues.measurements should contain allOf ( - Measurement(0, 1), - Measurement(15, 1), - Measurement(50, 1), - Measurement(100, 1)) - } - } - -} diff --git a/kamon-core/src/test/scala/kamon/metrics/MetricSnapshotSpec.scala b/kamon-core/src/test/scala/kamon/metrics/MetricSnapshotSpec.scala deleted file mode 100644 index 4d6ebc49..00000000 --- a/kamon-core/src/test/scala/kamon/metrics/MetricSnapshotSpec.scala +++ /dev/null @@ -1,72 +0,0 @@ -/* - * ========================================================================================= - * Copyright © 2013 the kamon project <http://kamon.io/> - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file - * except in compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the - * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, - * either express or implied. See the License for the specific language governing permissions - * and limitations under the License. - * ========================================================================================= - */ - -package kamon.metrics - -import org.scalatest.{ Matchers, WordSpec } -import kamon.metrics.MetricSnapshot.Measurement - -class MetricSnapshotSpec extends WordSpec with Matchers { - - "a metric snapshot" should { - "support a max operation" in new SnapshotFixtures { - snapshotA.max should be(17) - snapshotB.max should be(10) - snapshotC.max should be(1) - } - - "support a min operation" in new SnapshotFixtures { - snapshotA.min should be(1) - snapshotB.min should be(2) - snapshotC.min should be(1) - } - - "be able to merge with other snapshot" in new SnapshotFixtures { - val merged = snapshotA.merge(snapshotB).merge(snapshotC) - - merged.min should be(1) - merged.max should be(17) - merged.numberOfMeasurements should be(300) - merged.measurements.map(_.value) should contain inOrderOnly (1, 2, 4, 5, 7, 10, 17) - } - - "be able to merge with empty snapshots" in new SnapshotFixtures { - snapshotA.merge(emptySnapshot) should be(snapshotA) - emptySnapshot.merge(snapshotA).merge(emptySnapshot) should be(snapshotA) - snapshotC.merge(emptySnapshot) should be(snapshotC) - } - - } - - trait SnapshotFixtures { - val emptySnapshot = MetricSnapshot(InstrumentTypes.Histogram, 0, Scale.Unit, Vector.empty) - - val snapshotA = MetricSnapshot(InstrumentTypes.Histogram, 100, Scale.Unit, Vector( - Measurement(1, 3), - Measurement(2, 15), - Measurement(5, 68), - Measurement(7, 13), - Measurement(17, 1))) - - val snapshotB = MetricSnapshot(InstrumentTypes.Histogram, 100, Scale.Unit, Vector( - Measurement(2, 6), - Measurement(4, 48), - Measurement(5, 39), - Measurement(10, 7))) - - val snapshotC = MetricSnapshot(InstrumentTypes.Counter, 100, Scale.Unit, Vector(Measurement(1, 100))) - } -} diff --git a/kamon-core/src/test/scala/kamon/metrics/instrument/MinMaxCounterSpec.scala b/kamon-core/src/test/scala/kamon/metrics/instrument/MinMaxCounterSpec.scala deleted file mode 100644 index 14f1573f..00000000 --- a/kamon-core/src/test/scala/kamon/metrics/instrument/MinMaxCounterSpec.scala +++ /dev/null @@ -1,110 +0,0 @@ -/* ========================================================================================= - * Copyright © 2013-2014 the kamon project <http://kamon.io/> - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file - * except in compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the - * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, - * either express or implied. See the License for the specific language governing permissions - * and limitations under the License. - * ========================================================================================= - */ -package kamon.metrics.instrument - -import org.scalatest.{ Matchers, WordSpecLike } -import kamon.metrics.instruments.MinMaxCounter -import kamon.metrics.instruments.MinMaxCounter.CounterMeasurement - -class MinMaxCounterSpec extends WordSpecLike with Matchers { - - "the MinMaxCounter" should { - "increment" in { - val counter = MinMaxCounter() - - counter.increment() - counter.increment() - counter.increment() - counter.increment() - counter.increment() - - val CounterMeasurement(_, _, current) = counter.collect() - - current should be(5) - } - - "decrement" in { - val counter = MinMaxCounter() - counter.increment(5L) - - counter.decrement() - counter.decrement() - counter.decrement() - counter.decrement() - counter.decrement() - - val CounterMeasurement(_, _, current) = counter.collect() - - current should be(0) - } - - "reset the min and max with the sum value when the collect method is called" in { - val counter = MinMaxCounter() - - counter.increment(10) - counter.increment(20) - counter.increment(30) - counter.increment(40) - counter.increment(50) - - counter.collect() //only for check the last value after reset min max - - val CounterMeasurement(min, max, current) = counter.collect() - - min should be(current) - max should be(current) - current should be(150) - } - } - - "track the min value" in { - val counter = MinMaxCounter() - - counter.increment(10) - counter.increment(20) - counter.increment(30) - counter.increment(40) - counter.increment(50) - - val CounterMeasurement(min, _, _) = counter.collect() - - min should be(0) - - counter.increment(50) - - val CounterMeasurement(minAfterCollectAndAddSomeValues, _, _) = counter.collect() - - minAfterCollectAndAddSomeValues should be(150) - } - - "track the max value" in { - val counter = MinMaxCounter() - counter.increment(10) - counter.increment(20) - counter.increment(30) - counter.increment(40) - counter.increment(50) - - val CounterMeasurement(_, max, _) = counter.collect() - - max should be(150) - - counter.increment(200) - - val CounterMeasurement(_, maxAfterCollectAndAddSomeValues, _) = counter.collect() - - maxAfterCollectAndAddSomeValues should be(350) - } -} diff --git a/kamon-core/src/test/scala/kamon/trace/TraceContextManipulationSpec.scala b/kamon-core/src/test/scala/kamon/trace/TraceContextManipulationSpec.scala new file mode 100644 index 00000000..d073f68e --- /dev/null +++ b/kamon-core/src/test/scala/kamon/trace/TraceContextManipulationSpec.scala @@ -0,0 +1,96 @@ +package kamon.trace + +import akka.actor.ActorSystem +import akka.testkit.TestKitBase +import com.typesafe.config.ConfigFactory +import kamon.trace.TraceContext.SegmentIdentity +import org.scalatest.{ Matchers, WordSpecLike } + +class TraceContextManipulationSpec extends TestKitBase with WordSpecLike with Matchers { + implicit def self = testActor + implicit lazy val system: ActorSystem = ActorSystem("trace-metrics-spec", ConfigFactory.parseString( + """ + |kamon.metrics { + | tick-interval = 1 hour + | filters = [ + | { + | trace { + | includes = [ "*" ] + | excludes = [ "non-tracked-trace"] + | } + | } + | ] + | precision { + | default-histogram-precision { + | highest-trackable-value = 3600000000000 + | significant-value-digits = 2 + | } + | + | default-min-max-counter-precision { + | refresh-interval = 1 second + | highest-trackable-value = 999999999 + | significant-value-digits = 2 + | } + | } + |} + """.stripMargin)) + + "the TraceRecorder api" should { + "allow starting a trace within a specified block of code, and only within that block of code" in { + val createdContext = TraceRecorder.withNewTraceContext("start-context") { + TraceRecorder.currentContext should not be empty + TraceRecorder.currentContext.get + } + + TraceRecorder.currentContext shouldBe empty + createdContext.name shouldBe ("start-context") + } + + "allow starting a trace within a specified block of code, providing a trace-token and only within that block of code" in { + val createdContext = TraceRecorder.withNewTraceContext("start-context-with-token", Some("token-1")) { + TraceRecorder.currentContext should not be empty + TraceRecorder.currentContext.get + } + + TraceRecorder.currentContext shouldBe empty + createdContext.name shouldBe ("start-context-with-token") + createdContext.token should be("token-1") + } + + "allow providing a TraceContext and make it available within a block of code" in { + val createdContext = TraceRecorder.withNewTraceContext("manually-provided-trace-context") { TraceRecorder.currentContext } + + TraceRecorder.currentContext shouldBe empty + TraceRecorder.withTraceContext(createdContext) { + TraceRecorder.currentContext should be(createdContext) + } + + TraceRecorder.currentContext shouldBe empty + } + + "allow renaming a trace" in { + val createdContext = TraceRecorder.withNewTraceContext("trace-before-rename") { + TraceRecorder.rename("renamed-trace") + TraceRecorder.currentContext.get + } + + TraceRecorder.currentContext shouldBe empty + createdContext.name shouldBe ("renamed-trace") + } + + "allow creating a segment within a trace" in { + val createdContext = TraceRecorder.withNewTraceContext("trace-with-segments") { + val segmentHandle = TraceRecorder.startSegment(TraceManipulationTestSegment("segment-1")) + + TraceRecorder.currentContext.get + } + + TraceRecorder.currentContext shouldBe empty + createdContext.name shouldBe ("trace-with-segments") + + } + } + + case class TraceManipulationTestSegment(name: String) extends SegmentIdentity + +} |