github.com/grafana/pyroscope@v1.18.0/pkg/scheduler/schedulerdiscovery/ring.go (about) 1 // SPDX-License-Identifier: AGPL-3.0-only 2 3 package schedulerdiscovery 4 5 import ( 6 "net" 7 "strconv" 8 9 "github.com/go-kit/log" 10 "github.com/grafana/dskit/kv" 11 "github.com/grafana/dskit/ring" 12 "github.com/pkg/errors" 13 "github.com/prometheus/client_golang/prometheus" 14 15 "github.com/grafana/pyroscope/pkg/util" 16 ) 17 18 const ( 19 // ringKey is the key under which we store the query-schedulers ring in the KVStore. 20 ringKey = "query-scheduler" 21 22 // ringNumTokens is how many tokens each query-scheduler should have in the ring. 23 // Query-schedulers use a ring for service-discovery so just 1 token is enough. 24 ringNumTokens = 1 25 26 // ringAutoForgetUnhealthyPeriods is how many consecutive timeout periods an unhealthy instance 27 // in the ring will be automatically removed after. 28 ringAutoForgetUnhealthyPeriods = 4 29 30 // sharedOptionWithRingClient is a message appended to all config options that should be also 31 // set on the components running the query-scheduler ring client. 32 sharedOptionWithRingClient = " When query-scheduler ring-based service discovery is enabled, this option needs be set on query-schedulers, query-frontends and queriers." 33 ) 34 35 // toBasicLifecyclerConfig returns a ring.BasicLifecyclerConfig based on the query-scheduler ring config. 36 func toBasicLifecyclerConfig(cfg util.CommonRingConfig, logger log.Logger) (ring.BasicLifecyclerConfig, error) { 37 instanceAddr, err := ring.GetInstanceAddr(cfg.InstanceAddr, cfg.InstanceInterfaceNames, logger, cfg.EnableIPv6) 38 if err != nil { 39 return ring.BasicLifecyclerConfig{}, err 40 } 41 42 instancePort := ring.GetInstancePort(cfg.InstancePort, cfg.ListenPort) 43 44 return ring.BasicLifecyclerConfig{ 45 ID: cfg.InstanceID, 46 Addr: net.JoinHostPort(instanceAddr, strconv.Itoa(instancePort)), 47 HeartbeatPeriod: cfg.HeartbeatPeriod, 48 HeartbeatTimeout: cfg.HeartbeatTimeout, 49 TokensObservePeriod: 0, 50 NumTokens: ringNumTokens, 51 KeepInstanceInTheRingOnShutdown: false, 52 }, nil 53 } 54 55 // NewRingLifecycler creates a new query-scheduler ring lifecycler with all required lifecycler delegates. 56 func NewRingLifecycler(cfg util.CommonRingConfig, logger log.Logger, reg prometheus.Registerer) (*ring.BasicLifecycler, error) { 57 reg = prometheus.WrapRegistererWithPrefix("pyroscope_", reg) 58 kvStore, err := kv.NewClient(cfg.KVStore, ring.GetCodec(), kv.RegistererWithKVName(reg, "query-scheduler-lifecycler"), logger) 59 if err != nil { 60 return nil, errors.Wrap(err, "failed to initialize query-schedulers' KV store") 61 } 62 63 lifecyclerCfg, err := toBasicLifecyclerConfig(cfg, logger) 64 if err != nil { 65 return nil, errors.Wrap(err, "failed to build query-schedulers' lifecycler config") 66 } 67 68 var delegate ring.BasicLifecyclerDelegate 69 delegate = ring.NewInstanceRegisterDelegate(ring.ACTIVE, ringNumTokens) 70 delegate = ring.NewLeaveOnStoppingDelegate(delegate, logger) 71 delegate = ring.NewAutoForgetDelegate(ringAutoForgetUnhealthyPeriods*cfg.HeartbeatTimeout, delegate, logger) 72 73 lifecycler, err := ring.NewBasicLifecycler(lifecyclerCfg, "query-scheduler", ringKey, kvStore, delegate, logger, reg) 74 if err != nil { 75 return nil, errors.Wrap(err, "failed to initialize query-schedulers' lifecycler") 76 } 77 78 return lifecycler, nil 79 } 80 81 // NewRingClient creates a client for the query-schedulers ring. 82 func NewRingClient(cfg util.CommonRingConfig, component string, logger log.Logger, reg prometheus.Registerer) (*ring.Ring, error) { 83 client, err := ring.New(cfg.ToRingConfig(), component, ringKey, logger, prometheus.WrapRegistererWithPrefix("pyroscope_", reg)) 84 if err != nil { 85 return nil, errors.Wrap(err, "failed to initialize query-schedulers' ring client") 86 } 87 88 return client, err 89 }