github.com/pyroscope-io/pyroscope@v0.37.3-0.20230725203016-5f6947968bd0/pkg/health/controller.go (about) 1 package health 2 3 import ( 4 "fmt" 5 "sync" 6 "time" 7 8 "github.com/sirupsen/logrus" 9 ) 10 11 // Controller performs probes of health conditions. 12 type Controller struct { 13 m sync.RWMutex 14 conditions []Condition 15 history [][]StatusMessage 16 current []StatusMessage 17 18 interval time.Duration 19 logger *logrus.Logger 20 21 close chan struct{} 22 } 23 24 const historySize = 5 25 26 func NewController(logger *logrus.Logger, interval time.Duration, conditions ...Condition) *Controller { 27 c := Controller{ 28 conditions: conditions, 29 history: make([][]StatusMessage, len(conditions)), 30 current: make([]StatusMessage, len(conditions)), 31 interval: interval, 32 logger: logger, 33 close: make(chan struct{}), 34 } 35 for i := range c.history { 36 c.history[i] = make([]StatusMessage, historySize) 37 } 38 return &c 39 } 40 41 func (c *Controller) Start() { 42 c.probe() 43 go func() { 44 t := time.NewTicker(c.interval) 45 defer t.Stop() 46 for { 47 select { 48 case <-c.close: 49 return 50 case <-t.C: 51 c.probe() 52 } 53 } 54 }() 55 } 56 57 func (c *Controller) Stop() { close(c.close) } 58 59 func (c *Controller) probe() { 60 c.m.Lock() 61 defer c.m.Unlock() 62 for i, condition := range c.conditions { 63 history := c.history[i] 64 copy(history, history[1:]) 65 s, err := condition.Probe() 66 if err != nil { 67 s = StatusMessage{Message: err.Error()} 68 c.logger.WithError(err). 69 WithField("probe-name", fmt.Sprintf("%T", condition)). 70 Warn("failed to make probe") 71 } 72 history[len(history)-1] = s 73 current := s 74 for _, x := range history { 75 if x.Status > current.Status { 76 current = x 77 } 78 } 79 c.current[i] = current 80 } 81 } 82 83 func (c *Controller) Unhealthy() []StatusMessage { 84 c.m.RLock() 85 defer c.m.RUnlock() 86 m := make([]StatusMessage, 0, len(c.current)) 87 for _, x := range c.current { 88 if x.Status > Healthy { 89 m = append(m, x) 90 } 91 } 92 return m 93 } 94 95 // NotificationText satisfies server.Notifier. 96 // 97 // TODO(kolesnikovae): I think we need to make UI notifications 98 // structured (explicit status field) and support multiple messages. 99 // At the moment there can be only one notification. 100 func (c *Controller) NotificationText() string { 101 if u := c.Unhealthy(); len(u) > 0 { 102 return u[0].Message 103 } 104 return "" 105 } 106 107 func (c *Controller) IsOutOfDiskSpace() bool { 108 c.m.RLock() 109 defer c.m.RUnlock() 110 for i := range c.conditions { 111 if _, ok := c.conditions[i].(DiskPressure); ok && c.current[i].Status > Healthy { 112 return true 113 } 114 } 115 return false 116 }