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
+}