This repository has been archived by the owner on Jan 12, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 21
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Initial commit, three unit tested filters, one controller
- Loading branch information
0 parents
commit 1fe4b6c
Showing
16 changed files
with
387 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,8 @@ | ||
logs | ||
target | ||
/.idea | ||
/.idea_modules | ||
/.classpath | ||
/.project | ||
/.settings | ||
/RUNNING_PID |
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,56 @@ | ||
#Play prometheus play.prometheus.filters | ||
|
||
This play library provides three types of filters that collect prometheus metrics. | ||
|
||
##The filters | ||
|
||
####Request counter | ||
This filter counts all the requests in your application and adds a label for the status | ||
|
||
####Latency filter | ||
This filter collects the latency of all requests | ||
|
||
####Route Action Method Latency Filter | ||
This filter collects the latency for all requests and adds a label call RouteActionMethod. | ||
This action method is the method name of the method you provided your routes file. | ||
This filter makes it possible to measure the latency for all your routes. | ||
|
||
Example: | ||
|
||
``` | ||
GET /metrics play.prometheus.controllers.PrometheusController.getMetrics | ||
``` | ||
|
||
The RouteActionMethod for the above example would be getMetrics | ||
|
||
##How to enable the filters | ||
See the [documentation of play](https://www.playframework.com/documentation/2.5.x/ScalaHttpFilters#Using-filters) | ||
|
||
You should make a filters class: | ||
|
||
```scala | ||
import javax.inject.Inject | ||
import play.api.http.DefaultHttpFilters | ||
import play.prometheus.filters.LatencyFilter | ||
import play.prometheus.filters.StatusCounterFilter | ||
|
||
class MyFilters @Inject() ( | ||
latencyFilter: LatencyFilter, | ||
statusCounterFilter: StatusCounterFilter | ||
) extends DefaultHttpFilters(latencyFilter, statusCounterFilter) | ||
``` | ||
|
||
And the enable this filter in the application.conf | ||
|
||
```$xslt | ||
play.http.filters=com.example.MyFilters | ||
``` | ||
|
||
##Prometheus controller | ||
The project also provides a prometheus controller with a get metric method. If you add the following to your routes file: | ||
|
||
``` | ||
GET /metrics play.prometheus.controllers.PrometheusController.getMetrics | ||
``` | ||
|
||
You should be able to immediately get the metrics |
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,11 @@ | ||
package play.prometheus | ||
|
||
import com.google.inject.AbstractModule | ||
import io.prometheus.client.CollectorRegistry | ||
|
||
class PrometheusModule extends AbstractModule { | ||
|
||
override def configure(): Unit = { | ||
bind(classOf[CollectorRegistry]).toInstance(CollectorRegistry.defaultRegistry) | ||
} | ||
} |
25 changes: 25 additions & 0 deletions
25
app/play/prometheus/controllers/PrometheusController.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,25 @@ | ||
package play.prometheus.controllers | ||
|
||
import akka.util.ByteString | ||
import com.google.inject.Inject | ||
import io.prometheus.client.CollectorRegistry | ||
import io.prometheus.client.exporter.common.TextFormat | ||
import play.api.http.HttpEntity | ||
import play.api.mvc._ | ||
import play.prometheus.utils.WriterAdapter | ||
|
||
class PrometheusController @Inject()(registry: CollectorRegistry) extends Controller { | ||
|
||
def getMetrics = Action { | ||
val samples = new StringBuilder() | ||
val writer = new WriterAdapter(samples) | ||
TextFormat.write004(writer, registry.metricFamilySamples()) | ||
writer.close() | ||
|
||
Result( | ||
header = ResponseHeader(200, Map.empty), | ||
body = HttpEntity.Strict(ByteString(samples.toString), Some(TextFormat.CONTENT_TYPE_004)) | ||
) | ||
} | ||
|
||
} |
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,28 @@ | ||
package play.prometheus.filters | ||
|
||
import akka.stream.Materializer | ||
import com.google.inject.{Inject, Singleton} | ||
import io.prometheus.client.{CollectorRegistry, Histogram} | ||
import play.api.mvc.{Filter, RequestHeader, Result} | ||
|
||
import scala.concurrent.{ExecutionContext, Future} | ||
|
||
@Singleton | ||
class LatencyFilter @Inject()(registry: CollectorRegistry) (implicit val mat: Materializer, ec: ExecutionContext) extends Filter { | ||
|
||
private[filters] val requestLatency = Histogram.build | ||
.name("requests_latency_seconds") | ||
.help("Request latency in seconds.") | ||
.register(registry) | ||
|
||
def apply(nextFilter: RequestHeader => Future[Result]) | ||
(requestHeader: RequestHeader): Future[Result] = { | ||
|
||
val requestTimer = requestLatency.startTimer | ||
nextFilter(requestHeader).map { result => | ||
requestTimer.observeDuration() | ||
result | ||
} | ||
} | ||
|
||
} |
29 changes: 29 additions & 0 deletions
29
app/play/prometheus/filters/RouteActionMethodLatencyFilter.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,29 @@ | ||
package play.prometheus.filters | ||
|
||
import akka.stream.Materializer | ||
import com.google.inject.{Inject, Singleton} | ||
import io.prometheus.client.{CollectorRegistry, Histogram} | ||
import play.api.mvc.{Filter, RequestHeader, Result} | ||
import play.api.routing.Router.Tags | ||
|
||
import scala.concurrent.{ExecutionContext, Future} | ||
|
||
@Singleton | ||
class RouteActionMethodLatencyFilter @Inject()(registry: CollectorRegistry) (implicit val mat: Materializer, ec: ExecutionContext) extends Filter { | ||
|
||
private[filters] val requestLatency = Histogram.build | ||
.name("requests_latency_seconds") | ||
.help("Request latency in seconds.") | ||
.labelNames("RouteActionMethod") | ||
.register(registry) | ||
|
||
def apply(nextFilter: RequestHeader => Future[Result]) | ||
(requestHeader: RequestHeader): Future[Result] = { | ||
val requestTimer = requestLatency.labels(requestHeader.tags(Tags.RouteActionMethod)).startTimer | ||
nextFilter(requestHeader).map { result => | ||
requestTimer.observeDuration() | ||
result | ||
} | ||
} | ||
|
||
} |
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,29 @@ | ||
package play.prometheus.filters | ||
|
||
import javax.inject.Inject | ||
|
||
import akka.stream.Materializer | ||
import com.google.inject.Singleton | ||
import io.prometheus.client.{CollectorRegistry, Counter} | ||
import play.api.mvc.{Filter, RequestHeader, Result} | ||
|
||
import scala.concurrent.{ExecutionContext, Future} | ||
|
||
@Singleton | ||
class StatusCounterFilter @Inject()(registry: CollectorRegistry) (implicit val mat: Materializer, ec: ExecutionContext) extends Filter { | ||
|
||
private[filters] val requestCounter = Counter.build() | ||
.name("http_requests_total") | ||
.help("Total amount of requests") | ||
.labelNames("status") | ||
.register(registry) | ||
|
||
def apply(nextFilter: RequestHeader => Future[Result]) | ||
(requestHeader: RequestHeader): Future[Result] = { | ||
|
||
nextFilter(requestHeader).map { result => | ||
requestCounter.labels(result.header.status.toString).inc() | ||
result | ||
} | ||
} | ||
} |
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 @@ | ||
package play.prometheus.utils | ||
|
||
import java.io.Writer | ||
|
||
class WriterAdapter(buffer: StringBuilder) extends Writer { | ||
|
||
override def write(charArray: Array[Char], offset: Int, length: Int): Unit = { | ||
buffer ++= new String(new String(charArray, offset, length).getBytes("UTF-8"), "UTF-8") | ||
} | ||
|
||
override def flush(): Unit = {} | ||
|
||
override def close(): Unit = {} | ||
} |
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,18 @@ | ||
name := """play-prometheus-play.prometheus.filters""" | ||
|
||
version := "1.0-SNAPSHOT" | ||
|
||
lazy val root = (project in file(".")) | ||
.enablePlugins(PlayScala) | ||
|
||
scalaVersion := "2.11.11" | ||
|
||
libraryDependencies ++= Seq( | ||
"io.prometheus" % "simpleclient" % "0.0.23", | ||
"io.prometheus" % "simpleclient_servlet" % "0.0.23" | ||
) | ||
|
||
libraryDependencies ++= Seq( | ||
"org.scalatestplus.play" %% "scalatestplus-play" % "2.0.0" % Test, | ||
"org.mockito" % "mockito-core" % "2.7.22" % Test | ||
) |
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 @@ | ||
play.modules.enabled += play.prometheus.PrometheusModule |
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.13.15 |
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,2 @@ | ||
// The Play plugin | ||
addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.5.15") |
33 changes: 33 additions & 0 deletions
33
test/play/prometheus/controllers/PrometheusControllerSpec.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,33 @@ | ||
package play.prometheus.controllers | ||
|
||
import java.util.Collections | ||
|
||
import io.prometheus.client.Collector.MetricFamilySamples | ||
import io.prometheus.client.{Collector, CollectorRegistry} | ||
import org.mockito.Mockito._ | ||
import org.scalatest.mockito.MockitoSugar | ||
import org.scalatestplus.play.PlaySpec | ||
import play.api.mvc.Results | ||
import play.api.test.FakeRequest | ||
import play.api.test.Helpers._ | ||
|
||
|
||
class PrometheusControllerSpec extends PlaySpec with Results with MockitoSugar { | ||
|
||
"Get metrics method" should { | ||
"Return the prometheus metrics" in { | ||
val collectorRegistry = mock[CollectorRegistry] | ||
val metricsFamilySample = new MetricFamilySamples("test", Collector.Type.COUNTER, "help", Collections.emptyList()) | ||
when(collectorRegistry.metricFamilySamples()).thenReturn(new java.util.Vector(Collections.singleton(metricsFamilySample)).elements) | ||
|
||
val client = new PrometheusController(collectorRegistry) | ||
|
||
val request = FakeRequest(GET, "/metrics") | ||
|
||
val result = client.getMetrics.apply(request) | ||
status(result) mustBe OK | ||
contentAsString(result) mustBe "# HELP test help\n# TYPE test counter\n" | ||
} | ||
} | ||
|
||
} |
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,43 @@ | ||
package play.prometheus.filters | ||
|
||
import akka.stream.Materializer | ||
import io.prometheus.client.CollectorRegistry | ||
import org.mockito.ArgumentMatchers._ | ||
import org.mockito.Mockito._ | ||
import org.scalatest.mockito.MockitoSugar | ||
import org.scalatest.{MustMatchers, WordSpec} | ||
import org.scalatestplus.play.guice.GuiceOneAppPerSuite | ||
import play.api.libs.concurrent.Execution.Implicits._ | ||
import play.api.mvc._ | ||
import play.api.test.{DefaultAwaitTimeout, FakeRequest, FutureAwaits} | ||
|
||
class LatencyFilterSpec extends WordSpec with MustMatchers with MockitoSugar with Results with DefaultAwaitTimeout with FutureAwaits with GuiceOneAppPerSuite { | ||
|
||
"Filter constructor" should { | ||
"Add a histogram to the prometheus registry" in { | ||
val collectorRegistry = mock[CollectorRegistry] | ||
new LatencyFilter(collectorRegistry)(mock[Materializer], defaultContext) | ||
verify(collectorRegistry).register(any()) | ||
} | ||
} | ||
|
||
"Apply method" should { | ||
"Measure the latency" in { | ||
implicit val mat = app.materializer | ||
val filter = new LatencyFilter(mock[CollectorRegistry]) | ||
val rh = FakeRequest() | ||
val action = Action(Ok("success")) | ||
|
||
await(filter(action)(rh).run()) | ||
|
||
val metrics = filter.requestLatency.collect() | ||
metrics must have size 1 | ||
val samples = metrics.get(0).samples | ||
//this is the count sample | ||
val countSample = samples.get(samples.size() - 2) | ||
countSample.value mustBe 1.0 | ||
countSample.labelValues must have size 0 | ||
} | ||
} | ||
|
||
} |
47 changes: 47 additions & 0 deletions
47
test/play/prometheus/filters/RouteActionMethodLatencyFilterSpec.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,47 @@ | ||
package play.prometheus.filters | ||
|
||
import akka.stream.Materializer | ||
import io.prometheus.client.CollectorRegistry | ||
import org.mockito.ArgumentMatchers.any | ||
import org.mockito.Mockito.verify | ||
import org.scalatest.mockito.MockitoSugar | ||
import org.scalatest.{MustMatchers, WordSpec} | ||
import org.scalatestplus.play.guice.GuiceOneAppPerSuite | ||
import play.api.libs.concurrent.Execution.Implicits.defaultContext | ||
import play.api.mvc.Action | ||
import play.api.test.{DefaultAwaitTimeout, FakeRequest, FutureAwaits} | ||
import play.api.mvc._ | ||
import play.api.routing.Router.Tags | ||
|
||
class RouteActionMethodLatencyFilterSpec extends WordSpec with MustMatchers with MockitoSugar with Results with DefaultAwaitTimeout with FutureAwaits with GuiceOneAppPerSuite { | ||
|
||
private implicit val mat = app.materializer | ||
|
||
"Filter constructor" should { | ||
"Add a histogram to the prometheus registry" in { | ||
val collectorRegistry = mock[CollectorRegistry] | ||
new RouteActionMethodLatencyFilter(collectorRegistry) | ||
verify(collectorRegistry).register(any()) | ||
} | ||
} | ||
|
||
"Apply method" should { | ||
"Measure the latency" in { | ||
val filter = new RouteActionMethodLatencyFilter(mock[CollectorRegistry]) | ||
val rh = FakeRequest().withTag(Tags.RouteActionMethod, "test") | ||
val action = Action(Ok("success")) | ||
|
||
await(filter(action)(rh).run()) | ||
|
||
val metrics = filter.requestLatency.collect() | ||
metrics must have size 1 | ||
val samples = metrics.get(0).samples | ||
//this is the count sample | ||
val countSample = samples.get(samples.size() - 2) | ||
countSample.value mustBe 1.0 | ||
countSample.labelValues must have size 1 | ||
countSample.labelValues.get(0) mustBe "test" | ||
} | ||
} | ||
|
||
} |
Oops, something went wrong.