scalamonix

What's the best way to wrap a monix Task with a start time and end time print the difference?


This is what I'm trying right now but it only prints "hey" and not metrics. I don't want to add metric related stuff in the main function.

import java.util.Date

import monix.eval.Task
import monix.execution.Scheduler.Implicits.global

import scala.concurrent.Await
import scala.concurrent.duration.Duration

class A {
  def fellow(): Task[Unit] = {
    val result = Task {
      println("hey")
      Thread.sleep(1000)
    }
    result
  }
}

trait AA extends A {
  override def fellow(): Task[Unit] = {
    println("AA")
    val result = super.fellow()
    val start = new Date()
    result.foreach(e => {
      println("AA", new Date().getTime - start.getTime)
    })
    result
  }
}

val a = new A with AA
val res: Task[Unit] = a.fellow()
Await.result(res.runAsync, Duration.Inf)

Solution

  • You can describe a function such as this:

    def measure[A](task: Task[A], logMillis: Long => Task[Unit]): Task[A] =
      Task.deferAction { sc =>
        val start = sc.clockMonotonic(TimeUnit.MILLISECONDS)
        val stopTimer = Task.suspend {
          val end = sc.clockMonotonic(TimeUnit.MILLISECONDS)
          logMillis(end - start)
        }
    
        task.redeemWith(
          a => stopTimer.map(_ => a)
          e => stopTimer.flatMap(_ => Task.raiseError(e))
        )
      }
    

    Some piece of advice:

    1. Task values should be pure, along with the functions returning Tasks — functions that trigger side effects and return Task as results are broken
      • Task is not a 1:1 replacement for Future; when describing a Task, all side effects should be suspended (wrapped) in Task
      • foreach triggers the Task's evaluation and that's not good, because it triggers the side effects; I was thinking of deprecating and removing it, since its presence is tempting
    2. stop using trait inheritance and just use plain functions — unless you deeply understand OOP and subtyping, it's best to avoid it if possible; and if you're into the Cake pattern, stop doing it and maybe join a support group 🙂
    3. never measure time duration via new Date(), you need a monotonic clock for that and on top of the JVM that's System.nanoTime, which can be accessed via Monix's Scheduler by clockMonotonic, as exemplified above, the Scheduler being given to you via deferAction
    4. stop blocking threads, because that's error prone — instead of doing Thread.sleep, do Task.sleep and all Await.result calls are problematic, unless they are in main or in some other place where dealing with asynchrony is not possible

    Hope this helps.

    Cheers,