This repository has been archived by the owner on Feb 9, 2019. It is now read-only.
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
18 changed files
with
1,062 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
target/ | ||
project/boot/ | ||
project/target/ | ||
logs/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
documentation/manual/home.textile |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
h1. statsd | ||
|
||
This is a simple **statsd** module for **Play! Framework 2**. It pulls in configuration from @conf/application.conf@ | ||
and provides a singleton object @Statsd@ with methods for **counter** and **timing** calls to **statsd**. It provides | ||
both a Scala and Java interface. Similar to the Play 2 convention, the Scala interface is called | ||
@play.modules.statsd.api.Statsd@, and the Java interface is @play.modules.statsd.Statsd@. | ||
|
||
h2. Getting started | ||
|
||
To install, add @"com.typesafe.play.plugins" %% "play-statsd" % "2.1.0"@ to your dependencies, for example: | ||
|
||
bc.. val appDendencies = Seq(@"com.typesafe.play.plugins" %% "play-statsd" % "2.1.0") | ||
|
||
h2. Configuration | ||
|
||
The following are configuration flags that belong in @conf/application.conf@: | ||
|
||
* @statsd.enabled@: Should be @true@ to use this module. Can be @false@ for testing. | ||
* @statsd.stat.prefix@: The prefix for all stats sent by this app. They will appear in a folder of the same name on graphite. | ||
* @statsd.host@: The hostname of the statsd server. | ||
* @statsd.port@: The port for the statsd server. | ||
|
||
p(note). If there are any configuration problems (missing or unparseable settings), there will be a warning the first time the module is used but will not cause an error in your app. | ||
|
||
h2. Scala Usage | ||
|
||
To use this module, first add this import: | ||
|
||
@import play.modules.statsd.api.Statsd@ | ||
|
||
Now you can call it like this: | ||
|
||
bc.. Statsd.increment("my.stat") // Increment my.stat by 1 | ||
Statsd.increment("my.bigger.stat", value = 100) // Increment my.bigger.stat by 100 | ||
Statsd.increment("my.frequent.stat", samplingRate = 0.1) // Increment my.frequent.stat 10% of the time | ||
Statsd.timing("my.operation", 100) // my.operation took 100 ms | ||
Statsd.timing("my.frequent.operation", 10, 0.5) // my operation took 50 ms. Send this stat 50% of the time | ||
Statsd.time("my.operation.i.dont.want.to.time.myself") { | ||
// do some stuff... | ||
} // This will get timed automatically. | ||
Statsd.gauge("my.value", 42) // Record 42 for my.value | ||
|
||
p(note). Any errors will be logged, but will not cause the app to fail. | ||
|
||
h2. Java Usage | ||
|
||
To use this module, first add this import: | ||
|
||
@import play.modules.statsd.Statsd;@ | ||
|
||
Now you can call it like this: | ||
|
||
bc.. Statsd.increment("my.stat"); // Increment my.stat by 1 | ||
Statsd.increment("my.bigger.stat", 100); // Increment my.bigger.stat by 100 | ||
Statsd.increment("my.frequent.stat", 0.1); // Increment my.frequent.stat 10% of the time | ||
Statsd.timing("my.operation", 100); // my.operation took 100 ms | ||
Statsd.timing("my.frequent.operation", 10, 0.5); // my operation took 50 ms. Send this stat 50% of the time | ||
String result = Statsd.time("my.operation.i.dont.want.to.time.myself", new F.Function0<String>() { | ||
public String apply() { | ||
return "some result"; | ||
}}); // This will get timed automatically. | ||
Statsd.gauge("my.value", 42L) // Record 42 for my.value | ||
|
||
p(note). Any errors will be logged, but will not cause the app to fail. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
import sbt._ | ||
import sbt.Keys._ | ||
import scala.Some | ||
|
||
object StatsdBuild extends Build { | ||
|
||
val buildVersion = "2.1.0-SNAPSHOT" | ||
val playVersion = "2.1-SNAPSHOT" | ||
|
||
val typesafeSnapshot = "Typesafe Snapshots Repository" at "http://repo.typesafe.com/typesafe/snapshots/" | ||
val typesafe = "Typesafe Repository" at "http://repo.typesafe.com/typesafe/releases/" | ||
val repo = if (buildVersion.endsWith("SNAPSHOT")) typesafeSnapshot else typesafe | ||
|
||
lazy val root = Project(id = "play-statsd", base = file("."), settings = Project.defaultSettings).settings( | ||
version := buildVersion, | ||
scalaVersion := "2.10.0", | ||
publishTo <<= (version) { version: String => | ||
val nexus = "http://typesafe.artifactoryonline.com/typesafe/" | ||
if (version.trim.endsWith("SNAPSHOT")) Some("snapshots" at nexus + "maven-snapshots/") | ||
else Some("releases" at nexus + "maven-releases/") | ||
}, | ||
organization := "com.typesafe.play.plugins", | ||
resolvers += repo, | ||
libraryDependencies ++= Seq( | ||
"play" %% "play" % playVersion % "provided", | ||
"play" %% "play-test" % playVersion % "test" | ||
), | ||
parallelExecution in test := false, | ||
testOptions += Tests.Argument(TestFrameworks.JUnit, "-q", "-v") | ||
) | ||
|
||
lazy val sample = play.Project(name = "play-statsd-sample", path = file("sample/sample-statsd")).settings( | ||
Keys.fork in test := false | ||
).dependsOn(root).aggregate(root) | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
sbt.version=0.12.2 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
// Comment to get more information during initialization | ||
// logLevel := Level.Warn | ||
|
||
// The Typesafe repository | ||
resolvers += "Typesafe repository" at "http://repo.typesafe.com/typesafe/releases/" | ||
|
||
// Use the Play sbt plugin for Play projects | ||
addSbtPlugin("play" % "sbt-plugin" % "2.1-SNAPSHOT") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
import play.api.mvc.WithFilters | ||
import play.modules.statsd.api.StatsdFilter | ||
|
||
object Global extends WithFilters(new StatsdFilter) |
52 changes: 52 additions & 0 deletions
52
statsd/sample/sample-statsd/app/controllers/Application.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
package controllers | ||
|
||
import play.api.mvc._ | ||
import play.api.libs.concurrent.Execution.Implicits._ | ||
import scala.concurrent.Future | ||
|
||
object Application extends Controller { | ||
def index = Action { | ||
Thread.sleep(2) | ||
Ok | ||
} | ||
|
||
def singleParam(p: String) = Action { | ||
Thread.sleep(2) | ||
Ok | ||
} | ||
|
||
def twoParams(p1: String, p2: String) = Action { | ||
Thread.sleep(2) | ||
Ok | ||
} | ||
|
||
def async = Action { | ||
Async { | ||
Future { | ||
Thread.sleep(2) | ||
Ok | ||
} | ||
} | ||
} | ||
|
||
def syncFailure = Action { | ||
Thread.sleep(2) | ||
if (true) throw new RuntimeException | ||
Ok | ||
} | ||
|
||
def asyncFailure = Action { | ||
Async { | ||
Future { | ||
Thread.sleep(2) | ||
if (true) throw new RuntimeException | ||
Ok | ||
} | ||
} | ||
} | ||
|
||
def error = Action { | ||
Thread.sleep(2) | ||
ServiceUnavailable | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
|
||
application.secret="h:ueBbICA:P37;UIGe5ib>VSN@14UAag/e6t>_AtZ[uiZ?Ae/]QTLN?vfSSyOv2" | ||
application.langs="en" | ||
|
||
statsd.enabled=true | ||
statsd.host=localhost | ||
statsd.port=8125 | ||
statsd.stat.prefix=statsd-sample |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
|
||
GET / controllers.Application.index | ||
GET /foo/bar controllers.Application.index | ||
GET /single/end/:param controllers.Application.singleParam(param) | ||
GET /single/middle/:param/f controllers.Application.singleParam(param) | ||
GET /regex/$param<[0-9]+> controllers.Application.singleParam(param) | ||
GET /rest/*param controllers.Application.singleParam(param) | ||
GET /multiple/:param1/:param2 controllers.Application.twoParams(param1, param2) | ||
# @statsd.key custom.key | ||
GET /key/in/comments controllers.Application.index | ||
GET /async controllers.Application.async | ||
GET /sync/failure controllers.Application.syncFailure | ||
GET /async/failure controllers.Application.asyncFailure | ||
GET /error controllers.Application.error |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,162 @@ | ||
package test | ||
|
||
import java.net.{SocketTimeoutException, DatagramPacket, DatagramSocket} | ||
import org.specs2.mutable._ | ||
import play.api.test.Helpers._ | ||
import play.api.test._ | ||
import org.specs2.execute.Result | ||
import collection.mutable.ListBuffer | ||
import play.api.libs.ws.WS | ||
import concurrent.Await | ||
import concurrent.duration.Duration | ||
|
||
object IntegrationTestSpec extends Specification { | ||
"statsd filters" should { | ||
|
||
"report stats on /" in new Setup { | ||
makeRequest("/") | ||
receive(count("sample.routes.get"), timing("sample.routes.get"), combinedTime, combinedSuccess, combined200) | ||
} | ||
|
||
"report stats on simple path" in new Setup { | ||
makeRequest("/foo/bar") | ||
receive(count("sample.routes.foo.bar.get"), timing("sample.routes.foo.bar.get"), combinedTime, combinedSuccess, combined200) | ||
} | ||
|
||
"report stats on path with dynamic end" in new Setup { | ||
makeRequest("/single/end/blah") | ||
receive(count("sample.routes.single.end.param.get"), timing("sample.routes.single.end.param.get"), combinedTime, combinedSuccess, combined200) | ||
} | ||
|
||
"report stats on path with dynamic middle" in new Setup { | ||
makeRequest("/single/middle/blah/f") | ||
receive(count("sample.routes.single.middle.param.f.get"), timing("sample.routes.single.middle.param.f.get"), combinedTime, combinedSuccess, combined200) | ||
} | ||
|
||
"report stats on path with regex" in new Setup { | ||
makeRequest("/regex/21") | ||
receive(count("sample.routes.regex.param.get"), timing("sample.routes.regex.param.get"), combinedTime, combinedSuccess, combined200) | ||
} | ||
|
||
"report stats on path with wildcard" in new Setup { | ||
makeRequest("/rest/blah/blah") | ||
receive(count("sample.routes.rest.param.get"), timing("sample.routes.rest.param.get"), combinedTime, combinedSuccess, combined200) | ||
} | ||
|
||
"report stats on path with multiple params" in new Setup { | ||
makeRequest("/multiple/foo/bar") | ||
receive(count("sample.routes.multiple.param1.param2.get"), timing("sample.routes.multiple.param1.param2.get"), combinedTime, combinedSuccess, combined200) | ||
} | ||
|
||
"report stats on async action" in new Setup { | ||
makeRequest("/async") | ||
receive(count("sample.routes.async.get"), timing("sample.routes.async.get"), combinedTime, combinedSuccess, combined200) | ||
} | ||
|
||
"report stats on failure" in new Setup { | ||
makeWsRequest("/sync/failure") | ||
receive(count("sample.routes.sync.failure.get"), timing("sample.routes.sync.failure.get"), combinedTime, combinedError, combined500) | ||
} | ||
|
||
"report stats on failure thrown in async" in new Setup { | ||
makeWsRequest("/async/failure") | ||
receive(count("sample.routes.async.failure.get"), timing("sample.routes.async.failure.get"), combinedTime, combinedError, combined500) | ||
} | ||
|
||
"report stats on action returning 503" in new Setup { | ||
makeRequest("/error", 503) | ||
receive(count("sample.routes.error.get"), timing("sample.routes.error.get"), combinedTime, combinedError, combined503) | ||
} | ||
|
||
"report stats on handlerNotFound" in new Setup { | ||
makeWsRequest("/does/not/exist", 404) | ||
receive(count("sample.routes.combined.handlerNotFound"), timing("sample.routes.combined.handlerNotFound", 0), timing("sample.routes.combined.time", 0), combinedSuccess, combined404) | ||
} | ||
|
||
} | ||
|
||
def makeRequest(path: String, expectedStatus: Int = 200) { | ||
status(route(FakeRequest("GET", path)).get) must_== expectedStatus | ||
} | ||
|
||
def makeWsRequest(path: String, expectedStatus: Int = 500) { | ||
Await.result(WS.url("http://localhost:9001" + path).get(), Duration.apply("2s")).status must_== expectedStatus | ||
} | ||
|
||
trait Setup extends Around { | ||
lazy val PORT = 57476 | ||
implicit lazy val fakeApp = FakeApplication(additionalConfiguration = Map( | ||
"statsd.enabled" -> "true", | ||
"statsd.host" -> "localhost", | ||
"statsd.port" -> PORT.toString, | ||
"statsd.stat.prefix" -> "sample")) | ||
lazy val mockStatsd = { | ||
val socket = new DatagramSocket(PORT) | ||
socket.setSoTimeout(2000) | ||
socket | ||
} | ||
|
||
def receive(ps: PartialFunction[String, Unit]*) = { | ||
val expects = ListBuffer(ps :_*) | ||
for (i <- 1 until ps.size + 1) { | ||
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 no " + i + " within 2s") | ||
} | ||
val data = new String(packet.getData, 0, packet.getLength) | ||
val matched = expects.collectFirst { | ||
case expect if expect.isDefinedAt(data) => { | ||
expects -= expect | ||
expect(data) | ||
} | ||
} | ||
matched aka("No matching assertion for data: '%s' ".format(data)) must beSome[Unit] | ||
} | ||
|
||
expects must beEmpty | ||
} | ||
|
||
|
||
def around[T](t: => T)(implicit evidence$1: (T) => Result) = running(TestServer(9001, fakeApp)) { | ||
mockStatsd | ||
try { | ||
t | ||
} finally { | ||
mockStatsd.close() | ||
} | ||
} | ||
} | ||
|
||
def combinedSuccess = count("sample.routes.combined.success") | ||
def combinedError = count("sample.routes.combined.error") | ||
def combined200 = count("sample.routes.combined.200") | ||
def combined404 = count("sample.routes.combined.404") | ||
def combined500 = count("sample.routes.combined.500") | ||
def combined503 = count("sample.routes.combined.503") | ||
def combinedTime = timing("sample.routes.combined.time") | ||
|
||
def count(key: String): PartialFunction[String, Unit] = { | ||
case Count(k) if k == key => Unit | ||
} | ||
|
||
def timing(key: String, atLeast: Int = 2): PartialFunction[String, Unit] = { | ||
case Timing(k, time) if k == key => time must beGreaterThanOrEqualTo(atLeast) | ||
} | ||
|
||
object Timing { | ||
val regex = """([^:]+):([0-9]+)\|ms""".r | ||
def unapply(s: String): Option[(String, Int)] = { | ||
s match { | ||
case regex(key, millis) => Some((key, millis.toInt)) | ||
case _ => None | ||
} | ||
} | ||
} | ||
|
||
val Count = """([^:]+):1\|c""".r | ||
|
||
} |
Oops, something went wrong.