Skip to content

Commit

Permalink
Merge 34ada37 into 6917732
Browse files Browse the repository at this point in the history
  • Loading branch information
Sergey committed Mar 21, 2021
2 parents 6917732 + 34ada37 commit 5d9c8a4
Show file tree
Hide file tree
Showing 8 changed files with 173 additions and 24 deletions.
3 changes: 2 additions & 1 deletion build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@ lazy val smetrics = (project
Cats.core,
Cats.effect,
`cats-helper`,
scalatest % Test)))
scalatest % Test,
mockito % Test)))

lazy val prometheus = (project
in file("modules/prometheus")
Expand Down
13 changes: 7 additions & 6 deletions project/Dependencies.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,13 @@ import sbt._
object Dependencies {

private val prometheusVersion = "0.8.1"
val prometheus = "io.prometheus" % "simpleclient" % prometheusVersion
val prometheusCommon = "io.prometheus" % "simpleclient_common" % prometheusVersion
val scalatest = "org.scalatest" %% "scalatest" % "3.2.3"
val `cats-helper` = "com.evolutiongaming" %% "cats-helper" % "2.1.3"
val http4s = "org.http4s" %% "http4s-core" % "0.21.6"
val doobie = "org.tpolecat" %% "doobie-core" % "0.9.0"
val prometheus = "io.prometheus" % "simpleclient" % prometheusVersion
val prometheusCommon = "io.prometheus" % "simpleclient_common" % prometheusVersion
val scalatest = "org.scalatest" %% "scalatest" % "3.2.3"
val mockito = "org.mockito" %% "mockito-scala-scalatest" % "1.16.3"
val `cats-helper` = "com.evolutiongaming" %% "cats-helper" % "2.1.3"
val http4s = "org.http4s" %% "http4s-core" % "0.21.6"
val doobie = "org.tpolecat" %% "doobie-core" % "0.9.0"

object Cats {
private val version = "2.1.1"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,10 @@ object MeasureDuration {
def apply[F[_]](implicit F: MeasureDuration[F]): MeasureDuration[F] = F


def fromClock[F[_] : FlatMap](clock: Clock[F]): MeasureDuration[F] = {
implicit def fromClock[F[_] : Clock : FlatMap]: MeasureDuration[F] = {
val timeUnit = TimeUnit.NANOSECONDS
val duration = for {
duration <- clock.monotonic(timeUnit)
duration <- Clock[F].monotonic(timeUnit)
} yield {
FiniteDuration(duration, timeUnit)
}
Expand Down Expand Up @@ -59,4 +59,5 @@ object MeasureDuration {
val start = f(self.start.map(f.apply))
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.evolutiongaming.smetrics

import com.evolutiongaming.smetrics.syntax.AllSmetricsSyntax

package object implicits extends AllSmetricsSyntax
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package com.evolutiongaming.smetrics.syntax

trait AllSmetricsSyntax
extends MeasureDurationSyntax
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package com.evolutiongaming.smetrics.syntax

import cats.syntax.functor._
import cats.syntax.flatMap._
import cats.syntax.applicativeError._
import cats.syntax.either._
import cats.{Monad, MonadError}
import com.evolutiongaming.smetrics.MeasureDuration

import scala.concurrent.duration.FiniteDuration


trait MeasureDurationSyntax {
implicit def measureDurationSyntax[F[_], A](fa: F[A]): MeasureDurationOps[F, A] =
new MeasureDurationOps[F, A](fa)
}

final class MeasureDurationOps[F[_], A](private val fa: F[A]) extends AnyVal {

/**
* Calculate time spent on F execution and handle result using provided function.
*
* Example:
* {{{
* import com.evolutiongaming.smetrics.syntax.measureDuration._
* val handler: FiniteDuration => F[Unit] = latency => Sync[F].delay { println(s"Successful execution. Latency is $latency") }
* Sync[F]
* .delay { toBeMeasuredSideEffectCall() }
* .measured { handler }
* }}}
*
* @param handleF function to consume calculated duration
* @return measured source F[A]
*/
def measured(
handleF: FiniteDuration => F[Unit]
)(implicit F: Monad[F], measureDuration: MeasureDuration[F]): F[A] =
for {
measure <- measureDuration.start
result <- fa
duration <- measure
_ <- handleF(duration)
} yield result

/**
* Calculate time spent on F execution and handle success/failure result cases using provided functions.
*
* Example:
* {{{
* import com.evolutiongaming.smetrics.syntax.measureDuration._
* val successHandler: FiniteDuration => F[Unit] = latency => Sync[F].delay { println(s"Successful execution. Latency is $latency") }
* val failureHandler: FiniteDuration => F[Unit] = latency => Sync[F].delay { println(s"Failed execution. Latency is $latency") }
* Sync[F]
* .delay { toBeMeasuredSideEffectCall() }
* .measuredCase { successHandler, failureHandler }
* }}}
*
* @param successF function to consume calculated duration in case of success
* @param failureF function to consume calculated duration in case of failure
* @return measured source F[A]
*/
def measuredCase[E](
successF: FiniteDuration => F[Unit],
failureF: FiniteDuration => F[Unit]
)(implicit F: MonadError[F, E], measureDuration: MeasureDuration[F]): F[A] =
for {
measure <- measureDuration.start
result <- fa.attempt
duration <- measure
_ <- result match {
case Right(_) => successF(duration)
case Left(_) => failureF(duration)
}
result <- result.liftTo[F]
} yield result

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.evolutiongaming.smetrics

package object syntax {
object all extends AllSmetricsSyntax
object measureDuration extends MeasureDurationSyntax
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,24 @@ package com.evolutiongaming.smetrics
import java.util.concurrent.TimeUnit

import cats.Id
import cats.effect.Clock
import cats.effect.{Clock, IO, Timer}
import cats.syntax.functor._
import cats.syntax.applicativeError._
import com.evolutiongaming.smetrics.syntax.measureDuration._

import scala.concurrent.duration._
import org.scalatest.funsuite.AnyFunSuite
import org.scalatest.matchers.should.Matchers
import IOSuite._
import org.mockito.{ArgumentCaptor, ArgumentMatchersSugar}
import org.mockito.scalatest.MockitoSugar

class MeasureDurationSpec extends AnyFunSuite with Matchers {
class MeasureDurationSpec extends AnyFunSuite with Matchers with MockitoSugar with ArgumentMatchersSugar {

import MeasureDurationSpec._

test("measure duration") {
val measureDuration = MeasureDuration.fromClock[StateT](Clock[StateT])
val measureDuration = MeasureDuration[StateT]
val stateT = for {
duration <- measureDuration.start
duration <- duration
Expand All @@ -24,6 +30,46 @@ class MeasureDurationSpec extends AnyFunSuite with Matchers {
duration shouldEqual 2.nanos
state shouldEqual State.Empty
}

test("MeasureDurationOps.measured") {
val handler: FiniteDuration => StateT[Unit] =
duration => StateT { state =>
val timestamp = duration.toNanos
timestamp shouldEqual 5
(state, timestamp)
}.void
val (state, _) = Timer[StateT]
.sleep(5.nanos)
.measured(handler)
.run(State.Empty)
state shouldEqual State.Empty
}
test("MeasureDurationOps.measuredCase success") {
val successHandler = mock[FiniteDuration => IO[Unit]]
val failureHandler = mock[FiniteDuration => IO[Unit]]
val argument: ArgumentCaptor[FiniteDuration] = ArgumentCaptor.forClass(classOf[FiniteDuration])
doReturn(IO.unit).when(successHandler).apply(argument.capture())
Timer[IO]
.sleep(3.second)
.measuredCase(successHandler, failureHandler)
.unsafeRunSync()
assertResult(3)(argument.getValue.toSeconds)
verifyNoMoreInteractions(successHandler, failureHandler)
}
test("MeasureDurationOps.measuredCase failure") {
val successHandler = mock[FiniteDuration => IO[Unit]]
val failureHandler = mock[FiniteDuration => IO[Unit]]
val argument: ArgumentCaptor[FiniteDuration] = ArgumentCaptor.forClass(classOf[FiniteDuration])
doReturn(IO.unit).when(failureHandler).apply(argument.capture())
assertThrows[RuntimeException] {
(Timer[IO].sleep(3.second) *> new RuntimeException().raiseError[IO, Unit])
.measuredCase(successHandler, failureHandler)
.unsafeRunSync()
}
assertResult(3)(argument.getValue.toSeconds)
verifyNoMoreInteractions(successHandler, failureHandler)
}

}

object MeasureDurationSpec {
Expand Down Expand Up @@ -51,22 +97,30 @@ object MeasureDurationSpec {
}


implicit val clockStateT: Clock[StateT] = new Clock[StateT] {
implicit val timerStateT: Timer[StateT] = new Timer[StateT] {

def realTime(unit: TimeUnit) = {
StateT { state =>
val (state1, timestamp) = state.timestamp
val timestamp1 = unit.convert(timestamp, TimeUnit.NANOSECONDS)
(state1, timestamp1)
}
def clock: Clock[StateT] = new Clock[StateT] {
def realTime(unit: TimeUnit) =
StateT { state =>
val (state1, timestamp) = state.timestamp
val timestamp1 = unit.convert(timestamp, TimeUnit.NANOSECONDS)
(state1, timestamp1)
}

def monotonic(unit: TimeUnit) =
StateT { state =>
val (state1, timestamp) = state.timestamp
val timestamp1 = unit.convert(timestamp, TimeUnit.NANOSECONDS)
(state1, timestamp1)
}
}

def monotonic(unit: TimeUnit) = {
def sleep(duration: FiniteDuration) =
StateT { state =>
val (state1, timestamp) = state.timestamp
val timestamp1 = unit.convert(timestamp, TimeUnit.NANOSECONDS)
(state1, timestamp1)
val timestamp1 = timestamp + duration.toNanos
val newState = state1.copy(timestamps = state1.timestamps :+ timestamp1)
(newState, ())
}
}
}
}
}

0 comments on commit 5d9c8a4

Please sign in to comment.