master
go 190 lines 5.48 KB
Raw
1 // SPDX-License-Identifier: GPL-3.0-or-later
2
3 package chartengine
4
5 import (
6 "fmt"
7
8 "github.com/netdata/netdata/go/plugins/logger"
9 "github.com/netdata/netdata/go/plugins/pkg/metrix"
10 metrixselector "github.com/netdata/netdata/go/plugins/pkg/metrix/selector"
11 "github.com/netdata/netdata/go/plugins/plugin/framework/runtimecomp"
12 )
13
14 type engineConfig struct {
15 autogen AutogenPolicy
16 autogenTypeID string
17 // autogenContextNamespace prefixes autogen chart contexts (the spec's root
18 // context_namespace), so autogen and template charts share one namespace.
19 autogenContextNamespace string
20 selector metrixselector.Selector
21 autogenOverride policyOverride[AutogenPolicy]
22 selectorOverride policyOverride[metrixselector.Selector]
23 runtimeStore metrix.RuntimeStore
24 runtimeStoreSet bool
25 runtimeObserver func(PlanRuntimeSample)
26 log *logger.Logger
27 seriesSelection seriesSelectionMode
28 runtimePlanner bool
29 }
30
31 type policyOverride[T any] struct {
32 set bool
33 value T
34 }
35
36 // Option mutates engine configuration at construction time.
37 type Option func(*engineConfig) error
38
39 const (
40 defaultMaxTypeIDLen = 1200
41 )
42
43 type seriesSelectionMode uint8
44
45 const (
46 seriesSelectionLastSuccessOnly seriesSelectionMode = iota
47 seriesSelectionAllVisible
48 )
49
50 // AutogenPolicy controls unmatched-series fallback chart generation.
51 // It aliases runtime component policy to keep one policy contract.
52 type AutogenPolicy = runtimecomp.AutogenPolicy
53
54 // EnginePolicy controls chartengine matching/materialization behavior.
55 type EnginePolicy struct {
56 // Selector filters input series globally before template/autogen routing.
57 // Nil means "no override", and an explicitly empty expr means "override to allow all".
58 Selector *metrixselector.Expr
59
60 // Autogen controls unmatched-series fallback behavior.
61 Autogen *AutogenPolicy
62 }
63
64 func defaultAutogenPolicy() AutogenPolicy {
65 return AutogenPolicy{
66 Enabled: false,
67 MaxTypeIDLen: defaultMaxTypeIDLen,
68 }
69 }
70
71 func normalizeAutogenPolicy(policy AutogenPolicy) (AutogenPolicy, error) {
72 maxLen := policy.MaxTypeIDLen
73 if maxLen <= 0 {
74 maxLen = defaultMaxTypeIDLen
75 }
76 if maxLen < 4 {
77 return AutogenPolicy{}, fmt.Errorf("autogen max type.id len must be >= 4, got %d", maxLen)
78 }
79 policy.MaxTypeIDLen = maxLen
80 return policy, nil
81 }
82
83 func compileEngineSelector(expr metrixselector.Expr) (metrixselector.Selector, error) {
84 if expr.Empty() {
85 return nil, nil
86 }
87 return expr.Parse()
88 }
89
90 // WithEnginePolicy configures chartengine matching/materialization policy.
91 func WithEnginePolicy(policy EnginePolicy) Option {
92 return func(cfg *engineConfig) error {
93 if policy.Autogen != nil {
94 autogen, err := normalizeAutogenPolicy(*policy.Autogen)
95 if err != nil {
96 return err
97 }
98 cfg.autogenOverride = policyOverride[AutogenPolicy]{set: true, value: autogen}
99 cfg.autogen = autogen
100 }
101 if policy.Selector != nil {
102 selector, err := compileEngineSelector(*policy.Selector)
103 if err != nil {
104 return fmt.Errorf("invalid engine selector: %w", err)
105 }
106 cfg.selectorOverride = policyOverride[metrixselector.Selector]{set: true, value: selector}
107 cfg.selector = selector
108 }
109 return nil
110 }
111 }
112
113 // WithRuntimeStore configures internal chartengine runtime metrics store.
114 // Passing nil disables chartengine self-metrics.
115 func WithRuntimeStore(store metrix.RuntimeStore) Option {
116 return func(cfg *engineConfig) error {
117 cfg.runtimeStore = store
118 cfg.runtimeStoreSet = true
119 return nil
120 }
121 }
122
123 // WithRuntimeSampleObserver configures a callback for per-build runtime samples.
124 //
125 // The callback fires for successful builds, build errors, and collect-status
126 // skips. It does not fire for pre-build contract errors such as an outstanding
127 // plan attempt.
128 //
129 // The callback is in addition to WithRuntimeStore. Pass WithRuntimeStore(nil)
130 // when samples are aggregated elsewhere and the engine should not write its own
131 // runtime metrics.
132 func WithRuntimeSampleObserver(fn func(PlanRuntimeSample)) Option {
133 return func(cfg *engineConfig) error {
134 cfg.runtimeObserver = fn
135 return nil
136 }
137 }
138
139 // WithLogger configures chartengine logger.
140 func WithLogger(l *logger.Logger) Option {
141 return func(cfg *engineConfig) error {
142 cfg.log = l
143 return nil
144 }
145 }
146
147 // WithSeriesSelectionAllVisible configures planner scan to use all visible
148 // series from the reader (instead of only latest-success-seq series).
149 // This is intended for runtime/internal stores.
150 func WithSeriesSelectionAllVisible() Option {
151 return func(cfg *engineConfig) error {
152 cfg.seriesSelection = seriesSelectionAllVisible
153 return nil
154 }
155 }
156
157 // WithRuntimePlannerMode enables runtime/internal planner semantics for
158 // build-cycle dedupe bookkeeping while keeping lifecycle/cache tied to
159 // source LastSuccessSeq.
160 func WithRuntimePlannerMode() Option {
161 return func(cfg *engineConfig) error {
162 cfg.runtimePlanner = true
163 return nil
164 }
165 }
166
167 // WithEmitTypeIDBudgetPrefix configures chartengine autogen type-id budget
168 // checks to use the effective emission type-id prefix (for example job fullName).
169 func WithEmitTypeIDBudgetPrefix(typeID string) Option {
170 return func(cfg *engineConfig) error {
171 cfg.autogenTypeID = typeID
172 return nil
173 }
174 }
175
176 func applyOptions(opts ...Option) (engineConfig, error) {
177 cfg := engineConfig{
178 autogen: defaultAutogenPolicy(),
179 seriesSelection: seriesSelectionLastSuccessOnly,
180 }
181 for i, opt := range opts {
182 if opt == nil {
183 continue
184 }
185 if err := opt(&cfg); err != nil {
186 return engineConfig{}, fmt.Errorf("chartengine: option[%d]: %w", i, err)
187 }
188 }
189 return cfg, nil
190 }