master
go 178 lines 4.71 KB
Raw
1 // SPDX-License-Identifier: GPL-3.0-or-later
2
3 package runtimechartemit
4
5 import (
6 "fmt"
7 "maps"
8 "sort"
9 "strings"
10 "sync"
11
12 "github.com/netdata/netdata/go/plugins/pkg/metrix"
13 "github.com/netdata/netdata/go/plugins/plugin/agent/internal/naming"
14 "github.com/netdata/netdata/go/plugins/plugin/framework/chartemit"
15 "github.com/netdata/netdata/go/plugins/plugin/framework/runtimecomp"
16 )
17
18 // Minimal valid template for runtime components that rely on autogen.
19 // It intentionally matches no real metric; autogen handles real series.
20 const defaultAutogenTemplateYAML = `
21 version: v1
22 groups:
23 - family: Internal
24 metrics:
25 - __runtime_dummy_metric
26 charts:
27 - id: runtime_dummy
28 title: Runtime Dummy
29 context: netdata.go.plugin.runtime_internal.runtime_dummy
30 units: "1"
31 dimensions:
32 - selector: __runtime_dummy_metric
33 name: value
34 `
35
36 // ComponentConfig registers one internal/runtime metrics component.
37 //
38 // The component provides:
39 // - RuntimeStore: writer-owned internal metrics store.
40 // - TemplateYAML: chart template for turning that store into chart actions.
41 // - EmitEnv fields: chart emission metadata (TypeID, plugin/module/job labels).
42 //
43 // Emission cadence is owned by the dedicated runtime metrics job.
44 type ComponentConfig = runtimecomp.ComponentConfig
45
46 type componentSpec struct {
47 Name string
48 Generation uint64
49
50 Store metrix.RuntimeStore
51 TemplateYAML []byte
52 UpdateEvery int
53 Autogen runtimecomp.AutogenPolicy
54 EmitEnv chartemit.EmitEnv
55 }
56
57 type componentRegistry struct {
58 mu sync.RWMutex
59 rev uint64
60 items map[string]componentSpec
61 }
62
63 func newComponentRegistry() *componentRegistry {
64 return &componentRegistry{
65 items: make(map[string]componentSpec),
66 }
67 }
68
69 func (r *componentRegistry) upsert(spec componentSpec) uint64 {
70 r.mu.Lock()
71 defer r.mu.Unlock()
72
73 r.rev++
74 spec.Generation = r.rev
75 r.items[spec.Name] = spec
76 return spec.Generation
77 }
78
79 func (r *componentRegistry) remove(name string) {
80 r.mu.Lock()
81 defer r.mu.Unlock()
82 delete(r.items, name)
83 }
84
85 func (r *componentRegistry) snapshot() []componentSpec {
86 r.mu.RLock()
87 defer r.mu.RUnlock()
88
89 out := make([]componentSpec, 0, len(r.items))
90 for _, spec := range r.items {
91 out = append(out, componentSpec{
92 Name: spec.Name,
93 Generation: spec.Generation,
94 Store: spec.Store,
95 TemplateYAML: append([]byte(nil),
96 spec.TemplateYAML...),
97 UpdateEvery: spec.UpdateEvery,
98 Autogen: spec.Autogen,
99 EmitEnv: cloneEmitEnv(spec.EmitEnv),
100 })
101 }
102 sort.Slice(out, func(i, j int) bool { return out[i].Name < out[j].Name })
103 return out
104 }
105
106 func cloneEmitEnv(env chartemit.EmitEnv) chartemit.EmitEnv {
107 out := env
108 out.JobLabels = maps.Clone(env.JobLabels)
109 if env.HostScope != nil {
110 out.HostScope = &chartemit.HostScope{
111 GUID: env.HostScope.GUID,
112 }
113 if env.HostScope.Define != nil {
114 define := *env.HostScope.Define
115 define.Labels = maps.Clone(env.HostScope.Define.Labels)
116 out.HostScope.Define = &define
117 }
118 }
119 return out
120 }
121
122 func normalizeComponent(cfg ComponentConfig, pluginName string) (componentSpec, error) {
123 name := strings.TrimSpace(cfg.Name)
124 if name == "" {
125 return componentSpec{}, fmt.Errorf("runtimemgr: runtime component name is required")
126 }
127 if cfg.Store == nil {
128 return componentSpec{}, fmt.Errorf("runtimemgr: runtime component %q store is required", name)
129 }
130 templateYAML := cfg.TemplateYAML
131 if len(templateYAML) == 0 {
132 if !cfg.Autogen.Enabled {
133 return componentSpec{}, fmt.Errorf("runtimemgr: runtime component %q template is required when autogen is disabled", name)
134 }
135 templateYAML = []byte(defaultAutogenTemplateYAML)
136 }
137 updateEvery := cfg.UpdateEvery
138 if updateEvery <= 0 {
139 updateEvery = 1
140 }
141
142 typeID := strings.TrimSpace(cfg.TypeID)
143 if typeID == "" {
144 typeID = defaultInternalTypeID(pluginName, name)
145 }
146 env := chartemit.EmitEnv{
147 TypeID: typeID,
148 UpdateEvery: updateEvery,
149 Plugin: firstNotEmpty(strings.TrimSpace(cfg.Plugin), pluginName),
150 Module: firstNotEmpty(strings.TrimSpace(cfg.Module), "internal"),
151 JobName: firstNotEmpty(strings.TrimSpace(cfg.JobName), name),
152 JobLabels: maps.Clone(cfg.JobLabels),
153 }
154
155 return componentSpec{
156 Name: name,
157 Store: cfg.Store,
158 TemplateYAML: append([]byte(nil), templateYAML...),
159 UpdateEvery: updateEvery,
160 Autogen: cfg.Autogen,
161 EmitEnv: env,
162 }, nil
163 }
164
165 func firstNotEmpty(items ...string) string {
166 for _, item := range items {
167 if item = strings.TrimSpace(item); item != "" {
168 return item
169 }
170 }
171 return ""
172 }
173
174 func defaultInternalTypeID(pluginName, componentName string) string {
175 plugin := naming.Sanitize(firstNotEmpty(pluginName, "go.d"))
176 component := naming.Sanitize(componentName)
177 return fmt.Sprintf("netdata.%s.internal.%s", plugin, component)
178 }