package component12import (3"encoding"4"fmt"5"time"6)78// HealthComponent is an optional extension interface for Components which9// report health information.10//11// Health information is exposed to the end user for informational purposes and12// cannot be referened in a River expression.13type HealthComponent interface {14Component1516// CurrentHealth returns the current Health status for the component.17//18// CurrentHealth may be overridden by the Flow controller if there is a19// higher-level issue, such as a config file being invalid or a Component20// shutting down unexpectedly.21CurrentHealth() Health22}2324// Health is the reported health state of a component. It can be encoded to25// River.26type Health struct {27// The specific health value.28Health HealthType `river:"state,attr"`2930// An optional message to describe the health; useful to say why a component31// is unhealthy.32Message string `river:"message,attr,optional"`3334// An optional time to indicate when the component last modified something35// which updated its health.36UpdateTime time.Time `river:"update_time,attr,optional"`37}3839// HealthType holds the health value for a component.40type HealthType uint84142var (43_ encoding.TextMarshaler = HealthType(0)44_ encoding.TextUnmarshaler = (*HealthType)(nil)45)4647const (48// HealthTypeUnknown is the initial health of components, set when they're49// first created.50HealthTypeUnknown HealthType = iota5152// HealthTypeHealthy represents a component which is working as expected.53HealthTypeHealthy5455// HealthTypeUnhealthy represents a component which is not working as56// expected.57HealthTypeUnhealthy5859// HealthTypeExited represents a component which has stopped running.60HealthTypeExited61)6263// String returns the string representation of ht.64func (ht HealthType) String() string {65switch ht {66case HealthTypeHealthy:67return "healthy"68case HealthTypeUnhealthy:69return "unhealthy"70case HealthTypeExited:71return "exited"72default:73return "unknown"74}75}7677// MarshalText implements encoding.TextMarshaler.78func (ht HealthType) MarshalText() (text []byte, err error) {79return []byte(ht.String()), nil80}8182// UnmarshalText implements encoding.TextUnmarshaler.83func (ht *HealthType) UnmarshalText(text []byte) error {84switch string(text) {85case "healthy":86*ht = HealthTypeHealthy87case "unhealthy":88*ht = HealthTypeUnhealthy89case "unknown":90*ht = HealthTypeUnknown91case "exited":92*ht = HealthTypeExited93default:94return fmt.Errorf("invalid health type %q", string(text))95}96return nil97}9899// LeastHealthy returns the Health from the provided arguments which is100// considered to be the least healthy.101//102// Health types are first prioritized by [HealthTypeExited], followed by103// [HealthTypeUnhealthy], [HealthTypeUnknown], and [HealthTypeHealthy].104//105// If multiple arguments have the same Health type, the Health with the most106// recent timestamp is returned.107//108// Finally, if multiple arguments have the same Health type and the same109// timestamp, the earlier argument is chosen.110func LeastHealthy(h Health, hh ...Health) Health {111if len(hh) == 0 {112return h113}114115leastHealthy := h116117for _, compareHealth := range hh {118switch {119case healthPriority[compareHealth.Health] > healthPriority[leastHealthy.Health]:120// Higher health precedence.121leastHealthy = compareHealth122case compareHealth.Health == leastHealthy.Health:123// Same health precedence; check timestamp.124if compareHealth.UpdateTime.After(leastHealthy.UpdateTime) {125leastHealthy = compareHealth126}127}128}129130return leastHealthy131}132133// healthPriority maps a HealthType to its priority; higher numbers means "less134// healthy."135var healthPriority = [...]int{136HealthTypeHealthy: 0,137HealthTypeUnknown: 1,138HealthTypeUnhealthy: 2,139HealthTypeExited: 3,140}141142143