All Downloads are FREE. Search and download functionalities are using the official Maven repository.

pl.touk.nussknacker.engine.util.service.TimeMeasuringService.scala Maven / Gradle / Ivy

There is a newer version: 1.17.0
Show newest version
package pl.touk.nussknacker.engine.util.service

import cats.data.NonEmptyList
import com.typesafe.scalalogging.LazyLogging
import pl.touk.nussknacker.engine.api.Service
import pl.touk.nussknacker.engine.api.runtimecontext.EngineRuntimeContext
import pl.touk.nussknacker.engine.util.SafeLazyValues
import pl.touk.nussknacker.engine.util.metrics.MetricIdentifier

import scala.concurrent.duration.{Duration, DurationInt}
import scala.concurrent.{ExecutionContext, Future}
import scala.util.{Failure, Success, Try}

trait TimeMeasuringService { self: Service =>

  protected var timeMeasurement: AsyncExecutionTimeMeasurement = _

  override def open(context: EngineRuntimeContext): Unit = {
    timeMeasurement = new AsyncExecutionTimeMeasurement(context, serviceName, tags)
  }

  def measuring[T](actionFun: => Future[T])(implicit ec: ExecutionContext): Future[T] =
    timeMeasurement.measuring(actionFun)

  protected def tags: Map[String, String] = Map()

  protected def serviceName: String

}

class AsyncExecutionTimeMeasurement(
    context: EngineRuntimeContext,
    serviceName: String,
    tags: Map[String, String],
    instantTimerWindow: Duration = 20 seconds
) extends LazyLogging {

  protected def metricName: NonEmptyList[String] = NonEmptyList.of("service")

  // TODO: add metrics eagerly during open, so that we don't need this map
  private val metrics = new SafeLazyValues[String, EspTimer]

  def measuring[T](actionFun: => Future[T])(implicit ec: ExecutionContext): Future[T] = {
    measuring(tags)(actionFun)
  }

  def measuring[T](tags: Map[String, String])(actionFun: => Future[T])(implicit ec: ExecutionContext): Future[T] = {
    val start = System.nanoTime()
    // we use transform instead of onComplete, so we don't e.g. measure wrong value when onComplete waits due to contention...)
    // (also tests are difficult to get right then)
    actionFun.transform { result =>
      detectMeterName(result).foreach { meterName =>
        getOrCreateTimer(tags, meterName).update(start)
      }
      result
    }
  }

  protected def detectMeterName(result: Try[Any]): Option[String] = result match {
    case Success(_) => Some("OK")
    case Failure(_) => Some("FAIL")
  }

  private def getOrCreateTimer(tags: Map[String, String], meterType: String): EspTimer = {
    metrics.getOrCreate(meterType, () => espTimer(tags + ("serviceName" -> serviceName), metricName :+ meterType))
  }

  def espTimer(tags: Map[String, String], name: NonEmptyList[String]): EspTimer = {
    // TODO: so far in ServiceQuery we don't do open(...) because there's no RuntimeContext
    // we should make it nicer than below, but it's still better than throwing NullPointerException
    if (context == null) {
      logger.info("open not called on TimeMeasuringService - is it ServiceQuery? Using dummy timer")
      EspTimer(() => (), _ => {})
    } else {
      context.metricsProvider.espTimer(MetricIdentifier(name, tags), instantTimerWindow.toSeconds)
    }
  }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy