Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

Upgraded to play 2, but having problems with tests

  • Loading branch information...
commit e58d7d00897d3a392fc6e209477058aeb3a4a125 1 parent 5b1de83
James Roper jroper authored
4 .gitignore
... ... @@ -0,0 +1,4 @@
  1 +target/
  2 +lib_managed/
  3 +src_managed/
  4 +project/boot/
5 build.sbt
@@ -3,6 +3,7 @@
3 3 name := "play-statsd"
4 4
5 5 // So that we can publish it into Sonatype
  6 +
6 7 organization := "net.vz.play.statsd"
7 8
8 9 // The version comes from version.sbt, and is generated by the release plugin
@@ -18,10 +19,12 @@ libraryDependencies ++= Seq(
18 19 // Test dependencies
19 20
20 21 libraryDependencies ++= Seq(
21   - "org.specs2" %% "specs2" % "1.8.2" % "test",
  22 + "org.specs2" %% "specs2" % "1.9" % "test",
22 23 "play" %% "play-test" % "2.0" % "test"
23 24 )
24 25
  26 +parallelExecution in Test := false
  27 +
25 28 // Configuration required for deploying to sonatype
26 29
27 30 publishMavenStyle := true
108 src/main/java/play/modules/statsd/Statsd.java
... ... @@ -0,0 +1,108 @@
  1 +package play.modules.statsd;
  2 +
  3 +import play.libs.F;
  4 +import play.modules.statsd.api.Statsd$;
  5 +import play.modules.statsd.api.StatsdClient;
  6 +import scala.runtime.AbstractFunction0;
  7 +
  8 +/**
  9 + * Java API to Statsd
  10 + */
  11 +public class Statsd {
  12 +
  13 + /**
  14 + * Increment the given key by 1
  15 + *
  16 + * @param key The key to increment
  17 + */
  18 + public static void increment(String key) {
  19 + client().increment(key, 1, 1.0);
  20 + }
  21 +
  22 + /**
  23 + * Increment the given key by the given value
  24 + *
  25 + * @param key The key to increment
  26 + * @param value The value to increment it by
  27 + */
  28 + public static void increment(String key, long value) {
  29 + client().increment(key, value, 1.0);
  30 + }
  31 +
  32 + /**
  33 + * Increment the given key by the given value at the given rate
  34 + *
  35 + * @param key The key to increment
  36 + * @param value The value to increment it by
  37 + * @param rate The rate to sample at
  38 + */
  39 + public static void increment(String key, long value, double rate) {
  40 + client().increment(key, value, rate);
  41 + }
  42 +
  43 + /**
  44 + * Increment the given key by 1 at the given rate
  45 + *
  46 + * @param key The key to increment
  47 + * @param rate The rate to sample at
  48 + */
  49 + public static void increment(String key, double rate) {
  50 + client().increment(key, 1, rate);
  51 + }
  52 +
  53 + /**
  54 + * Reporting timing for the given key
  55 + *
  56 + * @param key The key to report timing for
  57 + * @param ms The time to report
  58 + */
  59 + public static void timing(String key, long ms) {
  60 + client().timing(key, ms, 1.0);
  61 + }
  62 +
  63 + /**
  64 + * Reporting timing for the given key at the given rate
  65 + *
  66 + * @param key The key to report timing for
  67 + * @param ms The time to report
  68 + * @param rate The rate to sample at
  69 + */
  70 + public static void timing(String key, long ms, double rate) {
  71 + client().timing(key, ms, rate);
  72 + }
  73 +
  74 + /**
  75 + * Time the given function and report the timing on the given key
  76 + *
  77 + * @param key The key to report timing for
  78 + * @param timed The function to time
  79 + */
  80 + public static <T> T time(String key, F.Function0<T> timed) {
  81 + return time(key, 1.0, timed);
  82 + }
  83 +
  84 + /**
  85 + * Time the given function and report the timing on the given key at the given rate
  86 + *
  87 + * @param key The key to report timing for
  88 + * @param rate The rate to sample at
  89 + * @param timed The function to time
  90 + */
  91 + public static <T> T time(String key, double rate, final F.Function0<T> timed) {
  92 + return client().time(key, rate, new AbstractFunction0<T>() {
  93 + public T apply() {
  94 + try {
  95 + return timed.apply();
  96 + } catch (RuntimeException e) {
  97 + throw e;
  98 + } catch (Throwable t) {
  99 + throw new RuntimeException(t);
  100 + }
  101 + }
  102 + });
  103 + }
  104 +
  105 + private static StatsdClient client() {
  106 + return Statsd$.MODULE$;
  107 + }
  108 +}
44 src/play/modules/statsd/StatsdClient.scala → .../scala/play/modules/statsd/api/StatsdClient.scala
... ... @@ -1,26 +1,23 @@
1   -package play.modules.statsd
  1 +package play.modules.statsd.api
2 2
3   -import java.net.DatagramPacket
4 3 import play.Logger
5   -import scala.util.Random
6 4
7 5 /**
8 6 * Trait defining the statsd interface. It defines the two stats calls in
9   - * statsd: {@code increment} and {@code timing}. It must be instantiated with
10   - * {@link StatsdClientCake} which handles the sending of stats over the network.
  7 + * statsd: `increment` and `timing`. It must be instantiated with
  8 + * [[play.modules.statsd.api.StatsdClientCake]] which handles the sending of stats over the network.
11 9 *
12   - * <p><ul>Two stats-related function are supported.
13   - * <li><b>increment</b>: Increment a given stat key.
14   - * <li><b>timing</b>: Sending timing info for a given operation key.
15   - * </ul>
  10 + * Two stats-related function are supported:
  11 + * - `increment`: Increment a given stat key.
  12 + * - `timing`: Sending timing info for a given operation key.
16 13 *
17   - * <p>For both, an optional {@code samplingRate} parameter can be provided.
  14 + * For both, an optional `samplingRate` parameter can be provided.
18 15 * For parameters between 0 and 1.0, the client will send the stat
19   - * {@code (samplingRate * 100)%} of the time. This is useful for some stats that
  16 + * `samplingRate * 100` of the time. This is useful for some stats that
20 17 * occur extremely frequently and therefore put too much load on the statsd
21 18 * server.
22 19 *
23   - * <p>The functionality is exposed to Play Apps using the {@link Statsd} object.
  20 + * The functionality is exposed to Play Apps using the [[play.modules.statsd.Statsd]] object.
24 21 */
25 22 trait StatsdClient {
26 23 self: StatsdClientCake =>
@@ -39,18 +36,18 @@ trait StatsdClient {
39 36 * @param samplingRate The probability for which to increment. Defaults to 1.
40 37 */
41 38 def increment(key: String, value: Long = 1, samplingRate: Double = 1.0) {
42   - safely { maybeSend(statFor(key, value, IncrementSuffix), samplingRate) }
  39 + safely { maybeSend(statFor(key, value, IncrementSuffix, samplingRate), samplingRate) }
43 40 }
44 41
45 42 /**
46 43 * Timing data for given stat key. Optionally give it a sampling rate.
47 44 *
48 45 * @param key The stat key to be timed.
49   - * @param value The number of milliseconds the operation took.
  46 + * @param millis The number of milliseconds the operation took.
50 47 * @param samplingRate The probability for which to increment. Defaults to 1.
51 48 */
52 49 def timing(key: String, millis: Long, samplingRate: Double = 1.0) {
53   - safely { maybeSend(statFor(key, millis, TimingSuffix), samplingRate) }
  50 + safely { maybeSend(statFor(key, millis, TimingSuffix, samplingRate), samplingRate) }
54 51 }
55 52
56 53 /**
@@ -58,7 +55,7 @@ trait StatsdClient {
58 55 *
59 56 * @param key The stat key to be timed.
60 57 * @param samplingRate The probability for which to increment. Defaults to 1.
61   - * @param operation An arbitrary block of code to be timed.
  58 + * @param timed An arbitrary block of code to be timed.
62 59 * @return The result of the timed operation.
63 60 */
64 61 def time[T](key: String, samplingRate: Double = 1.0)(timed: => T): T = {
@@ -79,9 +76,14 @@ trait StatsdClient {
79 76 * Creates the stat string to send to statsd.
80 77 * For counters, it provides something like {@code key:value|c}.
81 78 * For timing, it provides something like {@code key:millis|ms}.
  79 + * If sampling rate is less than 1, it provides something like {@code key:value|type|@rate}
82 80 */
83   - private def statFor(key: String, value: Long, suffix: String): String = {
84   - "%s.%s:%s|%s".format(statPrefix, key, value, suffix)
  81 + private def statFor(key: String, value: Long, suffix: String, samplingRate: Double): String = {
  82 + samplingRate match {
  83 + case x if x >= 1.0 => "%s.%s:%s|%s".format(statPrefix, key, value, suffix)
  84 + case _ => "%s.%s:%s|%s|@%f".format(statPrefix, key, value, suffix, samplingRate)
  85 + }
  86 +
85 87 }
86 88
87 89 /*
@@ -102,13 +104,13 @@ trait StatsdClient {
102 104 try {
103 105 operation
104 106 } catch {
105   - case error => please warn error -> "Unhandled throwable sending stat."
  107 + case error => Logger.warn("Unhandled throwable sending stat.", error)
106 108 }
107 109 }
108 110 }
109 111
110 112 /**
111   - * Wrap the {@link StatsdClient} trait configured with
112   - * {@link RealStatsdClientCake} in an object to make it available to the app.
  113 + * Wrap the [[play.modules.statsd.api.StatsdClient]] trait configured with
  114 + * [[play.modules.statsd.api.RealStatsdClientCake]] in an object to make it available to the app.
113 115 */
114 116 object Statsd extends StatsdClient with RealStatsdClientCake
46 src/play/modules/statsd/StatsdClientCake.scala → ...la/play/modules/statsd/api/StatsdClientCake.scala
... ... @@ -1,14 +1,14 @@
1   -package play.modules.statsd
  1 +package play.modules.statsd.api
2 2
3 3 import java.net.DatagramPacket
4 4 import java.net.DatagramSocket
5 5 import java.net.InetAddress
6 6 import play.Logger
7 7 import scala.util.Random
8   -import please._
  8 +import play.api.Play
9 9
10 10 /**
11   - * Configuration trait for the {@link StatsdClient}.
  11 + * Configuration trait for the [[play.modules.statsd.api.StatsdClient]].
12 12 *
13 13 * Provides to the client the prefix for all stats sent to statsd and mechanism
14 14 * for sending stats over the network.
@@ -28,14 +28,13 @@ trait StatsdClientCake {
28 28 }
29 29
30 30 /**
31   - * Real implementation of {@link StatsdClientCake}.
  31 + * Real implementation of [[play.modules.statsd.api.StatsdClientCake]].
32 32 *
33   - * <p><ul>This implementation:
34   - * <li>Reads in values from {@code conf/application.conf}
35   - * <li>Sends stats using a {@link DatagramSocket} to statsd server.
36   - * <ul>
  33 + * This implementation:
  34 + * - Reads in values from `conf/application.conf`
  35 + * - Sends stats using a `DatagramSocket` to statsd server.
37 36 */
38   -private[statsd] trait RealStatsdClientCake extends StatsdClientCake {
  37 +private[api] trait RealStatsdClientCake extends StatsdClientCake {
39 38
40 39 // The property name for whether or not the statsd sending should be enabled.
41 40 private val StatsdEnabledProperty = "statsd.enabled"
@@ -54,36 +53,37 @@ private[statsd] trait RealStatsdClientCake extends StatsdClientCake {
54 53
55 54 // The stat prefix used by the client.
56 55 override val statPrefix = {
57   - val default = "statsd"
58   - val configured = play.configuration(StatPrefixProperty, default)
59   - if (configured == default)
60   - Logger.warn("using default prefix: " + default)
61   - configured
  56 + Play.current.configuration.getString(StatPrefixProperty) getOrElse {
  57 + Logger.warn("No stat prefix configured, using default of statsd")
  58 + "statsd"
  59 + }
62 60 }
63 61
64 62 /**
65   - * Use {@code System.currentTimeMillis()} to get the current time.
  63 + * Use `System.currentTimeMillis()` to get the current time.
66 64 */
67 65 override def now(): Long = System.currentTimeMillis()
68 66
69 67 /**
70   - * Use scala's {@link Random} util for {@code nextFloat}.
  68 + * Use scala's [[scala.util.Random]] util for `nextFloat`.
71 69 */
72 70 override def nextFloat(): Float = random.nextFloat()
73 71
74 72 /**
75   - * Expose a {@code send} function to the client. Is configured with the
  73 + * Expose a `send` function to the client. It is configured with the hostname and port.
  74 + *
  75 + * If statsd isn't enabled, it will be a noop function.
76 76 */
77 77 override lazy val send: Function1[String, Unit] = {
78 78 try {
79 79 // Check if Statsd sending is enabled.
80   - val enabled = booleanConfig(StatsdEnabledProperty)
  80 + val enabled = please.booleanConfig(StatsdEnabledProperty)
81 81 if (enabled) {
82 82 // Initialize the socket, host, and port to be used to send the data.
83 83 val socket = new DatagramSocket
84   - val hostname = config(HostnameProperty)
  84 + val hostname = please.config(HostnameProperty)
85 85 val host = InetAddress.getByName(hostname)
86   - val port = intConfig(PortProperty)
  86 + val port = please.intConfig(PortProperty)
87 87
88 88 // Return the real send function, partially applied with the
89 89 // socket, host, and port so the client only has to call "send(stat)".
@@ -98,13 +98,13 @@ private[statsd] trait RealStatsdClientCake extends StatsdClientCake {
98 98 // If there is any error configuring the send function, log a warning
99 99 // but don't throw an error. Use a noop function for all sends.
100 100 case error: Throwable =>
101   - please warn error -> "Send will NOOP because of configuration problem."
  101 + Logger.warn("Send will NOOP because of configuration problem.", error)
102 102 noopSend _
103 103 }
104 104 }
105 105
106 106 /**
107   - * Send the stat in a {@link DatagramPacket} to statsd.
  107 + * Send the stat in a [[java.net.DatagramPacket]] to statsd.
108 108 */
109 109 private def socketSend(
110 110 socket: DatagramSocket, host: InetAddress, port: Int)(stat: String) {
@@ -112,7 +112,7 @@ private[statsd] trait RealStatsdClientCake extends StatsdClientCake {
112 112 val data = stat.getBytes
113 113 socket.send(new DatagramPacket(data, data.length, host, port))
114 114 } catch {
115   - case error: Throwable => please report error
  115 + case error: Throwable => Logger.error("", error)
116 116 }
117 117 }
118 118
21 src/main/scala/play/modules/statsd/api/Sugar.scala
... ... @@ -0,0 +1,21 @@
  1 +package play.modules.statsd.api
  2 +import play.api.Play
  3 +
  4 +/**
  5 + * Sugar to make using Java API for Play nicer.
  6 + */
  7 +object please {
  8 + private[api] def config(name: String): String = {
  9 + Play.current.configuration.getString(name) getOrElse {
  10 + throw new IllegalStateException("[%s] prop is null".format(name))
  11 + }
  12 + }
  13 +
  14 + private[api] def booleanConfig(name: String): Boolean = {
  15 + Play.current.configuration.getBoolean(name).getOrElse(false)
  16 + }
  17 +
  18 + private[api] def intConfig(name: String): Int = {
  19 + Integer.parseInt(config(name))
  20 + }
  21 +}
37 src/play/modules/statsd/Sugar.scala
... ... @@ -1,37 +0,0 @@
1   -package play.modules.statsd
2   -import play.Logger
3   -import org.apache.commons.lang.exception.ExceptionUtils
4   -
5   -/**
6   - * Sugar to make using Java API for Play nicer.
7   - */
8   -object please {
9   - private[statsd] def config(name: String): String = {
10   - checkNotNull(play.configuration(name), "[%s] prop is null".format(name))
11   - }
12   -
13   - private[statsd] def booleanConfig(name: String): Boolean = {
14   - "true" equals config(name).toLowerCase
15   - }
16   -
17   - private[statsd] def intConfig(name: String): Int = {
18   - Integer.parseInt(config(name))
19   - }
20   -
21   - private[statsd] def checkNotNull[T](ref: T, message: String = ""): T = {
22   - if (ref == null) {
23   - throw new IllegalStateException(message)
24   - }
25   - ref
26   - }
27   -
28   - def report(error: Throwable): Unit = this report error -> ""
29   -
30   - def report(pair: (Throwable, String)) {
31   - Logger.error(ExceptionUtils.getStackTrace(pair._1), pair._2)
32   - }
33   -
34   - def warn(pair: (Throwable, String)) {
35   - Logger.warn(ExceptionUtils.getStackTrace(pair._1), pair._2)
36   - }
37   -}
74 src/test/scala/play/modules/statsd/api/StatsdSpec.scala
... ... @@ -0,0 +1,74 @@
  1 +package play.modules.statsd.api
  2 +
  3 +import play.api.test.FakeApplication
  4 +import java.net.{SocketTimeoutException, DatagramPacket, DatagramSocket}
  5 +import play.api.test.Helpers.running
  6 +import org.specs2.mutable.{BeforeAfter, Specification, After}
  7 +
  8 +case class StatsdSpec() extends Specification {
  9 + "Statsd" should {
  10 + "send increment by one message" in new Setup {
  11 + running(fakeApp) {
  12 + println("Executing 1")
  13 + mockStatsd
  14 + Statsd.increment("test")
  15 + receive() mustEqual "statsd.test:1|c"
  16 + }
  17 + }
  18 + "send increment by more message" in new Setup {
  19 + running(fakeApp) {
  20 + println("Executing 2")
  21 + mockStatsd
  22 + Statsd.increment("test", 10)
  23 + receive() mustEqual "statsd.test:10|c"
  24 + }
  25 + }
  26 + "hopefully send a message when sampling rate is only just below 1" in new Setup {
  27 + running(fakeApp) {
  28 + println("Executing 3")
  29 + mockStatsd
  30 + Statsd.increment("test", 10, 0.9999999999)
  31 + receive() mustEqual "statsd.test:10|c|@0.999999"
  32 + }
  33 + }
  34 + }
  35 +
  36 + trait Setup extends BeforeAfter {
  37 + val PORT = 57475;
  38 + val fakeApp = FakeApplication(additionalConfiguration = Map(
  39 + "ehcacheplugin" -> "disabled",
  40 + "statsd.enabled" -> "true",
  41 + "statsd.host" -> "localhost",
  42 + "statsd.port" -> PORT.toString))
  43 + lazy val mockStatsd = {
  44 + println("Starting mock")
  45 + val socket = new DatagramSocket(PORT)
  46 + socket.setSoTimeout(1000)
  47 + socket
  48 + }
  49 +
  50 + def receive() = {
  51 + val buf: Array[Byte] = new Array[Byte](1024)
  52 + val packet = new DatagramPacket(buf, buf.length)
  53 + try {
  54 + mockStatsd.receive(packet)
  55 + }
  56 + catch {
  57 + case s: SocketTimeoutException => failure("Didn't receive message within one second")
  58 + }
  59 + new String(packet.getData, 0, packet.getLength)
  60 + }
  61 +
  62 + def before {
  63 + println("Executing before")
  64 + // mockStatsd
  65 + }
  66 +
  67 + def after {
  68 + println("Stopping mock")
  69 + mockStatsd.close()
  70 + }
  71 + }
  72 +
  73 +
  74 +}

0 comments on commit e58d7d0

Please sign in to comment.
Something went wrong with that request. Please try again.