core/internal/health/health.go

113 lines
1.9 KiB
Go

package health
import (
"context"
"sync"
"time"
)
type Health struct {
liveChecks []Checker
readyChecks []Checker
maxConcurrent int
}
func New(opts ...Option) *Health {
cfg := newConfig(opts...)
return &Health{
liveChecks: cfg.liveChecks,
readyChecks: cfg.readyChecks,
maxConcurrent: cfg.maxConcurrent,
}
}
type Status string
const (
StatusPass Status = "pass"
StatusFail Status = "fail"
)
func (s Status) String() string {
return string(s)
}
type SingleCheckResult struct {
Status Status
Err error
Time time.Time
}
type Result struct {
Status Status
Checks map[string][]SingleCheckResult
}
func (h *Health) CheckLive(ctx context.Context) Result {
return h.check(ctx, h.liveChecks)
}
func (h *Health) CheckReady(ctx context.Context) Result {
return h.check(ctx, h.readyChecks)
}
type singleCheckResultWithName struct {
name string
SingleCheckResult
}
func (h *Health) check(ctx context.Context, checks []Checker) Result {
limiterCh := make(chan struct{}, h.maxConcurrent)
checkCh := make(chan singleCheckResultWithName)
var wg sync.WaitGroup
for _, ch := range checks {
wg.Add(1)
go func(ch Checker) {
defer wg.Done()
limiterCh <- struct{}{}
defer func() {
<-limiterCh
}()
check := SingleCheckResult{
Status: StatusPass,
Time: time.Now(),
}
if err := ch.Check(ctx); err != nil {
check.Status = StatusFail
check.Err = err
}
checkCh <- singleCheckResultWithName{
name: ch.Name(),
SingleCheckResult: check,
}
}(ch)
}
go func() {
wg.Wait()
close(limiterCh)
close(checkCh)
}()
res := Result{
Status: StatusPass,
Checks: make(map[string][]SingleCheckResult, len(checks)),
}
for check := range checkCh {
if check.Status == StatusFail {
res.Status = StatusFail
}
res.Checks[check.name] = append(res.Checks[check.name], check.SingleCheckResult)
}
return res
}