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)) } }
TimedMetricis just acase classwhich receives the timer name and themetricsRegistryTimerMetric.timereturns afunctionbecause it returns a call toaroundandaroundis a function frombefore: RequestContextto the output which is(RequestContext, Any => Any).- Note the output has the
RequestContextbecause you wish to have therequestwhen handling response that would be nice. - Note the output outcome is
Any => Anywhich means it’s just a general function whatever you coded to handle the request. aroundthen runsbeforeon context theninnerthenafterits a general wrapper.TimerMetric.timecalls thearoundand as a code block to execute it passes thetimerstarting and stopping.- So when you called TimerMetric.time you are passing it a
blockto run because.timerunsaroundandaroundreceives acode blockand the code block you are passing to it is the actual code to run. However.timedoes not only run that code it also creates the actualmetricstimer initializes it records thestartTimeand creates another block to be run theafterwhich is the `buildAfter(timerContext, startTime) - The
aroundknows to run thataftercode block after the whole run of the code block to run has finishes, by havingctxForInnerRoute.withRouteResponseMapped(after)