@cryptotaxi247 / netdata-1 / commits / 0c03fd925

chore(go.d/agent): improve terminal-mode behavior (#22125)

Ilya Mashchenko committed Apr 3, 2026 at 11:44 UTC 0c03fd9259f80a6a40322eee6ff8a7872311deef
9 files changed +238 -27
src/go/cmd/godplugin/main.go
+6 -12
@@ -78,6 +78,8 @@ func main() {
78 isInsideK8s := hostinfo.IsInsideK8sCluster()
79 moduleRegistry := moduleRegistryWithSystemdPolicy(collectorapi.DefaultRegistry, hostinfo.SystemdVersion)
80
81 + runModePolicy := policy.Agent(isTerminal)
82 +
83 a := agent.New(agent.Config{
84 Name: executable.Name,
85 PluginConfigDir: pluginconfig.ConfigDir(),
@@ -87,11 +89,7 @@ func main() {
89 VarLibDir: pluginconfig.VarLibDir(),
90 ModuleRegistry: moduleRegistry,
91 IsInsideK8s: isInsideK8s,
90 - RunModePolicy: policy.RunModePolicy{
91 - IsTerminal: isTerminal,
92 - AutoEnableDiscovered: isTerminal,
93 - UseFileStatusPersistence: !isTerminal,
94 - },
92 + RunModePolicy: runModePolicy,
93 DiscoveryProviders: []discovery.ProviderFactory{
94 discoveryproviders.File(),
95 discoveryproviders.Dummy(),
@@ -202,13 +200,9 @@ func runFunctionCLI(opts *cli.Option) int {
200 defer cancel()
201
202 jobMgr := jobmgr.New(jobmgr.Config{
205 - PluginName: executable.Name,
206 - Out: io.Discard,
207 - RunModePolicy: policy.RunModePolicy{
208 - IsTerminal: false,
209 - AutoEnableDiscovered: true,
210 - UseFileStatusPersistence: true,
211 - },
203 + PluginName: executable.Name,
204 + Out: io.Discard,
205 + RunModePolicy: policy.FunctionCLI(),
206 VarLibDir: pluginconfig.VarLibDir(),
207 Modules: collectorapi.Registry{moduleName: creator},
208 ConfigDefaults: reg,
src/go/cmd/ibmdplugin/main.go
+1 -5
@@ -120,11 +120,7 @@ func main() {
120 VarLibDir: pluginconfig.VarLibDir(),
121 ModuleRegistry: collectorapi.DefaultRegistry,
122 IsInsideK8s: hostinfo.IsInsideK8sCluster(),
123 - RunModePolicy: policy.RunModePolicy{
124 - IsTerminal: isTerminal,
125 - AutoEnableDiscovered: isTerminal,
126 - UseFileStatusPersistence: !isTerminal,
127 - },
123 + RunModePolicy: policy.Agent(isTerminal),
124 DiscoveryProviders: []discovery.ProviderFactory{
125 discoveryproviders.File(),
126 discoveryproviders.Dummy(),
src/go/cmd/scriptsdplugin/main.go
+1 -5
@@ -75,11 +75,7 @@ func main() {
75 VarLibDir: pluginconfig.VarLibDir(),
76 ModuleRegistry: collectorapi.DefaultRegistry,
77 IsInsideK8s: hostinfo.IsInsideK8sCluster(),
78 - RunModePolicy: policy.RunModePolicy{
79 - IsTerminal: isTerminal,
80 - AutoEnableDiscovered: isTerminal,
81 - UseFileStatusPersistence: !isTerminal,
82 - },
78 + RunModePolicy: policy.Agent(isTerminal),
79 DiscoveryProviders: []discovery.ProviderFactory{
80 discoveryproviders.File(),
81 discoveryproviders.Dummy(),
src/go/plugin/agent/agent.go
+22 -3
@@ -22,6 +22,7 @@ import (
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.
@@ -227,9 +228,10 @@ func (a *Agent) run(ctx context.Context) {
228 return
229 }
230
230 - runtimeSvc := runtimechartemit.New(a.Logger.With(slog.String("component", "runtime metrics service")))
231 - runtimeSvc.Start(a.Name, a.Out)
232 - defer runtimeSvc.Stop()
231 + runtimeSvc, stopRuntimeSvc := a.setupRuntimeService()
232 + if stopRuntimeSvc != nil {
233 + defer stopRuntimeSvc()
234 + }
235 fnMgr.SetRuntimeService(runtimeSvc)
236
237 var runJob []string
@@ -283,6 +285,23 @@ func (a *Agent) printMetricsAudit() {
285 }
286 }
287
288 +func (a *Agent) serviceDiscoveryEnabled() bool {
289 + if a == nil {
290 + return false
291 + }
292 + return !a.DisableServiceDiscovery && a.runModePolicy.EnableServiceDiscovery
293 +}
294 +
295 +func (a *Agent) setupRuntimeService() (runtimecomp.Service, func()) {
296 + if a == nil || !a.runModePolicy.EnableRuntimeCharts {
297 + return nil, nil
298 + }
299 +
300 + svc := runtimechartemit.New(a.Logger.With(slog.String("component", "runtime metrics service")))
301 + svc.Start(a.Name, a.Out)
302 + return svc, svc.Stop
303 +}
304 +
305 func (a *Agent) signalAuditComplete() {
306 a.quitOnce.Do(func() {
307 a.Infof("metrics-audit data collection complete, shutting down")
src/go/plugin/agent/agent_test.go
+71
@@ -5,6 +5,7 @@ package agent
5 import (
6 "bytes"
7 "context"
8 + "io"
9 "sync"
10 "testing"
11 "time"
@@ -16,6 +17,7 @@ import (
17 "github.com/netdata/netdata/go/plugins/plugin/framework/collectorapi"
18
19 "github.com/stretchr/testify/assert"
20 + "github.com/stretchr/testify/require"
21 )
22
23 func TestNew(t *testing.T) {
@@ -31,6 +33,74 @@ func TestNew(t *testing.T) {
33 })
34 }
35
36 +func TestAgent_serviceDiscoveryEnabled(t *testing.T) {
37 + tests := map[string]struct {
38 + agent *Agent
39 + want bool
40 + }{
41 + "non-terminal policy enables service discovery": {
42 + agent: &Agent{runModePolicy: policy.Agent(false)},
43 + want: true,
44 + },
45 + "terminal policy disables service discovery": {
46 + agent: &Agent{runModePolicy: policy.Agent(true)},
47 + want: false,
48 + },
49 + "plugin-level disable wins over policy": {
50 + agent: &Agent{
51 + runModePolicy: policy.Agent(false),
52 + DisableServiceDiscovery: true,
53 + },
54 + want: false,
55 + },
56 + }
57 +
58 + for name, test := range tests {
59 + t.Run(name, func(t *testing.T) {
60 + require.NotNil(t, test.agent)
61 + assert.Equal(t, test.want, test.agent.serviceDiscoveryEnabled())
62 + })
63 + }
64 +}
65 +
66 +func TestAgent_setupRuntimeService(t *testing.T) {
67 + tests := map[string]struct {
68 + policy policy.RunModePolicy
69 + wantEnabled bool
70 + }{
71 + "terminal mode disables runtime service": {
72 + policy: policy.Agent(true),
73 + wantEnabled: false,
74 + },
75 + "non-terminal mode enables runtime service": {
76 + policy: policy.Agent(false),
77 + wantEnabled: true,
78 + },
79 + }
80 +
81 + for name, test := range tests {
82 + t.Run(name, func(t *testing.T) {
83 + a := New(Config{
84 + Name: "test",
85 + RunModePolicy: test.policy,
86 + })
87 + require.NotNil(t, a)
88 + a.Out = io.Discard
89 +
90 + svc, stop := a.setupRuntimeService()
91 + if !test.wantEnabled {
92 + assert.Nil(t, svc)
93 + assert.Nil(t, stop)
94 + return
95 + }
96 +
97 + require.NotNil(t, svc)
98 + require.NotNil(t, stop)
99 + stop()
100 + })
101 + }
102 +}
103 +
104 func TestAgent_Run(t *testing.T) {
105 a := New(Config{
106 Name: "test",
@@ -38,6 +108,7 @@ func TestAgent_Run(t *testing.T) {
108 IsTerminal: false,
109 AutoEnableDiscovered: true,
110 UseFileStatusPersistence: true,
111 + EnableRuntimeCharts: true,
112 },
113 DiscoveryProviders: []discovery.ProviderFactory{
114 discovery.NewProviderFactory("dummy", func(ctx discovery.BuildContext) (discovery.Discoverer, bool, error) {
src/go/plugin/agent/policy/runmode.go
+24
@@ -7,4 +7,28 @@ type RunModePolicy struct {
7 IsTerminal bool
8 AutoEnableDiscovered bool
9 UseFileStatusPersistence bool
10 + EnableServiceDiscovery bool
11 + EnableRuntimeCharts bool
12 +}
13 +
14 +// Agent returns the shared defaults for long-lived agent processes.
15 +func Agent(isTerminal bool) RunModePolicy {
16 + return RunModePolicy{
17 + IsTerminal: isTerminal,
18 + AutoEnableDiscovered: isTerminal,
19 + UseFileStatusPersistence: !isTerminal,
20 + EnableServiceDiscovery: !isTerminal,
21 + EnableRuntimeCharts: !isTerminal,
22 + }
23 +}
24 +
25 +// FunctionCLI returns the shared defaults for one-shot function execution.
26 +func FunctionCLI() RunModePolicy {
27 + return RunModePolicy{
28 + IsTerminal: false,
29 + AutoEnableDiscovered: true,
30 + UseFileStatusPersistence: true,
31 + EnableServiceDiscovery: false,
32 + EnableRuntimeCharts: false,
33 + }
34 }
src/go/plugin/agent/policy/runmode_test.go new
+55
@@ -0,0 +1,55 @@
1 +// SPDX-License-Identifier: GPL-3.0-or-later
2 +
3 +package policy
4 +
5 +import (
6 + "testing"
7 +
8 + "github.com/stretchr/testify/assert"
9 + "github.com/stretchr/testify/require"
10 +)
11 +
12 +func TestRunModeConstructors(t *testing.T) {
13 + tests := map[string]struct {
14 + makePolicy func() RunModePolicy
15 + want RunModePolicy
16 + }{
17 + "agent terminal": {
18 + makePolicy: func() RunModePolicy { return Agent(true) },
19 + want: RunModePolicy{
20 + IsTerminal: true,
21 + AutoEnableDiscovered: true,
22 + UseFileStatusPersistence: false,
23 + EnableServiceDiscovery: false,
24 + EnableRuntimeCharts: false,
25 + },
26 + },
27 + "agent daemon": {
28 + makePolicy: func() RunModePolicy { return Agent(false) },
29 + want: RunModePolicy{
30 + IsTerminal: false,
31 + AutoEnableDiscovered: false,
32 + UseFileStatusPersistence: true,
33 + EnableServiceDiscovery: true,
34 + EnableRuntimeCharts: true,
35 + },
36 + },
37 + "function cli": {
38 + makePolicy: FunctionCLI,
39 + want: RunModePolicy{
40 + IsTerminal: false,
41 + AutoEnableDiscovered: true,
42 + UseFileStatusPersistence: true,
43 + EnableServiceDiscovery: false,
44 + EnableRuntimeCharts: false,
45 + },
46 + },
47 + }
48 +
49 + for name, test := range tests {
50 + t.Run(name, func(t *testing.T) {
51 + require.NotNil(t, test.makePolicy)
52 + assert.Equal(t, test.want, test.makePolicy())
53 + })
54 + }
55 +}
src/go/plugin/agent/setup.go
+1 -2
@@ -88,8 +88,7 @@ func (a *Agent) buildDiscoveryConf(enabled collectorapi.Registry, fnReg function
88
89 watchPaths := a.CollectorsConfigWatchPath
90 sdConfDir := a.ServiceDiscoveryConfigDir
91 - if a.DisableServiceDiscovery {
92 - dummyPaths = nil
91 + if !a.serviceDiscoveryEnabled() {
92 sdConfDir = nil
93 }
94
src/go/plugin/agent/setup_test.go
+57
@@ -8,6 +8,7 @@ import (
8 "testing"
9
10 "github.com/netdata/netdata/go/plugins/plugin/agent/discovery"
11 + "github.com/netdata/netdata/go/plugins/plugin/agent/policy"
12 "github.com/netdata/netdata/go/plugins/plugin/agent/secrets/secretstore"
13 "github.com/netdata/netdata/go/plugins/plugin/framework/collectorapi"
14 "github.com/netdata/netdata/go/plugins/plugin/framework/confgroup"
@@ -300,3 +301,59 @@ func TestAgent_buildDiscoveryConf(t *testing.T) {
301 assert.Len(t, cfg.Providers, 1)
302 })
303 }
304 +
305 +func TestAgent_buildDiscoveryConf_serviceDiscoveryGating(t *testing.T) {
306 + providers := []discovery.ProviderFactory{
307 + discovery.NewProviderFactory("noop", nil),
308 + }
309 + enabled := collectorapi.Registry{
310 + "module1": collectorapi.Creator{},
311 + }
312 +
313 + tests := map[string]struct {
314 + agent *Agent
315 + wantSDDir []string
316 + wantWatchPath []string
317 + }{
318 + "terminal mode disables service discovery without changing collector watch paths": {
319 + agent: &Agent{
320 + runModePolicy: policy.Agent(true),
321 + ServiceDiscoveryConfigDir: []string{"sd"},
322 + CollectorsConfDir: []string{"collectors"},
323 + CollectorsConfigWatchPath: []string{"watch/*.conf"},
324 + DiscoveryProviders: providers,
325 + },
326 + wantWatchPath: []string{"watch/*.conf"},
327 + },
328 + "plugin-level disable overrides non-terminal service discovery policy": {
329 + agent: &Agent{
330 + runModePolicy: policy.Agent(false),
331 + DisableServiceDiscovery: true,
332 + ServiceDiscoveryConfigDir: []string{"sd"},
333 + CollectorsConfDir: []string{"collectors"},
334 + DiscoveryProviders: providers,
335 + },
336 + },
337 + "non-terminal mode keeps service discovery enabled": {
338 + agent: &Agent{
339 + runModePolicy: policy.Agent(false),
340 + ServiceDiscoveryConfigDir: []string{"sd"},
341 + CollectorsConfDir: []string{"collectors"},
342 + CollectorsConfigWatchPath: []string{"watch/*.conf"},
343 + DiscoveryProviders: providers,
344 + },
345 + wantSDDir: []string{"sd"},
346 + wantWatchPath: []string{"watch/*.conf"},
347 + },
348 + }
349 +
350 + for name, test := range tests {
351 + t.Run(name, func(t *testing.T) {
352 + require.NotNil(t, test.agent)
353 +
354 + cfg := test.agent.buildDiscoveryConf(enabled, nil)
355 + assert.Equal(t, test.wantSDDir, []string(cfg.BuildContext.Paths.ServiceDiscoveryConfigDir))
356 + assert.Equal(t, test.wantWatchPath, cfg.BuildContext.Paths.CollectorsConfigWatchPath)
357 + })
358 + }
359 +}