Permalink
Browse files

Upgraded to play 2, but having problems with tests

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

0 comments on commit e58d7d0

Please sign in to comment.