Timing spray total request response time
So you wish to measure how much the spray request-response
cycle takes; you also wish to add it to your metrics
set. You have come to the right place, here is the complex
recipe for achieving that. (unfortunately there is no simple recipe it’s going to be damn complex), that’s life with spray, they taste both delicious and bad this is the effect of scala
+ DSL
+ reactive
+ continuations
- complex coding at it’s top glory. But in anyway its a good mind bending practice so the neurons won’t die. Note we are using codahale-metrics as our metrics
framework.
The routing directive
This is the routing directive we wish to measure
val helloRoute = path(“/hello”) { get { complete(“time this from request to response please (and hello to you!)”) } }
Create metric
val requestResponseTimer = RequestResponseTimedMetric(“spray.request.response.timer”, metricRegistry).time
TimedMetric
, warning, magic behind that, we are going to code that class below.
Update code to use the timed metric
val helloRoute = path(“/hello”) { get { requestResponseTimer { complete(“time this from request to response please (and hello to you!)”) } } }
RequestResponseTimedMetric explained
case class TimerMetric(timerName: String, metricRegistry: MetricRegistry) { val time: Directive0 = around { ctx => val timerContext = metricRegistry.timer(timerName).time() val startTime = System.currentTimeMillis() (ctx, buildAfter(timerContext, startTime)) }
def buildAfter(timerContext: Timer.Context, startTime: Long): Any => Any = { possibleRsp: Any =>
possibleRsp match {
case _ =>
timerContext.stop()
trace(durationMarker, s"$timerName duration of ${System.currentTimeMillis() - startTime}}")
}
possibleRsp
}
}
def around(before: RequestContext => (RequestContext, Any => Any)): Directive0 = mapInnerRoute { inner => ctx => val (ctxForInnerRoute, after) = before(ctx) try inner(ctxForInnerRoute.withRouteResponseMapped(after)) catch { case NonFatal(ex) => after(Failure(ex)) } }
TimedMetric
is just acase class
which receives the timer name and themetricsRegistry
TimerMetric.time
returns afunction
because it returns a call toaround
andaround
is a function frombefore: RequestContext
to the output which is(RequestContext, Any => Any)
.- Note the output has the
RequestContext
because you wish to have therequest
when handling response that would be nice. - Note the output outcome is
Any => Any
which means it’s just a general function whatever you coded to handle the request. around
then runsbefore
on context theninner
thenafter
its a general wrapper.TimerMetric.time
calls thearound
and as a code block to execute it passes thetimer
starting and stopping.- So when you called TimerMetric.time you are passing it a
block
to run because.time
runsaround
andaround
receives acode block
and the code block you are passing to it is the actual code to run. However.time
does not only run that code it also creates the actualmetrics
timer initializes it records thestartTime
and creates another block to be run theafter
which is the `buildAfter(timerContext, startTime) - The
around
knows to run thatafter
code block after the whole run of the code block to run has finishes, by havingctxForInnerRoute.withRouteResponseMapped(after)