github.com/netdata/go.d.plugin@v0.58.1/agent/discovery/manager.go (about) 1 // SPDX-License-Identifier: GPL-3.0-or-later 2 3 package discovery 4 5 import ( 6 "context" 7 "errors" 8 "fmt" 9 "log/slog" 10 "sync" 11 "time" 12 13 "github.com/netdata/go.d.plugin/agent/confgroup" 14 "github.com/netdata/go.d.plugin/agent/discovery/dummy" 15 "github.com/netdata/go.d.plugin/agent/discovery/file" 16 "github.com/netdata/go.d.plugin/logger" 17 ) 18 19 func NewManager(cfg Config) (*Manager, error) { 20 if err := validateConfig(cfg); err != nil { 21 return nil, fmt.Errorf("discovery manager config validation: %v", err) 22 } 23 24 mgr := &Manager{ 25 Logger: logger.New().With( 26 slog.String("component", "discovery manager"), 27 ), 28 send: make(chan struct{}, 1), 29 sendEvery: time.Second * 2, // timeout to aggregate changes 30 discoverers: make([]discoverer, 0), 31 mux: &sync.RWMutex{}, 32 cache: newCache(), 33 } 34 35 if err := mgr.registerDiscoverers(cfg); err != nil { 36 return nil, fmt.Errorf("discovery manager initializaion: %v", err) 37 } 38 39 return mgr, nil 40 } 41 42 type discoverer interface { 43 Run(ctx context.Context, in chan<- []*confgroup.Group) 44 } 45 46 type Manager struct { 47 *logger.Logger 48 discoverers []discoverer 49 send chan struct{} 50 sendEvery time.Duration 51 mux *sync.RWMutex 52 cache *cache 53 } 54 55 func (m *Manager) String() string { 56 return fmt.Sprintf("discovery manager: %v", m.discoverers) 57 } 58 59 func (m *Manager) Add(d discoverer) { 60 m.discoverers = append(m.discoverers, d) 61 } 62 63 func (m *Manager) Run(ctx context.Context, in chan<- []*confgroup.Group) { 64 m.Info("instance is started") 65 defer func() { m.Info("instance is stopped") }() 66 67 var wg sync.WaitGroup 68 69 for _, d := range m.discoverers { 70 wg.Add(1) 71 go func(d discoverer) { 72 defer wg.Done() 73 m.runDiscoverer(ctx, d) 74 }(d) 75 } 76 77 wg.Add(1) 78 go func() { 79 defer wg.Done() 80 m.sendLoop(ctx, in) 81 }() 82 83 wg.Wait() 84 <-ctx.Done() 85 } 86 87 func (m *Manager) registerDiscoverers(cfg Config) error { 88 if len(cfg.File.Read) > 0 || len(cfg.File.Watch) > 0 { 89 cfg.File.Registry = cfg.Registry 90 d, err := file.NewDiscovery(cfg.File) 91 if err != nil { 92 return err 93 } 94 m.Add(d) 95 } 96 97 if len(cfg.Dummy.Names) > 0 { 98 cfg.Dummy.Registry = cfg.Registry 99 d, err := dummy.NewDiscovery(cfg.Dummy) 100 if err != nil { 101 return err 102 } 103 m.Add(d) 104 } 105 106 if len(m.discoverers) == 0 { 107 return errors.New("zero registered discoverers") 108 } 109 110 m.Infof("registered discoverers: %v", m.discoverers) 111 return nil 112 } 113 114 func (m *Manager) runDiscoverer(ctx context.Context, d discoverer) { 115 updates := make(chan []*confgroup.Group) 116 go d.Run(ctx, updates) 117 118 for { 119 select { 120 case <-ctx.Done(): 121 return 122 case groups, ok := <-updates: 123 if !ok { 124 return 125 } 126 func() { 127 m.mux.Lock() 128 defer m.mux.Unlock() 129 130 m.cache.update(groups) 131 m.triggerSend() 132 }() 133 } 134 } 135 } 136 137 func (m *Manager) sendLoop(ctx context.Context, in chan<- []*confgroup.Group) { 138 m.mustSend(ctx, in) 139 140 tk := time.NewTicker(m.sendEvery) 141 defer tk.Stop() 142 143 for { 144 select { 145 case <-ctx.Done(): 146 return 147 case <-tk.C: 148 select { 149 case <-m.send: 150 m.trySend(in) 151 default: 152 } 153 } 154 } 155 } 156 157 func (m *Manager) mustSend(ctx context.Context, in chan<- []*confgroup.Group) { 158 select { 159 case <-ctx.Done(): 160 return 161 case <-m.send: 162 m.mux.Lock() 163 groups := m.cache.groups() 164 m.cache.reset() 165 m.mux.Unlock() 166 167 select { 168 case <-ctx.Done(): 169 case in <- groups: 170 } 171 return 172 } 173 } 174 175 func (m *Manager) trySend(in chan<- []*confgroup.Group) { 176 m.mux.Lock() 177 defer m.mux.Unlock() 178 179 select { 180 case in <- m.cache.groups(): 181 m.cache.reset() 182 default: 183 m.triggerSend() 184 } 185 } 186 187 func (m *Manager) triggerSend() { 188 select { 189 case m.send <- struct{}{}: 190 default: 191 } 192 }