1package cbreaker
2
3import (
4	"fmt"
5	"time"
6
7	"github.com/mailgun/timetools"
8	log "github.com/sirupsen/logrus"
9)
10
11// ratioController allows passing portions traffic back to the endpoints,
12// increasing the amount of passed requests using linear function:
13//
14//   allowedRequestsRatio = 0.5 * (Now() - Start())/Duration
15//
16type ratioController struct {
17	duration time.Duration
18	start    time.Time
19	tm       timetools.TimeProvider
20	allowed  int
21	denied   int
22
23	log *log.Logger
24}
25
26func newRatioController(tm timetools.TimeProvider, rampUp time.Duration, log *log.Logger) *ratioController {
27	return &ratioController{
28		duration: rampUp,
29		tm:       tm,
30		start:    tm.UtcNow(),
31
32		log: log,
33	}
34}
35
36func (r *ratioController) String() string {
37	return fmt.Sprintf("RatioController(target=%f, current=%f, allowed=%d, denied=%d)", r.targetRatio(), r.computeRatio(r.allowed, r.denied), r.allowed, r.denied)
38}
39
40func (r *ratioController) allowRequest() bool {
41	r.log.Debugf("%v", r)
42	t := r.targetRatio()
43	// This condition answers the question - would we satisfy the target ratio if we allow this request?
44	e := r.computeRatio(r.allowed+1, r.denied)
45	if e < t {
46		r.allowed++
47		r.log.Debugf("%v allowed", r)
48		return true
49	}
50	r.denied++
51	r.log.Debugf("%v denied", r)
52	return false
53}
54
55func (r *ratioController) computeRatio(allowed, denied int) float64 {
56	if denied+allowed == 0 {
57		return 0
58	}
59	return float64(allowed) / float64(denied+allowed)
60}
61
62func (r *ratioController) targetRatio() float64 {
63	// Here's why it's 0.5:
64	// We are watching the following ratio
65	// ratio = a / (a + d)
66	// We can notice, that once we get to 0.5
67	// 0.5 = a / (a + d)
68	// we can evaluate that a = d
69	// that means equilibrium, where we would allow all the requests
70	// after this point to achieve ratio of 1 (that can never be reached unless d is 0)
71	// so we stop from there
72	multiplier := 0.5 / float64(r.duration)
73	return multiplier * float64(r.tm.UtcNow().Sub(r.start))
74}
75