Files
2025-07-21 22:24:25 +02:00

254 lines
5.3 KiB
Go

package cbreaker
import (
"fmt"
"github.com/vulcand/oxy/v2/internal/holsterv4/clock"
"github.com/vulcand/predicate"
)
type hpredicate func(*CircuitBreaker) bool
// parseExpression parses expression in the go language into predicates.
func parseExpression(in string) (hpredicate, error) {
p, err := predicate.NewParser(predicate.Def{
Operators: predicate.Operators{
AND: and,
OR: or,
EQ: eq,
NEQ: neq,
LT: lt,
LE: le,
GT: gt,
GE: ge,
},
Functions: map[string]any{
"LatencyAtQuantileMS": latencyAtQuantile,
"NetworkErrorRatio": networkErrorRatio,
"ResponseCodeRatio": responseCodeRatio,
},
})
if err != nil {
return nil, err
}
out, err := p.Parse(in)
if err != nil {
return nil, err
}
pr, ok := out.(hpredicate)
if !ok {
return nil, fmt.Errorf("expected predicate, got %T", out)
}
return pr, nil
}
type toInt func(c *CircuitBreaker) int
type toFloat64 func(c *CircuitBreaker) float64
func latencyAtQuantile(quantile float64) toInt {
return func(c *CircuitBreaker) int {
h, err := c.metrics.LatencyHistogram()
if err != nil {
c.log.Error("Failed to get latency histogram, for %v error: %v", c, err)
return 0
}
return int(h.LatencyAtQuantile(quantile) / clock.Millisecond)
}
}
func networkErrorRatio() toFloat64 {
return func(c *CircuitBreaker) float64 {
return c.metrics.NetworkErrorRatio()
}
}
func responseCodeRatio(startA, endA, startB, endB int) toFloat64 {
return func(c *CircuitBreaker) float64 {
return c.metrics.ResponseCodeRatio(startA, endA, startB, endB)
}
}
// or returns predicate by joining the passed predicates with logical 'or'.
func or(fns ...hpredicate) hpredicate {
return func(c *CircuitBreaker) bool {
for _, fn := range fns {
if fn(c) {
return true
}
}
return false
}
}
// and returns predicate by joining the passed predicates with logical 'and'.
func and(fns ...hpredicate) hpredicate {
return func(c *CircuitBreaker) bool {
for _, fn := range fns {
if !fn(c) {
return false
}
}
return true
}
}
// not creates negation of the passed predicate.
func not(p hpredicate) hpredicate {
return func(c *CircuitBreaker) bool {
return !p(c)
}
}
// eq returns predicate that tests for equality of the value of the mapper and the constant.
func eq(m any, value any) (hpredicate, error) {
switch mapper := m.(type) {
case toInt:
return intEQ(mapper, value)
case toFloat64:
return float64EQ(mapper, value)
}
return nil, fmt.Errorf("eq: unsupported argument: %T", m)
}
// neq returns predicate that tests for inequality of the value of the mapper and the constant.
func neq(m any, value any) (hpredicate, error) {
p, err := eq(m, value)
if err != nil {
return nil, err
}
return not(p), nil
}
// lt returns predicate that tests that value of the mapper function is less than the constant.
func lt(m any, value any) (hpredicate, error) {
switch mapper := m.(type) {
case toInt:
return intLT(mapper, value)
case toFloat64:
return float64LT(mapper, value)
}
return nil, fmt.Errorf("lt: unsupported argument: %T", m)
}
// le returns predicate that tests that value of the mapper function is less or equal than the constant.
func le(m any, value any) (hpredicate, error) {
l, err := lt(m, value)
if err != nil {
return nil, err
}
e, err := eq(m, value)
if err != nil {
return nil, err
}
return func(c *CircuitBreaker) bool {
return l(c) || e(c)
}, nil
}
// gt returns predicate that tests that value of the mapper function is greater than the constant.
func gt(m any, value any) (hpredicate, error) {
switch mapper := m.(type) {
case toInt:
return intGT(mapper, value)
case toFloat64:
return float64GT(mapper, value)
}
return nil, fmt.Errorf("gt: unsupported argument: %T", m)
}
// ge returns predicate that tests that value of the mapper function is less or equal than the constant.
func ge(m any, value any) (hpredicate, error) {
g, err := gt(m, value)
if err != nil {
return nil, err
}
e, err := eq(m, value)
if err != nil {
return nil, err
}
return func(c *CircuitBreaker) bool {
return g(c) || e(c)
}, nil
}
func intEQ(m toInt, val any) (hpredicate, error) {
value, ok := val.(int)
if !ok {
return nil, fmt.Errorf("expected int, got %T", val)
}
return func(c *CircuitBreaker) bool {
return m(c) == value
}, nil
}
func float64EQ(m toFloat64, val any) (hpredicate, error) {
value, ok := val.(float64)
if !ok {
return nil, fmt.Errorf("expected float64, got %T", val)
}
return func(c *CircuitBreaker) bool {
return m(c) == value
}, nil
}
func intLT(m toInt, val any) (hpredicate, error) {
value, ok := val.(int)
if !ok {
return nil, fmt.Errorf("expected int, got %T", val)
}
return func(c *CircuitBreaker) bool {
return m(c) < value
}, nil
}
func intGT(m toInt, val any) (hpredicate, error) {
value, ok := val.(int)
if !ok {
return nil, fmt.Errorf("expected int, got %T", val)
}
return func(c *CircuitBreaker) bool {
return m(c) > value
}, nil
}
func float64LT(m toFloat64, val any) (hpredicate, error) {
value, ok := val.(float64)
if !ok {
return nil, fmt.Errorf("expected int, got %T", val)
}
return func(c *CircuitBreaker) bool {
return m(c) < value
}, nil
}
func float64GT(m toFloat64, val any) (hpredicate, error) {
value, ok := val.(float64)
if !ok {
return nil, fmt.Errorf("expected int, got %T", val)
}
return func(c *CircuitBreaker) bool {
return m(c) > value
}, nil
}