Posted by & filed under Developers.

We’re using Spray for some products here at Primal. Spray is a really neat library, but it’s missing the ability to gather runtime metrics, and while you can instrument your app any way you’d like, we want to do things the Spray way.

Along with our upcoming Agent framework, which implements a pretty cool inter-actor protocol, we have other services that will be hosted in Spray. The guys that figure out whether or not people like our features, and the other guys that figure out how well they’re working, want to know what’s happening, and the guys that code it don’t want that stuff interfering too much with the way that they write their code.

In this post we’ll have a look at how we’re integrating the Coda Hale Metrics library with Spray. Eventually we’ll be making a pull request to the Spray project to integrate this thing for real.

The Goal

Spray has created a pretty serious routing DSL that’s also highly extensible. Our goal is to integrate the metrics library as a Directive in this DSL, so that can write something like this:


path("milk") {
get {
measure {
complete(/* Get some milk */)
}
}
}

In the DSL we’ll add the ability to measure the calls to GET /milk with a simple directive.

The How

One of the interesting things about Spray is that its routing DSL follows a very functional design. In fact, it’s a highly flexible, and really quite awesome design; the routing DSL creates a lattice of functions through which the request, response and events flow. Our main job as integrators is to define functions that can be applied through the route from request to response.

We’re going to do this by creating an instance of a Directive0 that can process the fully HTTP operation.

BasicDirectives.around

The routing DSL provides us with the BasicDirectives trait that contains a number of methods we can use to construct new directives, and to it we’re going to add:

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)) }
  }

The around directive was suggested by Mathias Sirthias. With it we can change how we measure things based on the success or failure of the overall request. Let’s deconstruct it:

  • The argument to around is a function, which accepts a RequestContext (for all intents and purposes, “the request”).
  • The function (called before) must return a Tuple2, containing a (possibly transformed) RequestContext and a new function that transforms an Any to an Any.
  • It’s this “Any transformer” that’s the really interesting bit; it gets bound to after.
  • The Any parameter that the after function receives is potentially the HttpResponse, but it could also be some sort of failure.
  • In either case, the after function returns either what it was given or some sort of transformation thereof.

How is this achieved? We owe this bit of awesome to two key things.

  1. The BasicDirective‘s mapInnerRoute method.
  2. The withRouteResponseMapped method of the RequestContext

The mapInnerRoute is provided by BasicDirectives, which provides this and many other functions that create instances of Directive0. Directives aren’t exactly functions but it’s easy to think of them that way. So, mapInnerRoute gives us a function that will allow us to obtain the RequestContext and operate on it by operating on the overall Route that’s been provided.

Operating on the RequestContext isn’t enough; we need to operate on the corresponding result (either successful or otherwise). That result is “attached” to the RequestContext, which is where the withRouteResponseMapped method comes in; it allows us to attach our after function to the response. The last part of the equation is dealing with Failures, which we handle with the try / catch.

Adding a Timer

One of the coolest features of Coda Hale’s Metrics library is the Timer. Implementing one, given the existence of around, is pretty simple.

import com.codahale.metrics.{ MetricRegistry, Timer }

case class TimerMetric(timerName: String, metricRegistry: MetricRegistry) {
  import spray.routing.directives.BasicDirectives.around

  def buildAfter(timerContext: Timer.Context): Any => Any = { possibleRsp: Any =>
    possibleRsp match {
      case _ =>
        timerContext.stop()
    }
    possibleRsp
  }

  val time: Directive0 =
    around { ctx =>
      val timerContext = metricRegistry.timer(timerName).time()
      (ctx, buildAfter(timerContext))
    }
}

The time value is the directive; it takes on the value returned by around. The function given to around grabs the instance of Timer.Context for the given timer, which is basically the whole point.

The creation of the after function is handled by buildAfter, which closes over the instance of Timer.Context and stops it when the result comes in, no matter what that result is.

The before and after functions return their initial arguments unchanged allowing the update of the timer to be a side effect.

Usage

The actual implementation has a lot more to it, of course, but that’s the basic idea. However, the existing timer metric that we’ve built is still useful, even in this primitive form. Let’s give it a go:

val metricRegistry = new MetricRegistry()
val timed = TimerMetric("someTimer", metricRegistry).time
val route = path("something") {
  get {
    timed {
      complete(/* do something */)
    }
  }
}

And there you go.

Where is it?

I forked the Spray repository a little while ago in order to add metrics support. You can find my implementation in my personal repository, but with luck it’s already in Spray’s production release, or will be soon.

Getting Started

Developers, check out our developers site to get started.

And if you’re interested in working with us directly, we’re hiring!

Recommended for you
Powered by

Comments are closed.