github.com/kyma-incubator/compass/components/director@v0.0.0-20230623144113-d764f56ff805/internal/healthz/indicator.go (about) 1 package healthz 2 3 import ( 4 "context" 5 "math" 6 "sync" 7 "time" 8 9 "github.com/kyma-incubator/compass/components/director/pkg/log" 10 ) 11 12 // Indicator missing godoc 13 //go:generate mockery --name=Indicator --output=automock --outpkg=automock --case=underscore --disable-version-string 14 type Indicator interface { 15 Name() string 16 Configure(IndicatorConfig) 17 Run(ctx context.Context) 18 Status() Status 19 } 20 21 // Status missing godoc 22 //go:generate mockery --name=Status --output=automock --outpkg=automock --case=underscore --disable-version-string 23 type Status interface { 24 Error() error 25 Details() string 26 } 27 28 // IndicatorFunc missing godoc 29 type IndicatorFunc func(ctx context.Context) Status 30 31 // Implements Status interface 32 type status struct { 33 error error 34 details string 35 } 36 37 // Error returns status error 38 func (s *status) Error() error { 39 return s.error 40 } 41 42 // Details returns status details 43 func (s *status) Details() string { 44 return s.details 45 } 46 47 // Implements Indicator interface 48 type indicator struct { 49 name string 50 51 interval time.Duration 52 timeout time.Duration 53 initialDelay time.Duration 54 threshold int 55 56 indicatorFunc IndicatorFunc 57 status Status 58 statusLock sync.Mutex 59 failureCount int 60 } 61 62 // NewIndicator returns new indicator with the provided name and IndicatorFunc 63 func NewIndicator(name string, indicatorFunc IndicatorFunc) Indicator { 64 return &indicator{ 65 name: name, 66 indicatorFunc: indicatorFunc, 67 status: &status{details: ""}, 68 statusLock: sync.Mutex{}, 69 failureCount: 0, 70 } 71 } 72 73 // Name returns indicator name 74 func (i *indicator) Name() string { 75 return i.name 76 } 77 78 // Configure sets indicator config based on IndicatorConfig 79 func (i *indicator) Configure(cfg IndicatorConfig) { 80 i.interval = cfg.Interval 81 i.timeout = cfg.Timeout 82 i.initialDelay = cfg.InitialDelay 83 i.threshold = cfg.Threshold 84 } 85 86 // Run starts the periodic indicator checks 87 func (i *indicator) Run(ctx context.Context) { 88 go func() { 89 <-time.After(i.initialDelay) 90 91 ticker := time.NewTicker(i.interval) 92 for { 93 timeoutCtx, cancel := context.WithTimeout(ctx, i.timeout) 94 currentStatus := i.indicatorFunc(timeoutCtx) 95 cancel() 96 97 i.statusLock.Lock() 98 if currentStatus.Error() != nil { 99 // escape overflow 100 if i.failureCount < math.MaxInt32 { 101 i.failureCount++ 102 } 103 log.C(ctx).Warnf("Threshold for indicator %s is %d, current failure count is : %d, current error is: %s, details are: %s", 104 i.Name(), 105 i.threshold, 106 i.failureCount, 107 currentStatus.Error(), 108 currentStatus.Details()) 109 } else { 110 i.failureCount = 0 111 } 112 113 if (i.failureCount > i.threshold || i.failureCount == 0) && i.status.Error() != currentStatus.Error() { 114 log.C(ctx).Infof("Changing indicator %s state to %+v", i.Name(), currentStatus) 115 i.status = currentStatus 116 } 117 i.statusLock.Unlock() 118 119 select { 120 case <-ctx.Done(): 121 ticker.Stop() 122 return 123 case <-ticker.C: 124 } 125 } 126 }() 127 } 128 129 // Status reports the last calculated status of the indicator 130 func (i *indicator) Status() Status { 131 i.statusLock.Lock() 132 defer i.statusLock.Unlock() 133 return i.status 134 }