feat(go.d/framework): add float dimension option handling (#21825)
Ilya Mashchenko committed
Feb 26, 2026 at 12:47 UTC
654770c71b7b97a10732009e5b6e66c3d8e9b414
23 files changed
+220
-16
src/go/pkg/metrix/README.md
+1
-1
@@ -81,7 +81,7 @@
81
| `WithSummaryReservoirSize(...)` | Stateful summary estimator size |
82
| `WithStateSetStates(...)` | StateSet allowed states |
83
| `WithStateSetMode(...)` | `ModeBitSet` (multiple simultaneous active states) or `ModeEnum` (exactly one active state) |
84
-| `WithDescription(...)`, `WithChartFamily(...)`, `WithUnit(...)` | Metric metadata hints for downstream consumers (e.g., autogen) |
84
+| `WithDescription(...)`, `WithChartFamily(...)`, `WithUnit(...)`, `WithFloat(...)` | Metric metadata hints for downstream consumers (e.g., autogen chart identity + float SET mode) |
85
86
## Read Modes
87
src/go/pkg/metrix/collector_store.go
+4
@@ -499,6 +499,7 @@ func (c *storeCore) registerInstrument(name string, kind metricKind, mode metric
499
Description: strings.TrimSpace(cfg.description),
500
ChartFamily: strings.TrimSpace(cfg.chartFamily),
501
Unit: strings.TrimSpace(cfg.unit),
502
+ Float: cfg.float,
503
}
504
505
var histogram *histogramSchema
@@ -564,6 +565,9 @@ func (c *storeCore) registerInstrument(name string, kind metricKind, mode metric
565
if cfg.unitSet && d.meta.Unit != metricMeta.Unit {
566
return nil, fmt.Errorf("metrix: metric unit mismatch for %s", name)
567
}
568
+ if cfg.floatSet && d.meta.Float != metricMeta.Float {
569
+ return nil, fmt.Errorf("metrix: metric float mismatch for %s", name)
570
+ }
571
return d, nil
572
}
573
src/go/pkg/metrix/metric_meta_store_test.go
+18
@@ -22,6 +22,7 @@ func TestMetricMetaScenarios(t *testing.T) {
22
WithDescription("Busy workers"),
23
WithChartFamily("Workers"),
24
WithUnit("workers"),
25
+ WithFloat(true),
26
)
27
28
cc.BeginCycle()
@@ -33,6 +34,7 @@ func TestMetricMetaScenarios(t *testing.T) {
34
assert.Equal(t, "Busy workers", meta.Description)
35
assert.Equal(t, "Workers", meta.ChartFamily)
36
assert.Equal(t, "workers", meta.Unit)
37
+ assert.True(t, meta.Float)
38
},
39
},
40
"unknown metric metadata is unavailable": {
@@ -53,6 +55,7 @@ func TestMetricMetaScenarios(t *testing.T) {
55
WithDescription("Latency"),
56
WithChartFamily("Service"),
57
WithUnit("ms"),
58
+ WithFloat(true),
59
)
60
61
cc.BeginCycle()
@@ -76,18 +79,21 @@ func TestMetricMetaScenarios(t *testing.T) {
79
assert.Equal(t, "Latency", meta.Description)
80
assert.Equal(t, "Service", meta.ChartFamily)
81
assert.Equal(t, "ms", meta.Unit)
82
+ assert.True(t, meta.Float)
83
84
meta, ok = flat.MetricMeta("svc.latency_count")
85
require.True(t, ok, "expected flattened histogram count metric metadata")
86
assert.Equal(t, "Latency", meta.Description)
87
assert.Equal(t, "Service", meta.ChartFamily)
88
assert.Equal(t, "ms", meta.Unit)
89
+ assert.True(t, meta.Float)
90
91
meta, ok = flat.MetricMeta("svc.latency_sum")
92
require.True(t, ok, "expected flattened histogram sum metric metadata")
93
assert.Equal(t, "Latency", meta.Description)
94
assert.Equal(t, "Service", meta.ChartFamily)
95
assert.Equal(t, "ms", meta.Unit)
96
+ assert.True(t, meta.Float)
97
},
98
},
99
"metadata redeclaration conflict panics": {
@@ -100,6 +106,16 @@ func TestMetricMetaScenarios(t *testing.T) {
106
})
107
},
108
},
109
+ "float metadata redeclaration conflict panics": {
110
+ run: func(t *testing.T) {
111
+ s := NewCollectorStore()
112
+ w := s.Write().SnapshotMeter("apache")
113
+ _ = w.Gauge("workers_busy", WithFloat(true))
114
+ expectPanic(t, func() {
115
+ _ = w.Gauge("workers_busy", WithFloat(false))
116
+ })
117
+ },
118
+ },
119
"redeclare without metadata options keeps first metadata": {
120
run: func(t *testing.T) {
121
s := NewCollectorStore()
@@ -109,6 +125,7 @@ func TestMetricMetaScenarios(t *testing.T) {
125
WithDescription("Busy workers"),
126
WithChartFamily("Workers"),
127
WithUnit("workers"),
128
+ WithFloat(true),
129
)
130
_ = w.Gauge("workers_busy")
131
@@ -122,6 +139,7 @@ func TestMetricMetaScenarios(t *testing.T) {
139
assert.Equal(t, "Busy workers", meta.Description)
140
assert.Equal(t, "Workers", meta.ChartFamily)
141
assert.Equal(t, "workers", meta.Unit)
142
+ assert.True(t, meta.Float)
143
},
144
},
145
}
src/go/pkg/metrix/options.go
+10
@@ -30,6 +30,8 @@ type instrumentConfig struct {
30
chartFamily string
31
unitSet bool
32
unit string
33
+ floatSet bool
34
+ float bool
35
}
36
37
func WithFreshness(policy FreshnessPolicy) InstrumentOption {
@@ -103,3 +105,11 @@ func WithUnit(unit string) InstrumentOption {
105
cfg.unit = unit
106
})
107
}
108
+
109
+// WithFloat sets optional metric-family float-dimension metadata hint.
110
+func WithFloat(isFloat bool) InstrumentOption {
111
+ return optionFunc(func(cfg *instrumentConfig) {
112
+ cfg.floatSet = true
113
+ cfg.float = isFloat
114
+ })
115
+}
src/go/pkg/metrix/types.go
+1
@@ -54,6 +54,7 @@ type MetricMeta struct {
54
Description string
55
ChartFamily string
56
Unit string
57
+ Float bool
58
}
59
60
// MetricKind identifies the logical metric family type.
src/go/plugin/framework/chartemit/apply.go
+8
-6
@@ -29,6 +29,7 @@ type normalizedActions struct {
29
type dimensionEmission struct {
30
Name string
31
Hidden bool
32
+ Float bool
33
Algorithm string
34
Multiplier int
35
Divisor int
@@ -124,6 +125,7 @@ func emitCreatePhase(api *netdataapi.API, env EmitEnv, actions normalizedActions
125
emitDimension(api, dimensionEmission{
126
Name: dim.Name,
127
Hidden: dim.Hidden,
128
+ Float: dim.Float,
129
Algorithm: string(dim.Algorithm),
130
Multiplier: dim.Multiplier,
131
Divisor: dim.Divisor,
@@ -151,6 +153,7 @@ func emitCreatePhase(api *netdataapi.API, env EmitEnv, actions normalizedActions
153
emitDimension(api, dimensionEmission{
154
Name: dim.Name,
155
Hidden: dim.Hidden,
156
+ Float: dim.Float,
157
Algorithm: string(dim.Algorithm),
158
Multiplier: dim.Multiplier,
159
Divisor: dim.Divisor,
@@ -167,13 +170,11 @@ func emitUpdatePhase(api *netdataapi.API, env EmitEnv, updates []UpdateChartActi
170
api.SETEMPTY(sanitizeWireID(dim.Name))
171
continue
172
}
170
- value := dim.Int64
173
if dim.IsFloat {
172
- // NOTE: V2 currently emits integer SET only.
173
- // TODO(godv2): switch to SETFLOAT when Netdata float wire support is available.
174
- value = int64(dim.Float64)
174
+ api.SETFLOAT(sanitizeWireID(dim.Name), dim.Float64)
175
+ continue
176
}
176
- api.SET(sanitizeWireID(dim.Name), value)
177
+ api.SET(sanitizeWireID(dim.Name), dim.Int64)
178
}
179
api.END()
180
}
@@ -185,6 +186,7 @@ func emitRemovePhase(api *netdataapi.API, env EmitEnv, actions normalizedActions
186
emitDimension(api, dimensionEmission{
187
Name: removeDim.Name,
188
Hidden: removeDim.Hidden,
189
+ Float: removeDim.Float,
190
Algorithm: string(removeDim.Algorithm),
191
Multiplier: removeDim.Multiplier,
192
Divisor: removeDim.Divisor,
@@ -207,6 +209,6 @@ func emitDimension(api *netdataapi.API, dim dimensionEmission) {
209
Algorithm: dim.Algorithm,
210
Multiplier: handleZero(dim.Multiplier),
211
Divisor: handleZero(dim.Divisor),
210
- Options: makeDimensionOptions(dim.Hidden, dim.Obsolete),
212
+ Options: makeDimensionOptions(dim.Hidden, dim.Obsolete, dim.Float),
213
})
214
}
src/go/plugin/framework/chartemit/apply_test.go
+64
-3
@@ -40,6 +40,7 @@ func TestApplyPlanEmitsNetdataWire(t *testing.T) {
40
ChartMeta: meta,
41
Name: "received",
42
Hidden: false,
43
+ Float: true,
44
Algorithm: chartengine.AlgorithmIncremental,
45
Multiplier: 1,
46
Divisor: 1,
@@ -59,6 +60,7 @@ func TestApplyPlanEmitsNetdataWire(t *testing.T) {
60
ChartMeta: meta,
61
Name: "received",
62
Hidden: false,
63
+ Float: true,
64
Algorithm: chartengine.AlgorithmIncremental,
65
Multiplier: 1,
66
Divisor: 1,
@@ -88,10 +90,10 @@ func TestApplyPlanEmitsNetdataWire(t *testing.T) {
90
assert.Contains(t, out, "CLABEL 'instance' 'localhost' '2'")
91
assert.Contains(t, out, "CLABEL '_collect_job' 'job01' '1'")
92
assert.Contains(t, out, "CLABEL_COMMIT")
91
- assert.Contains(t, out, "DIMENSION 'received' 'received' 'incremental' '1' '1' ''")
93
+ assert.Contains(t, out, "DIMENSION 'received' 'received' 'incremental' '1' '1' 'type=float'")
94
assert.Contains(t, out, "BEGIN 'collector.job.win_nic_traffic_eth0' 100")
93
- assert.Contains(t, out, "SET 'received' = 123")
94
- assert.Contains(t, out, "DIMENSION 'received' 'received' 'incremental' '1' '1' 'obsolete'")
95
+ assert.Contains(t, out, "SET 'received' = 123.5")
96
+ assert.Contains(t, out, "DIMENSION 'received' 'received' 'incremental' '1' '1' 'obsolete type=float'")
97
assert.Contains(t, out, "obsolete")
98
99
createPos := strings.Index(out, "CHART 'collector.job.win_nic_traffic_eth0'")
@@ -167,6 +169,65 @@ func TestApplyPlanAutogenChartCreateUpdateRemove(t *testing.T) {
169
assert.Contains(t, out, "obsolete")
170
}
171
172
+func TestApplyPlanUsesIntegerSETForNonFloatUpdates(t *testing.T) {
173
+ var buf bytes.Buffer
174
+ api := netdataapi.New(&buf)
175
+
176
+ meta := chartengine.ChartMeta{
177
+ Title: "Runtime jobs",
178
+ Family: "Runtime",
179
+ Context: "runtime.jobs",
180
+ Units: "jobs",
181
+ Algorithm: chartengine.AlgorithmAbsolute,
182
+ Type: chartengine.ChartTypeLine,
183
+ }
184
+
185
+ plan := Plan{
186
+ Actions: []EngineAction{
187
+ chartengine.CreateChartAction{
188
+ ChartTemplateID: "g0c0",
189
+ ChartID: "runtime_jobs",
190
+ Meta: meta,
191
+ },
192
+ chartengine.CreateDimensionAction{
193
+ ChartID: "runtime_jobs",
194
+ ChartMeta: meta,
195
+ Name: "total",
196
+ Algorithm: chartengine.AlgorithmAbsolute,
197
+ Multiplier: 1,
198
+ Divisor: 1,
199
+ },
200
+ chartengine.UpdateChartAction{
201
+ ChartID: "runtime_jobs",
202
+ Values: []chartengine.UpdateDimensionValue{
203
+ {
204
+ Name: "total",
205
+ IsFloat: false,
206
+ Int64: 7,
207
+ Float64: 7.9,
208
+ },
209
+ },
210
+ },
211
+ },
212
+ }
213
+
214
+ err := ApplyPlan(api, plan, EmitEnv{
215
+ TypeID: "collector.job",
216
+ UpdateEvery: 1,
217
+ Plugin: "go.d.plugin",
218
+ Module: "runtime",
219
+ JobName: "job01",
220
+ MSSinceLast: 1,
221
+ })
222
+ require.NoError(t, err)
223
+
224
+ out := buf.String()
225
+ assert.Contains(t, out, "DIMENSION 'total' 'total' 'absolute' '1' '1' ''")
226
+ assert.Contains(t, out, "BEGIN 'collector.job.runtime_jobs' 1")
227
+ assert.Contains(t, out, "SET 'total' = 7")
228
+ assert.NotContains(t, out, "SET 'total' = 7.9")
229
+}
230
+
231
func TestApplyPlanDimensionOnlyCreateEmitsLabelsAndCommit(t *testing.T) {
232
var buf bytes.Buffer
233
api := netdataapi.New(&buf)
src/go/plugin/framework/chartemit/order.go
+4
-1
@@ -4,7 +4,7 @@ package chartemit
4
5
import "strings"
6
7
-func makeDimensionOptions(hidden, obsolete bool) string {
7
+func makeDimensionOptions(hidden, obsolete, float bool) string {
8
var parts []string
9
if hidden {
10
parts = append(parts, "hidden")
@@ -12,6 +12,9 @@ func makeDimensionOptions(hidden, obsolete bool) string {
12
if obsolete {
13
parts = append(parts, "obsolete")
14
}
15
+ if float {
16
+ parts = append(parts, "type=float")
17
+ }
18
return strings.Join(parts, " ")
19
}
20
src/go/plugin/framework/chartengine/actions.go
+2
@@ -38,6 +38,7 @@ type CreateDimensionAction struct {
38
ChartMeta program.ChartMeta
39
Name string
40
Hidden bool
41
+ Float bool
42
Algorithm program.Algorithm
43
Multiplier int
44
Divisor int
@@ -68,6 +69,7 @@ type RemoveDimensionAction struct {
69
ChartMeta program.ChartMeta
70
Name string
71
Hidden bool
72
+ Float bool
73
Algorithm program.Algorithm
74
Multiplier int
75
Divisor int
src/go/plugin/framework/chartengine/autogen.go
+3
@@ -27,6 +27,7 @@ type autogenRoute struct {
27
family string
28
contextName string
29
staticDimension bool
30
+ float bool
31
}
32
33
type autogenSourceBuilder func(
@@ -99,6 +100,7 @@ func (e *Engine) resolveAutogenRoute(
100
Hidden: false,
101
Multiplier: 1,
102
Divisor: 1,
103
+ Float: route.float,
104
Static: route.staticDimension,
105
Inferred: false,
106
Autogen: true,
@@ -188,6 +190,7 @@ func applyAutogenMetricMeta(route autogenRoute, meta metrix.MetricMeta, seriesMe
190
route.units = normalizeAutogenUnitByAlgorithm(unit, route.algorithm)
191
route.chartType = chartTypeFromUnits(route.units)
192
}
193
+ route.float = meta.Float
194
return route
195
}
196
src/go/plugin/framework/chartengine/compiler.go
+3
@@ -239,6 +239,7 @@ func compileDimension(dim charttpl.Dimension, visibleMetrics map[string]struct{}
239
Hidden: options.hidden,
240
Multiplier: options.multiplier,
241
Divisor: options.divisor,
242
+ Float: options.float,
243
Dynamic: inferFromSeriesMeta || nameFromLabel != "",
244
},
245
selectorKeys: append([]string(nil), meta.ConstrainedLabelKeys...),
@@ -251,6 +252,7 @@ type compiledDimensionOptions struct {
252
hidden bool
253
multiplier int
254
divisor int
255
+ float bool
256
}
257
258
func compileDimensionOptions(in *charttpl.DimensionOptions) compiledDimensionOptions {
@@ -262,6 +264,7 @@ func compileDimensionOptions(in *charttpl.DimensionOptions) compiledDimensionOpt
264
return out
265
}
266
out.hidden = in.Hidden
267
+ out.float = in.Float
268
if in.Multiplier != 0 {
269
out.multiplier = in.Multiplier
270
}
src/go/plugin/framework/chartengine/compiler_test.go
+2
@@ -60,6 +60,7 @@ func TestCompileScenarios(t *testing.T) {
60
Name: "total",
61
Options: &charttpl.DimensionOptions{
62
Hidden: true,
63
+ Float: true,
64
Multiplier: -8,
65
Divisor: 1000,
66
},
@@ -76,6 +77,7 @@ func TestCompileScenarios(t *testing.T) {
77
require.Len(t, charts, 1)
78
require.Len(t, charts[0].Dimensions, 1)
79
assert.True(t, charts[0].Dimensions[0].Hidden)
80
+ assert.True(t, charts[0].Dimensions[0].Float)
81
assert.Equal(t, -8, charts[0].Dimensions[0].Multiplier)
82
assert.Equal(t, 1000, charts[0].Dimensions[0].Divisor)
83
},
src/go/plugin/framework/chartengine/internal/program/dimension.go
+2
@@ -49,6 +49,8 @@ type Dimension struct {
49
Multiplier int
50
// Divisor maps to DIMENSION divisor option.
51
Divisor int
52
+ // Float maps to DIMENSION type=float option.
53
+ Float bool
54
// Dynamic is compile-derived and true when rendering can fan out by labels.
55
Dynamic bool
56
}
src/go/plugin/framework/chartengine/lifecycle.go
+3
@@ -27,6 +27,7 @@ type materializedChartState struct {
27
type materializedDimensionState struct {
28
name string
29
hidden bool
30
+ float bool
31
static bool
32
order int
33
algorithm program.Algorithm
@@ -77,6 +78,7 @@ func (c *materializedChartState) ensureDimension(name string, state dimensionSta
78
c.orderedDimsDirty = true
79
}
80
dim.hidden = state.hidden
81
+ dim.float = state.float
82
dim.static = state.static
83
dim.order = state.order
84
dim.algorithm = state.algorithm
@@ -87,6 +89,7 @@ func (c *materializedChartState) ensureDimension(name string, state dimensionSta
89
dim = &materializedDimensionState{
90
name: name,
91
hidden: state.hidden,
92
+ float: state.float,
93
static: state.static,
94
order: state.order,
95
algorithm: state.algorithm,
src/go/plugin/framework/chartengine/matcher.go
+2
@@ -22,6 +22,7 @@ type routeBinding struct {
22
Hidden bool
23
Multiplier int
24
Divisor int
25
+ Float bool
26
Static bool
27
Inferred bool
28
Autogen bool
@@ -130,6 +131,7 @@ func (e *Engine) resolveSeriesRoutes(
131
Hidden: candidate.dimension.Hidden,
132
Multiplier: candidate.dimension.Multiplier,
133
Divisor: candidate.dimension.Divisor,
134
+ Float: candidate.dimension.Float,
135
Static: !candidate.dimension.Dynamic,
136
Inferred: candidate.dimension.InferNameFromSeriesMeta,
137
Autogen: false,
src/go/plugin/framework/chartengine/planner.go
+8
-1
@@ -72,6 +72,7 @@ type InferredDimension struct {
72
73
type dimensionState struct {
74
hidden bool
75
+ float bool
76
static bool
77
order int
78
algorithm program.Algorithm
@@ -500,6 +501,7 @@ func (ctx *planBuildContext) accumulateRoute(
501
entry.value = value
502
entry.dimensionState = dimensionState{
503
hidden: route.Hidden,
504
+ float: route.Float,
505
static: route.Static,
506
order: route.DimensionIndex,
507
algorithm: route.Algorithm,
@@ -511,6 +513,9 @@ func (ctx *planBuildContext) accumulateRoute(
513
if entry.hidden != route.Hidden {
514
// First-observed hidden flag wins within one build; conflicting routes are ignored.
515
}
516
+ if entry.float != route.Float {
517
+ // First-observed float flag wins within one build; conflicting routes are ignored.
518
+ }
519
entry.value += value
520
}
521
@@ -578,6 +583,7 @@ func (e *Engine) materializePlanCharts(ctx *planBuildContext) error {
583
ChartMeta: cs.meta,
584
Name: name,
585
Hidden: entry.hidden,
586
+ Float: entry.float,
587
Algorithm: entry.algorithm,
588
Multiplier: entry.multiplier,
589
Divisor: entry.divisor,
@@ -593,7 +599,8 @@ func (e *Engine) materializePlanCharts(ctx *planBuildContext) error {
599
if ok && entry != nil && entry.seenSeq == cs.currentBuildSeq {
600
values = append(values, UpdateDimensionValue{
601
Name: name,
596
- IsFloat: true,
602
+ IsFloat: entry.float,
603
+ Int64: int64(entry.value),
604
Float64: entry.value,
605
})
606
continue
src/go/plugin/framework/chartengine/planner_lifecycle.go
+2
@@ -245,6 +245,7 @@ func enforceDimensionCaps(
245
ChartMeta: matChart.meta,
246
Name: name,
247
Hidden: dim.hidden,
248
+ Float: dim.float,
249
Algorithm: dim.algorithm,
250
Multiplier: dim.multiplier,
251
Divisor: dim.divisor,
@@ -319,6 +320,7 @@ func collectExpiryRemovals(
320
ChartMeta: matChart.meta,
321
Name: name,
322
Hidden: dim.hidden,
323
+ Float: dim.float,
324
Algorithm: dim.algorithm,
325
Multiplier: dim.multiplier,
326
Divisor: dim.divisor,
src/go/plugin/framework/chartengine/planner_test.go
+73
-1
@@ -1056,6 +1056,58 @@ groups:
1056
assert.Equal(t, "ms/s", sum.Meta.Units)
1057
}
1058
1059
+func TestBuildPlanAutogenUsesMetricFloatMetadataForScalar(t *testing.T) {
1060
+ e, err := New(WithAutogenPolicy(AutogenPolicy{Enabled: true}))
1061
+ require.NoError(t, err)
1062
+
1063
+ yaml := `
1064
+version: v1
1065
+groups:
1066
+ - family: Service
1067
+ metrics:
1068
+ - svc.requests_total
1069
+ charts:
1070
+ - title: Requests
1071
+ context: requests
1072
+ units: requests/s
1073
+ dimensions:
1074
+ - selector: svc.requests_total
1075
+ name: total
1076
+`
1077
+ require.NoError(t, e.LoadYAML([]byte(yaml), 1))
1078
+
1079
+ store := metrix.NewCollectorStore()
1080
+ cc := mustCycleController(t, store)
1081
+ unmatched := store.Write().SnapshotMeter("svc").Gauge(
1082
+ "temperature_celsius",
1083
+ metrix.WithFloat(true),
1084
+ )
1085
+
1086
+ cc.BeginCycle()
1087
+ unmatched.Observe(10.5)
1088
+ cc.CommitCycleSuccess()
1089
+
1090
+ plan, err := e.BuildPlan(store.Read(metrix.ReadFlatten()))
1091
+ require.NoError(t, err)
1092
+
1093
+ var created *CreateDimensionAction
1094
+ for _, action := range plan.Actions {
1095
+ dim, ok := action.(CreateDimensionAction)
1096
+ if !ok || dim.ChartID != "svc.temperature_celsius" {
1097
+ continue
1098
+ }
1099
+ created = &dim
1100
+ break
1101
+ }
1102
+ require.NotNil(t, created)
1103
+ assert.True(t, created.Float)
1104
+ update := findUpdateAction(plan)
1105
+ require.NotNil(t, update)
1106
+ require.Len(t, update.Values, 1)
1107
+ assert.True(t, update.Values[0].IsFloat)
1108
+ assert.Equal(t, float64(10.5), update.Values[0].Float64)
1109
+}
1110
+
1111
func TestBuildPlanAutogenUsesMetricMetadataForSummaryWithoutQuantiles(t *testing.T) {
1112
e, err := New(WithAutogenPolicy(AutogenPolicy{Enabled: true}))
1113
require.NoError(t, err)
@@ -1126,7 +1178,7 @@ groups:
1178
store := metrix.NewCollectorStore()
1179
cc := mustCycleController(t, store)
1180
sm := store.Write().SnapshotMeter("svc")
1129
- m := sm.Counter("requests_total")
1181
+ m := sm.Counter("requests_total", metrix.WithFloat(true))
1182
methodGET := sm.LabelSet(metrix.Label{Key: "method", Value: "GET"})
1183
1184
cc.BeginCycle()
@@ -1140,6 +1192,22 @@ groups:
1192
require.NotNil(t, create)
1193
assert.Equal(t, "svc_requests", create.ChartID)
1194
assert.NotEqual(t, "svc.requests_total-method=GET", create.ChartID)
1195
+ var createdDim *CreateDimensionAction
1196
+ for _, action := range plan.Actions {
1197
+ dim, ok := action.(CreateDimensionAction)
1198
+ if !ok || dim.ChartID != "svc_requests" {
1199
+ continue
1200
+ }
1201
+ createdDim = &dim
1202
+ break
1203
+ }
1204
+ require.NotNil(t, createdDim)
1205
+ assert.False(t, createdDim.Float)
1206
+ update := findUpdateAction(plan)
1207
+ require.NotNil(t, update)
1208
+ require.Len(t, update.Values, 1)
1209
+ assert.False(t, update.Values[0].IsFloat)
1210
+ assert.Equal(t, int64(10), update.Values[0].Int64)
1211
}
1212
1213
func TestBuildPlanAutogenStrictOverflowDrop(t *testing.T) {
@@ -1517,10 +1585,12 @@ groups:
1585
name_from_label: mode
1586
options:
1587
hidden: true
1588
+ float: true
1589
- selector: m_b
1590
name_from_label: mode
1591
options:
1592
hidden: false
1593
+ float: false
1594
`
1595
require.NoError(t, e.LoadYAML([]byte(yaml), 1))
1596
@@ -1557,10 +1627,12 @@ groups:
1627
require.NotNil(t, created)
1628
assert.Equal(t, "total", created.Name)
1629
assert.True(t, created.Hidden)
1630
+ assert.True(t, created.Float)
1631
1632
update := findUpdateAction(plan)
1633
require.NotNil(t, update)
1634
require.Len(t, update.Values, 1)
1635
+ assert.True(t, update.Values[0].IsFloat)
1636
assert.Equal(t, "total", update.Values[0].Name)
1637
assert.Equal(t, float64(8), update.Values[0].Float64)
1638
}
src/go/plugin/framework/chartengine/runtime_metrics_test.go
+2
-2
@@ -191,8 +191,8 @@ func TestEngineRuntimeObservabilityScenarios(t *testing.T) {
191
require.Equal(t, "netdata_go_plugin_component_component_jobs", update.ChartID)
192
require.Len(t, update.Values, 1)
193
assert.Equal(t, "total", update.Values[0].Name)
194
- assert.True(t, update.Values[0].IsFloat)
195
- assert.Equal(t, float64(7), update.Values[0].Float64)
194
+ assert.False(t, update.Values[0].IsFloat)
195
+ assert.Equal(t, int64(7), update.Values[0].Int64)
196
},
197
},
198
"autogen planning uses runtime metric metadata": {
src/go/plugin/framework/charttpl/README.md
+1
@@ -94,6 +94,7 @@ When multiple series share the same instance identity labels, they appear as dim
94
| `options.multiplier` | int | no | DIM multiplier (`0` means default `1`) |
95
| `options.divisor` | int | no | DIM divisor (`0` means default `1`) |
96
| `options.hidden` | bool | no | Mark dimension hidden |
97
+| `options.float` | bool | no | Emit `type=float` and use `SETFLOAT` updates |
98
99
**Selector syntax**: A selector takes the form `metric_name` or `metric_name{label=value, ...}`.
100
The metric name prefix is required; label-only selectors like `{label=value}` are rejected.
src/go/plugin/framework/charttpl/config_schema.json
+3
@@ -269,6 +269,9 @@
269
},
270
"hidden": {
271
"type": "boolean"
272
+ },
273
+ "float": {
274
+ "type": "boolean"
275
}
276
}
277
}
src/go/plugin/framework/charttpl/spec.go
+2
-1
@@ -94,9 +94,10 @@ type Dimension struct {
94
Options *DimensionOptions `yaml:"options,omitempty" json:"options,omitempty"`
95
}
96
97
-// DimensionOptions controls emitted DIMENSION options.
97
+// DimensionOptions controls DIMENSION options and update emission mode.
98
type DimensionOptions struct {
99
Multiplier int `yaml:"multiplier,omitempty" json:"multiplier,omitempty"`
100
Divisor int `yaml:"divisor,omitempty" json:"divisor,omitempty"`
101
Hidden bool `yaml:"hidden,omitempty" json:"hidden,omitempty"`
102
+ Float bool `yaml:"float,omitempty" json:"float,omitempty"`
103
}
src/go/plugin/framework/charttpl/spec_test.go
+2
@@ -47,6 +47,7 @@ groups:
47
multiplier: -8
48
divisor: 1000
49
hidden: true
50
+ float: true
51
`,
52
assert: func(t *testing.T, spec *Spec) {
53
t.Helper()
@@ -58,6 +59,7 @@ groups:
59
assert.Equal(t, -8, spec.Groups[0].Charts[0].Dimensions[0].Options.Multiplier)
60
assert.Equal(t, 1000, spec.Groups[0].Charts[0].Dimensions[0].Options.Divisor)
61
assert.True(t, spec.Groups[0].Charts[0].Dimensions[0].Options.Hidden)
62
+ assert.True(t, spec.Groups[0].Charts[0].Dimensions[0].Options.Float)
63
require.NotNil(t, spec.Engine)
64
require.NotNil(t, spec.Engine.Selector)
65
assert.Equal(t, []string{`mysql_queries_total{db="main"}`}, spec.Engine.Selector.Allow)