1

Is there a simple way to get the response time for a request to a url (other than keeping track of time in the code separately)?

import dispatch._, Defaults._
import scala.util.{Failure, Success}

val svc = dispatch.url(url)
  val response: Future[com.ning.http.client.Response] = Http(svc > (x => x))
  response onComplete {
    case Success(content) => {
      println(s"SUCCESS: ${content.getStatusCode()} for $url")
      //how long did this take??
 }
    case Failure(t) => {
      println(s"ERROR: timeout/failure for $url")
    }
  }
Urist McDev
  • 498
  • 3
  • 14
FelixHJ
  • 1,071
  • 3
  • 12
  • 26

2 Answers2

1

This is a different approach for computing the elapsed time, might need a bit more customization (like handling of failure, NonFatal most probably), but I think this is a bit more idiomatic. It works only with Futures though.

First create a class for the result with time (could be replaced with a tuple too) and one for the failure with time.

import scala.concurrent.Future
import scala.concurrent.ExecutionContext.Implicits.global

case class ResultWithTime[ResultType](val result: ResultType, val time: Long)

class FailureWithTime(failure: Throwable, val time: Long) extends Throwable(failure)

Then create a method that wraps the future to execute and handles the case when it fails (might use Promise if you do not like the Future's map and recoverWith methods (as they require an implicit ExecutionContext)).

object TimerFuture {
  def apply[ResultType](fut: =>Future[ResultType]): Future[ResultWithTime[ResultType]] = {
    val startTime = System.currentTimeMillis
    fut.map(r => ResultWithTime(r, System.currentTimeMillis - startTime))
       .recoverWith{case t:Throwable =>
         Future.failed(new FailureWithTime(t, System.currentTimeMillis - startTime))
         }
  }
}

Here is an example how to use it:

import scala.concurrent.Future
import scala.concurrent.ExecutionContext.Implicits.global
import scala.io.StdIn


object Y extends App {
   TimerFuture(Future{Thread.sleep(200); "Hello"})
     .onSuccess({case ResultWithTime(r, t) => println(s"Took $t ms to get: $r")})
   TimerFuture(Future{Thread.sleep(100); throw new NullPointerException()})
     .onFailure({case f: FailureWithTime => println(s"Took ${f.time} ms to get: ${f.getCause}")})
   StdIn.readLine()
}
Gábor Bakos
  • 8,982
  • 52
  • 35
  • 52
  • I should add if you do not like this implementation, [rapture-core](https://github.com/propensive/rapture-core)'s modes provide an alternative using `implicit val handler = modes.returnFuture compose modes.timeExecution`. – Gábor Bakos Jul 14 '15 at 18:13
  • Where could I find a complete code example for doing this with rapture-core? I tried the examples from the readlme, but I couldn't get them to work – Emil D Sep 08 '15 at 20:34
  • @EmilD I think the example above should work for the call site. Or you mean [how to write a method](https://github.com/propensive/rapture-core#writing-methods-to-use-modes) that could be used this way? Or [wrap existing methods](https://github.com/propensive/rapture-core#conveniences-for-existing-methods)? – Gábor Bakos Sep 09 '15 at 07:12
0

You can wrap your code with something like this (not tested):

class Timer[ResultType](computation: =>Future[ResultType]) {
  private val startTime = System.currentTimeMillis
  val result: Future[ResultType] = computation
  private var endTime: Option[Long] = None
  result.onComplete{case _ => endTime = Some(System.currentTimeMillis)}
  def responseTime: Option[Long] = endTime.map(_ - startTime)
  //probably also provide onComplete and other Future methods wrapping result.
}

object Timer{
  def apply[ResultType](computation: =>Future[ResultType]) = {
    new Timer(computation)
  }
}

You can create more specific timers with factory methods in companion objects to help working with the computations you want to measure.

Usage:

val timedComputation = Timer{Http(svc > (x => x))}
timedComputation.result.onComplete{case _ => ???}
//In a completed event:
val responseTimeInMillis: Long = timedComputation.responseTime.get
Gábor Bakos
  • 8,982
  • 52
  • 35
  • 52
  • `val' parameters may not be call-by-name [error] class Timer[ResultType](val computation: => Future[ResultType]) { – FelixHJ Jun 11 '15 at 07:07
  • In the companion object it complains about "def apply(computation: =>Future[ResultType]) ={" It works with "def apply(computation: => Future[com.ning.http.client.Response]) = {" – FelixHJ Jun 12 '15 at 07:19
  • Silly me. I forget to add the generic parameter there. Updated. – Gábor Bakos Jun 12 '15 at 07:26