Last active
October 11, 2019 12:15
-
-
Save danilbykov/957487c9463a66daa69290698a9320ac to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import cats.effect.ExitCase._ | |
import cats.effect.Sync | |
import cats.effect.concurrent.Ref | |
import cats.syntax.flatMap._ | |
import cats.syntax.functor._ | |
trait Tap[F[_]] { | |
def apply[A](effect: F[A]): F[A] | |
} | |
object Tap { | |
type Percentage = Double | |
def make[F[_]: Sync](errBound: Percentage, | |
qualified: Throwable => Boolean, | |
rejected: => Throwable): F[Tap[F]] = { | |
for { | |
ref <- Ref.of[F, TapState](TapState(Nil)) | |
} yield new TapImpl(ref, errBound, qualified, rejected) | |
} | |
val tailSize = 100 | |
private case class TapState(lastResults: List[Boolean]) | |
private final class TapImpl[F[_]: Sync](ref: Ref[F, TapState], | |
errBound: Percentage, | |
qualified: Throwable => Boolean, | |
rejected: => Throwable) extends Tap[F] { | |
override def apply[A](effect: F[A]): F[A] = | |
for { | |
state <- ref.get | |
failed = state.lastResults.count(_ == false) | |
result <- | |
if (failed <= state.lastResults.size * errBound) { | |
Sync[F].guaranteeCase(effect) { | |
case Completed => | |
ref.update(updateState(true)) | |
case Error(e) => | |
ref.update(updateState(qualified(e))) | |
case Canceled => | |
Sync[F].unit | |
} | |
} else { | |
ref.update(updateState(true)) >> Sync[F].raiseError(rejected) | |
} | |
} yield result | |
private def updateState(newResult: Boolean)(state: TapState): TapState = | |
state.copy(lastResults = (newResult :: state.lastResults).take(tailSize)) | |
} | |
} |
I think, there is no need to store list of bools:
final case class ErrorStats(bufferSize: Int, errorCount: Int = 0) {
def update(newResult: Boolean): ErrorStats = {
val newErrorCount = {
val delta = if (newResult) -1 else 1
Math.min(Math.max(errorCount + delta, 0), bufferSize)
}
copy(bufferSize, newErrorCount)
}
def rate = errorCount.toDouble / bufferSize
}
Let's assume that every second request fails on our service so we get series of true, false, true, false and so on. We feed these results into ErrorStats(100)
:
(1 to 100).foldLeft(ErrorStats(100)) {
case (stat, _) => stat.update(true).update(false)
} rate
Final rate is 0.01 which is too good where only half of requests is successful.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Thank you.