diff options
author | Ivan Topolnjak <ivantopo@gmail.com> | 2014-07-03 14:36:42 -0300 |
---|---|---|
committer | Ivan Topolnjak <ivantopo@gmail.com> | 2014-07-03 14:36:18 -0300 |
commit | a3353d3e3fcb1dfab3e8f401187e236e99df2202 (patch) | |
tree | 4e9e246201cf169f1496bc72928ea2d35d03fcd0 /kamon-core/src/test/scala/kamon/metric/ActorMetricsSpec.scala | |
parent | 6d7970c6dd5b96b512c846181771bb11a43bc82a (diff) | |
download | Kamon-a3353d3e3fcb1dfab3e8f401187e236e99df2202.tar.gz Kamon-a3353d3e3fcb1dfab3e8f401187e236e99df2202.tar.bz2 Kamon-a3353d3e3fcb1dfab3e8f401187e236e99df2202.zip |
! all: refactor the core metric recording instruments and accomodate UserMetrics
This PR is including several changes to the kamon-core, most notably:
- Formalize the interface for Histograms, Counters and MinMaxCounters. Making sure
that the interfaces are as clean as possible.
- Move away from the all Vector[Measurement] based Histogram snapshot to a new approach
in which we use a single long to store both the index in the counts array and the
frequency on that bucket. The leftmost 2 bytes of each long are used for storing the
counts array index and the remaining 6 bytes are used for the actual count, and
everything is put into a simple long array. This way only the buckets that actually
have values will be included in the snapshot with the smallest possible memory
footprint.
- Introduce Gauges.
- Reorganize the instrumentation for Akka and Scala and rewrite most of the tests
of this components to avoid going through the subscription protocol to test.
- Introduce trace tests and fixes on various tests.
- Necessary changes on new relic, datadog and statsd modules to compile with the new
codebase.
Pending:
- Finish the upgrade of the new relic to the current model.
- Introduce proper limit checks for histograms to ensure that we never pass the 2/6 bytes
limits.
- More testing, more testing, more testing.
- Create the KamonStandalone module.
Diffstat (limited to 'kamon-core/src/test/scala/kamon/metric/ActorMetricsSpec.scala')
-rw-r--r-- | kamon-core/src/test/scala/kamon/metric/ActorMetricsSpec.scala | 202 |
1 files changed, 202 insertions, 0 deletions
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..481f03c5 --- /dev/null +++ b/kamon-core/src/test/scala/kamon/metric/ActorMetricsSpec.scala @@ -0,0 +1,202 @@ +/* ========================================================================================= + * 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 kamon.metric.instrument.Histogram.MutableRecord +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.Subscriptions.TickMetricSnapshot +import kamon.metric.ActorMetrics.{ ActorMetricsRecorder, ActorMetricSnapshot } + +class ActorMetricsSpec extends TestKitBase with WordSpecLike with Matchers with ImplicitSender { + implicit lazy val system: ActorSystem = ActorSystem("actor-metrics-spec", ConfigFactory.parseString( + """ + |kamon.metrics { + | 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 + } +} |