master
go 310 lines 7.57 KB
Raw
1 // SPDX-License-Identifier: GPL-3.0-or-later
2
3 package agent
4
5 import (
6 "context"
7 "fmt"
8 "io"
9 "log/slog"
10 "sync"
11 "time"
12
13 "github.com/netdata/netdata/go/plugins/logger"
14 "github.com/netdata/netdata/go/plugins/pkg/multipath"
15 "github.com/netdata/netdata/go/plugins/pkg/netdataapi"
16 "github.com/netdata/netdata/go/plugins/pkg/safewriter"
17 "github.com/netdata/netdata/go/plugins/plugin/agent/discovery"
18 "github.com/netdata/netdata/go/plugins/plugin/agent/jobmgr"
19 "github.com/netdata/netdata/go/plugins/plugin/agent/policy"
20 "github.com/netdata/netdata/go/plugins/plugin/agent/runtimechartemit"
21 "github.com/netdata/netdata/go/plugins/plugin/framework/collectorapi"
22 "github.com/netdata/netdata/go/plugins/plugin/framework/confgroup"
23 "github.com/netdata/netdata/go/plugins/plugin/framework/functions"
24 "github.com/netdata/netdata/go/plugins/plugin/framework/metricsaudit"
25 "github.com/netdata/netdata/go/plugins/plugin/framework/runtimecomp"
26 )
27
28 // Config is an Agent configuration.
29 type Config struct {
30 Name string
31 PluginConfigDir []string
32
33 CollectorsConfigDir []string
34 CollectorsConfigWatchPath []string
35 ServiceDiscoveryConfigDir []string
36 VarLibDir string
37
38 ModuleRegistry collectorapi.Registry
39 RunModule string
40 RunJob []string
41 MinUpdateEvery int
42
43 DisableServiceDiscovery bool
44
45 IsInsideK8s bool
46
47 RunModePolicy policy.RunModePolicy
48
49 DiscoveryProviders []discovery.ProviderFactory
50
51 AuditDuration time.Duration
52 AuditSummary bool
53 AuditDataDir string
54 }
55
56 // Agent represents orchestrator.
57 type Agent struct {
58 *logger.Logger
59
60 Name string
61
62 ConfigDir multipath.MultiPath
63 CollectorsConfDir multipath.MultiPath
64 CollectorsConfigWatchPath []string
65 ServiceDiscoveryConfigDir multipath.MultiPath
66
67 VarLibDir string
68
69 RunModule string
70 RunJob []string
71 MinUpdateEvery int
72
73 DisableServiceDiscovery bool
74
75 IsInsideK8s bool
76
77 runModePolicy policy.RunModePolicy
78
79 DiscoveryProviders []discovery.ProviderFactory
80
81 ModuleRegistry collectorapi.Registry
82 Out io.Writer
83
84 api *netdataapi.API
85
86 quitCh chan struct{}
87
88 // Metrics-audit mode.
89 auditDuration time.Duration
90 auditSummary bool
91 auditAnalyzer *metricsaudit.Auditor
92
93 auditDataDir string
94 quitOnce sync.Once
95 auditOnce sync.Once
96 }
97
98 // New creates a new Agent.
99 func New(cfg Config) *Agent {
100 a := &Agent{
101 Logger: logger.New().With(
102 slog.String("component", "agent"),
103 ),
104 Name: cfg.Name,
105 ConfigDir: cfg.PluginConfigDir,
106 CollectorsConfDir: cfg.CollectorsConfigDir,
107 ServiceDiscoveryConfigDir: cfg.ServiceDiscoveryConfigDir,
108 CollectorsConfigWatchPath: cfg.CollectorsConfigWatchPath,
109 VarLibDir: cfg.VarLibDir,
110 RunModule: cfg.RunModule,
111 RunJob: cfg.RunJob,
112 MinUpdateEvery: cfg.MinUpdateEvery,
113 IsInsideK8s: cfg.IsInsideK8s,
114 runModePolicy: cfg.RunModePolicy,
115 ModuleRegistry: cfg.ModuleRegistry,
116 DiscoveryProviders: cfg.DiscoveryProviders,
117 Out: safewriter.Stdout,
118 api: netdataapi.New(safewriter.Stdout),
119 quitCh: make(chan struct{}, 1),
120 auditDuration: cfg.AuditDuration,
121 auditSummary: cfg.AuditSummary,
122 DisableServiceDiscovery: cfg.DisableServiceDiscovery,
123 }
124
125 if a.auditDuration > 0 {
126 a.auditAnalyzer = metricsaudit.New()
127 a.Infof("metrics-audit mode enabled: will run for %v and analyze metric structure", a.auditDuration)
128 if a.auditSummary {
129 a.Infof("metrics-audit summary enabled: will show consolidated summary across all jobs")
130 }
131 }
132
133 if cfg.AuditDataDir != "" {
134 a.auditDataDir = cfg.AuditDataDir
135 if a.auditAnalyzer == nil {
136 a.auditAnalyzer = metricsaudit.New()
137 }
138 a.auditAnalyzer.EnableDataCapture(cfg.AuditDataDir, a.signalAuditComplete)
139 a.Infof("metrics-audit data directory: %s", cfg.AuditDataDir)
140 }
141
142 return a
143 }
144
145 // RunContext runs one agent instance lifecycle on the provided context.
146 func (a *Agent) RunContext(ctx context.Context) {
147 a.run(ctx)
148 }
149
150 // IsTerminalMode reports whether run-mode policy is interactive terminal.
151 func (a *Agent) IsTerminalMode() bool {
152 return a.runModePolicy.IsTerminal
153 }
154
155 // RunKeepAlive runs keepalive loop until context cancellation or too many failures.
156 func (a *Agent) RunKeepAlive(ctx context.Context) error {
157 tk := time.NewTicker(time.Second)
158 defer tk.Stop()
159
160 var n int
161 for {
162 select {
163 case <-ctx.Done():
164 return nil
165 case <-tk.C:
166 if err := a.api.EMPTYLINE(); err != nil {
167 n++
168 } else {
169 n = 0
170 }
171 if n >= 30 {
172 return fmt.Errorf("too many keepAlive errors")
173 }
174 }
175 }
176 }
177
178 // QuitCh returns agent quit notifications (e.g., metrics-audit completion).
179 func (a *Agent) QuitCh() <-chan struct{} {
180 return a.quitCh
181 }
182
183 // AuditDuration returns configured metrics-audit timer duration.
184 func (a *Agent) AuditDuration() time.Duration {
185 return a.auditDuration
186 }
187
188 // FinalizeMetricsAudit prints metrics-audit analysis report once.
189 func (a *Agent) FinalizeMetricsAudit(reason string) {
190 a.auditOnce.Do(func() {
191 if a.auditAnalyzer == nil {
192 return
193 }
194 if reason != "" {
195 a.Infof("finalizing metrics audit (%s)", reason)
196 }
197 a.printMetricsAudit()
198 })
199 }
200
201 func (a *Agent) run(ctx context.Context) {
202 a.Info("instance is started")
203 defer func() { a.Info("instance is stopped") }()
204
205 cfg := a.loadPluginConfig()
206 a.Infof("using config: %s", cfg.String())
207
208 if !cfg.Enabled {
209 a.Info("plugin is disabled in the configuration file, exiting...")
210 a.api.DISABLE()
211 return
212 }
213
214 enabledModules := a.loadEnabledModules(cfg)
215 if len(enabledModules) == 0 {
216 a.Info("no modules to run")
217 a.api.DISABLE()
218 return
219 }
220
221 fnMgr := functions.NewManager()
222
223 discCfg := a.buildDiscoveryConf(enabledModules, fnMgr)
224
225 discMgr, err := discovery.NewManager(discCfg)
226 if err != nil {
227 a.Error(err)
228 return
229 }
230
231 runtimeSvc, stopRuntimeSvc := a.setupRuntimeService()
232 if stopRuntimeSvc != nil {
233 defer stopRuntimeSvc()
234 }
235 fnMgr.SetRuntimeService(runtimeSvc)
236
237 var runJob []string
238 if a.RunModule != "" && a.RunModule != "all" {
239 runJob = a.RunJob
240 }
241
242 jobMgr := jobmgr.New(jobmgr.Config{
243 PluginName: a.Name,
244 Out: a.Out,
245 RunModePolicy: a.runModePolicy,
246 Modules: enabledModules,
247 RunJob: runJob,
248 ConfigDefaults: discCfg.Registry,
249 VarLibDir: a.VarLibDir,
250 FnReg: fnMgr,
251 Vnodes: a.setupVnodeRegistry(),
252 SecretStores: a.setupSecretStoreConfigs(),
253 AuditMode: a.auditDuration > 0,
254 AuditAnalyzer: a.auditAnalyzer,
255 AuditDataDir: a.auditDataDir,
256 RuntimeService: runtimeSvc,
257 })
258
259 in := make(chan []*confgroup.Group)
260 var wg sync.WaitGroup
261
262 wg.Go(func() { fnMgr.Run(ctx, a.quitCh) })
263
264 wg.Go(func() { jobMgr.Run(ctx, in) })
265
266 wg.Go(func() { discMgr.Run(ctx, in) })
267
268 wg.Wait()
269 <-ctx.Done()
270 }
271
272 func (a *Agent) printMetricsAudit() {
273 if a.auditAnalyzer == nil {
274 return
275 }
276
277 // Print the analysis report
278 if a.auditSummary {
279 a.auditAnalyzer.PrintSummary()
280 } else {
281 a.auditAnalyzer.PrintReport()
282 }
283 }
284
285 func (a *Agent) serviceDiscoveryEnabled() bool {
286 if a == nil {
287 return false
288 }
289 return !a.DisableServiceDiscovery && a.runModePolicy.EnableServiceDiscovery
290 }
291
292 func (a *Agent) setupRuntimeService() (runtimecomp.Service, func()) {
293 if a == nil || !a.runModePolicy.EnableRuntimeCharts {
294 return nil, nil
295 }
296
297 svc := runtimechartemit.New(a.Logger.With(slog.String("component", "runtime metrics service")))
298 svc.Start(a.Name, a.Out)
299 return svc, svc.Stop
300 }
301
302 func (a *Agent) signalAuditComplete() {
303 a.quitOnce.Do(func() {
304 a.Infof("metrics-audit data collection complete, shutting down")
305 select {
306 case a.quitCh <- struct{}{}:
307 default:
308 }
309 })
310 }