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  }